@bonesofspring/ai-rules 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/presets/_shared/core/agent-team/agent-artifact-contracts.md +114 -0
  4. package/presets/_shared/core/agent-team/agent-team-orchestrator.md +64 -5
  5. package/presets/claude/go/agents/build-verifier.md +9 -2
  6. package/presets/claude/go/agents/code-reviewer.md +8 -1
  7. package/presets/claude/go/agents/feature-developer.md +8 -1
  8. package/presets/claude/go/agents/task-analyst.md +8 -1
  9. package/presets/claude/go/agents/task-router.md +33 -5
  10. package/presets/claude/go/commands/task.md +8 -6
  11. package/presets/claude/go/hooks/chain-team-phases.sh +195 -53
  12. package/presets/claude/go/rules/tooling-and-review/agent-team-orchestrator.md +30 -5
  13. package/presets/claude/go/team/README.md +13 -2
  14. package/presets/claude/ios-swift/agents/build-verifier.md +8 -1
  15. package/presets/claude/ios-swift/agents/code-reviewer.md +9 -14
  16. package/presets/claude/ios-swift/agents/feature-developer.md +8 -1
  17. package/presets/claude/ios-swift/agents/task-analyst.md +8 -1
  18. package/presets/claude/ios-swift/agents/task-router.md +31 -3
  19. package/presets/claude/ios-swift/commands/task.md +8 -6
  20. package/presets/claude/ios-swift/hooks/chain-team-phases.sh +177 -54
  21. package/presets/claude/ios-swift/rules/tooling-and-review/agent-team-orchestrator.md +33 -2
  22. package/presets/claude/ios-swift/team/README.md +13 -2
  23. package/presets/claude/java/agents/build-verifier.md +8 -1
  24. package/presets/claude/java/agents/code-reviewer.md +8 -1
  25. package/presets/claude/java/agents/feature-developer.md +8 -1
  26. package/presets/claude/java/agents/task-analyst.md +8 -1
  27. package/presets/claude/java/agents/task-router.md +31 -3
  28. package/presets/claude/java/commands/task.md +8 -6
  29. package/presets/claude/java/hooks/chain-team-phases.sh +195 -53
  30. package/presets/claude/java/rules/tooling-and-review/agent-team-orchestrator.md +30 -5
  31. package/presets/claude/java/team/README.md +13 -2
  32. package/presets/claude/mcp-ts/agents/build-verifier.md +9 -2
  33. package/presets/claude/mcp-ts/agents/feature-developer.md +7 -1
  34. package/presets/claude/mcp-ts/agents/task-analyst.md +8 -1
  35. package/presets/claude/mcp-ts/agents/task-router.md +33 -5
  36. package/presets/claude/mcp-ts/commands/task.md +8 -6
  37. package/presets/claude/mcp-ts/hooks/chain-team-phases.sh +181 -39
  38. package/presets/claude/mcp-ts/rules/tooling-and-review/agent-team-orchestrator.md +30 -5
  39. package/presets/claude/mcp-ts/team/README.md +13 -2
  40. package/presets/claude/next/agents/accessibility-reviewer.md +6 -12
  41. package/presets/claude/next/agents/api-contract-reviewer.md +6 -12
  42. package/presets/claude/next/agents/build-verifier.md +8 -12
  43. package/presets/claude/next/agents/ci-investigator.md +7 -13
  44. package/presets/claude/next/agents/code-reviewer.md +9 -14
  45. package/presets/claude/next/agents/debugger.md +6 -12
  46. package/presets/claude/next/agents/feature-developer.md +8 -1
  47. package/presets/claude/next/agents/migration-specialist.md +6 -12
  48. package/presets/claude/next/agents/performance-auditor.md +5 -11
  49. package/presets/claude/next/agents/qa-tester.md +6 -12
  50. package/presets/claude/next/agents/security-reviewer.md +6 -12
  51. package/presets/claude/next/agents/solution-architect.md +6 -12
  52. package/presets/claude/next/agents/task-analyst.md +10 -16
  53. package/presets/claude/next/agents/task-router.md +31 -3
  54. package/presets/claude/next/agents/tech-writer.md +6 -13
  55. package/presets/claude/next/agents/unit-test-generator.md +6 -10
  56. package/presets/claude/next/agents/unit-test-healer.md +6 -10
  57. package/presets/claude/next/agents/unit-test-planner.md +5 -11
  58. package/presets/claude/next/commands/task.md +8 -6
  59. package/presets/claude/next/hooks/chain-team-phases.sh +197 -54
  60. package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +29 -4
  61. package/presets/claude/next/team/README.md +15 -3
  62. package/presets/claude/next/team/fixtures/artifact-manifest.example.json +61 -0
  63. package/presets/claude/next/team/fixtures/bugfix-standard.json +10 -0
  64. package/presets/claude/next/team/fixtures/feature-full.json +10 -0
  65. package/presets/claude/next/team/fixtures/feature-light.json +10 -0
  66. package/presets/claude/php-hexagonal/agents/build-verifier.md +9 -2
  67. package/presets/claude/php-hexagonal/agents/code-reviewer.md +8 -1
  68. package/presets/claude/php-hexagonal/agents/feature-developer.md +8 -1
  69. package/presets/claude/php-hexagonal/agents/task-analyst.md +8 -1
  70. package/presets/claude/php-hexagonal/agents/task-router.md +31 -3
  71. package/presets/claude/php-hexagonal/commands/task.md +8 -6
  72. package/presets/claude/php-hexagonal/hooks/README.md +1 -1
  73. package/presets/claude/php-hexagonal/hooks/chain-team-phases.sh +195 -53
  74. package/presets/claude/php-hexagonal/rules/tooling-and-review/agent-team-orchestrator.md +30 -5
  75. package/presets/claude/php-hexagonal/team/README.md +13 -2
  76. package/presets/claude/php-laravel/agents/build-verifier.md +9 -2
  77. package/presets/claude/php-laravel/agents/code-reviewer.md +8 -1
  78. package/presets/claude/php-laravel/agents/feature-developer.md +8 -1
  79. package/presets/claude/php-laravel/agents/task-analyst.md +8 -1
  80. package/presets/claude/php-laravel/agents/task-router.md +31 -3
  81. package/presets/claude/php-laravel/commands/task.md +8 -6
  82. package/presets/claude/php-laravel/hooks/chain-team-phases.sh +195 -53
  83. package/presets/claude/php-laravel/rules/tooling-and-review/agent-team-orchestrator.md +30 -5
  84. package/presets/claude/php-laravel/team/README.md +13 -2
  85. package/presets/cursor/go/agents/build-verifier.md +8 -1
  86. package/presets/cursor/go/agents/code-reviewer.md +8 -1
  87. package/presets/cursor/go/agents/feature-developer.md +8 -1
  88. package/presets/cursor/go/agents/task-analyst.md +8 -1
  89. package/presets/cursor/go/agents/task-router.md +31 -3
  90. package/presets/cursor/go/commands/task.md +8 -6
  91. package/presets/cursor/go/hooks/chain-team-phases.sh +195 -53
  92. package/presets/cursor/go/rules/agent-team-orchestrator.mdc +30 -5
  93. package/presets/cursor/go/team/README.md +13 -2
  94. package/presets/cursor/ios-swift/agents/build-verifier.md +8 -1
  95. package/presets/cursor/ios-swift/agents/code-reviewer.md +9 -14
  96. package/presets/cursor/ios-swift/agents/feature-developer.md +8 -1
  97. package/presets/cursor/ios-swift/agents/task-analyst.md +8 -1
  98. package/presets/cursor/ios-swift/agents/task-router.md +31 -3
  99. package/presets/cursor/ios-swift/commands/task.md +8 -6
  100. package/presets/cursor/ios-swift/hooks/chain-team-phases.sh +195 -54
  101. package/presets/cursor/ios-swift/rules/agent-team-orchestrator.mdc +23 -2
  102. package/presets/cursor/ios-swift/team/README.md +13 -2
  103. package/presets/cursor/java/agents/build-verifier.md +8 -1
  104. package/presets/cursor/java/agents/code-reviewer.md +8 -1
  105. package/presets/cursor/java/agents/feature-developer.md +8 -1
  106. package/presets/cursor/java/agents/task-analyst.md +8 -1
  107. package/presets/cursor/java/agents/task-router.md +31 -3
  108. package/presets/cursor/java/commands/task.md +8 -6
  109. package/presets/cursor/java/hooks/chain-team-phases.sh +195 -53
  110. package/presets/cursor/java/rules/agent-team-orchestrator.mdc +30 -5
  111. package/presets/cursor/java/team/README.md +13 -2
  112. package/presets/cursor/mcp-ts/agents/build-verifier.md +8 -1
  113. package/presets/cursor/mcp-ts/agents/feature-developer.md +7 -1
  114. package/presets/cursor/mcp-ts/agents/task-analyst.md +8 -1
  115. package/presets/cursor/mcp-ts/agents/task-router.md +31 -3
  116. package/presets/cursor/mcp-ts/commands/task.md +8 -6
  117. package/presets/cursor/mcp-ts/hooks/chain-team-phases.sh +181 -39
  118. package/presets/cursor/mcp-ts/rules/agent-team-orchestrator.mdc +30 -5
  119. package/presets/cursor/mcp-ts/team/README.md +13 -2
  120. package/presets/cursor/next/agents/accessibility-reviewer.md +7 -14
  121. package/presets/cursor/next/agents/api-contract-reviewer.md +7 -14
  122. package/presets/cursor/next/agents/build-verifier.md +8 -13
  123. package/presets/cursor/next/agents/ci-investigator.md +7 -14
  124. package/presets/cursor/next/agents/code-reviewer.md +9 -14
  125. package/presets/cursor/next/agents/debugger.md +6 -12
  126. package/presets/cursor/next/agents/feature-developer.md +8 -1
  127. package/presets/cursor/next/agents/migration-specialist.md +7 -14
  128. package/presets/cursor/next/agents/performance-auditor.md +6 -13
  129. package/presets/cursor/next/agents/qa-tester.md +6 -13
  130. package/presets/cursor/next/agents/security-reviewer.md +7 -14
  131. package/presets/cursor/next/agents/solution-architect.md +6 -12
  132. package/presets/cursor/next/agents/task-analyst.md +10 -16
  133. package/presets/cursor/next/agents/task-router.md +31 -3
  134. package/presets/cursor/next/agents/tech-writer.md +6 -13
  135. package/presets/cursor/next/agents/unit-test-generator.md +6 -11
  136. package/presets/cursor/next/agents/unit-test-healer.md +6 -11
  137. package/presets/cursor/next/agents/unit-test-planner.md +6 -13
  138. package/presets/cursor/next/commands/task.md +8 -6
  139. package/presets/cursor/next/hooks/chain-team-phases.sh +197 -54
  140. package/presets/cursor/next/rules/agent-team-orchestrator.mdc +30 -5
  141. package/presets/cursor/next/team/README.md +16 -4
  142. package/presets/cursor/next/team/fixtures/artifact-manifest.example.json +61 -0
  143. package/presets/cursor/next/team/fixtures/bugfix-standard.json +10 -0
  144. package/presets/cursor/next/team/fixtures/feature-full.json +10 -0
  145. package/presets/cursor/next/team/fixtures/feature-light.json +10 -0
  146. package/presets/cursor/php-hexagonal/agents/build-verifier.md +8 -1
  147. package/presets/cursor/php-hexagonal/agents/code-reviewer.md +8 -1
  148. package/presets/cursor/php-hexagonal/agents/feature-developer.md +8 -1
  149. package/presets/cursor/php-hexagonal/agents/task-analyst.md +8 -1
  150. package/presets/cursor/php-hexagonal/agents/task-router.md +31 -3
  151. package/presets/cursor/php-hexagonal/commands/task.md +8 -6
  152. package/presets/cursor/php-hexagonal/hooks/chain-team-phases.sh +195 -53
  153. package/presets/cursor/php-hexagonal/rules/agent-team-orchestrator.mdc +30 -5
  154. package/presets/cursor/php-hexagonal/team/README.md +13 -2
  155. package/presets/cursor/php-laravel/agents/build-verifier.md +8 -1
  156. package/presets/cursor/php-laravel/agents/code-reviewer.md +8 -1
  157. package/presets/cursor/php-laravel/agents/feature-developer.md +8 -1
  158. package/presets/cursor/php-laravel/agents/task-analyst.md +8 -1
  159. package/presets/cursor/php-laravel/agents/task-router.md +31 -3
  160. package/presets/cursor/php-laravel/commands/task.md +8 -6
  161. package/presets/cursor/php-laravel/hooks/chain-team-phases.sh +195 -53
  162. package/presets/cursor/php-laravel/rules/agent-team-orchestrator.mdc +30 -5
  163. package/presets/cursor/php-laravel/team/README.md +13 -2
