diff --git a/docs/configuration.ja.md b/docs/configuration.ja.md index 6c69337..2aed5b4 100644 --- a/docs/configuration.ja.md +++ b/docs/configuration.ja.md @@ -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 固有のパーミッションプロファイル | diff --git a/docs/configuration.md b/docs/configuration.md index 7c72576..22eda07 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 | diff --git a/src/__tests__/config-env-overrides.test.ts b/src/__tests__/config-env-overrides.test.ts index f92883a..69d7500 100644 --- a/src/__tests__/config-env-overrides.test.ts +++ b/src/__tests__/config-env-overrides.test.ts @@ -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 = {}; applyProjectConfigEnvOverrides(raw); expect(raw.verbose).toBe(true); + expect(raw.concurrency).toBe(3); expect(raw.analytics).toEqual({ events_path: '/tmp/project-analytics', }); diff --git a/src/__tests__/config.test.ts b/src/__tests__/config.test.ts index b1f54cc..116849a 100644 --- a/src/__tests__/config.test.ts +++ b/src/__tests__/config.test.ts @@ -1246,6 +1246,14 @@ describe('saveProjectConfig snake_case denormalization', () => { expect((saved as Record).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); + }); }); diff --git a/src/__tests__/opencode-config.test.ts b/src/__tests__/opencode-config.test.ts index 31582ab..6be44a4 100644 --- a/src/__tests__/opencode-config.test.ts +++ b/src/__tests__/opencode-config.test.ts @@ -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', diff --git a/src/core/models/persisted-global-config.ts b/src/core/models/persisted-global-config.ts index 972114e..699f498 100644 --- a/src/core/models/persisted-global-config.ts +++ b/src/core/models/persisted-global-config.ts @@ -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; } diff --git a/src/core/models/schemas.ts b/src/core/models/schemas.ts index 092cede..e04f782 100644 --- a/src/core/models/schemas.ts +++ b/src/core/models/schemas.ts @@ -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(), }); diff --git a/src/infra/config/env/config-env-overrides.ts b/src/infra/config/env/config-env-overrides.ts index ae2b1ce..99bb8ca 100644 --- a/src/infra/config/env/config-env-overrides.ts +++ b/src/infra/config/env/config-env-overrides.ts @@ -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' }, diff --git a/src/infra/config/types.ts b/src/infra/config/types.ts index 7dbd7f6..3a2bc51 100644 --- a/src/infra/config/types.ts +++ b/src/infra/config/types.ts @@ -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) */