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

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 (277) hide show
  1. package/CHANGELOG.md +78 -206
  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 +80 -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-case-coverage-analysis.js +99 -4
  172. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  173. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  174. package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
  175. package/dist/workflows/dag/budget-enforcement.js +10 -0
  176. package/dist/workflows/dag/context-receipt.js +305 -0
  177. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  178. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  179. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  180. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  181. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  182. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  183. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  184. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  185. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  186. package/dist/workflows/dag/frontend-repair.js +432 -1
  187. package/dist/workflows/dag/frontend-review-context.js +15 -261
  188. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  189. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  190. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  191. package/dist/workflows/dag/init-hybrid.js +740 -718
  192. package/dist/workflows/dag/interrupt-request.js +0 -7
  193. package/dist/workflows/dag/node-execution.js +136 -759
  194. package/dist/workflows/dag/path-safety.js +1 -0
  195. package/dist/workflows/dag/prompt.js +30 -38
  196. package/dist/workflows/dag/report.js +1 -37
  197. package/dist/workflows/dag/rerun-feedback.js +1 -256
  198. package/dist/workflows/dag/rerun-plan.js +557 -11
  199. package/dist/workflows/dag/rerun-run.js +601 -38
  200. package/dist/workflows/dag/rerun-task.js +0 -29
  201. package/dist/workflows/dag/retry-policy.js +109 -214
  202. package/dist/workflows/dag/runner.js +142 -430
  203. package/dist/workflows/dag/scheduler.js +16 -114
  204. package/dist/workflows/dag/skill-snapshot.js +17 -0
  205. package/dist/workflows/dag/types.js +195 -246
  206. package/dist/workflows/dag/validate.js +12 -28
  207. package/docs/architecture/runtime-boundaries.md +6 -6
  208. package/docs/architecture/worker-and-feature.md +1 -1
  209. package/docs/init-surface.manifest.json +12 -30
  210. package/docs/skills/vetted-skill-registry.md +2 -4
  211. package/docs/templates/README.md +0 -2
  212. package/docs/templates/agent-dag-report.schema.json +2 -8
  213. package/docs/templates/agent-dag.schema.json +33 -1
  214. package/docs/templates/backend-test-dag.json +18 -18
  215. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  216. package/docs/templates/product-line/task.yaml +1 -1
  217. package/harness.json +3 -3
  218. package/package.json +3 -1
  219. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  220. package/skills/frontend-design-review/SKILL.md +41 -22
  221. package/skills/frontend-implementation/SKILL.md +52 -0
  222. package/skills/frontend-implementation/references/code-standards.md +33 -0
  223. package/skills/frontend-implementation/references/design-spec.md +56 -0
  224. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  225. package/skills/frontend-review/SKILL.md +15 -20
  226. package/skills/frontend-review/references/review-findings.md +7 -6
  227. package/skills/frontend-verification/SKILL.md +1 -1
  228. package/skills/loop-agent/references/command-reference.md +6 -1
  229. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  230. package/dist/commands/dag-follow-up.js +0 -138
  231. package/dist/executors/pi-read-budget-policy.js +0 -239
  232. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  233. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  234. package/dist/worker/console/operation-store.js +0 -389
  235. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  236. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  237. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  238. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  239. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  240. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  241. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  242. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  243. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  244. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  245. package/dist/workflows/dag/frontend-closeout.js +0 -221
  246. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  247. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  248. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  249. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  250. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  251. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  252. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  253. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  254. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  255. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  256. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  257. package/dist/workflows/dag/frontend-shape.js +0 -427
  258. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  259. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  260. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  261. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  262. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  263. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  264. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  265. package/dist/workflows/dag/recovery-lease.js +0 -80
  266. package/docs/templates/frontend-implementation-dag.json +0 -89
  267. package/docs/templates/spec-registry.schema.json +0 -45
  268. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  269. package/skills/frontend-contract/SKILL.md +0 -23
  270. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  271. package/skills/frontend-plan/SKILL.md +0 -26
  272. package/skills/frontend-plan/references/decision-contract.md +0 -37
  273. package/skills/frontend-plan/references/design-decisions.md +0 -17
  274. package/skills/frontend-scout/SKILL.md +0 -25
  275. package/skills/frontend-scout/references/design-evidence.md +0 -16
  276. package/skills/frontend-scout/references/scout-evidence.md +0 -23
  277. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -0,0 +1,149 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { canonicalSha256 } from "../../shared/context-transfer.js";
