@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
@@ -122,6 +122,27 @@ function roleFor(agent) {
122
122
  if (/writer/.test(agent || '')) return 'docs';
123
123
  return 'implementation';
124
124
  }
125
+ const DEFAULT_ARTIFACT_OUTPUT_IDS = {
126
+ 'task-router': 'routing',
127
+ 'task-analyst': 'analysis',
128
+ 'solution-architect': 'architecture',
129
+ 'migration-specialist': 'migration-plan',
130
+ debugger: 'debug',
131
+ 'ci-investigator': 'ci',
132
+ 'feature-developer': 'implementation',
133
+ 'build-verifier': 'validation',
134
+ 'code-reviewer': 'review',
135
+ 'security-reviewer': 'security-review',
136
+ 'accessibility-reviewer': 'a11y-review',
137
+ 'performance-auditor': 'perf-report',
138
+ 'api-contract-reviewer': 'api-contract-review',
139
+ 'tech-writer': 'documentation',
140
+ };
141
+ function artifactOutputIdFor(step, agent) {
142
+ if (step && typeof step.artifactOutputId === 'string') return step.artifactOutputId;
143
+ if (step?.artifactOutputIds && typeof step.artifactOutputIds[agent] === 'string') return step.artifactOutputIds[agent];
144
+ return DEFAULT_ARTIFACT_OUTPUT_IDS[agent] || agent;
145
+ }
125
146
  function activateAttempts(step, stepIndex) {
126
147
  const active = { ...(status.activeAttempts || {}) }; const agents = getStepAgents(step);
127
148
  const parallelGroupId = step.parallel && agents.length > 1 ? `${status.metricsRunId || slug}:step-${stepIndex}` : null;
@@ -144,11 +165,61 @@ function resolvedAttempt() {
144
165
  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 {}
145
166
  return null;
146
167
  }
168
+ function readBoundReceiptOutcome(agent, step, resolved) {
169
+ if (!agent || !resolved) return { outcome: null, receiptRequired: false };
170
+ const manifestPath = path.join(taskDir, 'artifact-manifest.json');
171
+ if (!fs.existsSync(manifestPath)) return { outcome: null, receiptRequired: false };
172
+ try {
173
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
174
+ const artifacts = Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
175
+ const expectedOutputId = artifactOutputIdFor(step, agent);
176
+ const attemptStartedAt = Date.parse(resolved.entry.startedAt);
177
+ const owned = artifacts
178
+ .filter((entry) => {
179
+ if (!entry || entry.producer !== agent || !entry.receipt || typeof entry.receipt.outcome !== 'string') return false;
180
+ const boundByAttempt = typeof entry.receipt.attemptId === 'string' && entry.receipt.attemptId === resolved.attemptId;
181
+ const boundByOutput = entry.receipt.attemptId == null && entry.id === expectedOutputId;
182
+ if (!boundByAttempt && !boundByOutput) return false;
183
+ const terminalAt = Date.parse(entry.timestamps?.completedAt || entry.timestamps?.updatedAt || '');
184
+ return Number.isFinite(terminalAt) && (!Number.isFinite(attemptStartedAt) || terminalAt >= attemptStartedAt);
185
+ })
186
+ .sort((a, b) => {
187
+ const ta = Date.parse(a.timestamps?.completedAt || a.timestamps?.updatedAt || 0) || 0;
188
+ const tb = Date.parse(b.timestamps?.completedAt || b.timestamps?.updatedAt || 0) || 0;
189
+ return tb - ta;
190
+ });
191
+ return { outcome: owned[0]?.receipt?.outcome || null, receiptRequired: true };
192
+ } catch {
193
+ return { outcome: null, receiptRequired: false };
194
+ }
195
+ }
196
+ function reconcileStatusFromManifest(agent, step, resolved) {
197
+ const terminal = new Set(['completed', 'blocked', 'changes_requested', 'validation_failed']);
198
+ const { outcome, receiptRequired } = readBoundReceiptOutcome(agent, step, resolved);
199
+ if (terminal.has(outcome)) {
200
+ status.state = outcome;
201
+ return outcome;
202
+ }
203
+ if (receiptRequired) {
204
+ status.state = 'awaiting_artifact_receipt';
205
+ status.phase = 'blocked';
206
+ return status.state;
207
+ }
208
+ if (terminal.has(status.state)) return status.state;
209
+ // Host reported subagent completed and no blocking receipt → treat as completed for metrics/advance.
210
+ if (hookInput.status === 'completed') {
211
+ status.state = 'completed';
212
+ return 'completed';
213
+ }
214
+ return status.state;
215
+ }
216
+
147
217
  function finishActiveAttempt() {
148
218
  const resolved = resolvedAttempt(); if (!resolved) return null;
149
219
  const durationMs = Date.parse(now) - Date.parse(resolved.entry.startedAt);
150
- 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 });
151
- const active = { ...(status.activeAttempts || {}) }; delete active[resolved.attemptId]; status.activeAttempts = active; status.lastCompletedAttempt = { attemptId: resolved.attemptId, completedAt: now }; return resolved;
220
+ const outcome = ['completed', 'blocked', 'changes_requested', 'validation_failed'].includes(status.state) ? status.state : 'unknown';
221
+ 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 });
222
+ const active = { ...(status.activeAttempts || {}) }; delete active[resolved.attemptId]; status.activeAttempts = active; status.lastCompletedAttempt = { attemptId: resolved.attemptId, completedAt: now }; return { ...resolved, outcome };
152
223
  }
