feat: ProjectLocalConfig に concurrency を追加

This commit is contained in:
nrslib 2026-02-23 14:59:40 +09:00
parent 6a28929497
commit 95cd36037a
9 changed files with 35 additions and 1 deletions

View File

@ -123,6 +123,7 @@ piece: default # このプロジェクトの現在の piece
provider: claude # このプロジェクトの provider 上書き
auto_pr: true # worktree 実行後に PR を自動作成
verbose: false # 詳細出力モード
concurrency: 2 # このプロジェクトでの takt run 並列タスク数1-10
# provider 固有オプショングローバルを上書き、piece/movement で上書き可能)
# provider_options:
@ -145,6 +146,7 @@ verbose: false # 詳細出力モード
| `provider` | `"claude"` \| `"codex"` \| `"opencode"` \| `"mock"` | - | provider 上書き |
| `auto_pr` | boolean | - | worktree 実行後に PR を自動作成 |
| `verbose` | boolean | - | 詳細出力モード |
| `concurrency` | number (1-10) | `1`global 設定由来) | `takt run` の並列タスク数 |
| `provider_options` | object | - | provider 固有オプション |
| `provider_profiles` | object | - | provider 固有のパーミッションプロファイル |

View File

@ -123,6 +123,7 @@ piece: default # Current piece for this project
provider: claude # Override provider for this project
auto_pr: true # Auto-create PR after worktree execution
verbose: false # Verbose output mode
concurrency: 2 # Parallel task count for takt run in this project (1-10)
# Provider-specific options (overrides global, overridden by piece/movement)
# provider_options:
@ -145,6 +146,7 @@ verbose: false # Verbose output mode
| `provider` | `"claude"` \| `"codex"` \| `"opencode"` \| `"mock"` | - | Override provider |
| `auto_pr` | boolean | - | Auto-create PR after worktree execution |
| `verbose` | boolean | - | Verbose output mode |
| `concurrency` | number (1-10) | `1` (from global) | Parallel task count for `takt run` |
| `provider_options` | object | - | Provider-specific options |
| `provider_profiles` | object | - | Provider-specific permission profiles |

View File

@ -53,12 +53,14 @@ describe('config env overrides', () => {
it('should apply project env overrides from generated env names', () => {
process.env.TAKT_VERBOSE = 'true';
process.env.TAKT_CONCURRENCY = '3';
process.env.TAKT_ANALYTICS_EVENTS_PATH = '/tmp/project-analytics';
const raw: Record<string, unknown> = {};
applyProjectConfigEnvOverrides(raw);
expect(raw.verbose).toBe(true);
expect(raw.concurrency).toBe(3);
expect(raw.analytics).toEqual({
events_path: '/tmp/project-analytics',
});

View File

@ -1246,6 +1246,14 @@ describe('saveProjectConfig snake_case denormalization', () => {
expect((saved as Record<string, unknown>).base_branch).toBeUndefined();
});
it('should persist concurrency and reload correctly', () => {
saveProjectConfig(testDir, { piece: 'default', concurrency: 3 });
const saved = loadProjectConfig(testDir);
expect(saved.concurrency).toBe(3);
});
it('should not write camelCase keys to YAML file', () => {
saveProjectConfig(testDir, { piece: 'default', autoPr: true, draftPr: false, baseBranch: 'develop' });
@ -1261,7 +1269,7 @@ describe('saveProjectConfig snake_case denormalization', () => {
});
});
describe('resolveConfigValue autoPr/draftPr/baseBranch from project config', () => {
describe('resolveConfigValue autoPr/draftPr/baseBranch/concurrency from project config', () => {
let testDir: string;
let originalTaktConfigDir: string | undefined;
@ -1309,4 +1317,12 @@ describe('resolveConfigValue autoPr/draftPr/baseBranch from project config', ()
expect(resolveConfigValue(testDir, 'baseBranch')).toBe('main');
});
it('should resolve concurrency from project config', () => {
const projectConfigDir = getProjectConfigDir(testDir);
mkdirSync(projectConfigDir, { recursive: true });
writeFileSync(join(projectConfigDir, 'config.yaml'), 'concurrency: 3\n');
expect(resolveConfigValue(testDir, 'concurrency')).toBe(3);
});
});

View File

@ -36,6 +36,11 @@ describe('Schemas accept opencode provider', () => {
expect(result.provider).toBe('opencode');
});
it('should accept concurrency in ProjectConfigSchema', () => {
const result = ProjectConfigSchema.parse({ concurrency: 3 });
expect(result.concurrency).toBe(3);
});
it('should accept opencode in CustomAgentConfigSchema', () => {
const result = CustomAgentConfigSchema.parse({
name: 'test',

View File

@ -137,6 +137,8 @@ export interface ProjectConfig {
providerOptions?: MovementProviderOptions;
/** Provider-specific permission profiles */
providerProfiles?: ProviderPermissionProfiles;
/** Number of tasks to run concurrently in takt run (1-10) */
concurrency?: number;
/** Base branch to clone from (overrides global baseBranch) */
baseBranch?: string;
}

View File

@ -494,6 +494,8 @@ export const ProjectConfigSchema = z.object({
model: z.string().optional(),
provider_options: MovementProviderOptionsSchema,
provider_profiles: ProviderPermissionProfilesSchema,
/** Number of tasks to run concurrently in takt run (default from global: 1, max: 10) */
concurrency: z.number().int().min(1).max(10).optional(),
/** Base branch to clone from (overrides global base_branch) */
base_branch: z.string().optional(),
});

View File

@ -132,6 +132,7 @@ const PROJECT_ENV_SPECS: readonly EnvSpec[] = [
{ path: 'piece', type: 'string' },
{ path: 'provider', type: 'string' },
{ path: 'verbose', type: 'boolean' },
{ path: 'concurrency', type: 'number' },
{ path: 'analytics', type: 'json' },
{ path: 'analytics.enabled', type: 'boolean' },
{ path: 'analytics.events_path', type: 'string' },

View File

@ -20,6 +20,8 @@ export interface ProjectLocalConfig {
baseBranch?: string;
/** Verbose output mode */
verbose?: boolean;
/** Number of tasks to run concurrently in takt run (1-10) */
concurrency?: number;
/** Project-level analytics overrides */
analytics?: AnalyticsConfig;
/** Provider-specific options (overrides global, overridden by piece/movement) */