5
+ export function buildArtifactBindingGraph(spec) {
6
+ const consumers = new Map();
7
+ for (const task of spec.tasks) {
8
+ for (const binding of task.consumesArtifacts ?? []) {
9
+ const nodeIds = consumers.get(binding.artifactId) ?? [];
10
+ nodeIds.push(task.id);
11
+ consumers.set(binding.artifactId, nodeIds);
12
+ }
13
+ }
14
+ const artifacts = spec.tasks
15
+ .flatMap((task) => (task.producesArtifacts ?? []).map((artifact) => ({
16
+ ...artifact,
17
+ producerNodeId: task.id,
18
+ directConsumerNodeIds: [...(consumers.get(artifact.artifactId) ?? [])].sort(),
19
+ })))
20
+ .sort((a, b) => a.artifactId.localeCompare(b.artifactId));
21
+ const available = artifacts.length > 0;
22
+ const payload = { schemaVersion: 1, artifacts };
23
+ return {
24
+ ...payload,
25
+ available,
26
+ ...(available ? {} : { reasonCode: "ARTIFACT_BINDING_UNAVAILABLE" }),
27
+ artifactGraphSha256: canonicalSha256(payload),
28
+ };
29
+ }
30
+ function collectDescendants(spec, roots) {
31
+ const children = new Map();
32
+ for (const task of spec.tasks) {
33
+ for (const parent of task.depends_on) {
34
+ const values = children.get(parent) ?? [];
35
+ values.push(task.id);
36
+ children.set(parent, values);
37
+ }
38
+ }
39
+ const queue = [...roots];
40
+ const seen = new Set();
41
+ while (queue.length > 0) {
42
+ const nodeId = queue.shift();
43
+ if (seen.has(nodeId))
44
+ continue;
45
+ seen.add(nodeId);
46
+ queue.push(...(children.get(nodeId) ?? []));
47
+ }
48
+ return spec.tasks.map((task) => task.id).filter((nodeId) => seen.has(nodeId));
49
+ }
50
+ export function computeArtifactImpact(spec, artifactId) {
51
+ const graph = buildArtifactBindingGraph(spec);
52
+ const artifact = graph.artifacts.find((entry) => entry.artifactId === artifactId);
53
+ if (!artifact || artifact.directConsumerNodeIds.length === 0)
54
+ return undefined;
55
+ const affectedNodeIds = collectDescendants(spec, artifact.directConsumerNodeIds);
56
+ const affectedSet = new Set(affectedNodeIds);
57
+ const importedNodeIds = spec.tasks
58
+ .map((task) => task.id)
59
+ .filter((nodeId) => !affectedSet.has(nodeId));
60
+ return {
61
+ artifactId,
62
+ producerNodeId: artifact.producerNodeId,
63
+ directConsumerNodeIds: artifact.directConsumerNodeIds,
64
+ effectiveFromNodeId: affectedNodeIds[0],
65
+ affectedNodeIds,
66
+ importedNodeIds,
67
+ };
68
+ }
69
+ function runtimeArtifactFields(node, kind) {
70
+ if (kind === "structured") {
71
+ return {
72
+ path: node.structuredArtifactPath,
73
+ sha256: node.structuredArtifactSha256,
74
+ schemaId: node.structuredArtifactSchemaId,
75
+ };
76
+ }
77
+ if (kind === "assistant") {
78
+ return { path: node.assistantArtifactPath, sha256: node.assistantSha256 };
79
+ }
80
+ return { path: node.stdoutArtifactPath, sha256: node.stdoutSha256 };
81
+ }
82
+ export async function materializeDeclaredArtifactFacts(input) {
83
+ const facts = [];
84
+ for (const declaration of input.task.producesArtifacts ?? []) {
85
+ const runtime = runtimeArtifactFields(input.node, declaration.kind);
86
+ const base = { ...declaration, revisionEligible: false };
87
+ if (!runtime.path || !runtime.sha256) {
88
+ facts.push({
89
+ ...base,
90
+ status: "missing",
91
+ reasonCode: "ARTIFACT_NOT_MATERIALIZED",
92
+ });
93
+ continue;
94
+ }
95
+ const relativePath = path
96
+ .relative(input.runDir, runtime.path)
97
+ .split(path.sep)
98
+ .join("/");
99
+ if (relativePath !== declaration.path) {
100
+ facts.push({
101
+ ...base,
102
+ status: "mismatch",
103
+ sha256: runtime.sha256,
104
+ reasonCode: "ARTIFACT_PATH_MISMATCH",
105
+ });
106
+ continue;
107
+ }
108
+ if (declaration.schemaId !== runtime.schemaId) {
109
+ facts.push({
110
+ ...base,
111
+ status: "mismatch",
112
+ sha256: runtime.sha256,
113
+ reasonCode: "ARTIFACT_SCHEMA_MISMATCH",
114
+ });
115
+ continue;
116
+ }
117
+ let bytes;
118
+ try {
119
+ bytes = await readFile(runtime.path);
120
+ }
121
+ catch {
122
+ facts.push({
123
+ ...base,
124
+ status: "missing",
125
+ reasonCode: "ARTIFACT_NOT_MATERIALIZED",
126
+ });
127
+ continue;
128
+ }
129
+ const actualSha256 = createHash("sha256").update(bytes).digest("hex");
130
+ if (actualSha256 !== runtime.sha256) {
131
+ facts.push({
132
+ ...base,
133
+ status: "mismatch",
134
+ sha256: actualSha256,
135
+ bytes: bytes.byteLength,
136
+ reasonCode: "ARTIFACT_HASH_MISMATCH",
137
+ });
138
+ continue;
139
+ }
140
+ facts.push({
141
+ ...base,
142
+ status: "materialized",
143
+ sha256: actualSha256,
144
+ bytes: bytes.byteLength,
145
+ revisionEligible: declaration.revisionPolicy === "replace-file",
146
+ });
147
+ }
148
+ return facts;
149
+ }
@@ -0,0 +1,31 @@
1
+ import { backendPytestCollectionFactsSchema, backendTestExecutionReadinessSchema } from "./backend-test-pytest-collection.js";
2
+ import { backendTestCaseManifestSchema } from "./backend-test-case-manifest.js";
3
+ import { backendTestAnalysisContractSchema } from "./backend-test-analysis-contract.js";
4
+ import { backendTestExecutionContractSchema } from "./backend-test-execution-contract.js";
5
+ import { backendTestCaseCoverageFactsSchema } from "./backend-test-case-coverage-analysis.js";
6
+ import { frontendImplementationContractSchema } from "./frontend-implementation-contract.js";
7
+ /**
8
+ * Standalone artifact revision schema registry (exec-plan
9
+ * 2026-08-25-console-node-context-artifact-rerun W4.3 / M4 exit criteria).
10
+ *
11
+ * Maps a frozen produced-artifact `schemaId` to a deterministic zod validator
12
+ * that can check candidate revision bytes without run context. Declared
13
+ * schemaIds without an entry here are rejected fail-closed with
14
+ * ARTIFACT_REVISION_SCHEMA_INVALID — an artifact cannot be staged until its
15
+ * schema is registered. Never guessed, never silently passed.
16
+ */
17
+ const ARTIFACT_REVISION_SCHEMAS = Object.freeze({
18
+ "backend-test-pytest-collection-v3": backendPytestCollectionFactsSchema,
19
+ "backend-test-execution-readiness-v2": backendTestExecutionReadinessSchema,
20
+ "backend-test-case-manifest-v1": backendTestCaseManifestSchema,
21
+ "backend-test-analysis-v2": backendTestAnalysisContractSchema,
22
+ "backend-test-execution-v1": backendTestExecutionContractSchema,
23
+ "backend-test-case-coverage-facts-v4": backendTestCaseCoverageFactsSchema,
24
+ "frontend-implementation-contract-v1": frontendImplementationContractSchema,
25
+ });
26
+ export function resolveArtifactRevisionSchema(schemaId) {
27
+ return ARTIFACT_REVISION_SCHEMAS[schemaId];
28
+ }
29
+ export function listArtifactRevisionSchemaIds() {
30
+ return Object.keys(ARTIFACT_REVISION_SCHEMAS).sort();
31
+ }
@@ -668,6 +668,92 @@ function automationLabelBody(body, labels) {
668
668
  const match = new RegExp(`^\\s*[-*+]\\s*(?:${escaped})\\s*[::]\\s*(.*)$`, "mi").exec(automation);
669
669
  return match?.[1]?.replaceAll("`", "").trim() ?? "";
670
670
  }
