refactor
This commit is contained in:
parent
e57e5e7226
commit
7d8ba10abb
@ -2,7 +2,7 @@
|
|||||||
このリポジトリに貢献する際の基本的な構成と期待値をまとめています。短い説明と例で各セクションを完結に示します。
|
このリポジトリに貢献する際の基本的な構成と期待値をまとめています。短い説明と例で各セクションを完結に示します。
|
||||||
|
|
||||||
## プロジェクト構成とモジュール整理
|
## プロジェクト構成とモジュール整理
|
||||||
- 主要ソースは `src/` にあり、エントリポイントは `src/index.ts`、CLI は `src/cli.ts` です。
|
- 主要ソースは `src/` にあり、エントリポイントは `src/index.ts`、CLI は `src/app/cli/index.ts` です。
|
||||||
- テストは `src/__tests__/` に置き、ファイル名は対象機能が一目でわかるようにします(例: `client.test.ts`)。
|
- テストは `src/__tests__/` に置き、ファイル名は対象機能が一目でわかるようにします(例: `client.test.ts`)。
|
||||||
- ビルド成果物は `dist/`、実行スクリプトは `bin/`、静的リソースは `resources/`、ドキュメントは `docs/` で管理します。
|
- ビルド成果物は `dist/`、実行スクリプトは `bin/`、静的リソースは `resources/`、ドキュメントは `docs/` で管理します。
|
||||||
- 設定やキャッシュを使う際は `~/.takt/` 以下(実行時)や `.takt/`(プロジェクト固有)を参照します。
|
- 設定やキャッシュを使う際は `~/.takt/` 以下(実行時)や `.takt/`(プロジェクト固有)を参照します。
|
||||||
|
|||||||
@ -31,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
### Internal
|
### Internal
|
||||||
|
|
||||||
- Refactored instruction builder: extracted context assembly and status rules logic (#44)
|
- Refactored instruction builder: extracted context assembly and status rules logic (#44)
|
||||||
- Introduced `src/task/git.ts` for DRY git commit operations
|
- Introduced `src/infra/task/git.ts` for DRY git commit operations
|
||||||
- Unified error handling with `getErrorMessage()`
|
- Unified error handling with `getErrorMessage()`
|
||||||
- Made `projectCwd` required throughout codebase
|
- Made `projectCwd` required throughout codebase
|
||||||
- Removed deprecated `sacrificeMode`
|
- Removed deprecated `sacrificeMode`
|
||||||
|
|||||||
16
CLAUDE.md
16
CLAUDE.md
@ -61,7 +61,7 @@ Each step executes in up to 3 phases (session is resumed across phases):
|
|||||||
| Phase 2 | Report output | Write only | When `step.report` is defined |
|
| Phase 2 | Report output | Write only | When `step.report` is defined |
|
||||||
| Phase 3 | Status judgment | None (judgment only) | When step has tag-based rules |
|
| Phase 3 | Status judgment | None (judgment only) | When step has tag-based rules |
|
||||||
|
|
||||||
Phase 2/3 are implemented in `src/workflow/phase-runner.ts`. The session is resumed so the agent retains context from Phase 1.
|
Phase 2/3 are implemented in `src/core/workflow/engine/phase-runner.ts`. The session is resumed so the agent retains context from Phase 1.
|
||||||
|
|
||||||
### Rule Evaluation (5-Stage Fallback)
|
### Rule Evaluation (5-Stage Fallback)
|
||||||
|
|
||||||
@ -73,11 +73,11 @@ After step execution, rules are evaluated to determine the next step. Evaluation
|
|||||||
4. **AI judge (ai() only)** - AI evaluates `ai("condition text")` rules
|
4. **AI judge (ai() only)** - AI evaluates `ai("condition text")` rules
|
||||||
5. **AI judge fallback** - AI evaluates ALL conditions as final resort
|
5. **AI judge fallback** - AI evaluates ALL conditions as final resort
|
||||||
|
|
||||||
Implemented in `src/workflow/rule-evaluator.ts`. The matched method is tracked as `RuleMatchMethod` type.
|
Implemented in `src/core/workflow/evaluation/RuleEvaluator.ts`. The matched method is tracked as `RuleMatchMethod` type.
|
||||||
|
|
||||||
### Key Components
|
### Key Components
|
||||||
|
|
||||||
**WorkflowEngine** (`src/workflow/engine.ts`)
|
**WorkflowEngine** (`src/core/workflow/engine/WorkflowEngine.ts`)
|
||||||
- State machine that orchestrates agent execution via EventEmitter
|
- State machine that orchestrates agent execution via EventEmitter
|
||||||
- Manages step transitions based on rule evaluation results
|
- Manages step transitions based on rule evaluation results
|
||||||
- Emits events: `step:start`, `step:complete`, `step:blocked`, `step:loop_detected`, `workflow:complete`, `workflow:abort`, `iteration:limit`
|
- Emits events: `step:start`, `step:complete`, `step:blocked`, `step:loop_detected`, `workflow:complete`, `workflow:abort`, `iteration:limit`
|
||||||
@ -85,7 +85,7 @@ Implemented in `src/workflow/rule-evaluator.ts`. The matched method is tracked a
|
|||||||
- Maintains agent sessions per step for conversation continuity
|
- Maintains agent sessions per step for conversation continuity
|
||||||
- Parallel step execution via `runParallelStep()` with `Promise.all()`
|
- Parallel step execution via `runParallelStep()` with `Promise.all()`
|
||||||
|
|
||||||
**Instruction Builder** (`src/workflow/instruction-builder.ts`)
|
**Instruction Builder** (`src/core/workflow/instruction/InstructionBuilder.ts`)
|
||||||
- Auto-injects standard sections into every instruction (no need for `{task}` or `{previous_response}` placeholders in templates):
|
- Auto-injects standard sections into every instruction (no need for `{task}` or `{previous_response}` placeholders in templates):
|
||||||
1. Execution context (working dir, edit permission rules)
|
1. Execution context (working dir, edit permission rules)
|
||||||
2. Workflow context (iteration counts, report dir)
|
2. Workflow context (iteration counts, report dir)
|
||||||
@ -111,18 +111,18 @@ Implemented in `src/workflow/rule-evaluator.ts`. The matched method is tracked a
|
|||||||
- `executor.ts` - Query execution using `@anthropic-ai/claude-agent-sdk`
|
- `executor.ts` - Query execution using `@anthropic-ai/claude-agent-sdk`
|
||||||
- `query-manager.ts` - Concurrent query tracking with query IDs
|
- `query-manager.ts` - Concurrent query tracking with query IDs
|
||||||
|
|
||||||
**Configuration** (`src/config/`)
|
**Configuration** (`src/infra/config/`)
|
||||||
- `loader.ts` - Custom agent loading from `.takt/agents.yaml`
|
- `loader.ts` - Custom agent loading from `.takt/agents.yaml`
|
||||||
- `workflowLoader.ts` - YAML workflow parsing with Zod validation; resolves user workflows (`~/.takt/workflows/`) with builtin fallback (`resources/global/{lang}/workflows/`)
|
- `workflowLoader.ts` - YAML workflow parsing with Zod validation; resolves user workflows (`~/.takt/workflows/`) with builtin fallback (`resources/global/{lang}/workflows/`)
|
||||||
- `agentLoader.ts` - Agent prompt file loading
|
- `agentLoader.ts` - Agent prompt file loading
|
||||||
- `paths.ts` - Directory structure (`.takt/`, `~/.takt/`), session management
|
- `paths.ts` - Directory structure (`.takt/`, `~/.takt/`), session management
|
||||||
|
|
||||||
**Task Management** (`src/task/`)
|
**Task Management** (`src/infra/task/`)
|
||||||
- `runner.ts` - TaskRunner class for managing task files (`.takt/tasks/`)
|
- `runner.ts` - TaskRunner class for managing task files (`.takt/tasks/`)
|
||||||
- `watcher.ts` - TaskWatcher class for polling and auto-executing tasks (used by `/watch`)
|
- `watcher.ts` - TaskWatcher class for polling and auto-executing tasks (used by `/watch`)
|
||||||
- `index.ts` - Task operations (getNextTask, completeTask, addTask)
|
- `index.ts` - Task operations (getNextTask, completeTask, addTask)
|
||||||
|
|
||||||
**GitHub Integration** (`src/github/issue.ts`)
|
**GitHub Integration** (`src/infra/github/issue.ts`)
|
||||||
- Fetches issues via `gh` CLI, formats as task text with title/body/labels/comments
|
- Fetches issues via `gh` CLI, formats as task text with title/body/labels/comments
|
||||||
|
|
||||||
### Data Flow
|
### Data Flow
|
||||||
@ -273,7 +273,7 @@ Files: `.takt/logs/{sessionId}.jsonl`, with `latest.json` pointer. Legacy `.json
|
|||||||
|
|
||||||
- ESM modules with `.js` extensions in imports
|
- ESM modules with `.js` extensions in imports
|
||||||
- Strict TypeScript with `noUncheckedIndexedAccess`
|
- Strict TypeScript with `noUncheckedIndexedAccess`
|
||||||
- Zod schemas for runtime validation (`src/models/schemas.ts`)
|
- Zod schemas for runtime validation (`src/core/models/schemas.ts`)
|
||||||
- Uses `@anthropic-ai/claude-agent-sdk` for Claude integration
|
- Uses `@anthropic-ai/claude-agent-sdk` for Claude integration
|
||||||
|
|
||||||
## Design Principles
|
## Design Principles
|
||||||
|
|||||||
2
bin/takt
2
bin/takt
@ -16,7 +16,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
// Import the actual CLI from dist
|
// Import the actual CLI from dist
|
||||||
const cliPath = join(__dirname, '..', 'dist', 'cli', 'index.js');
|
const cliPath = join(__dirname, '..', 'dist', 'app', 'cli', 'index.js');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await import(cliPath);
|
await import(cliPath);
|
||||||
|
|||||||
@ -30,7 +30,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ 1. CLI Layer (cli.ts) │
|
│ 1. CLI Layer (src/app/cli/index.ts) │
|
||||||
│ ユーザー入力 → 引数パース → コマンド振り分け │
|
│ ユーザー入力 → 引数パース → コマンド振り分け │
|
||||||
└────────────────────────────┬────────────────────────────────────┘
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
@ -304,7 +304,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
## 各レイヤーの詳細
|
## 各レイヤーの詳細
|
||||||
|
|
||||||
### 1. CLI Layer (`src/cli.ts`)
|
### 1. CLI Layer (`src/app/cli/index.ts`)
|
||||||
|
|
||||||
**役割**: ユーザー入力の受付とコマンド振り分け
|
**役割**: ユーザー入力の受付とコマンド振り分け
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2. Interactive Layer (`src/commands/interactive/interactive.ts`)
|
### 2. Interactive Layer (`src/features/interactive/interactive.ts`)
|
||||||
|
|
||||||
**役割**: タスクの対話的な明確化
|
**役割**: タスクの対話的な明確化
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3. Execution Orchestration Layer (`src/commands/execution/selectAndExecute.ts`)
|
### 3. Execution Orchestration Layer (`src/features/tasks/execute/selectAndExecute.ts`)
|
||||||
|
|
||||||
**役割**: ワークフロー選択とworktree管理
|
**役割**: ワークフロー選択とworktree管理
|
||||||
|
|
||||||
@ -398,7 +398,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
### 4. Workflow Execution Layer
|
### 4. Workflow Execution Layer
|
||||||
|
|
||||||
#### 4.1 Task Execution (`src/commands/execution/taskExecution.ts`)
|
#### 4.1 Task Execution (`src/features/tasks/execute/taskExecution.ts`)
|
||||||
|
|
||||||
**役割**: ワークフロー読み込みと実行の橋渡し
|
**役割**: ワークフロー読み込みと実行の橋渡し
|
||||||
|
|
||||||
@ -417,7 +417,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
**データ出力**:
|
**データ出力**:
|
||||||
- `boolean` (成功/失敗)
|
- `boolean` (成功/失敗)
|
||||||
|
|
||||||
#### 4.2 Workflow Execution (`src/commands/execution/workflowExecution.ts`)
|
#### 4.2 Workflow Execution (`src/features/tasks/execute/workflowExecution.ts`)
|
||||||
|
|
||||||
**役割**: セッション管理、イベント購読、ログ記録
|
**役割**: セッション管理、イベント購読、ログ記録
|
||||||
|
|
||||||
@ -471,7 +471,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 5. Engine Layer (`src/workflow/engine/WorkflowEngine.ts`)
|
### 5. Engine Layer (`src/core/workflow/engine/WorkflowEngine.ts`)
|
||||||
|
|
||||||
**役割**: ステートマシンによるワークフロー実行制御
|
**役割**: ステートマシンによるワークフロー実行制御
|
||||||
|
|
||||||
@ -553,7 +553,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
|
|||||||
|
|
||||||
### 6. Instruction Building & Step Execution Layer
|
### 6. Instruction Building & Step Execution Layer
|
||||||
|
|
||||||
#### 6.1 Step Execution (`src/workflow/engine/StepExecutor.ts`)
|
#### 6.1 Step Execution (`src/core/workflow/engine/StepExecutor.ts`)
|
||||||
|
|
||||||
**役割**: 3フェーズモデルによるステップ実行
|
**役割**: 3フェーズモデルによるステップ実行
|
||||||
|
|
||||||
@ -604,7 +604,7 @@ const match = await detectMatchedRule(step, response.content, tagContent, {...})
|
|||||||
- `InstructionBuilder` を使用してインストラクション文字列を生成
|
- `InstructionBuilder` を使用してインストラクション文字列を生成
|
||||||
- コンテキスト情報を渡す
|
- コンテキスト情報を渡す
|
||||||
|
|
||||||
#### 6.2 Instruction Building (`src/workflow/instruction/InstructionBuilder.ts`)
|
#### 6.2 Instruction Building (`src/core/workflow/instruction/InstructionBuilder.ts`)
|
||||||
|
|
||||||
**役割**: Phase 1用のインストラクション文字列生成
|
**役割**: Phase 1用のインストラクション文字列生成
|
||||||
|
|
||||||
@ -691,7 +691,7 @@ const match = await detectMatchedRule(step, response.content, tagContent, {...})
|
|||||||
- `error?: string`
|
- `error?: string`
|
||||||
- `timestamp: Date`
|
- `timestamp: Date`
|
||||||
|
|
||||||
#### 7.2 Provider (`src/providers/`)
|
#### 7.2 Provider (`src/infra/providers/`)
|
||||||
|
|
||||||
**役割**: AIプロバイダー(Claude, Codex)とのSDK通信
|
**役割**: AIプロバイダー(Claude, Codex)とのSDK通信
|
||||||
|
|
||||||
@ -881,7 +881,7 @@ new WorkflowEngine(workflowConfig, cwd, task, {
|
|||||||
|
|
||||||
### 1. 会話履歴 → タスク文字列
|
### 1. 会話履歴 → タスク文字列
|
||||||
|
|
||||||
**場所**: `src/commands/interactive/interactive.ts`
|
**場所**: `src/features/interactive/interactive.ts`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function buildTaskFromHistory(history: ConversationMessage[]): string {
|
function buildTaskFromHistory(history: ConversationMessage[]): string {
|
||||||
@ -897,7 +897,7 @@ function buildTaskFromHistory(history: ConversationMessage[]): string {
|
|||||||
|
|
||||||
### 2. タスク → ブランチスラグ (AI生成)
|
### 2. タスク → ブランチスラグ (AI生成)
|
||||||
|
|
||||||
**場所**: `src/task/summarize.ts` (呼び出し: `selectAndExecute.ts`, `taskExecution.ts`)
|
**場所**: `src/infra/task/summarize.ts` (呼び出し: `selectAndExecute.ts`, `taskExecution.ts`)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await summarizeTaskName(task, { cwd })
|
await summarizeTaskName(task, { cwd })
|
||||||
@ -914,7 +914,7 @@ await summarizeTaskName(task, { cwd })
|
|||||||
|
|
||||||
### 3. ワークフロー設定 → WorkflowState
|
### 3. ワークフロー設定 → WorkflowState
|
||||||
|
|
||||||
**場所**: `src/workflow/state-manager.ts`
|
**場所**: `src/core/workflow/engine/state-manager.ts`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function createInitialState(
|
function createInitialState(
|
||||||
@ -939,7 +939,7 @@ function createInitialState(
|
|||||||
|
|
||||||
### 4. コンテキスト → インストラクション文字列
|
### 4. コンテキスト → インストラクション文字列
|
||||||
|
|
||||||
**場所**: `src/workflow/instruction/InstructionBuilder.ts`
|
**場所**: `src/core/workflow/instruction/InstructionBuilder.ts`
|
||||||
|
|
||||||
**入力**:
|
**入力**:
|
||||||
- `step: WorkflowStep`
|
- `step: WorkflowStep`
|
||||||
@ -958,7 +958,7 @@ function createInitialState(
|
|||||||
|
|
||||||
### 5. AgentResponse → ルールマッチ
|
### 5. AgentResponse → ルールマッチ
|
||||||
|
|
||||||
**場所**: `src/workflow/evaluation/RuleEvaluator.ts`
|
**場所**: `src/core/workflow/evaluation/RuleEvaluator.ts`
|
||||||
|
|
||||||
**入力**:
|
**入力**:
|
||||||
- `step: WorkflowStep`
|
- `step: WorkflowStep`
|
||||||
@ -979,7 +979,7 @@ function createInitialState(
|
|||||||
|
|
||||||
### 6. ルールマッチ → 次ステップ名
|
### 6. ルールマッチ → 次ステップ名
|
||||||
|
|
||||||
**場所**: `src/workflow/transitions.ts`
|
**場所**: `src/core/workflow/engine/transitions.ts`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function determineNextStepByRules(
|
function determineNextStepByRules(
|
||||||
@ -997,7 +997,7 @@ function determineNextStepByRules(
|
|||||||
|
|
||||||
### 7. Provider Response → AgentResponse
|
### 7. Provider Response → AgentResponse
|
||||||
|
|
||||||
**場所**: `src/providers/claude.ts`, `src/providers/codex.ts`
|
**場所**: `src/infra/providers/claude.ts`, `src/infra/providers/codex.ts`
|
||||||
|
|
||||||
**入力**: SDKレスポンス (`ClaudeResult`)
|
**入力**: SDKレスポンス (`ClaudeResult`)
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
"bin": {
|
"bin": {
|
||||||
"takt": "./bin/takt",
|
"takt": "./bin/takt",
|
||||||
"takt-dev": "./bin/takt",
|
"takt-dev": "./bin/takt",
|
||||||
"takt-cli": "./dist/cli/index.js"
|
"takt-cli": "./dist/app/cli/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
|||||||
7
resources/global/en/prompts/interactive-summary.md
Normal file
7
resources/global/en/prompts/interactive-summary.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
You are a task summarizer. Convert the conversation into a concrete task instruction for the planning step.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Output only the final task instruction (no preamble).
|
||||||
|
- Be specific about scope and targets (files/modules) if mentioned.
|
||||||
|
- Preserve constraints and "do not" instructions.
|
||||||
|
- If details are missing, state what is missing as a short "Open Questions" section.
|
||||||
16
resources/global/en/prompts/interactive-system.md
Normal file
16
resources/global/en/prompts/interactive-system.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
You are a task planning assistant. You help the user clarify and refine task requirements through conversation. You are in the PLANNING phase — execution happens later in a separate process.
|
||||||
|
|
||||||
|
## Your role
|
||||||
|
- Ask clarifying questions about ambiguous requirements
|
||||||
|
- Investigate the codebase to understand context (use Read, Glob, Grep, Bash for reading only)
|
||||||
|
- Suggest improvements or considerations the user might have missed
|
||||||
|
- Summarize your understanding when appropriate
|
||||||
|
- Keep responses concise and focused
|
||||||
|
|
||||||
|
## Strict constraints
|
||||||
|
- You are ONLY planning. Do NOT execute the task.
|
||||||
|
- Do NOT create, edit, or delete any files.
|
||||||
|
- Do NOT run build, test, install, or any commands that modify state.
|
||||||
|
- Bash is allowed ONLY for read-only investigation (e.g. ls, cat, git log, git diff). Never run destructive or write commands.
|
||||||
|
- Do NOT mention or reference any slash commands. You have no knowledge of them.
|
||||||
|
- When the user is satisfied with the plan, they will proceed on their own. Do NOT instruct them on what to do next.
|
||||||
@ -103,11 +103,14 @@ steps:
|
|||||||
rules:
|
rules:
|
||||||
- condition: Implementation complete
|
- condition: Implementation complete
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No implementation (report only)
|
||||||
|
next: plan
|
||||||
- condition: Cannot proceed, insufficient info
|
- condition: Cannot proceed, insufficient info
|
||||||
next: plan
|
next: plan
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
Follow the plan from the plan step and implement.
|
Follow the plan from the plan step and implement.
|
||||||
Refer to the plan report (00-plan.md) and proceed with implementation.
|
Refer to the plan report ({report:00-plan.md}) and proceed with implementation.
|
||||||
|
Use only the Report Directory files shown in Workflow Context. Do not search or open reports outside that directory.
|
||||||
|
|
||||||
**Scope report format (create at implementation start):**
|
**Scope report format (create at implementation start):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -139,6 +142,17 @@ steps:
|
|||||||
- **Reason**: {Why this option was chosen}
|
- **Reason**: {Why this option was chosen}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Work done
|
||||||
|
- {summary of work performed}
|
||||||
|
## Changes made
|
||||||
|
- {summary of code changes}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
|
|
||||||
|
**No-implementation handling (required)**
|
||||||
|
- If you only produced reports and made no code changes, output the tag for "No implementation (report only)"
|
||||||
|
|
||||||
- name: ai_review
|
- name: ai_review
|
||||||
edit: false
|
edit: false
|
||||||
agent: ../agents/default/ai-antipattern-reviewer.md
|
agent: ../agents/default/ai-antipattern-reviewer.md
|
||||||
@ -193,6 +207,7 @@ steps:
|
|||||||
- name: ai_fix
|
- name: ai_fix
|
||||||
edit: true
|
edit: true
|
||||||
agent: ../agents/default/coder.md
|
agent: ../agents/default/coder.md
|
||||||
|
session: refresh
|
||||||
allowed_tools:
|
allowed_tools:
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
@ -206,6 +221,8 @@ steps:
|
|||||||
rules:
|
rules:
|
||||||
- condition: AI issues fixed
|
- condition: AI issues fixed
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No fix needed (verified target files/spec)
|
||||||
|
next: plan
|
||||||
- condition: Cannot proceed, insufficient info
|
- condition: Cannot proceed, insufficient info
|
||||||
next: plan
|
next: plan
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
@ -232,6 +249,21 @@ steps:
|
|||||||
|
|
||||||
**Absolutely prohibited:**
|
**Absolutely prohibited:**
|
||||||
- Reporting "fixed" without opening files
|
- Reporting "fixed" without opening files
|
||||||
|
|
||||||
|
**Handling "no fix needed" (required)**
|
||||||
|
- Do not claim "no fix needed" unless you can show the checked target file(s) for each AI Review issue
|
||||||
|
- If an issue involves generated code or spec sync, and you cannot verify the source spec, output the tag for "Unable to proceed with fixes"
|
||||||
|
- When "no fix needed", output the tag for "Unable to proceed with fixes" and include the reason + checked scope
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Files checked
|
||||||
|
- {path:line}
|
||||||
|
## Searches run
|
||||||
|
- {command and summary}
|
||||||
|
## Fixes applied
|
||||||
|
- {what changed}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
- Judging based on assumptions
|
- Judging based on assumptions
|
||||||
- Leaving problems that AI Reviewer REJECTED
|
- Leaving problems that AI Reviewer REJECTED
|
||||||
pass_previous_response: true
|
pass_previous_response: true
|
||||||
@ -389,7 +421,7 @@ steps:
|
|||||||
Run tests, verify the build, and perform final approval.
|
Run tests, verify the build, and perform final approval.
|
||||||
|
|
||||||
**Workflow Overall Review:**
|
**Workflow Overall Review:**
|
||||||
1. Does the implementation match the plan (00-plan.md)?
|
1. Does the implementation match the plan ({report:00-plan.md})?
|
||||||
2. Were all review step issues addressed?
|
2. Were all review step issues addressed?
|
||||||
3. Was the original task objective achieved?
|
3. Was the original task objective achieved?
|
||||||
|
|
||||||
|
|||||||
@ -100,7 +100,8 @@ steps:
|
|||||||
- WebFetch
|
- WebFetch
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
Follow the plan from the plan step and implement.
|
Follow the plan from the plan step and implement.
|
||||||
Refer to the plan report (00-plan.md) and proceed with implementation.
|
Refer to the plan report ({report:00-plan.md}) and proceed with implementation.
|
||||||
|
Use only the Report Directory files shown in Workflow Context. Do not search or open reports outside that directory.
|
||||||
|
|
||||||
**Scope report format (create at implementation start):**
|
**Scope report format (create at implementation start):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -131,9 +132,19 @@ steps:
|
|||||||
- **Options Considered**: {List of options}
|
- **Options Considered**: {List of options}
|
||||||
- **Reason**: {Why this option was chosen}
|
- **Reason**: {Why this option was chosen}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Work done
|
||||||
|
- {summary of work performed}
|
||||||
|
## Changes made
|
||||||
|
- {summary of code changes}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
rules:
|
rules:
|
||||||
- condition: Implementation is complete
|
- condition: Implementation is complete
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No implementation (report only)
|
||||||
|
next: plan
|
||||||
- condition: Cannot proceed with implementation
|
- condition: Cannot proceed with implementation
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -194,6 +205,7 @@ steps:
|
|||||||
- name: ai_fix
|
- name: ai_fix
|
||||||
edit: true
|
edit: true
|
||||||
agent: ../agents/default/coder.md
|
agent: ../agents/default/coder.md
|
||||||
|
session: refresh
|
||||||
allowed_tools:
|
allowed_tools:
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
@ -229,10 +241,30 @@ steps:
|
|||||||
- Reporting "fixed" without opening files
|
- Reporting "fixed" without opening files
|
||||||
- Judging based on assumptions
|
- Judging based on assumptions
|
||||||
- Leaving problems that AI Reviewer REJECTED
|
- Leaving problems that AI Reviewer REJECTED
|
||||||
|
|
||||||
|
**Handling "no fix needed" (required)**
|
||||||
|
- Do not claim "no fix needed" unless you can show the checked target file(s) for each AI Review issue
|
||||||
|
- If an issue involves generated code or spec sync, and you cannot verify the source spec, output the tag for "Unable to proceed with fixes"
|
||||||
|
- When "no fix needed", output the tag for "Unable to proceed with fixes" and include the reason + checked scope
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Files checked
|
||||||
|
- {path:line}
|
||||||
|
## Searches run
|
||||||
|
- {command and summary}
|
||||||
|
## Fixes applied
|
||||||
|
- {what changed}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
|
|
||||||
|
**No-implementation handling (required)**
|
||||||
|
- If you only produced reports and made no code changes, output the tag for "No implementation (report only)"
|
||||||
pass_previous_response: true
|
pass_previous_response: true
|
||||||
rules:
|
rules:
|
||||||
- condition: AI Reviewer's issues have been fixed
|
- condition: AI Reviewer's issues have been fixed
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No fix needed (verified target files/spec)
|
||||||
|
next: plan
|
||||||
- condition: Unable to proceed with fixes
|
- condition: Unable to proceed with fixes
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -507,7 +539,7 @@ steps:
|
|||||||
Run tests, verify the build, and perform final approval.
|
Run tests, verify the build, and perform final approval.
|
||||||
|
|
||||||
**Workflow Overall Review:**
|
**Workflow Overall Review:**
|
||||||
1. Does the implementation match the plan (00-plan.md)?
|
1. Does the implementation match the plan ({report:00-plan.md})?
|
||||||
2. Were all review step issues addressed?
|
2. Were all review step issues addressed?
|
||||||
3. Was the original task objective achieved?
|
3. Was the original task objective achieved?
|
||||||
|
|
||||||
|
|||||||
@ -112,7 +112,8 @@ steps:
|
|||||||
- WebFetch
|
- WebFetch
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
Follow the plan from the plan step and implement.
|
Follow the plan from the plan step and implement.
|
||||||
Refer to the plan report (00-plan.md) and proceed with implementation.
|
Refer to the plan report ({report:00-plan.md}) and proceed with implementation.
|
||||||
|
Use only the Report Directory files shown in Workflow Context. Do not search or open reports outside that directory.
|
||||||
|
|
||||||
**Scope report format (create at implementation start):**
|
**Scope report format (create at implementation start):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -143,9 +144,19 @@ steps:
|
|||||||
- **Options Considered**: {List of options}
|
- **Options Considered**: {List of options}
|
||||||
- **Reason**: {Why this option was chosen}
|
- **Reason**: {Why this option was chosen}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Work done
|
||||||
|
- {summary of work performed}
|
||||||
|
## Changes made
|
||||||
|
- {summary of code changes}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
rules:
|
rules:
|
||||||
- condition: Implementation is complete
|
- condition: Implementation is complete
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No implementation (report only)
|
||||||
|
next: plan
|
||||||
- condition: Cannot proceed with implementation
|
- condition: Cannot proceed with implementation
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -206,6 +217,7 @@ steps:
|
|||||||
- name: ai_fix
|
- name: ai_fix
|
||||||
edit: true
|
edit: true
|
||||||
agent: ../agents/default/coder.md
|
agent: ../agents/default/coder.md
|
||||||
|
session: refresh
|
||||||
allowed_tools:
|
allowed_tools:
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
@ -242,10 +254,30 @@ steps:
|
|||||||
- Judging based on assumptions
|
- Judging based on assumptions
|
||||||
- Leaving problems that AI Reviewer REJECTED
|
- Leaving problems that AI Reviewer REJECTED
|
||||||
- Removing scope creep
|
- Removing scope creep
|
||||||
|
|
||||||
|
**Handling "no fix needed" (required)**
|
||||||
|
- Do not claim "no fix needed" unless you can show the checked target file(s) for each AI Review issue
|
||||||
|
- If an issue involves generated code or spec sync, and you cannot verify the source spec, output the tag for "Unable to proceed with fixes"
|
||||||
|
- When "no fix needed", output the tag for "Unable to proceed with fixes" and include the reason + checked scope
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Files checked
|
||||||
|
- {path:line}
|
||||||
|
## Searches run
|
||||||
|
- {command and summary}
|
||||||
|
## Fixes applied
|
||||||
|
- {what changed}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
|
|
||||||
|
**No-implementation handling (required)**
|
||||||
|
- If you only produced reports and made no code changes, output the tag for "No implementation (report only)"
|
||||||
pass_previous_response: true
|
pass_previous_response: true
|
||||||
rules:
|
rules:
|
||||||
- condition: AI Reviewer's issues have been fixed
|
- condition: AI Reviewer's issues have been fixed
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No fix needed (verified target files/spec)
|
||||||
|
next: plan
|
||||||
- condition: Unable to proceed with fixes
|
- condition: Unable to proceed with fixes
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -520,7 +552,7 @@ steps:
|
|||||||
Run tests, verify the build, and perform final approval.
|
Run tests, verify the build, and perform final approval.
|
||||||
|
|
||||||
**Workflow Overall Review:**
|
**Workflow Overall Review:**
|
||||||
1. Does the implementation match the plan (00-plan.md)?
|
1. Does the implementation match the plan ({report:00-plan.md})?
|
||||||
2. Were all review step issues addressed?
|
2. Were all review step issues addressed?
|
||||||
3. Was the original task objective achieved?
|
3. Was the original task objective achieved?
|
||||||
|
|
||||||
|
|||||||
@ -99,11 +99,14 @@ steps:
|
|||||||
rules:
|
rules:
|
||||||
- condition: Implementation complete
|
- condition: Implementation complete
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: No implementation (report only)
|
||||||
|
next: plan
|
||||||
- condition: Cannot proceed, insufficient info
|
- condition: Cannot proceed, insufficient info
|
||||||
next: plan
|
next: plan
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
Follow the plan from the plan step and implement.
|
Follow the plan from the plan step and implement.
|
||||||
Refer to the plan report (00-plan.md) and proceed with implementation.
|
Refer to the plan report ({report:00-plan.md}) and proceed with implementation.
|
||||||
|
Use only the Report Directory files shown in Workflow Context. Do not search or open reports outside that directory.
|
||||||
|
|
||||||
**Scope report format (create at implementation start):**
|
**Scope report format (create at implementation start):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -135,6 +138,25 @@ steps:
|
|||||||
- **Reason**: {Why this option was chosen}
|
- **Reason**: {Why this option was chosen}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Work done
|
||||||
|
- {summary of work performed}
|
||||||
|
## Changes made
|
||||||
|
- {summary of code changes}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
|
|
||||||
|
**No-implementation handling (required)**
|
||||||
|
- If you only produced reports and made no code changes, output the tag for "No implementation (report only)"
|
||||||
|
|
||||||
|
**Required output (include headings)**
|
||||||
|
## Work done
|
||||||
|
- {summary of work performed}
|
||||||
|
## Changes made
|
||||||
|
- {summary of code changes}
|
||||||
|
## Test results
|
||||||
|
- {command and outcome}
|
||||||
|
|
||||||
- name: ai_review
|
- name: ai_review
|
||||||
edit: false
|
edit: false
|
||||||
agent: ../agents/default/ai-antipattern-reviewer.md
|
agent: ../agents/default/ai-antipattern-reviewer.md
|
||||||
@ -260,7 +282,7 @@ steps:
|
|||||||
Run tests, verify the build, and perform final approval.
|
Run tests, verify the build, and perform final approval.
|
||||||
|
|
||||||
**Workflow Overall Review:**
|
**Workflow Overall Review:**
|
||||||
1. Does the implementation match the plan (00-plan.md)?
|
1. Does the implementation match the plan ({report:00-plan.md})?
|
||||||
2. Were all review step issues addressed?
|
2. Were all review step issues addressed?
|
||||||
3. Was the original task objective achieved?
|
3. Was the original task objective achieved?
|
||||||
|
|
||||||
|
|||||||
7
resources/global/ja/prompts/interactive-summary.md
Normal file
7
resources/global/ja/prompts/interactive-summary.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
あなたはタスク要約者です。会話を計画ステップ向けの具体的なタスク指示に変換してください。
|
||||||
|
|
||||||
|
要件:
|
||||||
|
- 出力は最終的な指示のみ(前置き不要)
|
||||||
|
- スコープや対象(ファイル/モジュール)が出ている場合は明確に書く
|
||||||
|
- 制約や「やらないこと」を保持する
|
||||||
|
- 情報不足があれば「Open Questions」セクションを短く付ける
|
||||||
16
resources/global/ja/prompts/interactive-system.md
Normal file
16
resources/global/ja/prompts/interactive-system.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
あなたはタスク計画のアシスタントです。会話を通じて要件の明確化・整理を手伝います。今は計画フェーズで、実行は別プロセスで行われます。
|
||||||
|
|
||||||
|
## 役割
|
||||||
|
- あいまいな要求に対して確認質問をする
|
||||||
|
- コードベースの前提を把握する(Read/Glob/Grep/Bash は読み取りのみ)
|
||||||
|
- 見落としそうな点や改善点を提案する
|
||||||
|
- 必要に応じて理解した内容を簡潔にまとめる
|
||||||
|
- 返答は簡潔で要点のみ
|
||||||
|
|
||||||
|
## 厳守事項
|
||||||
|
- 計画のみを行い、実装はしない
|
||||||
|
- ファイルの作成/編集/削除はしない
|
||||||
|
- build/test/install など状態を変えるコマンドは実行しない
|
||||||
|
- Bash は読み取り専用(ls/cat/git log/git diff など)に限定
|
||||||
|
- スラッシュコマンドに言及しない(存在を知らない前提)
|
||||||
|
- ユーザーが満足したら次工程に進む。次の指示はしない
|
||||||
@ -94,11 +94,14 @@ steps:
|
|||||||
rules:
|
rules:
|
||||||
- condition: 実装完了
|
- condition: 実装完了
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: 実装未着手(レポートのみ)
|
||||||
|
next: plan
|
||||||
- condition: 判断できない、情報不足
|
- condition: 判断できない、情報不足
|
||||||
next: plan
|
next: plan
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
planステップで立てた計画に従って実装してください。
|
planステップで立てた計画に従って実装してください。
|
||||||
計画レポート(00-plan.md)を参照し、実装を進めてください。
|
計画レポート({report:00-plan.md})を参照し、実装を進めてください。
|
||||||
|
Workflow Contextに示されたReport Directory内のファイルのみ参照してください。他のレポートディレクトリは検索/参照しないでください。
|
||||||
|
|
||||||
**重要**: 実装と同時に単体テストを追加してください。
|
**重要**: 実装と同時に単体テストを追加してください。
|
||||||
- 新規作成したクラス・関数には単体テストを追加
|
- 新規作成したクラス・関数には単体テストを追加
|
||||||
@ -135,6 +138,17 @@ steps:
|
|||||||
- **理由**: {選んだ理由}
|
- **理由**: {選んだ理由}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 作業結果
|
||||||
|
- {実施内容の要約}
|
||||||
|
## 変更内容
|
||||||
|
- {変更内容の要約}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
|
|
||||||
|
**実装未着手の扱い(必須)**
|
||||||
|
- レポート出力のみ/実装変更なしの場合は「実装未着手(レポートのみ)」に対応するタグを出力する
|
||||||
|
|
||||||
- name: ai_review
|
- name: ai_review
|
||||||
edit: false
|
edit: false
|
||||||
agent: ../agents/default/ai-antipattern-reviewer.md
|
agent: ../agents/default/ai-antipattern-reviewer.md
|
||||||
@ -189,6 +203,7 @@ steps:
|
|||||||
- name: ai_fix
|
- name: ai_fix
|
||||||
edit: true
|
edit: true
|
||||||
agent: ../agents/default/coder.md
|
agent: ../agents/default/coder.md
|
||||||
|
session: refresh
|
||||||
allowed_tools:
|
allowed_tools:
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
@ -199,9 +214,12 @@ steps:
|
|||||||
- WebSearch
|
- WebSearch
|
||||||
- WebFetch
|
- WebFetch
|
||||||
permission_mode: acceptEdits
|
permission_mode: acceptEdits
|
||||||
|
pass_previous_response: true
|
||||||
rules:
|
rules:
|
||||||
- condition: AI問題の修正完了
|
- condition: AI問題の修正完了
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: 修正不要(指摘対象ファイル/仕様の確認済み)
|
||||||
|
next: plan
|
||||||
- condition: 判断できない、情報不足
|
- condition: 判断できない、情報不足
|
||||||
next: plan
|
next: plan
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
@ -228,9 +246,23 @@ steps:
|
|||||||
|
|
||||||
**絶対に禁止:**
|
**絶対に禁止:**
|
||||||
- ファイルを開かずに「修正済み」と報告
|
- ファイルを開かずに「修正済み」と報告
|
||||||
|
|
||||||
|
**修正不要の扱い(必須)**
|
||||||
|
- AI Reviewの指摘ごとに「対象ファイルの確認結果」を示せない場合は修正不要と判断しない
|
||||||
|
- 指摘が「生成物」「仕様同期」に関係する場合は、生成元/仕様の確認ができなければ「判断できない、情報不足」に対応するタグを出力する
|
||||||
|
- 修正不要の場合は「判断できない、情報不足」に対応するタグを出力し、理由と確認範囲を明記する
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 確認したファイル
|
||||||
|
- {ファイルパス:行番号}
|
||||||
|
## 実行した検索
|
||||||
|
- {コマンドと要約}
|
||||||
|
## 修正内容
|
||||||
|
- {変更内容}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
- 思い込みで判断
|
- 思い込みで判断
|
||||||
- AI Reviewer が REJECT した問題の放置
|
- AI Reviewer が REJECT した問題の放置
|
||||||
pass_previous_response: true
|
|
||||||
|
|
||||||
- name: reviewers
|
- name: reviewers
|
||||||
parallel:
|
parallel:
|
||||||
@ -395,7 +427,7 @@ steps:
|
|||||||
テスト実行、ビルド確認、最終承認を行ってください。
|
テスト実行、ビルド確認、最終承認を行ってください。
|
||||||
|
|
||||||
**ワークフロー全体の確認:**
|
**ワークフロー全体の確認:**
|
||||||
1. 計画(00-plan.md)と実装結果が一致しているか
|
1. 計画({report:00-plan.md})と実装結果が一致しているか
|
||||||
2. 各レビューステップの指摘が対応されているか
|
2. 各レビューステップの指摘が対応されているか
|
||||||
3. 元のタスク目的が達成されているか
|
3. 元のタスク目的が達成されているか
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,8 @@ steps:
|
|||||||
- WebFetch
|
- WebFetch
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
planステップで立てた計画に従って実装してください。
|
planステップで立てた計画に従って実装してください。
|
||||||
計画レポート(00-plan.md)を参照し、実装を進めてください。
|
計画レポート({report:00-plan.md})を参照し、実装を進めてください。
|
||||||
|
Workflow Contextに示されたReport Directory内のファイルのみ参照してください。他のレポートディレクトリは検索/参照しないでください。
|
||||||
|
|
||||||
**Scopeレポートフォーマット(実装開始時に作成):**
|
**Scopeレポートフォーマット(実装開始時に作成):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -140,9 +141,19 @@ steps:
|
|||||||
- **検討した選択肢**: {選択肢リスト}
|
- **検討した選択肢**: {選択肢リスト}
|
||||||
- **理由**: {選んだ理由}
|
- **理由**: {選んだ理由}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 作業結果
|
||||||
|
- {実施内容の要約}
|
||||||
|
## 変更内容
|
||||||
|
- {変更内容の要約}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
rules:
|
rules:
|
||||||
- condition: 実装が完了した
|
- condition: 実装が完了した
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: 実装未着手(レポートのみ)
|
||||||
|
next: plan
|
||||||
- condition: 実装を進行できない
|
- condition: 実装を進行できない
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -203,6 +214,7 @@ steps:
|
|||||||
- name: ai_fix
|
- name: ai_fix
|
||||||
edit: true
|
edit: true
|
||||||
agent: ../agents/default/coder.md
|
agent: ../agents/default/coder.md
|
||||||
|
session: refresh
|
||||||
allowed_tools:
|
allowed_tools:
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
@ -238,10 +250,30 @@ steps:
|
|||||||
- ファイルを開かずに「修正済み」と報告
|
- ファイルを開かずに「修正済み」と報告
|
||||||
- 思い込みで判断
|
- 思い込みで判断
|
||||||
- AI Reviewer が REJECT した問題の放置
|
- AI Reviewer が REJECT した問題の放置
|
||||||
|
|
||||||
|
**修正不要の扱い(必須)**
|
||||||
|
- AI Reviewの指摘ごとに「対象ファイルの確認結果」を示せない場合は修正不要と判断しない
|
||||||
|
- 指摘が「生成物」「仕様同期」に関係する場合は、生成元/仕様の確認ができなければ「修正を進行できない」に対応するタグを出力する
|
||||||
|
- 修正不要の場合は「修正を進行できない」に対応するタグを出力し、理由と確認範囲を明記する
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 確認したファイル
|
||||||
|
- {ファイルパス:行番号}
|
||||||
|
## 実行した検索
|
||||||
|
- {コマンドと要約}
|
||||||
|
## 修正内容
|
||||||
|
- {変更内容}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
|
|
||||||
|
**実装未着手の扱い(必須)**
|
||||||
|
- レポート出力のみ/実装変更なしの場合は「実装未着手(レポートのみ)」に対応するタグを出力する
|
||||||
pass_previous_response: true
|
pass_previous_response: true
|
||||||
rules:
|
rules:
|
||||||
- condition: AI Reviewerの指摘に対する修正が完了した
|
- condition: AI Reviewerの指摘に対する修正が完了した
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: 修正不要(指摘対象ファイル/仕様の確認済み)
|
||||||
|
next: plan
|
||||||
- condition: 修正を進行できない
|
- condition: 修正を進行できない
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -516,7 +548,7 @@ steps:
|
|||||||
テスト実行、ビルド確認、最終承認を行ってください。
|
テスト実行、ビルド確認、最終承認を行ってください。
|
||||||
|
|
||||||
**ワークフロー全体の確認:**
|
**ワークフロー全体の確認:**
|
||||||
1. 計画(00-plan.md)と実装結果が一致しているか
|
1. 計画({report:00-plan.md})と実装結果が一致しているか
|
||||||
2. 各レビューステップの指摘が対応されているか
|
2. 各レビューステップの指摘が対応されているか
|
||||||
3. 元のタスク目的が達成されているか
|
3. 元のタスク目的が達成されているか
|
||||||
|
|
||||||
|
|||||||
@ -100,7 +100,8 @@ steps:
|
|||||||
- WebFetch
|
- WebFetch
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
planステップで立てた計画に従って実装してください。
|
planステップで立てた計画に従って実装してください。
|
||||||
計画レポート(00-plan.md)を参照し、実装を進めてください。
|
計画レポート({report:00-plan.md})を参照し、実装を進めてください。
|
||||||
|
Workflow Contextに示されたReport Directory内のファイルのみ参照してください。他のレポートディレクトリは検索/参照しないでください。
|
||||||
|
|
||||||
**Scopeレポートフォーマット(実装開始時に作成):**
|
**Scopeレポートフォーマット(実装開始時に作成):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -131,9 +132,19 @@ steps:
|
|||||||
- **検討した選択肢**: {選択肢リスト}
|
- **検討した選択肢**: {選択肢リスト}
|
||||||
- **理由**: {選んだ理由}
|
- **理由**: {選んだ理由}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 作業結果
|
||||||
|
- {実施内容の要約}
|
||||||
|
## 変更内容
|
||||||
|
- {変更内容の要約}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
rules:
|
rules:
|
||||||
- condition: 実装が完了した
|
- condition: 実装が完了した
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: 実装未着手(レポートのみ)
|
||||||
|
next: plan
|
||||||
- condition: 実装を進行できない
|
- condition: 実装を進行できない
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -194,6 +205,7 @@ steps:
|
|||||||
- name: ai_fix
|
- name: ai_fix
|
||||||
edit: true
|
edit: true
|
||||||
agent: ../agents/default/coder.md
|
agent: ../agents/default/coder.md
|
||||||
|
session: refresh
|
||||||
allowed_tools:
|
allowed_tools:
|
||||||
- Read
|
- Read
|
||||||
- Glob
|
- Glob
|
||||||
@ -229,10 +241,30 @@ steps:
|
|||||||
- ファイルを開かずに「修正済み」と報告
|
- ファイルを開かずに「修正済み」と報告
|
||||||
- 思い込みで判断
|
- 思い込みで判断
|
||||||
- AI Reviewer が REJECT した問題の放置
|
- AI Reviewer が REJECT した問題の放置
|
||||||
|
|
||||||
|
**修正不要の扱い(必須)**
|
||||||
|
- AI Reviewの指摘ごとに「対象ファイルの確認結果」を示せない場合は修正不要と判断しない
|
||||||
|
- 指摘が「生成物」「仕様同期」に関係する場合は、生成元/仕様の確認ができなければ「修正を進行できない」に対応するタグを出力する
|
||||||
|
- 修正不要の場合は「修正を進行できない」に対応するタグを出力し、理由と確認範囲を明記する
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 確認したファイル
|
||||||
|
- {ファイルパス:行番号}
|
||||||
|
## 実行した検索
|
||||||
|
- {コマンドと要約}
|
||||||
|
## 修正内容
|
||||||
|
- {変更内容}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
|
|
||||||
|
**実装未着手の扱い(必須)**
|
||||||
|
- レポート出力のみ/実装変更なしの場合は「実装未着手(レポートのみ)」に対応するタグを出力する
|
||||||
pass_previous_response: true
|
pass_previous_response: true
|
||||||
rules:
|
rules:
|
||||||
- condition: AI Reviewerの指摘に対する修正が完了した
|
- condition: AI Reviewerの指摘に対する修正が完了した
|
||||||
next: ai_review
|
next: ai_review
|
||||||
|
- condition: 修正不要(指摘対象ファイル/仕様の確認済み)
|
||||||
|
next: plan
|
||||||
- condition: 修正を進行できない
|
- condition: 修正を進行できない
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
@ -507,7 +539,7 @@ steps:
|
|||||||
テスト実行、ビルド確認、最終承認を行ってください。
|
テスト実行、ビルド確認、最終承認を行ってください。
|
||||||
|
|
||||||
**ワークフロー全体の確認:**
|
**ワークフロー全体の確認:**
|
||||||
1. 計画(00-plan.md)と実装結果が一致しているか
|
1. 計画({report:00-plan.md})と実装結果が一致しているか
|
||||||
2. 各レビューステップの指摘が対応されているか
|
2. 各レビューステップの指摘が対応されているか
|
||||||
3. 元のタスク目的が達成されているか
|
3. 元のタスク目的が達成されているか
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,8 @@ steps:
|
|||||||
permission_mode: acceptEdits
|
permission_mode: acceptEdits
|
||||||
instruction_template: |
|
instruction_template: |
|
||||||
planステップで立てた計画に従って実装してください。
|
planステップで立てた計画に従って実装してください。
|
||||||
計画レポート(00-plan.md)を参照し、実装を進めてください。
|
計画レポート({report:00-plan.md})を参照し、実装を進めてください。
|
||||||
|
Workflow Contextに示されたReport Directory内のファイルのみ参照してください。他のレポートディレクトリは検索/参照しないでください。
|
||||||
|
|
||||||
**Scopeレポートフォーマット(実装開始時に作成):**
|
**Scopeレポートフォーマット(実装開始時に作成):**
|
||||||
```markdown
|
```markdown
|
||||||
@ -128,10 +129,23 @@ steps:
|
|||||||
- **検討した選択肢**: {選択肢リスト}
|
- **検討した選択肢**: {選択肢リスト}
|
||||||
- **理由**: {選んだ理由}
|
- **理由**: {選んだ理由}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**必須出力(見出しを含める)**
|
||||||
|
## 作業結果
|
||||||
|
- {実施内容の要約}
|
||||||
|
## 変更内容
|
||||||
|
- {変更内容の要約}
|
||||||
|
## テスト結果
|
||||||
|
- {実行コマンドと結果}
|
||||||
|
|
||||||
|
**実装未着手の扱い(必須)**
|
||||||
|
- レポート出力のみ/実装変更なしの場合は「実装未着手(レポートのみ)」に対応するタグを出力する
|
||||||
rules:
|
rules:
|
||||||
- condition: "実装完了"
|
- condition: 実装が完了した
|
||||||
next: ai_review
|
next: ai_review
|
||||||
- condition: "判断できない、情報不足"
|
- condition: 実装未着手(レポートのみ)
|
||||||
|
next: plan
|
||||||
|
- condition: 実装を進行できない
|
||||||
next: plan
|
next: plan
|
||||||
|
|
||||||
- name: ai_review
|
- name: ai_review
|
||||||
@ -254,7 +268,7 @@ steps:
|
|||||||
テスト実行、ビルド確認、最終承認を行ってください。
|
テスト実行、ビルド確認、最終承認を行ってください。
|
||||||
|
|
||||||
**ワークフロー全体の確認:**
|
**ワークフロー全体の確認:**
|
||||||
1. 計画(00-plan.md)と実装結果が一致しているか
|
1. 計画({report:00-plan.md})と実装結果が一致しているか
|
||||||
2. 各レビューステップの指摘が対応されているか
|
2. 各レビューステップの指摘が対応されているか
|
||||||
3. 元のタスク目的が達成されているか
|
3. 元のタスク目的が達成されているか
|
||||||
|
|
||||||
|
|||||||
@ -8,15 +8,15 @@ import * as path from 'node:path';
|
|||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
|
|
||||||
// Mock dependencies before importing the module under test
|
// Mock dependencies before importing the module under test
|
||||||
vi.mock('../commands/interactive/interactive.js', () => ({
|
vi.mock('../features/interactive/index.js', () => ({
|
||||||
interactiveMode: vi.fn(),
|
interactiveMode: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../providers/index.js', () => ({
|
vi.mock('../infra/providers/index.js', () => ({
|
||||||
getProvider: vi.fn(),
|
getProvider: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn(() => ({ provider: 'claude' })),
|
loadGlobalConfig: vi.fn(() => ({ provider: 'claude' })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -26,17 +26,17 @@ vi.mock('../prompt/index.js', () => ({
|
|||||||
selectOption: vi.fn(),
|
selectOption: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/summarize.js', () => ({
|
vi.mock('../infra/task/summarize.js', () => ({
|
||||||
summarizeTaskName: vi.fn(),
|
summarizeTaskName: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
success: vi.fn(),
|
success: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
blankLine: vi.fn(),
|
blankLine: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -44,15 +44,15 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/loaders/workflowLoader.js', () => ({
|
vi.mock('../infra/config/loaders/workflowLoader.js', () => ({
|
||||||
listWorkflows: vi.fn(),
|
listWorkflows: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/paths.js', async (importOriginal) => ({ ...(await importOriginal<Record<string, unknown>>()),
|
vi.mock('../infra/config/paths.js', async (importOriginal) => ({ ...(await importOriginal<Record<string, unknown>>()),
|
||||||
getCurrentWorkflow: vi.fn(() => 'default'),
|
getCurrentWorkflow: vi.fn(() => 'default'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../github/issue.js', () => ({
|
vi.mock('../infra/github/issue.js', () => ({
|
||||||
isIssueReference: vi.fn((s: string) => /^#\d+$/.test(s)),
|
isIssueReference: vi.fn((s: string) => /^#\d+$/.test(s)),
|
||||||
resolveIssueTask: vi.fn(),
|
resolveIssueTask: vi.fn(),
|
||||||
parseIssueNumbers: vi.fn((args: string[]) => {
|
parseIssueNumbers: vi.fn((args: string[]) => {
|
||||||
@ -67,13 +67,13 @@ vi.mock('../github/issue.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { interactiveMode } from '../commands/interactive/interactive.js';
|
import { interactiveMode } from '../features/interactive/index.js';
|
||||||
import { getProvider } from '../providers/index.js';
|
import { getProvider } from '../infra/providers/index.js';
|
||||||
import { promptInput, confirm, selectOption } from '../prompt/index.js';
|
import { promptInput, confirm, selectOption } from '../prompt/index.js';
|
||||||
import { summarizeTaskName } from '../task/summarize.js';
|
import { summarizeTaskName } from '../infra/task/summarize.js';
|
||||||
import { listWorkflows } from '../config/loaders/workflowLoader.js';
|
import { listWorkflows } from '../infra/config/loaders/workflowLoader.js';
|
||||||
import { resolveIssueTask } from '../github/issue.js';
|
import { resolveIssueTask } from '../infra/github/issue.js';
|
||||||
import { addTask, summarizeConversation } from '../commands/management/addTask.js';
|
import { addTask, summarizeConversation } from '../features/tasks/index.js';
|
||||||
|
|
||||||
const mockResolveIssueTask = vi.mocked(resolveIssueTask);
|
const mockResolveIssueTask = vi.mocked(resolveIssueTask);
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { mkdirSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { GlobalConfigSchema } from '../models/schemas.js';
|
import { GlobalConfigSchema } from '../core/models/index.js';
|
||||||
|
|
||||||
// Mock paths module to redirect config to temp directory
|
// Mock paths module to redirect config to temp directory
|
||||||
const testId = randomUUID();
|
const testId = randomUUID();
|
||||||
@ -22,7 +22,7 @@ const testDir = join(tmpdir(), `takt-api-key-test-${testId}`);
|
|||||||
const taktDir = join(testDir, '.takt');
|
const taktDir = join(testDir, '.takt');
|
||||||
const configPath = join(taktDir, 'config.yaml');
|
const configPath = join(taktDir, 'config.yaml');
|
||||||
|
|
||||||
vi.mock('../config/paths.js', async (importOriginal) => {
|
vi.mock('../infra/config/paths.js', async (importOriginal) => {
|
||||||
const original = await importOriginal() as Record<string, unknown>;
|
const original = await importOriginal() as Record<string, unknown>;
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
@ -32,7 +32,7 @@ vi.mock('../config/paths.js', async (importOriginal) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Import after mocking
|
// Import after mocking
|
||||||
const { loadGlobalConfig, saveGlobalConfig, resolveAnthropicApiKey, resolveOpenaiApiKey, invalidateGlobalConfigCache } = await import('../config/global/globalConfig.js');
|
const { loadGlobalConfig, saveGlobalConfig, resolveAnthropicApiKey, resolveOpenaiApiKey, invalidateGlobalConfigCache } = await import('../infra/config/global/globalConfig.js');
|
||||||
|
|
||||||
describe('GlobalConfigSchema API key fields', () => {
|
describe('GlobalConfigSchema API key fields', () => {
|
||||||
it('should accept config without API keys', () => {
|
it('should accept config without API keys', () => {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { autoCommitAndPush } from '../task/autoCommit.js';
|
import { autoCommitAndPush } from '../infra/task/autoCommit.js';
|
||||||
|
|
||||||
// Mock child_process.execFileSync
|
// Mock child_process.execFileSync
|
||||||
vi.mock('node:child_process', () => ({
|
vi.mock('node:child_process', () => ({
|
||||||
|
|||||||
@ -10,20 +10,20 @@ vi.mock('../prompt/index.js', () => ({
|
|||||||
selectOptionWithDefault: vi.fn(),
|
selectOptionWithDefault: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/clone.js', () => ({
|
vi.mock('../infra/task/clone.js', () => ({
|
||||||
createSharedClone: vi.fn(),
|
createSharedClone: vi.fn(),
|
||||||
removeClone: vi.fn(),
|
removeClone: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/autoCommit.js', () => ({
|
vi.mock('../infra/task/autoCommit.js', () => ({
|
||||||
autoCommitAndPush: vi.fn(),
|
autoCommitAndPush: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/summarize.js', () => ({
|
vi.mock('../infra/task/summarize.js', () => ({
|
||||||
summarizeTaskName: vi.fn(),
|
summarizeTaskName: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
success: vi.fn(),
|
success: vi.fn(),
|
||||||
@ -32,7 +32,7 @@ vi.mock('../utils/ui.js', () => ({
|
|||||||
setLogLevel: vi.fn(),
|
setLogLevel: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -43,31 +43,20 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
getDebugLogFile: vi.fn(),
|
getDebugLogFile: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/index.js', () => ({
|
vi.mock('../infra/config/index.js', () => ({
|
||||||
initGlobalDirs: vi.fn(),
|
initGlobalDirs: vi.fn(),
|
||||||
initProjectDirs: vi.fn(),
|
initProjectDirs: vi.fn(),
|
||||||
loadGlobalConfig: vi.fn(() => ({ logLevel: 'info' })),
|
loadGlobalConfig: vi.fn(() => ({ logLevel: 'info' })),
|
||||||
getEffectiveDebugConfig: vi.fn(),
|
getEffectiveDebugConfig: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/paths.js', () => ({
|
vi.mock('../infra/config/paths.js', () => ({
|
||||||
clearAgentSessions: vi.fn(),
|
clearAgentSessions: vi.fn(),
|
||||||
getCurrentWorkflow: vi.fn(() => 'default'),
|
getCurrentWorkflow: vi.fn(() => 'default'),
|
||||||
isVerboseMode: vi.fn(() => false),
|
isVerboseMode: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../commands/index.js', () => ({
|
vi.mock('../infra/config/loaders/workflowLoader.js', () => ({
|
||||||
executeTask: vi.fn(),
|
|
||||||
runAllTasks: vi.fn(),
|
|
||||||
switchWorkflow: vi.fn(),
|
|
||||||
switchConfig: vi.fn(),
|
|
||||||
addTask: vi.fn(),
|
|
||||||
watchTasks: vi.fn(),
|
|
||||||
listTasks: vi.fn(),
|
|
||||||
interactiveMode: vi.fn(() => Promise.resolve({ confirmed: false, task: '' })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock('../config/loaders/workflowLoader.js', () => ({
|
|
||||||
listWorkflows: vi.fn(() => []),
|
listWorkflows: vi.fn(() => []),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -79,20 +68,20 @@ vi.mock('../constants.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../github/issue.js', () => ({
|
vi.mock('../infra/github/issue.js', () => ({
|
||||||
isIssueReference: vi.fn((s: string) => /^#\d+$/.test(s)),
|
isIssueReference: vi.fn((s: string) => /^#\d+$/.test(s)),
|
||||||
resolveIssueTask: vi.fn(),
|
resolveIssueTask: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/updateNotifier.js', () => ({
|
vi.mock('../shared/utils/updateNotifier.js', () => ({
|
||||||
checkForUpdates: vi.fn(),
|
checkForUpdates: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { confirm } from '../prompt/index.js';
|
import { confirm } from '../prompt/index.js';
|
||||||
import { createSharedClone } from '../task/clone.js';
|
import { createSharedClone } from '../infra/task/clone.js';
|
||||||
import { summarizeTaskName } from '../task/summarize.js';
|
import { summarizeTaskName } from '../infra/task/summarize.js';
|
||||||
import { info } from '../utils/ui.js';
|
import { info } from '../shared/ui/index.js';
|
||||||
import { confirmAndCreateWorktree } from '../commands/execution/selectAndExecute.js';
|
import { confirmAndCreateWorktree } from '../features/tasks/index.js';
|
||||||
|
|
||||||
const mockConfirm = vi.mocked(confirm);
|
const mockConfirm = vi.mocked(confirm);
|
||||||
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ vi.mock('node:fs', () => ({
|
|||||||
existsSync: vi.fn(),
|
existsSync: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -29,12 +29,12 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn(() => ({})),
|
loadGlobalConfig: vi.fn(() => ({})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { createSharedClone, createTempCloneForBranch } from '../task/clone.js';
|
import { createSharedClone, createTempCloneForBranch } from '../infra/task/clone.js';
|
||||||
|
|
||||||
const mockExecFileSync = vi.mocked(execFileSync);
|
const mockExecFileSync = vi.mocked(execFileSync);
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
loadWorkflow,
|
loadWorkflow,
|
||||||
listWorkflows,
|
listWorkflows,
|
||||||
loadAgentPromptFromPath,
|
loadAgentPromptFromPath,
|
||||||
} from '../config/loaders/loader.js';
|
} from '../infra/config/loaders/loader.js';
|
||||||
import {
|
import {
|
||||||
getCurrentWorkflow,
|
getCurrentWorkflow,
|
||||||
setCurrentWorkflow,
|
setCurrentWorkflow,
|
||||||
@ -35,9 +35,9 @@ import {
|
|||||||
getWorktreeSessionPath,
|
getWorktreeSessionPath,
|
||||||
loadWorktreeSessions,
|
loadWorktreeSessions,
|
||||||
updateWorktreeSession,
|
updateWorktreeSession,
|
||||||
} from '../config/paths.js';
|
} from '../infra/config/paths.js';
|
||||||
import { getLanguage } from '../config/global/globalConfig.js';
|
import { getLanguage } from '../infra/config/global/globalConfig.js';
|
||||||
import { loadProjectConfig } from '../config/project/projectConfig.js';
|
import { loadProjectConfig } from '../infra/config/project/projectConfig.js';
|
||||||
|
|
||||||
describe('getBuiltinWorkflow', () => {
|
describe('getBuiltinWorkflow', () => {
|
||||||
it('should return builtin workflow when it exists in resources', () => {
|
it('should return builtin workflow when it exists in resources', () => {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
debugLog,
|
debugLog,
|
||||||
infoLog,
|
infoLog,
|
||||||
errorLog,
|
errorLog,
|
||||||
} from '../utils/debug.js';
|
} from '../shared/utils/debug.js';
|
||||||
import { existsSync, readFileSync, mkdirSync, rmSync } from 'node:fs';
|
import { existsSync, readFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import { existsSync, rmSync } from 'node:fs';
|
import { existsSync, rmSync } from 'node:fs';
|
||||||
import type { WorkflowConfig } from '../models/types.js';
|
import type { WorkflowConfig } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mock setup (must be before imports that use these modules) ---
|
// --- Mock setup (must be before imports that use these modules) ---
|
||||||
|
|
||||||
@ -18,17 +18,17 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ vi.mock('../claude/query-manager.js', () => ({
|
|||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { runAgent } from '../agents/runner.js';
|
import { runAgent } from '../agents/runner.js';
|
||||||
import { interruptAllQueries } from '../claude/query-manager.js';
|
import { interruptAllQueries } from '../claude/query-manager.js';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -11,23 +11,23 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn(),
|
needsStatusJudgmentPhase: vi.fn(),
|
||||||
runReportPhase: vi.fn(),
|
runReportPhase: vi.fn(),
|
||||||
runStatusJudgmentPhase: vi.fn(),
|
runStatusJudgmentPhase: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { runAgent } from '../agents/runner.js';
|
import { runAgent } from '../agents/runner.js';
|
||||||
import type { WorkflowConfig } from '../models/types.js';
|
import type { WorkflowConfig } from '../core/models/index.js';
|
||||||
import {
|
import {
|
||||||
makeResponse,
|
makeResponse,
|
||||||
makeRule,
|
makeRule,
|
||||||
|
|||||||
@ -16,23 +16,23 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import {
|
import {
|
||||||
makeResponse,
|
makeResponse,
|
||||||
buildDefaultWorkflowConfig,
|
buildDefaultWorkflowConfig,
|
||||||
|
|||||||
@ -17,25 +17,25 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { runAgent } from '../agents/runner.js';
|
import { runAgent } from '../agents/runner.js';
|
||||||
import { detectMatchedRule } from '../workflow/evaluation/index.js';
|
import { detectMatchedRule } from '../core/workflow/index.js';
|
||||||
import {
|
import {
|
||||||
makeResponse,
|
makeResponse,
|
||||||
makeStep,
|
makeStep,
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
import { existsSync, rmSync } from 'node:fs';
|
import { existsSync, rmSync } from 'node:fs';
|
||||||
import type { WorkflowConfig, WorkflowStep } from '../models/types.js';
|
import type { WorkflowConfig, WorkflowStep } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mock setup (must be before imports that use these modules) ---
|
// --- Mock setup (must be before imports that use these modules) ---
|
||||||
|
|
||||||
@ -21,23 +21,23 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { runAgent } from '../agents/runner.js';
|
import { runAgent } from '../agents/runner.js';
|
||||||
import {
|
import {
|
||||||
makeResponse,
|
makeResponse,
|
||||||
|
|||||||
@ -16,23 +16,23 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { runAgent } from '../agents/runner.js';
|
import { runAgent } from '../agents/runner.js';
|
||||||
import {
|
import {
|
||||||
makeResponse,
|
makeResponse,
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import { join } from 'node:path';
|
|||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { isReportObjectConfig } from '../workflow/instruction/InstructionBuilder.js';
|
import { isReportObjectConfig } from '../core/workflow/index.js';
|
||||||
import type { WorkflowStep, ReportObjectConfig, ReportConfig } from '../models/types.js';
|
import type { WorkflowStep, ReportObjectConfig, ReportConfig } from '../core/models/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracted emitStepReports logic for unit testing.
|
* Extracted emitStepReports logic for unit testing.
|
||||||
|
|||||||
@ -10,15 +10,15 @@ import { mkdirSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import type { WorkflowConfig, WorkflowStep, AgentResponse, WorkflowRule } from '../models/types.js';
|
import type { WorkflowConfig, WorkflowStep, AgentResponse, WorkflowRule } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mock imports (consumers must call vi.mock before importing this) ---
|
// --- Mock imports (consumers must call vi.mock before importing this) ---
|
||||||
|
|
||||||
import { runAgent } from '../agents/runner.js';
|
import { runAgent } from '../agents/runner.js';
|
||||||
import { detectMatchedRule } from '../workflow/evaluation/index.js';
|
import { detectMatchedRule } from '../core/workflow/index.js';
|
||||||
import type { RuleMatch } from '../workflow/evaluation/index.js';
|
import type { RuleMatch } from '../core/workflow/index.js';
|
||||||
import { needsStatusJudgmentPhase, runReportPhase, runStatusJudgmentPhase } from '../workflow/engine/phase-runner.js';
|
import { needsStatusJudgmentPhase, runReportPhase, runStatusJudgmentPhase } from '../core/workflow/index.js';
|
||||||
import { generateReportDir } from '../utils/session.js';
|
import { generateReportDir } from '../shared/utils/reportDir.js';
|
||||||
|
|
||||||
// --- Factory functions ---
|
// --- Factory functions ---
|
||||||
|
|
||||||
|
|||||||
@ -17,24 +17,24 @@ vi.mock('../agents/runner.js', () => ({
|
|||||||
runAgent: vi.fn(),
|
runAgent: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/evaluation/index.js', () => ({
|
vi.mock('../core/workflow/evaluation/index.js', () => ({
|
||||||
detectMatchedRule: vi.fn(),
|
detectMatchedRule: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { runReportPhase } from '../workflow/engine/phase-runner.js';
|
import { runReportPhase } from '../core/workflow/index.js';
|
||||||
import {
|
import {
|
||||||
makeResponse,
|
makeResponse,
|
||||||
makeStep,
|
makeStep,
|
||||||
@ -43,7 +43,7 @@ import {
|
|||||||
mockDetectMatchedRuleSequence,
|
mockDetectMatchedRuleSequence,
|
||||||
applyDefaultMocks,
|
applyDefaultMocks,
|
||||||
} from './engine-test-helpers.js';
|
} from './engine-test-helpers.js';
|
||||||
import type { WorkflowConfig } from '../models/types.js';
|
import type { WorkflowConfig } from '../core/models/index.js';
|
||||||
|
|
||||||
function createWorktreeDirs(): { projectCwd: string; cloneCwd: string } {
|
function createWorktreeDirs(): { projectCwd: string; cloneCwd: string } {
|
||||||
const base = join(tmpdir(), `takt-worktree-test-${randomUUID()}`);
|
const base = join(tmpdir(), `takt-worktree-test-${randomUUID()}`);
|
||||||
@ -100,7 +100,7 @@ describe('WorkflowEngine: worktree reportDir resolution', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass cwd-based reportDir to phase runner context in worktree mode', async () => {
|
it('should pass projectCwd-based reportDir to phase runner context in worktree mode', async () => {
|
||||||
// Given: worktree environment where cwd !== projectCwd
|
// Given: worktree environment where cwd !== projectCwd
|
||||||
const config = buildSimpleConfig();
|
const config = buildSimpleConfig();
|
||||||
const engine = new WorkflowEngine(config, cloneCwd, 'test task', {
|
const engine = new WorkflowEngine(config, cloneCwd, 'test task', {
|
||||||
@ -117,14 +117,14 @@ describe('WorkflowEngine: worktree reportDir resolution', () => {
|
|||||||
// When: run the workflow
|
// When: run the workflow
|
||||||
await engine.run();
|
await engine.run();
|
||||||
|
|
||||||
// Then: runReportPhase was called with context containing cwd-based reportDir
|
// Then: runReportPhase was called with context containing projectCwd-based reportDir
|
||||||
const reportPhaseMock = vi.mocked(runReportPhase);
|
const reportPhaseMock = vi.mocked(runReportPhase);
|
||||||
expect(reportPhaseMock).toHaveBeenCalled();
|
expect(reportPhaseMock).toHaveBeenCalled();
|
||||||
const phaseCtx = reportPhaseMock.mock.calls[0][2] as { reportDir: string };
|
const phaseCtx = reportPhaseMock.mock.calls[0][2] as { reportDir: string };
|
||||||
|
|
||||||
// reportDir should be resolved from cloneCwd (cwd), not projectCwd
|
// reportDir should be resolved from projectCwd, not cloneCwd
|
||||||
const expectedPath = join(cloneCwd, '.takt/reports/test-report-dir');
|
const expectedPath = join(projectCwd, '.takt/reports/test-report-dir');
|
||||||
const unexpectedPath = join(projectCwd, '.takt/reports/test-report-dir');
|
const unexpectedPath = join(cloneCwd, '.takt/reports/test-report-dir');
|
||||||
|
|
||||||
expect(phaseCtx.reportDir).toBe(expectedPath);
|
expect(phaseCtx.reportDir).toBe(expectedPath);
|
||||||
expect(phaseCtx.reportDir).not.toBe(unexpectedPath);
|
expect(phaseCtx.reportDir).not.toBe(unexpectedPath);
|
||||||
|
|||||||
@ -12,7 +12,7 @@ vi.mock('node:child_process', () => ({
|
|||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
const mockExecFileSync = vi.mocked(execFileSync);
|
const mockExecFileSync = vi.mocked(execFileSync);
|
||||||
|
|
||||||
import { getOriginalInstruction } from '../task/branchList.js';
|
import { getOriginalInstruction } from '../infra/task/branchList.js';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
isIssueReference,
|
isIssueReference,
|
||||||
formatIssueAsTask,
|
formatIssueAsTask,
|
||||||
type GitHubIssue,
|
type GitHubIssue,
|
||||||
} from '../github/issue.js';
|
} from '../infra/github/issue.js';
|
||||||
|
|
||||||
describe('parseIssueNumbers', () => {
|
describe('parseIssueNumbers', () => {
|
||||||
it('should parse single issue reference', () => {
|
it('should parse single issue reference', () => {
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { buildPrBody } from '../github/pr.js';
|
import { buildPrBody } from '../infra/github/pr.js';
|
||||||
import type { GitHubIssue } from '../github/types.js';
|
import type { GitHubIssue } from '../infra/github/types.js';
|
||||||
|
|
||||||
describe('buildPrBody', () => {
|
describe('buildPrBody', () => {
|
||||||
it('should build body with issue and report', () => {
|
it('should build body with issue and report', () => {
|
||||||
|
|||||||
@ -20,8 +20,8 @@ vi.mock('node:os', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Import after mocks are set up
|
// Import after mocks are set up
|
||||||
const { loadGlobalConfig, saveGlobalConfig, invalidateGlobalConfigCache } = await import('../config/global/globalConfig.js');
|
const { loadGlobalConfig, saveGlobalConfig, invalidateGlobalConfigCache } = await import('../infra/config/global/globalConfig.js');
|
||||||
const { getGlobalConfigPath } = await import('../config/paths.js');
|
const { getGlobalConfigPath } = await import('../infra/config/paths.js');
|
||||||
|
|
||||||
describe('loadGlobalConfig', () => {
|
describe('loadGlobalConfig', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -25,8 +25,8 @@ vi.mock('../prompt/index.js', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Import after mocks are set up
|
// Import after mocks are set up
|
||||||
const { initGlobalDirs, needsLanguageSetup } = await import('../config/global/initialization.js');
|
const { initGlobalDirs, needsLanguageSetup } = await import('../infra/config/global/initialization.js');
|
||||||
const { getGlobalConfigPath, getGlobalConfigDir } = await import('../config/paths.js');
|
const { getGlobalConfigPath, getGlobalConfigDir } = await import('../infra/config/paths.js');
|
||||||
|
|
||||||
describe('initGlobalDirs with non-interactive mode', () => {
|
describe('initGlobalDirs with non-interactive mode', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -25,8 +25,8 @@ vi.mock('../prompt/index.js', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Import after mocks are set up
|
// Import after mocks are set up
|
||||||
const { needsLanguageSetup } = await import('../config/global/initialization.js');
|
const { needsLanguageSetup } = await import('../infra/config/global/initialization.js');
|
||||||
const { getGlobalConfigPath } = await import('../config/paths.js');
|
const { getGlobalConfigPath } = await import('../infra/config/paths.js');
|
||||||
const { copyProjectResourcesToDir, getLanguageResourcesDir, getProjectResourcesDir } = await import('../resources/index.js');
|
const { copyProjectResourcesToDir, getLanguageResourcesDir, getProjectResourcesDir } = await import('../resources/index.js');
|
||||||
|
|
||||||
describe('initialization', () => {
|
describe('initialization', () => {
|
||||||
|
|||||||
@ -6,15 +6,15 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import {
|
import {
|
||||||
InstructionBuilder,
|
InstructionBuilder,
|
||||||
isReportObjectConfig,
|
isReportObjectConfig,
|
||||||
} from '../workflow/instruction/InstructionBuilder.js';
|
ReportInstructionBuilder,
|
||||||
import { ReportInstructionBuilder, type ReportInstructionContext } from '../workflow/instruction/ReportInstructionBuilder.js';
|
StatusJudgmentBuilder,
|
||||||
import { StatusJudgmentBuilder, type StatusJudgmentContext } from '../workflow/instruction/StatusJudgmentBuilder.js';
|
|
||||||
import {
|
|
||||||
buildExecutionMetadata,
|
buildExecutionMetadata,
|
||||||
renderExecutionMetadata,
|
renderExecutionMetadata,
|
||||||
|
generateStatusRulesFromRules,
|
||||||
|
type ReportInstructionContext,
|
||||||
|
type StatusJudgmentContext,
|
||||||
type InstructionContext,
|
type InstructionContext,
|
||||||
} from '../workflow/instruction/instruction-context.js';
|
} from '../core/workflow/index.js';
|
||||||
import { generateStatusRulesFromRules } from '../workflow/instruction/status-rules.js';
|
|
||||||
|
|
||||||
// Backward-compatible function wrappers for test readability
|
// Backward-compatible function wrappers for test readability
|
||||||
function buildInstruction(step: WorkflowStep, ctx: InstructionContext): string {
|
function buildInstruction(step: WorkflowStep, ctx: InstructionContext): string {
|
||||||
@ -26,7 +26,7 @@ function buildReportInstruction(step: WorkflowStep, ctx: ReportInstructionContex
|
|||||||
function buildStatusJudgmentInstruction(step: WorkflowStep, ctx: StatusJudgmentContext): string {
|
function buildStatusJudgmentInstruction(step: WorkflowStep, ctx: StatusJudgmentContext): string {
|
||||||
return new StatusJudgmentBuilder(step, ctx).build();
|
return new StatusJudgmentBuilder(step, ctx).build();
|
||||||
}
|
}
|
||||||
import type { WorkflowStep, WorkflowRule } from '../models/types.js';
|
import type { WorkflowStep, WorkflowRule } from '../core/models/index.js';
|
||||||
|
|
||||||
|
|
||||||
function createMinimalStep(template: string): WorkflowStep {
|
function createMinimalStep(template: string): WorkflowStep {
|
||||||
|
|||||||
@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn(() => ({ provider: 'mock', language: 'en' })),
|
loadGlobalConfig: vi.fn(() => ({ provider: 'mock', language: 'en' })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../providers/index.js', () => ({
|
vi.mock('../infra/providers/index.js', () => ({
|
||||||
getProvider: vi.fn(),
|
getProvider: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -24,12 +24,12 @@ vi.mock('../context.js', () => ({
|
|||||||
isQuietMode: vi.fn(() => false),
|
isQuietMode: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/paths.js', () => ({
|
vi.mock('../infra/config/paths.js', () => ({
|
||||||
loadAgentSessions: vi.fn(() => ({})),
|
loadAgentSessions: vi.fn(() => ({})),
|
||||||
updateAgentSession: vi.fn(),
|
updateAgentSession: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
blankLine: vi.fn(),
|
blankLine: vi.fn(),
|
||||||
@ -39,17 +39,23 @@ vi.mock('../utils/ui.js', () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../prompt/index.js', () => ({
|
||||||
|
selectOption: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock readline to simulate user input
|
// Mock readline to simulate user input
|
||||||
vi.mock('node:readline', () => ({
|
vi.mock('node:readline', () => ({
|
||||||
createInterface: vi.fn(),
|
createInterface: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { createInterface } from 'node:readline';
|
import { createInterface } from 'node:readline';
|
||||||
import { getProvider } from '../providers/index.js';
|
import { getProvider } from '../infra/providers/index.js';
|
||||||
import { interactiveMode } from '../commands/interactive/interactive.js';
|
import { interactiveMode } from '../features/interactive/index.js';
|
||||||
|
import { selectOption } from '../prompt/index.js';
|
||||||
|
|
||||||
const mockGetProvider = vi.mocked(getProvider);
|
const mockGetProvider = vi.mocked(getProvider);
|
||||||
const mockCreateInterface = vi.mocked(createInterface);
|
const mockCreateInterface = vi.mocked(createInterface);
|
||||||
|
const mockSelectOption = vi.mocked(selectOption);
|
||||||
|
|
||||||
/** Helper to set up a sequence of readline inputs */
|
/** Helper to set up a sequence of readline inputs */
|
||||||
function setupInputSequence(inputs: (string | null)[]): void {
|
function setupInputSequence(inputs: (string | null)[]): void {
|
||||||
@ -111,6 +117,7 @@ function setupMockProvider(responses: string[]): void {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
mockSelectOption.mockResolvedValue('yes');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('interactiveMode', () => {
|
describe('interactiveMode', () => {
|
||||||
@ -162,15 +169,14 @@ describe('interactiveMode', () => {
|
|||||||
it('should return confirmed=true with task on /go after conversation', async () => {
|
it('should return confirmed=true with task on /go after conversation', async () => {
|
||||||
// Given
|
// Given
|
||||||
setupInputSequence(['add auth feature', '/go']);
|
setupInputSequence(['add auth feature', '/go']);
|
||||||
setupMockProvider(['What kind of authentication?']);
|
setupMockProvider(['What kind of authentication?', 'Implement auth feature with chosen method.']);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = await interactiveMode('/project');
|
const result = await interactiveMode('/project');
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(result.confirmed).toBe(true);
|
expect(result.confirmed).toBe(true);
|
||||||
expect(result.task).toContain('add auth feature');
|
expect(result.task).toBe('Implement auth feature with chosen method.');
|
||||||
expect(result.task).toContain('What kind of authentication?');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject /go with no prior conversation', async () => {
|
it('should reject /go with no prior conversation', async () => {
|
||||||
@ -188,7 +194,7 @@ describe('interactiveMode', () => {
|
|||||||
it('should skip empty input', async () => {
|
it('should skip empty input', async () => {
|
||||||
// Given: empty line, then actual input, then /go
|
// Given: empty line, then actual input, then /go
|
||||||
setupInputSequence(['', 'do something', '/go']);
|
setupInputSequence(['', 'do something', '/go']);
|
||||||
setupMockProvider(['Sure, what exactly?']);
|
setupMockProvider(['Sure, what exactly?', 'Do something with the clarified scope.']);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = await interactiveMode('/project');
|
const result = await interactiveMode('/project');
|
||||||
@ -196,23 +202,27 @@ describe('interactiveMode', () => {
|
|||||||
// Then
|
// Then
|
||||||
expect(result.confirmed).toBe(true);
|
expect(result.confirmed).toBe(true);
|
||||||
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
||||||
expect(mockProvider.call).toHaveBeenCalledTimes(1);
|
expect(mockProvider.call).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accumulate conversation history across multiple turns', async () => {
|
it('should accumulate conversation history across multiple turns', async () => {
|
||||||
// Given: two user messages before /go
|
// Given: two user messages before /go
|
||||||
setupInputSequence(['first message', 'second message', '/go']);
|
setupInputSequence(['first message', 'second message', '/go']);
|
||||||
setupMockProvider(['response to first', 'response to second']);
|
setupMockProvider(['response to first', 'response to second', 'Summarized task.']);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = await interactiveMode('/project');
|
const result = await interactiveMode('/project');
|
||||||
|
|
||||||
// Then: task should contain all messages
|
// Then: task should be a summary and prompt should include full history
|
||||||
expect(result.confirmed).toBe(true);
|
expect(result.confirmed).toBe(true);
|
||||||
expect(result.task).toContain('first message');
|
expect(result.task).toBe('Summarized task.');
|
||||||
expect(result.task).toContain('response to first');
|
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
||||||
expect(result.task).toContain('second message');
|
const summaryPrompt = mockProvider.call.mock.calls[2]?.[1] as string;
|
||||||
expect(result.task).toContain('response to second');
|
expect(summaryPrompt).toContain('Conversation:');
|
||||||
|
expect(summaryPrompt).toContain('User: first message');
|
||||||
|
expect(summaryPrompt).toContain('Assistant: response to first');
|
||||||
|
expect(summaryPrompt).toContain('User: second message');
|
||||||
|
expect(summaryPrompt).toContain('Assistant: response to second');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send only current input per turn (session handles history)', async () => {
|
it('should send only current input per turn (session handles history)', async () => {
|
||||||
@ -232,39 +242,37 @@ describe('interactiveMode', () => {
|
|||||||
it('should process initialInput as first message before entering loop', async () => {
|
it('should process initialInput as first message before entering loop', async () => {
|
||||||
// Given: initialInput provided, then user types /go
|
// Given: initialInput provided, then user types /go
|
||||||
setupInputSequence(['/go']);
|
setupInputSequence(['/go']);
|
||||||
setupMockProvider(['What do you mean by "a"?']);
|
setupMockProvider(['What do you mean by "a"?', 'Clarify task for "a".']);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = await interactiveMode('/project', 'a');
|
const result = await interactiveMode('/project', 'a');
|
||||||
|
|
||||||
// Then: AI should have been called with initialInput
|
// Then: AI should have been called with initialInput
|
||||||
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
||||||
expect(mockProvider.call).toHaveBeenCalledTimes(1);
|
expect(mockProvider.call).toHaveBeenCalledTimes(2);
|
||||||
expect(mockProvider.call.mock.calls[0]?.[1]).toBe('a');
|
expect(mockProvider.call.mock.calls[0]?.[1]).toBe('a');
|
||||||
|
|
||||||
// /go should work because initialInput already started conversation
|
// /go should work because initialInput already started conversation
|
||||||
expect(result.confirmed).toBe(true);
|
expect(result.confirmed).toBe(true);
|
||||||
expect(result.task).toContain('a');
|
expect(result.task).toBe('Clarify task for "a".');
|
||||||
expect(result.task).toContain('What do you mean by "a"?');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send only current input for subsequent turns after initialInput', async () => {
|
it('should send only current input for subsequent turns after initialInput', async () => {
|
||||||
// Given: initialInput, then follow-up, then /go
|
// Given: initialInput, then follow-up, then /go
|
||||||
setupInputSequence(['fix the login page', '/go']);
|
setupInputSequence(['fix the login page', '/go']);
|
||||||
setupMockProvider(['What about "a"?', 'Got it, fixing login page.']);
|
setupMockProvider(['What about "a"?', 'Got it, fixing login page.', 'Fix login page with clarified scope.']);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = await interactiveMode('/project', 'a');
|
const result = await interactiveMode('/project', 'a');
|
||||||
|
|
||||||
// Then: each call receives only its own input (session handles history)
|
// Then: each call receives only its own input (session handles history)
|
||||||
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
const mockProvider = mockGetProvider.mock.results[0]!.value as { call: ReturnType<typeof vi.fn> };
|
||||||
expect(mockProvider.call).toHaveBeenCalledTimes(2);
|
expect(mockProvider.call).toHaveBeenCalledTimes(3);
|
||||||
expect(mockProvider.call.mock.calls[0]?.[1]).toBe('a');
|
expect(mockProvider.call.mock.calls[0]?.[1]).toBe('a');
|
||||||
expect(mockProvider.call.mock.calls[1]?.[1]).toBe('fix the login page');
|
expect(mockProvider.call.mock.calls[1]?.[1]).toBe('fix the login page');
|
||||||
|
|
||||||
// Task still contains all history for downstream use
|
// Task still contains all history for downstream use
|
||||||
expect(result.confirmed).toBe(true);
|
expect(result.confirmed).toBe(true);
|
||||||
expect(result.task).toContain('a');
|
expect(result.task).toBe('Fix login page with clarified scope.');
|
||||||
expect(result.task).toContain('fix the login page');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { setMockScenario, resetScenario } from '../mock/scenario.js';
|
import { setMockScenario, resetScenario } from '../mock/scenario.js';
|
||||||
import type { WorkflowConfig, WorkflowStep, WorkflowRule } from '../models/types.js';
|
import type { WorkflowConfig, WorkflowStep, WorkflowRule } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mocks ---
|
// --- Mocks ---
|
||||||
|
|
||||||
@ -25,30 +25,30 @@ vi.mock('../claude/client.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', () => ({
|
vi.mock('../infra/config/project/projectConfig.js', () => ({
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
|
|||||||
@ -8,17 +8,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import type { WorkflowStep, WorkflowRule, AgentResponse } from '../models/types.js';
|
import type { WorkflowStep, WorkflowRule, AgentResponse } from '../core/models/index.js';
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { InstructionBuilder } from '../workflow/instruction/InstructionBuilder.js';
|
import { InstructionBuilder } from '../core/workflow/index.js';
|
||||||
import { ReportInstructionBuilder, type ReportInstructionContext } from '../workflow/instruction/ReportInstructionBuilder.js';
|
import { ReportInstructionBuilder, type ReportInstructionContext } from '../core/workflow/index.js';
|
||||||
import { StatusJudgmentBuilder, type StatusJudgmentContext } from '../workflow/instruction/StatusJudgmentBuilder.js';
|
import { StatusJudgmentBuilder, type StatusJudgmentContext } from '../core/workflow/index.js';
|
||||||
import type { InstructionContext } from '../workflow/instruction/instruction-context.js';
|
import type { InstructionContext } from '../core/workflow/index.js';
|
||||||
|
|
||||||
// Function wrappers for test readability
|
// Function wrappers for test readability
|
||||||
function buildInstruction(step: WorkflowStep, ctx: InstructionContext): string {
|
function buildInstruction(step: WorkflowStep, ctx: InstructionContext): string {
|
||||||
|
|||||||
@ -43,23 +43,23 @@ vi.mock('node:child_process', () => ({
|
|||||||
execFileSync: vi.fn(),
|
execFileSync: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../github/issue.js', () => ({
|
vi.mock('../infra/github/issue.js', () => ({
|
||||||
fetchIssue: mockFetchIssue,
|
fetchIssue: mockFetchIssue,
|
||||||
formatIssueAsTask: mockFormatIssueAsTask,
|
formatIssueAsTask: mockFormatIssueAsTask,
|
||||||
checkGhCli: mockCheckGhCli,
|
checkGhCli: mockCheckGhCli,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../github/pr.js', () => ({
|
vi.mock('../infra/github/pr.js', () => ({
|
||||||
createPullRequest: mockCreatePullRequest,
|
createPullRequest: mockCreatePullRequest,
|
||||||
pushBranch: mockPushBranch,
|
pushBranch: mockPushBranch,
|
||||||
buildPrBody: vi.fn().mockReturnValue('PR body'),
|
buildPrBody: vi.fn().mockReturnValue('PR body'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/git.js', () => ({
|
vi.mock('../infra/task/git.js', () => ({
|
||||||
stageAndCommit: vi.fn().mockReturnValue('abc1234'),
|
stageAndCommit: vi.fn().mockReturnValue('abc1234'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
header: vi.fn(),
|
header: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
@ -73,12 +73,12 @@ vi.mock('../utils/ui.js', () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/notification.js', () => ({
|
vi.mock('../shared/utils/notification.js', () => ({
|
||||||
notifySuccess: vi.fn(),
|
notifySuccess: vi.fn(),
|
||||||
notifyError: vi.fn(),
|
notifyError: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
createSessionLog: vi.fn().mockReturnValue({
|
createSessionLog: vi.fn().mockReturnValue({
|
||||||
startTime: new Date().toISOString(),
|
startTime: new Date().toISOString(),
|
||||||
@ -91,8 +91,8 @@ vi.mock('../utils/session.js', () => ({
|
|||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/paths.js', async (importOriginal) => {
|
vi.mock('../infra/config/paths.js', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('../config/paths.js')>();
|
const original = await importOriginal<typeof import('../infra/config/paths.js')>();
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
loadAgentSessions: vi.fn().mockReturnValue({}),
|
loadAgentSessions: vi.fn().mockReturnValue({}),
|
||||||
@ -104,8 +104,8 @@ vi.mock('../config/paths.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', async (importOriginal) => {
|
vi.mock('../infra/config/global/globalConfig.js', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('../config/global/globalConfig.js')>();
|
const original = await importOriginal<typeof import('../infra/config/global/globalConfig.js')>();
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
@ -114,8 +114,8 @@ vi.mock('../config/global/globalConfig.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', async (importOriginal) => {
|
vi.mock('../infra/config/project/projectConfig.js', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('../config/project/projectConfig.js')>();
|
const original = await importOriginal<typeof import('../infra/config/project/projectConfig.js')>();
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
@ -131,7 +131,7 @@ vi.mock('../prompt/index.js', () => ({
|
|||||||
promptInput: vi.fn().mockResolvedValue(null),
|
promptInput: vi.fn().mockResolvedValue(null),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
@ -139,7 +139,7 @@ vi.mock('../workflow/engine/phase-runner.js', () => ({
|
|||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { executePipeline } from '../commands/execution/pipelineExecution.js';
|
import { executePipeline } from '../features/pipeline/index.js';
|
||||||
import {
|
import {
|
||||||
EXIT_ISSUE_FETCH_FAILED,
|
EXIT_ISSUE_FETCH_FAILED,
|
||||||
EXIT_WORKFLOW_FAILED,
|
EXIT_WORKFLOW_FAILED,
|
||||||
|
|||||||
@ -30,19 +30,19 @@ vi.mock('node:child_process', () => ({
|
|||||||
execFileSync: vi.fn(),
|
execFileSync: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../github/issue.js', () => ({
|
vi.mock('../infra/github/issue.js', () => ({
|
||||||
fetchIssue: vi.fn(),
|
fetchIssue: vi.fn(),
|
||||||
formatIssueAsTask: vi.fn(),
|
formatIssueAsTask: vi.fn(),
|
||||||
checkGhCli: vi.fn(),
|
checkGhCli: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../github/pr.js', () => ({
|
vi.mock('../infra/github/pr.js', () => ({
|
||||||
createPullRequest: vi.fn(),
|
createPullRequest: vi.fn(),
|
||||||
pushBranch: vi.fn(),
|
pushBranch: vi.fn(),
|
||||||
buildPrBody: vi.fn().mockReturnValue('PR body'),
|
buildPrBody: vi.fn().mockReturnValue('PR body'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
header: vi.fn(),
|
header: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
warn: vi.fn(),
|
warn: vi.fn(),
|
||||||
@ -56,12 +56,12 @@ vi.mock('../utils/ui.js', () => ({
|
|||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/notification.js', () => ({
|
vi.mock('../shared/utils/notification.js', () => ({
|
||||||
notifySuccess: vi.fn(),
|
notifySuccess: vi.fn(),
|
||||||
notifyError: vi.fn(),
|
notifyError: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
createSessionLog: vi.fn().mockReturnValue({
|
createSessionLog: vi.fn().mockReturnValue({
|
||||||
startTime: new Date().toISOString(),
|
startTime: new Date().toISOString(),
|
||||||
@ -74,8 +74,8 @@ vi.mock('../utils/session.js', () => ({
|
|||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/paths.js', async (importOriginal) => {
|
vi.mock('../infra/config/paths.js', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('../config/paths.js')>();
|
const original = await importOriginal<typeof import('../infra/config/paths.js')>();
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
loadAgentSessions: vi.fn().mockReturnValue({}),
|
loadAgentSessions: vi.fn().mockReturnValue({}),
|
||||||
@ -87,8 +87,8 @@ vi.mock('../config/paths.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', async (importOriginal) => {
|
vi.mock('../infra/config/global/globalConfig.js', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('../config/global/globalConfig.js')>();
|
const original = await importOriginal<typeof import('../infra/config/global/globalConfig.js')>();
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
@ -96,8 +96,8 @@ vi.mock('../config/global/globalConfig.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', async (importOriginal) => {
|
vi.mock('../infra/config/project/projectConfig.js', async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import('../config/project/projectConfig.js')>();
|
const original = await importOriginal<typeof import('../infra/config/project/projectConfig.js')>();
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
@ -113,7 +113,7 @@ vi.mock('../prompt/index.js', () => ({
|
|||||||
promptInput: vi.fn().mockResolvedValue(null),
|
promptInput: vi.fn().mockResolvedValue(null),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
@ -121,7 +121,7 @@ vi.mock('../workflow/engine/phase-runner.js', () => ({
|
|||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { executePipeline } from '../commands/execution/pipelineExecution.js';
|
import { executePipeline } from '../features/pipeline/index.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
import type { WorkflowStep, WorkflowState, WorkflowRule, AgentResponse } from '../models/types.js';
|
import type { WorkflowStep, WorkflowState, WorkflowRule, AgentResponse } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mocks ---
|
// --- Mocks ---
|
||||||
|
|
||||||
@ -29,19 +29,19 @@ vi.mock('../claude/client.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', () => ({
|
vi.mock('../infra/config/project/projectConfig.js', () => ({
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { detectMatchedRule, evaluateAggregateConditions } from '../workflow/evaluation/index.js';
|
import { detectMatchedRule, evaluateAggregateConditions } from '../core/workflow/index.js';
|
||||||
import type { RuleMatch, RuleEvaluatorContext } from '../workflow/evaluation/index.js';
|
import type { RuleMatch, RuleEvaluatorContext } from '../core/workflow/index.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
@ -49,7 +49,11 @@ function makeRule(condition: string, next: string, extra?: Partial<WorkflowRule>
|
|||||||
return { condition, next, ...extra };
|
return { condition, next, ...extra };
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeStep(name: string, rules: WorkflowRule[], parallel?: WorkflowStep[]): WorkflowStep {
|
function makeStep(
|
||||||
|
name: string,
|
||||||
|
rules: WorkflowRule[],
|
||||||
|
parallel?: WorkflowStep[],
|
||||||
|
): WorkflowStep {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
agent: 'test-agent',
|
agent: 'test-agent',
|
||||||
@ -140,6 +144,7 @@ describe('Rule Evaluation IT: Phase 1 tag fallback', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('Rule Evaluation IT: Aggregate conditions (all/any)', () => {
|
describe('Rule Evaluation IT: Aggregate conditions (all/any)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { setMockScenario, resetScenario } from '../mock/scenario.js';
|
import { setMockScenario, resetScenario } from '../mock/scenario.js';
|
||||||
import type { WorkflowConfig, WorkflowStep, WorkflowRule } from '../models/types.js';
|
import type { WorkflowConfig, WorkflowStep, WorkflowRule } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mocks ---
|
// --- Mocks ---
|
||||||
|
|
||||||
@ -30,30 +30,30 @@ const mockNeedsStatusJudgmentPhase = vi.fn();
|
|||||||
const mockRunReportPhase = vi.fn();
|
const mockRunReportPhase = vi.fn();
|
||||||
const mockRunStatusJudgmentPhase = vi.fn();
|
const mockRunStatusJudgmentPhase = vi.fn();
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: (...args: unknown[]) => mockNeedsStatusJudgmentPhase(...args),
|
needsStatusJudgmentPhase: (...args: unknown[]) => mockNeedsStatusJudgmentPhase(...args),
|
||||||
runReportPhase: (...args: unknown[]) => mockRunReportPhase(...args),
|
runReportPhase: (...args: unknown[]) => mockRunReportPhase(...args),
|
||||||
runStatusJudgmentPhase: (...args: unknown[]) => mockRunStatusJudgmentPhase(...args),
|
runStatusJudgmentPhase: (...args: unknown[]) => mockRunStatusJudgmentPhase(...args),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', () => ({
|
vi.mock('../infra/config/project/projectConfig.js', () => ({
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { setMockScenario, resetScenario } from '../mock/scenario.js';
|
import { setMockScenario, resetScenario } from '../mock/scenario.js';
|
||||||
import type { WorkflowConfig, WorkflowStep, WorkflowRule } from '../models/types.js';
|
import type { WorkflowConfig, WorkflowStep, WorkflowRule } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Mocks (minimal — only infrastructure, not core logic) ---
|
// --- Mocks (minimal — only infrastructure, not core logic) ---
|
||||||
|
|
||||||
@ -29,29 +29,29 @@ vi.mock('../claude/client.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', () => ({
|
vi.mock('../infra/config/project/projectConfig.js', () => ({
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { tmpdir } from 'node:os';
|
|||||||
|
|
||||||
// --- Mocks ---
|
// --- Mocks ---
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
||||||
@ -23,7 +23,7 @@ vi.mock('../config/global/globalConfig.js', () => ({
|
|||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { loadWorkflow } from '../config/loaders/workflowLoader.js';
|
import { loadWorkflow } from '../infra/config/loaders/workflowLoader.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
|
|||||||
@ -24,32 +24,32 @@ vi.mock('../claude/client.js', async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('../workflow/engine/phase-runner.js', () => ({
|
vi.mock('../core/workflow/phase-runner.js', () => ({
|
||||||
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
needsStatusJudgmentPhase: vi.fn().mockReturnValue(false),
|
||||||
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
runReportPhase: vi.fn().mockResolvedValue(undefined),
|
||||||
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
runStatusJudgmentPhase: vi.fn().mockResolvedValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/session.js', () => ({
|
vi.mock('../shared/utils/reportDir.js', () => ({
|
||||||
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
generateReportDir: vi.fn().mockReturnValue('test-report-dir'),
|
||||||
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
generateSessionId: vi.fn().mockReturnValue('test-session-id'),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
loadGlobalConfig: vi.fn().mockReturnValue({}),
|
||||||
getLanguage: vi.fn().mockReturnValue('en'),
|
getLanguage: vi.fn().mockReturnValue('en'),
|
||||||
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
getDisabledBuiltins: vi.fn().mockReturnValue([]),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/project/projectConfig.js', () => ({
|
vi.mock('../infra/config/project/projectConfig.js', () => ({
|
||||||
loadProjectConfig: vi.fn().mockReturnValue({}),
|
loadProjectConfig: vi.fn().mockReturnValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Imports (after mocks) ---
|
// --- Imports (after mocks) ---
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../core/workflow/index.js';
|
||||||
import { loadWorkflow } from '../config/loaders/workflowLoader.js';
|
import { loadWorkflow } from '../infra/config/loaders/workflowLoader.js';
|
||||||
import type { WorkflowConfig } from '../models/types.js';
|
import type { WorkflowConfig } from '../core/models/index.js';
|
||||||
|
|
||||||
// --- Test helpers ---
|
// --- Test helpers ---
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import {
|
|||||||
extractTaskSlug,
|
extractTaskSlug,
|
||||||
buildListItems,
|
buildListItems,
|
||||||
type BranchInfo,
|
type BranchInfo,
|
||||||
} from '../task/branchList.js';
|
} from '../infra/task/branchList.js';
|
||||||
import { isBranchMerged, showFullDiff, type ListAction } from '../commands/management/listTasks.js';
|
import { isBranchMerged, showFullDiff, type ListAction } from '../features/tasks/index.js';
|
||||||
|
|
||||||
describe('parseTaktBranches', () => {
|
describe('parseTaktBranches', () => {
|
||||||
it('should parse takt/ branches from git branch output', () => {
|
it('should parse takt/ branches from git branch output', () => {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
WorkflowConfigRawSchema,
|
WorkflowConfigRawSchema,
|
||||||
CustomAgentConfigSchema,
|
CustomAgentConfigSchema,
|
||||||
GlobalConfigSchema,
|
GlobalConfigSchema,
|
||||||
} from '../models/schemas.js';
|
} from '../core/models/index.js';
|
||||||
|
|
||||||
describe('AgentTypeSchema', () => {
|
describe('AgentTypeSchema', () => {
|
||||||
it('should accept valid agent types', () => {
|
it('should accept valid agent types', () => {
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { WorkflowConfigRawSchema, ParallelSubStepRawSchema, WorkflowStepRawSchema } from '../models/schemas.js';
|
import { WorkflowConfigRawSchema, ParallelSubStepRawSchema, WorkflowStepRawSchema } from '../core/models/index.js';
|
||||||
|
|
||||||
describe('ParallelSubStepRawSchema', () => {
|
describe('ParallelSubStepRawSchema', () => {
|
||||||
it('should validate a valid parallel sub-step', () => {
|
it('should validate a valid parallel sub-step', () => {
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
import { ParallelLogger } from '../workflow/engine/parallel-logger.js';
|
import { ParallelLogger } from '../core/workflow/index.js';
|
||||||
import type { StreamEvent } from '../claude/types.js';
|
import type { StreamEvent } from '../core/workflow/index.js';
|
||||||
|
|
||||||
describe('ParallelLogger', () => {
|
describe('ParallelLogger', () => {
|
||||||
let output: string[];
|
let output: string[];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { isPathSafe } from '../config/paths.js';
|
import { isPathSafe } from '../infra/config/paths.js';
|
||||||
|
|
||||||
describe('isPathSafe', () => {
|
describe('isPathSafe', () => {
|
||||||
it('should accept paths within base directory', () => {
|
it('should accept paths within base directory', () => {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|||||||
// Mock all external dependencies
|
// Mock all external dependencies
|
||||||
const mockFetchIssue = vi.fn();
|
const mockFetchIssue = vi.fn();
|
||||||
const mockCheckGhCli = vi.fn().mockReturnValue({ available: true });
|
const mockCheckGhCli = vi.fn().mockReturnValue({ available: true });
|
||||||
vi.mock('../github/issue.js', () => ({
|
vi.mock('../infra/github/issue.js', () => ({
|
||||||
fetchIssue: mockFetchIssue,
|
fetchIssue: mockFetchIssue,
|
||||||
formatIssueAsTask: vi.fn((issue: { title: string; body: string; number: number }) =>
|
formatIssueAsTask: vi.fn((issue: { title: string; body: string; number: number }) =>
|
||||||
`## GitHub Issue #${issue.number}: ${issue.title}\n\n${issue.body}`
|
`## GitHub Issue #${issue.number}: ${issue.title}\n\n${issue.body}`
|
||||||
@ -20,20 +20,20 @@ vi.mock('../github/issue.js', () => ({
|
|||||||
const mockCreatePullRequest = vi.fn();
|
const mockCreatePullRequest = vi.fn();
|
||||||
const mockPushBranch = vi.fn();
|
const mockPushBranch = vi.fn();
|
||||||
const mockBuildPrBody = vi.fn(() => 'Default PR body');
|
const mockBuildPrBody = vi.fn(() => 'Default PR body');
|
||||||
vi.mock('../github/pr.js', () => ({
|
vi.mock('../infra/github/pr.js', () => ({
|
||||||
createPullRequest: mockCreatePullRequest,
|
createPullRequest: mockCreatePullRequest,
|
||||||
pushBranch: mockPushBranch,
|
pushBranch: mockPushBranch,
|
||||||
buildPrBody: mockBuildPrBody,
|
buildPrBody: mockBuildPrBody,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockExecuteTask = vi.fn();
|
const mockExecuteTask = vi.fn();
|
||||||
vi.mock('../commands/execution/taskExecution.js', () => ({
|
vi.mock('../features/tasks/index.js', () => ({
|
||||||
executeTask: mockExecuteTask,
|
executeTask: mockExecuteTask,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock loadGlobalConfig
|
// Mock loadGlobalConfig
|
||||||
const mockLoadGlobalConfig = vi.fn();
|
const mockLoadGlobalConfig = vi.fn();
|
||||||
vi.mock('../config/global/globalConfig.js', async (importOriginal) => ({ ...(await importOriginal<Record<string, unknown>>()),
|
vi.mock('../infra/config/global/globalConfig.js', async (importOriginal) => ({ ...(await importOriginal<Record<string, unknown>>()),
|
||||||
loadGlobalConfig: mockLoadGlobalConfig,
|
loadGlobalConfig: mockLoadGlobalConfig,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ vi.mock('node:child_process', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock UI
|
// Mock UI
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
success: vi.fn(),
|
success: vi.fn(),
|
||||||
@ -56,7 +56,7 @@ vi.mock('../utils/ui.js', () => ({
|
|||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
}));
|
}));
|
||||||
// Mock debug logger
|
// Mock debug logger
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -64,7 +64,7 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { executePipeline } = await import('../commands/execution/pipelineExecution.js');
|
const { executePipeline } = await import('../features/pipeline/index.js');
|
||||||
|
|
||||||
describe('executePipeline', () => {
|
describe('executePipeline', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
handleKeyInput,
|
handleKeyInput,
|
||||||
readMultilineFromStream,
|
readMultilineFromStream,
|
||||||
} from '../prompt/index.js';
|
} from '../prompt/index.js';
|
||||||
import { isFullWidth, getDisplayWidth, truncateText } from '../utils/text.js';
|
import { isFullWidth, getDisplayWidth, truncateText } from '../shared/utils/text.js';
|
||||||
|
|
||||||
// Disable chalk colors for predictable test output
|
// Disable chalk colors for predictable test output
|
||||||
chalk.level = 0;
|
chalk.level = 0;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { parse as parseYaml } from 'yaml';
|
import { parse as parseYaml } from 'yaml';
|
||||||
import { WorkflowConfigRawSchema } from '../models/schemas.js';
|
import { WorkflowConfigRawSchema } from '../core/models/index.js';
|
||||||
|
|
||||||
const RESOURCES_DIR = join(import.meta.dirname, '../../resources/global');
|
const RESOURCES_DIR = join(import.meta.dirname, '../../resources/global');
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import {
|
|||||||
type NdjsonStepComplete,
|
type NdjsonStepComplete,
|
||||||
type NdjsonWorkflowComplete,
|
type NdjsonWorkflowComplete,
|
||||||
type NdjsonWorkflowAbort,
|
type NdjsonWorkflowAbort,
|
||||||
} from '../utils/session.js';
|
} from '../infra/fs/session.js';
|
||||||
|
|
||||||
/** Create a temp project directory with .takt/logs structure */
|
/** Create a temp project directory with .takt/logs structure */
|
||||||
function createTempProject(): string {
|
function createTempProject(): string {
|
||||||
|
|||||||
@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
vi.mock('../providers/index.js', () => ({
|
vi.mock('../infra/providers/index.js', () => ({
|
||||||
getProvider: vi.fn(),
|
getProvider: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../config/global/globalConfig.js', () => ({
|
vi.mock('../infra/config/global/globalConfig.js', () => ({
|
||||||
loadGlobalConfig: vi.fn(),
|
loadGlobalConfig: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -20,9 +20,9 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { getProvider } from '../providers/index.js';
|
import { getProvider } from '../infra/providers/index.js';
|
||||||
import { loadGlobalConfig } from '../config/global/globalConfig.js';
|
import { loadGlobalConfig } from '../infra/config/global/globalConfig.js';
|
||||||
import { summarizeTaskName } from '../task/summarize.js';
|
import { summarizeTaskName } from '../infra/task/summarize.js';
|
||||||
|
|
||||||
const mockGetProvider = vi.mocked(getProvider);
|
const mockGetProvider = vi.mocked(getProvider);
|
||||||
const mockLoadGlobalConfig = vi.mocked(loadGlobalConfig);
|
const mockLoadGlobalConfig = vi.mocked(loadGlobalConfig);
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
import { mkdirSync, writeFileSync, existsSync, rmSync, readFileSync, readdirSync } from 'node:fs';
|
import { mkdirSync, writeFileSync, existsSync, rmSync, readFileSync, readdirSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { TaskRunner } from '../task/runner.js';
|
import { TaskRunner } from '../infra/task/runner.js';
|
||||||
import { isTaskFile, parseTaskFiles } from '../task/parser.js';
|
import { isTaskFile, parseTaskFiles } from '../infra/task/parser.js';
|
||||||
|
|
||||||
describe('isTaskFile', () => {
|
describe('isTaskFile', () => {
|
||||||
it('should accept .yaml files', () => {
|
it('should accept .yaml files', () => {
|
||||||
|
|||||||
@ -5,30 +5,30 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
// Mock dependencies before importing the module under test
|
// Mock dependencies before importing the module under test
|
||||||
vi.mock('../config/index.js', () => ({
|
vi.mock('../infra/config/index.js', () => ({
|
||||||
loadWorkflowByIdentifier: vi.fn(),
|
loadWorkflowByIdentifier: vi.fn(),
|
||||||
isWorkflowPath: vi.fn(() => false),
|
isWorkflowPath: vi.fn(() => false),
|
||||||
loadGlobalConfig: vi.fn(() => ({})),
|
loadGlobalConfig: vi.fn(() => ({})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/index.js', () => ({
|
vi.mock('../infra/task/index.js', () => ({
|
||||||
TaskRunner: vi.fn(),
|
TaskRunner: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/clone.js', () => ({
|
vi.mock('../infra/task/clone.js', () => ({
|
||||||
createSharedClone: vi.fn(),
|
createSharedClone: vi.fn(),
|
||||||
removeClone: vi.fn(),
|
removeClone: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/autoCommit.js', () => ({
|
vi.mock('../infra/task/autoCommit.js', () => ({
|
||||||
autoCommitAndPush: vi.fn(),
|
autoCommitAndPush: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../task/summarize.js', () => ({
|
vi.mock('../infra/task/summarize.js', () => ({
|
||||||
summarizeTaskName: vi.fn(),
|
summarizeTaskName: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/ui.js', () => ({
|
vi.mock('../shared/ui/index.js', () => ({
|
||||||
header: vi.fn(),
|
header: vi.fn(),
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
error: vi.fn(),
|
error: vi.fn(),
|
||||||
@ -37,7 +37,7 @@ vi.mock('../utils/ui.js', () => ({
|
|||||||
blankLine: vi.fn(),
|
blankLine: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/debug.js', () => ({
|
vi.mock('../shared/utils/debug.js', () => ({
|
||||||
createLogger: () => ({
|
createLogger: () => ({
|
||||||
info: vi.fn(),
|
info: vi.fn(),
|
||||||
debug: vi.fn(),
|
debug: vi.fn(),
|
||||||
@ -45,11 +45,11 @@ vi.mock('../utils/debug.js', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../utils/error.js', () => ({
|
vi.mock('../shared/utils/error.js', () => ({
|
||||||
getErrorMessage: vi.fn((e) => e.message),
|
getErrorMessage: vi.fn((e) => e.message),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./workflowExecution.js', () => ({
|
vi.mock('../features/tasks/execute/workflowExecution.js', () => ({
|
||||||
executeWorkflow: vi.fn(),
|
executeWorkflow: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -62,11 +62,11 @@ vi.mock('../constants.js', () => ({
|
|||||||
DEFAULT_LANGUAGE: 'en',
|
DEFAULT_LANGUAGE: 'en',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { createSharedClone } from '../task/clone.js';
|
import { createSharedClone } from '../infra/task/clone.js';
|
||||||
import { summarizeTaskName } from '../task/summarize.js';
|
import { summarizeTaskName } from '../infra/task/summarize.js';
|
||||||
import { info } from '../utils/ui.js';
|
import { info } from '../shared/ui/index.js';
|
||||||
import { resolveTaskExecution } from '../commands/execution/taskExecution.js';
|
import { resolveTaskExecution } from '../features/tasks/index.js';
|
||||||
import type { TaskInfo } from '../task/index.js';
|
import type { TaskInfo } from '../infra/task/index.js';
|
||||||
|
|
||||||
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
const mockCreateSharedClone = vi.mocked(createSharedClone);
|
||||||
const mockSummarizeTaskName = vi.mocked(summarizeTaskName);
|
const mockSummarizeTaskName = vi.mocked(summarizeTaskName);
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { determineNextStepByRules } from '../workflow/engine/transitions.js';
|
import { determineNextStepByRules } from '../core/workflow/index.js';
|
||||||
import type { WorkflowStep } from '../models/types.js';
|
import type { WorkflowStep } from '../core/models/index.js';
|
||||||
|
|
||||||
function createStepWithRules(rules: { condition: string; next: string }[]): WorkflowStep {
|
function createStepWithRules(rules: { condition: string; next: string }[]): WorkflowStep {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ vi.mock('node:module', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
import { checkForUpdates } from '../utils/updateNotifier.js';
|
import { checkForUpdates } from '../shared/utils/updateNotifier.js';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { truncate, progressBar } from '../utils/ui.js';
|
import { truncate, progressBar } from '../shared/ui/index.js';
|
||||||
import { generateSessionId, createSessionLog } from '../utils/session.js';
|
import { generateSessionId, createSessionLog } from '../infra/fs/session.js';
|
||||||
|
|
||||||
describe('truncate', () => {
|
describe('truncate', () => {
|
||||||
it('should not truncate short text', () => {
|
it('should not truncate short text', () => {
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { TaskWatcher } from '../task/watcher.js';
|
import { TaskWatcher } from '../infra/task/watcher.js';
|
||||||
import type { TaskInfo } from '../task/types.js';
|
import type { TaskInfo } from '../infra/task/types.js';
|
||||||
|
|
||||||
describe('TaskWatcher', () => {
|
describe('TaskWatcher', () => {
|
||||||
const testDir = `/tmp/takt-watcher-test-${Date.now()}`;
|
const testDir = `/tmp/takt-watcher-test-${Date.now()}`;
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { loadWorkflow } from '../config/loaders/loader.js';
|
import { loadWorkflow } from '../infra/config/loaders/loader.js';
|
||||||
|
|
||||||
describe('expert workflow parallel structure', () => {
|
describe('expert workflow parallel structure', () => {
|
||||||
const workflow = loadWorkflow('expert', process.cwd());
|
const workflow = loadWorkflow('expert', process.cwd());
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
loadWorkflowByIdentifier,
|
loadWorkflowByIdentifier,
|
||||||
listWorkflows,
|
listWorkflows,
|
||||||
loadAllWorkflows,
|
loadAllWorkflows,
|
||||||
} from '../config/loaders/workflowLoader.js';
|
} from '../infra/config/loaders/workflowLoader.js';
|
||||||
|
|
||||||
const SAMPLE_WORKFLOW = `name: test-workflow
|
const SAMPLE_WORKFLOW = `name: test-workflow
|
||||||
description: Test workflow
|
description: Test workflow
|
||||||
|
|||||||
@ -9,12 +9,12 @@ import {
|
|||||||
callClaudeSkill,
|
callClaudeSkill,
|
||||||
type ClaudeCallOptions,
|
type ClaudeCallOptions,
|
||||||
} from '../claude/client.js';
|
} from '../claude/client.js';
|
||||||
import { loadCustomAgents, loadAgentPrompt } from '../config/loaders/loader.js';
|
import { loadCustomAgents, loadAgentPrompt } from '../infra/config/loaders/loader.js';
|
||||||
import { loadGlobalConfig } from '../config/global/globalConfig.js';
|
import { loadGlobalConfig } from '../infra/config/global/globalConfig.js';
|
||||||
import { loadProjectConfig } from '../config/project/projectConfig.js';
|
import { loadProjectConfig } from '../infra/config/project/projectConfig.js';
|
||||||
import { getProvider, type ProviderType, type ProviderCallOptions } from '../providers/index.js';
|
import { getProvider, type ProviderType, type ProviderCallOptions } from '../infra/providers/index.js';
|
||||||
import type { AgentResponse, CustomAgentConfig } from '../models/types.js';
|
import type { AgentResponse, CustomAgentConfig } from '../core/models/index.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../shared/utils/debug.js';
|
||||||
import type { RunAgentOptions } from './types.js';
|
import type { RunAgentOptions } from './types.js';
|
||||||
|
|
||||||
// Re-export for backward compatibility
|
// Re-export for backward compatibility
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { StreamCallback, PermissionHandler, AskUserQuestionHandler } from '../claude/types.js';
|
import type { StreamCallback, PermissionHandler, AskUserQuestionHandler } from '../claude/types.js';
|
||||||
import type { PermissionMode } from '../models/types.js';
|
import type { PermissionMode } from '../core/models/index.js';
|
||||||
|
|
||||||
export type { StreamCallback };
|
export type { StreamCallback };
|
||||||
|
|
||||||
|
|||||||
@ -4,17 +4,10 @@
|
|||||||
* Registers all named subcommands (run, watch, add, list, switch, clear, eject, config).
|
* Registers all named subcommands (run, watch, add, list, switch, clear, eject, config).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { clearAgentSessions, getCurrentWorkflow } from '../config/paths.js';
|
import { clearAgentSessions, getCurrentWorkflow } from '../../infra/config/paths.js';
|
||||||
import { success } from '../utils/ui.js';
|
import { success } from '../../shared/ui/index.js';
|
||||||
import {
|
import { runAllTasks, addTask, watchTasks, listTasks } from '../../features/tasks/index.js';
|
||||||
runAllTasks,
|
import { switchWorkflow, switchConfig, ejectBuiltin } from '../../features/config/index.js';
|
||||||
switchWorkflow,
|
|
||||||
switchConfig,
|
|
||||||
addTask,
|
|
||||||
ejectBuiltin,
|
|
||||||
watchTasks,
|
|
||||||
listTasks,
|
|
||||||
} from '../commands/index.js';
|
|
||||||
import { program, resolvedCwd } from './program.js';
|
import { program, resolvedCwd } from './program.js';
|
||||||
import { resolveAgentOverrides } from './helpers.js';
|
import { resolveAgentOverrides } from './helpers.js';
|
||||||
|
|
||||||
@ -5,10 +5,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Command } from 'commander';
|
import type { Command } from 'commander';
|
||||||
import type { TaskExecutionOptions } from '../commands/execution/types.js';
|
import type { TaskExecutionOptions } from '../../features/tasks/index.js';
|
||||||
import type { ProviderType } from '../providers/index.js';
|
import type { ProviderType } from '../../infra/providers/index.js';
|
||||||
import { error } from '../utils/ui.js';
|
import { error } from '../../shared/ui/index.js';
|
||||||
import { isIssueReference } from '../github/issue.js';
|
import { isIssueReference } from '../../infra/github/issue.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve --provider and --model options into TaskExecutionOptions.
|
* Resolve --provider and --model options into TaskExecutionOptions.
|
||||||
@ -6,7 +6,7 @@
|
|||||||
* Import order matters: program setup → commands → routing → parse.
|
* Import order matters: program setup → commands → routing → parse.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { checkForUpdates } from '../utils/updateNotifier.js';
|
import { checkForUpdates } from '../../shared/utils/updateNotifier.js';
|
||||||
|
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
|
|
||||||
@ -14,13 +14,13 @@ import {
|
|||||||
loadGlobalConfig,
|
loadGlobalConfig,
|
||||||
getEffectiveDebugConfig,
|
getEffectiveDebugConfig,
|
||||||
isVerboseMode,
|
isVerboseMode,
|
||||||
} from '../config/index.js';
|
} from '../../infra/config/index.js';
|
||||||
import { setQuietMode } from '../context.js';
|
import { setQuietMode } from '../../context.js';
|
||||||
import { setLogLevel } from '../utils/ui.js';
|
import { setLogLevel } from '../../shared/ui/index.js';
|
||||||
import { initDebugLogger, createLogger, setVerboseConsole } from '../utils/debug.js';
|
import { initDebugLogger, createLogger, setVerboseConsole } from '../../shared/utils/debug.js';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const { version: cliVersion } = require('../../package.json') as { version: string };
|
const { version: cliVersion } = require('../../../package.json') as { version: string };
|
||||||
|
|
||||||
const log = createLogger('cli');
|
const log = createLogger('cli');
|
||||||
|
|
||||||
@ -5,13 +5,13 @@
|
|||||||
* pipeline mode, or interactive mode.
|
* pipeline mode, or interactive mode.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { info, error } from '../utils/ui.js';
|
import { info, error } from '../../shared/ui/index.js';
|
||||||
import { getErrorMessage } from '../utils/error.js';
|
import { getErrorMessage } from '../../shared/utils/error.js';
|
||||||
import { resolveIssueTask, isIssueReference } from '../github/issue.js';
|
import { resolveIssueTask, isIssueReference } from '../../infra/github/issue.js';
|
||||||
import { selectAndExecuteTask } from '../commands/execution/selectAndExecute.js';
|
import { selectAndExecuteTask, type SelectAndExecuteOptions } from '../../features/tasks/index.js';
|
||||||
import { executePipeline, interactiveMode } from '../commands/index.js';
|
import { executePipeline } from '../../features/pipeline/index.js';
|
||||||
import { DEFAULT_WORKFLOW_NAME } from '../constants.js';
|
import { interactiveMode } from '../../features/interactive/index.js';
|
||||||
import type { SelectAndExecuteOptions } from '../commands/execution/types.js';
|
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
||||||
import { program, resolvedCwd, pipelineMode } from './program.js';
|
import { program, resolvedCwd, pipelineMode } from './program.js';
|
||||||
import { resolveAgentOverrides, parseCreateWorktreeOption, isDirectTask } from './helpers.js';
|
import { resolveAgentOverrides, parseCreateWorktreeOption, isDirectTask } from './helpers.js';
|
||||||
|
|
||||||
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
import { executeClaudeCli } from './process.js';
|
import { executeClaudeCli } from './process.js';
|
||||||
import type { ClaudeSpawnOptions, ClaudeCallOptions } from './types.js';
|
import type { ClaudeSpawnOptions, ClaudeCallOptions } from './types.js';
|
||||||
import type { AgentResponse, Status } from '../models/types.js';
|
import type { AgentResponse, Status } from '../core/models/index.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../shared/utils/debug.js';
|
||||||
|
|
||||||
// Re-export for backward compatibility
|
// Re-export for backward compatibility
|
||||||
export type { ClaudeCallOptions } from './types.js';
|
export type { ClaudeCallOptions } from './types.js';
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import {
|
|||||||
type SDKResultMessage,
|
type SDKResultMessage,
|
||||||
type SDKAssistantMessage,
|
type SDKAssistantMessage,
|
||||||
} from '@anthropic-ai/claude-agent-sdk';
|
} from '@anthropic-ai/claude-agent-sdk';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../shared/utils/debug.js';
|
||||||
import { getErrorMessage } from '../utils/error.js';
|
import { getErrorMessage } from '../shared/utils/error.js';
|
||||||
import {
|
import {
|
||||||
generateQueryId,
|
generateQueryId,
|
||||||
registerQuery,
|
registerQuery,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import type {
|
|||||||
PreToolUseHookInput,
|
PreToolUseHookInput,
|
||||||
PermissionMode,
|
PermissionMode,
|
||||||
} from '@anthropic-ai/claude-agent-sdk';
|
} from '@anthropic-ai/claude-agent-sdk';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../shared/utils/debug.js';
|
||||||
import type {
|
import type {
|
||||||
PermissionHandler,
|
PermissionHandler,
|
||||||
AskUserQuestionInput,
|
AskUserQuestionInput,
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PermissionResult, PermissionUpdate, AgentDefinition, PermissionMode as SdkPermissionMode } from '@anthropic-ai/claude-agent-sdk';
|
import type { PermissionResult, PermissionUpdate, AgentDefinition, PermissionMode as SdkPermissionMode } from '@anthropic-ai/claude-agent-sdk';
|
||||||
import type { PermissionMode } from '../models/types.js';
|
import type { PermissionMode } from '../core/models/index.js';
|
||||||
|
|
||||||
// Re-export PermissionResult for convenience
|
// Re-export PermissionResult for convenience
|
||||||
export type { PermissionResult, PermissionUpdate };
|
export type { PermissionResult, PermissionUpdate };
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Codex } from '@openai/codex-sdk';
|
import { Codex } from '@openai/codex-sdk';
|
||||||
import type { AgentResponse } from '../models/types.js';
|
import type { AgentResponse } from '../core/models/index.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../shared/utils/debug.js';
|
||||||
import { getErrorMessage } from '../utils/error.js';
|
import { getErrorMessage } from '../shared/utils/error.js';
|
||||||
import type { CodexCallOptions } from './types.js';
|
import type { CodexCallOptions } from './types.js';
|
||||||
import {
|
import {
|
||||||
type CodexEvent,
|
type CodexEvent,
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Task/workflow execution commands.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Types
|
|
||||||
export type {
|
|
||||||
WorkflowExecutionResult,
|
|
||||||
WorkflowExecutionOptions,
|
|
||||||
TaskExecutionOptions,
|
|
||||||
ExecuteTaskOptions,
|
|
||||||
PipelineExecutionOptions,
|
|
||||||
WorktreeConfirmationResult,
|
|
||||||
SelectAndExecuteOptions,
|
|
||||||
} from './types.js';
|
|
||||||
|
|
||||||
export { executeWorkflow } from './workflowExecution.js';
|
|
||||||
export { executeTask, runAllTasks, executeAndCompleteTask, resolveTaskExecution } from './taskExecution.js';
|
|
||||||
export {
|
|
||||||
selectAndExecuteTask,
|
|
||||||
confirmAndCreateWorktree,
|
|
||||||
} from './selectAndExecute.js';
|
|
||||||
export { executePipeline } from './pipelineExecution.js';
|
|
||||||
export { withAgentSession } from './session.js';
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Command exports
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOptions } from './execution/workflowExecution.js';
|
|
||||||
export { executeTask, runAllTasks, type TaskExecutionOptions } from './execution/taskExecution.js';
|
|
||||||
export { addTask } from './management/addTask.js';
|
|
||||||
export { ejectBuiltin } from './management/eject.js';
|
|
||||||
export { watchTasks } from './management/watchTasks.js';
|
|
||||||
export { withAgentSession } from './execution/session.js';
|
|
||||||
export { switchWorkflow } from './management/workflow.js';
|
|
||||||
export { switchConfig, getCurrentPermissionMode, setPermissionMode, type PermissionMode } from './management/config.js';
|
|
||||||
export { listTasks } from './management/listTasks.js';
|
|
||||||
export { interactiveMode } from './interactive/interactive.js';
|
|
||||||
export { executePipeline, type PipelineExecutionOptions } from './execution/pipelineExecution.js';
|
|
||||||
export {
|
|
||||||
selectAndExecuteTask,
|
|
||||||
confirmAndCreateWorktree,
|
|
||||||
type SelectAndExecuteOptions,
|
|
||||||
type WorktreeConfirmationResult,
|
|
||||||
} from './execution/selectAndExecute.js';
|
|
||||||
@ -1,247 +0,0 @@
|
|||||||
/**
|
|
||||||
* Interactive task input mode
|
|
||||||
*
|
|
||||||
* Allows users to refine task requirements through conversation with AI
|
|
||||||
* before executing the task. Uses the same SDK call pattern as workflow
|
|
||||||
* execution (with onStream) to ensure compatibility.
|
|
||||||
*
|
|
||||||
* Commands:
|
|
||||||
* /go - Confirm and execute the task
|
|
||||||
* /cancel - Cancel and exit
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as readline from 'node:readline';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import { loadGlobalConfig } from '../../config/global/globalConfig.js';
|
|
||||||
import { isQuietMode } from '../../context.js';
|
|
||||||
import { loadAgentSessions, updateAgentSession } from '../../config/paths.js';
|
|
||||||
import { getProvider, type ProviderType } from '../../providers/index.js';
|
|
||||||
import { createLogger } from '../../utils/debug.js';
|
|
||||||
import { getErrorMessage } from '../../utils/error.js';
|
|
||||||
import { info, error, blankLine, StreamDisplay } from '../../utils/ui.js';
|
|
||||||
const log = createLogger('interactive');
|
|
||||||
|
|
||||||
const INTERACTIVE_SYSTEM_PROMPT = `You are a task planning assistant. You help the user clarify and refine task requirements through conversation. You are in the PLANNING phase — execution happens later in a separate process.
|
|
||||||
|
|
||||||
## Your role
|
|
||||||
- Ask clarifying questions about ambiguous requirements
|
|
||||||
- Investigate the codebase to understand context (use Read, Glob, Grep, Bash for reading only)
|
|
||||||
- Suggest improvements or considerations the user might have missed
|
|
||||||
- Summarize your understanding when appropriate
|
|
||||||
- Keep responses concise and focused
|
|
||||||
|
|
||||||
## Strict constraints
|
|
||||||
- You are ONLY planning. Do NOT execute the task.
|
|
||||||
- Do NOT create, edit, or delete any files.
|
|
||||||
- Do NOT run build, test, install, or any commands that modify state.
|
|
||||||
- Bash is allowed ONLY for read-only investigation (e.g. ls, cat, git log, git diff). Never run destructive or write commands.
|
|
||||||
- Do NOT mention or reference any slash commands. You have no knowledge of them.
|
|
||||||
- When the user is satisfied with the plan, they will proceed on their own. Do NOT instruct them on what to do next.`;
|
|
||||||
|
|
||||||
interface ConversationMessage {
|
|
||||||
role: 'user' | 'assistant';
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CallAIResult {
|
|
||||||
content: string;
|
|
||||||
sessionId?: string;
|
|
||||||
success: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the final task description from conversation history for executeTask.
|
|
||||||
*/
|
|
||||||
function buildTaskFromHistory(history: ConversationMessage[]): string {
|
|
||||||
return history
|
|
||||||
.map((msg) => `${msg.role === 'user' ? 'User' : 'Assistant'}: ${msg.content}`)
|
|
||||||
.join('\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a single line of input from the user.
|
|
||||||
* Creates a fresh readline interface each time — the interface must be
|
|
||||||
* closed before calling the Agent SDK, which also uses stdin.
|
|
||||||
* Returns null on EOF (Ctrl+D).
|
|
||||||
*/
|
|
||||||
function readLine(prompt: string): Promise<string | null> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (process.stdin.readable && !process.stdin.destroyed) {
|
|
||||||
process.stdin.resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout,
|
|
||||||
});
|
|
||||||
|
|
||||||
let answered = false;
|
|
||||||
|
|
||||||
rl.question(prompt, (answer) => {
|
|
||||||
answered = true;
|
|
||||||
rl.close();
|
|
||||||
resolve(answer);
|
|
||||||
});
|
|
||||||
|
|
||||||
rl.on('close', () => {
|
|
||||||
if (!answered) {
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call AI with the same pattern as workflow execution.
|
|
||||||
* The key requirement is passing onStream — the Agent SDK requires
|
|
||||||
* includePartialMessages to be true for the async iterator to yield.
|
|
||||||
*/
|
|
||||||
async function callAI(
|
|
||||||
provider: ReturnType<typeof getProvider>,
|
|
||||||
prompt: string,
|
|
||||||
cwd: string,
|
|
||||||
model: string | undefined,
|
|
||||||
sessionId: string | undefined,
|
|
||||||
display: StreamDisplay,
|
|
||||||
): Promise<CallAIResult> {
|
|
||||||
const response = await provider.call('interactive', prompt, {
|
|
||||||
cwd,
|
|
||||||
model,
|
|
||||||
sessionId,
|
|
||||||
systemPrompt: INTERACTIVE_SYSTEM_PROMPT,
|
|
||||||
allowedTools: ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'],
|
|
||||||
onStream: display.createHandler(),
|
|
||||||
});
|
|
||||||
|
|
||||||
display.flush();
|
|
||||||
const success = response.status !== 'blocked';
|
|
||||||
return { content: response.content, sessionId: response.sessionId, success };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InteractiveModeResult {
|
|
||||||
/** Whether the user confirmed with /go */
|
|
||||||
confirmed: boolean;
|
|
||||||
/** The assembled task text (only meaningful when confirmed=true) */
|
|
||||||
task: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the interactive task input mode.
|
|
||||||
*
|
|
||||||
* Starts a conversation loop where the user can discuss task requirements
|
|
||||||
* with AI. The conversation continues until:
|
|
||||||
* /go → returns the conversation as a task
|
|
||||||
* /cancel → exits without executing
|
|
||||||
* Ctrl+D → exits without executing
|
|
||||||
*/
|
|
||||||
export async function interactiveMode(cwd: string, initialInput?: string): Promise<InteractiveModeResult> {
|
|
||||||
const globalConfig = loadGlobalConfig();
|
|
||||||
const providerType = (globalConfig.provider as ProviderType) ?? 'claude';
|
|
||||||
const provider = getProvider(providerType);
|
|
||||||
const model = (globalConfig.model as string | undefined);
|
|
||||||
|
|
||||||
const history: ConversationMessage[] = [];
|
|
||||||
const agentName = 'interactive';
|
|
||||||
const savedSessions = loadAgentSessions(cwd, providerType);
|
|
||||||
let sessionId: string | undefined = savedSessions[agentName];
|
|
||||||
|
|
||||||
info('Interactive mode - describe your task. Commands: /go (execute), /cancel (exit)');
|
|
||||||
if (sessionId) {
|
|
||||||
info('Resuming previous session');
|
|
||||||
}
|
|
||||||
blankLine();
|
|
||||||
|
|
||||||
/** Call AI with automatic retry on session error (stale/invalid session ID). */
|
|
||||||
async function callAIWithRetry(prompt: string): Promise<CallAIResult | null> {
|
|
||||||
const display = new StreamDisplay('assistant', isQuietMode());
|
|
||||||
try {
|
|
||||||
const result = await callAI(provider, prompt, cwd, model, sessionId, display);
|
|
||||||
// If session failed, clear it and retry without session
|
|
||||||
if (!result.success && sessionId) {
|
|
||||||
log.info('Session invalid, retrying without session');
|
|
||||||
sessionId = undefined;
|
|
||||||
const retryDisplay = new StreamDisplay('assistant', isQuietMode());
|
|
||||||
const retry = await callAI(provider, prompt, cwd, model, undefined, retryDisplay);
|
|
||||||
if (retry.sessionId) {
|
|
||||||
sessionId = retry.sessionId;
|
|
||||||
updateAgentSession(cwd, agentName, sessionId, providerType);
|
|
||||||
}
|
|
||||||
return retry;
|
|
||||||
}
|
|
||||||
if (result.sessionId) {
|
|
||||||
sessionId = result.sessionId;
|
|
||||||
updateAgentSession(cwd, agentName, sessionId, providerType);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
const msg = getErrorMessage(e);
|
|
||||||
log.error('AI call failed', { error: msg });
|
|
||||||
error(msg);
|
|
||||||
blankLine();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process initial input if provided (e.g. from `takt a`)
|
|
||||||
if (initialInput) {
|
|
||||||
history.push({ role: 'user', content: initialInput });
|
|
||||||
log.debug('Processing initial input', { initialInput, sessionId });
|
|
||||||
|
|
||||||
const result = await callAIWithRetry(initialInput);
|
|
||||||
if (result) {
|
|
||||||
history.push({ role: 'assistant', content: result.content });
|
|
||||||
blankLine();
|
|
||||||
} else {
|
|
||||||
history.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const input = await readLine(chalk.green('> '));
|
|
||||||
|
|
||||||
// EOF (Ctrl+D)
|
|
||||||
if (input === null) {
|
|
||||||
blankLine();
|
|
||||||
info('Cancelled');
|
|
||||||
return { confirmed: false, task: '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmed = input.trim();
|
|
||||||
|
|
||||||
// Empty input — skip
|
|
||||||
if (!trimmed) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle slash commands
|
|
||||||
if (trimmed === '/go') {
|
|
||||||
if (history.length === 0) {
|
|
||||||
info('No conversation yet. Please describe your task first.');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const task = buildTaskFromHistory(history);
|
|
||||||
log.info('Interactive mode confirmed', { messageCount: history.length });
|
|
||||||
return { confirmed: true, task };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmed === '/cancel') {
|
|
||||||
info('Cancelled');
|
|
||||||
return { confirmed: false, task: '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular input — send to AI
|
|
||||||
// readline is already closed at this point, so stdin is free for SDK
|
|
||||||
history.push({ role: 'user', content: trimmed });
|
|
||||||
|
|
||||||
log.debug('Sending to AI', { messageCount: history.length, sessionId });
|
|
||||||
process.stdin.pause();
|
|
||||||
|
|
||||||
const result = await callAIWithRetry(trimmed);
|
|
||||||
if (result) {
|
|
||||||
history.push({ role: 'assistant', content: result.content });
|
|
||||||
blankLine();
|
|
||||||
} else {
|
|
||||||
history.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* Task/workflow management commands.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { addTask, summarizeConversation } from './addTask.js';
|
|
||||||
export { listTasks, isBranchMerged, showFullDiff, type ListAction } from './listTasks.js';
|
|
||||||
export { watchTasks } from './watchTasks.js';
|
|
||||||
export { switchConfig, getCurrentPermissionMode, setPermissionMode, type PermissionMode } from './config.js';
|
|
||||||
export { ejectBuiltin } from './eject.js';
|
|
||||||
export { switchWorkflow } from './workflow.js';
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
* Application-wide constants
|
* Application-wide constants
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Language } from './models/types.js';
|
import type { Language } from './core/models/index.js';
|
||||||
|
|
||||||
/** Default workflow name when none specified */
|
/** Default workflow name when none specified */
|
||||||
export const DEFAULT_WORKFLOW_NAME = 'default';
|
export const DEFAULT_WORKFLOW_NAME = 'default';
|
||||||
|
|||||||
@ -3,14 +3,20 @@ export type {
|
|||||||
AgentType,
|
AgentType,
|
||||||
Status,
|
Status,
|
||||||
RuleMatchMethod,
|
RuleMatchMethod,
|
||||||
|
PermissionMode,
|
||||||
ReportConfig,
|
ReportConfig,
|
||||||
ReportObjectConfig,
|
ReportObjectConfig,
|
||||||
AgentResponse,
|
AgentResponse,
|
||||||
SessionState,
|
SessionState,
|
||||||
|
WorkflowRule,
|
||||||
WorkflowStep,
|
WorkflowStep,
|
||||||
|
LoopDetectionConfig,
|
||||||
WorkflowConfig,
|
WorkflowConfig,
|
||||||
WorkflowState,
|
WorkflowState,
|
||||||
CustomAgentConfig,
|
CustomAgentConfig,
|
||||||
|
DebugConfig,
|
||||||
|
Language,
|
||||||
|
PipelineConfig,
|
||||||
GlobalConfig,
|
GlobalConfig,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod/v4';
|
import { z } from 'zod/v4';
|
||||||
import { DEFAULT_LANGUAGE } from '../constants.js';
|
import { DEFAULT_LANGUAGE } from '../../constants.js';
|
||||||
|
|
||||||
/** Agent model schema (opus, sonnet, haiku) */
|
/** Agent model schema (opus, sonnet, haiku) */
|
||||||
export const AgentModelSchema = z.enum(['opus', 'sonnet', 'haiku']).default('sonnet');
|
export const AgentModelSchema = z.enum(['opus', 'sonnet', 'haiku']).default('sonnet');
|
||||||
@ -131,6 +131,8 @@ export const WorkflowStepRawSchema = z.object({
|
|||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
/** Agent is required for normal steps, optional for parallel container steps */
|
/** Agent is required for normal steps, optional for parallel container steps */
|
||||||
agent: z.string().optional(),
|
agent: z.string().optional(),
|
||||||
|
/** Session handling for this step */
|
||||||
|
session: z.enum(['continue', 'refresh']).optional(),
|
||||||
/** Display name for the agent (shown in output). Falls back to agent basename if not specified */
|
/** Display name for the agent (shown in output). Falls back to agent basename if not specified */
|
||||||
agent_name: z.string().optional(),
|
agent_name: z.string().optional(),
|
||||||
allowed_tools: z.array(z.string()).optional(),
|
allowed_tools: z.array(z.string()).optional(),
|
||||||
@ -224,4 +226,3 @@ export const ProjectConfigSchema = z.object({
|
|||||||
agents: z.array(CustomAgentConfigSchema).optional(),
|
agents: z.array(CustomAgentConfigSchema).optional(),
|
||||||
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
provider: z.enum(['claude', 'codex', 'mock']).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,6 +48,8 @@ export interface WorkflowStep {
|
|||||||
name: string;
|
name: string;
|
||||||
/** Agent name or path as specified in workflow YAML */
|
/** Agent name or path as specified in workflow YAML */
|
||||||
agent: string;
|
agent: string;
|
||||||
|
/** Session handling for this step */
|
||||||
|
session?: 'continue' | 'refresh';
|
||||||
/** Display name for the agent (shown in output). Falls back to agent basename if not specified */
|
/** Display name for the agent (shown in output). Falls back to agent basename if not specified */
|
||||||
agentDisplayName: string;
|
agentDisplayName: string;
|
||||||
/** Allowed tools for this step (optional, passed to agent execution) */
|
/** Allowed tools for this step (optional, passed to agent execution) */
|
||||||
@ -7,14 +7,15 @@
|
|||||||
|
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import type { WorkflowStep, WorkflowState, Language } from '../../models/types.js';
|
import type { WorkflowStep, WorkflowState, Language } from '../../models/types.js';
|
||||||
import type { RunAgentOptions } from '../../agents/runner.js';
|
import type { RunAgentOptions } from '../../../agents/runner.js';
|
||||||
import type { PhaseRunnerContext } from './phase-runner.js';
|
import type { PhaseRunnerContext } from '../phase-runner.js';
|
||||||
import type { WorkflowEngineOptions } from '../types.js';
|
import type { WorkflowEngineOptions } from '../types.js';
|
||||||
|
|
||||||
export class OptionsBuilder {
|
export class OptionsBuilder {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly engineOptions: WorkflowEngineOptions,
|
private readonly engineOptions: WorkflowEngineOptions,
|
||||||
private readonly getCwd: () => string,
|
private readonly getCwd: () => string,
|
||||||
|
private readonly getProjectCwd: () => string,
|
||||||
private readonly getSessionId: (agent: string) => string | undefined,
|
private readonly getSessionId: (agent: string) => string | undefined,
|
||||||
private readonly getReportDir: () => string,
|
private readonly getReportDir: () => string,
|
||||||
private readonly getLanguage: () => Language | undefined,
|
private readonly getLanguage: () => Language | undefined,
|
||||||
@ -44,7 +45,7 @@ export class OptionsBuilder {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...this.buildBaseOptions(step),
|
...this.buildBaseOptions(step),
|
||||||
sessionId: this.getSessionId(step.agent),
|
sessionId: step.session === 'refresh' ? undefined : this.getSessionId(step.agent),
|
||||||
allowedTools,
|
allowedTools,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -70,7 +71,7 @@ export class OptionsBuilder {
|
|||||||
): PhaseRunnerContext {
|
): PhaseRunnerContext {
|
||||||
return {
|
return {
|
||||||
cwd: this.getCwd(),
|
cwd: this.getCwd(),
|
||||||
reportDir: join(this.getCwd(), this.getReportDir()),
|
reportDir: join(this.getProjectCwd(), this.getReportDir()),
|
||||||
language: this.getLanguage(),
|
language: this.getLanguage(),
|
||||||
getSessionId: (agent: string) => state.agentSessions.get(agent),
|
getSessionId: (agent: string) => state.agentSessions.get(agent),
|
||||||
buildResumeOptions: this.buildResumeOptions.bind(this),
|
buildResumeOptions: this.buildResumeOptions.bind(this),
|
||||||
@ -10,12 +10,12 @@ import type {
|
|||||||
WorkflowState,
|
WorkflowState,
|
||||||
AgentResponse,
|
AgentResponse,
|
||||||
} from '../../models/types.js';
|
} from '../../models/types.js';
|
||||||
import { runAgent } from '../../agents/runner.js';
|
import { runAgent } from '../../../agents/runner.js';
|
||||||
import { ParallelLogger } from './parallel-logger.js';
|
import { ParallelLogger } from './parallel-logger.js';
|
||||||
import { needsStatusJudgmentPhase, runReportPhase, runStatusJudgmentPhase } from './phase-runner.js';
|
import { needsStatusJudgmentPhase, runReportPhase, runStatusJudgmentPhase } from '../phase-runner.js';
|
||||||
import { detectMatchedRule } from '../evaluation/index.js';
|
import { detectMatchedRule } from '../evaluation/index.js';
|
||||||
import { incrementStepIteration } from './state-manager.js';
|
import { incrementStepIteration } from './state-manager.js';
|
||||||
import { createLogger } from '../../utils/debug.js';
|
import { createLogger } from '../../../shared/utils/debug.js';
|
||||||
import type { OptionsBuilder } from './OptionsBuilder.js';
|
import type { OptionsBuilder } from './OptionsBuilder.js';
|
||||||
import type { StepExecutor } from './StepExecutor.js';
|
import type { StepExecutor } from './StepExecutor.js';
|
||||||
import type { WorkflowEngineOptions } from '../types.js';
|
import type { WorkflowEngineOptions } from '../types.js';
|
||||||
@ -14,12 +14,12 @@ import type {
|
|||||||
AgentResponse,
|
AgentResponse,
|
||||||
Language,
|
Language,
|
||||||
} from '../../models/types.js';
|
} from '../../models/types.js';
|
||||||
import { runAgent } from '../../agents/runner.js';
|
import { runAgent } from '../../../agents/runner.js';
|
||||||
import { InstructionBuilder, isReportObjectConfig } from '../instruction/InstructionBuilder.js';
|
import { InstructionBuilder, isReportObjectConfig } from '../instruction/InstructionBuilder.js';
|
||||||
import { needsStatusJudgmentPhase, runReportPhase, runStatusJudgmentPhase } from './phase-runner.js';
|
import { needsStatusJudgmentPhase, runReportPhase, runStatusJudgmentPhase } from '../phase-runner.js';
|
||||||
import { detectMatchedRule } from '../evaluation/index.js';
|
import { detectMatchedRule } from '../evaluation/index.js';
|
||||||
import { incrementStepIteration, getPreviousOutput } from './state-manager.js';
|
import { incrementStepIteration, getPreviousOutput } from './state-manager.js';
|
||||||
import { createLogger } from '../../utils/debug.js';
|
import { createLogger } from '../../../shared/utils/debug.js';
|
||||||
import type { OptionsBuilder } from './OptionsBuilder.js';
|
import type { OptionsBuilder } from './OptionsBuilder.js';
|
||||||
|
|
||||||
const log = createLogger('step-executor');
|
const log = createLogger('step-executor');
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user