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