671
+ const BINDING_NEGATION_OR_AMBIGUITY = /(?:不是|并非|非变体|非断言|非横切|可能|待定|或者|\b(?:TBD|unknown)\b)/i;
672
+ function explicitBindingModeFromLine(line) {
673
+ if (BINDING_NEGATION_OR_AMBIGUITY.test(line))
674
+ return undefined;
675
+ const modes = TEST_POINT_BINDING_MODES.filter((mode) => AUTOMATION_BINDING_LABELS[mode].some((label) => line.includes(label)));
676
+ return modes.length === 1 ? modes[0] : undefined;
677
+ }
678
+ /**
679
+ * Canonicalize only explicit, unambiguous Test Point classification marks.
680
+ * The model occasionally writes `TP-X(变体测试点)`, `[场景断言测试点] TP-X`
681
+ * or a pipe-delimited equivalent. Those forms carry deterministic semantics,
682
+ * but are not the machine contract consumed by automationLabelBody(). Keep the
683
+ * original prose and materialize the same binding under `### 自动化映射` as the
684
+ * canonical single-line labels. Never infer a mode from ordinary prose.
685
+ */
686
+ export function canonicalizeBackendTestMarkdownTestPointBindings(markdown) {
687
+ const headings = [...markdown.matchAll(CASE_HEADING)];
688
+ let output = "";
689
+ let cursor = 0;
690
+ const unsafeTestPoints = [];
691
+ const canonicalizedTestPoints = [];
692
+ for (let index = 0; index < headings.length; index += 1) {
693
+ const heading = headings[index];
694
+ const start = heading.index;
695
+ const end = headings[index + 1]?.index ?? markdown.length;
696
+ const body = markdown.slice(start, end);
697
+ output += markdown.slice(cursor, start);
698
+ cursor = end;
699
+ const declared = listTokens(body, CASE_SECTION_ALIASES.testPoints, TEST_POINT);
700
+ const current = testPointBindingFacts(body, declared);
701
+ const currentByMode = new Map(TEST_POINT_BINDING_MODES.map((mode) => [
702
+ mode,
703
+ current.testPointBindings.filter((item) => item.mode === mode).map((item) => item.testPoint),
704
+ ]));
705
+ const unresolved = new Set(current.unclassifiedTestPoints);
706
+ for (const line of body.split(/\r?\n/)) {
707
+ const mode = explicitBindingModeFromLine(line);
708
+ if (!mode)
709
+ continue;
710
+ const tokens = orderedUnique(line.match(/\bTP-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/g) ?? []);
711
+ for (const token of tokens) {
712
+ if (!unresolved.has(token))
713
+ continue;
714
+ const existingModes = TEST_POINT_BINDING_MODES.filter((candidate) => (currentByMode.get(candidate) ?? []).includes(token));
715
+ if (existingModes.length > 0)
716
+ continue;
717
+ currentByMode.set(mode, orderedUnique([...(currentByMode.get(mode) ?? []), token]));
718
+ unresolved.delete(token);
719
+ canonicalizedTestPoints.push(token);
720
+ }
721
+ }
722
+ unsafeTestPoints.push(...unresolved);
723
+ if (unresolved.size === current.unclassifiedTestPoints.length) {
724
+ output += body;
725
+ continue;
726
+ }
727
+ const automationMarker = /^###\s+(?:自动化映射|Automation Mapping)\s*$/mi.exec(body);
728
+ if (!automationMarker) {
729
+ output += body;
730
+ continue;
731
+ }
732
+ const automationStart = automationMarker.index + automationMarker[0].length;
733
+ const rest = body.slice(automationStart);
734
+ const nextSection = /^###\s+/m.exec(rest);
735
+ const automationEnd = automationStart + (nextSection?.index ?? rest.length);
736
+ let automation = body.slice(automationStart, automationEnd);
737
+ for (const mode of TEST_POINT_BINDING_MODES) {
738
+ const labels = AUTOMATION_BINDING_LABELS[mode];
739
+ const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
740
+ const canonicalLine = `- ${AUTOMATION_BINDING_LABELS[mode][0]}:${(currentByMode.get(mode) ?? []).join("; ") || "无"}`;
741
+ const linePattern = new RegExp(`^\\s*[-*+]\\s*(?:${escaped})\\s*[::].*$`, "mi");
742
+ if (linePattern.test(automation))
743
+ automation = automation.replace(linePattern, canonicalLine);
744
+ else
745
+ automation = `${automation.replace(/\s*$/, "")}\n${canonicalLine}\n`;
746
+ }
747
+ output += body.slice(0, automationStart) + automation + body.slice(automationEnd);
748
+ }
749
+ output += markdown.slice(cursor);
750
+ return {
751
+ markdown: output,
752
+ changed: output !== markdown,
753
+ unsafeTestPoints: orderedUnique(unsafeTestPoints),
754
+ canonicalizedTestPoints: orderedUnique(canonicalizedTestPoints),
755
+ };
756
+ }
671
757
  export function inspectBackendTestMarkdownTestPointBindings(markdown) {
672
758
  const headings = [...markdown.matchAll(CASE_HEADING)];
673
759
  return headings.map((heading, index) => {
@@ -1312,8 +1398,16 @@ export async function analyzeBackendTestCaseCoverage(input) {
1312
1398
  function normalizeModuleStem(markdownPath) {
1313
1399
  return path.posix.basename(markdownPath).replace(/\.md$/i, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
1314
1400
  }
1315
- function expectedScript(markdownPath, layout) {
1401
+ function expectedScript(markdownPath, layout, declaredScripts = []) {
1316
1402
  const resolved = layout ?? resolveBackendTestLayout(undefined);
1403
+ if (declaredScripts.length === 1) {
1404
+ const candidate = declaredScripts[0].replaceAll("\\", "/").replace(/^\.\//, "");
1405
+ const prefix = resolved.scriptDir === "." ? "" : `${resolved.scriptDir}/`;
1406
+ const relative = prefix && candidate.startsWith(prefix) ? candidate.slice(prefix.length) : candidate;
1407
+ if ((!prefix || candidate.startsWith(prefix)) && !relative.includes("..") && /^test_[A-Za-z0-9_.-]+\.py$/.test(path.posix.basename(candidate))) {
1408
+ return candidate;
1409
+ }
1410
+ }
1317
1411
  return `${resolved.scriptDir}/test_${normalizeModuleStem(markdownPath)}.py`;
1318
1412
  }
1319
1413
  function symbolCaseId(symbol) {
@@ -1863,7 +1957,7 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
1863
1957
  const layout = input.layout ?? resolveBackendTestLayout(undefined);
1864
1958
  const cases = await parseMarkdownCases(input.workspaceRoot, layout);
1865
1959
  const modulePaths = orderedUnique(cases.map((item) => item.markdownPath));
1866
- const scripts = orderedUnique(cases.flatMap((item) => [...item.declaredScripts, expectedScript(item.markdownPath, layout)]));
1960
+ const scripts = orderedUnique(cases.flatMap((item) => [expectedScript(item.markdownPath, layout, item.declaredScripts)]));
1867
1961
  const allSymbols = [];
1868
1962
  const scriptFiles = [];
1869
1963
  for (const script of scripts) {
@@ -1876,7 +1970,7 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
1876
1970
  const findings = [];
1877
1971
  const entries = [];
1878
1972
  for (const testCase of cases) {
1879
- const expected = expectedScript(testCase.markdownPath, layout);
1973
+ const expected = expectedScript(testCase.markdownPath, layout, testCase.declaredScripts);
1880
1974
  const declared = testCase.declaredScripts[0];
1881
1975
  const refs = allSymbols.filter((item) => item.caseIds.includes(testCase.caseId));
1882
1976
  const actualScripts = orderedUnique(refs.map((item) => item.script));
@@ -2034,8 +2128,9 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
2034
2128
  for (const [signature, owners] of duplicateSignatureGroups)
2035
2129
  findings.push(`duplicate execution signature ${signature}: ${orderedUnique(owners).join(", ")}`);
2036
2130
  const exactModuleCount = modulePaths.filter((modulePath) => {
2037
- const expected = expectedScript(modulePath, layout);
2038
2131
  const moduleCases = cases.filter((item) => item.markdownPath === modulePath);
2132
+ const moduleDeclaredScripts = orderedUnique(moduleCases.flatMap((item) => item.declaredScripts));
2133
+ const expected = expectedScript(modulePath, layout, moduleDeclaredScripts);
2039
2134
  return moduleCases.every((item) => item.declaredScripts.length === 1 && item.declaredScripts[0] === expected) && scriptFiles.some((file) => path.relative(input.workspaceRoot, file).replaceAll(path.sep, "/") === expected);
2040
2135
  }).length;
2041
2136
  const caseEntries = entries.filter((entry) => entry.cardinality !== "0:1");
@@ -285,6 +285,7 @@ export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
285
285
  moduleIndexHeadingCount,
286
286
  declaredStems: [],
287
287
  declaredMdFiles: [],
288
+ declaredScriptByMarkdown: {},
288
289
  declaredMissing: [],
289
290
  unmanifested: [],
290
291
  });
@@ -306,6 +307,15 @@ export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
306
307
  return invalid("duplicate", moduleIndexHeadingCount);
307
308
  }
308
309
  const declaredStems = extractDeclaredModuleIndexFilenames(readme);
310
+ const declaredScriptByMarkdown = {};
311
+ const mdPrefix = escapeRegExp(`${resolved.markdownDir}/`);
312
+ const scriptPrefix = escapeRegExp(`${resolved.scriptDir}/`);
313
+ for (const line of readme.split(/\r?\n/)) {
314
+ const markdown = new RegExp(`(${mdPrefix}[A-Za-z0-9_.-]+\\.md)`).exec(line)?.[1];
315
+ const pytest = new RegExp(`(${scriptPrefix}[A-Za-z0-9_./-]+\\.py)`).exec(line)?.[1];
316
+ if (markdown && pytest)
317
+ declaredScriptByMarkdown[markdown] = pytest;
318
+ }
309
319
  if (declaredStems.length === 0) {
310
320
  return invalid("empty-unparsable", moduleIndexHeadingCount);
311
321
  }
@@ -345,6 +355,7 @@ export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
345
355
  moduleIndexHeadingCount,
346
356
  declaredStems,
347
357
  declaredMdFiles: declaredMdFiles.sort(),
358
+ declaredScriptByMarkdown,
348
359
  declaredMissing: declaredMissing.sort(),
349
360
  unmanifested: unmanifested.sort(),
350
361
  };
@@ -694,7 +705,8 @@ export async function validateBackendMarkdownCases(input) {
694
705
  if (!hasAssertableExpectedResult(expected)) {
695
706
  findings.push(`${testCase.id} has no structured expected result`);
696
707
  }
697
- const expectedScript = expectedBackendTestPytestScriptForMarkdownModule(relativeFile, layout);
708
+ const expectedScript = manifest.declaredScriptByMarkdown[relativeFile] ??
709
+ expectedBackendTestPytestScriptForMarkdownModule(relativeFile, layout);
698
710
  const mappedScripts = extractMappedPytestScripts(testCase.body);
699
711
  if (isEvidenceOnlyMarkdownCaseBody(testCase.body)) {
700
712
  // Evidence-only meta cases intentionally declare 脚本/primary symbol as 无 and keep only assertion/cross-cutting TPs.
@@ -934,7 +934,7 @@ export async function materializeBackendTestResultFromPytestHtml(input) {
934
934
  outcome = "command-error";
935
935
  }
936
936
  const commandSummary = input.commandSummary ??
937
- `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python -m pytest -v -p no:cacheprovider --html=${htmlRelativePath} --self-contained-html`;
937
+ `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 python reports/backend-test-pytest-launcher.py reports/backend-test-pytest-targets.txt ${htmlRelativePath} -> pytest.main(-v -p no:cacheprovider --self-contained-html)`;
938
938
  assertNoSecrets("commandSummary", commandSummary);
939
939
  const result = backendTestResultContractSchema.parse({
940
940
  schemaVersion: 1,
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { z } from "zod";
5
5
  import { BACKEND_TEST_MAX_MODULE_COUNT, isOpaqueHashBackendTestModuleStem, isPriorityOnlyBackendTestModuleStem, } from "./backend-test-module-stem.js";
6
6
  import { resolveBackendTestLayout, } from "./backend-test-layout.js";
7
- import { inspectBackendTestMarkdownTestPointBindings } from "./backend-test-case-coverage-analysis.js";
7
+ import { canonicalizeBackendTestMarkdownTestPointBindings, inspectBackendTestMarkdownTestPointBindings, } from "./backend-test-case-coverage-analysis.js";
8
8
  /**
9
9
  * Maps a backend-test generation writer task id to its writer-progress role.
10
10
  * Returns undefined for nodes that are not completeness-gated generators.
@@ -47,8 +47,16 @@ const progressSchema = z
47
47
  path: z.string().optional(),
48
48
  detail: z.string(),
49
49
  recoverable: z.boolean(),
50
+ failureSubreason: z.literal("markdown-test-point-binding-format").optional(),
51
+ affectedCases: z.array(z.string()).optional(),
52
+ testPointIds: z.array(z.string()).optional(),
53
+ canonicalizationEligible: z.boolean().optional(),
54
+ canonicalizationOutcome: z.enum(["not-attempted", "changed", "already-canonical", "unsafe", "revalidation-failed"]).optional(),
50
55
  })
51
56
  .strict()),
57
+ canonicalizationOutcome: z.enum(["not-attempted", "changed", "already-canonical", "unsafe", "revalidation-failed"]).optional(),
58
+ canonicalizedTestPointCount: z.number().int().min(0).optional(),
59
+ lightweightRepairEligible: z.boolean().optional(),
52
60
  })
53
61
  .strict();
54
62
  async function exists(filePath) {
@@ -520,6 +528,9 @@ export async function assessBackendTestShardChildCompleteness(input) {
520
528
  const actualPaths = [];
521
529
  const missingPaths = [];
522
530
  const brokenPaths = [];
531
+ let canonicalizationOutcome = role === "md-generate" ? "already-canonical" : undefined;
532
+ let canonicalizedTestPointCount = 0;
533
+ let lightweightRepairEligible = false;
523
534
  for (const target of targetPaths) {
524
535
  // Rendered map-child writeSet entries are concrete (e.g.
525
536
  // testcase/md/health.md / testcase/test_orders.py); glob-only entries are
@@ -538,16 +549,44 @@ export async function assessBackendTestShardChildCompleteness(input) {
538
549
  continue;
539
550
  }
540
551
  actualPaths.push(target);
541
- const body = await readFile(absolute, "utf8");
552
+ let body = await readFile(absolute, "utf8");
542
553
  if (role === "md-generate") {
554
+ const beforeBindings = inspectBackendTestMarkdownTestPointBindings(body);
555
+ const canonicalization = canonicalizeBackendTestMarkdownTestPointBindings(body);
556
+ if (canonicalization.changed) {
557
+ await writeFile(absolute, canonicalization.markdown, "utf8");
558
+ body = canonicalization.markdown;
559
+ canonicalizationOutcome = "changed";
560
+ canonicalizedTestPointCount += canonicalization.canonicalizedTestPoints.length;
561
+ }
562
+ const afterBindings = inspectBackendTestMarkdownTestPointBindings(body);
563
+ const unresolved = afterBindings.flatMap((entry) => entry.unclassifiedTestPoints);
564
+ const bindingCases = afterBindings
565
+ .filter((entry) => entry.unclassifiedTestPoints.length > 0 || entry.duplicateBindingTestPoints.length > 0)
566
+ .map((entry) => entry.caseId);
543
567
  const structural = moduleStructurallyComplete(body);
544
568
  if (!structural.ok) {
569
+ lightweightRepairEligible = bindingCases.length > 0;
570
+ if (bindingCases.length > 0)
571
+ canonicalizationOutcome = canonicalization.changed ? "revalidation-failed" : "unsafe";
545
572
  brokenPaths.push(target);
573
+ const bindingFormatIssue = bindingCases.length > 0;
546
574
  issues.push({
547
575
  code: "T5",
548
576
  path: target,
549
577
  detail: moduleStructuralDetail(structural),
550
578
  recoverable: true,
579
+ ...(bindingFormatIssue
580
+ ? {
581
+ failureSubreason: "markdown-test-point-binding-format",
582
+ affectedCases: orderedUnique(bindingCases),
583
+ testPointIds: orderedUnique(unresolved),
584
+ canonicalizationEligible: canonicalization.canonicalizedTestPoints.length > 0,
585
+ canonicalizationOutcome: canonicalization.changed
586
+ ? (unresolved.length === 0 ? "changed" : "revalidation-failed")
587
+ : (beforeBindings.some((entry) => entry.unclassifiedTestPoints.length > 0) ? "unsafe" : "already-canonical"),
588
+ }
589
+ : {}),
551
590
  });
552
591
  }
553
592
  }
@@ -586,6 +625,9 @@ export async function assessBackendTestShardChildCompleteness(input) {
586
625
  brokenPaths: orderedUnique(brokenPaths),
587
626
  targetPaths: recoverableTargets,
588
627
  issues,
628
+ ...(role === "md-generate"
629
+ ? { canonicalizationOutcome, canonicalizedTestPointCount, lightweightRepairEligible }
630
+ : {}),
589
631
  };
590
632
  }
591
633
  export async function assessBackendTestMdWriterCompleteness(workspaceRoot, layout) {
@@ -853,7 +895,7 @@ export async function writeBackendTestWriterProgressArtifacts(input) {
853
895
  "## Issues",
854
896
  "",
855
897
  ...(input.progress.issues.length
856
- ? input.progress.issues.map((issue) => `- [${issue.code}] ${issue.path ?? "(workspace)"}: ${issue.detail}`)
898
+ ? input.progress.issues.map((issue) => `- [${issue.code}] ${issue.path ?? "(workspace)"}: ${issue.detail}${issue.failureSubreason ? `; failureSubreason=${issue.failureSubreason}; canonicalizationOutcome=${issue.canonicalizationOutcome ?? "not-attempted"}; canonicalizationEligible=${issue.canonicalizationEligible ?? false}; affectedCases=${(issue.affectedCases ?? []).join(",") || "none"}; testPointIds=${(issue.testPointIds ?? []).join(",") || "none"}` : ""}`)
857
899
  : ["- None"]),
858
900
  "",
859
901
  `<!-- sha256:${createHash("sha256").update(JSON.stringify(parsed)).digest("hex")} -->`,
@@ -8,9 +8,19 @@ export function initRunBudgetLedger(state, budget) {
8
8
  state.budgetLedger = createBudgetLedger(budget);
9
9
  }
10
10
  function wallTimeMs(state, now = Date.now()) {
11
+ const lineage = state.continuation?.budgetLineage;
12
+ if (lineage) {
13
+ return (lineage.inheritedConsumed.wallTimeMs +
14
+ Math.max(0, now - new Date(lineage.inheritedAt).getTime()));
15
+ }
11
16
  return Math.max(0, now - new Date(state.startedAt).getTime());
12
17
  }
13
18
  function repairPasses(state) {
19
+ const lineage = state.continuation?.budgetLineage;
20
+ if (lineage) {
21
+ return (lineage.inheritedConsumed.repairPasses +
22
+ Math.max(0, (state.convergence?.currentPass ?? 1) - 1));
23
+ }
14
24
  return state.convergence?.currentPass ?? 0;
15
25
  }
16
26
  export function applyHardBudgetBreach(state, breach) {