Remove worktree prompt from execute action (#414)
* fix: remove execute worktree prompt and deprecate create-worktree option * test(e2e): align specs with removed --create-worktree * fix: remove execute worktree leftovers and align docs/tests --------- Co-authored-by: Takashi Morikubo <azurite0107@gmail.com>
This commit is contained in:
parent
769bd98724
commit
50935a1244
@ -67,10 +67,9 @@ TAKT (TAKT Agent Koordination Topology) is a multi-agent orchestration system fo
|
||||
| `--pr <number>` | PR number to fetch review comments and fix |
|
||||
| `-w, --piece <name or path>` | Piece name or path to piece YAML file |
|
||||
| `-b, --branch <name>` | Branch name (auto-generated if omitted) |
|
||||
| `--auto-pr` | Create PR after execution (interactive: skip confirmation, pipeline: enable PR) |
|
||||
| `--auto-pr` | Create PR after execution (pipeline mode only) |
|
||||
| `--skip-git` | Skip branch creation, commit, and push (pipeline mode, piece-only) |
|
||||
| `--repo <owner/repo>` | Repository for PR creation |
|
||||
| `--create-worktree <yes\|no>` | Skip worktree confirmation prompt |
|
||||
| `-q, --quiet` | Minimal output mode: suppress AI output (for CI) |
|
||||
| `--provider <name>` | Override agent provider (claude\|codex\|opencode\|mock) |
|
||||
| `--model <name>` | Override agent model |
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
| `--draft` | PR をドラフトとして作成(`--auto-pr` または `auto_pr` 設定が必要) |
|
||||
| `--skip-git` | ブランチ作成、コミット、プッシュをスキップ(pipeline モード、piece のみ実行) |
|
||||
| `--repo <owner/repo>` | リポジトリを指定(PR 作成用) |
|
||||
| `--create-worktree <yes\|no>` | worktree 確認プロンプトをスキップ |
|
||||
| `-q, --quiet` | 最小出力モード: AI 出力を抑制(CI 向け) |
|
||||
| `--provider <name>` | エージェント provider を上書き(claude\|codex\|opencode\|cursor\|copilot\|mock) |
|
||||
| `--model <name>` | エージェントモデルを上書き |
|
||||
@ -44,7 +43,7 @@ takt hello
|
||||
2. インタラクティブモードを選択(assistant / persona / quiet / passthrough)
|
||||
3. AI との会話でタスク内容を精緻化
|
||||
4. `/go` でタスク指示を確定(`/go 追加の指示` のように追記も可能)、または `/play <task>` でタスクを即座に実行
|
||||
5. 実行(worktree 作成、piece 実行、PR 作成)
|
||||
5. 実行(piece 実行、PR 作成)
|
||||
|
||||
### インタラクティブモードの種類
|
||||
|
||||
@ -89,8 +88,6 @@ Requirements:
|
||||
|
||||
Proceed with these task instructions? (Y/n) y
|
||||
|
||||
? Create worktree? (Y/n) y
|
||||
|
||||
[Piece の実行を開始...]
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,6 @@ This document provides a complete reference for all TAKT CLI commands and option
|
||||
| `--draft` | Create PR as draft (requires `--auto-pr` or `auto_pr` config) |
|
||||
| `--skip-git` | Skip branch creation, commit, and push (pipeline mode, piece-only) |
|
||||
| `--repo <owner/repo>` | Specify repository (for PR creation) |
|
||||
| `--create-worktree <yes\|no>` | Skip worktree confirmation prompt |
|
||||
| `-q, --quiet` | Minimal output mode: suppress AI output (for CI) |
|
||||
| `--provider <name>` | Override agent provider (claude\|codex\|opencode\|cursor\|copilot\|mock) |
|
||||
| `--model <name>` | Override agent model |
|
||||
@ -44,7 +43,7 @@ takt hello
|
||||
2. Select interactive mode (assistant / persona / quiet / passthrough)
|
||||
3. Refine task content through conversation with AI
|
||||
4. Finalize task instructions with `/go` (you can also add additional instructions like `/go additional instructions`), or use `/play <task>` to execute a task immediately
|
||||
5. Execute (create worktree, run piece, create PR)
|
||||
5. Execute (run piece, create PR)
|
||||
|
||||
### Interactive Mode Variants
|
||||
|
||||
@ -89,8 +88,6 @@ Requirements:
|
||||
|
||||
Proceed with these task instructions? (Y/n) y
|
||||
|
||||
? Create worktree? (Y/n) y
|
||||
|
||||
[Piece execution starts...]
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
||||
|
||||
1. **CLI Layer** - ユーザー入力の受付
|
||||
2. **Interactive Layer** - タスクの対話的な明確化
|
||||
3. **Execution Orchestration Layer** - ピース選択とworktree管理
|
||||
3. **Execution Orchestration Layer** - ピース選択と実行開始
|
||||
4. **Piece Execution Layer** - セッション管理とイベント処理
|
||||
5. **Engine Layer** - ステートマシンによるステップ実行
|
||||
6. **Instruction Building Layer** - プロンプト生成
|
||||
@ -72,13 +72,6 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
||||
│ │ pieceIdentifier: string │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ confirmAndCreateWorktree() │ │
|
||||
│ │ - AI branchname generation │ │
|
||||
│ │ - createSharedClone() │ │
|
||||
│ └─────────┬────────────────────────┘ │
|
||||
│ │ { execCwd, isWorktree, branch } │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ executeTask() │ │
|
||||
│ │ - task: string │ │
|
||||
│ │ - cwd: string (実行ディレクトリ) │ │
|
||||
@ -362,7 +355,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
||||
|
||||
### 3. Execution Orchestration Layer (`src/features/tasks/execute/selectAndExecute.ts`)
|
||||
|
||||
**役割**: ピース選択とworktree管理
|
||||
**役割**: ピース選択と実行オーケストレーション
|
||||
|
||||
**主要な処理**:
|
||||
|
||||
@ -372,26 +365,18 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
||||
- 名前形式 → バリデーション
|
||||
- オーバーライドなし → インタラクティブ選択 (`selectPiece()`)
|
||||
|
||||
2. **Worktree作成** (`confirmAndCreateWorktree()`):
|
||||
- ユーザー確認 (または `--create-worktree` フラグ)
|
||||
- ブランチ名生成 (`summarizeTaskName()` - AIでタスクから英語スラグ生成)
|
||||
- `createSharedClone()`: git clone --shared で軽量クローン作成
|
||||
|
||||
3. **タスク実行開始** (`selectAndExecuteTask()`):
|
||||
2. **タスク実行開始** (`selectAndExecuteTask()`):
|
||||
- `executeTask()` を呼び出し
|
||||
- 成功時: Auto-commit & Push
|
||||
- PR作成 (オプション)
|
||||
- 成功/失敗を `tasks.yaml` に記録(`skipTaskList` 設定時を除く)
|
||||
|
||||
**データ入力**:
|
||||
- `task: string`
|
||||
- `options?: SelectAndExecuteOptions`:
|
||||
- `piece?: string`
|
||||
- `createWorktree?: boolean`
|
||||
- `autoPr?: boolean`
|
||||
- `skipTaskList?: boolean`
|
||||
- `agentOverrides?: TaskExecutionOptions`
|
||||
|
||||
**データ出力**:
|
||||
- `{ execCwd, isWorktree, branch }`
|
||||
- タスク実行成功/失敗
|
||||
|
||||
---
|
||||
@ -763,15 +748,9 @@ async call(
|
||||
- `--piece` フラグ → 検証
|
||||
- なし → インタラクティブ選択 (`selectPiece()`)
|
||||
|
||||
**Worktree作成** (オプション):
|
||||
- `confirmAndCreateWorktree()`:
|
||||
- ユーザー確認または `--create-worktree` フラグ
|
||||
- `summarizeTaskName()`: タスク → 英語スラグ (AI呼び出し)
|
||||
- `createSharedClone()`: git clone --shared
|
||||
|
||||
**データ**:
|
||||
- `pieceIdentifier: string`
|
||||
- `{ execCwd, isWorktree, branch }`
|
||||
- `execCwd: string` (実行ディレクトリ)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -48,10 +48,10 @@ E2Eテストを追加・変更した場合は、このドキュメントも更
|
||||
- `README.md` に行が追加されることを確認する。
|
||||
- 実行後にタスクが `tasks.yaml` で `completed` ステータスになることを確認する。
|
||||
- Worktree/Clone isolation(`e2e/specs/worktree.e2e.ts`)
|
||||
- 目的: `--create-worktree yes` 指定で隔離環境に実行されることを確認。
|
||||
- 目的: `worktree: true` タスクが隔離環境に実行されることを確認。
|
||||
- LLM: 条件付き(`TAKT_E2E_PROVIDER` が `claude` / `codex` の場合に呼び出す)
|
||||
- 手順(ユーザー行動/コマンド):
|
||||
- `takt --task 'Add a line "worktree test" to README.md' --piece e2e/fixtures/pieces/simple.yaml --create-worktree yes` を実行する。
|
||||
- `.takt/tasks.yaml` に `worktree: true` のタスクを追加して `takt run` を実行する。
|
||||
- コマンドが成功終了することを確認する。
|
||||
- Pipeline mode(`e2e/specs/pipeline.e2e.ts`)
|
||||
- 目的: ブランチ作成→タスク実行→コミット→push→PR作成の一連フローを確認。
|
||||
@ -73,7 +73,7 @@ E2Eテストを追加・変更した場合は、このドキュメントも更
|
||||
- 目的: `--task` の直接実行が、プロンプトなしで完了することを確認。
|
||||
- LLM: 呼び出さない(`--provider mock` 固定)
|
||||
- 手順(ユーザー行動/コマンド):
|
||||
- `takt --task 'Create a file called noop.txt' --piece e2e/fixtures/pieces/mock-single-step.yaml --create-worktree no --provider mock` を実行する。
|
||||
- `takt --task 'Create a file called noop.txt' --piece e2e/fixtures/pieces/mock-single-step.yaml --provider mock` を実行する。
|
||||
- `TAKT_MOCK_SCENARIO=e2e/fixtures/scenarios/execute-done.json` を設定する。
|
||||
- 出力に `Piece completed` が含まれることを確認する。
|
||||
- Pipeline mode with --skip-git(`e2e/specs/pipeline-skip-git.e2e.ts`)
|
||||
@ -87,7 +87,7 @@ E2Eテストを追加・変更した場合は、このドキュメントも更
|
||||
- 目的: reportフェーズとjudgeフェーズを通ることを確認(mockシナリオ)。
|
||||
- LLM: 呼び出さない(`--provider mock` 固定)
|
||||
- 手順(ユーザー行動/コマンド):
|
||||
- `takt --task 'Create a short report and finish' --piece e2e/fixtures/pieces/report-judge.yaml --create-worktree no --provider mock` を実行する。
|
||||
- `takt --task 'Create a short report and finish' --piece e2e/fixtures/pieces/report-judge.yaml --provider mock` を実行する。
|
||||
- `TAKT_MOCK_SCENARIO=e2e/fixtures/scenarios/report-judge.json` を設定する。
|
||||
- 出力に `Piece completed` が含まれることを確認する。
|
||||
- Add task(`e2e/specs/add.e2e.ts`)
|
||||
@ -135,7 +135,7 @@ E2Eテストを追加・変更した場合は、このドキュメントも更
|
||||
- LLM: 条件付き(`TAKT_E2E_PROVIDER` が `claude` / `codex` / `opencode` の場合に実行、未指定時は skip)
|
||||
- 手順(ユーザー行動/コマンド):
|
||||
- E2E用 `config.yaml` に `runtime.prepare: [gradle, node]` を設定する。
|
||||
- `takt --task '<gradle/npm を実行する指示>' --piece e2e/fixtures/pieces/simple.yaml --create-worktree no` を実行する。
|
||||
- `takt --task '<gradle/npm を実行する指示>' --piece e2e/fixtures/pieces/simple.yaml` を実行する。
|
||||
- 正例では、作業リポジトリに `.runtime/env.sh` と `.runtime/{tmp,cache,config,state,gradle,npm}` が作成されていることを確認する。
|
||||
- 負例(`runtime.prepare` 未設定)では、`GRADLE_USER_HOME is required` と npm キャッシュ書き込み失敗が出力され、`.runtime/env.sh` が生成されないことを確認する。
|
||||
- List tasks non-interactive(`e2e/specs/list-non-interactive.e2e.ts`)
|
||||
|
||||
@ -77,74 +77,21 @@ describe('E2E: Config priority (piece / autoPr)', () => {
|
||||
|
||||
it('should default auto_pr to true when unset in config/env', () => {
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
||||
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
||||
|
||||
runTakt({
|
||||
args: [
|
||||
'--task', 'Auto PR default behavior',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'yes',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
env: {
|
||||
...isolatedEnv.env,
|
||||
TAKT_MOCK_SCENARIO: scenarioPath,
|
||||
},
|
||||
timeout: 240_000,
|
||||
});
|
||||
|
||||
const task = readFirstTask(testRepo.path);
|
||||
expect(task['auto_pr']).toBe(true);
|
||||
}, 240_000);
|
||||
|
||||
it('should use auto_pr from config when set', () => {
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
||||
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
||||
updateIsolatedConfig(isolatedEnv.taktDir, { auto_pr: false });
|
||||
|
||||
const result = runTakt({
|
||||
args: [
|
||||
'--task', 'Auto PR from config',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'yes',
|
||||
'--provider', 'mock',
|
||||
'add',
|
||||
'Auto PR default behavior',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
env: {
|
||||
...isolatedEnv.env,
|
||||
TAKT_MOCK_SCENARIO: scenarioPath,
|
||||
},
|
||||
env: isolatedEnv.env,
|
||||
timeout: 240_000,
|
||||
});
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
const task = readFirstTask(testRepo.path);
|
||||
expect(task['auto_pr']).toBe(false);
|
||||
}, 240_000);
|
||||
|
||||
it('should prioritize env auto_pr over config', () => {
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
||||
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
||||
updateIsolatedConfig(isolatedEnv.taktDir, { auto_pr: false });
|
||||
|
||||
runTakt({
|
||||
args: [
|
||||
'--task', 'Auto PR from env override',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'yes',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
env: {
|
||||
...isolatedEnv.env,
|
||||
TAKT_AUTO_PR: 'true',
|
||||
TAKT_MOCK_SCENARIO: scenarioPath,
|
||||
},
|
||||
timeout: 240_000,
|
||||
});
|
||||
|
||||
const task = readFirstTask(testRepo.path);
|
||||
expect(task['auto_pr']).toBe(true);
|
||||
}, 240_000);
|
||||
|
||||
});
|
||||
|
||||
@ -39,7 +39,6 @@ describe('E2E: Cycle detection via loop_monitors (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test cycle detection abort',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -68,7 +67,6 @@ describe('E2E: Cycle detection via loop_monitors (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test cycle detection pass',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -9,7 +9,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Direct task execution (--task --create-worktree no)', () => {
|
||||
describe('E2E: Direct task execution (--task)', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let testRepo: TestRepo;
|
||||
|
||||
@ -39,7 +39,6 @@ describe('E2E: Direct task execution (--task --create-worktree no)', () => {
|
||||
args: [
|
||||
'--task', 'Create a file called noop.txt',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
|
||||
@ -31,7 +31,6 @@ describe('E2E: Error handling edge cases (mock)', () => {
|
||||
args: [
|
||||
'--task', 'test',
|
||||
'--piece', '/nonexistent/path/to/piece.yaml',
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -53,7 +52,6 @@ describe('E2E: Error handling edge cases (mock)', () => {
|
||||
args: [
|
||||
'--task', 'test',
|
||||
'--piece', 'nonexistent-piece-name-xyz',
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -90,8 +88,8 @@ describe('E2E: Error handling edge cases (mock)', () => {
|
||||
expect(combined).toMatch(/task|issue|required/i);
|
||||
}, 240_000);
|
||||
|
||||
it('should error when --create-worktree receives an invalid value', () => {
|
||||
// Given: invalid worktree value
|
||||
it('should error when deprecated --create-worktree option is used', () => {
|
||||
// Given: deprecated option value
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-single-step.yaml');
|
||||
|
||||
// When: running with invalid worktree option
|
||||
@ -107,10 +105,10 @@ describe('E2E: Error handling edge cases (mock)', () => {
|
||||
timeout: 240_000,
|
||||
});
|
||||
|
||||
// Then: exits with error or warning about invalid value
|
||||
// Then: exits with migration error
|
||||
const combined = result.stdout + result.stderr;
|
||||
const hasError = result.exitCode !== 0 || combined.match(/invalid|error|must be/i);
|
||||
expect(hasError).toBeTruthy();
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(combined).toContain('--create-worktree has been removed');
|
||||
}, 240_000);
|
||||
|
||||
it('should error when piece file contains invalid YAML', () => {
|
||||
@ -122,7 +120,6 @@ describe('E2E: Error handling edge cases (mock)', () => {
|
||||
args: [
|
||||
'--task', 'test',
|
||||
'--piece', brokenPiecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -31,7 +31,6 @@ describe('E2E: --model option override (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test model override direct',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
'--model', 'mock-model-override',
|
||||
],
|
||||
|
||||
@ -40,7 +40,6 @@ describe('E2E: Multi-step with parallel movements (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Implement a feature',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
@ -62,7 +61,6 @@ describe('E2E: Multi-step with parallel movements (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Implement a feature with issues',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
|
||||
@ -32,7 +32,6 @@ describe('E2E: Sequential multi-step session log transitions (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test sequential transitions',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -33,7 +33,6 @@ describe('E2E: Piece error handling (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test error status abort',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -60,7 +59,6 @@ describe('E2E: Piece error handling (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test max movements',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -87,7 +85,6 @@ describe('E2E: Piece error handling (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test previous response passing',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -54,7 +54,7 @@ function runTaskWithPiece(args: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): ReturnType<typeof runTakt> {
|
||||
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/execute-done.json');
|
||||
const baseArgs = ['--task', 'Create a file called noop.txt', '--create-worktree', 'no', '--provider', 'mock'];
|
||||
const baseArgs = ['--task', 'Create a file called noop.txt', '--provider', 'mock'];
|
||||
const fullArgs = args.piece ? [...baseArgs, '--piece', args.piece] : baseArgs;
|
||||
return runTakt({
|
||||
args: fullArgs,
|
||||
|
||||
@ -41,7 +41,6 @@ describe('E2E: Provider error handling (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test provider override',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -67,7 +66,6 @@ describe('E2E: Provider error handling (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test scenario exhaustion',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -93,7 +91,6 @@ describe('E2E: Provider error handling (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test bad scenario',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -33,7 +33,6 @@ describe('E2E: Quiet mode (--quiet)', () => {
|
||||
args: [
|
||||
'--task', 'Test quiet mode',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
'--quiet',
|
||||
],
|
||||
|
||||
@ -39,7 +39,6 @@ describe('E2E: Report file output (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test report output',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -39,7 +39,6 @@ describe('E2E: Report + Judge phases (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Create a short report and finish',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: testRepo.path,
|
||||
|
||||
@ -113,7 +113,6 @@ describe('E2E: runtime.prepare with provider', () => {
|
||||
'If both commands succeed, respond exactly with: Task completed',
|
||||
].join(' '),
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
],
|
||||
cwd: repo.path,
|
||||
env: isolatedEnv.env,
|
||||
@ -151,7 +150,6 @@ describe('E2E: runtime.prepare with provider', () => {
|
||||
'If both commands succeed, respond exactly with: Task completed',
|
||||
].join(' '),
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
],
|
||||
cwd: repo.path,
|
||||
env: isolatedEnv.env,
|
||||
|
||||
@ -32,7 +32,6 @@ describe('E2E: Session NDJSON log output (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test session log success',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
@ -59,7 +58,6 @@ describe('E2E: Session NDJSON log output (mock)', () => {
|
||||
args: [
|
||||
'--task', 'Test session log abort',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
'--provider', 'mock',
|
||||
],
|
||||
cwd: repo.path,
|
||||
|
||||
@ -52,7 +52,6 @@ describe('E2E: Structured output rule matching', () => {
|
||||
args: [
|
||||
'--task', 'Say hello',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
],
|
||||
cwd: repo.path,
|
||||
env: isolatedEnv.env,
|
||||
|
||||
@ -39,8 +39,6 @@ describe('E2E: Team leader refill threshold', () => {
|
||||
'Create exactly seven files: rt-1.txt, rt-2.txt, rt-3.txt, rt-4.txt, rt-5.txt, rt-6.txt, rt-7.txt. Each file must contain its own filename as content. Each part must create exactly one file.',
|
||||
'--piece',
|
||||
piecePath,
|
||||
'--create-worktree',
|
||||
'no',
|
||||
],
|
||||
cwd: repo.path,
|
||||
env: {
|
||||
|
||||
@ -38,8 +38,6 @@ describe('E2E: Team leader worker-pool dynamic scheduling', () => {
|
||||
'Create exactly five files: wp-1.txt, wp-2.txt, wp-3.txt, wp-4.txt, wp-5.txt. Each file must contain its own filename as content. Each part must create exactly one file, and you must complete all five files.',
|
||||
'--piece',
|
||||
piecePath,
|
||||
'--create-worktree',
|
||||
'no',
|
||||
],
|
||||
cwd: repo.path,
|
||||
env: isolatedEnv.env,
|
||||
|
||||
@ -45,7 +45,6 @@ describe('E2E: Team leader movement', () => {
|
||||
args: [
|
||||
'--task', 'Create two files: hello-en.txt containing "Hello World" and hello-ja.txt containing "こんにちは世界"',
|
||||
'--piece', piecePath,
|
||||
'--create-worktree', 'no',
|
||||
],
|
||||
cwd: repo.path,
|
||||
env: isolatedEnv.env,
|
||||
|
||||
@ -9,7 +9,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// E2E更新時は docs/testing/e2e.md も更新すること
|
||||
describe('E2E: Worktree/Clone isolation (--create-worktree yes)', () => {
|
||||
describe('E2E: Removed --create-worktree option', () => {
|
||||
let isolatedEnv: IsolatedEnv;
|
||||
let testRepo: TestRepo;
|
||||
|
||||
@ -31,7 +31,7 @@ describe('E2E: Worktree/Clone isolation (--create-worktree yes)', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should execute task in an isolated worktree/clone', () => {
|
||||
it('should fail fast with migration guidance', () => {
|
||||
const piecePath = resolve(__dirname, '../fixtures/pieces/simple.yaml');
|
||||
|
||||
const result = runTakt({
|
||||
@ -45,7 +45,8 @@ describe('E2E: Worktree/Clone isolation (--create-worktree yes)', () => {
|
||||
timeout: 240_000,
|
||||
});
|
||||
|
||||
// Task should succeed
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
const combined = result.stdout + result.stderr;
|
||||
expect(combined).toContain('--create-worktree has been removed');
|
||||
}, 240_000);
|
||||
});
|
||||
|
||||
@ -110,7 +110,6 @@ vi.mock('../app/cli/program.js', () => {
|
||||
|
||||
vi.mock('../app/cli/helpers.js', () => ({
|
||||
resolveAgentOverrides: vi.fn(),
|
||||
parseCreateWorktreeOption: vi.fn(),
|
||||
isDirectTask: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
@ -120,7 +119,7 @@ import { interactiveMode } from '../features/interactive/index.js';
|
||||
import { resolveConfigValues, loadPersonaSessions } from '../infra/config/index.js';
|
||||
import { isDirectTask } from '../app/cli/helpers.js';
|
||||
import { executeDefaultAction } from '../app/cli/routing.js';
|
||||
import { info } from '../shared/ui/index.js';
|
||||
import { info, error } from '../shared/ui/index.js';
|
||||
import type { Issue } from '../infra/git/index.js';
|
||||
|
||||
const mockFormatIssueAsTask = vi.mocked(formatIssueAsTask);
|
||||
@ -133,6 +132,7 @@ const mockLoadPersonaSessions = vi.mocked(loadPersonaSessions);
|
||||
const mockResolveConfigValues = vi.mocked(resolveConfigValues);
|
||||
const mockIsDirectTask = vi.mocked(isDirectTask);
|
||||
const mockInfo = vi.mocked(info);
|
||||
const mockError = vi.mocked(error);
|
||||
const mockTaskRunnerListAllTaskItems = vi.mocked(mockListAllTaskItems);
|
||||
|
||||
function createMockIssue(number: number): Issue {
|
||||
@ -161,6 +161,43 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
describe('Issue resolution in routing', () => {
|
||||
it('should show error and exit when --auto-pr/--draft are used outside pipeline mode', async () => {
|
||||
mockOpts.autoPr = true;
|
||||
mockOpts.draft = true;
|
||||
|
||||
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error('process.exit called');
|
||||
});
|
||||
|
||||
await expect(executeDefaultAction()).rejects.toThrow('process.exit called');
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith('--auto-pr/--draft are supported only in --pipeline mode');
|
||||
expect(mockExit).toHaveBeenCalledWith(1);
|
||||
expect(mockInteractiveMode).not.toHaveBeenCalled();
|
||||
expect(mockSelectAndExecuteTask).not.toHaveBeenCalled();
|
||||
|
||||
mockExit.mockRestore();
|
||||
});
|
||||
|
||||
it('should show migration error and exit when deprecated --create-worktree is used', async () => {
|
||||
mockOpts.createWorktree = 'yes';
|
||||
|
||||
const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
|
||||
throw new Error('process.exit called');
|
||||
});
|
||||
|
||||
await expect(executeDefaultAction()).rejects.toThrow('process.exit called');
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
'--create-worktree has been removed. execute now always runs in-place. Use "takt add" (save_task) + "takt run" for worktree-based execution.'
|
||||
);
|
||||
expect(mockExit).toHaveBeenCalledWith(1);
|
||||
expect(mockInteractiveMode).not.toHaveBeenCalled();
|
||||
expect(mockSelectAndExecuteTask).not.toHaveBeenCalled();
|
||||
|
||||
mockExit.mockRestore();
|
||||
});
|
||||
|
||||
describe('--issue option', () => {
|
||||
it('should resolve issue and pass to interactive mode when --issue is specified', async () => {
|
||||
// Given
|
||||
|
||||
@ -112,7 +112,6 @@ vi.mock('../app/cli/program.js', () => {
|
||||
|
||||
vi.mock('../app/cli/helpers.js', () => ({
|
||||
resolveAgentOverrides: vi.fn(),
|
||||
parseCreateWorktreeOption: vi.fn(),
|
||||
isDirectTask: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Tests for resolveAutoPr default behavior in selectAndExecuteTask
|
||||
* Tests for selectAndExecuteTask behavior in execute path
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
@ -25,10 +25,6 @@ const {
|
||||
mockResolvePieceConfigValue: vi.fn((_: string, key: string) => (key === 'autoPr' ? undefined : 'default')),
|
||||
}));
|
||||
|
||||
vi.mock('../shared/prompt/index.js', () => ({
|
||||
confirm: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/config/index.js', () => ({
|
||||
resolvePieceConfigValue: (...args: unknown[]) => mockResolvePieceConfigValue(...args),
|
||||
listPieces: vi.fn(() => ['default']),
|
||||
@ -86,17 +82,13 @@ vi.mock('../features/pieceSelection/index.js', () => ({
|
||||
selectPiece: vi.fn(),
|
||||
}));
|
||||
|
||||
import { confirm } from '../shared/prompt/index.js';
|
||||
import { loadPieceByIdentifier } from '../infra/config/index.js';
|
||||
import { createSharedClone, autoCommitAndPush, summarizeTaskName } from '../infra/task/index.js';
|
||||
import { autoCommitAndPush } from '../infra/task/index.js';
|
||||
import { selectPiece } from '../features/pieceSelection/index.js';
|
||||
import { selectAndExecuteTask, determinePiece } from '../features/tasks/execute/selectAndExecute.js';
|
||||
|
||||
const mockConfirm = vi.mocked(confirm);
|
||||
const mockLoadPieceByIdentifier = vi.mocked(loadPieceByIdentifier);
|
||||
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
||||
const mockAutoCommitAndPush = vi.mocked(autoCommitAndPush);
|
||||
const mockSummarizeTaskName = vi.mocked(summarizeTaskName);
|
||||
const mockSelectPiece = vi.mocked(selectPiece);
|
||||
|
||||
beforeEach(() => {
|
||||
@ -104,74 +96,14 @@ beforeEach(() => {
|
||||
mockExecuteTask.mockResolvedValue(true);
|
||||
});
|
||||
|
||||
describe('resolveAutoPr default in selectAndExecuteTask', () => {
|
||||
it('should call auto-PR confirm with default true when no CLI option or config', async () => {
|
||||
// Given: worktree is enabled via override, no autoPr option, no config autoPr
|
||||
mockConfirm.mockResolvedValue(true);
|
||||
mockSummarizeTaskName.mockResolvedValue('test-task');
|
||||
mockCreateSharedClone.mockReturnValue({
|
||||
path: '/project/../clone',
|
||||
branch: 'takt/test-task',
|
||||
});
|
||||
|
||||
mockAutoCommitAndPush.mockReturnValue({
|
||||
success: false,
|
||||
message: 'no changes',
|
||||
});
|
||||
|
||||
// When
|
||||
describe('selectAndExecuteTask (execute path)', () => {
|
||||
it('should execute in-place without worktree setup or PR prompts', async () => {
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
createWorktree: true,
|
||||
});
|
||||
|
||||
const autoPrCall = mockConfirm.mock.calls.find((call) => call[0] === 'Create pull request?');
|
||||
expect(autoPrCall).toBeDefined();
|
||||
expect(autoPrCall![1]).toBe(true);
|
||||
});
|
||||
|
||||
it('shouldCreatePr=true の場合、"Create as draft?" プロンプトが表示される', async () => {
|
||||
// confirm はすべての呼び出しに対して true を返す(autoPr=true → draftPr prompt)
|
||||
mockConfirm.mockResolvedValue(true);
|
||||
mockSummarizeTaskName.mockResolvedValue('test-task');
|
||||
mockCreateSharedClone.mockReturnValue({
|
||||
path: '/project/../clone',
|
||||
branch: 'takt/test-task',
|
||||
});
|
||||
mockAutoCommitAndPush.mockReturnValue({
|
||||
success: false,
|
||||
message: 'no changes',
|
||||
});
|
||||
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
createWorktree: true,
|
||||
});
|
||||
|
||||
const draftPrCall = mockConfirm.mock.calls.find((call) => call[0] === 'Create as draft?');
|
||||
expect(draftPrCall).toBeDefined();
|
||||
expect(draftPrCall![1]).toBe(true);
|
||||
});
|
||||
|
||||
it('shouldCreatePr=false の場合、"Create as draft?" プロンプトは表示されない', async () => {
|
||||
mockConfirm.mockResolvedValue(false); // autoPr=false → draft prompt skipped
|
||||
mockSummarizeTaskName.mockResolvedValue('test-task');
|
||||
mockCreateSharedClone.mockReturnValue({
|
||||
path: '/project/../clone',
|
||||
branch: 'takt/test-task',
|
||||
});
|
||||
mockAutoCommitAndPush.mockReturnValue({
|
||||
success: false,
|
||||
message: 'no changes',
|
||||
});
|
||||
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
createWorktree: true,
|
||||
});
|
||||
|
||||
const draftPrCall = mockConfirm.mock.calls.find((call) => call[0] === 'Create as draft?');
|
||||
expect(draftPrCall).toBeUndefined();
|
||||
expect(mockAutoCommitAndPush).not.toHaveBeenCalled();
|
||||
expect(mockAddTask).toHaveBeenCalledWith('test task', { piece: 'default' });
|
||||
});
|
||||
|
||||
it('should call selectPiece when no override is provided', async () => {
|
||||
@ -192,17 +124,10 @@ describe('resolveAutoPr default in selectAndExecuteTask', () => {
|
||||
});
|
||||
|
||||
it('should fail task record when executeTask throws', async () => {
|
||||
mockConfirm.mockResolvedValue(true);
|
||||
mockSummarizeTaskName.mockResolvedValue('test-task');
|
||||
mockCreateSharedClone.mockReturnValue({
|
||||
path: '/project/../clone',
|
||||
branch: 'takt/test-task',
|
||||
});
|
||||
mockExecuteTask.mockRejectedValue(new Error('boom'));
|
||||
|
||||
await expect(selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
createWorktree: true,
|
||||
})).rejects.toThrow('boom');
|
||||
|
||||
expect(mockAddTask).toHaveBeenCalledTimes(1);
|
||||
@ -211,38 +136,18 @@ describe('resolveAutoPr default in selectAndExecuteTask', () => {
|
||||
});
|
||||
|
||||
it('should record task and complete when executeTask returns true', async () => {
|
||||
mockConfirm.mockResolvedValue(true);
|
||||
mockSummarizeTaskName.mockResolvedValue('test-task');
|
||||
mockCreateSharedClone.mockReturnValue({
|
||||
path: '/project/../clone',
|
||||
branch: 'takt/test-task',
|
||||
});
|
||||
mockExecuteTask.mockResolvedValue(true);
|
||||
|
||||
await selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
createWorktree: true,
|
||||
});
|
||||
|
||||
expect(mockAddTask).toHaveBeenCalledWith('test task', expect.objectContaining({
|
||||
piece: 'default',
|
||||
worktree: true,
|
||||
branch: 'takt/test-task',
|
||||
worktree_path: '/project/../clone',
|
||||
auto_pr: true,
|
||||
draft_pr: true,
|
||||
}));
|
||||
expect(mockAddTask).toHaveBeenCalledWith('test task', { piece: 'default' });
|
||||
expect(mockCompleteTask).toHaveBeenCalledTimes(1);
|
||||
expect(mockFailTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should record task and fail when executeTask returns false', async () => {
|
||||
mockConfirm.mockResolvedValue(false);
|
||||
mockSummarizeTaskName.mockResolvedValue('test-task');
|
||||
mockCreateSharedClone.mockReturnValue({
|
||||
path: '/project/../clone',
|
||||
branch: 'takt/test-task',
|
||||
});
|
||||
mockExecuteTask.mockResolvedValue(false);
|
||||
|
||||
const processExitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {
|
||||
@ -251,16 +156,9 @@ describe('resolveAutoPr default in selectAndExecuteTask', () => {
|
||||
|
||||
await expect(selectAndExecuteTask('/project', 'test task', {
|
||||
piece: 'default',
|
||||
createWorktree: true,
|
||||
})).rejects.toThrow('process exit');
|
||||
|
||||
expect(mockAddTask).toHaveBeenCalledWith('test task', expect.objectContaining({
|
||||
piece: 'default',
|
||||
worktree: true,
|
||||
branch: 'takt/test-task',
|
||||
worktree_path: '/project/../clone',
|
||||
auto_pr: false,
|
||||
}));
|
||||
expect(mockAddTask).toHaveBeenCalledWith('test task', { piece: 'default' });
|
||||
expect(mockFailTask).toHaveBeenCalledTimes(1);
|
||||
expect(mockCompleteTask).not.toHaveBeenCalled();
|
||||
processExitSpy.mockRestore();
|
||||
|
||||
@ -26,7 +26,6 @@ const {
|
||||
}));
|
||||
|
||||
vi.mock('../shared/prompt/index.js', () => ({
|
||||
confirm: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/config/index.js', () => ({
|
||||
@ -85,16 +84,11 @@ vi.mock('../features/pieceSelection/index.js', () => ({
|
||||
selectPiece: vi.fn(),
|
||||
}));
|
||||
|
||||
import { confirm } from '../shared/prompt/index.js';
|
||||
import { selectAndExecuteTask } from '../features/tasks/execute/selectAndExecute.js';
|
||||
|
||||
const mockConfirm = vi.mocked(confirm);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockExecuteTask.mockResolvedValue(true);
|
||||
// worktree を使わない(confirm で false)
|
||||
mockConfirm.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
describe('skipTaskList option in selectAndExecuteTask', () => {
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
import type { Command } from 'commander';
|
||||
import type { TaskExecutionOptions } from '../../features/tasks/index.js';
|
||||
import type { ProviderType } from '../../infra/providers/index.js';
|
||||
import { error } from '../../shared/ui/index.js';
|
||||
import { isIssueReference } from '../../infra/github/index.js';
|
||||
|
||||
/**
|
||||
@ -26,28 +25,6 @@ export function resolveAgentOverrides(program: Command): TaskExecutionOptions |
|
||||
return { provider, model };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse --create-worktree option value (yes/no/true/false).
|
||||
* Returns undefined if not specified, boolean otherwise.
|
||||
* Exits with error on invalid value.
|
||||
*/
|
||||
export function parseCreateWorktreeOption(value?: string): boolean | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value.toLowerCase();
|
||||
if (normalized === 'yes' || normalized === 'true') {
|
||||
return true;
|
||||
}
|
||||
if (normalized === 'no' || normalized === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
error('Invalid value for --create-worktree. Use yes or no.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the input is a task description that should execute directly
|
||||
* vs one that should enter interactive mode.
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
import { Command } from 'commander';
|
||||
import { Command, Option } from 'commander';
|
||||
import { resolve } from 'node:path';
|
||||
import {
|
||||
initGlobalDirs,
|
||||
@ -52,7 +52,8 @@ program
|
||||
.option('-t, --task <string>', 'Task content (as alternative to GitHub issue)')
|
||||
.option('--pipeline', 'Pipeline mode: non-interactive, no worktree, direct branch creation')
|
||||
.option('--skip-git', 'Skip branch creation, commit, and push (pipeline mode)')
|
||||
.option('--create-worktree <yes|no>', 'Skip the worktree prompt by explicitly specifying yes or no')
|
||||
// Deprecated compatibility option: keep parsing to show migration guidance.
|
||||
.addOption(new Option('--create-worktree <yes|no>').hideHelp())
|
||||
.option('-q, --quiet', 'Minimal output mode: suppress AI output (for CI)')
|
||||
.option('-c, --continue', 'Continue from the last assistant session');
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
} from '../../features/interactive/index.js';
|
||||
import { getPieceDescription, resolveConfigValue, resolveConfigValues, loadPersonaSessions } from '../../infra/config/index.js';
|
||||
import { program, resolvedCwd, pipelineMode } from './program.js';
|
||||
import { resolveAgentOverrides, parseCreateWorktreeOption, isDirectTask } from './helpers.js';
|
||||
import { resolveAgentOverrides, isDirectTask } from './helpers.js';
|
||||
import { loadTaskHistory } from './taskHistory.js';
|
||||
|
||||
/**
|
||||
@ -113,6 +113,18 @@ async function resolvePrInput(
|
||||
*/
|
||||
export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
const opts = program.opts();
|
||||
if (opts.createWorktree !== undefined) {
|
||||
logError(
|
||||
'--create-worktree has been removed. ' +
|
||||
'execute now always runs in-place. ' +
|
||||
'Use "takt add" (save_task) + "takt run" for worktree-based execution.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
if (!pipelineMode && (opts.autoPr === true || opts.draft === true)) {
|
||||
logError('--auto-pr/--draft are supported only in --pipeline mode');
|
||||
process.exit(1);
|
||||
}
|
||||
const prNumber = opts.pr as number | undefined;
|
||||
const issueNumber = opts.issue as number | undefined;
|
||||
|
||||
@ -125,9 +137,7 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
logError('--pr and --task cannot be used together');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const agentOverrides = resolveAgentOverrides(program);
|
||||
const createWorktreeOverride = parseCreateWorktreeOption(opts.createWorktree as string | undefined);
|
||||
const resolvedPipelinePiece = (opts.piece as string | undefined) ?? resolveConfigValue(resolvedCwd, 'piece');
|
||||
const resolvedPipelineAutoPr = opts.autoPr === true
|
||||
? true
|
||||
@ -136,11 +146,8 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
? true
|
||||
: (resolveConfigValue(resolvedCwd, 'draftPr') ?? false);
|
||||
const selectOptions: SelectAndExecuteOptions = {
|
||||
autoPr: opts.autoPr === true ? true : undefined,
|
||||
draftPr: opts.draft === true ? true : undefined,
|
||||
repo: opts.repo as string | undefined,
|
||||
piece: opts.piece as string | undefined,
|
||||
createWorktree: createWorktreeOverride,
|
||||
};
|
||||
|
||||
// --- Pipeline mode (non-interactive): triggered by --pipeline ---
|
||||
@ -158,7 +165,6 @@ export async function executeDefaultAction(task?: string): Promise<void> {
|
||||
cwd: resolvedCwd,
|
||||
provider: agentOverrides?.provider,
|
||||
model: agentOverrides?.model,
|
||||
createWorktree: createWorktreeOverride,
|
||||
});
|
||||
|
||||
if (exitCode !== 0) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Task execution orchestration.
|
||||
*
|
||||
* Coordinates piece selection, worktree creation, task execution,
|
||||
* Coordinates piece selection and task execution,
|
||||
* auto-commit, and PR creation. Extracted from cli.ts to avoid
|
||||
* mixing CLI parsing with business logic.
|
||||
*/
|
||||
@ -15,7 +15,6 @@ import { createSharedClone, summarizeTaskName, resolveBaseBranch, TaskRunner } f
|
||||
import { info, error, withProgress } from '../../../shared/ui/index.js';
|
||||
import { createLogger } from '../../../shared/utils/index.js';
|
||||
import { executeTask } from './taskExecution.js';
|
||||
import { resolveAutoPr, resolveDraftPr, postExecutionFlow } from './postExecution.js';
|
||||
import type { TaskExecutionOptions, WorktreeConfirmationResult, SelectAndExecuteOptions } from './types.js';
|
||||
import { selectPiece } from '../../pieceSelection/index.js';
|
||||
import { buildBooleanTaskResult, persistTaskError, persistTaskResult } from './taskResultHandler.js';
|
||||
@ -76,7 +75,7 @@ export async function confirmAndCreateWorktree(
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task with piece selection, optional worktree, and auto-commit.
|
||||
* Execute a task with piece selection.
|
||||
* Shared by direct task execution and interactive mode.
|
||||
*/
|
||||
export async function selectAndExecuteTask(
|
||||
@ -92,35 +91,14 @@ export async function selectAndExecuteTask(
|
||||
return;
|
||||
}
|
||||
|
||||
const { execCwd, isWorktree, branch, baseBranch, taskSlug } = await confirmAndCreateWorktree(
|
||||
cwd,
|
||||
task,
|
||||
options?.createWorktree,
|
||||
options?.branch,
|
||||
);
|
||||
|
||||
// Ask for PR creation BEFORE execution (only if worktree is enabled)
|
||||
let shouldCreatePr = false;
|
||||
let shouldDraftPr = false;
|
||||
if (isWorktree) {
|
||||
shouldCreatePr = await resolveAutoPr(options?.autoPr, cwd);
|
||||
if (shouldCreatePr) {
|
||||
shouldDraftPr = await resolveDraftPr(options?.draftPr, cwd);
|
||||
}
|
||||
}
|
||||
|
||||
log.info('Starting task execution', { piece: pieceIdentifier, worktree: isWorktree, autoPr: shouldCreatePr, draftPr: shouldDraftPr });
|
||||
// execute action always runs in-place (no worktree prompt/creation).
|
||||
const execCwd = cwd;
|
||||
log.info('Starting task execution', { piece: pieceIdentifier, worktree: false });
|
||||
const taskRunner = new TaskRunner(cwd);
|
||||
let taskRecord: Awaited<ReturnType<TaskRunner['addTask']>> | null = null;
|
||||
if (options?.skipTaskList !== true || isWorktree) {
|
||||
if (options?.skipTaskList !== true) {
|
||||
taskRecord = taskRunner.addTask(task, {
|
||||
piece: pieceIdentifier,
|
||||
...(isWorktree ? { worktree: true } : {}),
|
||||
...(branch ? { branch } : {}),
|
||||
...(isWorktree ? { worktree_path: execCwd } : {}),
|
||||
auto_pr: shouldCreatePr,
|
||||
draft_pr: shouldDraftPr,
|
||||
...(taskSlug ? { slug: taskSlug } : {}),
|
||||
});
|
||||
}
|
||||
const startedAt = new Date().toISOString();
|
||||
@ -148,36 +126,15 @@ export async function selectAndExecuteTask(
|
||||
|
||||
const completedAt = new Date().toISOString();
|
||||
|
||||
let prFailed = false;
|
||||
let prError: string | undefined;
|
||||
if (taskSuccess && isWorktree) {
|
||||
const postResult = await postExecutionFlow({
|
||||
execCwd,
|
||||
projectCwd: cwd,
|
||||
task,
|
||||
branch,
|
||||
baseBranch,
|
||||
shouldCreatePr,
|
||||
draftPr: shouldDraftPr,
|
||||
pieceIdentifier,
|
||||
issues: options?.issues,
|
||||
repo: options?.repo,
|
||||
});
|
||||
prFailed = postResult.prFailed ?? false;
|
||||
prError = postResult.prError;
|
||||
}
|
||||
|
||||
const effectiveSuccess = taskSuccess && !prFailed;
|
||||
const effectiveSuccess = taskSuccess;
|
||||
if (taskRecord) {
|
||||
const taskResult = buildBooleanTaskResult({
|
||||
task: taskRecord,
|
||||
taskSuccess: effectiveSuccess,
|
||||
successResponse: 'Task completed successfully',
|
||||
failureResponse: prFailed ? `PR creation failed: ${prError}` : 'Task failed',
|
||||
failureResponse: 'Task failed',
|
||||
startedAt,
|
||||
completedAt,
|
||||
branch,
|
||||
...(isWorktree ? { worktreePath: execCwd } : {}),
|
||||
});
|
||||
persistTaskResult(taskRunner, taskResult);
|
||||
}
|
||||
|
||||
@ -136,11 +136,8 @@ export interface WorktreeConfirmationResult {
|
||||
}
|
||||
|
||||
export interface SelectAndExecuteOptions {
|
||||
autoPr?: boolean;
|
||||
draftPr?: boolean;
|
||||
repo?: string;
|
||||
piece?: string;
|
||||
createWorktree?: boolean | undefined;
|
||||
/** Override branch name (e.g., PR head branch for --pr) */
|
||||
branch?: string;
|
||||
/** Enable interactive user input during step transitions */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user