@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.1

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 (273) hide show
  1. package/CHANGELOG.md +61 -207
  2. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  3. package/dist/adapters/context-transfer/pi-session.js +61 -0
  4. package/dist/application/dag/generate-task-dag.js +19 -75
  5. package/dist/application/task-lifecycle/advance.js +5 -24
  6. package/dist/application/task-lifecycle/observe.js +17 -171
  7. package/dist/application/task-lifecycle/plan-transitions.js +7 -42
  8. package/dist/application/task-lifecycle/recommendations.js +2 -13
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +12 -7
  11. package/dist/cli/program.js +23 -6
  12. package/dist/commands/client-recovery.js +0 -3
  13. package/dist/commands/dag-artifact.js +284 -0
  14. package/dist/commands/dag-context.js +184 -0
  15. package/dist/commands/dag-rerun.js +206 -1
  16. package/dist/commands/init.js +1 -27
  17. package/dist/commands/task-advance.js +0 -19
  18. package/dist/executors/dag-pi-executor.js +266 -3792
  19. package/dist/executors/pi-executor.js +4 -15
  20. package/dist/executors/pi-sdk-executor.js +3 -0
  21. package/dist/executors/shell-executor.js +217 -945
  22. package/dist/executors/shell-write-guard.js +0 -7
  23. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  24. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  25. package/dist/infrastructure/console/context-export-store.js +160 -0
  26. package/dist/infrastructure/console/dir-lock.js +132 -0
  27. package/dist/infrastructure/console/operation-store.js +197 -0
  28. package/dist/infrastructure/harness/artifact-store.js +10 -1
  29. package/dist/infrastructure/harness/atomic-write.js +12 -2
  30. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  31. package/dist/shared/context-transfer.js +418 -0
  32. package/dist/shared/dag-failure-category.js +0 -12
  33. package/dist/shared/openspec-spec.js +4 -70
  34. package/dist/shared/operator/capabilities.js +14 -7
  35. package/dist/shared/operator/safe-run-summary.js +1 -0
  36. package/dist/shared/path-safety.js +93 -0
  37. package/dist/shared/preview.js +28 -4
  38. package/dist/task/config-types.js +9 -35
  39. package/dist/task/contract/adopt.js +0 -4
  40. package/dist/task/contract/import-revision.js +0 -4
  41. package/dist/task/contract/project.js +0 -3
  42. package/dist/task/contract/schema.js +1 -2
  43. package/dist/task/frontend-project-capability.js +20 -203
  44. package/dist/task/runtime.js +2 -5
  45. package/dist/task/source-prepare/build-draft.js +3 -3
  46. package/dist/task/source-prepare/fragment-inventory.js +9 -15
  47. package/dist/task/source-prepare/prepare.js +1 -89
  48. package/dist/task/source-prepare/semantic-intake.js +2 -6
  49. package/dist/task/source-references.js +1 -22
  50. package/dist/task/task-demand-routing.js +3 -0
  51. package/dist/worker/console/chat/chat-event-store.js +2 -2
  52. package/dist/worker/console/chat/pi-runtime.js +3 -2
  53. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  54. package/dist/worker/console/chat/routes.js +13 -4
  55. package/dist/worker/console/chat/semantic-activity.js +12 -11
  56. package/dist/worker/console/chat/session-stats.js +96 -0
  57. package/dist/worker/console/chat/session-store.js +1 -1
  58. package/dist/worker/console/chat/turn-process.js +28 -4
  59. package/dist/worker/console/chat/user-questions.js +1 -1
  60. package/dist/worker/console/console-update-runtime.js +1 -1
  61. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  62. package/dist/worker/console/dag-confirmation.js +1 -1
  63. package/dist/worker/console/dag-execution-receipt.js +1 -1
  64. package/dist/worker/console/doctor.js +1 -1
  65. package/dist/worker/console/draft-store.js +1 -1
  66. package/dist/worker/console/human-gate-token.js +1 -1
  67. package/dist/worker/console/index.js +3 -6
  68. package/dist/worker/console/interview/assessment.js +1 -1
  69. package/dist/worker/console/interview/session.js +1 -1
  70. package/dist/worker/console/operation-runner.js +45 -4
  71. package/dist/worker/console/operation-sse.js +1 -1
  72. package/dist/worker/console/operation-wait.js +1 -1
  73. package/dist/worker/console/operator-actions.js +107 -168
  74. package/dist/worker/console/operator-surface-health.js +1 -1
  75. package/dist/worker/console/operator-user-error.js +169 -0
  76. package/dist/worker/console/pi-plugins.js +60 -1
  77. package/dist/worker/console/routes.js +637 -8
  78. package/dist/worker/console/security.js +35 -0
  79. package/dist/worker/console/server.js +6 -6
  80. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
  81. package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
  82. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
  83. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
  84. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
  85. package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
  86. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
  87. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
  88. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
  89. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
  90. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
  91. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
  96. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
  97. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
  98. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
  99. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
  100. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
  101. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
  102. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
  103. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
  104. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
  105. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
  106. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
  107. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
  108. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
  109. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
  110. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
  111. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
  112. package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
  113. package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
  114. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
  115. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
  116. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
  117. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
  118. package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
  119. package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
  120. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
  121. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
  122. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
  123. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
  124. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
  125. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
  126. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
  127. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
  128. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
  129. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
  130. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
  131. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
  132. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
  133. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
  134. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
  135. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
  136. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
  137. package/dist/worker/console/static/index.html +7 -2
  138. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  139. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  140. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  141. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  142. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
  143. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  144. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  145. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  146. package/dist/worker/console/workspace-context.js +28 -112
  147. package/dist/worker/console/workspace-registry.js +2 -2
  148. package/dist/worker/continuation/worker-continuation.js +278 -0
  149. package/dist/worker/observability/read-model.js +4 -0
  150. package/dist/worker/observe/health.js +1 -1
  151. package/dist/worker/observe/node-transparency.js +572 -0
  152. package/dist/worker/observe/routes.js +51 -1
  153. package/dist/worker/observe/static/api.js +69 -5
  154. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  155. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  156. package/dist/worker/observe/static/dag-node-purpose.js +0 -5
  157. package/dist/worker/observe/static/state.js +3 -1
  158. package/dist/worker/observe/static/styles.css +1037 -126
  159. package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
  160. package/dist/worker/observe/static/views/session-timeline.js +121 -25
  161. package/dist/worker/outcomes/adapters.js +19 -0
  162. package/dist/worker/outcomes/types.js +1 -0
  163. package/dist/worker/pool/attempt-lease.js +97 -66
  164. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  165. package/dist/worker/pool/reconcile.js +46 -1
  166. package/dist/worker/pool/run-store.js +2 -0
  167. package/dist/worker/pool/state-projection.js +2 -0
  168. package/dist/worker/task-spec/workflow-routing.js +8 -3
  169. package/dist/workflows/dag/artifact-bindings.js +149 -0
  170. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  171. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  172. package/dist/workflows/dag/budget-enforcement.js +10 -0
  173. package/dist/workflows/dag/context-receipt.js +305 -0
  174. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  175. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  176. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  177. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  178. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  179. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  180. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  181. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  182. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  183. package/dist/workflows/dag/frontend-repair.js +432 -1
  184. package/dist/workflows/dag/frontend-review-context.js +15 -261
  185. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  186. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  187. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  188. package/dist/workflows/dag/init-hybrid.js +640 -685
  189. package/dist/workflows/dag/interrupt-request.js +0 -7
  190. package/dist/workflows/dag/node-execution.js +116 -759
  191. package/dist/workflows/dag/path-safety.js +1 -0
  192. package/dist/workflows/dag/prompt.js +30 -38
  193. package/dist/workflows/dag/report.js +1 -37
  194. package/dist/workflows/dag/rerun-feedback.js +1 -256
  195. package/dist/workflows/dag/rerun-plan.js +557 -11
  196. package/dist/workflows/dag/rerun-run.js +601 -38
  197. package/dist/workflows/dag/rerun-task.js +0 -29
  198. package/dist/workflows/dag/retry-policy.js +104 -214
  199. package/dist/workflows/dag/runner.js +142 -430
  200. package/dist/workflows/dag/scheduler.js +16 -114
  201. package/dist/workflows/dag/skill-snapshot.js +17 -0
  202. package/dist/workflows/dag/types.js +195 -246
  203. package/dist/workflows/dag/validate.js +12 -28
  204. package/docs/architecture/runtime-boundaries.md +6 -6
  205. package/docs/architecture/worker-and-feature.md +1 -1
  206. package/docs/init-surface.manifest.json +12 -30
  207. package/docs/skills/vetted-skill-registry.md +2 -4
  208. package/docs/templates/README.md +0 -2
  209. package/docs/templates/agent-dag-report.schema.json +2 -8
  210. package/docs/templates/agent-dag.schema.json +33 -1
  211. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  212. package/docs/templates/product-line/task.yaml +1 -1
  213. package/harness.json +3 -3
  214. package/package.json +3 -1
  215. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  216. package/skills/frontend-design-review/SKILL.md +41 -22
  217. package/skills/frontend-implementation/SKILL.md +52 -0
  218. package/skills/frontend-implementation/references/code-standards.md +33 -0
  219. package/skills/frontend-implementation/references/design-spec.md +56 -0
  220. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  221. package/skills/frontend-review/SKILL.md +15 -20
  222. package/skills/frontend-review/references/review-findings.md +7 -6
  223. package/skills/frontend-verification/SKILL.md +1 -1
  224. package/skills/loop-agent/references/command-reference.md +6 -1
  225. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  226. package/dist/commands/dag-follow-up.js +0 -138
  227. package/dist/executors/pi-read-budget-policy.js +0 -239
  228. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  229. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  230. package/dist/worker/console/operation-store.js +0 -389
  231. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  232. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  233. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  234. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  235. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  236. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  237. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  238. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  239. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  240. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  241. package/dist/workflows/dag/frontend-closeout.js +0 -221
  242. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  243. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  244. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  245. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  246. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  247. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  248. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  249. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  250. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  251. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  252. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  253. package/dist/workflows/dag/frontend-shape.js +0 -427
  254. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  255. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  256. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  257. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  258. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  259. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  260. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  261. package/dist/workflows/dag/recovery-lease.js +0 -80
  262. package/docs/templates/frontend-implementation-dag.json +0 -89
  263. package/docs/templates/spec-registry.schema.json +0 -45
  264. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  265. package/skills/frontend-contract/SKILL.md +0 -23
  266. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  267. package/skills/frontend-plan/SKILL.md +0 -26
  268. package/skills/frontend-plan/references/decision-contract.md +0 -37
  269. package/skills/frontend-plan/references/design-decisions.md +0 -17
  270. package/skills/frontend-scout/SKILL.md +0 -25
  271. package/skills/frontend-scout/references/design-evidence.md +0 -16
  272. package/skills/frontend-scout/references/scout-evidence.md +0 -23
  273. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -14,19 +14,19 @@ export const FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH = ".runtime/import-manif
