Compare commits

..

1 Commits

Author SHA1 Message Date
nrslib
0e1cfd2eaa takt: refactor-instruction-field 2026-03-06 09:16:14 +09:00
93 changed files with 563 additions and 182 deletions

View File

@ -162,14 +162,14 @@ Implemented in `src/core/piece/evaluation/RuleEvaluator.ts`. The matched method
3. User request (`{task}` — auto-injected unless placeholder present) 3. User request (`{task}` — auto-injected unless placeholder present)
4. Previous response (auto-injected if `pass_previous_response: true`) 4. Previous response (auto-injected if `pass_previous_response: true`)
5. User inputs (auto-injected unless `{user_inputs}` placeholder present) 5. User inputs (auto-injected unless `{user_inputs}` placeholder present)
6. `instruction_template` content 6. `instruction` content
7. Status output rules (auto-injected for tag-based rules) 7. Status output rules (auto-injected for tag-based rules)
- Localized for `en` and `ja` - Localized for `en` and `ja`
- Related: `ReportInstructionBuilder` (Phase 2), `StatusJudgmentBuilder` (Phase 3) - Related: `ReportInstructionBuilder` (Phase 2), `StatusJudgmentBuilder` (Phase 3)
**Agent Runner** (`src/agents/runner.ts`) **Agent Runner** (`src/agents/runner.ts`)
- Resolves agent specs (name or path) to agent configurations - Resolves agent specs (name or path) to agent configurations
- Agent is optional — movements can execute with `instruction_template` only (no system prompt) - Agent is optional — movements can execute with `instruction` only (no system prompt)
- 5-layer resolution for provider/model: CLI `--provider` / `--model` → persona_providers → movement override → project `.takt/config.yaml` → global `~/.takt/config.yaml` - 5-layer resolution for provider/model: CLI `--provider` / `--model` → persona_providers → movement override → project `.takt/config.yaml` → global `~/.takt/config.yaml`
- Custom personas via `~/.takt/personas/<name>.md` or prompt files (.md) - Custom personas via `~/.takt/personas/<name>.md` or prompt files (.md)
- Inline system prompts: If agent file doesn't exist, the agent string is used as inline system prompt - Inline system prompts: If agent file doesn't exist, the agent string is used as inline system prompt
@ -299,7 +299,7 @@ loop_monitors:
threshold: 3 # Cycles before triggering judge threshold: 3 # Cycles before triggering judge
judge: judge:
persona: supervisor persona: supervisor
instruction_template: "Evaluate if the fix loop is making progress..." instruction: "Evaluate if the fix loop is making progress..."
rules: rules:
- condition: "Progress is being made" - condition: "Progress is being made"
next: fix next: fix
@ -342,7 +342,7 @@ movements:
my-server: my-server:
command: npx command: npx
args: [-y, my-mcp-server] args: [-y, my-mcp-server]
instruction_template: | instruction: |
Custom instructions for this movement. Custom instructions for this movement.
{task}, {previous_response} are auto-injected if not present as placeholders. {task}, {previous_response} are auto-injected if not present as placeholders.
pass_previous_response: true # Default: true pass_previous_response: true # Default: true
@ -413,7 +413,7 @@ movements:
part_edit: true # Edit permission for parts part_edit: true # Edit permission for parts
part_permission_mode: edit # Permission mode for parts part_permission_mode: edit # Permission mode for parts
part_allowed_tools: [Read, Glob, Grep, Edit, Write, Bash] part_allowed_tools: [Read, Glob, Grep, Edit, Write, Bash]
instruction_template: | instruction: |
Decompose this task into independent subtasks. Decompose this task into independent subtasks.
rules: rules:
- condition: "All parts completed" - condition: "All parts completed"
@ -549,7 +549,7 @@ Key rules:
- Policy REJECT lists are what reviewers enforce. If a criterion is not in the policy REJECT list, reviewers will not catch it — even if knowledge explains the reasoning - Policy REJECT lists are what reviewers enforce. If a criterion is not in the policy REJECT list, reviewers will not catch it — even if knowledge explains the reasoning
- Knowledge provides the WHY behind policy criteria. Knowledge alone does not trigger enforcement - Knowledge provides the WHY behind policy criteria. Knowledge alone does not trigger enforcement
- Instructions are bound to a single piece movement. They reference procedures, not principles - Instructions are bound to a single piece movement. They reference procedures, not principles
- Piece YAML `instruction_template` is for movement-specific details (which reports to read, movement routing, output templates) - Piece YAML `instruction` is for movement-specific details (which reports to read, movement routing, output templates)
**Separation of concerns in piece engine:** **Separation of concerns in piece engine:**
- `PieceEngine` - Orchestration, state management, event emission - `PieceEngine` - Orchestration, state management, event emission

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -108,7 +108,7 @@ movements:
rules: rules:
- condition: synthesis complete - condition: synthesis complete
next: COMPLETE next: COMPLETE
instruction_template: | instruction: |
Two models (Claude / Codex) independently answered the same instruction. Two models (Claude / Codex) independently answered the same instruction.
Synthesize their responses. Synthesize their responses.

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The ai_review ↔ ai_fix loop has repeated {cycle_count} times. The ai_review ↔ ai_fix loop has repeated {cycle_count} times.
Review the reports from each cycle and determine whether this loop Review the reports from each cycle and determine whether this loop
@ -39,7 +39,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-ai-fix instruction: loop-monitor-ai-fix
rules: rules:
- condition: Healthy (making progress) - condition: Healthy (making progress)
next: ai_review next: ai_review
@ -27,7 +27,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (findings decreasing, fixes applied) - condition: Healthy (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-ai-fix instruction: loop-monitor-ai-fix
rules: rules:
- condition: Healthy (making progress) - condition: Healthy (making progress)
next: ai_review next: ai_review
@ -27,7 +27,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The ai_review ↔ ai_fix loop has repeated {cycle_count} times. The ai_review ↔ ai_fix loop has repeated {cycle_count} times.
Review the reports from each cycle and determine whether this loop Review the reports from each cycle and determine whether this loop

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: converging (findings decreasing, fixes applied) - condition: converging (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -18,7 +18,7 @@ initial_movement: melchior
movements: movements:
- name: melchior - name: melchior
persona: melchior persona: melchior
instruction_template: | instruction: |
# MAGI System Initiated # MAGI System Initiated
## Matter for Deliberation ## Matter for Deliberation
@ -48,7 +48,7 @@ movements:
next: balthasar next: balthasar
- name: balthasar - name: balthasar
persona: balthasar persona: balthasar
instruction_template: | instruction: |
# MAGI System Continuing # MAGI System Continuing
## Matter for Deliberation ## Matter for Deliberation
@ -82,7 +82,7 @@ movements:
next: casper next: casper
- name: casper - name: casper
persona: casper persona: casper
instruction_template: | instruction: |
# MAGI System Final Deliberation # MAGI System Final Deliberation
## Matter for Deliberation ## Matter for Deliberation

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -177,7 +177,7 @@ movements:
rules: rules:
- condition: Review synthesis complete - condition: Review synthesis complete
next: COMPLETE next: COMPLETE
instruction_template: | instruction: |
## Review Results ## Review Results
{previous_response} {previous_response}

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (progress being made) - condition: Healthy (progress being made)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-ai-fix instruction: loop-monitor-ai-fix
rules: rules:
- condition: Healthy (making progress) - condition: Healthy (making progress)
next: ai_review next: ai_review
@ -27,7 +27,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (findings decreasing, fixes applied) - condition: Healthy (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The ai_review ↔ ai_fix loop has repeated {cycle_count} times. The ai_review ↔ ai_fix loop has repeated {cycle_count} times.
Review the reports from each cycle and determine whether this loop Review the reports from each cycle and determine whether this loop
@ -39,7 +39,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: Healthy (findings decreasing, fixes applied) - condition: Healthy (findings decreasing, fixes applied)
next: reviewers next: reviewers

View File

@ -265,7 +265,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The review → fix cycle has repeated {cycle_count} times. The review → fix cycle has repeated {cycle_count} times.
Check the review report history in the Report Directory and assess convergence. Check the review report history in the Report Directory and assess convergence.
@ -282,7 +282,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The AI fix → review cycle has repeated {cycle_count} times. The AI fix → review cycle has repeated {cycle_count} times.
Check the review report history in the Report Directory and assess convergence. Check the review report history in the Report Directory and assess convergence.
@ -299,7 +299,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The supervisor fix → review cycle has repeated {cycle_count} times. The supervisor fix → review cycle has repeated {cycle_count} times.
Check the review report history in the Report Directory and assess convergence. Check the review report history in the Report Directory and assess convergence.

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
The ai_review ↔ ai_fix loop has repeated {cycle_count} times. The ai_review ↔ ai_fix loop has repeated {cycle_count} times.
Review the reports from each cycle and determine whether this loop Review the reports from each cycle and determine whether this loop

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -106,7 +106,7 @@ movements:
rules: rules:
- condition: 統合完了 - condition: 統合完了
next: COMPLETE next: COMPLETE
instruction_template: | instruction: |
2つのモデルClaude / Codexが同じ指示に対して独立に回答しました。 2つのモデルClaude / Codexが同じ指示に対して独立に回答しました。
両者の回答を統合してください。 両者の回答を統合してください。

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
ai_review と ai_fix のループが {cycle_count} 回繰り返されました。 ai_review と ai_fix のループが {cycle_count} 回繰り返されました。
各サイクルのレポートを確認し、このループが健全(進捗がある)か、 各サイクルのレポートを確認し、このループが健全(進捗がある)か、
@ -39,7 +39,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-ai-fix instruction: loop-monitor-ai-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: ai_review next: ai_review
@ -27,7 +27,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-ai-fix instruction: loop-monitor-ai-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: ai_review next: ai_review
@ -27,7 +27,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
ai_review と ai_fix のループが {cycle_count} 回繰り返されました。 ai_review と ai_fix のループが {cycle_count} 回繰り返されました。
各サイクルのレポートを確認し、このループが健全(進捗がある)か、 各サイクルのレポートを確認し、このループが健全(進捗がある)か、

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: fix_both next: fix_both

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -18,7 +18,7 @@ initial_movement: melchior
movements: movements:
- name: melchior - name: melchior
persona: melchior persona: melchior
instruction_template: | instruction: |
# MAGI System 起動 # MAGI System 起動
## 審議事項 ## 審議事項
@ -48,7 +48,7 @@ movements:
next: balthasar next: balthasar
- name: balthasar - name: balthasar
persona: balthasar persona: balthasar
instruction_template: | instruction: |
# MAGI System 継続 # MAGI System 継続
## 審議事項 ## 審議事項
@ -82,7 +82,7 @@ movements:
next: casper next: casper
- name: casper - name: casper
persona: casper persona: casper
instruction_template: | instruction: |
# MAGI System 最終審議 # MAGI System 最終審議
## 審議事項 ## 審議事項

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -177,7 +177,7 @@ movements:
rules: rules:
- condition: レビュー統合完了 - condition: レビュー統合完了
next: COMPLETE next: COMPLETE
instruction_template: | instruction: |
## レビュー結果 ## レビュー結果
{previous_response} {previous_response}

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-ai-fix instruction: loop-monitor-ai-fix
rules: rules:
- condition: 健全(進捗あり) - condition: 健全(進捗あり)
next: ai_review next: ai_review
@ -27,7 +27,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
ai_review と ai_fix のループが {cycle_count} 回繰り返されました。 ai_review と ai_fix のループが {cycle_count} 回繰り返されました。
各サイクルのレポートを確認し、このループが健全(進捗がある)か、 各サイクルのレポートを確認し、このループが健全(進捗がある)か、
@ -39,7 +39,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: loop-monitor-reviewers-fix instruction: loop-monitor-reviewers-fix
rules: rules:
- condition: 健全(指摘数が減少、修正が反映されている) - condition: 健全(指摘数が減少、修正が反映されている)
next: reviewers next: reviewers

View File

@ -265,7 +265,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
レビュー → 修正のサイクルが {cycle_count} 回繰り返されました。 レビュー → 修正のサイクルが {cycle_count} 回繰り返されました。
Report Directory 内のレビューレポート履歴を確認し、収束状況を判断してください。 Report Directory 内のレビューレポート履歴を確認し、収束状況を判断してください。
@ -282,7 +282,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
AI修正 → レビューのサイクルが {cycle_count} 回繰り返されました。 AI修正 → レビューのサイクルが {cycle_count} 回繰り返されました。
Report Directory 内のレビューレポート履歴を確認し、収束状況を判断してください。 Report Directory 内のレビューレポート履歴を確認し、収束状況を判断してください。
@ -299,7 +299,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
監督修正 → レビューのサイクルが {cycle_count} 回繰り返されました。 監督修正 → レビューのサイクルが {cycle_count} 回繰り返されました。
Report Directory 内のレビューレポート履歴を確認し、収束状況を判断してください。 Report Directory 内のレビューレポート履歴を確認し、収束状況を判断してください。

View File

@ -15,7 +15,7 @@ loop_monitors:
threshold: 3 threshold: 3
judge: judge:
persona: supervisor persona: supervisor
instruction_template: | instruction: |
ai_review と ai_fix のループが {cycle_count} 回繰り返されました。 ai_review と ai_fix のループが {cycle_count} 回繰り返されました。
各サイクルのレポートを確認し、このループが健全(進捗がある)か、 各サイクルのレポートを確認し、このループが健全(進捗がある)か、

View File

@ -47,22 +47,41 @@ movement 内では**キー名**で参照する(パスを直接書かない)
- name: movement-name # movement 名(必須、一意) - name: movement-name # movement 名(必須、一意)
persona: coder # ペルソナキーpersonas マップを参照、任意) persona: coder # ペルソナキーpersonas マップを参照、任意)
policy: coding # ポリシーキーpolicies マップを参照、任意) policy: coding # ポリシーキーpolicies マップを参照、任意)
policy: [coding, testing] # 複数指定も可(配列) instruction: implement # 指示instructions マップのキー参照、またはインライン、任意)
instruction: implement # 指示テンプレートキーinstructions マップを参照、任意)
knowledge: architecture # ナレッジキーknowledge マップを参照、任意) knowledge: architecture # ナレッジキーknowledge マップを参照、任意)
edit: true # ファイル編集可否(必須) edit: true # ファイル編集可否(必須)
required_permission_mode: edit # 必要最小権限: edit / readonly / full任意 required_permission_mode: edit # 必要最小権限: edit / readonly / full任意
session: refresh # セッション管理(任意) session: refresh # セッション管理(任意)
pass_previous_response: true # 前の出力を渡すか(デフォルト: true pass_previous_response: true # 前の出力を渡すか(デフォルト: true
allowed_tools: [...] # 許可ツール一覧(任意、参考情報) allowed_tools: [...] # 許可ツール一覧(任意、参考情報)
instruction_template: | # 指示テンプレート(参照解決またはインライン、任意)
指示内容...
output_contracts: [...] # 出力契約設定(任意) output_contracts: [...] # 出力契約設定(任意)
quality_gates: [...] # 品質ゲートAIへの指示、任意 quality_gates: [...] # 品質ゲートAIへの指示、任意
rules: [...] # 遷移ルール(必須) rules: [...] # 遷移ルール(必須)
``` ```
**`instruction` vs `instruction_template`**: どちらも同じ参照解決ルート(セクションマップ → パス → 3-layer facet → インライン)を使う。`instruction_template` はインライン文字列もそのまま使える。通常はどちらか一方を使用する。 複数ポリシー指定(配列):
```yaml
- name: movement-name
policy: [coding, testing]
```
参照形式:
```yaml
- name: movement-name
instruction: implement
```
インライン形式:
```yaml
- name: movement-name
instruction: |
指示内容...
```
**`instruction`(正式) / `instruction_template`deprecated**: どちらも同じ参照解決ルート(セクションマップ → パス → 3-layer facet → インライン)を使う。`instruction_template` も互換のため受理されるが、新規定義では `instruction` を使う。
### Parallel Movement ### Parallel Movement
@ -185,7 +204,7 @@ quality_gates:
## テンプレート変数 ## テンプレート変数
`instruction_template`(またはインストラクションファイル)内で使用可能な変数: `instruction`(またはインストラクションファイル)内で使用可能な変数:
| 変数 | 説明 | | 変数 | 説明 |
|-----|------| |-----|------|
@ -207,7 +226,7 @@ loop_monitors:
threshold: 3 # 発動閾値(サイクル回数) threshold: 3 # 発動閾値(サイクル回数)
judge: judge:
persona: supervisor # ペルソナキー参照 persona: supervisor # ペルソナキー参照
instruction_template: | # 判定用指示 instruction: | # 判定用指示
サイクルが {cycle_count} 回繰り返されました。 サイクルが {cycle_count} 回繰り返されました。
健全性を判断してください。 健全性を判断してください。
rules: rules:

View File

@ -35,7 +35,7 @@ function makeSubMovement(name: string, conditions: string[]): PieceMovement {
return { return {
name, name,
personaDisplayName: name, personaDisplayName: name,
instructionTemplate: '', instruction: '',
passPreviousResponse: false, passPreviousResponse: false,
rules: conditions.map((c) => ({ condition: c })), rules: conditions.map((c) => ({ condition: c })),
}; };
@ -48,7 +48,7 @@ function makeParentMovement(
return { return {
name: 'parent', name: 'parent',
personaDisplayName: 'parent', personaDisplayName: 'parent',
instructionTemplate: '', instruction: '',
passPreviousResponse: false, passPreviousResponse: false,
parallel, parallel,
rules, rules,
@ -264,7 +264,7 @@ describe('AggregateEvaluator', () => {
const step: PieceMovement = { const step: PieceMovement = {
name: 'test-movement', name: 'test-movement',
personaDisplayName: 'tester', personaDisplayName: 'tester',
instructionTemplate: '', instruction: '',
passPreviousResponse: false, passPreviousResponse: false,
rules: [ rules: [
{ {

View File

@ -77,7 +77,7 @@ describe('getBuiltinPiece', () => {
const planMovement = piece!.movements.find((movement) => movement.name === 'plan'); const planMovement = piece!.movements.find((movement) => movement.name === 'plan');
expect(planMovement).toBeDefined(); expect(planMovement).toBeDefined();
expect(planMovement!.instructionTemplate).not.toBe('plan'); expect(planMovement!.instruction).not.toBe('plan');
}); });
it('should return null for non-existent piece names', () => { it('should return null for non-existent piece names', () => {

View File

@ -50,7 +50,7 @@ function createMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
name: 'test-movement', name: 'test-movement',
persona: 'coder', persona: 'coder',
personaDisplayName: 'Coder', personaDisplayName: 'Coder',
instructionTemplate: '', instruction: '',
passPreviousResponse: false, passPreviousResponse: false,
...overrides, ...overrides,
}; };

View File

@ -32,7 +32,7 @@ function buildTeamLeaderConfig(): PieceConfig {
maxMovements: 5, maxMovements: 5,
movements: [ movements: [
makeMovement('implement', { makeMovement('implement', {
instructionTemplate: 'Task: {task}', instruction: 'Task: {task}',
teamLeader: { teamLeader: {
persona: '../personas/team-leader.md', persona: '../personas/team-leader.md',
maxParts: 3, maxParts: 3,

View File

@ -41,7 +41,7 @@ export function makeMovement(name: string, overrides: Partial<PieceMovement> = {
name, name,
persona: `../personas/${name}.md`, persona: `../personas/${name}.md`,
personaDisplayName: name, personaDisplayName: name,
instructionTemplate: `Run ${name}`, instruction: `Run ${name}`,
passPreviousResponse: true, passPreviousResponse: true,
...overrides, ...overrides,
}; };

View File

@ -137,7 +137,7 @@ describe('PieceEngine: worktree reportDir resolution', () => {
initialMovement: 'review', initialMovement: 'review',
movements: [ movements: [
makeMovement('review', { makeMovement('review', {
instructionTemplate: 'Write report to {report_dir}', instruction: 'Write report to {report_dir}',
outputContracts: [{ name: '00-review.md', format: '00-review', useJudge: true }], outputContracts: [{ name: '00-review.md', format: '00-review', useJudge: true }],
rules: [ rules: [
makeRule('approved', 'COMPLETE'), makeRule('approved', 'COMPLETE'),

View File

@ -520,7 +520,6 @@ describe('normalizePieceConfig with layer resolution', () => {
name: 'step1', name: 'step1',
persona: 'coder', persona: 'coder',
instruction_template: 'implement', instruction_template: 'implement',
instruction: '{task}',
}, },
], ],
}; };
@ -528,7 +527,7 @@ describe('normalizePieceConfig with layer resolution', () => {
const context: FacetResolutionContext = { projectDir, lang: 'ja' }; const context: FacetResolutionContext = { projectDir, lang: 'ja' };
const config = normalizePieceConfig(raw, pieceDir, context); const config = normalizePieceConfig(raw, pieceDir, context);
expect(config.movements[0]!.instructionTemplate).toBe('Mapped instruction template'); expect(config.movements[0]!.instruction).toBe('Mapped instruction template');
}); });
it('should resolve instruction_template by name via layer resolution', () => { it('should resolve instruction_template by name via layer resolution', () => {
@ -543,7 +542,6 @@ describe('normalizePieceConfig with layer resolution', () => {
name: 'step1', name: 'step1',
persona: 'coder', persona: 'coder',
instruction_template: 'implement', instruction_template: 'implement',
instruction: '{task}',
}, },
], ],
}; };
@ -551,7 +549,7 @@ describe('normalizePieceConfig with layer resolution', () => {
const context: FacetResolutionContext = { projectDir, lang: 'ja' }; const context: FacetResolutionContext = { projectDir, lang: 'ja' };
const config = normalizePieceConfig(raw, pieceDir, context); const config = normalizePieceConfig(raw, pieceDir, context);
expect(config.movements[0]!.instructionTemplate).toBe('Project implement template'); expect(config.movements[0]!.instruction).toBe('Project implement template');
}); });
it('should keep inline instruction_template when no facet is found', () => { it('should keep inline instruction_template when no facet is found', () => {
@ -564,7 +562,6 @@ Second line remains inline.`;
name: 'step1', name: 'step1',
persona: 'coder', persona: 'coder',
instruction_template: inlineTemplate, instruction_template: inlineTemplate,
instruction: '{task}',
}, },
], ],
}; };
@ -572,7 +569,7 @@ Second line remains inline.`;
const context: FacetResolutionContext = { projectDir, lang: 'ja' }; const context: FacetResolutionContext = { projectDir, lang: 'ja' };
const config = normalizePieceConfig(raw, pieceDir, context); const config = normalizePieceConfig(raw, pieceDir, context);
expect(config.movements[0]!.instructionTemplate).toBe(inlineTemplate); expect(config.movements[0]!.instruction).toBe(inlineTemplate);
}); });
it('should resolve loop monitor judge instruction_template via layer resolution', () => { it('should resolve loop monitor judge instruction_template via layer resolution', () => {
@ -612,6 +609,6 @@ Second line remains inline.`;
const context: FacetResolutionContext = { projectDir, lang: 'ja' }; const context: FacetResolutionContext = { projectDir, lang: 'ja' };
const config = normalizePieceConfig(raw, pieceDir, context); const config = normalizePieceConfig(raw, pieceDir, context);
expect(config.loopMonitors?.[0]?.judge.instructionTemplate).toBe('Project judge template'); expect(config.loopMonitors?.[0]?.judge.instruction).toBe('Project judge template');
}); });
}); });

View File

@ -32,7 +32,7 @@ function createMinimalStep(template: string): PieceMovement {
name: 'test-step', name: 'test-step',
persona: 'test-agent', persona: 'test-agent',
personaDisplayName: 'Test Agent', personaDisplayName: 'Test Agent',
instructionTemplate: template, instruction: template,
passPreviousResponse: false, passPreviousResponse: false,
}; };
} }

View File

@ -63,7 +63,7 @@ function makeMovement(name: string, agentPath: string, rules: PieceRule[]): Piec
persona: `./personas/${name}.md`, persona: `./personas/${name}.md`,
personaDisplayName: name, personaDisplayName: name,
personaPath: agentPath, personaPath: agentPath,
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: true, passPreviousResponse: true,
rules, rules,
}; };

View File

@ -40,7 +40,7 @@ function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
name: 'test-step', name: 'test-step',
persona: 'test-agent', persona: 'test-agent',
personaDisplayName: 'test-step', personaDisplayName: 'test-step',
instructionTemplate: 'Do the work.', instruction: 'Do the work.',
passPreviousResponse: false, passPreviousResponse: false,
rules: [ rules: [
makeRule('Done', 'COMPLETE'), makeRule('Done', 'COMPLETE'),
@ -66,7 +66,7 @@ function makeContext(overrides: Partial<InstructionContext> = {}): InstructionCo
describe('Instruction Builder IT: task auto-injection', () => { describe('Instruction Builder IT: task auto-injection', () => {
it('should auto-inject task as "User Request" section when template has no {task}', () => { it('should auto-inject task as "User Request" section when template has no {task}', () => {
const step = makeMovement({ instructionTemplate: 'Do the work.' }); const step = makeMovement({ instruction: 'Do the work.' });
const ctx = makeContext({ task: 'Build the login page' }); const ctx = makeContext({ task: 'Build the login page' });
const result = buildInstruction(step, ctx); const result = buildInstruction(step, ctx);
@ -76,7 +76,7 @@ describe('Instruction Builder IT: task auto-injection', () => {
}); });
it('should NOT auto-inject task section when template contains {task}', () => { it('should NOT auto-inject task section when template contains {task}', () => {
const step = makeMovement({ instructionTemplate: 'Here is the task: {task}' }); const step = makeMovement({ instruction: 'Here is the task: {task}' });
const ctx = makeContext({ task: 'Build the login page' }); const ctx = makeContext({ task: 'Build the login page' });
const result = buildInstruction(step, ctx); const result = buildInstruction(step, ctx);
@ -93,7 +93,7 @@ describe('Instruction Builder IT: previous_response auto-injection', () => {
it('should auto-inject previous response when passPreviousResponse is true', () => { it('should auto-inject previous response when passPreviousResponse is true', () => {
const step = makeMovement({ const step = makeMovement({
passPreviousResponse: true, passPreviousResponse: true,
instructionTemplate: 'Continue the work.', instruction: 'Continue the work.',
}); });
const previousOutput: AgentResponse = { const previousOutput: AgentResponse = {
persona: 'previous-agent', persona: 'previous-agent',
@ -112,7 +112,7 @@ describe('Instruction Builder IT: previous_response auto-injection', () => {
it('should NOT inject previous response when passPreviousResponse is false', () => { it('should NOT inject previous response when passPreviousResponse is false', () => {
const step = makeMovement({ const step = makeMovement({
passPreviousResponse: false, passPreviousResponse: false,
instructionTemplate: 'Do fresh work.', instruction: 'Do fresh work.',
}); });
const previousOutput: AgentResponse = { const previousOutput: AgentResponse = {
persona: 'previous-agent', persona: 'previous-agent',
@ -131,7 +131,7 @@ describe('Instruction Builder IT: previous_response auto-injection', () => {
it('should NOT auto-inject when template contains {previous_response}', () => { it('should NOT auto-inject when template contains {previous_response}', () => {
const step = makeMovement({ const step = makeMovement({
passPreviousResponse: true, passPreviousResponse: true,
instructionTemplate: '## Context\n{previous_response}\n\nDo work.', instruction: '## Context\n{previous_response}\n\nDo work.',
}); });
const previousOutput: AgentResponse = { const previousOutput: AgentResponse = {
persona: 'prev', status: 'done', content: 'Prior work done.', timestamp: new Date(), persona: 'prev', status: 'done', content: 'Prior work done.', timestamp: new Date(),
@ -161,7 +161,7 @@ describe('Instruction Builder IT: user_inputs auto-injection', () => {
}); });
it('should NOT auto-inject when template contains {user_inputs}', () => { it('should NOT auto-inject when template contains {user_inputs}', () => {
const step = makeMovement({ instructionTemplate: 'Inputs: {user_inputs}' }); const step = makeMovement({ instruction: 'Inputs: {user_inputs}' });
const ctx = makeContext({ userInputs: ['Input A'] }); const ctx = makeContext({ userInputs: ['Input A'] });
const result = buildInstruction(step, ctx); const result = buildInstruction(step, ctx);
@ -175,7 +175,7 @@ describe('Instruction Builder IT: user_inputs auto-injection', () => {
describe('Instruction Builder IT: iteration variables', () => { describe('Instruction Builder IT: iteration variables', () => {
it('should replace {iteration}, {max_movements}, {movement_iteration} in template', () => { it('should replace {iteration}, {max_movements}, {movement_iteration} in template', () => {
const step = makeMovement({ const step = makeMovement({
instructionTemplate: 'Iter: {iteration}/{max_movements}, movement iter: {movement_iteration}', instruction: 'Iter: {iteration}/{max_movements}, movement iter: {movement_iteration}',
}); });
const ctx = makeContext({ iteration: 5, maxMovements: 30, movementIteration: 2 }); const ctx = makeContext({ iteration: 5, maxMovements: 30, movementIteration: 2 });
@ -198,7 +198,7 @@ describe('Instruction Builder IT: iteration variables', () => {
describe('Instruction Builder IT: report_dir expansion', () => { describe('Instruction Builder IT: report_dir expansion', () => {
it('should replace {report_dir} in template', () => { it('should replace {report_dir} in template', () => {
const step = makeMovement({ const step = makeMovement({
instructionTemplate: 'Read the plan from {report_dir}/00-plan.md', instruction: 'Read the plan from {report_dir}/00-plan.md',
}); });
const ctx = makeContext({ reportDir: '/tmp/test-project/.takt/runs/20250126-task/reports' }); const ctx = makeContext({ reportDir: '/tmp/test-project/.takt/runs/20250126-task/reports' });
@ -209,7 +209,7 @@ describe('Instruction Builder IT: report_dir expansion', () => {
it('should replace {report:filename} with full path', () => { it('should replace {report:filename} with full path', () => {
const step = makeMovement({ const step = makeMovement({
instructionTemplate: 'Read {report:00-plan.md} for the plan.', instruction: 'Read {report:00-plan.md} for the plan.',
}); });
const ctx = makeContext({ reportDir: '/tmp/reports' }); const ctx = makeContext({ reportDir: '/tmp/reports' });
@ -388,7 +388,7 @@ describe('Instruction Builder IT: template injection prevention', () => {
it('should escape curly braces in previous response content', () => { it('should escape curly braces in previous response content', () => {
const step = makeMovement({ const step = makeMovement({
passPreviousResponse: true, passPreviousResponse: true,
instructionTemplate: 'Continue.', instruction: 'Continue.',
}); });
const ctx = makeContext({ const ctx = makeContext({
previousOutput: { previousOutput: {

View File

@ -224,7 +224,7 @@ function makeConfig(): PieceConfig {
name: 'step1', name: 'step1',
persona: '../agents/coder.md', persona: '../agents/coder.md',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do something', instruction: 'Do something',
passPreviousResponse: true, passPreviousResponse: true,
rules: [ rules: [
{ condition: 'done', next: 'COMPLETE' }, { condition: 'done', next: 'COMPLETE' },

View File

@ -66,7 +66,7 @@ function makeMovement(name: string, agentPath: string, rules: PieceRule[]): Piec
persona: `./personas/${name}.md`, persona: `./personas/${name}.md`,
personaDisplayName: name, personaDisplayName: name,
personaPath: agentPath, personaPath: agentPath,
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: true, passPreviousResponse: true,
rules, rules,
}; };

View File

@ -92,8 +92,8 @@ describe('Piece Loader IT: builtin piece loading', () => {
expect(planMovement).toBeDefined(); expect(planMovement).toBeDefined();
expect(implementMovement).toBeDefined(); expect(implementMovement).toBeDefined();
expect(planMovement!.instructionTemplate).toContain('missing E2E tests'); expect(planMovement!.instruction).toContain('missing E2E tests');
expect(implementMovement!.instructionTemplate).toContain('npm run test:e2e:mock'); expect(implementMovement!.instruction).toContain('npm run test:e2e:mock');
}); });
it('should load e2e-test as a builtin piece in ja locale', () => { it('should load e2e-test as a builtin piece in ja locale', () => {
@ -110,8 +110,8 @@ describe('Piece Loader IT: builtin piece loading', () => {
expect(planMovement).toBeDefined(); expect(planMovement).toBeDefined();
expect(implementMovement).toBeDefined(); expect(implementMovement).toBeDefined();
expect(planMovement!.instructionTemplate).toContain('E2Eテスト'); expect(planMovement!.instruction).toContain('E2Eテスト');
expect(implementMovement!.instructionTemplate).toContain('npm run test:e2e:mock'); expect(implementMovement!.instruction).toContain('npm run test:e2e:mock');
}); });
}); });
@ -156,6 +156,49 @@ movements:
expect(config!.movements.length).toBe(1); expect(config!.movements.length).toBe(1);
expect(config!.movements[0]!.name).toBe('start'); expect(config!.movements[0]!.name).toBe('start');
}); });
it('should propagate canonical instruction field through loader for movement and loop monitor judge', () => {
// Given: project-local piece that uses instruction on both movement and loop monitor judge
const piecesDir = join(testDir, '.takt', 'pieces');
mkdirSync(piecesDir, { recursive: true });
writeFileSync(join(piecesDir, 'instruction-canonical.yaml'), `
name: instruction-canonical
max_movements: 8
initial_movement: step1
movements:
- name: step1
instruction: "Step 1 instruction"
rules:
- condition: next
next: step2
- name: step2
instruction: "Step 2 instruction"
rules:
- condition: done
next: COMPLETE
loop_monitors:
- cycle: [step1, step2]
threshold: 2
judge:
instruction: "Judge instruction"
rules:
- condition: continue
next: step2
`);
// When: loading the piece through the integration entry point
const config = loadPiece('instruction-canonical', testDir);
// Then: canonical instruction is available on normalized movement/judge models
expect(config).not.toBeNull();
const step1 = config!.movements[0] as unknown as Record<string, unknown>;
const judge = config!.loopMonitors?.[0]?.judge as unknown as Record<string, unknown>;
expect(step1.instruction).toBe('Step 1 instruction');
expect(judge.instruction).toBe('Judge instruction');
});
}); });
describe('Piece Loader IT: agent path resolution', () => { describe('Piece Loader IT: agent path resolution', () => {

View File

@ -50,7 +50,7 @@ function makeMovement(
name, name,
persona: 'test-agent', persona: 'test-agent',
personaDisplayName: name, personaDisplayName: name,
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: true, passPreviousResponse: true,
rules, rules,
parallel, parallel,
@ -401,7 +401,7 @@ describe('Rule Evaluation IT: movements without rules', () => {
name: 'step', name: 'step',
persona: 'agent', persona: 'agent',
personaDisplayName: 'step', personaDisplayName: 'step',
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: true, passPreviousResponse: true,
}; };

View File

@ -224,7 +224,7 @@ describe('executePiece: SIGINT handler integration', () => {
name: 'step1', name: 'step1',
persona: '../agents/coder.md', persona: '../agents/coder.md',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do something', instruction: 'Do something',
passPreviousResponse: true, passPreviousResponse: true,
rules: [ rules: [
{ condition: 'done', next: 'COMPLETE' }, { condition: 'done', next: 'COMPLETE' },

View File

@ -93,7 +93,7 @@ function makeMovement(
persona: './agents/agent.md', persona: './agents/agent.md',
personaDisplayName: name, personaDisplayName: name,
personaPath: agentPath, personaPath: agentPath,
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: true, passPreviousResponse: true,
rules, rules,
outputContracts: options.outputContracts, outputContracts: options.outputContracts,

View File

@ -317,11 +317,11 @@ describe('normalizePieceConfig knowledge resolution', () => {
// --- Test helpers for InstructionBuilder --- // --- Test helpers for InstructionBuilder ---
function createMinimalStep(instructionTemplate: string): PieceMovement { function createMinimalStep(instruction: string): PieceMovement {
return { return {
name: 'test-step', name: 'test-step',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate, instruction,
passPreviousResponse: false, passPreviousResponse: false,
}; };
} }

View File

@ -7,7 +7,7 @@ function createMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
return { return {
name: 'reviewers', name: 'reviewers',
personaDisplayName: 'Reviewers', personaDisplayName: 'Reviewers',
instructionTemplate: 'review', instruction: 'review',
passPreviousResponse: false, passPreviousResponse: false,
...overrides, ...overrides,
}; };

View File

@ -8,7 +8,12 @@
*/ */
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { PieceConfigRawSchema, ParallelSubMovementRawSchema, PieceMovementRawSchema } from '../core/models/index.js'; import {
PieceConfigRawSchema,
ParallelSubMovementRawSchema,
PieceMovementRawSchema,
LoopMonitorJudgeSchema,
} from '../core/models/index.js';
describe('ParallelSubMovementRawSchema', () => { describe('ParallelSubMovementRawSchema', () => {
it('should validate a valid parallel sub-movement', () => { it('should validate a valid parallel sub-movement', () => {
@ -32,6 +37,39 @@ describe('ParallelSubMovementRawSchema', () => {
expect(result.success).toBe(true); expect(result.success).toBe(true);
}); });
it('should accept a sub-movement with instruction field', () => {
// Given: a parallel sub-movement that uses the new canonical field
const raw = {
name: 'no-agent-step',
instruction: 'Do something',
};
// When: validating the sub-movement schema
const result = ParallelSubMovementRawSchema.safeParse(raw);
// Then: it is accepted
expect(result.success).toBe(true);
});
it('should accept a sub-movement when instruction and instruction_template are both provided', () => {
// Given: both canonical and deprecated fields are present during migration
const raw = {
name: 'dual-field-sub-step',
instruction: 'Canonical instruction',
instruction_template: 'Legacy instruction',
};
// When: validating the sub-movement schema
const result = ParallelSubMovementRawSchema.safeParse(raw);
// Then: schema keeps backward compatibility and accepts both fields
expect(result.success).toBe(true);
if (result.success) {
expect((result.data as unknown as Record<string, unknown>).instruction).toBe('Canonical instruction');
expect((result.data as unknown as Record<string, unknown>).instruction_template).toBe('Legacy instruction');
}
});
it('should accept optional fields', () => { it('should accept optional fields', () => {
const raw = { const raw = {
name: 'full-sub-step', name: 'full-sub-step',
@ -144,6 +182,39 @@ describe('PieceMovementRawSchema with parallel', () => {
expect(result.success).toBe(true); expect(result.success).toBe(true);
}); });
it('should accept a movement with instruction only', () => {
// Given: a movement that only uses instruction
const raw = {
name: 'orphan-step',
instruction: 'Do something',
};
// When: validating the movement schema
const result = PieceMovementRawSchema.safeParse(raw);
// Then: it is accepted
expect(result.success).toBe(true);
});
it('should accept a movement when instruction and instruction_template are both provided', () => {
// Given: movement includes both canonical and deprecated instruction fields
const raw = {
name: 'orphan-step',
instruction: 'Canonical movement instruction',
instruction_template: 'Legacy movement instruction',
};
// When: validating the movement schema
const result = PieceMovementRawSchema.safeParse(raw);
// Then: schema accepts both fields for deprecation window
expect(result.success).toBe(true);
if (result.success) {
expect((result.data as unknown as Record<string, unknown>).instruction).toBe('Canonical movement instruction');
expect((result.data as unknown as Record<string, unknown>).instruction_template).toBe('Legacy movement instruction');
}
});
it('should accept a movement with persona (no parallel)', () => { it('should accept a movement with persona (no parallel)', () => {
const raw = { const raw = {
name: 'normal-step', name: 'normal-step',
@ -182,6 +253,46 @@ describe('PieceMovementRawSchema with parallel', () => {
}); });
}); });
describe('LoopMonitorJudgeSchema', () => {
it('should accept judge configuration with instruction field', () => {
// Given: a loop monitor judge with canonical instruction
const raw = {
persona: 'reviewer',
instruction: 'Judge loop health',
rules: [{ condition: 'continue', next: 'ai_fix' }],
};
// When: validating judge schema
const result = LoopMonitorJudgeSchema.safeParse(raw);
// Then: it is accepted
expect(result.success).toBe(true);
if (result.success) {
expect((result.data as unknown as Record<string, unknown>).instruction).toBe('Judge loop health');
}
});
it('should accept judge configuration during deprecation window when both fields exist', () => {
// Given: judge config with both new and deprecated fields
const raw = {
persona: 'reviewer',
instruction: 'Judge loop health',
instruction_template: 'legacy judge instruction',
rules: [{ condition: 'continue', next: 'ai_fix' }],
};
// When: validating judge schema
const result = LoopMonitorJudgeSchema.safeParse(raw);
// Then: it is accepted for backward compatibility
expect(result.success).toBe(true);
if (result.success) {
expect((result.data as unknown as Record<string, unknown>).instruction).toBe('Judge loop health');
expect((result.data as unknown as Record<string, unknown>).instruction_template).toBe('legacy judge instruction');
}
});
});
describe('PieceConfigRawSchema with parallel movements', () => { describe('PieceConfigRawSchema with parallel movements', () => {
it('should validate a piece with parallel movement', () => { it('should validate a piece with parallel movement', () => {
const raw = { const raw = {

View File

@ -17,7 +17,7 @@ function createStep(fileName: string): PieceMovement {
return { return {
name: 'reviewers', name: 'reviewers',
personaDisplayName: 'Reviewers', personaDisplayName: 'Reviewers',
instructionTemplate: 'review', instruction: 'review',
passPreviousResponse: false, passPreviousResponse: false,
outputContracts: [{ name: fileName }], outputContracts: [{ name: fileName }],
}; };

View File

@ -49,7 +49,7 @@ const { MockPieceEngine } = vi.hoisted(() => {
return { status: 'aborted', iteration: 1 }; return { status: 'aborted', iteration: 1 };
} }
if (firstStep) { if (firstStep) {
this.emit('movement:start', firstStep, 1, firstStep.instructionTemplate, { provider: undefined, model: undefined }); this.emit('movement:start', firstStep, 1, firstStep.instruction, { provider: undefined, model: undefined });
} }
this.emit('piece:complete', { status: 'completed', iteration: 1 }); this.emit('piece:complete', { status: 'completed', iteration: 1 });
return { status: 'completed', iteration: 1 }; return { status: 'completed', iteration: 1 };
@ -171,7 +171,7 @@ function makeConfig(): PieceConfig {
name: 'implement', name: 'implement',
persona: '../agents/coder.md', persona: '../agents/coder.md',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Implement task', instruction: 'Implement task',
passPreviousResponse: true, passPreviousResponse: true,
rules: [{ condition: 'done', next: 'COMPLETE' }], rules: [{ condition: 'done', next: 'COMPLETE' }],
}, },

View File

@ -236,7 +236,7 @@ describe('executePiece debug prompts logging', () => {
name: 'implement', name: 'implement',
persona: '../agents/coder.md', persona: '../agents/coder.md',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Implement task', instruction: 'Implement task',
passPreviousResponse: true, passPreviousResponse: true,
rules: [{ condition: 'done', next: 'COMPLETE' }], rules: [{ condition: 'done', next: 'COMPLETE' }],
}, },

View File

@ -77,7 +77,7 @@ const {
const firstStep = this.config.movements[0]; const firstStep = this.config.movements[0];
if (firstStep) { if (firstStep) {
const providerInfo = resolveProviderInfo(firstStep, this.receivedOptions); const providerInfo = resolveProviderInfo(firstStep, this.receivedOptions);
this.emit('movement:start', firstStep, 1, firstStep.instructionTemplate, providerInfo); this.emit('movement:start', firstStep, 1, firstStep.instruction, providerInfo);
this.emit('movement:complete', firstStep, { this.emit('movement:complete', firstStep, {
persona: firstStep.personaDisplayName, persona: firstStep.personaDisplayName,
status: 'done', status: 'done',
@ -85,7 +85,7 @@ const {
timestamp: new Date('2026-03-04T00:00:00.000Z'), timestamp: new Date('2026-03-04T00:00:00.000Z'),
sessionId: 'movement-session', sessionId: 'movement-session',
providerUsage: mockMovementResponse.providerUsage, providerUsage: mockMovementResponse.providerUsage,
}, firstStep.instructionTemplate); }, firstStep.instruction);
} }
this.emit('piece:complete', { status: 'completed', iteration: 1 }); this.emit('piece:complete', { status: 'completed', iteration: 1 });
return { status: 'completed', iteration: 1 }; return { status: 'completed', iteration: 1 };
@ -230,7 +230,7 @@ function makeConfig(): PieceConfig {
name: 'implement', name: 'implement',
persona: '../agents/coder.md', persona: '../agents/coder.md',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Implement task', instruction: 'Implement task',
passPreviousResponse: true, passPreviousResponse: true,
rules: [{ condition: 'done', next: 'COMPLETE' }], rules: [{ condition: 'done', next: 'COMPLETE' }],
}, },

View File

@ -9,7 +9,7 @@
* - File-based policy content loading via resolveContentPath * - File-based policy content loading via resolveContentPath
*/ */
import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; 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';
@ -371,7 +371,7 @@ describe('InstructionBuilder policy injection', () => {
const step = { const step = {
name: 'test-step', name: 'test-step',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do the thing.', instruction: 'Do the thing.',
passPreviousResponse: false, passPreviousResponse: false,
policyContents: ['# Coding Policy\n\nWrite clean code.'], policyContents: ['# Coding Policy\n\nWrite clean code.'],
}; };
@ -390,7 +390,7 @@ describe('InstructionBuilder policy injection', () => {
const step = { const step = {
name: 'test-step', name: 'test-step',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do the thing.', instruction: 'Do the thing.',
passPreviousResponse: false, passPreviousResponse: false,
policyContents: ['# Coding Policy\n\nWrite clean code.'], policyContents: ['# Coding Policy\n\nWrite clean code.'],
}; };
@ -408,7 +408,7 @@ describe('InstructionBuilder policy injection', () => {
const step = { const step = {
name: 'test-step', name: 'test-step',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do the thing.', instruction: 'Do the thing.',
passPreviousResponse: false, passPreviousResponse: false,
}; };
@ -423,7 +423,7 @@ describe('InstructionBuilder policy injection', () => {
const step = { const step = {
name: 'test-step', name: 'test-step',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do the thing.', instruction: 'Do the thing.',
passPreviousResponse: false, passPreviousResponse: false,
policyContents: ['Policy A content.', 'Policy B content.'], policyContents: ['Policy A content.', 'Policy B content.'],
}; };
@ -441,7 +441,7 @@ describe('InstructionBuilder policy injection', () => {
const step = { const step = {
name: 'test-step', name: 'test-step',
personaDisplayName: 'coder', personaDisplayName: 'coder',
instructionTemplate: 'Do the thing.', instruction: 'Do the thing.',
passPreviousResponse: false, passPreviousResponse: false,
policyContents: ['Step policy.'], policyContents: ['Step policy.'],
}; };
@ -545,7 +545,22 @@ describe('section reference resolution', () => {
}; };
const config = normalizePieceConfig(raw, testDir); const config = normalizePieceConfig(raw, testDir);
expect(config.movements[0]!.instructionTemplate).toBe('Implement the feature.'); expect(config.movements[0]!.instruction).toBe('Implement the feature.');
});
it('should expose normalized movement instruction on instruction field', () => {
const raw = {
name: 'test-piece',
movements: [{
name: 'impl',
persona: 'coder',
instruction: 'Canonical movement instruction',
}],
};
const config = normalizePieceConfig(raw, testDir);
const movement = config.movements[0] as unknown as Record<string, unknown>;
expect(movement.instruction).toBe('Canonical movement instruction');
}); });
it('should resolve output contract from report_formats section by name', () => { it('should resolve output contract from report_formats section by name', () => {
@ -586,7 +601,7 @@ describe('section reference resolution', () => {
expect(config.movements[0]!.persona).toBe('nonexistent'); expect(config.movements[0]!.persona).toBe('nonexistent');
}); });
it('should prefer instruction_template over instruction section reference', () => { it('should prefer instruction over instruction_template when both are provided', () => {
const raw = { const raw = {
name: 'test-piece', name: 'test-piece',
instructions: { implement: './instructions/implement.md' }, instructions: { implement: './instructions/implement.md' },
@ -599,7 +614,144 @@ describe('section reference resolution', () => {
}; };
const config = normalizePieceConfig(raw, testDir); const config = normalizePieceConfig(raw, testDir);
expect(config.movements[0]!.instructionTemplate).toBe('Inline template takes priority.'); expect(config.movements[0]!.instruction).toBe('Implement the feature.');
});
it('should emit deprecation warning when movement uses instruction_template', () => {
// Given: deprecated instruction_template is used on a movement
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
try {
const raw = {
name: 'test-piece',
movements: [{
name: 'impl',
persona: 'coder',
instruction_template: 'Legacy movement instruction',
}],
};
// When: normalizing piece config
normalizePieceConfig(raw, testDir);
// Then: deprecation warning is emitted
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('instruction_template'));
} finally {
warnSpy.mockRestore();
}
});
it('should emit deprecation warning when loop monitor judge uses instruction_template', () => {
// Given: deprecated instruction_template is used on loop monitor judge
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
try {
const raw = {
name: 'test-piece',
movements: [
{
name: 'step1',
persona: 'coder',
instruction: '{task}',
rules: [{ condition: 'next', next: 'step2' }],
},
{
name: 'step2',
persona: 'coder',
instruction: '{task}',
rules: [{ condition: 'done', next: 'COMPLETE' }],
},
],
loop_monitors: [
{
cycle: ['step1', 'step2'],
threshold: 2,
judge: {
persona: 'coder',
instruction_template: 'Legacy judge instruction',
rules: [{ condition: 'continue', next: 'step2' }],
},
},
],
};
// When: normalizing piece config
normalizePieceConfig(raw, testDir);
// Then: deprecation warning is emitted
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('instruction_template'));
} finally {
warnSpy.mockRestore();
}
});
it('should prefer loop monitor judge instruction over instruction_template when both are provided', () => {
const raw = {
name: 'test-piece',
instructions: { judge_template: './instructions/implement.md' },
movements: [
{
name: 'step1',
persona: 'coder',
instruction: '{task}',
rules: [{ condition: 'next', next: 'step2' }],
},
{
name: 'step2',
persona: 'coder',
instruction: '{task}',
rules: [{ condition: 'done', next: 'COMPLETE' }],
},
],
loop_monitors: [
{
cycle: ['step1', 'step2'],
threshold: 2,
judge: {
persona: 'coder',
instruction: 'judge_template',
instruction_template: 'Legacy judge template',
rules: [{ condition: 'continue', next: 'step2' }],
},
},
],
};
const config = normalizePieceConfig(raw, testDir);
expect(config.loopMonitors?.[0]?.judge.instruction).toBe('Implement the feature.');
});
it('should expose normalized loop monitor judge instruction on instruction field', () => {
const raw = {
name: 'test-piece',
movements: [
{
name: 'step1',
persona: 'coder',
instruction: '{task}',
rules: [{ condition: 'next', next: 'step2' }],
},
{
name: 'step2',
persona: 'coder',
instruction: '{task}',
rules: [{ condition: 'done', next: 'COMPLETE' }],
},
],
loop_monitors: [
{
cycle: ['step1', 'step2'],
threshold: 2,
judge: {
persona: 'coder',
instruction: 'Canonical judge instruction',
rules: [{ condition: 'continue', next: 'step2' }],
},
},
],
};
const config = normalizePieceConfig(raw, testDir);
const judge = config.loopMonitors?.[0]?.judge as unknown as Record<string, unknown>;
expect(judge.instruction).toBe('Canonical judge instruction');
}); });
it('should store resolved sections on PieceConfig', () => { it('should store resolved sections on PieceConfig', () => {
@ -653,7 +805,7 @@ describe('section reference resolution', () => {
const parallel = config.movements[0]!.parallel!; const parallel = config.movements[0]!.parallel!;
expect(parallel[0]!.persona).toBe('./personas/coder.md'); expect(parallel[0]!.persona).toBe('./personas/coder.md');
expect(parallel[0]!.policyContents).toEqual(['# Coding Policy\nWrite clean code.']); expect(parallel[0]!.policyContents).toEqual(['# Coding Policy\nWrite clean code.']);
expect(parallel[0]!.instructionTemplate).toBe('Implement the feature.'); expect(parallel[0]!.instruction).toBe('Implement the feature.');
expect(parallel[1]!.policyContents).toEqual([ expect(parallel[1]!.policyContents).toEqual([
'# Coding Policy\nWrite clean code.', '# Coding Policy\nWrite clean code.',
'# Testing Policy\nTest everything.', '# Testing Policy\nTest everything.',

View File

@ -17,7 +17,7 @@ function createStep(fileName: string): PieceMovement {
name: 'implement', name: 'implement',
persona: 'coder', persona: 'coder',
personaDisplayName: 'Coder', personaDisplayName: 'Coder',
instructionTemplate: 'Implement task', instruction: 'Implement task',
passPreviousResponse: false, passPreviousResponse: false,
outputContracts: [{ name: fileName }], outputContracts: [{ name: fileName }],
}; };

View File

@ -11,7 +11,7 @@ function createMovement(overrides: Partial<PieceMovement> = {}): PieceMovement {
name: 'test-movement', name: 'test-movement',
personaDisplayName: 'test', personaDisplayName: 'test',
edit: false, edit: false,
instructionTemplate: '', instruction: '',
passPreviousResponse: true, passPreviousResponse: true,
...overrides, ...overrides,
}; };

View File

@ -43,7 +43,7 @@ describe('runStatusJudgmentPhase', () => {
name: 'review', name: 'review',
persona: 'reviewer', persona: 'reviewer',
personaDisplayName: 'reviewer', personaDisplayName: 'reviewer',
instructionTemplate: 'Review', instruction: 'Review',
passPreviousResponse: true, passPreviousResponse: true,
rules: [ rules: [
{ condition: 'needs_fix', next: 'fix' }, { condition: 'needs_fix', next: 'fix' },
@ -103,7 +103,7 @@ describe('runStatusJudgmentPhase', () => {
name: 'review', name: 'review',
persona: 'reviewer', persona: 'reviewer',
personaDisplayName: 'reviewer', personaDisplayName: 'reviewer',
instructionTemplate: 'Review', instruction: 'Review',
passPreviousResponse: true, passPreviousResponse: true,
rules: [ rules: [
{ condition: 'needs_fix', next: 'fix' }, { condition: 'needs_fix', next: 'fix' },

View File

@ -9,7 +9,7 @@ describe('createPartMovement', () => {
name: 'implement', name: 'implement',
persona: 'coder', persona: 'coder',
personaDisplayName: 'Coder', personaDisplayName: 'Coder',
instructionTemplate: 'do work', instruction: 'do work',
passPreviousResponse: false, passPreviousResponse: false,
providerOptions: { providerOptions: {
claude: { claude: {

View File

@ -16,7 +16,7 @@ export function makeMovement(overrides: Partial<PieceMovement> = {}): PieceMovem
return { return {
name: 'test-movement', name: 'test-movement',
personaDisplayName: 'tester', personaDisplayName: 'tester',
instructionTemplate: '', instruction: '',
passPreviousResponse: false, passPreviousResponse: false,
...overrides, ...overrides,
}; };

View File

@ -12,7 +12,7 @@ function createMovementWithRules(rules: { condition: string; next: string }[]):
name: 'test-step', name: 'test-step',
persona: 'test-agent', persona: 'test-agent',
personaDisplayName: 'Test Agent', personaDisplayName: 'Test Agent',
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: false, passPreviousResponse: false,
rules: rules.map((r) => ({ rules: rules.map((r) => ({
condition: r.condition, condition: r.condition,
@ -47,7 +47,7 @@ describe('determineNextMovementByRules', () => {
name: 'test-step', name: 'test-step',
persona: 'test-agent', persona: 'test-agent',
personaDisplayName: 'Test Agent', personaDisplayName: 'Test Agent',
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: false, passPreviousResponse: false,
}; };
@ -68,7 +68,7 @@ describe('determineNextMovementByRules', () => {
name: 'sub-step', name: 'sub-step',
persona: 'test-agent', persona: 'test-agent',
personaDisplayName: 'Test Agent', personaDisplayName: 'Test Agent',
instructionTemplate: '{task}', instruction: '{task}',
passPreviousResponse: false, passPreviousResponse: false,
rules: [ rules: [
{ condition: 'approved' }, { condition: 'approved' },

View File

@ -126,7 +126,7 @@ function buildTestPieceConfig(): PieceConfig {
name: 'plan', name: 'plan',
persona: '../personas/plan.md', persona: '../personas/plan.md',
personaDisplayName: 'plan', personaDisplayName: 'plan',
instructionTemplate: 'Run plan', instruction: 'Run plan',
passPreviousResponse: true, passPreviousResponse: true,
rules: [], rules: [],
}, },

View File

@ -0,0 +1,47 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
describe('yaml-schema reference', () => {
it('normal movement examples should not duplicate instruction keys in one movement block', () => {
const schemaPath = join(process.cwd(), 'builtins', 'skill', 'references', 'yaml-schema.md');
const schemaText = readFileSync(schemaPath, 'utf-8');
const normalSectionMatch = schemaText.match(/### 通常 Movement([\s\S]*?)### Parallel Movement/);
expect(normalSectionMatch).not.toBeNull();
const normalSection = normalSectionMatch![1];
const yamlBlocks = [...normalSection.matchAll(/```yaml\n([\s\S]*?)```/g)].map((m) => m[1]);
expect(yamlBlocks.length).toBeGreaterThan(0);
const hasDuplicatedInstruction = yamlBlocks.some((block) => {
const instructionKeys = block
.split('\n')
.filter((line) => /^ {2}instruction:\s/.test(line));
return instructionKeys.length > 1;
});
expect(hasDuplicatedInstruction).toBe(false);
});
it('normal movement examples should not duplicate policy keys in one movement block', () => {
const schemaPath = join(process.cwd(), 'builtins', 'skill', 'references', 'yaml-schema.md');
const schemaText = readFileSync(schemaPath, 'utf-8');
const normalSectionMatch = schemaText.match(/### 通常 Movement([\s\S]*?)### Parallel Movement/);
expect(normalSectionMatch).not.toBeNull();
const normalSection = normalSectionMatch![1];
const yamlBlocks = [...normalSection.matchAll(/```yaml\n([\s\S]*?)```/g)].map((m) => m[1]);
expect(yamlBlocks.length).toBeGreaterThan(0);
const hasDuplicatedPolicy = yamlBlocks.some((block) => {
const policyKeys = block
.split('\n')
.filter((line) => /^ {2}policy:\s/.test(line));
return policyKeys.length > 1;
});
expect(hasDuplicatedPolicy).toBe(false);
});
});

View File

@ -42,7 +42,7 @@ export interface OutputContractItem {
format: string; format: string;
/** Whether this report is used as input for status judgment phase (default: true) */ /** Whether this report is used as input for status judgment phase (default: true) */
useJudge?: boolean; useJudge?: boolean;
/** Instruction prepended before instruction_template (e.g., output destination) */ /** Instruction prepended before movement instruction (e.g., output destination) */
order?: string; order?: string;
} }
@ -143,7 +143,7 @@ export interface PieceMovement {
providerOptions?: MovementProviderOptions; providerOptions?: MovementProviderOptions;
/** Whether this movement is allowed to edit project files (true=allowed, false=prohibited, undefined=no prompt) */ /** Whether this movement is allowed to edit project files (true=allowed, false=prohibited, undefined=no prompt) */
edit?: boolean; edit?: boolean;
instructionTemplate: string; instruction: string;
/** Rules for movement routing */ /** Rules for movement routing */
rules?: PieceRule[]; rules?: PieceRule[];
/** Output contracts for this movement (report definitions) */ /** Output contracts for this movement (report definitions) */
@ -219,8 +219,8 @@ export interface LoopMonitorJudge {
persona?: string; persona?: string;
/** Resolved absolute path to persona prompt file (set by loader) */ /** Resolved absolute path to persona prompt file (set by loader) */
personaPath?: string; personaPath?: string;
/** Custom instruction template for the judge (uses default if omitted) */ /** Custom instruction for the judge (uses default if omitted) */
instructionTemplate?: string; instruction?: string;
/** Rules for the judge's decision */ /** Rules for the judge's decision */
rules: LoopMonitorRule[]; rules: LoopMonitorRule[];
} }

View File

@ -162,11 +162,11 @@ export const PieceProviderOptionsSchema = z.object({
export const OutputContractItemSchema = z.object({ export const OutputContractItemSchema = z.object({
/** Report file name */ /** Report file name */
name: z.string().min(1), name: z.string().min(1),
/** Instruction appended after instruction_template (e.g., output format) */ /** Instruction appended after movement instruction (e.g., output format) */
format: z.string().min(1), format: z.string().min(1),
/** Whether this report is used as input for status judgment phase */ /** Whether this report is used as input for status judgment phase */
use_judge: z.boolean().optional().default(true), use_judge: z.boolean().optional().default(true),
/** Instruction prepended before instruction_template (e.g., output destination) */ /** Instruction prepended before movement instruction (e.g., output destination) */
order: z.string().optional(), order: z.string().optional(),
}); });
@ -376,7 +376,9 @@ export const LoopMonitorRuleSchema = z.object({
export const LoopMonitorJudgeSchema = z.object({ export const LoopMonitorJudgeSchema = z.object({
/** Persona reference — key name from piece-level personas map, or file path */ /** Persona reference — key name from piece-level personas map, or file path */
persona: z.string().optional(), persona: z.string().optional(),
/** Custom instruction template for the judge */ /** Custom judge instruction */
instruction: z.string().optional(),
/** Deprecated alias */
instruction_template: z.string().optional(), instruction_template: z.string().optional(),
/** Rules for the judge's decision */ /** Rules for the judge's decision */
rules: z.array(LoopMonitorRuleSchema).min(1), rules: z.array(LoopMonitorRuleSchema).min(1),

View File

@ -462,10 +462,10 @@ export class PieceEngine extends EventEmitter {
} }
/** /**
* Build the default instruction template for a loop monitor judge. * Build the default instruction for a loop monitor judge.
* Used when the monitor config does not specify a custom instruction_template. * Used when the monitor config does not specify a custom instruction.
*/ */
private buildDefaultJudgeInstructionTemplate( private buildDefaultJudgeInstruction(
monitor: LoopMonitorConfig, monitor: LoopMonitorConfig,
cycleCount: number, cycleCount: number,
language: string, language: string,
@ -513,11 +513,11 @@ export class PieceEngine extends EventEmitter {
cycleCount: number, cycleCount: number,
): Promise<string> { ): Promise<string> {
const language = this.options.language ?? 'en'; const language = this.options.language ?? 'en';
const instructionTemplate = monitor.judge.instructionTemplate const instruction = monitor.judge.instruction
?? this.buildDefaultJudgeInstructionTemplate(monitor, cycleCount, language); ?? this.buildDefaultJudgeInstruction(monitor, cycleCount, language);
// Replace {cycle_count} in custom templates // Replace {cycle_count} in custom instructions
const processedTemplate = instructionTemplate.replace(/\{cycle_count\}/g, String(cycleCount)); const processedInstruction = instruction.replace(/\{cycle_count\}/g, String(cycleCount));
// Build a synthetic PieceMovement for the judge // Build a synthetic PieceMovement for the judge
const judgeMovement: PieceMovement = { const judgeMovement: PieceMovement = {
@ -531,7 +531,7 @@ export class PieceEngine extends EventEmitter {
allowedTools: ['Read', 'Glob', 'Grep'], allowedTools: ['Read', 'Glob', 'Grep'],
}, },
}, },
instructionTemplate: processedTemplate, instruction: processedInstruction,
rules: monitor.judge.rules.map((r) => ({ rules: monitor.judge.rules.map((r) => ({
condition: r.condition, condition: r.condition,
next: r.next, next: r.next,
@ -553,7 +553,7 @@ export class PieceEngine extends EventEmitter {
this.emit('movement:start', judgeMovement, this.state.iteration, prebuiltInstruction, this.optionsBuilder.resolveStepProviderModel(judgeMovement)); this.emit('movement:start', judgeMovement, this.state.iteration, prebuiltInstruction, this.optionsBuilder.resolveStepProviderModel(judgeMovement));
const { response, instruction } = await this.movementExecutor.runNormalMovement( const { response, instruction: executedInstruction } = await this.movementExecutor.runNormalMovement(
judgeMovement, judgeMovement,
this.state, this.state,
this.task, this.task,
@ -562,7 +562,7 @@ export class PieceEngine extends EventEmitter {
prebuiltInstruction, prebuiltInstruction,
); );
this.emitCollectedReports(); this.emitCollectedReports();
this.emit('movement:complete', judgeMovement, response, instruction); this.emit('movement:complete', judgeMovement, response, executedInstruction);
// Resolve next movement from the judge's rules // Resolve next movement from the judge's rules
const nextMovement = this.resolveNextMovementFromDone(judgeMovement, response); const nextMovement = this.resolveNextMovementFromDone(judgeMovement, response);

View File

@ -45,7 +45,7 @@ export function createPartMovement(step: PieceMovement, part: PartDefinition): P
model: step.model, model: step.model,
requiredPermissionMode: step.teamLeader.partPermissionMode ?? step.requiredPermissionMode, requiredPermissionMode: step.teamLeader.partPermissionMode ?? step.requiredPermissionMode,
edit: step.teamLeader.partEdit ?? step.edit, edit: step.teamLeader.partEdit ?? step.edit,
instructionTemplate: part.instruction, instruction: part.instruction,
passPreviousResponse: false, passPreviousResponse: false,
}; };
} }

View File

@ -89,7 +89,7 @@ export class InstructionBuilder {
} }
// Skip auto-injection for sections whose placeholders exist in the template // Skip auto-injection for sections whose placeholders exist in the template
const tmpl = this.step.instructionTemplate; const tmpl = this.step.instruction;
const hasTaskPlaceholder = tmpl.includes('{task}'); const hasTaskPlaceholder = tmpl.includes('{task}');
const hasPreviousResponsePlaceholder = tmpl.includes('{previous_response}'); const hasPreviousResponsePlaceholder = tmpl.includes('{previous_response}');
const hasUserInputsPlaceholder = tmpl.includes('{user_inputs}'); const hasUserInputsPlaceholder = tmpl.includes('{user_inputs}');
@ -120,9 +120,9 @@ export class InstructionBuilder {
? escapeTemplateChars(this.context.userInputs.join('\n')) ? escapeTemplateChars(this.context.userInputs.join('\n'))
: ''; : '';
// Instructions (instruction_template processed) // Instructions (movement instruction with placeholder processing)
const instructions = replaceTemplatePlaceholders( const instructions = replaceTemplatePlaceholders(
this.step.instructionTemplate, tmpl,
this.step, this.step,
{ {
...this.context, ...this.context,

View File

@ -272,6 +272,12 @@ function normalizeStepFromRaw(
const expandedInstruction = step.instruction const expandedInstruction = step.instruction
? resolveRefToContent(step.instruction, sections.resolvedInstructions, pieceDir, 'instructions', context) ? resolveRefToContent(step.instruction, sections.resolvedInstructions, pieceDir, 'instructions', context)
: undefined; : undefined;
if (step.instruction_template !== undefined) {
console.warn(`Movement "${step.name}" uses deprecated field "instruction_template". Use "instruction" instead.`);
}
const expandedLegacyInstruction = step.instruction_template
? resolveRefToContent(step.instruction_template, sections.resolvedInstructions, pieceDir, 'instructions', context)
: undefined;
const result: PieceMovement = { const result: PieceMovement = {
name: step.name, name: step.name,
@ -286,9 +292,7 @@ function normalizeStepFromRaw(
requiredPermissionMode: step.required_permission_mode, requiredPermissionMode: step.required_permission_mode,
providerOptions: mergeProviderOptions(inheritedProviderOptions, normalizedProvider.providerOptions), providerOptions: mergeProviderOptions(inheritedProviderOptions, normalizedProvider.providerOptions),
edit: step.edit, edit: step.edit,
instructionTemplate: (step.instruction_template instruction: expandedInstruction || expandedLegacyInstruction || '{task}',
? resolveRefToContent(step.instruction_template, sections.resolvedInstructions, pieceDir, 'instructions', context)
: undefined) || expandedInstruction || '{task}',
rules, rules,
outputContracts: normalizeOutputContracts(step.output_contracts, pieceDir, sections.resolvedReportFormats, context), outputContracts: normalizeOutputContracts(step.output_contracts, pieceDir, sections.resolvedReportFormats, context),
qualityGates: applyQualityGateOverrides( qualityGates: applyQualityGateOverrides(
@ -335,19 +339,25 @@ function normalizeStepFromRaw(
/** Normalize a raw loop monitor judge from YAML into internal format. */ /** Normalize a raw loop monitor judge from YAML into internal format. */
function normalizeLoopMonitorJudge( function normalizeLoopMonitorJudge(
raw: { persona?: string; instruction_template?: string; rules: Array<{ condition: string; next: string }> }, raw: { persona?: string; instruction?: string; instruction_template?: string; rules: Array<{ condition: string; next: string }> },
pieceDir: string, pieceDir: string,
sections: PieceSections, sections: PieceSections,
context?: FacetResolutionContext, context?: FacetResolutionContext,
): LoopMonitorJudge { ): LoopMonitorJudge {
const { personaSpec, personaPath } = resolvePersona(raw.persona, sections, pieceDir, context); const { personaSpec, personaPath } = resolvePersona(raw.persona, sections, pieceDir, context);
if (raw.instruction_template !== undefined) {
console.warn('loop_monitors judge uses deprecated field "instruction_template". Use "instruction" instead.');
}
const resolvedInstruction = raw.instruction
? resolveRefToContent(raw.instruction, sections.resolvedInstructions, pieceDir, 'instructions', context)
: raw.instruction_template
? resolveRefToContent(raw.instruction_template, sections.resolvedInstructions, pieceDir, 'instructions', context)
: undefined;
return { return {
persona: personaSpec, persona: personaSpec,
personaPath, personaPath,
instructionTemplate: raw.instruction_template instruction: resolvedInstruction,
? resolveRefToContent(raw.instruction_template, sections.resolvedInstructions, pieceDir, 'instructions', context)
: undefined,
rules: raw.rules.map((r) => ({ condition: r.condition, next: r.next })), rules: raw.rules.map((r) => ({ condition: r.condition, next: r.next })),
}; };
} }
@ -356,7 +366,7 @@ function normalizeLoopMonitorJudge(
* Normalize raw loop monitors from YAML into internal format. * Normalize raw loop monitors from YAML into internal format.
*/ */
function normalizeLoopMonitors( function normalizeLoopMonitors(
raw: Array<{ cycle: string[]; threshold: number; judge: { persona?: string; instruction_template?: string; rules: Array<{ condition: string; next: string }> } }> | undefined, raw: Array<{ cycle: string[]; threshold: number; judge: { persona?: string; instruction?: string; instruction_template?: string; rules: Array<{ condition: string; next: string }> } }> | undefined,
pieceDir: string, pieceDir: string,
sections: PieceSections, sections: PieceSections,
context?: FacetResolutionContext, context?: FacetResolutionContext,

View File

@ -229,7 +229,7 @@ function buildMovementPreviews(piece: PieceConfig, maxCount: number): MovementPr
name: movement.name, name: movement.name,
personaDisplayName: movement.personaDisplayName, personaDisplayName: movement.personaDisplayName,
personaContent: readMovementPersona(movement), personaContent: readMovementPersona(movement),
instructionContent: movement.instructionTemplate, instructionContent: movement.instruction,
allowedTools: movement.providerOptions?.claude?.allowedTools ?? [], allowedTools: movement.providerOptions?.claude?.allowedTools ?? [],
canEdit: movement.edit === true, canEdit: movement.edit === true,
}); });