takt: github-issue-212-max-iteration-max-movement-ostinato (#217)

This commit is contained in:
nrs 2026-02-10 23:43:29 +09:00 committed by GitHub
parent 0214f7f5e6
commit de6b5b5c2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
115 changed files with 266 additions and 266 deletions

View File

@ -218,7 +218,7 @@ Builtin resources are embedded in the npm package (`builtins/`). User files in `
```yaml
name: piece-name
description: Optional description
max_iterations: 10
max_movements: 10
initial_step: plan # First step to execute
steps:
@ -291,7 +291,7 @@ Key points about parallel steps:
|----------|-------------|
| `{task}` | Original user request (auto-injected if not in template) |
| `{iteration}` | Piece-wide iteration count |
| `{max_iterations}` | Maximum iterations allowed |
| `{max_movements}` | Maximum movements allowed |
| `{step_iteration}` | Per-step iteration count |
| `{previous_response}` | Previous step output (auto-injected if not in template) |
| `{user_inputs}` | Accumulated user inputs (auto-injected if not in template) |

View File

@ -335,7 +335,7 @@ TAKT uses YAML-based piece definitions and rule-based routing. Builtin pieces ar
```yaml
name: default
max_iterations: 10
max_movements: 10
initial_movement: plan
# Section maps — key: file path (relative to this YAML)
@ -709,7 +709,7 @@ takt eject default
# ~/.takt/pieces/my-piece.yaml
name: my-piece
description: Custom piece
max_iterations: 5
max_movements: 5
initial_movement: analyze
personas:
@ -759,7 +759,7 @@ Variables available in `instruction_template`:
|----------|-------------|
| `{task}` | Original user request (auto-injected if not in template) |
| `{iteration}` | Piece-wide turn count (total steps executed) |
| `{max_iterations}` | Maximum iteration count |
| `{max_movements}` | Maximum iteration count |
| `{movement_iteration}` | Per-movement iteration count (times this movement has been executed) |
| `{previous_response}` | Output from previous movement (auto-injected if not in template) |
| `{user_inputs}` | Additional user inputs during piece (auto-injected if not in template) |

View File

@ -1,6 +1,6 @@
name: coding
description: Lightweight development piece with planning and parallel reviews (plan -> implement -> parallel review -> complete)
max_iterations: 20
max_movements: 20
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: compound-eye
description: Multi-model review - send the same instruction to Claude and Codex simultaneously, synthesize both responses
max_iterations: 10
max_movements: 10
initial_movement: evaluate
movements:
- name: evaluate

View File

@ -1,6 +1,6 @@
name: default
description: Standard development piece with planning and specialized reviews
max_iterations: 30
max_movements: 30
initial_movement: plan
loop_monitors:
- cycle:

View File

@ -1,6 +1,6 @@
name: e2e-test
description: E2E test focused piece (E2E analysis → E2E implementation → review → fix)
max_iterations: 20
max_movements: 20
initial_movement: plan_test
loop_monitors:
- cycle:

View File

@ -1,6 +1,6 @@
name: expert-cqrs
description: CQRS+ES, Frontend, Security, QA Expert Review
max_iterations: 30
max_movements: 30
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: expert
description: Architecture, Frontend, Security, QA Expert Review
max_iterations: 30
max_movements: 30
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: frontend
description: Frontend, Security, QA Expert Review
max_iterations: 30
max_movements: 30
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: magi
description: MAGI Deliberation System - Analyze from 3 perspectives and decide by majority
max_iterations: 5
max_movements: 5
initial_movement: melchior
movements:
- name: melchior

View File

@ -1,6 +1,6 @@
name: minimal
description: Minimal development piece (implement -> parallel review -> fix if needed -> complete)
max_iterations: 20
max_movements: 20
initial_movement: implement
movements:
- name: implement

View File

@ -1,6 +1,6 @@
name: passthrough
description: Single-agent thin wrapper. Pass task directly to coder as-is.
max_iterations: 10
max_movements: 10
initial_movement: execute
movements:
- name: execute

View File

@ -1,6 +1,6 @@
name: research
description: Research piece - autonomously executes research without asking questions
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:
- name: plan
@ -13,7 +13,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: plan
@ -48,7 +48,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: dig
@ -88,7 +88,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: supervise (research quality evaluation)

View File

@ -1,6 +1,6 @@
name: review-fix-minimal
description: Review and fix piece for existing code (starts with review, no implementation)
max_iterations: 20
max_movements: 20
initial_movement: reviewers
movements:
- name: implement

View File

@ -1,6 +1,6 @@
name: review-only
description: Review-only piece - reviews code without making edits
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: structural-reform
description: Full project review and structural reform - iterative codebase restructuring with staged file splits
max_iterations: 50
max_movements: 50
initial_movement: review
loop_monitors:
- cycle:
@ -44,7 +44,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: review (full project review)
@ -126,7 +126,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: plan_reform (reform plan creation)
@ -323,7 +323,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: verify (build and test verification)
@ -378,7 +378,7 @@ movements:
- WebFetch
instruction_template: |
## Piece Status
- Iteration: {iteration}/{max_iterations} (piece-wide)
- Iteration: {iteration}/{max_movements} (piece-wide)
- Movement Iteration: {movement_iteration} (times this movement has run)
- Movement: next_target (progress check and next target selection)

View File

@ -1,6 +1,6 @@
name: unit-test
description: Unit test focused piece (test analysis → test implementation → review → fix)
max_iterations: 20
max_movements: 20
initial_movement: plan_test
loop_monitors:
- cycle:

View File

@ -82,7 +82,7 @@ InstructionBuilder が instruction_template 内の `{変数名}` を展開する
| 変数 | 内容 |
|------|------|
| `{iteration}` | ピース全体のイテレーション数 |
| `{max_iterations}` | 最大イテレーション数 |
| `{max_movements}` | 最大イテレーション数 |
| `{movement_iteration}` | ムーブメント単位のイテレーション数 |
| `{report_dir}` | レポートディレクトリ名(`.takt/runs/{slug}/reports` |
| `{report:filename}` | 指定レポートの内容展開(ファイルが存在する場合) |

View File

@ -1,6 +1,6 @@
name: coding
description: Lightweight development piece with planning and parallel reviews (plan -> implement -> parallel review -> complete)
max_iterations: 20
max_movements: 20
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: compound-eye
description: 複眼レビュー - 同じ指示を Claude と Codex に同時に投げ、両者の回答を統合する
max_iterations: 10
max_movements: 10
initial_movement: evaluate
movements:

View File

@ -1,6 +1,6 @@
name: default
description: Standard development piece with planning and specialized reviews
max_iterations: 30
max_movements: 30
initial_movement: plan
loop_monitors:
- cycle:

View File

@ -1,6 +1,6 @@
name: e2e-test
description: E2Eテスト追加に特化したピースE2E分析→E2E実装→レビュー→修正
max_iterations: 20
max_movements: 20
initial_movement: plan_test
loop_monitors:
- cycle:

View File

@ -1,6 +1,6 @@
name: expert-cqrs
description: CQRS+ES・フロントエンド・セキュリティ・QA専門家レビュー
max_iterations: 30
max_movements: 30
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: expert
description: アーキテクチャ・フロントエンド・セキュリティ・QA専門家レビュー
max_iterations: 30
max_movements: 30
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: frontend
description: フロントエンド・セキュリティ・QA専門家レビュー
max_iterations: 30
max_movements: 30
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: magi
description: MAGI合議システム - 3つの観点から分析し多数決で判定
max_iterations: 5
max_movements: 5
initial_movement: melchior
movements:
- name: melchior

View File

@ -1,6 +1,6 @@
name: minimal
description: Minimal development piece (implement -> parallel review -> fix if needed -> complete)
max_iterations: 20
max_movements: 20
initial_movement: implement
movements:
- name: implement

View File

@ -1,6 +1,6 @@
name: passthrough
description: Single-agent thin wrapper. Pass task directly to coder as-is.
max_iterations: 10
max_movements: 10
initial_movement: execute
movements:
- name: execute

View File

@ -1,6 +1,6 @@
name: research
description: 調査ピース - 質問せずに自律的に調査を実行
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:
- name: plan
@ -13,7 +13,7 @@ movements:
- WebFetch
instruction_template: |
## ピース状況
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメント実行回数: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: plan
@ -48,7 +48,7 @@ movements:
- WebFetch
instruction_template: |
## ピース状況
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメント実行回数: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: dig
@ -88,7 +88,7 @@ movements:
- WebFetch
instruction_template: |
## ピース状況
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメント実行回数: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: supervise (調査品質評価)

View File

@ -1,6 +1,6 @@
name: review-fix-minimal
description: 既存コードのレビューと修正ピース(レビュー開始、実装なし)
max_iterations: 20
max_movements: 20
initial_movement: reviewers
movements:
- name: implement

View File

@ -1,6 +1,6 @@
name: review-only
description: レビュー専用ピース - コードをレビューするだけで編集は行わない
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:
- name: plan

View File

@ -1,6 +1,6 @@
name: structural-reform
description: プロジェクト全体レビューと構造改革 - 段階的なファイル分割による反復的コードベース再構築
max_iterations: 50
max_movements: 50
initial_movement: review
loop_monitors:
- cycle:
@ -44,7 +44,7 @@ movements:
- WebFetch
instruction_template: |
## ピースステータス
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメントイテレーション: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: reviewプロジェクト全体レビュー
@ -126,7 +126,7 @@ movements:
- WebFetch
instruction_template: |
## ピースステータス
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメントイテレーション: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: plan_reform改革計画策定
@ -323,7 +323,7 @@ movements:
- WebFetch
instruction_template: |
## ピースステータス
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメントイテレーション: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: verifyビルド・テスト検証
@ -378,7 +378,7 @@ movements:
- WebFetch
instruction_template: |
## ピースステータス
- イテレーション: {iteration}/{max_iterations}(ピース全体)
- イテレーション: {iteration}/{max_movements}(ピース全体)
- ムーブメントイテレーション: {movement_iteration}(このムーブメントの実行回数)
- ムーブメント: next_target進捗確認と次ターゲット選択

View File

@ -1,6 +1,6 @@
name: unit-test
description: 単体テスト追加に特化したピース(テスト分析→テスト実装→レビュー→修正)
max_iterations: 20
max_movements: 20
initial_movement: plan_test
loop_monitors:
- cycle:

View File

@ -83,7 +83,7 @@ $ARGUMENTS を以下のように解析する:
3. 見つからない場合: 上記2ディレクトリを Glob で列挙し、AskUserQuestion で選択させる
YAMLから以下を抽出する→ references/yaml-schema.md 参照):
- `name`, `max_iterations`, `initial_movement`, `movements` 配列
- `name`, `max_movements`, `initial_movement`, `movements` 配列
- セクションマップ: `personas`, `policies`, `instructions`, `output_contracts`, `knowledge`
### 手順 2: セクションリソースの事前読み込み
@ -130,7 +130,7 @@ TeamCreate tool を呼ぶ:
### 手順 5: チームメイト起動
**iteration が max_iterations を超えていたら → 手順 8ABORT: イテレーション上限)に進む。**
**iteration が max_movements を超えていたら → 手順 8ABORT: イテレーション上限)に進む。**
current_movement のプロンプトを構築する(→ references/engine.md のプロンプト構築を参照)。

View File

@ -133,7 +133,7 @@ movement の `instruction:` キーから指示テンプレートファイルを
- ワーキングディレクトリ: {cwd}
- ピース: {piece_name}
- Movement: {movement_name}
- イテレーション: {iteration} / {max_iterations}
- イテレーション: {iteration} / {max_movements}
- Movement イテレーション: {movement_iteration} 回目
```
@ -146,7 +146,7 @@ movement の `instruction:` キーから指示テンプレートファイルを
| `{task}` | ユーザーが入力したタスク内容 |
| `{previous_response}` | 前の movement のチームメイト出力 |
| `{iteration}` | ピース全体のイテレーション数1始まり |
| `{max_iterations}` | ピースの max_iterations 値 |
| `{max_movements}` | ピースの max_movements 値 |
| `{movement_iteration}` | この movement が実行された回数1始まり |
| `{report_dir}` | レポートディレクトリパス(`.takt/runs/{slug}/reports` |
| `{report:ファイル名}` | 指定レポートファイルの内容Read で取得) |
@ -317,7 +317,7 @@ parallel のサブステップにも同様にタグ出力指示を注入する
### 基本ルール
- 同じ movement が連続3回以上実行されたら警告を表示する
- `max_iterations` に到達したら強制終了ABORTする
- `max_movements` に到達したら強制終了ABORTする
### カウンター管理

View File

@ -7,7 +7,7 @@
```yaml
name: piece-name # ピース名(必須)
description: 説明テキスト # ピースの説明(任意)
max_iterations: 10 # 最大イテレーション数(必須)
max_movements: 10 # 最大イテレーション数(必須)
initial_movement: plan # 最初に実行する movement 名(必須)
# セクションマップ(キー → ファイルパスの対応表)
@ -192,7 +192,7 @@ quality_gates:
| `{task}` | ユーザーのタスク入力template に含まれない場合は自動追加) |
| `{previous_response}` | 前の movement の出力pass_previous_response: true 時、自動追加) |
| `{iteration}` | ピース全体のイテレーション数 |
| `{max_iterations}` | 最大イテレーション数 |
| `{max_movements}` | 最大イテレーション数 |
| `{movement_iteration}` | この movement の実行回数 |
| `{report_dir}` | レポートディレクトリ名 |
| `{report:ファイル名}` | 指定レポートファイルの内容を展開 |

View File

@ -335,7 +335,7 @@ TAKTはYAMLベースのピース定義とルールベースルーティングを
```yaml
name: default
max_iterations: 10
max_movements: 10
initial_movement: plan
# セクションマップ — キー: ファイルパスこのYAMLからの相対パス
@ -709,7 +709,7 @@ takt eject default
# ~/.takt/pieces/my-piece.yaml
name: my-piece
description: カスタムピース
max_iterations: 5
max_movements: 5
initial_movement: analyze
personas:
@ -759,7 +759,7 @@ personas:
|------|------|
| `{task}` | 元のユーザーリクエスト(テンプレートになければ自動注入) |
| `{iteration}` | ピース全体のターン数(実行された全ムーブメント数) |
| `{max_iterations}` | 最大イテレーション数 |
| `{max_movements}` | 最大イテレーション数 |
| `{movement_iteration}` | ムーブメントごとのイテレーション数(このムーブメントが実行された回数) |
| `{previous_response}` | 前のムーブメントの出力(テンプレートになければ自動注入) |
| `{user_inputs}` | ピース中の追加ユーザー入力(テンプレートになければ自動注入) |

View File

@ -498,7 +498,7 @@ TAKTのデータフローは以下の7つの主要なレイヤーで構成され
while (state.status === 'running') {
// 1. Abort & Iteration チェック
if (abortRequested) { ... }
if (iteration >= maxIterations) { ... }
if (iteration >= maxMovements) { ... }
// 2. ステップ取得
const step = getStep(state.currentStep);
@ -646,7 +646,7 @@ const match = await detectMatchedRule(step, response.content, tagContent, {...})
- `{previous_response}`: 前ステップの出力
- `{user_inputs}`: 追加ユーザー入力
- `{iteration}`: ピース全体のイテレーション
- `{max_iterations}`: 最大イテレーション
- `{max_movements}`: 最大イテレーション
- `{step_iteration}`: ステップのイテレーション
- `{report_dir}`: レポートディレクトリ
@ -824,7 +824,7 @@ new PieceEngine(pieceConfig, cwd, task, {
1. **コンテキスト収集**:
- `task`: 元のユーザーリクエスト
- `iteration`, `maxIterations`: イテレーション情報
- `iteration`, `maxMovements`: イテレーション情報
- `stepIteration`: ステップごとの実行回数
- `cwd`, `projectCwd`: ディレクトリ情報
- `userInputs`: blocked時の追加入力

View File

@ -331,7 +331,7 @@ Faceted Promptingの中核メカニズムは**宣言的な合成**である。
```yaml
name: my-workflow
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:

View File

@ -331,7 +331,7 @@ Key properties:
```yaml
name: my-workflow
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:

View File

@ -25,7 +25,7 @@ A piece is a YAML file that defines a sequence of steps executed by AI agents. E
```yaml
name: my-piece
description: Optional description
max_iterations: 10
max_movements: 10
initial_step: first-step # Optional, defaults to first step
steps:
@ -55,7 +55,7 @@ steps:
|----------|-------------|
| `{task}` | Original user request (auto-injected if not in template) |
| `{iteration}` | Piece-wide turn count (total steps executed) |
| `{max_iterations}` | Maximum iterations allowed |
| `{max_movements}` | Maximum movements allowed |
| `{step_iteration}` | Per-step iteration count (how many times THIS step has run) |
| `{previous_response}` | Previous step's output (auto-injected if not in template) |
| `{user_inputs}` | Additional user inputs during piece (auto-injected if not in template) |
@ -170,7 +170,7 @@ report:
```yaml
name: simple-impl
max_iterations: 5
max_movements: 5
steps:
- name: implement
@ -191,7 +191,7 @@ steps:
```yaml
name: with-review
max_iterations: 10
max_movements: 10
steps:
- name: implement

View File

@ -1,7 +1,7 @@
name: e2e-mock-max-iter
description: Piece with max_iterations=2 that loops between two steps
description: Piece with max_movements=2 that loops between two steps
max_iterations: 2
max_movements: 2
initial_movement: step-a

View File

@ -1,7 +1,7 @@
name: e2e-mock-no-match
description: Piece with a strict rule condition that will not match mock output
max_iterations: 3
max_movements: 3
movements:
- name: execute

View File

@ -1,7 +1,7 @@
name: e2e-mock-single
description: Minimal mock-only piece for CLI E2E
max_iterations: 3
max_movements: 3
movements:
- name: execute

View File

@ -1,7 +1,7 @@
name: e2e-mock-slow-multi-step
description: Multi-step mock piece to keep tasks in-flight long enough for SIGINT E2E
max_iterations: 20
max_movements: 20
initial_movement: step-1

View File

@ -1,7 +1,7 @@
name: e2e-mock-two-step
description: Two-step sequential piece for E2E testing
max_iterations: 5
max_movements: 5
initial_movement: step-1

View File

@ -1,7 +1,7 @@
name: e2e-multi-step-parallel
description: Multi-step piece with parallel sub-movements for E2E testing
max_iterations: 10
max_movements: 10
initial_movement: plan

View File

@ -1,7 +1,7 @@
name: e2e-report-judge
description: E2E piece that exercises report + judge phases
max_iterations: 3
max_movements: 3
movements:
- name: execute

View File

@ -1,7 +1,7 @@
name: e2e-simple
description: Minimal E2E test piece
max_iterations: 5
max_movements: 5
movements:
- name: execute

View File

@ -69,15 +69,15 @@ describe('E2E: Piece error handling (mock)', () => {
expect(combined).toMatch(/failed|aborted|error/i);
}, 240_000);
it('should abort when max_iterations is reached', () => {
// Given: a piece with max_iterations=2 that loops between step-a and step-b
it('should abort when max_movements is reached', () => {
// Given: a piece with max_movements=2 that loops between step-a and step-b
const piecePath = resolve(__dirname, '../fixtures/pieces/mock-max-iter.yaml');
const scenarioPath = resolve(__dirname, '../fixtures/scenarios/max-iter-loop.json');
// When: executing the piece
const result = runTakt({
args: [
'--task', 'Test max iterations',
'--task', 'Test max movements',
'--piece', piecePath,
'--create-worktree', 'no',
'--provider', 'mock',
@ -93,7 +93,7 @@ describe('E2E: Piece error handling (mock)', () => {
// Then: piece aborts due to iteration limit
expect(result.exitCode).not.toBe(0);
const combined = result.stdout + result.stderr;
expect(combined).toMatch(/Max iterations|iteration|aborted/i);
expect(combined).toMatch(/Max movements|iteration|aborted/i);
}, 240_000);
it('should pass previous response between sequential steps', () => {

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "takt",
"version": "0.11.0",
"version": "0.11.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "takt",
"version": "0.11.0",
"version": "0.11.1",
"license": "MIT",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.37",

View File

@ -22,7 +22,7 @@ describe('StreamDisplay', () => {
describe('progress info display', () => {
const progressInfo: ProgressInfo = {
iteration: 3,
maxIterations: 10,
maxMovements: 10,
movementIndex: 1,
totalMovements: 4,
};
@ -253,7 +253,7 @@ describe('StreamDisplay', () => {
it('should format progress as (iteration/max) step index/total', () => {
const progressInfo: ProgressInfo = {
iteration: 5,
maxIterations: 20,
maxMovements: 20,
movementIndex: 2,
totalMovements: 6,
};
@ -267,7 +267,7 @@ describe('StreamDisplay', () => {
it('should convert 0-indexed movementIndex to 1-indexed display', () => {
const progressInfo: ProgressInfo = {
iteration: 1,
maxIterations: 10,
maxMovements: 10,
movementIndex: 0, // First movement (0-indexed)
totalMovements: 4,
};

View File

@ -188,7 +188,7 @@ describe('loadAllPieces', () => {
const samplePiece = `
name: test-piece
description: Test piece
max_iterations: 10
max_movements: 10
movements:
- name: step1
persona: coder

View File

@ -65,7 +65,7 @@ describe('PieceEngine: Abort (SIGINT)', () => {
function makeSimpleConfig(): PieceConfig {
return {
name: 'test',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'step1',
movements: [
makeMovement('step1', {

View File

@ -54,7 +54,7 @@ describe('PieceEngine agent overrides', () => {
name: 'override-test',
movements: [movement],
initialMovement: 'plan',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([
@ -83,7 +83,7 @@ describe('PieceEngine agent overrides', () => {
name: 'override-fallback',
movements: [movement],
initialMovement: 'plan',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([
@ -114,7 +114,7 @@ describe('PieceEngine agent overrides', () => {
name: 'movement-defaults',
movements: [movement],
initialMovement: 'plan',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([

View File

@ -75,7 +75,7 @@ function buildArpeggioPieceConfig(arpeggioConfig: ArpeggioMovementConfig, tmpDir
return {
name: 'test-arpeggio',
description: 'Test arpeggio piece',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'process',
movements: [
{

View File

@ -117,7 +117,7 @@ describe('PieceEngine Integration: Error Handling', () => {
describe('Loop detection', () => {
it('should abort when loop detected with action: abort', async () => {
const config = buildDefaultPieceConfig({
maxIterations: 100,
maxMovements: 100,
loopDetection: { maxConsecutiveSameStep: 3, action: 'abort' },
initialMovement: 'loop-step',
movements: [
@ -156,7 +156,7 @@ describe('PieceEngine Integration: Error Handling', () => {
// =====================================================
describe('Iteration limit', () => {
it('should abort when max iterations reached without onIterationLimit callback', async () => {
const config = buildDefaultPieceConfig({ maxIterations: 2 });
const config = buildDefaultPieceConfig({ maxMovements: 2 });
const engine = new PieceEngine(config, tmpDir, 'test task', { projectCwd: tmpDir });
mockRunAgentSequence([
@ -182,11 +182,11 @@ describe('PieceEngine Integration: Error Handling', () => {
expect(limitFn).toHaveBeenCalledWith(2, 2);
expect(abortFn).toHaveBeenCalledOnce();
const reason = abortFn.mock.calls[0]![1] as string;
expect(reason).toContain('Max iterations');
expect(reason).toContain('Max movements');
});
it('should extend iterations when onIterationLimit provides additional iterations', async () => {
const config = buildDefaultPieceConfig({ maxIterations: 2 });
const config = buildDefaultPieceConfig({ maxMovements: 2 });
const onIterationLimit = vi.fn().mockResolvedValueOnce(10);

View File

@ -388,7 +388,7 @@ describe('PieceEngine Integration: Happy Path', () => {
it('should pass instruction to movement:start for normal movements', async () => {
const simpleConfig: PieceConfig = {
name: 'test',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'plan',
movements: [
makeMovement('plan', {
@ -456,7 +456,7 @@ describe('PieceEngine Integration: Happy Path', () => {
});
it('should emit iteration:limit when max iterations reached', async () => {
const config = buildDefaultPieceConfig({ maxIterations: 1 });
const config = buildDefaultPieceConfig({ maxMovements: 1 });
engine = new PieceEngine(config, tmpDir, 'test task', { projectCwd: tmpDir });
mockRunAgentSequence([
@ -518,7 +518,7 @@ describe('PieceEngine Integration: Happy Path', () => {
it('should emit phase:start and phase:complete events for Phase 1', async () => {
const simpleConfig: PieceConfig = {
name: 'test',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'plan',
movements: [
makeMovement('plan', {
@ -609,7 +609,7 @@ describe('PieceEngine Integration: Happy Path', () => {
it('should throw when rule references nonexistent movement', () => {
const config: PieceConfig = {
name: 'test',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'step1',
movements: [
makeMovement('step1', {

View File

@ -60,7 +60,7 @@ function buildConfigWithLoopMonitor(
return {
name: 'test-loop-monitor',
description: 'Test piece with loop monitors',
maxIterations: 30,
maxMovements: 30,
initialMovement: 'implement',
loopMonitors: [
{

View File

@ -54,7 +54,7 @@ function buildParallelOnlyConfig(): PieceConfig {
return {
name: 'test-parallel-failure',
description: 'Test parallel failure handling',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'reviewers',
movements: [
makeMovement('reviewers', {

View File

@ -55,7 +55,7 @@ describe('PieceEngine persona_providers override', () => {
name: 'persona-provider-test',
movements: [movement],
initialMovement: 'implement',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([
@ -84,7 +84,7 @@ describe('PieceEngine persona_providers override', () => {
name: 'persona-provider-nomatch',
movements: [movement],
initialMovement: 'plan',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([
@ -114,7 +114,7 @@ describe('PieceEngine persona_providers override', () => {
name: 'movement-over-persona',
movements: [movement],
initialMovement: 'implement',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([
@ -143,7 +143,7 @@ describe('PieceEngine persona_providers override', () => {
name: 'no-persona-providers',
movements: [movement],
initialMovement: 'plan',
maxIterations: 1,
maxMovements: 1,
};
mockRunAgentSequence([
@ -175,7 +175,7 @@ describe('PieceEngine persona_providers override', () => {
name: 'multi-persona-providers',
movements: [planMovement, implementMovement],
initialMovement: 'plan',
maxIterations: 3,
maxMovements: 3,
};
mockRunAgentSequence([

View File

@ -70,7 +70,7 @@ export function buildDefaultPieceConfig(overrides: Partial<PieceConfig> = {}): P
return {
name: 'test-default',
description: 'Test piece',
maxIterations: 30,
maxMovements: 30,
initialMovement: 'plan',
movements: [
makeMovement('plan', {

View File

@ -64,7 +64,7 @@ function buildSimpleConfig(): PieceConfig {
return {
name: 'worktree-test',
description: 'Test piece for worktree',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'review',
movements: [
makeMovement('review', {
@ -133,7 +133,7 @@ describe('PieceEngine: worktree reportDir resolution', () => {
const config: PieceConfig = {
name: 'worktree-test',
description: 'Test',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'review',
movements: [
makeMovement('review', {
@ -247,7 +247,7 @@ describe('PieceEngine: worktree reportDir resolution', () => {
const config: PieceConfig = {
name: 'snapshot-test',
description: 'Test',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'implement',
movements: [
makeMovement('implement', {

View File

@ -26,7 +26,7 @@ function makeContext(overrides: Partial<InstructionContext> = {}): InstructionCo
return {
task: 'test task',
iteration: 1,
maxIterations: 10,
maxMovements: 10,
movementIteration: 1,
cwd: '/tmp/test',
projectCwd: '/tmp/project',
@ -78,10 +78,10 @@ describe('replaceTemplatePlaceholders', () => {
expect(result).toBe('fix bug in code');
});
it('should replace {iteration} and {max_iterations}', () => {
it('should replace {iteration} and {max_movements}', () => {
const step = makeMovement();
const ctx = makeContext({ iteration: 3, maxIterations: 20 });
const template = 'Iteration {iteration}/{max_iterations}';
const ctx = makeContext({ iteration: 3, maxMovements: 20 });
const template = 'Iteration {iteration}/{max_movements}';
const result = replaceTemplatePlaceholders(template, step, ctx);
expect(result).toBe('Iteration 3/20');
@ -186,11 +186,11 @@ describe('replaceTemplatePlaceholders', () => {
const ctx = makeContext({
task: 'test task',
iteration: 2,
maxIterations: 5,
maxMovements: 5,
movementIteration: 1,
reportDir: '/reports',
});
const template = '{task} - iter {iteration}/{max_iterations} - mv {movement_iteration} - dir {report_dir}';
const template = '{task} - iter {iteration}/{max_movements} - mv {movement_iteration} - dir {report_dir}';
const result = replaceTemplatePlaceholders(template, step, ctx);
expect(result).toBe('test task - iter 2/5 - mv 1 - dir /reports');

View File

@ -37,7 +37,7 @@ describe('getLabel', () => {
it('replaces {variableName} placeholders with provided values', () => {
const result = getLabel('piece.iterationLimit.maxReached', undefined, {
currentIteration: '5',
maxIterations: '10',
maxMovements: '10',
});
expect(result).toContain('(5/10)');
});

View File

@ -27,7 +27,7 @@ function makeContext(overrides: Partial<InstructionContext> = {}): InstructionCo
return {
task: 'test task',
iteration: 1,
maxIterations: 10,
maxMovements: 10,
movementIteration: 1,
cwd: '/tmp/test',
projectCwd: '/tmp/project',

View File

@ -41,7 +41,7 @@ function createMinimalContext(overrides: Partial<InstructionContext> = {}): Inst
return {
task: 'Test task',
iteration: 1,
maxIterations: 10,
maxMovements: 10,
movementIteration: 1,
cwd: '/project',
projectCwd: '/project',
@ -458,7 +458,7 @@ describe('instruction-builder', () => {
step.name = 'implement';
const context = createMinimalContext({
iteration: 3,
maxIterations: 20,
maxMovements: 20,
movementIteration: 2,
language: 'en',
});
@ -1035,9 +1035,9 @@ describe('instruction-builder', () => {
expect(result).toContain('Build the app');
});
it('should replace {iteration} and {max_iterations}', () => {
const step = createMinimalStep('Step {iteration}/{max_iterations}');
const context = createMinimalContext({ iteration: 3, maxIterations: 20 });
it('should replace {iteration} and {max_movements}', () => {
const step = createMinimalStep('Step {iteration}/{max_movements}');
const context = createMinimalContext({ iteration: 3, maxMovements: 20 });
const result = buildInstruction(step, context);

View File

@ -99,11 +99,11 @@ function buildEngineOptions(projectCwd: string) {
};
}
function buildPiece(agentPaths: Record<string, string>, maxIterations: number): PieceConfig {
function buildPiece(agentPaths: Record<string, string>, maxMovements: number): PieceConfig {
return {
name: 'it-error',
description: 'IT error recovery piece',
maxIterations,
maxMovements,
initialMovement: 'plan',
movements: [
makeMovement('plan', agentPaths.plan, [

View File

@ -57,7 +57,7 @@ function makeContext(overrides: Partial<InstructionContext> = {}): InstructionCo
return {
task: 'Test task description',
iteration: 3,
maxIterations: 30,
maxMovements: 30,
movementIteration: 2,
cwd: '/tmp/test-project',
projectCwd: '/tmp/test-project',
@ -176,11 +176,11 @@ describe('Instruction Builder IT: user_inputs auto-injection', () => {
});
describe('Instruction Builder IT: iteration variables', () => {
it('should replace {iteration}, {max_iterations}, {movement_iteration} in template', () => {
it('should replace {iteration}, {max_movements}, {movement_iteration} in template', () => {
const step = makeMovement({
instructionTemplate: 'Iter: {iteration}/{max_iterations}, movement iter: {movement_iteration}',
instructionTemplate: 'Iter: {iteration}/{max_movements}, movement iter: {movement_iteration}',
});
const ctx = makeContext({ iteration: 5, maxIterations: 30, movementIteration: 2 });
const ctx = makeContext({ iteration: 5, maxMovements: 30, movementIteration: 2 });
const result = buildInstruction(step, ctx);
@ -189,7 +189,7 @@ describe('Instruction Builder IT: iteration variables', () => {
it('should include iteration in Piece Context section', () => {
const step = makeMovement();
const ctx = makeContext({ iteration: 7, maxIterations: 20, movementIteration: 3 });
const ctx = makeContext({ iteration: 7, maxMovements: 20, movementIteration: 3 });
const result = buildInstruction(step, ctx);

View File

@ -74,7 +74,7 @@ const {
if (this.onIterationLimit) {
await this.onIterationLimit({
currentIteration: 10,
maxIterations: 10,
maxMovements: 10,
currentMovement: 'step1',
});
}
@ -201,7 +201,7 @@ import type { PieceConfig } from '../core/models/index.js';
function makeConfig(): PieceConfig {
return {
name: 'test-notify',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'step1',
movements: [
{

View File

@ -105,7 +105,7 @@ function buildSimplePiece(agentPaths: Record<string, string>): PieceConfig {
return {
name: 'it-simple',
description: 'IT simple piece',
maxIterations: 15,
maxMovements: 15,
initialMovement: 'plan',
movements: [
makeMovement('plan', agentPaths.planner, [
@ -128,7 +128,7 @@ function buildLoopPiece(agentPaths: Record<string, string>): PieceConfig {
return {
name: 'it-loop',
description: 'IT piece with fix loop',
maxIterations: 20,
maxMovements: 20,
initialMovement: 'plan',
movements: [
makeMovement('plan', agentPaths.planner, [
@ -286,7 +286,7 @@ describe('Piece Engine IT: Max Iterations', () => {
rmSync(testDir, { recursive: true, force: true });
});
it('should abort when maxIterations exceeded in infinite loop', async () => {
it('should abort when maxMovements exceeded in infinite loop', async () => {
// Create an infinite loop: plan always goes to implement, implement always goes back to plan
const infiniteScenario = Array.from({ length: 10 }, (_, i) => ({
status: 'done' as const,
@ -295,7 +295,7 @@ describe('Piece Engine IT: Max Iterations', () => {
setMockScenario(infiniteScenario);
const config = buildSimplePiece(agentPaths);
config.maxIterations = 5;
config.maxMovements = 5;
const engine = new PieceEngine(config, testDir, 'Looping task', {
...buildEngineOptions(testDir),

View File

@ -58,7 +58,7 @@ describe('Piece Loader IT: builtin piece loading', () => {
expect(config!.name).toBe(name);
expect(config!.movements.length).toBeGreaterThan(0);
expect(config!.initialMovement).toBeDefined();
expect(config!.maxIterations).toBeGreaterThan(0);
expect(config!.maxMovements).toBeGreaterThan(0);
});
}
@ -123,7 +123,7 @@ describe('Piece Loader IT: project-local piece override', () => {
writeFileSync(join(piecesDir, 'custom-wf.yaml'), `
name: custom-wf
description: Custom project piece
max_iterations: 5
max_movements: 5
initial_movement: start
movements:
@ -250,11 +250,11 @@ describe('Piece Loader IT: piece config validation', () => {
rmSync(testDir, { recursive: true, force: true });
});
it('should set max_iterations from YAML', () => {
it('should set max_movements from YAML', () => {
const config = loadPiece('minimal', testDir);
expect(config).not.toBeNull();
expect(typeof config!.maxIterations).toBe('number');
expect(config!.maxIterations).toBeGreaterThan(0);
expect(typeof config!.maxMovements).toBe('number');
expect(config!.maxMovements).toBeGreaterThan(0);
});
it('should set initial_movement from YAML', () => {
@ -397,7 +397,7 @@ describe('Piece Loader IT: quality_gates loading', () => {
writeFileSync(join(piecesDir, 'with-gates.yaml'), `
name: with-gates
description: Piece with quality gates
max_iterations: 5
max_movements: 5
initial_movement: implement
movements:
@ -434,7 +434,7 @@ movements:
writeFileSync(join(piecesDir, 'no-gates.yaml'), `
name: no-gates
description: Piece without quality gates
max_iterations: 5
max_movements: 5
initial_movement: implement
movements:
@ -461,7 +461,7 @@ movements:
writeFileSync(join(piecesDir, 'empty-gates.yaml'), `
name: empty-gates
description: Piece with empty quality gates
max_iterations: 5
max_movements: 5
initial_movement: implement
movements:
@ -501,7 +501,7 @@ describe('Piece Loader IT: mcp_servers parsing', () => {
writeFileSync(join(piecesDir, 'with-mcp.yaml'), `
name: with-mcp
description: Piece with MCP servers
max_iterations: 5
max_movements: 5
initial_movement: e2e-test
movements:
@ -541,7 +541,7 @@ movements:
writeFileSync(join(piecesDir, 'no-mcp.yaml'), `
name: no-mcp
description: Piece without MCP servers
max_iterations: 5
max_movements: 5
initial_movement: implement
movements:
@ -568,7 +568,7 @@ movements:
writeFileSync(join(piecesDir, 'multi-mcp.yaml'), `
name: multi-mcp
description: Piece with multiple MCP servers
max_iterations: 5
max_movements: 5
initial_movement: test
movements:
@ -625,7 +625,7 @@ describe('Piece Loader IT: structural-reform piece', () => {
expect(config).not.toBeNull();
expect(config!.name).toBe('structural-reform');
expect(config!.movements.length).toBe(7);
expect(config!.maxIterations).toBe(50);
expect(config!.maxMovements).toBe(50);
expect(config!.initialMovement).toBe('review');
});

View File

@ -171,7 +171,7 @@ function createTestPieceDir(): { dir: string; piecePath: string } {
const pieceYaml = `
name: it-pipeline
description: Pipeline test piece
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:

View File

@ -152,7 +152,7 @@ function createTestPieceDir(): { dir: string; piecePath: string } {
const pieceYaml = `
name: it-simple
description: Integration test piece
max_iterations: 10
max_movements: 10
initial_movement: plan
movements:

View File

@ -196,7 +196,7 @@ describe('executePiece: SIGINT handler integration', () => {
function makeConfig(): PieceConfig {
return {
name: 'test-sigint',
maxIterations: 10,
maxMovements: 10,
initialMovement: 'step1',
movements: [
{

View File

@ -133,7 +133,7 @@ describe('Three-Phase Execution IT: phase1 only (no report, no tag rules)', () =
const config: PieceConfig = {
name: 'it-phase1-only',
description: 'Test',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'step',
movements: [
makeMovement('step', agentPath, [
@ -185,7 +185,7 @@ describe('Three-Phase Execution IT: phase1 + phase2 (report defined)', () => {
const config: PieceConfig = {
name: 'it-phase1-2',
description: 'Test',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'step',
movements: [
makeMovement('step', agentPath, [
@ -215,7 +215,7 @@ describe('Three-Phase Execution IT: phase1 + phase2 (report defined)', () => {
const config: PieceConfig = {
name: 'it-phase1-2-multi',
description: 'Test',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'step',
movements: [
makeMovement('step', agentPath, [
@ -266,7 +266,7 @@ describe('Three-Phase Execution IT: phase1 + phase3 (tag rules defined)', () =>
const config: PieceConfig = {
name: 'it-phase1-3',
description: 'Test',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'step',
movements: [
makeMovement('step', agentPath, [
@ -317,7 +317,7 @@ describe('Three-Phase Execution IT: all three phases', () => {
const config: PieceConfig = {
name: 'it-all-phases',
description: 'Test',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'step',
movements: [
makeMovement('step', agentPath, [
@ -377,7 +377,7 @@ describe('Three-Phase Execution IT: phase3 tag → rule match', () => {
const config: PieceConfig = {
name: 'it-phase3-tag',
description: 'Test',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'step1',
movements: [
makeMovement('step1', agentPath, [

View File

@ -330,7 +330,7 @@ function createMinimalContext(overrides: Partial<InstructionContext> = {}): Inst
return {
task: 'Test task',
iteration: 1,
maxIterations: 10,
maxMovements: 10,
movementIteration: 1,
cwd: '/tmp/test',
projectCwd: '/tmp/test',

View File

@ -81,7 +81,7 @@ describe('PieceConfigRawSchema', () => {
expect(result.name).toBe('test-piece');
expect(result.movements).toHaveLength(1);
expect(result.movements![0]?.allowed_tools).toEqual(['Read', 'Grep']);
expect(result.max_iterations).toBe(10);
expect(result.max_movements).toBe(10);
});
it('should parse movement with permission_mode', () => {

View File

@ -145,7 +145,7 @@ describe('PieceConfigRawSchema with parallel movements', () => {
},
],
initial_movement: 'plan',
max_iterations: 10,
max_movements: 10,
};
const result = PieceConfigRawSchema.safeParse(raw);

View File

@ -74,7 +74,7 @@ describe('ParallelLogger', () => {
writeFn,
progressInfo: {
iteration: 4,
maxIterations: 30,
maxMovements: 30,
},
taskLabel: 'override-persona-provider',
taskColorIndex: 0,
@ -529,7 +529,7 @@ describe('ParallelLogger', () => {
writeFn,
progressInfo: {
iteration: 3,
maxIterations: 10,
maxMovements: 10,
},
});
@ -545,7 +545,7 @@ describe('ParallelLogger', () => {
writeFn,
progressInfo: {
iteration: 5,
maxIterations: 20,
maxMovements: 20,
},
});
@ -576,7 +576,7 @@ describe('ParallelLogger', () => {
writeFn,
progressInfo: {
iteration: 2,
maxIterations: 5,
maxMovements: 5,
},
});
const handler = logger.createStreamHandler('step-a', 0);

View File

@ -24,7 +24,7 @@ import {
const SAMPLE_PIECE = `name: test-piece
description: Test piece
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1

View File

@ -65,7 +65,7 @@ function createPieceMap(entries: { name: string; source: 'builtin' | 'user' | 'p
name: entry.name,
movements: [],
initialMovement: 'start',
maxIterations: 1,
maxMovements: 1,
},
});
}

View File

@ -78,7 +78,7 @@ function createPieceMap(entries: { name: string; source: 'user' | 'builtin' }[])
name: e.name,
movements: [],
initialMovement: 'start',
maxIterations: 1,
maxMovements: 1,
},
});
}

View File

@ -170,7 +170,7 @@ describe('executePiece debug prompts logging', () => {
function makeConfig(): PieceConfig {
return {
name: 'test-piece',
maxIterations: 5,
maxMovements: 5,
initialMovement: 'implement',
movements: [
{

View File

@ -16,7 +16,7 @@ import {
const SAMPLE_PIECE = `name: test-piece
description: Test piece
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -173,7 +173,7 @@ describe('loadAllPieces with project-local', () => {
const overridePiece = `name: project-override
description: Project override
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1

View File

@ -23,7 +23,7 @@ describe('getPieceDescription', () => {
const pieceYaml = `name: test-piece
description: Test piece for workflow
initial_movement: plan
max_iterations: 3
max_movements: 3
movements:
- name: plan
@ -56,7 +56,7 @@ movements:
const pieceYaml = `name: coding
description: Full coding workflow
initial_movement: plan
max_iterations: 10
max_movements: 10
movements:
- name: plan
@ -98,7 +98,7 @@ movements:
it('should handle movements without descriptions', () => {
const pieceYaml = `name: minimal
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -132,7 +132,7 @@ movements:
it('should handle parallel movements without descriptions', () => {
const pieceYaml = `name: test-parallel
initial_movement: parent
max_iterations: 1
max_movements: 1
movements:
- name: parent
@ -174,7 +174,7 @@ describe('getPieceDescription with movementPreviews', () => {
const pieceYaml = `name: preview-test
description: Test piece
initial_movement: plan
max_iterations: 5
max_movements: 5
movements:
- name: plan
@ -237,7 +237,7 @@ movements:
it('should return empty previews when previewCount is 0', () => {
const pieceYaml = `name: test
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -256,7 +256,7 @@ movements:
it('should return empty previews when previewCount is not specified', () => {
const pieceYaml = `name: test
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -275,7 +275,7 @@ movements:
it('should stop at COMPLETE movement', () => {
const pieceYaml = `name: test-complete
initial_movement: step1
max_iterations: 3
max_movements: 3
movements:
- name: step1
@ -301,7 +301,7 @@ movements:
it('should stop at ABORT movement', () => {
const pieceYaml = `name: test-abort
initial_movement: step1
max_iterations: 3
max_movements: 3
movements:
- name: step1
@ -331,7 +331,7 @@ movements:
const pieceYaml = `name: test-persona-file
initial_movement: plan
max_iterations: 1
max_movements: 1
personas:
planner: ./planner.md
@ -355,7 +355,7 @@ movements:
it('should limit previews to maxCount', () => {
const pieceYaml = `name: test-limit
initial_movement: step1
max_iterations: 5
max_movements: 5
movements:
- name: step1
@ -388,7 +388,7 @@ movements:
it('should handle movements without rules (stop after first)', () => {
const pieceYaml = `name: test-no-rules
initial_movement: step1
max_iterations: 3
max_movements: 3
movements:
- name: step1
@ -411,7 +411,7 @@ movements:
it('should return empty previews when initial movement not found in list', () => {
const pieceYaml = `name: test-missing-initial
initial_movement: nonexistent
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -430,7 +430,7 @@ movements:
it('should handle self-referencing rule (prevent infinite loop)', () => {
const pieceYaml = `name: test-self-ref
initial_movement: step1
max_iterations: 5
max_movements: 5
movements:
- name: step1
@ -453,7 +453,7 @@ movements:
it('should handle multi-node cycle A→B→A (prevent duplicate previews)', () => {
const pieceYaml = `name: test-cycle
initial_movement: stepA
max_iterations: 10
max_movements: 10
movements:
- name: stepA
@ -489,7 +489,7 @@ movements:
it('should use inline persona content when no personaPath', () => {
const pieceYaml = `name: test-inline
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -515,7 +515,7 @@ movements:
const pieceYaml = `name: test-unreadable-persona
initial_movement: plan
max_iterations: 1
max_movements: 1
personas:
planner: ./unreadable-persona.md
@ -545,7 +545,7 @@ movements:
it('should include personaDisplayName in previews', () => {
const pieceYaml = `name: test-display
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -578,7 +578,7 @@ describe('getPieceDescription interactiveMode field', () => {
it('should return interactiveMode when piece defines interactive_mode', () => {
const pieceYaml = `name: test-mode
initial_movement: step1
max_iterations: 1
max_movements: 1
interactive_mode: quiet
movements:
@ -598,7 +598,7 @@ movements:
it('should return undefined interactiveMode when piece omits interactive_mode', () => {
const pieceYaml = `name: test-no-mode
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -618,7 +618,7 @@ movements:
for (const mode of ['assistant', 'persona', 'quiet', 'passthrough'] as const) {
const pieceYaml = `name: test-${mode}
initial_movement: step1
max_iterations: 1
max_movements: 1
interactive_mode: ${mode}
movements:
@ -651,7 +651,7 @@ describe('getPieceDescription firstMovement field', () => {
it('should return firstMovement with inline persona content', () => {
const pieceYaml = `name: test-first
initial_movement: plan
max_iterations: 1
max_movements: 1
movements:
- name: plan
@ -681,7 +681,7 @@ movements:
const pieceYaml = `name: test-persona-file
initial_movement: plan
max_iterations: 1
max_movements: 1
personas:
planner: ./planner-persona.md
@ -705,7 +705,7 @@ movements:
it('should return undefined firstMovement when initialMovement not found', () => {
const pieceYaml = `name: test-missing
initial_movement: nonexistent
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -724,7 +724,7 @@ movements:
it('should return empty allowedTools array when movement has no tools', () => {
const pieceYaml = `name: test-no-tools
initial_movement: step1
max_iterations: 1
max_movements: 1
movements:
- name: step1
@ -749,7 +749,7 @@ movements:
const pieceYaml = `name: test-fallback
initial_movement: step1
max_iterations: 1
max_movements: 1
personas:
myagent: ./unreadable.md

View File

@ -27,7 +27,7 @@ function makeContext(overrides: Partial<InstructionContext> = {}): InstructionCo
return {
task: 'Test task',
iteration: 1,
maxIterations: 10,
maxMovements: 10,
movementIteration: 1,
cwd: '/tmp/test',
projectCwd: '/tmp/test',

View File

@ -36,8 +36,8 @@ describe('review-only piece (EN)', () => {
expect(raw.initial_movement).toBe('plan');
});
it('should have max_iterations of 10', () => {
expect(raw.max_iterations).toBe(10);
it('should have max_movements of 10', () => {
expect(raw.max_movements).toBe(10);
});
it('should have 4 movements: plan, reviewers, supervise, pr-comment', () => {

View File

@ -311,7 +311,7 @@ describe('runAllTasks concurrency', () => {
name: 'default',
movements: [{ name: 'implement', personaDisplayName: 'coder' }],
initialMovement: 'implement',
maxIterations: 10,
maxMovements: 10,
};
beforeEach(() => {

View File

@ -131,7 +131,7 @@ describe('resolveAutoPr default in selectAndExecuteTask', () => {
name: 'default',
movements: [],
initialMovement: 'start',
maxIterations: 1,
maxMovements: 1,
},
}],
]));

View File

@ -188,7 +188,7 @@ describe('NDJSON log', () => {
const abort: NdjsonPieceAbort = {
type: 'piece_abort',
iterations: 1,
reason: 'Max iterations reached',
reason: 'Max movements reached',
endTime: '2025-01-01T00:00:03.000Z',
};
appendNdjsonLine(filepath, abort);

View File

@ -22,7 +22,7 @@ function makeConfig(overrides: Partial<PieceConfig> = {}): PieceConfig {
name: 'test-piece',
movements: [],
initialMovement: 'start',
maxIterations: 10,
maxMovements: 10,
...overrides,
};
}

View File

@ -57,7 +57,7 @@ describe('switchPiece', () => {
name: 'default',
movements: [],
initialMovement: 'start',
maxIterations: 1,
maxMovements: 1,
},
}],
]));

View File

@ -188,7 +188,7 @@ describe('TaskPrefixWriter', () => {
writer.setMovementContext({
movementName: 'implement',
iteration: 4,
maxIterations: 30,
maxMovements: 30,
movementIteration: 2,
});
writer.writeLine('content');

View File

@ -51,7 +51,7 @@ const defaultPieceConfig: PieceConfig = {
name: 'default',
description: 'Default piece',
initialMovement: 'plan',
maxIterations: 30,
maxMovements: 30,
movements: [
{ name: 'plan', persona: 'planner', instruction: '' },
{ name: 'implement', persona: 'coder', instruction: '' },

View File

@ -210,7 +210,7 @@ export interface PieceConfig {
reportFormats?: Record<string, string>;
movements: PieceMovement[];
initialMovement: string;
maxIterations: number;
maxMovements: number;
/** Loop detection settings */
loopDetection?: LoopDetectionConfig;
/** Loop monitors for detecting cyclic patterns between movements */

View File

@ -281,7 +281,7 @@ export const PieceConfigRawSchema = z.object({
report_formats: z.record(z.string(), z.string()).optional(),
movements: z.array(PieceMovementRawSchema).min(1),
initial_movement: z.string().optional(),
max_iterations: z.number().int().positive().optional().default(10),
max_movements: z.number().int().positive().optional().default(10),
loop_monitors: z.array(LoopMonitorSchema).optional(),
answer_agent: z.string().optional(),
/** Default interactive mode for this piece (overrides user default) */

View File

@ -12,7 +12,7 @@ export interface SessionState {
task: string;
projectDir: string;
iteration: number;
maxIterations: number;
maxMovements: number;
coderStatus: Status;
architectStatus: Status;
supervisorStatus: Status;
@ -32,7 +32,7 @@ export function createSessionState(
task,
projectDir,
iteration: 0,
maxIterations: 10,
maxMovements: 10,
coderStatus: 'pending',
architectStatus: 'pending',
supervisorStatus: 'pending',

View File

@ -19,5 +19,5 @@ export const ERROR_MESSAGES = {
`Loop detected: movement "${movementName}" ran ${count} times consecutively without progress.`,
UNKNOWN_MOVEMENT: (movementName: string) => `Unknown movement: ${movementName}`,
MOVEMENT_EXECUTION_FAILED: (message: string) => `Movement execution failed: ${message}`,
MAX_ITERATIONS_REACHED: 'Max iterations reached',
MAX_MOVEMENTS_REACHED: 'Max movements reached',
};

View File

@ -131,7 +131,7 @@ export class MovementExecutor {
movementIteration: number,
state: PieceState,
task: string,
maxIterations: number,
maxMovements: number,
): string {
this.ensurePreviousResponseSnapshot(state, step.name, movementIteration);
const policySnapshot = this.writeFacetSnapshot(
@ -150,7 +150,7 @@ export class MovementExecutor {
return new InstructionBuilder(step, {
task,
iteration: state.iteration,
maxIterations,
maxMovements,
movementIteration,
cwd: this.deps.getCwd(),
projectCwd: this.deps.getProjectCwd(),
@ -182,14 +182,14 @@ export class MovementExecutor {
step: PieceMovement,
state: PieceState,
task: string,
maxIterations: number,
maxMovements: number,
updatePersonaSession: (persona: string, sessionId: string | undefined) => void,
prebuiltInstruction?: string,
): Promise<{ response: AgentResponse; instruction: string }> {
const movementIteration = prebuiltInstruction
? state.movementIterations.get(step.name) ?? 1
: incrementMovementIteration(state, step.name);
const instruction = prebuiltInstruction ?? this.buildInstruction(step, movementIteration, state, task, maxIterations);
const instruction = prebuiltInstruction ?? this.buildInstruction(step, movementIteration, state, task, maxMovements);
const sessionKey = buildSessionKey(step);
log.debug('Running movement', {
movement: step.name,

Some files were not shown because too many files have changed in this diff Show More