14
14
  /** Staging directory prefix under `active/` for in-flight child materialization. */
15
15
  export const FRONTEND_RECOVERY_STAGING_PREFIX = ".frontend-recovery-staging-";
16
16
  export function deriveFrontendRecoveryFailureSource(result) {
17
- if (result.failureSource === "frontend-plan-pi" ||
18
- result.failureSource === "frontend-design-policy-shell" ||
19
- result.failureSource === "frontend-implement-pi" ||
20
- result.failureSource === "frontend-verify-shell") {
21
- return result.failureSource;
17
+ if (result.selectedPlanNodeId === "frontend-plan-revision-pi") {
18
+ return "frontend-plan-revision-pi";
22
19
  }
23
- return "frontend-writer-admission-shell";
20
+ if (result.selectedPlanNodeId === "frontend-plan-pi") {
21
+ return "frontend-plan-pi";
22
+ }
23
+ return "frontend-prewrite-gate-shell";
24
24
  }
25
25
  /**
26
26
  * Compute the frontend candidate-continuation reset partition:
27
- * - `frontend-plan-pi` failure → reset plan + design-review + policy/admission (+ descendants);
28
- * - `frontend-design-policy-shell` failure → reset policy/admission (+ descendants);
29
- * - admission/design-review failure → reset writer admission (+ descendants);
27
+ * - `frontend-plan-pi` failure → reset plan + design-review + prewrite (+ descendants);
28
+ * - `frontend-plan-revision-pi` failure → reset revision + final-review + prewrite (+ descendants);
29
+ * - prewrite-only materialization → reset prewrite (+ descendants), reuse verified plan/review.
30
30
  *
31
31
  * Everything upstream of the failure source is imported as verified parent facts.
32
32
  */
@@ -55,14 +55,7 @@ export function computeFrontendWriterRecoveryPlan(input) {
55
55
  });
