@tea-agent/loop-agent 0.43.0-next.17 → 0.43.0-next.19

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 (236) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +68 -2309
  3. package/README.md +7 -0
  4. package/dist/application/dag/args.js +8 -0
  5. package/dist/application/task-lifecycle/advance.js +38 -7
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +2 -0
  8. package/dist/cli/program.js +3 -1
  9. package/dist/cli/update/init-surface-notifier.js +2 -0
  10. package/dist/cli/update/policy.js +2 -0
  11. package/dist/commands/cursor-prompt.js +2 -0
  12. package/dist/commands/dag-approve.js +2 -0
  13. package/dist/commands/dag-final-verification.js +2 -0
  14. package/dist/commands/dag-init-hybrid.js +2 -0
  15. package/dist/commands/dag-reconcile-tasks.js +2 -0
  16. package/dist/commands/dag-reject.js +2 -0
  17. package/dist/commands/dag-report.js +2 -0
  18. package/dist/commands/dag-rerun.js +2 -0
  19. package/dist/commands/dag-resume.js +2 -0
  20. package/dist/commands/dag-workflow-compile.js +2 -0
  21. package/dist/commands/dag-workflow-plan.js +2 -0
  22. package/dist/commands/dag-workflow-validate.js +2 -0
  23. package/dist/commands/delegate.js +2 -0
  24. package/dist/commands/examples.js +2 -0
  25. package/dist/commands/import-prd.js +2 -0
  26. package/dist/commands/init-model-catalog.js +2 -0
  27. package/dist/commands/init.js +19 -13
  28. package/dist/commands/instructions.js +2 -0
  29. package/dist/commands/knowledge.js +2 -0
  30. package/dist/commands/loop-benchmark.js +2 -0
  31. package/dist/commands/pi-prompt.js +2 -0
  32. package/dist/commands/pi-reuse-benchmark.js +2 -0
  33. package/dist/commands/promote-run.js +2 -0
  34. package/dist/commands/stats.js +1 -1
  35. package/dist/commands/study-init.js +2 -0
  36. package/dist/commands/task-advance.js +34 -0
  37. package/dist/commands/task-source-prepare.js +2 -0
  38. package/dist/commands/worktree-create.js +2 -0
  39. package/dist/commands/worktree-remove.js +2 -0
  40. package/dist/executors/dag-pi-executor.js +1640 -194
  41. package/dist/executors/pi-executor.js +85 -3
  42. package/dist/executors/pi-sdk-executor.js +18 -0
  43. package/dist/executors/shell-executor.js +92 -7
  44. package/dist/executors/shell-verification.js +3 -0
  45. package/dist/governance/exec-plans.js +6 -3
  46. package/dist/infrastructure/console/app-data.js +6 -0
  47. package/dist/shared/artifacts-core.js +2 -0
  48. package/dist/shared/backend-dogfood-preflight.js +47 -0
  49. package/dist/shared/dag-failure-category.js +3 -0
  50. package/dist/shared/git-progress.js +2 -0
  51. package/dist/shared/one-shot-prompt-args.js +2 -0
  52. package/dist/shared/operator/capabilities.js +180 -0
  53. package/dist/shared/package-metadata.js +6 -4
  54. package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
  55. package/dist/shared/reference-context.js +2 -0
  56. package/dist/task/config-types.js +29 -0
  57. package/dist/task/contract/project.js +9 -0
  58. package/dist/task/contract/recover.js +6 -0
  59. package/dist/task/contract/schema.js +17 -0
  60. package/dist/task/source-prepare/artifact-meta.js +9 -2
  61. package/dist/task/source-prepare/build-draft.js +60 -0
  62. package/dist/task/source-prepare/fragment-inventory.js +4 -1
  63. package/dist/task/source-prepare/parse-intent.js +36 -11
  64. package/dist/task/task-demand-routing.js +5 -2
  65. package/dist/worker/console/chat/browser-automation.js +468 -308
  66. package/dist/worker/console/chat/browser-routes.js +175 -57
  67. package/dist/worker/console/chat/explore-tools.js +5 -2
  68. package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
  69. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +115 -20
  70. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +24 -16
  71. package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
  72. package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
  73. package/dist/worker/console/chat/pi-runtime.js +95 -28
  74. package/dist/worker/console/chat/routes.js +20 -1
  75. package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
  76. package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
  77. package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
  78. package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
  79. package/dist/worker/console/chat/session-mode.js +8 -4
  80. package/dist/worker/console/chat/session-store.js +42 -9
  81. package/dist/worker/console/chat/tool-preview.js +115 -0
  82. package/dist/worker/console/chat/tools.js +7 -1
  83. package/dist/worker/console/chat/turn-order.js +13 -0
  84. package/dist/worker/console/chat/turn-process.js +32 -30
  85. package/dist/worker/console/operator-actions.js +50 -0
  86. package/dist/worker/console/prd-intake-bridge.js +54 -1
  87. package/dist/worker/console/scheduled-goal-host.js +98 -0
  88. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  89. package/dist/worker/console/server.js +15 -0
  90. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  91. package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-eoQiMvuk.js} +1 -1
  92. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  93. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  94. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  95. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  96. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  101. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  102. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  106. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  107. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  108. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  109. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  110. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  111. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  112. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  113. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  114. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  115. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  116. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  117. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  118. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  119. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  120. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  121. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  122. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  123. package/dist/worker/console/static/assets/{index-wXlQohNs.js → index-Cdkvw_H6.js} +97 -97
  124. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  125. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  126. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  127. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  128. package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-BDZ9riWi.js} +1 -1
  129. package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-7pKqYtpZ.js} +5 -5
  130. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  131. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  132. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  133. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  134. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  135. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  136. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  137. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  138. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  139. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  140. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  141. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  142. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  143. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  144. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  145. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  146. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  147. package/dist/worker/console/static/index.html +2 -2
  148. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  149. package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
  150. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
  151. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  152. package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -5
  153. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
  154. package/dist/worker/console/static-src/prd-file-import.js +3 -1
  155. package/dist/worker/console/workspace-context.js +17 -1
  156. package/dist/worker/delivery/git-transaction.js +2 -0
  157. package/dist/worker/feature/fullstack-validate.js +2 -0
  158. package/dist/worker/loop-agent/loop-agent-client.js +8 -2
  159. package/dist/worker/materialize/harness-task-materializer.js +4 -1
  160. package/dist/worker/metrics/projector.js +1 -1
  161. package/dist/worker/observability/event-store.js +4 -1
  162. package/dist/worker/observability/read-model.js +4 -1
  163. package/dist/worker/observe/node-transparency.js +23 -1
  164. package/dist/worker/observe/routes.js +4 -0
  165. package/dist/worker/observe/static/operator-chrome.js +3 -1
  166. package/dist/worker/outcomes/declared-artifacts.js +2 -0
  167. package/dist/worker/preflight.js +3 -0
  168. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  169. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  170. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  171. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  172. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  173. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  174. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  175. package/dist/worker/task-spec/validate.js +4 -1
  176. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  177. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  178. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  179. package/dist/workflows/dag/decision-envelope.js +2 -0
  180. package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
  181. package/dist/workflows/dag/frontend-closeout.js +4 -2
  182. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  183. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  184. package/dist/workflows/dag/frontend-implementation-contract.js +365 -8
  185. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  186. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  187. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  188. package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
  189. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  190. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  191. package/dist/workflows/dag/frontend-review-context.js +43 -70
  192. package/dist/workflows/dag/frontend-risk.js +92 -10
  193. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  194. package/dist/workflows/dag/frontend-shape.js +11 -55
  195. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  196. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  197. package/dist/workflows/dag/frontend-verification-trace.js +25 -12
  198. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  199. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  200. package/dist/workflows/dag/init-hybrid.js +202 -111
  201. package/dist/workflows/dag/interrupt-request.js +2 -0
  202. package/dist/workflows/dag/lifecycle.js +11 -2
  203. package/dist/workflows/dag/node-execution.js +50 -13
  204. package/dist/workflows/dag/reconcile-run.js +2 -0
  205. package/dist/workflows/dag/repair-artifact.js +3 -1
  206. package/dist/workflows/dag/report.js +2 -0
  207. package/dist/workflows/dag/rerun-plan.js +10 -0
  208. package/dist/workflows/dag/rerun-task.js +79 -1
  209. package/dist/workflows/dag/retry-policy.js +18 -0
  210. package/dist/workflows/dag/scheduler.js +2 -4
  211. package/dist/workflows/dag/types.js +44 -6
  212. package/dist/workflows/dag/validate.js +4 -0
  213. package/docs/README.md +1 -0
  214. package/docs/governance/README.md +1 -0
  215. package/docs/init-surface.manifest.json +1 -0
  216. package/docs/operations/README.md +2 -0
  217. package/docs/templates/README.md +1 -1
  218. package/docs/templates/agent-dag.schema.json +2 -2
  219. package/docs/templates/backend-test-dag.json +14 -10
  220. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  221. package/docs/templates/frontend-implementation-dag.json +8 -9
  222. package/package.json +8 -3
  223. package/skills/frontend-bounded-implement/SKILL.md +3 -4
  224. package/skills/frontend-design-review/SKILL.md +7 -12
  225. package/skills/frontend-design-review/references/review-checklist.md +7 -7
  226. package/skills/frontend-plan/SKILL.md +8 -18
  227. package/skills/frontend-plan/references/decision-contract.md +19 -26
  228. package/skills/frontend-review/SKILL.md +21 -25
  229. package/skills/frontend-review/references/review-findings.md +48 -24
  230. package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
  231. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
  232. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
  233. package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
  234. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
  235. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
  236. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