153
224
  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; }
154
225
  function endHumanGateIfResumed() {
@@ -217,54 +288,54 @@ function findNextExecutableStep(steps, startIdx, dir) {
217
288
  const AGENT_HANDOFF = {
218
289
  'task-analyst': (s) =>
219
290
  `Invoke task-analyst for slug "${s}". Write brief.md and decomposition.md under .claude/team/tasks/${s}/. ` +
220
- `Set status.json: phase executing, state awaiting_approval if human gate applies. Do not use legacy phase analysis.`,
291
+ `Upsert the analysis receipt in artifact-manifest.json; do not mutate status.json lifecycle fields. Do not use legacy phase analysis.`,
221
292
  debugger: (s) =>
222
293
  `Invoke debugger subagent for slug "${s}". Investigate root cause; write debug-report.md with fixApplied: true/false. ` +
223
- `Update status.json when done.`,
294
+ `Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
224
295
  'solution-architect': (s) =>
225
- `Invoke solution-architect for slug "${s}". Read brief.md; write architecture.md. Update status.json when done.`,
296
+ `Invoke solution-architect for slug "${s}". Read brief.md; write architecture.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
226
297
  'migration-specialist': (s) =>
227
- `Invoke migration-specialist for slug "${s}". Read brief.md; write migration-plan.md. Update status.json when done.`,
298
+ `Invoke migration-specialist for slug "${s}". Read brief.md; write migration-plan.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
228
299
  'api-contract-reviewer': (s) =>
229
- `Invoke api-contract-reviewer (readonly) for slug "${s}". Write api-contract-review.md. Update status.json when done.`,
300
+ `Invoke api-contract-reviewer (readonly) for slug "${s}". Write api-contract-review.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
230
301
  'ci-investigator': (s) =>
231
302
  `Invoke ci-investigator for slug "${s}". Diagnose CI failure; write ci-report.md; apply minimal fix. ` +
232
- `Use ci-investigation skill. Update status.json when done.`,
303
+ `Use ci-investigation skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
233
304
  'feature-developer': (s) =>
234
305
  `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. ` +
235
- `Follow architecture/feature-delivery-workflow.md and architecture/reference-features.md. Include unit tests for mappers/logic before handoff. Update status.json when done.`,
306
+ `Follow architecture/feature-delivery-workflow.md and architecture/reference-features.md. Include unit tests for mappers/logic before handoff. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
236
307
  'build-verifier': (s) =>
237
308
  `Invoke build-verifier (readonly) for slug "${s}". Run lint:js, lint:css, type-check, and targeted unit tests from app/. ` +
238
- `Write validation-report.md. Set state completed or validation_failed. Do not edit production code.`,
309
+ `Write validation-report.md and emit receipt outcome completed or validation_failed. Do not edit production code.`,
239
310
  'accessibility-reviewer': (s) =>
240
- `Invoke accessibility-reviewer (readonly) for slug "${s}". Review UI diff; write a11y-review.md. Update status.json when done.`,
311
+ `Invoke accessibility-reviewer (readonly) for slug "${s}". Review UI diff; write a11y-review.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
241
312
  'performance-auditor': (s) =>
242
- `Invoke performance-auditor (readonly) for slug "${s}". Write perf-report.md. Update status.json when done.`,
313
+ `Invoke performance-auditor (readonly) for slug "${s}". Write perf-report.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
243
314
  'code-reviewer': (s) =>
244
315
  `Invoke code-reviewer subagent (readonly) for slug "${s}". Review git diff vs brief.md per tooling-and-review/code-review-mr.md. ` +
245
- `Update status.json when done.`,
316
+ `Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
246
317
  'security-reviewer': (s) =>
247
- `Invoke security-reviewer (readonly) for slug "${s}". Write security-review.md. Update status.json when done.`,
318
+ `Invoke security-reviewer (readonly) for slug "${s}". Write security-review.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
248
319
  'qa-tester': (s, scope) =>
249
320
  `Invoke qa-tester for slug "${s}"${scope ? ` (scope: ${scope})` : ''}. ` +
250
- `Add/update tests per brief. Follow testing/playwright-agents.md. Update status.json when done.`,
321
+ `Add/update tests per brief. Follow testing/playwright-agents.md. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
251
322
  'unit-test-planner': (s) =>
252
- `Invoke unit-test-planner for slug "${s}". Write unit-test-plan.md. Use unit-testing skill. Update status.json when done.`,
323
+ `Invoke unit-test-planner for slug "${s}". Write unit-test-plan.md. Use unit-testing skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
253
324
  'unit-test-generator': (s) =>
254
- `Invoke unit-test-generator for slug "${s}". Implement specs from unit-test-plan.md. Use unit-testing skill. Update status.json when done.`,
325
+ `Invoke unit-test-generator for slug "${s}". Implement specs from unit-test-plan.md. Use unit-testing skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
255
326
  'unit-test-healer': (s) =>
256
- `Invoke unit-test-healer for slug "${s}". Fix failing unit tests. Use unit-testing and debug-investigation skills. Update status.json when done.`,
327
+ `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.`,
257
328
  'playwright-test-planner': (s) =>
258
329
  `Invoke playwright-test-planner for slug "${s}". Create/update app/__tests__/e2e/**/*.cases.md. ` +
259
- `Follow testing/playwright-agents.md and the playwright-e2e skill. Update status.json when done.`,
330
+ `Follow testing/playwright-agents.md and the playwright-e2e skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
260
331
  'playwright-test-generator': (s) =>
261
332
  `Invoke playwright-test-generator for slug "${s}". Generate/update *.spec.ts from matching *.cases.md. ` +
262
- `Use playwright MCP when available. Follow testing/playwright-agents.md and the playwright-e2e skill. Update status.json when done.`,
333
+ `Use playwright MCP when available. Follow testing/playwright-agents.md and the playwright-e2e skill. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
263
334
  'playwright-test-healer': (s) =>
264
335
  `Invoke playwright-test-healer for slug "${s}". Fix failing/flaky e2e while preserving cases.md assertions. ` +
265
- `Use playwright MCP when available. Follow testing/playwright-agents.md and debug-investigation. Update status.json when done.`,
336
+ `Use playwright MCP when available. Follow testing/playwright-agents.md and debug-investigation. Upsert the owned terminal receipt in artifact-manifest.json; do not mutate status.json lifecycle fields.`,
266
337
  'tech-writer': (s) =>
267
- `Invoke tech-writer for slug "${s}". Write documentation.md from task artifacts. Update status.json when done.`,
338
+ `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.`,
268
339
  };
269
340
 
270
341
  const REVIEW_AGENTS = new Set([
@@ -277,15 +348,20 @@ const REVIEW_AGENTS = new Set([
277
348
  function metricsHandoff(agent) {
278
349
  const matches = Object.entries(status.activeAttempts || {}).filter(([, entry]) => entry.agent === agent);
279
350
  if (matches.length === 1) {
280
- 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.`;
351
+ 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.`;
281
352
  }
282
353
  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.';
283
354
  }
284
355
 
285
- function handoffForAgent(agent, scope) {
356
+ function artifactHandoff(step, agent) {
357
+ const inputIds = Array.isArray(step?.artifactInputIds) ? step.artifactInputIds.join(', ') : 'manifest defaults';
358
+ return ` Artifact inputs: ${inputIds}; owned output ID: ${artifactOutputIdFor(step, agent)}. Bind the terminal receipt to the supplied attemptId.`;
359
+ }
360
+
361
+ function handoffForAgent(agent, scope, step = null) {
286
362
  const fn = AGENT_HANDOFF[agent];
287
- const handoff = fn ? fn(slug, scope) : `Invoke ${agent} subagent for slug "${slug}". Update status.json when done.`;
288
- return handoff + metricsHandoff(agent);
363
+ 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.`;
364
+ return handoff + artifactHandoff(step, agent) + metricsHandoff(agent);
289
365
  }
290
366
 
291
367
  function handoffForParallelStep(step) {
@@ -295,7 +371,8 @@ function handoffForParallelStep(step) {
295
371
  .map(([attemptId, entry]) => `${entry.agent}=${attemptId}`);
296
372
  return (
297
373
  `Invoke IN PARALLEL (one message, multiple Task calls): ${agents.join(' AND ')} for slug "${slug}". ` +
298
- `Each agent writes its artifact and appends itself to status.json parallelCompleted when done. ` +
374
+ `Each agent writes its artifact and owned receipt; the hook records parallelCompleted from stops. Do not mutate status.json lifecycle fields. ` +
375
+ `Artifact inputs: ${Array.isArray(step.artifactInputIds) ? step.artifactInputIds.join(', ') : 'manifest defaults'}; owned output IDs: ${agents.map((agent) => `${agent}=${artifactOutputIdFor(step, agent)}`).join('; ')}. ` +
299
376
  `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.`
300
377
  );
301
378
  }
@@ -334,8 +411,12 @@ function legacyFlow() {
334
411
  appendMetric({ type: 'retry_scheduled', at: now, attemptId: predecessorAttemptId, nextAgent: agent, retryAfterFix: retryAfterFix || null, source: 'hook', attributionConfidence: 'unknown' });
335
412
  activateAttempts({ agent }, stepIndex);
336
413
  }
337
- if (status.state === 'changes_requested' && REVIEW_AGENTS.has(status.currentAgent)) {
414
+ if (status.state === 'changes_requested') {
338
415
  const reviewer = status.currentAgent;
416
+ if (!reviewer) {
417
+ process.stdout.write('{}');
418
+ process.exit(0);
419
+ }
339
420
  scheduleLegacyRetry('feature-developer', reviewer);
340
421
  writeStatus({
341
422
  currentAgent: 'feature-developer',
@@ -409,7 +490,7 @@ if (hookInput.action === 'end_human_gate') {
409
490
  parallelCompleted: [],
410
491
  });
411
492
  if (resumeNext.step.parallel && resumeAgents.length > 1) out(handoffForParallelStep(resumeNext.step));
412
- else out(handoffForAgent(resumeAgents[0], resumeNext.step.scope));
493
+ else out(handoffForAgent(resumeAgents[0], resumeNext.step.scope, resumeNext.step));
413
494
  process.exit(0);
414
495
  }
415
496
  }
@@ -438,6 +519,19 @@ const steps = pipeline.steps || [];
438
519
  const initialIdx = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
439
520
  const initialStep = steps[initialIdx];
440
521
  const activeAttemptCountBeforeFinish = Object.keys(status.activeAttempts || {}).length;
522
+ const completingAgentHint = (() => {
523
+ const resolved = resolvedAttempt();
524
+ if (resolved?.entry?.agent) return resolved.entry.agent;
525
+ if (status.currentAgent) return status.currentAgent;
526
+ return getStepAgents(initialStep)[0];
527
+ })();
528
+ const reconcilingAttempt = resolvedAttempt();
529
+ reconcileStatusFromManifest(completingAgentHint, initialStep, reconcilingAttempt);
530
+ if (status.state === 'awaiting_artifact_receipt') {
531
+ writeStatus({});
532
+ process.stdout.write('{}');
533
+ process.exit(0);
534
+ }
441
535
  const finishedAttempt = finishActiveAttempt();
442
536
  if (!finishedAttempt && (hookInput.attemptId || activeAttemptCountBeforeFinish > 0)) {
443
537
  process.stdout.write('{}');
@@ -445,6 +539,7 @@ if (!finishedAttempt && (hookInput.attemptId || activeAttemptCountBeforeFinish >
445
539
  }
446
540
  endHumanGateIfResumed();
447
541
  if (steps.length === 0 || pipeline.autoChain === false) {
542
+ writeStatus({});
448
543
  process.stdout.write('{}');
449
544
  process.exit(0);
450
545
  }
@@ -470,8 +565,75 @@ function scheduleRetry(agent, retryAfterFix, predecessor, explicitStepIndex = nu
470
565
  return stepIndex;
471
566
  }
472
567
 
473
- if (status.state === 'changes_requested' && REVIEW_AGENTS.has(status.currentAgent)) {
474
- const reviewer = status.currentAgent;
568
+ const idx = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
569
+ const currentStep = steps[idx];
570
+ const completedAgent = finishedAttempt?.entry?.agent || status.currentAgent || getStepAgents(currentStep)[0];
571
+ const stepAgents = getStepAgents(currentStep);
572
+
573
+ if (currentStep?.parallel && stepAgents.length > 1) {
574
+ const done = [...(status.parallelCompleted || [])];
575
+ const outcomes = { ...(status.parallelOutcomes || {}) };
576
+ if (finishedAttempt && completedAgent) {
577
+ if (!done.includes(completedAgent)) done.push(completedAgent);
578
+ outcomes[completedAgent] = {
579
+ outcome: finishedAttempt.outcome || status.state,
580
+ attemptId: finishedAttempt.attemptId,
581
+ confidence: finishedAttempt.confidence,
582
+ };
583
+ }
584
+ writeStatus({ parallelCompleted: done, parallelOutcomes: outcomes });
585
+ const remaining = stepAgents.filter((agent) => !done.includes(agent));
586
+ if (remaining.length > 0) {
587
+ process.stdout.write('{}');
588
+ process.exit(0);
589
+ }
590
+
591
+ const blockedProducer = stepAgents.find((agent) => outcomes[agent]?.outcome === 'blocked');
592
+ if (blockedProducer) {
593
+ writeStatus({ parallelCompleted: [], parallelOutcomes: {}, state: 'blocked', phase: 'blocked', currentAgent: blockedProducer });
594
+ process.stdout.write('{}');
595
+ process.exit(0);
596
+ }
597
+
598
+ const retryReviewer = stepAgents.find((agent) => outcomes[agent]?.outcome === 'changes_requested');
599
+ if (retryReviewer) {
600
+ const retryAfterFixStepIndex = idx;
601
+ const predecessor = outcomes[retryReviewer]
602
+ ? {
603
+ attemptId: outcomes[retryReviewer].attemptId,
604
+ confidence: outcomes[retryReviewer].confidence,
605
+ }
606
+ : finishedAttempt;
607
+ const developerIndex = scheduleRetry('feature-developer', retryReviewer, predecessor);
608
+ writeStatus({
609
+ pipelineIndex: developerIndex,
610
+ currentAgent: 'feature-developer',
611
+ state: 'in_progress',
612
+ phase: 'executing',
613
+ retryAfterFix: retryReviewer,
614
+ retryAfterFixStepIndex,
615
+ parallelCompleted: [],
616
+ parallelOutcomes: {},
617
+ });
618
+ out(handoffForAgent('feature-developer', steps[developerIndex]?.scope, steps[developerIndex]) + ` After fixes, re-run ${retryReviewer}.`);
619
+ process.exit(0);
620
+ }
621
+
622
+ writeStatus({ parallelCompleted: [], parallelOutcomes: {}, state: 'completed' });
623
+ }
624
+
625
+ if (status.state === 'blocked') {
626
+ writeStatus({ state: 'blocked', phase: 'blocked', currentAgent: completedAgent });
627
+ process.stdout.write('{}');
628
+ process.exit(0);
629
+ }
630
+
631
+ if (status.state === 'changes_requested') {
632
+ const reviewer = status.currentAgent || finishedAttempt?.entry?.agent;
633
+ if (!reviewer) {
634
+ process.stdout.write('{}');
635
+ process.exit(0);
636
+ }
475
637
  const retryAfterFixStepIndex = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
476
638
  const developerIndex = scheduleRetry('feature-developer', reviewer, finishedAttempt);
477
639
  writeStatus({
@@ -482,7 +644,7 @@ if (status.state === 'changes_requested' && REVIEW_AGENTS.has(status.currentAgen
482
644
  retryAfterFix: reviewer,
483
645
  retryAfterFixStepIndex,
484
646
  });
485
- out(handoffForAgent('feature-developer') + ` After fixes, re-run ${reviewer}.`);
647
+ out(handoffForAgent('feature-developer', steps[developerIndex]?.scope, steps[developerIndex]) + ` After fixes, re-run ${reviewer}.`);
486
648
  process.exit(0);
487
649
  }
488
650
 
@@ -497,7 +659,7 @@ if (status.state === 'validation_failed' && status.currentAgent === 'build-verif
497
659
  retryAfterFix: 'build-verifier',
498
660
  retryAfterFixStepIndex,
499
661
  });
500
- out(handoffForAgent('feature-developer') + ' Read validation-report.md. After fixes, re-run build-verifier.');
662
+ out(handoffForAgent('feature-developer', steps[developerIndex]?.scope, steps[developerIndex]) + ' Read validation-report.md. After fixes, re-run build-verifier.');
501
663
  process.exit(0);
502
664
  }
503
665
 
@@ -505,35 +667,16 @@ if (status.state === 'completed' && status.retryAfterFix) {
505
667
  const retry = status.retryAfterFix;
506
668
  const retryIndex = scheduleRetry(retry, null, finishedAttempt, status.retryAfterFixStepIndex);
507
669
  writeStatus({ pipelineIndex: retryIndex, currentAgent: retry, state: 'in_progress', retryAfterFix: null, retryAfterFixStepIndex: null });
508
- out(handoffForAgent(retry));
670
+ out(handoffForAgent(retry, steps[retryIndex]?.scope, steps[retryIndex]));
509
671
  process.exit(0);
510
672
  }
511
673
 
512
- const idx = typeof status.pipelineIndex === 'number' ? status.pipelineIndex : 0;
513
- const currentStep = steps[idx];
514
674
  if (!currentStep) {
515
675
  writeStatus({ phase: 'done', state: 'completed', currentAgent: null });
516
676
  out(`Pipeline complete for "${slug}". Run /technical-retro ${slug}.`);
517
677
  process.exit(0);
518
678
  }
519
679
 
520
- const completedAgent = finishedAttempt?.entry?.agent || status.currentAgent || getStepAgents(currentStep)[0];
521
- const stepAgents = getStepAgents(currentStep);
522
-
523
- if (currentStep.parallel && stepAgents.length > 1) {
524
- const done = [...(status.parallelCompleted || [])];
525
- if (completedAgent && !done.includes(completedAgent)) {
526
- done.push(completedAgent);
527
- }
528
- writeStatus({ parallelCompleted: done });
529
- const remaining = stepAgents.filter((a) => !done.includes(a));
530
- if (remaining.length > 0) {
531
- process.stdout.write('{}');
532
- process.exit(0);
533
- }
534
- writeStatus({ parallelCompleted: [] });
535
- }
536
-
537
680
  if (hasGateAfterStep(currentStep, pipeline.humanGates)) {
538
681
  beginHumanGate();
539
682
  writeStatus({
@@ -575,6 +718,6 @@ writeStatus({
575
718
  if (nextStep.parallel && nextAgents.length > 1) {
576
719
  out(handoffForParallelStep(nextStep));
577
720
  } else {
578
- out(handoffForAgent(nextAgents[0], nextStep.scope));
721
+ out(handoffForAgent(nextAgents[0], nextStep.scope, nextStep));
579
722
  }
580
723
  NODE
@@ -50,7 +50,7 @@ Work request без `/task` — см. **`tooling-and-review/agent-team-intake.md
50
50
  | `playwright-test-healer` | tests only | failing/flaky e2e fixes |
51
51
  | `tech-writer` | docs only | docs-only, feature/migration docs |
52
52
 
53
- Full prompts: `.claude/agents/*.md`. Artifact conventions: `.claude/team/README.md`.
53
+ Full prompts: `.claude/agents/*.md`. Artifact conventions: `.claude/team/README.md`; schema SoT: **agent-artifact-contracts**.
54
54
 
55
55
  ## Dynamic pipeline
56
56
 
@@ -76,9 +76,21 @@ flowchart TD
76
76
 
77
77
  **Source of truth for order:** `pipeline.json` → `steps[]`. Never hardcode analyst → dev → review → QA when `pipeline.json` exists.
78
78
 
79
+ New pipelines retain top-level `profile` and include `routingDecision` with `complexity`, `riskFlags`, non-empty `routingReasons`, `estimatedWorkPackages`, and `openDecisionCount`. The stack `task-router` owns heuristics; this metadata is auditable evidence and does not change hook semantics.
80
+
79
81
  ## Metrics lifecycle
80
82
 
81
- Use the local `metrics.json` ledger described by **agent-task-metrics**: initialize attempts before invocation, let hooks record host-clock ends, and never guess a parallel completion from `currentAgent`.
83
+ Use the local `metrics.json` ledger described by **agent-task-metrics**: the orchestration runtime initializes attempts before invocation, hooks record host-clock ends, and agents never guess a parallel completion from `currentAgent`.
84
+
85
+ ## Artifact context index and receipts
86
+
87
+ Each slug has one `artifact-manifest.json`: `.cursor/team/tasks/<slug>/` or `.claude/team/tasks/<slug>/`. Schema SoT: **agent-artifact-contracts**.
88
+
89
+ - Read the manifest before broad prior-artifact discovery. Each step receives authoritative input entry IDs, owns named output entry IDs, and finishes with one terminal receipt linked to AC/task IDs.
90
+ - Markdown remains detailed evidence; the manifest records producer, status, authoritative paths, timestamps, outcome, and evidence paths.
91
+ - Agents upsert only entries they own and preserve foreign entries. Parallel agents use distinct IDs; the parent/orchestrator serializes manifest merges.
92
+ - The orchestration runtime/hook is the single writer for `status.json` lifecycle fields and `metrics.json`. Agents read status for eligibility but never rewrite state, gate, retry, attempt, timestamp, or metrics fields.
93
+ - Missing/stale manifests are reported as a handoff gap; fall back to `brief.md`, `decomposition.md`, and `pipeline.json` rather than inventing authority.
82
94
 
83
95
  ## status.json (pipeline mode)
84
96
 
@@ -90,10 +102,23 @@ Use the local `metrics.json` ledger described by **agent-task-metrics**: initial
90
102
  "currentAgent": "task-analyst",
91
103
  "phase": "executing",
92
104
  "state": "in_progress",
93
- "awaitingHumanGate": false
105
+ "awaitingHumanGate": false,
106
+ "activeAttempts": {},
107
+ "lastCompletedAttempt": null,
108
+ "parallelCompleted": [],
109
+ "parallelOutcomes": {}
94
110
  }
95
111
  ```
96
112
 
113
+ | Field | Meaning |
114
+ |-------|---------|
115
+ | `activeAttempts` | Transient timing bridge keyed by `attemptId` |
116
+ | `lastCompletedAttempt` | Diagnostic pointer to the latest finished attempt |
117
+ | `parallelCompleted` | Agents that have stopped in the current parallel step |
118
+ | `parallelOutcomes` | Per-agent `{ outcome, attemptId, confidence }` for the current parallel join; cleared when the step advances or retries |
119
+
120
+ On each `subagentStop`, the hook reconciles `status.state` from the completing producer's terminal `artifact-manifest.json` receipt when agents left lifecycle fields untouched. Wait for that handoff before invoking the next step.
121
+
97
122
  | state | Meaning |
98
123
  |-------|---------|
99
124
  | `in_progress` | Current step running |
@@ -140,7 +165,7 @@ Use the available Claude Code subagent / Agent tool. Pass: slug, artifact paths,
140
165
  - **`strong`** → `opus` (matches strong-agent frontmatter). Prefer explicit per-invocation `model` when frontmatter pins are flaky.
141
166
  3. Parallel steps: apply the step-level `model` to each agent (or omit → each frontmatter).
142
167
 
143
- After each agent completes, ensure `status.json` has `state: completed` (or `awaiting_approval` if gate applies).
168
+ After each agent completes, verify its terminal manifest receipt, then let the orchestration runtime/hook reconcile `status.json` (`completed`, a blocking outcome, or `awaiting_approval`) from that receipt. Do not ask the agent to mutate shared lifecycle fields, and do not invoke the next specialist until the hook has written the handoff / updated `status.json`.
144
169
 
145
170
  ## Legacy mode
146
171
 
@@ -70,10 +70,11 @@
70
70
  "phase": "executing",
71
71
  "state": "in_progress",
72
72
  "awaitingHumanGate": false,
73
- "parallelCompleted": [],
74
73
  "retryAfterFix": null,
75
74
  "activeAttempts": {},
76
75
  "lastCompletedAttempt": null,
76
+ "parallelCompleted": [],
77
+ "parallelOutcomes": {},
77
78
  "updatedAt": "2026-05-29T12:00:00.000Z"
78
79
  }
79
80
  ```
@@ -107,11 +108,22 @@ Legacy: `/feature-start`, `/feature-continue`.
107
108
 
108
109
  Commit task artifacts if you want them to survive `ai-rules clean claude`. Or copy finished tasks to `docs/agent-workflow/tasks/<slug>/`.
109
110
 
111
+ ## Artifact manifest and receipts
112
+
113
+ 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.
114
+
115
+ Producer/consumer rules:
116
+
117
+ - 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.
118
+ - Producers upsert only their stable entry IDs, preserve foreign entries, and finish with `completed`, `blocked`, `changes_requested`, or `validation_failed` receipt evidence.
119
+ - 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.
120
+ - 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.
121
+
110
122
  ## Local metrics ledger
111
123
 
112
- `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. Policy SoT: `presets/_shared/core/agent-team/agent-task-metrics.md`. 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.
124
+ `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. Policy SoT: `presets/_shared/core/agent-team/agent-task-metrics.md`. 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.
113
125
 
114
- `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`.
126
+ `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`.
115
127
 
116
128
  After a human gate, `/task-continue` closes wait via the installed hook (do not hand-edit `pipelineIndex` first):
117
129
 
@@ -0,0 +1,61 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "slug": "order-history-date-filter",
4
+ "updatedAt": "2026-07-31T12:20:00.000Z",
5
+ "artifacts": [
6
+ {
7
+ "id": "routing",
8
+ "producer": "task-router",
9
+ "status": "completed",
10
+ "authoritativePaths": ["pipeline.json"],
11
+ "acceptanceCriteriaIds": [],
12
+ "taskIds": [],
13
+ "timestamps": {
14
+ "createdAt": "2026-07-31T12:00:00.000Z",
15
+ "updatedAt": "2026-07-31T12:00:00.000Z",
16
+ "completedAt": "2026-07-31T12:00:00.000Z"
17
+ },
18
+ "receipt": {
19
+ "outcome": "completed",
20
+ "summary": "The feature pipeline and routing evidence are authoritative.",
21
+ "evidencePaths": ["pipeline.json"]
22
+ }
23
+ },
24
+ {
25
+ "id": "analysis",
26
+ "producer": "task-analyst",
27
+ "status": "completed",
28
+ "authoritativePaths": ["brief.md", "decomposition.md"],
29
+ "acceptanceCriteriaIds": ["AC-1", "AC-2"],
30
+ "taskIds": ["T1", "T2"],
31
+ "timestamps": {
32
+ "createdAt": "2026-07-31T12:01:00.000Z",
33
+ "updatedAt": "2026-07-31T12:05:00.000Z",
34
+ "completedAt": "2026-07-31T12:05:00.000Z"
35
+ },
36
+ "receipt": {
37
+ "outcome": "completed",
38
+ "summary": "The acceptance criteria and implementation tasks are ready.",
39
+ "evidencePaths": ["brief.md", "decomposition.md"]
40
+ }
41
+ },
42
+ {
43
+ "id": "implementation",
44
+ "producer": "feature-developer",
45
+ "status": "completed",
46
+ "authoritativePaths": ["implementation.md"],
47
+ "acceptanceCriteriaIds": ["AC-1", "AC-2"],
48
+ "taskIds": ["T1", "T2"],
49
+ "timestamps": {
50
+ "createdAt": "2026-07-31T12:06:00.000Z",
51
+ "updatedAt": "2026-07-31T12:20:00.000Z",
52
+ "completedAt": "2026-07-31T12:20:00.000Z"
53
+ },
54
+ "receipt": {
55
+ "outcome": "completed",
56
+ "summary": "Implementation and focused unit coverage are complete.",
57
+ "evidencePaths": ["implementation.md"]
58
+ }
59
+ }
60
+ ]
61
+ }
@@ -2,6 +2,16 @@
2
2
  "slug": "example-bugfix-standard",
3
3
  "intent": "bugfix",
4
4
  "profile": "standard",
5
+ "routingDecision": {
6
+ "complexity": "moderate",
7
+ "riskFlags": ["behavior-regression"],
8
+ "routingReasons": [
9
+ "The regression needs root-cause evidence before deciding whether a separate fix pass is necessary.",
10
+ "The bounded behavior change still requires validation and independent review."
11
+ ],
12
+ "estimatedWorkPackages": 1,
13
+ "openDecisionCount": 0
14
+ },
5
15
  "summary": "Regression fix with review",
6
16
  "steps": [
7
17
  { "agent": "debugger", "label": "Root cause" },
@@ -2,6 +2,16 @@
2
2
  "slug": "example-feature-full",
3
3
  "intent": "feature",
4
4
  "profile": "full",
5
+ "routingDecision": {
6
+ "complexity": "high",
7
+ "riskFlags": ["cross-layer", "public-contract", "broad-test-surface"],
8
+ "routingReasons": [
9
+ "The feature spans UI, state, and API boundaries, so architecture and implementation checkpoints are required.",
10
+ "The public contract and e2e acceptance surface require independent validation and review."
11
+ ],
12
+ "estimatedWorkPackages": 2,
13
+ "openDecisionCount": 1
14
+ },
5
15
  "summary": "Multi-layer feature with e2e coverage",
6
16
  "steps": [
7
17
  { "agent": "task-analyst", "label": "Clarify and decompose" },
@@ -2,6 +2,16 @@
2
2
  "slug": "example-feature-light",
3
3
  "intent": "feature",
4
4
  "profile": "light",
5
+ "routingDecision": {
6
+ "complexity": "low",
7
+ "riskFlags": [],
8
+ "routingReasons": [
9
+ "The request has explicit acceptance criteria and a reversible single-file scope.",
10
+ "No public contract, cross-layer coordination, or open decision requires analysis or architecture."
11
+ ],
12
+ "estimatedWorkPackages": 1,
13
+ "openDecisionCount": 0
14
+ },
5
15
  "summary": "Trivial single-file change with explicit AC",
6
16
  "steps": [
7
17
  { "agent": "feature-developer", "label": "Implement", "model": "cheap" },
@@ -12,8 +12,8 @@ You are the build verifier for **PHP hexagonal** services (and for **ai-rules pr
12
12
  1. Detect Composer from `composer.json` / `composer.lock`; prefer Composer scripts when present.
13
13
  2. Run from module/project root: `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 (PHP-CS-Fixer, Pint, PHPStan, Psalm, 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` (or `.claude/team/...`) 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 PHP 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-hexagonal 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.