56
56
  }
57
57
  function computeFrontendRecoveryPlanForSource(input) {
58
- // Verification failure means the implementation did not pass the frozen
59
- // verification commands / trace. Resetting only verify + its descendants
60
- // would re-run the same failing checks against the same implementation, so
61
- // reset from implement: the recovery re-implements and re-verifies.
62
- const resetRoot = input.failureSource === "frontend-verify-shell"
63
- ? "frontend-implement-pi"
64
- : input.failureSource;
65
- const resetNodeIds = computeResetClosure(input.spec, resetRoot);
58
+ const resetNodeIds = computeResetClosure(input.spec, input.failureSource);
66
59
  const resetSet = new Set(resetNodeIds);
67
60
  const importedNodeIds = input.spec.tasks
68
61
  .map((task) => task.id)
@@ -6,7 +6,7 @@ import { FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, FRONTEND_RECOVERY_INTENT_RE
6
6
  import { dagRunDirExists, getDagRunDir, readDagRunSpec, readDagRunState, transferDagRunDir, writeDagRunState, } from "./lifecycle.js";
7
7
  import { buildDagRunId } from "./runner.js";
8
8
  import { prepareRunDir, writeNodeRecord } from "./run-store.js";
9
- import { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT, readFrontendWriterAdmissionResult, } from "./scheduler.js";
9
+ import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, readFrontendPrewriteResult, } from "./scheduler.js";
10
10
  import { topoSortToRanks } from "./topo.js";
11
11
  /**
12
12
  * Candidate-continuation child creation kernel (phase 3b).
@@ -19,8 +19,8 @@ import { topoSortToRanks } from "./topo.js";
19
19
  * phase=child-running. Scheduling/execution of the child is phase 3c and out
20
20
  * of scope here.
21
21
  */
22
- /** Canonical contract artifact default location; overridden by the design
23
- * policy shell's configured outputDir/artifactName when present. */
22
+ /** Canonical contract artifact default location; overridden by the prewrite
23
+ * gate's configured outputDir/artifactName when present. */
24
24
  const FRONTEND_CANONICAL_CONTRACT_DEFAULT_REL_PATH = "contracts/frontend-implementation-contract.json";
25
25
  function resolveDeps(deps) {
26
26
  return {
@@ -62,9 +62,6 @@ function isValidFrontendRecoveryMarker(raw) {
62
62
  * Read + validate the activation marker. Missing or invalid markers return
63
63
  * `undefined` so the phase-3c gate and tests can treat an unmarked active
64
64
  * child as not executable (AC-2 / AC-5d).
65
- *
66
- * @deprecated Read-only/scheduler compatibility only; the runner no longer
67
- * uses activation markers as the authoritative recovery entry point.
68
65
  */
69
66
  export async function readFrontendRecoveryMarker(parentRunDir, requestId) {
70
67
  let raw;
@@ -82,8 +79,8 @@ export async function readFrontendRecoveryMarker(parentRunDir, requestId) {
82
79
  return raw;
83
80
  }
84
81
  function classifyFrontendFactKind(nodeId) {
85
- if (/admission|design-policy|verify|policy/i.test(nodeId))
86
- return "gate";
82
+ if (/prewrite/i.test(nodeId))
83
+ return "prewrite";
87
84
  if (/scout/i.test(nodeId))
88
85
  return "scout";
89
86
  if (/contract/i.test(nodeId))
@@ -94,9 +91,9 @@ function classifyFrontendFactKind(nodeId) {
94
91
  }
95
92
  function resolveCanonicalContractRelPath(spec) {
96
93
  for (const task of spec.tasks) {
97
- const policy = task.shell?.frontendDesignPolicy;
98
- if (policy) {
99
- return path.posix.join(policy.outputDir, policy.artifactName);
94
+ const gate = task.shell?.frontendPrewriteGate;
95
+ if (gate) {
96
+ return path.posix.join(gate.outputDir, gate.artifactName);
100
97
  }
101
98
  }
102
99
  return FRONTEND_CANONICAL_CONTRACT_DEFAULT_REL_PATH;
@@ -289,15 +286,15 @@ async function materializeChildStaging(input) {
289
286
  });
290
287
  }
291
288
  else {
292
- const admissionRead = await readFrontendWriterAdmissionResult(parentRunDir);
293
- if (!admissionRead.ok) {
294
- throw new Error(admissionRead.reason);
289
+ const prewriteRead = await readFrontendPrewriteResult(parentRunDir);
290
+ if (!prewriteRead.ok) {
291
+ throw new Error(prewriteRead.reason);
295
292
  }
296
293
  plan = computeFrontendRecoveryPlan({
297
294
  spec: parentSpec,
298
295
  parentRunId,
299
296
  requestId,
300
- result: admissionRead.result,
297
+ result: prewriteRead.result,
301
298
  });
302
299
  }
303
300
  const { ranks } = topoSortToRanks(parentSpec);
@@ -333,17 +330,17 @@ async function materializeChildStaging(input) {
333
330
  throw new Error(`node ${task.id} missing from frontend recovery plan partition`);
334
331
  }
335
332
  }
336
- // Canonical contract + writer-admission identity facts are copied alongside
333
+ // Canonical contract + prewrite-result identity facts are copied alongside
337
334
  // the per-node facts so reset nodes can re-read them (AC-4).
338
335
  const canonicalContract = await copyArtifactVerified({
339
336
  parentRunDir,
340
337
  newRunDir: stagingDir,
341
338
  relativePath: resolveCanonicalContractRelPath(parentSpec),
342
339
  });
343
- const writerAdmissionResult = await copyArtifactVerified({
340
+ const prewriteResult = await copyArtifactVerified({
344
341
  parentRunDir,
345
342
  newRunDir: stagingDir,
346
- relativePath: FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT,
343
+ relativePath: FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT,
347
344
  });
348
345
  const childState = {
349
346
  version: 1,
@@ -380,9 +377,9 @@ async function materializeChildStaging(input) {
380
377
  relativePath: canonicalContract.destinationRelativePath,
381
378
  sha256: canonicalContract.sha256,
382
379
  },
383
- writerAdmissionResult: {
384
- relativePath: writerAdmissionResult.destinationRelativePath,
385
- sha256: writerAdmissionResult.sha256,
380
+ prewriteResult: {
381
+ relativePath: prewriteResult.destinationRelativePath,
382
+ sha256: prewriteResult.sha256,
386
383
  },
387
384
  importedFacts,
388
385
  };
@@ -420,9 +417,6 @@ async function verifyThreeWayConsistency(input) {
420
417
  * intent. Single-writer: the recovery coordinator is the only caller.
421
418
  * Fail-closed on revision drift, missing intent, unreadable prewrite result,
422
419
  * hash mismatch, marker-write failure, or consistency failure.
423
- *
424
- * @deprecated The M6 operation controller is the authoritative recovery loop;
425
- * this kernel is retained only for read-only/scheduler compatibility.
426
420
  */
427
421
  export async function stageRecoveryChild(options) {
428
422
  const { cwd, parentRunId } = options;
@@ -458,15 +452,8 @@ export async function stageRecoveryChild(options) {
458
452
  }
459
453
  }
460
454
  // Derive the childRunId exactly once; replay always reuses a reserved id.
461
- // buildDagRunId is deterministic for the same task title on the same day. A
462
- // recovery child must never reuse its still-active parent directory, or the
463
- // staging path aliases the parent and the required import manifest is absent.
464
- const childRunId = recovery.childRunId ?? await (async () => {
465
- const generatedChildRunId = await (options.buildChildRunId ?? buildDagRunId)(parentSpec, cwd);
466
- return generatedChildRunId === parentRunId
467
- ? `${generatedChildRunId}-recovery-${requestId.slice(0, 8)}`
468
- : generatedChildRunId;
469
- })();
455
+ const childRunId = recovery.childRunId ??
456
+ (await (options.buildChildRunId ?? buildDagRunId)(parentSpec, cwd));
470
457
  // Orphan staging cleanup (D4.3): remove any in-flight staging dir for this id.
471
458
  const activeRootDir = getDagRunDir(cwd, "active", "");
472
459
  const stagingDir = path.join(activeRootDir, `${FRONTEND_RECOVERY_STAGING_PREFIX}${childRunId}`);
@@ -1,5 +1,10 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
1
3
  import { z } from "zod";
2
- import { FRONTEND_NO_VERIFICATION_MARKER_TEXT, } from "./frontend-verification-trace.js";
4
+ import { pathMatchesPattern } from "../../shared/git-progress.js";
5
+ import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
6
+ import { FRONTEND_NO_VERIFICATION_MARKER_TEXT, FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE, } from "./frontend-verification-trace.js";
7
+ export const FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID = "frontend-repair-assessment-v1";
3
8
  export const frontendRepairFailureClassSchema = z.enum([
4
9
  "typecheck",
5
10
  "build",
@@ -24,6 +29,55 @@ export const frontendCommandCategorySchema = z.enum([
24
29
  "unit-test",
25
30
  "trace",
26
31
  ]);
32
+ const REPAIRABLE = new Set([
33
+ "typecheck",
34
+ "build",
35
+ "lint",
36
+ "component-test",
37
+ "unit-test",
38
+ "trace",
39
+ ]);
40
+ export function isFrontendRepairable(failureClass) {
41
+ return REPAIRABLE.has(failureClass);
42
+ }
43
+ /**
44
+ * Deterministic commandLabel → commandCategory mapping (AC-3).
45
+ * `targetType` comes from the contract verificationTargets[].type
46
+ * (static | unit | component | integration | mock); the literal commandLabel
47
+ * wins over type so a `static` typecheck target is never misread as a test.
48
+ */
49
+ export function mapCommandLabelToCategory(commandLabel, targetType) {
50
+ const label = commandLabel.toLowerCase();
51
+ if (/(?:typecheck|check-types|type-check|\btsc\b)/.test(label))
52
+ return "typecheck";
53
+ if (/(?:vite\s+build|next\s+build|webpack|rollup|\bbuild\b)/.test(label))
54
+ return "build";
55
+ if (/(?:eslint|\blint\b)/.test(label))
56
+ return "lint";
57
+ if (targetType === "component")
58
+ return "component-test";
59
+ if (targetType === "unit")
60
+ return "unit-test";
61
+ if (/(?:component|testing-library|render\()/.test(label))
62
+ return "component-test";
63
+ return "unit-test";
64
+ }
65
+ export function isReviewRepairable(finding, implementWriteSet) {
66
+ const hasFile = Boolean(finding.file && finding.file.trim().length > 0);
67
+ const hasLocation = Boolean((finding.line !== undefined && finding.line > 0) ||
68
+ (finding.symbol && finding.symbol.trim().length > 0));
69
+ const hasRequirement = Boolean((finding.requirementId && finding.requirementId.trim().length > 0) ||
70
+ (finding.acceptanceCriteriaId &&
71
+ finding.acceptanceCriteriaId.trim().length > 0));
72
+ const fixScope = finding.fixScope ?? (hasFile ? [finding.file] : []);
73
+ const fixScopeSubset = fixScope.length > 0 &&
74
+ fixScope.every((entry) => isPathWithinWriteSet(entry, implementWriteSet));
75
+ return (hasFile &&
76
+ hasLocation &&
77
+ hasRequirement &&
78
+ fixScopeSubset &&
79
+ finding.requiresContractReplan !== true);
80
+ }
27
81
  export function classifyFrontendFailure(input) {
28
82
  // AC-2: structured evidence takes priority over string-blob heuristics.
29
83
  if (input.failureCategory === "write-guard") {
@@ -122,3 +176,380 @@ export function classifyFrontendFailure(input) {
122
176
  return "path";
123
177
  return "unknown";
124
178
  }
179
+ export const frontendRepairAssessmentSchema = z
180
+ .object({
181
+ schemaVersion: z.literal(1),
182
+ schemaId: z.literal(FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID),
183
+ contract: z
184
+ .object({
185
+ relativePath: z.string().min(1),
186
+ schemaId: z.string().min(1),
187
+ })
188
+ .strict(),
189
+ failedNodeIds: z.array(z.string().min(1)),
190
+ failureClass: frontendRepairFailureClassSchema,
191
+ eligible: z.boolean(),
192
+ reason: z.string().min(1),
193
+ attempt: z.number().int().min(1).max(2),
194
+ maxAttempts: z.literal(1),
195
+ allowedRepairWriteSet: z.array(z.string().min(1)),
196
+ evidenceRefs: z.array(z.string().min(1)),
197
+ browserStatus: z.literal("not-run"),
198
+ // AC-5 structured evidence fields (all optional so existing minimal
199
+ // fixtures and frontend-review-context safeParse stay compatible).
200
+ verificationTargetId: z.string().min(1).optional(),
201
+ commandLabel: z.string().min(1).optional(),
202
+ exitCode: z.number().int().optional(),
203
+ commandCategory: frontendCommandCategorySchema.optional(),
204
+ requirementIds: z.array(z.string().min(1)).optional(),
205
+ acceptanceCriteriaIds: z.array(z.string().min(1)).optional(),
206
+ fixScope: z.array(z.string().min(1)).optional(),
207
+ changedPaths: z.array(z.string().min(1)).optional(),
208
+ traceRef: z
209
+ .object({
210
+ relativePath: z.string().min(1),
211
+ schemaId: z.string().min(1),
212
+ })
213
+ .strict()
214
+ .optional(),
215
+ })
216
+ .strict();
217
+ function nodeHadFailure(record) {
218
+ if (record.status === "ERROR")
219
+ return true;
220
+ if (recordIndicatesCommandFailure(record))
221
+ return true;
222
+ const results = record.commandResults ?? [];
223
+ if (results.some((item) => item.ok === false))
224
+ return true;
225
+ return false;
226
+ }
227
+ function normalize(value) {
228
+ return value.replace(/\\/g, "/").replace(/^\.\//, "");
229
+ }
230
+ /**
231
+ * Whether a concrete path or glob entry is contained by the implement writeSet.
232
+ * Exact glob equality is the norm (repair writeSet === implement writeSet);
233
+ * concrete (non-glob) paths may also be covered by an implement glob.
234
+ */
235
+ export function isPathWithinWriteSet(entry, implementWriteSet) {
236
+ const normalizedEntry = normalize(entry);
237
+ const normalizedImplement = implementWriteSet.map(normalize);
238
+ if (normalizedImplement.includes(normalizedEntry))
239
+ return true;
240
+ if (!normalizedEntry.includes("*")) {
241
+ return implementWriteSet.some((pattern) => pathMatchesPattern(normalizedEntry, pattern));
242
+ }
243
+ return false;
244
+ }
245
+ export function assertWriteSetSubset(repairWriteSet, implementWriteSet) {
246
+ for (const entry of repairWriteSet) {
247
+ if (!isPathWithinWriteSet(entry, implementWriteSet)) {
248
+ throw new Error(`repair writeSet entry "${entry}" is not ⊆ implement writeSet`);
249
+ }
250
+ }
251
+ }
252
+ async function loadImplementWriteSet(runDir, fallback) {
253
+ if (fallback.length > 0)
254
+ return fallback;
255
+ const candidates = ["run.json", "dag.json", "spec.json"];
256
+ for (const name of candidates) {
257
+ try {
258
+ const raw = JSON.parse(await readFile(path.join(runDir, name), "utf8"));
259
+ const tasks = raw.spec?.tasks ?? raw.tasks ?? [];
260
+ const implement = tasks.find((task) => task.id === "frontend-implement-pi");
261
+ if (implement?.writeSet?.length)
262
+ return implement.writeSet;
263
+ }
264
+ catch {
265
+ // continue
266
+ }
267
+ }
268
+ return fallback;
269
+ }
270
+ function matchVerificationTarget(contract, commandLabel, command) {
271
+ for (const target of contract.verificationTargets) {
272
+ if (commandLabel && target.commandLabel === commandLabel)
273
+ return target;
274
+ if (command &&
275
+ (command.includes(target.commandLabel) ||
276
+ target.commandLabel.includes(command))) {
277
+ return target;
278
+ }
279
+ }
280
+ return undefined;
281
+ }
282
+ function deriveFailureEvidence(input) {
283
+ const evidence = {};
284
+ for (const { record } of input.failed) {
285
+ const results = record.commandResults ?? [];
286
+ const failedResult = results.find((item) => item.ok === false) ?? results[0];
287
+ if (failedResult &&
288
+ evidence.exitCode === undefined &&
289
+ typeof failedResult.exitCode === "number") {
290
+ evidence.exitCode = failedResult.exitCode;
291
+ }
292
+ if (failedResult?.commandLabel && !evidence.commandLabel) {
293
+ evidence.commandLabel = failedResult.commandLabel;
294
+ }
295
+ // Structured trace facts passed by the verification bundle / trace gate.
296
+ const failedTraceTarget = (record.traceTargets ?? []).find((target) => target.status === "failed");
297
+ if (failedTraceTarget && !evidence.verificationTargetId) {
298
+ evidence.verificationTargetId = failedTraceTarget.id;
299
+ if (!evidence.commandLabel)
300
+ evidence.commandLabel = failedTraceTarget.commandLabel;
301
+ evidence.traceTarget = failedTraceTarget;
302
+ evidence.traceRef = {
303
+ relativePath: "contracts/frontend-verification-trace.json",
304
+ schemaId: "frontend-verification-trace-v1",
305
+ };
306
+ }
307
+ if (record.changedPaths?.length && !evidence.changedPaths) {
308
+ evidence.changedPaths = [...record.changedPaths];
309
+ }
310
+ }
311
+ // Resolve commandLabel → contract verification target → category + scope.
312
+ const target = matchVerificationTarget(input.contract, evidence.commandLabel);
313
+ if (target) {
314
+ if (!evidence.verificationTargetId)
315
+ evidence.verificationTargetId = target.id;
316
+ if (!evidence.commandLabel)
317
+ evidence.commandLabel = target.commandLabel;
318
+ evidence.requirementIds ??= [...target.requirementIds];
319
+ evidence.acceptanceCriteriaIds ??= [...target.requirementIds];
320
+ evidence.fixScope ??= [target.file];
321
+ }
322
+ if (evidence.commandLabel) {
323
+ evidence.commandCategory ??= mapCommandLabelToCategory(evidence.commandLabel, target?.type);
324
+ }
325
+ return evidence;
326
+ }
327
+ export async function runFrontendFailureAssessGate(input) {
328
+ const attempt = input.attempt ?? 1;
329
+ const implementWriteSet = await loadImplementWriteSet(input.runDir, input.implementWriteSet ?? []);
330
+ if (implementWriteSet.length === 0) {
331
+ throw new Error("assess: implement writeSet unavailable");
332
+ }
333
+ const contractRel = "contracts/frontend-implementation-contract.json";
334
+ const contractPath = path.join(input.runDir, contractRel);
335
+ let contractSchemaId = FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID;
336
+ let contract;
337
+ try {
338
+ const raw = JSON.parse(await readFile(contractPath, "utf8"));
339
+ const parsed = frontendImplementationContractSchema.safeParse(raw);
340
+ if (!parsed.success) {
341
+ throw new Error("invalid frontend implementation contract");
342
+ }
343
+ contract = parsed.data;
344
+ contractSchemaId = FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID;
345
+ }
346
+ catch {
347
+ // still allow assessment of failures without contract? fail-closed to contract
348
+ throw new Error("assess: missing or invalid contracts/frontend-implementation-contract.json");
349
+ }
350
+ const candidateNodeIds = [
351
+ "frontend-verify-assess-shell",
352
+ "frontend-reverify-shell",
353
+ "frontend-static-verify-shell",
354
+ "frontend-behavior-verify-shell",
355
+ "frontend-verification-trace-shell",
356
+ "frontend-static-reverify-shell",
357
+ "frontend-behavior-reverify-shell",
358
+ "frontend-verification-retrace-shell",
359
+ ];
360
+ const failed = [
361
+ ...(input.failureFacts ?? []).filter(({ record }) => nodeHadFailure(record) || recordIndicatesCommandFailure(record)),
362
+ ];
363
+ for (const nodeId of candidateNodeIds) {
364
+ if (failed.some((item) => item.nodeId === nodeId))
365
+ continue;
366
+ const nodePath = path.join(input.runDir, `${nodeId}.json`);
367
+ try {
368
+ const record = JSON.parse(await readFile(nodePath, "utf8"));
369
+ if (nodeHadFailure(record) || recordIndicatesCommandFailure(record)) {
370
+ failed.push({ nodeId, record });
371
+ }
372
+ }
373
+ catch {
374
+ // missing optional nodes ok
375
+ }
376
+ }
377
+ if (failed.length === 0) {
378
+ const assessment = {
379
+ schemaVersion: 1,
380
+ schemaId: FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID,
381
+ contract: {
382
+ relativePath: contractRel,
383
+ schemaId: contractSchemaId,
384
+ },
385
+ failedNodeIds: [],
386
+ failureClass: "unknown",
387
+ eligible: false,
388
+ reason: "no failed frontend verify/trace nodes in current run",
389
+ attempt,
390
+ maxAttempts: 1,
391
+ allowedRepairWriteSet: [...implementWriteSet],
392
+ evidenceRefs: [contractRel],
393
+ browserStatus: "not-run",
394
+ };
395
+ await writeAssessment(input.runDir, assessment);
396
+ return assessment;
397
+ }
398
+ const primary = failed[0];
399
+ const evidence = deriveFailureEvidence({ contract, failed });
400
+ const failureClass = classifyFrontendFailure({
401
+ nodeId: primary.nodeId,
402
+ failureCategory: primary.record.failureCategory,
403
+ stdout: primary.record.stdout,
404
+ stderr: primary.record.stderr,
405
+ commandLabel: evidence.commandLabel,
406
+ commandCategory: evidence.commandCategory,
407
+ exitCode: evidence.exitCode,
408
+ traceTarget: evidence.traceTarget ?? null,
409
+ changedPaths: evidence.changedPaths,
410
+ });
411
+ // AC-5: repairable=true requires structured evidence (a resolved exitCode,
412
+ // commandCategory, or trace-bound verification target). review has its own
413
+ // stricter gate (AC-4) and is never unconditionally repairable.
414
+ const baseEligible = isFrontendRepairable(failureClass) && attempt <= 1;
415
+ let eligible;
416
+ let reason;
417
+ if (failureClass === "review") {
418
+ const finding = primary.record.reviewFinding ?? {};
419
+ eligible = attempt <= 1 && isReviewRepairable(finding, implementWriteSet);
420
+ reason = eligible
421
+ ? `repairable review finding on ${primary.nodeId}`
422
+ : `review finding lacks file/line-symbol/requirement evidence or is outside writeSet on ${primary.nodeId}`;
423
+ }
424
+ else {
425
+ const hasStructuredEvidence = evidence.exitCode !== undefined ||
426
+ evidence.commandCategory !== undefined ||
427
+ Boolean(evidence.verificationTargetId);
428
+ eligible = baseEligible && hasStructuredEvidence;
429
+ reason = eligible
430
+ ? `repairable failureClass=${failureClass} on ${primary.nodeId}`
431
+ : !baseEligible
432
+ ? `non-repairable or ineligible: failureClass=${failureClass} attempt=${attempt}`
433
+ : `missing structured evidence for failureClass=${failureClass} on ${primary.nodeId}`;
434
+ if (!eligible &&
435
+ `${primary.record.stderr ?? ""}`.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT)) {
436
+ // Surface the actionable unavailable-verification reason in the
437
+ // assessment (and the non-repairable gate error) instead of a
438
+ // generic class label.
439
+ reason = FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE;
440
+ }
441
+ }
442
+ // fixScope and changedPaths must stay inside the implement writeSet.
443
+ if (evidence.fixScope?.length) {
444
+ assertWriteSetSubset(evidence.fixScope, implementWriteSet);
445
+ }
446
+ if (evidence.changedPaths?.length) {
447
+ assertWriteSetSubset(evidence.changedPaths, implementWriteSet);
448
+ }
449
+ const assessment = {
450
+ schemaVersion: 1,
451
+ schemaId: FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID,
452
+ contract: {
453
+ relativePath: contractRel,
454
+ schemaId: contractSchemaId,
455
+ },
456
+ failedNodeIds: failed.map((item) => item.nodeId),
457
+ failureClass,
458
+ eligible,
459
+ reason,
460
+ attempt,
461
+ maxAttempts: 1,
462
+ allowedRepairWriteSet: [...implementWriteSet],
463
+ evidenceRefs: [contractRel, ...failed.map((item) => `${item.nodeId}.json`)],
464
+ browserStatus: "not-run",
465
+ ...(evidence.verificationTargetId
466
+ ? { verificationTargetId: evidence.verificationTargetId }
467
+ : {}),
468
+ ...(evidence.commandLabel ? { commandLabel: evidence.commandLabel } : {}),
469
+ ...(evidence.exitCode !== undefined ? { exitCode: evidence.exitCode } : {}),
470
+ ...(evidence.commandCategory
471
+ ? { commandCategory: evidence.commandCategory }
472
+ : {}),
473
+ ...(evidence.requirementIds?.length
474
+ ? { requirementIds: evidence.requirementIds }
475
+ : {}),
476
+ ...(evidence.acceptanceCriteriaIds?.length
477
+ ? { acceptanceCriteriaIds: evidence.acceptanceCriteriaIds }
478
+ : {}),
479
+ ...(evidence.fixScope?.length ? { fixScope: evidence.fixScope } : {}),
480
+ ...(evidence.changedPaths?.length
481
+ ? { changedPaths: evidence.changedPaths }
482
+ : {}),
483
+ ...(evidence.traceRef ? { traceRef: evidence.traceRef } : {}),
484
+ };
485
+ await writeAssessment(input.runDir, assessment);
486
+ return assessment;
487
+ }
488
+ function recordIndicatesCommandFailure(record) {
489
+ // Prefer explicit commandResults when present
490
+ if (record.commandResults?.some((item) => item.ok === false))
491
+ return true;
492
+ // Node may be FINISHED with nonZeroExitPolicy=record; look for harness markers
493
+ const stdout = record.stdout ?? "";
494
+ if (/nonZeroExitPolicy=record/.test(stdout) &&
495
+ /exitCode=[1-9]/.test(stdout)) {
496
+ return true;
497
+ }
498
+ if (record.failureCategory && record.failureCategory !== "success") {
499
+ // recorded failures may still be FINISHED
500
+ if (record.status === "FINISHED") {
501
+ return (record.failureCategory === "nonzero-exit" ||
502
+ record.failureCategory === "invalid-output" ||
503
+ record.failureCategory === "timeout");
504
+ }
505
+ }
506
+ return false;
507
+ }
508
+ async function writeAssessment(runDir, assessment) {
509
+ const relative = "contracts/frontend-repair-assessment.json";
510
+ const absolute = path.join(runDir, relative);
511
+ await mkdir(path.dirname(absolute), { recursive: true });
512
+ await writeFile(absolute, `${JSON.stringify(assessment, null, 2)}\n`, "utf8");
513
+ return absolute;
514
+ }
515
+ export async function runFrontendRepairContractGate(input) {
516
+ const relative = "contracts/frontend-repair-assessment.json";
517
+ const absolute = path.join(input.runDir, relative);
518
+ const raw = JSON.parse(await readFile(absolute, "utf8"));
519
+ const parsed = frontendRepairAssessmentSchema.safeParse(raw);
520
+ if (!parsed.success) {
521
+ throw new Error(`repair-contract: invalid assessment: ${parsed.error.issues
522
+ .map((i) => i.message)
523
+ .join("; ")}`);
524
+ }
525
+ const assessment = parsed.data;
526
+ const implementWriteSet = await loadImplementWriteSet(input.runDir, input.implementWriteSet ?? []);
527
+ assertWriteSetSubset(assessment.allowedRepairWriteSet, implementWriteSet.length
528
+ ? implementWriteSet
529
+ : assessment.allowedRepairWriteSet);
530
+ if (assessment.attempt > assessment.maxAttempts) {
531
+ throw new Error("repair-contract: attempt exceeds maxAttempts");
532
+ }
533
+ if (assessment.failedNodeIds.length === 0) {
534
+ return { ok: true, assessment };
535
+ }
536
+ if (!assessment.eligible) {
537
+ throw new Error(`repair-contract: non-repairable failure (${assessment.failureClass}): ${assessment.reason}`);
538
+ }
539
+ if (!isFrontendRepairable(assessment.failureClass) &&
540
+ assessment.failureClass !== "review") {
541
+ throw new Error(`repair-contract: failureClass ${assessment.failureClass} is not repairable`);
542
+ }
543
+ return { ok: true, assessment };
544
+ }
545
+ export function formatFrontendFailureAssessStdout(assessment) {
546
+ return JSON.stringify(assessment);
547
+ }
548
+ export function formatFrontendRepairContractStdout(assessment) {
549
+ return [
550
+ "Frontend repair contract: pass",
551
+ `eligible=${assessment.eligible}`,
552
+ `failureClass=${assessment.failureClass}`,
553
+ `writeSetSize=${assessment.allowedRepairWriteSet.length}`,
554
+ ].join("\n");
555
+ }