@@ -28,10 +28,21 @@
28
28
 
29
29
  `playwright-test-*`, `xcuitest-test-*`, and `accessibility-reviewer` appear in fixture `skipped[]` **on purpose** — documented anti-patterns for backend presets (same as go/java). Leakage checker allows those stems only under `skipped[]`; scheduling them in `steps[]` is a **FAIL**.
30
30
 
31
+ ## Artifact manifest and receipts
32
+
33
+ Each slug stores one context index at `.claude/team/tasks/<slug>/artifact-manifest.json`. Schema v1 is defined by shared core `agent-team/agent-artifact-contracts.md`: every entry declares its producer, status, authoritative paths, acceptance-criteria IDs, decomposition task IDs, timestamps, and a terminal completion receipt.
34
+
35
+ Producer/consumer rules:
36
+
37
+ - Read the manifest first and use only the authoritative inputs needed by the current step; fall back to `brief.md`, `decomposition.md`, and `pipeline.json` when it is missing or stale, and report that gap.
38
+ - Producers upsert only their stable entry IDs, preserve foreign entries, and finish with `completed`, `blocked`, `changes_requested`, or `validation_failed` receipt evidence.
39
+ - The router owns `pipeline.json` and its routing entry; analyst, developer, verifier, and reviewer own their named outputs and receipts. Consumers do not edit another producer's receipt.
40
+ - Agents do not mutate `status.json` or `metrics.json`. The orchestration runtime/hook is their single writer. Parallel receipt merges are serialized by the parent/orchestrator.
41
+
31
42
  ## Local metrics ledger
32
43
 