@@ -454,6 +454,8 @@ export function parseDagRequestInterruptArgs(args) {
454
454
  for (let i = 0; i < args.length; i += 1) {
455
455
  const cursor = { value: i };
456
456
  const arg = args[i];
457
+ if (arg === undefined)
458
+ break;
457
459
  const runIdValue = readFlag(args, cursor, "run-id");
458
460
  if (runIdValue !== undefined) {
459
461
  runId = runIdValue;
@@ -684,6 +684,8 @@ export function parseDagStatusArgs(args) {
684
684
  let runId;
685
685
  for (let i = 0; i < args.length; i += 1) {
686
686
  const arg = args[i];
687
+ if (arg === undefined)
688
+ break;
687
689
  if (arg === "--run-id") {
688
690
  runId = args[++i];
689
691
  continue;
@@ -710,8 +712,11 @@ export async function runDagStatus(repoRoot, rawArgs) {
710
712
  export function parseDagDoctorArgs(args) {
711
713
  let runId;
712
714
  let markdown = false;
715
+ let json = false;
713
716
  for (let i = 0; i < args.length; i += 1) {
714
717
  const arg = args[i];
718
+ if (arg === undefined)
719
+ break;
715
720
  if (arg === "--run-id") {
716
721
  runId = args[++i];
717
722
  if (!runId || runId.startsWith("-")) {
@@ -725,6 +730,10 @@ export function parseDagDoctorArgs(args) {
725
730
  throw new Error("dag doctor --run-id requires a value");
726
731
  continue;
727
732
  }
733
+ if (arg === "--json") {
734
+ json = true;
735
+ continue;
736
+ }
728
737
  if (arg === "--markdown") {
729
738
  markdown = true;
730
739
  continue;
@@ -734,7 +743,7 @@ export function parseDagDoctorArgs(args) {
734
743
  }
735
744
  throw new Error(`unexpected positional argument: ${arg}`);
736
745
  }
737
- return { runId, markdown };
746
+ return { runId, markdown, json };
738
747
  }
739
748
  function findDoctorFailureNode(state) {
740
749
  if (state.pausedByNodeId) {
@@ -843,7 +852,7 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
843
852
  export async function runDagDoctor(repoRoot, rawArgs) {
844
853
  const parsed = parseDagDoctorArgs(rawArgs);
845
854
  if (parsed.runId) {
846
- if (parsed.markdown) {
855
+ if (parsed.markdown && !parsed.json) {
847
856
  console.log(await formatDagDoctorMarkdown(repoRoot, parsed.runId));
848
857
  return;
849
858
  }
@@ -15,7 +15,7 @@ import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./p
15
15
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
16
16
  import { materializeDeclaredArtifactFacts } from "./artifact-bindings.js";
17
17
  import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
18
- import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, OUTPUT_LIMIT_RETRY_CATEGORY, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
18
+ import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isProviderAvailabilityFailureCategory, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, OUTPUT_LIMIT_RETRY_CATEGORY, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
19
19
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
20
20
  import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
21
21
  import { getStructuredContractValidator } from "./contract-output-registry.js";
@@ -568,7 +568,7 @@ export function renderFrontendPlanInputContext(input) {
568
568
  "Do not read upstream artifacts, task sources, or repository files. If this input cannot support a decision, record a genuine evidence gap.",
569
569
  "</frontend_plan_input>",
570
570
  "<plan_review_checklist>",
571
- "The design reviewer re-runs these exact checks on the committed facts; satisfy every line before finalize_plan:",
571
+ "Satisfy these checks in the committed facts before the runtime compiles and finalizes the Plan:",
572
572
  ...checklistLines,
573
573
  "</plan_review_checklist>",
574
574
  ].join("\n");
@@ -614,6 +614,10 @@ export async function buildFrontendPlanInputContext(runDir, componentSourceCitat
614
614
  export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
615
615
  if (attemptNumber <= 1)
616
616
  return basePrompt;
617
+ // Match the executor segmented Plan identity; custom nodes retain their terminal.
618
+ const planCompletion = task.id === FRONTEND_PLAN_NODE_ID
619
+ ? "Stop after committing the required facts. The runtime compiles and finalizes the committed ledger automatically; only repair facts reported by its validation."
620
+ : "Then call finalize_plan exactly once.";
617
621
  // Context overflow is a transport/session failure, not a plan-protocol
618
622
  // failure. It must take precedence over the plan retry ladder so every
619
623
  // retry, including a repeated plan overflow, starts from the compact
@@ -656,8 +660,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
656
660
  previousProtocolReason) {
657
661
  // The frontend plan node's compile authority is the committed typed
658
662
  // ledger, not a fenced JSON text artifact: its retry guidance must
659
- // direct the model to re-commit corrected record_* facts and
660
- // finalize_plan. The legacy full-contract JSON guidance below applies
663
+ // direct the model to re-commit corrected record_* facts and respect
664
+ // terminal ownership. The full-contract JSON guidance below applies
661
665
  // only to nodes whose authority is still a text contract artifact.
662
666
  if (task.structuredContractOutput.schemaId ===
663
667
  "frontend-implementation-contract-plan-patch-v1") {
@@ -673,7 +677,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
673
677
  "<retry_instruction>",
674
678
  "Previous plan ledger facts failed canonical contract validation:",
675
679
  previousProtocolReason,
676
- "Fix the reported violations by re-committing corrected record_* facts and calling finalize_plan exactly once. The committed typed ledger is the only compile authority.",
680
+ "Fix the reported violations by re-committing corrected record_* facts. The committed typed ledger is the only compile authority.",
681
+ planCompletion,
677
682
  "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
678
683
  ...frontendPlanValidationRetryGuidance(previousProtocolReason),
679
684
  ...splitGuidance,
@@ -711,7 +716,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
711
716
  "",
712
717
  "<retry_instruction>",
713
718
  "Previous attempt was truncated by the provider (stopReason=length) before the plan facts were fully committed.",
714
- "Re-commit the missing record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority. Do NOT re-read contract/scout outputs or source files — use the facts already in context. Commit record_* facts one tool call per message, then finalize_plan immediately.",
719
+ "Re-commit the missing record_* facts; the committed typed ledger is the only compile authority. Do NOT re-read contract/scout outputs or source files — use the facts already in context. Commit record_* facts one tool call per message.",
720
+ planCompletion,
715
721
  "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
716
722
  "</retry_instruction>",
717
723
  ].join("\n");
@@ -732,7 +738,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
732
738
  "",
733
739
  "<retry_instruction>",
734
740
  "Frontend plan retry ladder step: compact-terminal-first.",
735
- "Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
741
+ "Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts and fill the missing required facts.",
742
+ planCompletion,
736
743
  "Keep the existing committed ledger intact; do not re-derive already committed facts.",
737
744
  "</retry_instruction>",
738
745
  ].join("\n");
@@ -743,7 +750,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
743
750
  "",
744
751
  "<retry_instruction>",
745
752
  "Frontend plan retry ladder step: bounded-tool-only.",
746
- "Reduce reasoning and wall-clock time. Use only read / fact / terminal tools and only for the necessary missing facts, then call finalize_plan exactly once.",
753
+ "Reduce reasoning and wall-clock time. Use only the tools exposed for this session and only for the necessary missing facts.",
754
+ planCompletion,
747
755
  "Do not expand scope or re-derive already committed facts.",
748
756
  "</retry_instruction>",
749
757
  ].join("\n");
@@ -754,7 +762,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
754
762
  "",
755
763
  "<retry_instruction>",
756
764
  "Frontend plan retry ladder step: backup-model.",
757
- "A backup provider/model route was selected after repeated non-converging failures. Re-derive only the missing committed facts, then call finalize_plan exactly once; do not repeat the failed strategy.",
765
+ "A backup provider/model route was selected after repeated non-converging failures. Re-derive only the missing committed facts; do not repeat the failed strategy.",
766
+ planCompletion,
758
767
  "</retry_instruction>",
759
768
  ].join("\n");
760
769
  }
@@ -793,7 +802,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
793
802
  "",
794
803
  "<retry_instruction>",
795
804
  "Previous plan attempt issued too many read-only tool calls (read/grep/ls/find) and blew up the context window. Trust the upstream frontend-contract-pi typed requirement facts and frontend-scout-pi target surface already provided — do NOT re-read contract/scout stdout, PRD/source files, or component sources you already inspected.",
796
- "Minimize discovery reads: only read what you genuinely need, once. Commit record_plan_requirement / record_plan_verification_target / record_* facts directly from the facts already in context (one tool call per message), then call finalize_plan exactly once.",
805
+ "Minimize discovery reads: only read what you genuinely need, once. Commit the required record_* facts directly from the facts already in context (one tool call per message).",
806
+ planCompletion,
797
807
  "</retry_instruction>",
798
808
  ].join("\n");
799
809
  }
@@ -917,7 +927,8 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
917
927
  "",
918
928
  "<retry_instruction>",
919
929
  "Previous attempt exceeded the structured output size limit.",
920
- "Re-commit corrected record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority.",
930
+ "Re-commit corrected record_* facts; the committed typed ledger is the only compile authority.",
931
+ planCompletion,
921
932
  "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
922
933
  "</retry_instruction>",
923
934
  ].join("\n");
@@ -1075,6 +1086,8 @@ function validateRepairArtifactGateBeforeShell(input) {
1075
1086
  if (!gate)
1076
1087
  return;
1077
1088
  const upstream = input.state.nodes[gate.fromNodeId];
1089
+ if (!upstream)
1090
+ return;
1078
1091
  const parsed = parseSupervisorRepairArtifact(upstream);
1079
1092
  if (!parsed.ok) {
1080
1093
  throw new Error(`repair artifact gate failed: ${parsed.reason}`);
@@ -1711,6 +1724,20 @@ export async function executeDagNode(input) {
1711
1724
  ? result.stderr.slice(markerIndex, markerIndex + 4_000).trim()
1712
1725
  : `backend-test Markdown plan attempt failed with ${result.failureCategory}`;
1713
1726
  }
1727
+ // Decision-path Plan bridge failures surface from the executor as
1728
+ // invalid-output (the derived relationship patch failed pre-validation),
1729
+ // NOT through the R1 structured self-check (which only runs when
1730
+ // result.ok). Capture the bridge diagnostics into previousProtocolReason
1731
+ // so the next attempt prompt tells the model exactly which decision facts
1732
+ // to repair instead of replaying the identical under-declared session.
1733
+ if (task.id === FRONTEND_PLAN_NODE_ID &&
1734
+ !result.ok &&
1735
+ result.failureCategory === "invalid-output" &&
1736
+ /frontend decision → relationship patch failed|frontend decision finalize failed/.test(result.stderr ?? "")) {
1737
+ previousProtocolReason = (result.stderr ?? "")
1738
+ .slice(-4_000)
1739
+ .trim();
1740
+ }
1714
1741
  if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
1715
1742
  const rawText = canonicalNodeOutput(result);
1716
1743
  if (rawText.trim().length > 0) {
@@ -1903,13 +1930,23 @@ export async function executeDagNode(input) {
1903
1930
  frontendPriorFingerprints.push(fingerprint);
1904
1931
  frontendPlanRetryStep = nextStep;
1905
1932
  if (nextStep === "unsupported-provider-capability") {
1933
+ // Reachability first: when the failed attempts never reached model
1934
+ // reasoning (gateway 5xx / rate limit / quota / network), the
1935
+ // ladder's fail-closed category would read as a capability mismatch
1936
+ // and send the diagnosis the wrong way. Keep the outage visible in
1937
+ // the node's category and explain why.
1938
+ const providerOutage = isProviderAvailabilityFailureCategory(result.failureCategory);
1906
1939
  result = {
1907
1940
  ...result,
1908
1941
  ok: false,
1909
- failureCategory: "unsupported-provider-capability",
1942
+ failureCategory: providerOutage
1943
+ ? "upstream-unavailable"
1944
+ : "unsupported-provider-capability",
1910
1945
  stderr: [
1911
1946
  result.stderr,
1912
- "frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
1947
+ providerOutage
1948
+ ? "frontend plan retry ladder: attempts failed on provider reachability (5xx / rate limit / quota / network), so this is an upstream outage, not a provider capability mismatch"
1949
+ : "frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
1913
1950
  ]
1914
1951
  .filter(Boolean)
1915
1952
  .join("\n"),
@@ -7,6 +7,8 @@ export function parseDagReconcileRunArgs(args) {
7
7
  let reason;
8
8
  for (let index = 0; index < args.length; index += 1) {
9
9
  const arg = args[index];
10
+ if (arg === undefined)
11
+ break;
10
12
  if (arg === "--run-id")
11
13
  runId = args[++index];
12
14
  else if (arg.startsWith("--run-id="))
@@ -330,7 +330,9 @@ export function resolveRepairTaskForGate(input) {
330
330
  }
331
331
  const candidates = directDownstream.filter(isGovernedRepairWriter);
332
332
  if (candidates.length === 1) {
333
- return { ok: true, repairTask: candidates[0] };
333
+ const [onlyCandidate] = candidates;
334
+ if (onlyCandidate)
335
+ return { ok: true, repairTask: onlyCandidate };
334
336
  }
335
337
  const candidateIds = directDownstream.map((task) => task.id).join(", ") || "none";
336
338
  if (candidates.length === 0) {
@@ -449,6 +449,8 @@ export async function buildDagRunReportEntry(input) {
449
449
  const rankNodes = input.state.ranks[rank] ?? [];
450
450
  for (let order = 0; order < rankNodes.length; order += 1) {
451
451
  const nodeId = rankNodes[order];
452
+ if (nodeId === undefined)
453
+ continue;
452
454
  const node = input.state.nodes[nodeId];
453
455
  if (!node)
454
456
  continue;
@@ -1,3 +1,4 @@
1
+ import { isCanonicalFrontendShapeDag } from "./frontend-shape-facts.js";
1
2
  import { createHash } from "node:crypto";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { z } from "zod";
@@ -1348,6 +1349,15 @@ export async function evaluateDagRerunPlan(input) {
1348
1349
  }
1349
1350
  }
1350
1351
  const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, {
1352
+ // The canonical frontend template tails into read-only artifact shells
1353
+ // (review-context, closeout) that carry no commands. Without artifact
1354
+ // continuation admission every from-node restart of that template was
1355
+ // blocked as `restart-subgraph-contains-unsafe-shell`, so a frontend run
1356
+ // could only ever be re-verified by repeating the whole chain (smoke
1357
+ // r27-r36: ~30 min per iteration instead of ~6).
1358
+ ...(isCanonicalFrontendShapeDag(new Map(input.parentSpec.tasks.map((task) => [task.id, task])))
1359
+ ? { artifactContinuation: true }
1360
+ : {}),
1351
1361
  ...(transportWriterRestart && effectiveFromNodeId
1352
1362
  ? { allowedTransportWriterNodeId: effectiveFromNodeId }
1353
1363
  : {}),
@@ -1,4 +1,4 @@
1
- import { access, readFile } from "node:fs/promises";
1
+ import { access, mkdir, readFile } from "node:fs/promises";
2
2
  import { createHash } from "node:crypto";
3
3
  import path from "node:path";
4
4
  import { generateTaskDagUseCase } from "../../application/dag/generate-task-dag.js";
@@ -8,6 +8,7 @@ import { stageRecoveryChild } from "./frontend-recovery-run.js";
8
8
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
9
9
  import { DAG_RUNS_DIR, isTerminalDagRunStatus, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
10
10
  import { deriveDagRerunFeedback } from "./rerun-feedback.js";
11
+ import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
11
12
  import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, } from "./recovery-lease.js";
12
13
  import { parseDagSpec, } from "./types.js";
13
14
  export const RERUN_TASK_LINEAGE_REL_PATH = ".runtime/rerun-task-lineage.json";
@@ -26,6 +27,8 @@ export function parseDagRerunTaskArgs(args) {
26
27
  let recoveryOperationId;
27
28
  for (let index = 0; index < args.length; index += 1) {
28
29
  const arg = args[index];
30
+ if (arg === undefined)
31
+ break;
29
32
  if (arg === "--run-id")
30
33
  parentRunId = args[++index];
31
34
  else if (arg.startsWith("--run-id="))
@@ -396,6 +399,81 @@ async function executeFrontendRecoveryContinuation(input) {
396
399
  if (!locatedChild || locatedChild.runDir !== staged.childRunDir) {
397
400
  throw new Error(`frontend recovery child lifecycle drift: ${staged.childRunId}`);
398
401
  }
402
+ // D (local design repair): publish a read-only snapshot of the parent's
403
+ // committed decision facts into the child run. The child plan node replays
404
+ // these through its own durable tools (normal commit entry, child binding)
405
+ // and the model only corrects the rows the design review flagged; the
406
+ // parent ledger is never rewritten and the finalize terminal is not
407
+ // inherited. Best-effort: a parent without plan facts plans fresh.
408
+ try {
409
+ const parentPlanLedger = path.join(located.runDir, "frontend-plan-pi", "plan-decision-facts.jsonl");
410
+ const parentRaw = await readFile(parentPlanLedger, "utf8");
411
+ const parentRecords = await readTypedEventStoreFromJsonl(parentPlanLedger);
412
+ // Collapse superseded records first: the parent ledger is append-only
413
+ // (replace:true appends a corrected row), and replaying both the original
414
+ // and its correction would trip FACT_IDENTITY_CONFLICT in the child.
415
+ const { collapseDecisionEntriesByKind } = await import("./frontend-plan-decision-contract.js");
416
+ const byKind = new Map();
417
+ for (const record of parentRecords) {
418
+ const kind = String(record.fact.kind);
419
+ if (record.phase !== "committed" || kind === "finalize_decision")
420
+ continue;
421
+ const entry = record.fact.entry;
422
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
423
+ continue;
424
+ const bucket = byKind.get(kind) ?? [];
425
+ bucket.push(entry);
426
+ byKind.set(kind, bucket);
427
+ }
428
+ let parentCanonicalSha256;
429
+ try {
430
+ const parentCanonical = await readFile(path.join(located.runDir, "contracts", "frontend-implementation-contract.json"), "utf8");
431
+ parentCanonicalSha256 = createHash("sha256").update(parentCanonical).digest("hex");
432
+ }
433
+ catch {
434
+ // Parent canonical unavailable → the identical-plan guard is skipped.
435
+ }
436
+ // Carry the design findings that blocked the parent run: the child's
437
+ // finalize runs a per-finding closure check against them.
438
+ let designFindings = [];
439
+ try {
440
+ const designFacts = await readTypedEventStoreFromJsonl(path.join(located.runDir, "frontend-design-review-pi", "design-typed-facts.jsonl"));
441
+ for (const record of designFacts) {
442
+ const fact = record.fact;
443
+ if (record.phase !== "committed" || fact.kind !== "design-finding")
444
+ continue;
445
+ const finding = (fact.finding ?? {});
446
+ designFindings.push({
447
+ severity: typeof finding.severity === "string" ? finding.severity : "Important",
448
+ issue: typeof finding.issue === "string" ? finding.issue : "",
449
+ requiredChange: typeof finding.requiredChange === "string" ? finding.requiredChange : "",
450
+ });
451
+ }
452
+ }
453
+ catch {
454
+ // No readable design findings → the closure check has no assertions.
455
+ }
456
+ const snapshot = {
457
+ schemaVersion: 1,
458
+ schemaId: "frontend-plan-parent-decision-snapshot-v1",
459
+ parentRunId: input.parentRunId,
460
+ parentLedgerSha256: createHash("sha256").update(parentRaw).digest("hex"),
461
+ ...(parentCanonicalSha256 ? { parentCanonicalSha256 } : {}),
462
+ designFindings,
463
+ facts: [...byKind.entries()].flatMap(([kind, entries]) => collapseDecisionEntriesByKind(kind, entries).map((entry) => ({
464
+ kind,
465
+ entry,
466
+ }))),
467
+ };
468
+ await mkdir(path.join(locatedChild.runDir, "frontend-plan-pi"), {
469
+ recursive: true,
470
+ });
471
+ await writeJsonAtomic(path.join(locatedChild.runDir, "frontend-plan-pi", "parent-decision-snapshot.json"), snapshot);
472
+ }
473
+ catch {
474
+ // No parent plan ledger (or unreadable) → the child plans fresh,
475
+ // exactly like a run without recovery lineage.
476
+ }
399
477
  const childSpec = await readDagRunSpec(locatedChild.runDir);
400
478
  const childState = await readDagRunState(locatedChild.runDir);
401
479
  let runSummary;
@@ -364,6 +364,24 @@ export function computeBackoffDelayMs(attemptNumber, policy) {
364
364
  const raw = policy.initialDelayMs * 2 ** exponent;
365
365
  return Math.min(raw, policy.maxDelayMs);
366
366
  }
367
+ /**
368
+ * Provider-reachability failure categories: the attempt never reached model
369
+ * reasoning, so no capability conclusion may be drawn from it. Used to keep the
370
+ * plan retry ladder from relabelling an upstream outage as
371
+ * `unsupported-provider-capability` (2026-09-11: the gateway returned 503 while
372
+ * r18/r19 reported the vague capability category).
373
+ */
374
+ export const PROVIDER_AVAILABILITY_FAILURE_CATEGORIES = [
375
+ "unavailable",
376
+ "upstream-unavailable",
377
+ "network",
378
+ "rate-limit",
379
+ "quota",
380
+ ];
381
+ export function isProviderAvailabilityFailureCategory(category) {
382
+ return (category !== undefined &&
383
+ PROVIDER_AVAILABILITY_FAILURE_CATEGORIES.includes(category));
384
+ }
367
385
  /**
368
386
  * A+B (AC-001/AC-005): project raw executor signals to the canonical 7-value
369
387
  * protocol failure reason. Specific signals win first, then the existing
@@ -115,7 +115,7 @@ export const FRONTEND_WRITER_NODE_IDS = [
115
115
  ];
116
116
  /**
117
117
  * Run-relative artifact location read by the frontend writer admission gate.
118
- * M8 re-pointed the legacy prewrite-result source at the new
118
+ * M8 points the admission source at the canonical
119
119
  * frontend-writer-admission-result.json artifact (written by the
120
120
  * frontend-writer-admission-shell); the exported name is retained because
121
121
  * runner.ts / node-execution.ts reference it as the admission evidence source.
@@ -125,9 +125,7 @@ export { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT };
125
125
  /**
126
126
  * Read and validate the writer admission result artifact.
127
127
  * Fail-closed: a missing file or invalid payload returns ok:false and never
128
- * throws. This is the M8 write authorization source (replaces the legacy
129
- * frontend-prewrite-result-v1 for dispatch; the legacy artifact remains
130
- * produced only by the shadow `runFrontendPrewriteGate` library export).
128
+ * throws. This is the M8 write authorization source for dispatch.
131
129
  */
132
130
  export async function readFrontendPrewriteResult(runDir) {
133
131
  const artifactPath = path.join(runDir, FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT);
@@ -192,6 +192,25 @@ export const dagJsonArtifactGateSchema = z.object({
192
192
  const dagFrontendNodeIdSchema = z
193
193
  .string()
194
194
  .regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
195
+ /**
196
+ * Requirement id accepted in a generated DAG's `requiredRequirementIds`.
197
+ *
198
+ * These ids are lifted verbatim from the task requirement ledger, which mirrors
199
+ * whatever the PRD wrote. A PRD that writes `AC1` (no hyphen) therefore yields an
200
+ * id this schema rejects, and `dagSpecSchema.parse` surfaces zod's raw issue array
201
+ * - so the operator saw only `"message": "Invalid"` plus a JSON pointer and had no
202
+ * way to learn the constraint. The message below carries it into that error.
203
+ *
204
+ * This is deliberately a fail-closed check, not a normalizer: `AC1` is
205
+ * canonicalized to `AC-1` upstream by `normalizeAcceptanceId` in the semantic
206
+ * intake, so every workflow reads the same ids. What remains here is the guard
207
+ * for genuinely malformed input - and because `dagSpecSchema.parse` surfaces
208
+ * zod's raw issue array, the message below is what makes that guard
209
+ * self-explanatory instead of `"message": "Invalid"` plus a JSON pointer.
210
+ */
211
+ const dagRequirementIdSchema = z
212
+ .string()
213
+ .regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/, 'requirement id must be "<REQ|BR|AC>-<SEGMENT>" with a hyphen after the prefix (e.g. AC-1, AC-FE-001, REQ-SRC-ABC123); ids come verbatim from the task requirement ledger, so a PRD writing "AC1" must write "AC-1"');
195
214
  const openspecCandidatePathSchema = z
196
215
  .string()
197
216
  .refine((candidate) => !candidate.includes("\\") &&
@@ -210,9 +229,7 @@ export const dagFrontendPrewriteGateSchema = z.object({
210
229
  planFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
211
230
  reviewFromNodeId: dagFrontendNodeIdSchema,
212
231
  reviewFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
213
- requiredRequirementIds: z
214
- .array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
215
- .default([]),
232
+ requiredRequirementIds: z.array(dagRequirementIdSchema).default([]),
216
233
  allowedMockStrategies: z
217
234
  .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
218
235
  .min(1),
@@ -422,9 +439,7 @@ export const dagFrontendDesignPolicySchema = z
422
439
  .object({
423
440
  schemaVersion: z.literal(1),
424
441
  planFromNodeId: dagFrontendNodeIdSchema,
425
- requiredRequirementIds: z
426
- .array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
427
- .default([]),
442
+ requiredRequirementIds: z.array(dagRequirementIdSchema).default([]),
428
443
  allowedMockStrategies: z
429
444
  .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
430
445
  .min(1),
@@ -434,6 +449,29 @@ export const dagFrontendDesignPolicySchema = z
434
449
  outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
435
450
  requireSourceFreshness: z.literal(true),
436
451
  implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
452
+ /** Generation-frozen requirement→file ownership. Violations are
453
+ * machine-rejected at plan finalize (smoke r35: prose-only ownership
454
+ * rules were ignored by the planner and re-violated by recovery). */
455
+ requirementOwnership: z
456
+ .array(z
457
+ .object({
458
+ requirementIds: z.array(z.string().min(1)).min(1),
459
+ implementationFiles: z.array(z.string().min(1)).optional(),
460
+ verificationFiles: z.array(z.string().min(1)).optional(),
461
+ })
462
+ .strict())
463
+ .optional(),
464
+ /** Generation-frozen test capability boundary composed into the
465
+ * canonical dependencyPolicy by the runtime. */
466
+ capabilityBoundary: z
467
+ .object({
468
+ allowed: z.array(z.string().min(1)).optional(),
469
+ forbidden: z.array(z.string().min(1)).optional(),
470
+ })
471
+ .strict()
472
+ .optional(),
473
+ /** Generation-frozen interaction id vocabulary (plan finalize check). */
474
+ interactionIds: z.array(z.string().min(1)).optional(),
437
475
  /** Generation-frozen roots that bound custom normative directories. */
438
476
  openspecSpecRoots: z
439
477
  .array(z.string().refine((root) => isValidFrontendSpecRoot(root), "openspec spec root must be a normalized repo-relative directory"))
@@ -222,6 +222,8 @@ function validateRepairArtifactGateConfig(task, spec, issues) {
222
222
  return;
223
223
  }
224
224
  const hardVerify = hardVerifyCandidates[0];
225
+ if (!hardVerify)
226
+ return;
225
227
  const descendantIds = new Set();
226
228
  const queue = [hardVerify.id];
227
229
  while (queue.length > 0) {
@@ -411,6 +413,8 @@ function validateSameRankWriteSetConflicts(spec, ranks, issues) {
411
413
  for (let j = i + 1; j < exclusiveWriters.length; j += 1) {
412
414
  const left = exclusiveWriters[i];
413
415
  const right = exclusiveWriters[j];
416
+ if (!left || !right)
417
+ continue;
414
418
  const leftSet = left.writeSet ?? [];
415
419
  const rightSet = right.writeSet ?? [];
416
420
  for (const leftEntry of leftSet) {
package/docs/README.md CHANGED
@@ -80,5 +80,6 @@
80
80
  | `reports/` | 验证、dogfood、merge、init evolution 与历史 handoff 证据 |
81
81
  | `skills/` | repo-local skill registry |
82
82
  | `templates/` | 可复用任务、报告、DAG 和 init 模板 |
83
+ | `fixtures/` | 文档与前端 DAG 验收使用的固定输入 |
83
84
 
84
85
  站上使用者文档在 `../website/docs/`;源仓治理事实以本目录、`AGENTS.md`、`harness.json`、`CHANGELOG.md` 和检查脚本为准。
@@ -10,6 +10,7 @@
10
10
  - [`harness-methodology-tdd.md`](harness-methodology-tdd.md):测试先行的方法与边界。
11
11
  - [`harness-methodology-verification.md`](harness-methodology-verification.md):如何用新鲜命令结果支持完成声明。
12
12
  - [`harness-methodology-debugging.md`](harness-methodology-debugging.md):系统化定位故障,不用猜测代替证据。
13
+ - [`defect-ledger.md`](defect-ledger.md):真实缺陷台账与优先级规则——治理目标先从真故障选,再看代理指标排名。
13
14
  - [`document-review-policy.md`](document-review-policy.md):文档 freshness、`reviewTier` 与 `lastReviewed` 更新边界。
14
15
  - [`lightweight-github-collaboration.md`](lightweight-github-collaboration.md):短分支、简短 PR、CI 与 Squash Merge 的轻量协作规则。
15
16
  - [`研发模式.md`](研发模式.md):Feature 团队推进、交付和知识回写方法论。
@@ -364,6 +364,7 @@
364
364
  "description": "Documentation and user-facing guidance changes that should be considered but should not slow normal iteration.",
365
365
  "patterns": [
366
366
  "CHANGELOG.md",
367
+ "CHANGELOG/**",
367
368
  "website/docs/**",
368
369
  "docs/*.md",
369
370
  "docs/architecture/**"
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## 入口
6
6
 
7
+ - [`scheduled-goal-host.md`](scheduled-goal-host.md):Console Chat 预约宿主、服务环境、状态判读与清理。
8
+
7
9
  - [`local-development-environment.md`](local-development-environment.md):本地环境、Windows 差异和常见排障。
8
10
  - [`branch-merge-guideline.md`](branch-merge-guideline.md):按风险选择合并模式并留下 source-SHA 证据。
9
11
  - [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及发布通道。
@@ -30,7 +30,7 @@
30
30
 
31
31
  ## Backend-test
32
32
 
33
- - `backend-test-dag.json` — backend-test DAG 模板;其中 `generate-backend-md-cases-pi` 是唯一允许 `writer-empty-diff` 重试的 writer(总共两次,仅限 post-write-guard attribution 确认的空 diff);N2/N5 合同限定 Scenario Partition 必须有源有限域。
33
+ - `backend-test-dag.json` — backend-test DAG 模板;由 runtime builder 生成,Module Index 的 8 列、separator、单一 resolved Markdown Path 和 Split Reason 集合以 `backend-test-plan-protocol.ts` 为真源并由 drift test 校验;其中 `generate-backend-md-cases-pi` 是唯一允许 `writer-empty-diff` 重试的 writer(总共两次,仅限 post-write-guard attribution 确认的空 diff);N2/N5 合同限定 Scenario Partition 必须有源有限域。
34
34
  - `backend-test-dag.classify.prompt.md`、`backend-test-dag.generate-pytest.prompt.md`、`backend-test-dag.review-cases.prompt.md`、`backend-test-dag.retrospect.prompt.md` — 分类、生成、审查和复盘提示。
35
35
  - `backend-test-analysis.schema.json`、`backend-test-execution.schema.json`、`backend-test-result.schema.json`、`backend-test-case-manifest.schema.json` — 分析、执行、结果与用例清单 schema。
36
36
 
@@ -466,9 +466,9 @@
466
466
  "retryCategories": {
467
467
  "type": "array",
468
468
  "items": {
469
- "enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-limit", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
469
+ "enum": ["timeout", "network", "rate-limit", "unavailable", "upstream-unavailable", "empty-output", "output-limit", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
470
470
  },
471
- "default": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-limit"],
471
+ "default": ["timeout", "network", "rate-limit", "unavailable", "upstream-unavailable", "empty-output", "output-limit"],
472
472
  "description": "Failure categories eligible for retry. quota/auth/write-guard are never eligible. invalid-output is allowed only on explicit structured/protocol policies; writer-empty-diff and incomplete-write-set are reserved for requireChangedFiles generation/completeness writers; writer-clean-timeout is reserved for standard bounded writers after explicit zero-write-tool-call and empty-diff evidence."
473
473
  }
474
474
  }