33
- `metrics.json` is an append-only schema-v1 event ledger for one task run. It contains only allowlisted IDs, task dimensions, timestamps, outcomes, and attribution confidence—never prompts, secrets, file paths, or contents. The router writes `task_started` / `attempt_started`; the hook records host-clock completion and human-gate edges under a per-task lock with temp-and-rename.
44
+ `metrics.json` is an append-only schema-v1 event ledger for one task run. It contains only allowlisted IDs, task dimensions, timestamps, outcomes, and attribution confidence—never prompts, secrets, file paths, or contents. The orchestration runtime writes `task_started` / `attempt_started`; the hook records host-clock completion and human-gate edges under a per-task lock with temp-and-rename.
34
45
 
35
- `status.json.activeAttempts` is a transient timing bridge keyed by `attemptId`; `lastCompletedAttempt` is diagnostic. For parallel stops, the hook must use a stable payload identity or the sole active attempt. Otherwise an explicit `.metrics-receipts.json` receipt is fallback data marked `source: agent`, `attributionConfidence: fallback`; it must never guess from `currentAgent`.
46
+ `status.json.activeAttempts` is a transient timing bridge keyed by `attemptId`; `lastCompletedAttempt` is diagnostic; `parallelOutcomes` holds per-agent outcomes for the current parallel join until the step advances. For parallel stops, the hook must use a stable payload identity or the sole active attempt. Otherwise an explicit `.metrics-receipts.json` receipt is fallback data marked `source: agent`, `attributionConfidence: fallback`; it must never guess from `currentAgent`.
36
47
 
37
48
  Aggregate with `node packages/ai-rules/scripts/aggregate-agent-task-metrics.mjs --team-root .claude/team`. It reports role/agent count, completed count, total/average duration, distinct completed run IDs, separate human-gate wait, and incomplete artifacts.
@@ -12,8 +12,8 @@ You are the build verifier for **Laravel** services (and for **ai-rules preset p
12
12
  1. Detect Composer from `composer.json` / `composer.lock`; prefer `php artisan test` when `artisan` exists.
13
13
  2. Run from module/project root: `php artisan test` or `composer test` or `vendor/bin/phpunit` (or Pest if that is the repo default / CI).
14
14
  3. If present in **repo or CI**: formatter + static analysis (Pint, PHPStan, Psalm, PHP-CS-Fixer, or project-equivalent) per `php-tooling` / `post-change-test`. Mirror CI gates locally when configs/scripts exist; do **not** invent a tool the repo does not use.
15
- 4. Write `.cursor/team/tasks/<slug>/validation-report.md` with commands + results.
16
- 5. On failure: set status `validation_failed` — do **not** patch production code (developer retries).
15
+ 4. Write `.claude/team/tasks/<slug>/validation-report.md` with commands + results.
16
+ 5. On failure: emit receipt outcome `validation_failed` — do **not** patch production code (developer retries).
17
17
 
18
18
  **Never** require Playwright, XCUITest, `lint:js`/`lint:css`, `xcodebuild`/`SwiftLint`, `mvn`, or `gradlew` as the Laravel gate.
19
19
 
@@ -43,3 +43,10 @@ Also document:
43
43
  5. **Rule eval (WARN):** `yarn check:rule-eval` is **next-only** (`eval/next/`); optional for maintainers; not part of default `check:preset-structure`. Missing php-laravel eval stems is not a FAIL.
44
44
 
45
45
  Document PASS/FAIL in `validation-report.md`.
46
+
47
+ ## Artifact contract
48
+
49
+ - **Authoritative inputs:** read `.claude/team/tasks/<slug>/artifact-manifest.json` first; consume handoff `artifactInputIds` when supplied, otherwise consume the manifest entries for brief/decomposition, implementation evidence, pipeline scope, and changed files.
50
+ - **Owned outputs:** `validation-report.md`; upsert only the manifest entry assigned by handoff (`artifactOutputId`; fallback `validation`) and preserve all foreign entries.
51
+ - **Terminal receipt:** cover applicable AC/task IDs, list authoritative evidence paths, and emit `completed` or `validation_failed`. Bind it to the supplied `attemptId` when present.
52
+ - **Shared state:** never create or mutate `status.json` or `metrics.json`; the orchestration runtime/hook owns lifecycle, gates, retries, attempts, and timestamps.
@@ -33,8 +33,15 @@ Apply skill `code-review` and `tooling-and-review/code-review.md`. Weigh `archit
33
33
 
34
34
  ## Artifact
35
35
 
36
- Write `.claude/team/tasks/<slug>/review.md` with verdict APPROVE | REQUEST_CHANGES, findings by severity, AC coverage. Update `status.json` accordingly.
36
+ Write `.claude/team/tasks/<slug>/review.md` with verdict APPROVE | REQUEST_CHANGES, findings by severity, AC coverage. Update the owned terminal receipt accordingly.
37
37
 
38
38
  ## Design guidance
39
39
 
40
40
  - Load `design-guidance` (rule stem) when assessing structure, smells, or pattern fit.
41
+
42
+ ## Artifact contract
43
+
44
+ - **Authoritative inputs:** read `.claude/team/tasks/<slug>/artifact-manifest.json` first; consume handoff `artifactInputIds` when supplied, otherwise consume the manifest entries for brief/decomposition, implementation, validation evidence, and the scoped git diff.
45
+ - **Owned outputs:** `review.md`; upsert only the manifest entry assigned by handoff (`artifactOutputId`; fallback `review`) and preserve all foreign entries.
46
+ - **Terminal receipt:** cover applicable AC/task IDs, list authoritative evidence paths, and emit `completed` or `changes_requested`. Bind it to the supplied `attemptId` when present.
47
+ - **Shared state:** never create or mutate `status.json` or `metrics.json`; the orchestration runtime/hook owns lifecycle, gates, retries, attempts, and timestamps.
@@ -36,7 +36,7 @@ Implement Laravel features following Laravel-first layer boundaries.
36
36
 
37
37
  When editing ai-rules `presets/**/php-laravel/**`, keep Cursor/Claude twins in sync and preserve chain fork ≠ next/ios-swift/go/java/mcp-ts/php-hexagonal (see `preset-no-cross-stack-leakage`).
38
38
 
39
- Update `.cursor/team/tasks/<slug>/implementation.md` and `status.json`.
39
+ Update `.claude/team/tasks/<slug>/implementation.md` and the owned implementation receipt.
40
40
 
41
41
  ## Design guidance
42
42
 
@@ -45,3 +45,10 @@ Update `.cursor/team/tasks/<slug>/implementation.md` and `status.json`.
45
45
  ## Anti-sycophancy discipline
46
46
 
47
47
  - When writing or changing code, load / follow rule `anti-sycophancy-discipline`.
48
+
49
+ ## Artifact contract
50
+
51
+ - **Authoritative inputs:** read `.claude/team/tasks/<slug>/artifact-manifest.json` first; consume handoff `artifactInputIds` when supplied, otherwise consume the manifest entries for `brief.md`, `decomposition.md`, current scope, and applicable prior findings.
52
+ - **Owned outputs:** `implementation.md` plus changed implementation/test paths; upsert only the manifest entry assigned by handoff (`artifactOutputId`; fallback `implementation`) and preserve all foreign entries.
53
+ - **Terminal receipt:** cover applicable AC/task IDs, list authoritative evidence paths, and emit `completed` or `blocked`. Bind it to the supplied `attemptId` when present.
54
+ - **Shared state:** never create or mutate `status.json` or `metrics.json`; the orchestration runtime/hook owns lifecycle, gates, retries, attempts, and timestamps.
@@ -18,8 +18,15 @@ Use HTTP, Actions/Services, Eloquent, Policies, Providers, Jobs/Events — not P
18
18
 
19
19
  ## Gates
20
20
 
21
- Set `awaiting_approval` when humanGates require it after analysis.
21
+ Emit an analysis receipt; the runtime applies any configured human gate.
22
22
 
23
23
  ## Design guidance (optional)
24
24
 
25
25
  - Optional: Load `design-guidance` (rule stem) when assessing structure, smells, or pattern fit — only when decomposing refactor/architecture tasks (not default).
26
+
27
+ ## Artifact contract
28
+
29
+ - **Authoritative inputs:** read `.claude/team/tasks/<slug>/artifact-manifest.json` first; consume `pipeline.json`, the manifest routing entry, user-approved decisions, and any authoritative architecture/approval entries.
30
+ - **Owned outputs:** `brief.md` and `decomposition.md`; upsert only the manifest entry assigned by handoff (`artifactOutputId`; fallback `analysis`) and preserve all foreign entries.
31
+ - **Terminal receipt:** cover applicable AC/task IDs, list authoritative evidence paths, and emit `completed` or `blocked`. Bind it to the supplied `attemptId` when present.
32
+ - **Shared state:** never create or mutate `status.json` or `metrics.json`; the orchestration runtime/hook owns lifecycle, gates, retries, attempts, and timestamps.
@@ -58,6 +58,27 @@ Schedule when the task or diff touches **any** of:
58
58
 
59
59
  **Never skip `build-verifier`** after developer when PHP sources changed.
60
60
 
61
+ ## Routing decision rubric
62
+
63
+ Keep top-level `profile` and emit this top-level object in every new `pipeline.json`:
64
+
65
+ ```json
66
+ "routingDecision": {
67
+ "complexity": "low | moderate | high",
68
+ "riskFlags": [],
69
+ "routingReasons": ["Concrete request signal and its routing consequence."],
70
+ "estimatedWorkPackages": 1,
71
+ "openDecisionCount": 0
72
+ }
73
+ ```
74
+
75
+ - `low`: one bounded layer/file set, explicit AC, reversible, no risk flags/open decisions → `light` when verification policy permits.
76
+ - `moderate`: adjacent concerns or a regression/refactor with known boundaries → usually `standard`.
77
+ - `high`: cross-layer/public contract, Laravel-sensitive surface, migration/destructive or concurrency/state risk, broad Feature-test surface, unclear AC, or multiple work packages → usually `full`.
78
+ - Use concrete flags: `public-contract`, `cross-layer`, `security-sensitive`, `migration`, `destructive`, `concurrency-state`, `external-dependency`, `unclear-acceptance-criteria`, `broad-test-surface`, `behavior-regression`.
79
+ - `routingReasons` must be non-empty and explain profile, gates, specialists, skips, or checkpoints; never write “standard by default.”
80
+ - `estimatedWorkPackages` is an integer ≥1; `openDecisionCount` is an integer ≥0. Do not lower complexity because implementation is familiar.
81
+
61
82
  ## Model tiers (`steps[].model`)
62
83
 
63
84
  Optional: `"model": "cheap" | "standard" | "strong"`. Same policy as other presets:
@@ -81,12 +102,19 @@ Fixtures: `.claude/team/fixtures/` (feature-full, feature-light, bugfix-standard
81
102
 
82
103
  ## Output
83
104
 
84
- Write `.claude/team/tasks/<slug>/pipeline.json` and initial `status.json` via the **Write** tool (creates parents; do not Shell-`mkdir`); set `.claude/team/active-task.json`.
105
+ Write `.claude/team/tasks/<slug>/pipeline.json` and initialize the routing entry in `artifact-manifest.json`; set `.claude/team/active-task.json`. The orchestration runtime creates and owns `status.json`.
85
106
 
86
107
  ## Handoff
87
108
 
88
109
  Intent + steps table + skipped + gates → invoke `steps[0].agent` (unless retro).
89
110
 
90
- ## Metrics initialization
111
+ ## Runtime initialization
112
+
113
+ Return the routing receipt to the orchestrator. The orchestration runtime/hook exclusively initializes and updates `status.json`, `metrics.json`, attempt identities, and gate lifecycle; the router does not write those shared runtime files.
114
+
115
+ ## Artifact contract
91
116
 
92
- Alongside `pipeline.json` and `status.json`, create `metrics.json` with `schemaVersion: 1`, immutable task dimensions, `task_started`, and a generated `runId`. Before the parent invokes each agent, append `attempt_started` and set `status.activeAttempts[attemptId]` with agent, step index, attempt number, and host-clock `startedAt`. For parallel work create one entry per agent; do not use `currentAgent` to identify their completion.
117
+ - **Authoritative inputs:** read `.claude/team/tasks/<slug>/artifact-manifest.json` first; consume the user request and any existing authoritative task/decision entries.
118
+ - **Owned outputs:** `pipeline.json` and the routing manifest entry; upsert only the manifest entry assigned by handoff (`artifactOutputId`; fallback `routing`) and preserve all foreign entries.
119
+ - **Terminal receipt:** cover applicable AC/task IDs, list authoritative evidence paths, and emit `completed` or `blocked`. Bind it to the supplied `attemptId` when present.
120
+ - **Shared state:** never create or mutate `status.json` or `metrics.json`; the orchestration runtime/hook owns lifecycle, gates, retries, attempts, and timestamps.
@@ -24,14 +24,16 @@ description: Route a natural-language task through the agent-team pipeline: task
24
24
 
25
25
  ## Алгоритм
26
26
 
27
- 1. **Slug** — kebab-case из описания (≤48 символов). Создай `.claude/team/tasks/<slug>/` через запись файла (Write / Edit) — **не** полагайся на Shell `mkdir` как единственный способ создать каталог.
27
+ 1. **Slug** — kebab-case из описания (≤48 символов). Создай `.claude/team/tasks/<slug>/` через запись файла (Write / Edit); runtime/hook владеет `status.json` — **не** полагайся на Shell `mkdir` как единственный способ создать каталог.
28
28
  2. **Router** — вызови subagent **task-router** с описанием задачи и путём к slug.
29
29
  3. **План** — покажи пользователю таблицу из `pipeline.json`: intent, steps, skipped, humanGates.
30
30
  4. **Intent retro** — если `intent === "retro"`, выполни `/technical-retro` и **остановись**.
31
- 5. **Старт** — вызови subagent для `pipeline.steps[0].agent`:
32
- - передай slug, пути к артефактам, scope из step (если есть);
33
- - обнови `status.json`: `pipelineIndex: 0`, `currentAgent`, `state: in_progress`.
34
- 6. **Human gate** если после текущего шага есть gate в `humanGates` (`after:<agent>`), после завершения агента **остановись** и попроси `/task-continue <slug>` (или `/feature-continue <slug>`).
31
+ 5. **Старт** — после router:
32
+ - подтверди `pipeline.json` и `active-task.json`;
33
+ - **не** пиши lifecycle-поля `status.json` / `metrics.json` сам — их владеет orchestration runtime/hook (см. **agent-artifact-contracts**). Если `status.json` отсутствует, допустим только минимальный bootstrap (`slug`, `intent`, `pipelineIndex: 0`, `currentAgent: steps[0]`, `state: in_progress`) — **не** увеличивай `pipelineIndex` вручную;
34
+ - вызови subagent для `pipeline.steps[0].agent` (slug, пути к артефактам, scope).
35
+ 6. **После каждого агента** — дождись hook handoff / reconciliation terminal receipt → `status.state`; не запускай следующий шаг, пока `status.json` не отражает handoff (или пока нет `followup_message` от hook).
36
+ 7. **Human gate** — если после текущего шага есть gate в `humanGates` (`after:<agent>`), после завершения агента **остановись** и попроси `/task-continue <slug>` (или `/feature-continue <slug>`).
35
37
 
36
38
  ## Не делать
37
39
 
@@ -50,4 +52,4 @@ description: Route a natural-language task through the agent-team pipeline: task
50
52
 
51
53
  ## Metrics
52
54
 
53
- Initialize `metrics.json` before the first invocation with frozen task dimensions and `task_started`; add one `activeAttempts[attemptId]` entry plus `attempt_started` per scheduled agent. On `/task-continue`, append `human_gate_ended` (referencing the stored gate event) before activating the next attempt. Do not calculate durations in agents.
55
+ Runtime/hook owns `metrics.json` and `status.json` attempt ledgers. Before the first invocation ensure `task_started` and one `activeAttempts[attemptId]` + `attempt_started` exist (router/runtime bootstrap — parent must not invent parallel indices). On `/task-continue`, run the installed chain hook `end_human_gate` action (or wrapper) so `human_gate_ended` lands before the next attempt. Do not calculate durations in agents.
@@ -124,6 +124,27 @@ function roleFor(agent) {
124
124
  if (/writer/.test(agent || '')) return 'docs';
125
125
  return 'implementation';
126
126
  }
127
+ const DEFAULT_ARTIFACT_OUTPUT_IDS = {
128
+ 'task-router': 'routing',
129
+ 'task-analyst': 'analysis',
130
+ 'solution-architect': 'architecture',
131
+ 'migration-specialist': 'migration-plan',
132
+ debugger: 'debug',
133
+ 'ci-investigator': 'ci',
134
+ 'feature-developer': 'implementation',
135
+ 'build-verifier': 'validation',
136
+ 'code-reviewer': 'review',
137
+ 'security-reviewer': 'security-review',
138
+ 'accessibility-reviewer': 'a11y-review',
139
+ 'performance-auditor': 'perf-report',
140
+ 'api-contract-reviewer': 'api-contract-review',
141
+ 'tech-writer': 'documentation',
142
+ };
143
+ function artifactOutputIdFor(step, agent) {
144
+ if (step && typeof step.artifactOutputId === 'string') return step.artifactOutputId;
145
+ if (step?.artifactOutputIds && typeof step.artifactOutputIds[agent] === 'string') return step.artifactOutputIds[agent];
146
+ return DEFAULT_ARTIFACT_OUTPUT_IDS[agent] || agent;
147
+ }
127
148
  function activateAttempts(step, stepIndex) {
128
149
  const active = { ...(status.activeAttempts || {}) }; const agents = getStepAgents(step);
129
150
  const parallelGroupId = step.parallel && agents.length > 1 ? `${status.metricsRunId || slug}:step-${stepIndex}` : null;
@@ -146,11 +167,61 @@ function resolvedAttempt() {
146
167
  try { const receipts = JSON.parse(fs.readFileSync(path.join(taskDir, '.metrics-receipts.json'), 'utf8')); const receipt = Array.isArray(receipts) ? receipts.find((item) => active[item.attemptId]) : null; if (receipt) return { attemptId: receipt.attemptId, entry: active[receipt.attemptId], confidence: 'fallback', source: 'agent' }; } catch {}
147
168
  return null;
148
169
  }
170
+ function readBoundReceiptOutcome(agent, step, resolved) {
171
+ if (!agent || !resolved) return { outcome: null, receiptRequired: false };
172
+ const manifestPath = path.join(taskDir, 'artifact-manifest.json');
173
+ if (!fs.existsSync(manifestPath)) return { outcome: null, receiptRequired: false };
174
+ try {
175
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
176
+ const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
177
+ const expectedOutputId = artifactOutputIdFor(step, agent);
178
+ const attemptStartedAt = Date.parse(resolved.entry.startedAt);
179
+ const owned = artifacts
180
+ .filter((entry) => {
181
+ if (!entry || entry.producer !== agent || !entry.receipt || typeof entry.receipt.outcome !== 'string') return false;
182
+ const boundByAttempt = typeof entry.receipt.attemptId === 'string' && entry.receipt.attemptId === resolved.attemptId;
183
+ const boundByOutput = entry.receipt.attemptId == null && entry.id === expectedOutputId;
184
+ if (!boundByAttempt && !boundByOutput) return false;
185
+ const terminalAt = Date.parse(entry.timestamps?.completedAt || entry.timestamps?.updatedAt || '');
186
+ return Number.isFinite(terminalAt) && (!Number.isFinite(attemptStartedAt) || terminalAt >= attemptStartedAt);
187
+ })
188
+ .sort((a, b) => {
189
+ const ta = Date.parse(a.timestamps?.completedAt || a.timestamps?.updatedAt || 0) || 0;
190
+ const tb = Date.parse(b.timestamps?.completedAt || b.timestamps?.updatedAt || 0) || 0;
191
+ return tb - ta;
192
+ });
193
+ return { outcome: owned[0]?.receipt?.outcome || null, receiptRequired: true };
194
+ } catch {
195
+ return { outcome: null, receiptRequired: false };
196
+ }
197
+ }
198
+ function reconcileStatusFromManifest(agent, step, resolved) {
199
+ const terminal = new Set(['completed', 'blocked', 'changes_requested', 'validation_failed']);
200
+ const { outcome, receiptRequired } = readBoundReceiptOutcome(agent, step, resolved);
201
+ if (terminal.has(outcome)) {
202
+ status.state = outcome;
203
+ return outcome;
204
+ }
205
+ if (receiptRequired) {
206
+ status.state = 'awaiting_artifact_receipt';
207
+ status.phase = 'blocked';
208
+ return status.state;
209
+ }
210
+ if (terminal.has(status.state)) return status.state;
211
+ // Host reported subagent completed and no blocking receipt → treat as completed for metrics/advance.
212
+ if (hookInput.status === 'completed') {
213
+ status.state = 'completed';
214
+ return 'completed';
215
+ }
216
+ return status.state;
217
+ }
218
+
149
219
  function finishActiveAttempt() {
150
220
  const resolved = resolvedAttempt(); if (!resolved) return null;
151
221
  const durationMs = Date.parse(now) - Date.parse(resolved.entry.startedAt);
152
- appendMetric({ type: 'attempt_finished', at: now, attemptId: resolved.attemptId, outcome: ['completed', 'changes_requested', 'validation_failed'].includes(status.state) ? status.state : 'unknown', durationMs: Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null, retryAfterFix: status.retryAfterFix || null, source: resolved.source || 'hook', attributionConfidence: resolved.confidence });
153
- const active = { ...(status.activeAttempts || {}) }; delete active[resolved.attemptId]; status.activeAttempts = active; status.lastCompletedAttempt = { attemptId: resolved.attemptId, completedAt: now }; return resolved;
222
+ const outcome = ['completed', 'blocked', 'changes_requested', 'validation_failed'].includes(status.state) ? status.state : 'unknown';
223
+ appendMetric({ type: 'attempt_finished', at: now, attemptId: resolved.attemptId, outcome, durationMs: Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null, retryAfterFix: status.retryAfterFix || null, source: resolved.source || 'hook', attributionConfidence: resolved.confidence });
224
+ const active = { ...(status.activeAttempts || {}) }; delete active[resolved.attemptId]; status.activeAttempts = active; status.lastCompletedAttempt = { attemptId: resolved.attemptId, completedAt: now }; return { ...resolved, outcome };
154
225
  }
155
226
  function beginHumanGate() { const eventId = `${status.metricsRunId || slug}:gate:${Date.now()}`; appendMetric({ eventId, type: 'human_gate_started', at: now, source: 'hook', attributionConfidence: 'high' }); status.activeHumanGateEventId = eventId; }
156
227
  function endHumanGateIfResumed() {
@@ -219,54 +290,54 @@ function findNextExecutableStep(steps, startIdx, dir) {
219
290
  const AGENT_HANDOFF = {
220
291
  'task-analyst': (s) =>
221
292
  `Invoke task-analyst for slug "${s}". Write brief.md and decomposition.md under .claude/team/tasks/${s}/. ` +
222
- `Set status.json: phase executing, state awaiting_approval if human gate applies. Do not use legacy phase analysis.`,
293
+ `Upsert the analysis receipt in artifact-manifest.json; do not mutate status.json lifecycle fields. Do not use legacy phase analysis.`,
223
294
  debugger: (s) =>
224
295
  `Invoke debugger subagent for slug "${s}". Investigate root cause with php artisan test / composer test / logs; write debug-report.md with fixApplied: true/false. ` +
225
- `Update status.json when done.`,
296
+ `Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
226
297
  'solution-architect': (s) =>
227
- `Invoke solution-architect for slug "${s}". Read brief.md; write architecture.md for Laravel layers. Update status.json when done.`,
298
+ `Invoke solution-architect for slug "${s}". Read brief.md; write architecture.md for Laravel layers. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
228
299
  'migration-specialist': (s) =>
229
- `Invoke migration-specialist for slug "${s}". Read brief.md; write migration-plan.md for PHP build/toolchain. Update status.json when done.`,
300
+ `Invoke migration-specialist for slug "${s}". Read brief.md; write migration-plan.md for PHP build/toolchain. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
230
301
  'api-contract-reviewer': (s) =>
231
- `Invoke api-contract-reviewer (readonly) for slug "${s}". Review OpenAPI (if present) vs routes/Resources/Form Requests; write api-contract-review.md. Update status.json when done.`,
302
+ `Invoke api-contract-reviewer (readonly) for slug "${s}". Review OpenAPI (if present) vs routes/Resources/Form Requests; write api-contract-review.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
232
303
  'ci-investigator': (s) =>
233
304
  `Invoke ci-investigator for slug "${s}". Diagnose CI failure (php artisan test / composer test / phpstan or preset-structure); write ci-report.md; apply minimal fix. ` +
234
- `Use ci-investigation skill. Update status.json when done.`,
305
+ `Use ci-investigation skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
235
306
  'feature-developer': (s) =>
236
307
  `Invoke feature-developer for slug "${s}". Read brief.md, decomposition.md, validation-report.md, review.md, architecture.md, migration-plan.md, api-contract-review.md, debug-report.md if present. ` +
237
308
  `Follow feature-delivery-workflow.mdc and reference-features.mdc. Prefer Laravel paths: routes → Form Request → Action/Service → Eloquent → Feature test (app/, routes/, tests/). ` +
238
- `Include PHPUnit/Pest via php artisan test / composer test (Unit + Feature) before handoff. Update status.json when done.`,
309
+ `Include PHPUnit/Pest via php artisan test / composer test (Unit + Feature) before handoff. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
239
310
  'build-verifier': (s) =>
240
311
  `Invoke build-verifier (readonly) for slug "${s}". Follow php-tooling.mdc and post-change-test.mdc. ` +
241
312
  `Run php artisan test / composer test and configured static analysis if present in repo/CI — never yarn/ESLint/Stylelint. ` +
242
- `For preset packaging: yarn check:preset-structure. Write validation-report.md. Set state completed or validation_failed. Do not edit production code.`,
313
+ `For preset packaging: yarn check:preset-structure. Write validation-report.md and emit receipt outcome completed or validation_failed. Do not edit production code.`,
243
314
  'performance-auditor': (s) =>
244
- `Invoke performance-auditor (readonly) for slug "${s}". Audit Laravel hot paths, Eloquent N+1, queue backlog; Blackfire/Xdebug/Telescope/query log if present; write perf-report.md. Update status.json when done.`,
315
+ `Invoke performance-auditor (readonly) for slug "${s}". Audit Laravel hot paths, Eloquent N+1, queue backlog; Blackfire/Xdebug/Telescope/query log if present; write perf-report.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
245
316
  'code-reviewer': (s) =>
246
317
  `Invoke code-reviewer subagent (readonly) for slug "${s}". Review git diff vs brief.md per code-review-mr.mdc (Laravel boundaries). ` +
247
- `Update status.json when done.`,
318
+ `Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
248
319
  'security-reviewer': (s) =>
249
- `Invoke security-reviewer (readonly) for slug "${s}". Follow security-php.mdc; write security-review.md. Update status.json when done.`,
320
+ `Invoke security-reviewer (readonly) for slug "${s}". Follow security-php.mdc; write security-review.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
250
321
  'qa-tester': (s, scope) =>
251
322
  `Invoke qa-tester for slug "${s}"${scope ? ` (scope: ${scope})` : ''}. ` +
252
- `Add/update PHPUnit unit and integration tests per brief. Follow tests-unit.mdc, tests-integration.mdc, and skill integration-testing. Update status.json when done.`,
323
+ `Add/update PHPUnit unit and integration tests per brief. Follow tests-unit.mdc, tests-integration.mdc, and skill integration-testing. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
253
324
  'unit-test-planner': (s) =>
254
- `Invoke unit-test-planner for slug "${s}". Write unit-test-plan.md for PHPUnit/Pest via composer test. Use unit-testing skill. Update status.json when done.`,
325
+ `Invoke unit-test-planner for slug "${s}". Write unit-test-plan.md for PHPUnit/Pest via composer test. Use unit-testing skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
255
326
  'unit-test-generator': (s) =>
256
- `Invoke unit-test-generator for slug "${s}". Implement *Test.php / *Tests.php from unit-test-plan.md under tests. Use unit-testing skill. Update status.json when done.`,
327
+ `Invoke unit-test-generator for slug "${s}". Implement *Test.php / *Tests.php from unit-test-plan.md under tests. Use unit-testing skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
257
328
  'unit-test-healer': (s) =>
258
- `Invoke unit-test-healer for slug "${s}". Fix failing unit tests. Use unit-testing and debug-investigation skills. Update status.json when done.`,
329
+ `Invoke unit-test-healer for slug "${s}". Fix failing unit tests. Use unit-testing and debug-investigation skills. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
259
330
  'integration-test-planner': (s) =>
260
331
  `Invoke integration-test-planner for slug "${s}". Create/update Feature/integration test plans (HTTP Feature tests / DB fixtures / external HTTP fakes / Bus::fake Event::fake). ` +
261
- `Follow tests-integration.mdc and the integration-testing skill. Do not use browser e2e tooling. Update status.json when done.`,
332
+ `Follow tests-integration.mdc and the integration-testing skill. Do not use browser e2e tooling. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
262
333
  'integration-test-generator': (s) =>
263
334
  `Invoke integration-test-generator for slug "${s}". Generate/update Feature tests (*Test.php under tests/Feature) from plans. ` +
264
- `Follow tests-integration.mdc and the integration-testing skill. Do not use browser e2e tooling. Update status.json when done.`,
335
+ `Follow tests-integration.mdc and the integration-testing skill. Do not use browser e2e tooling. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
265
336
  'integration-test-healer': (s) =>
266
337
  `Invoke integration-test-healer for slug "${s}". Fix failing/flaky Feature/integration tests while preserving plan assertions. ` +
267
- `Follow tests-integration.mdc, integration-testing, and debug-investigation. Do not use browser e2e tooling. Update status.json when done.`,
338
+ `Follow tests-integration.mdc, integration-testing, and debug-investigation. Do not use browser e2e tooling. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
268
339
  'tech-writer': (s) =>
269
- `Invoke tech-writer for slug "${s}". Write documentation.md from task artifacts. Update status.json when done.`,
340
+ `Invoke tech-writer for slug "${s}". Write documentation.md from task artifacts. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
270
341
  };
271
342
 
272
343
  const REVIEW_AGENTS = new Set([
@@ -279,15 +350,20 @@ const REVIEW_AGENTS = new Set([
279
350
  function metricsHandoff(agent) {
280
351
  const matches = Object.entries(status.activeAttempts || {}).filter(([, entry]) => entry.agent === agent);
281
352
  if (matches.length === 1) {
282
- return ` Metrics attemptId: ${matches[0][0]}; update normal status only. If the hook cannot identify a parallel stop, append a minimal { attemptId, outcome } receipt to .metrics-receipts.json; do not calculate duration.`;
353
+ return ` Metrics attemptId: ${matches[0][0]}; write owned artifact-manifest receipt outcome. If the hook cannot identify a parallel stop, append a minimal { attemptId, outcome } receipt to .metrics-receipts.json; do not mutate status.json or calculate duration.`;
283
354
  }
284
355
  return ' Metrics: use the supplied attemptId when available; for a parallel completion, write a minimal { attemptId, outcome } receipt to .metrics-receipts.json rather than relying on currentAgent. Do not calculate duration.';
285
356
  }
286
357
 
287
- function handoffForAgent(agent, scope) {
358
+ function artifactHandoff(step, agent) {
359
+ const inputIds = Array.isArray(step?.artifactInputIds) ? step.artifactInputIds.join(', ') : 'manifest defaults';
360
+ return ` Artifact inputs: ${inputIds}; owned output ID: ${artifactOutputIdFor(step, agent)}. Bind the terminal receipt to the supplied attemptId.`;
361
+ }
362
+
363
+ function handoffForAgent(agent, scope, step = null) {
288
364
  const fn = AGENT_HANDOFF[agent];
289
- const handoff = fn ? fn(slug, scope) : `Invoke ${agent} subagent for slug "${slug}". Update status.json when done.`;
290
- return handoff + metricsHandoff(agent);
365
+ const handoff = fn ? fn(slug, scope) : `Invoke ${agent} subagent for slug "${slug}". Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`;
366
+ return handoff + artifactHandoff(step, agent) + metricsHandoff(agent);
291
367
  }
292
368
 
293
369
  function handoffForParallelStep(step) {
@@ -297,7 +373,8 @@ function handoffForParallelStep(step) {
297
373
  .map(([attemptId, entry]) => `${entry.agent}=${attemptId}`);
298
374
  return (
299
375
  `Invoke IN PARALLEL (one message, multiple Task calls): ${agents.join(' AND ')} for slug "${slug}". ` +
300
- `Each agent writes its artifact and appends itself to status.json parallelCompleted when done. ` +
376
+ `Each agent writes its artifact and owned receipt; the hook records parallelCompleted from stops. Do not mutate status.json lifecycle fields. ` +
377
+ `Artifact inputs: ${Array.isArray(step.artifactInputIds) ? step.artifactInputIds.join(', ') : 'manifest defaults'}; owned output IDs: ${agents.map((agent) => `${agent}=${artifactOutputIdFor(step, agent)}`).join('; ')}. ` +
301
378
  `Metrics attemptIds: ${attemptIds.join('; ')}. If the hook cannot identify a parallel stop, append a minimal { attemptId, outcome } receipt to .metrics-receipts.json; do not calculate duration.`
302
379
  );
303
380
  }
@@ -336,8 +413,12 @@ function legacyFlow() {
336
413
  appendMetric({ type: 'retry_scheduled', at: now, attemptId: predecessorAttemptId, nextAgent: agent, retryAfterFix: retryAfterFix || null, source: 'hook', attributionConfidence: 'unknown' });
337
414
  activateAttempts({ agent }, stepIndex);
338
415
  }
339
- if (status.state === 'changes_requested' && REVIEW_AGENTS.has(status.currentAgent)) {
416
+ if (status.state === 'changes_requested') {
340
417
  const reviewer = status.currentAgent;
418
+ if (!reviewer) {
419
+ process.stdout.write('{}');
420
+ process.exit(0);
421
+ }
341
422
  scheduleLegacyRetry('feature-developer', reviewer);
342
423
  writeStatus({
343
424
  currentAgent: 'feature-developer',
@@ -411,7 +492,7 @@ if (hookInput.action === 'end_human_gate') {
411
492
  parallelCompleted: [],
412
493
  });
413
494
  if (resumeNext.step.parallel && resumeAgents.length > 1) out(handoffForParallelStep(resumeNext.step));
414
- else out(handoffForAgent(resumeAgents[0], resumeNext.step.scope));
495
+ else out(handoffForAgent(resumeAgents[0], resumeNext.step.scope, resumeNext.step));
415
496
  process.exit(0);
416
497
  }
417
498
  }
@@ -440,6 +521,19 @@ const steps = pipeline.steps || [];
440
521
  const initialIdx = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
441
522
  const initialStep = steps[initialIdx];
442
523
  const activeAttemptCountBeforeFinish = Object.keys(status.activeAttempts || {}).length;
524
+ const completingAgentHint = (() => {
525
+ const resolved = resolvedAttempt();
526
+ if (resolved?.entry?.agent) return resolved.entry.agent;
527
+ if (status.currentAgent) return status.currentAgent;
528
+ return getStepAgents(initialStep)[0];
529
+ })();
530
+ const reconcilingAttempt = resolvedAttempt();
531
+ reconcileStatusFromManifest(completingAgentHint, initialStep, reconcilingAttempt);
532
+ if (status.state === 'awaiting_artifact_receipt') {
533
+ writeStatus({});
534
+ process.stdout.write('{}');
535
+ process.exit(0);
536
+ }
443
537
  const finishedAttempt = finishActiveAttempt();
444
538
  if (!finishedAttempt && (hookInput.attemptId || activeAttemptCountBeforeFinish > 0)) {
445
539
  process.stdout.write('{}');
@@ -447,6 +541,7 @@ if (!finishedAttempt && (hookInput.attemptId || activeAttemptCountBeforeFinish >
447
541
  }
448
542
  endHumanGateIfResumed();
449
543
  if (steps.length === 0 || pipeline.autoChain === false) {
544
+ writeStatus({});
450
545
  process.stdout.write('{}');
451
546
  process.exit(0);
452
547
  }
@@ -472,8 +567,74 @@ function scheduleRetry(agent, retryAfterFix, predecessor, explicitStepIndex = nu
472
567
  return stepIndex;
473
568
  }
474
569
 
475
- if (status.state === 'changes_requested' && REVIEW_AGENTS.has(status.currentAgent)) {
476
- const reviewer = status.currentAgent;
570
+ const idx = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
571
+ const currentStep = steps[idx];
572
+ const completedAgent = finishedAttempt?.entry?.agent || status.currentAgent || getStepAgents(currentStep)[0];
573
+ const stepAgents = getStepAgents(currentStep);
574
+ if (currentStep?.parallel && stepAgents.length > 1) {
575
+ const done = [...(status.parallelCompleted || [])];
576
+ const outcomes = { ...(status.parallelOutcomes || {}) };
577
+ if (finishedAttempt && completedAgent) {
578
+ if (!done.includes(completedAgent)) done.push(completedAgent);
579
+ outcomes[completedAgent] = {
580
+ outcome: finishedAttempt.outcome || status.state,
581
+ attemptId: finishedAttempt.attemptId,
582
+ confidence: finishedAttempt.confidence,
583
+ };
584
+ }
585
+ writeStatus({ parallelCompleted: done, parallelOutcomes: outcomes });
586
+ const remaining = stepAgents.filter((agent) => !done.includes(agent));
587
+ if (remaining.length > 0) {
588
+ process.stdout.write('{}');
589
+ process.exit(0);
590
+ }
591
+
592
+ const blockedProducer = stepAgents.find((agent) => outcomes[agent]?.outcome === 'blocked');
593
+ if (blockedProducer) {
594
+ writeStatus({ parallelCompleted: [], parallelOutcomes: {}, state: 'blocked', phase: 'blocked', currentAgent: blockedProducer });
595
+ process.stdout.write('{}');
596
+ process.exit(0);
597
+ }
598
+
599
+ const retryReviewer = stepAgents.find((agent) => outcomes[agent]?.outcome === 'changes_requested');
600
+ if (retryReviewer) {
601
+ const retryAfterFixStepIndex = idx;
602
+ const predecessor = outcomes[retryReviewer]
603
+ ? {
604
+ attemptId: outcomes[retryReviewer].attemptId,
605
+ confidence: outcomes[retryReviewer].confidence,
606
+ }
607
+ : finishedAttempt;
608
+ const developerIndex = scheduleRetry('feature-developer', retryReviewer, predecessor);
609
+ writeStatus({
610
+ pipelineIndex: developerIndex,
611
+ currentAgent: 'feature-developer',
612
+ state: 'in_progress',
613
+ phase: 'executing',
614
+ retryAfterFix: retryReviewer,
615
+ retryAfterFixStepIndex,
616
+ parallelCompleted: [],
617
+ parallelOutcomes: {},
618
+ });
619
+ out(handoffForAgent('feature-developer', steps[developerIndex]?.scope, steps[developerIndex]) + ` After fixes, re-run ${retryReviewer}.`);
620
+ process.exit(0);
621
+ }
622
+
623
+ writeStatus({ parallelCompleted: [], parallelOutcomes: {}, state: 'completed' });
624
+ }
625
+
626
+ if (status.state === 'blocked') {
627
+ writeStatus({ state: 'blocked', phase: 'blocked', currentAgent: completedAgent });
628
+ process.stdout.write('{}');
629
+ process.exit(0);
630
+ }
631
+
632
+ if (status.state === 'changes_requested') {
633
+ const reviewer = status.currentAgent || finishedAttempt?.entry?.agent;
634
+ if (!reviewer) {
635
+ process.stdout.write('{}');
636
+ process.exit(0);
637
+ }
477
638
  const retryAfterFixStepIndex = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
478
639
  const developerIndex = scheduleRetry('feature-developer', reviewer, finishedAttempt);
479
640
  writeStatus({
@@ -484,7 +645,7 @@ if (status.state === 'changes_requested' && REVIEW_AGENTS.has(status.currentAgen
484
645
  retryAfterFix: reviewer,
485
646
  retryAfterFixStepIndex,
486
647
  });
487
- out(handoffForAgent('feature-developer') + ` After fixes, re-run ${reviewer}.`);
648
+ out(handoffForAgent('feature-developer', steps[developerIndex]?.scope, steps[developerIndex]) + ` After fixes, re-run ${reviewer}.`);
488
649
  process.exit(0);
489
650
  }
490
651
 
@@ -499,7 +660,7 @@ if (status.state === 'validation_failed' && status.currentAgent === 'build-verif
499
660
  retryAfterFix: 'build-verifier',
500
661
  retryAfterFixStepIndex,
501
662
  });
502
- out(handoffForAgent('feature-developer') + ' Read validation-report.md. After fixes, re-run build-verifier.');
663
+ out(handoffForAgent('feature-developer', steps[developerIndex]?.scope, steps[developerIndex]) + ' Read validation-report.md. After fixes, re-run build-verifier.');
503
664
  process.exit(0);
504
665
  }
505
666
 
@@ -507,35 +668,16 @@ if (status.state === 'completed' && status.retryAfterFix) {
507
668
  const retry = status.retryAfterFix;
508
669
  const retryIndex = scheduleRetry(retry, null, finishedAttempt, status.retryAfterFixStepIndex);
509
670
  writeStatus({ pipelineIndex: retryIndex, currentAgent: retry, state: 'in_progress', retryAfterFix: null, retryAfterFixStepIndex: null });
510
- out(handoffForAgent(retry));
671
+ out(handoffForAgent(retry, steps[retryIndex]?.scope, steps[retryIndex]));
511
672
  process.exit(0);
512
673
  }
513
674
 
514
- const idx = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
515
- const currentStep = steps[idx];
516
675
  if (!currentStep) {
517
676
  writeStatus({ phase: 'done', state: 'completed', currentAgent: null });
518
677
  out(`Pipeline complete for "${slug}". Run /technical-retro ${slug}.`);
519
678
  process.exit(0);
520
679
  }
521
680
 
522
- const completedAgent = finishedAttempt?.entry?.agent || status.currentAgent || getStepAgents(currentStep)[0];
523
- const stepAgents = getStepAgents(currentStep);
524
-
525
- if (currentStep.parallel && stepAgents.length > 1) {
526
- const done = [...(status.parallelCompleted || [])];
527
- if (completedAgent && !done.includes(completedAgent)) {
528
- done.push(completedAgent);
529
- }
530
- writeStatus({ parallelCompleted: done });
531
- const remaining = stepAgents.filter((a) => !done.includes(a));
532
- if (remaining.length > 0) {
533
- process.stdout.write('{}');
534
- process.exit(0);
535
- }
536
- writeStatus({ parallelCompleted: [] });
537
- }
538
-
539
681
  if (hasGateAfterStep(currentStep, pipeline.humanGates)) {
540
682
  beginHumanGate();
541
683
  writeStatus({
@@ -577,6 +719,6 @@ writeStatus({
577
719
  if (nextStep.parallel && nextAgents.length > 1) {
578
720
  out(handoffForParallelStep(nextStep));
579
721
  } else {
580
- out(handoffForAgent(nextAgents[0], nextStep.scope));
722
+ out(handoffForAgent(nextAgents[0], nextStep.scope, nextStep));
581
723
  }
582
724
  NODE