@tea-agent/loop-agent 0.39.0-next.9 → 0.39.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 (288) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +269 -69
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/dag/generate-task-dag.js +9 -0
  6. package/dist/application/task-lifecycle/advance.js +16 -0
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +16 -5
  9. package/dist/cli/program.js +16 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli/update/runtime-activity.js +1 -29
  12. package/dist/cli.js +2 -1
  13. package/dist/commands/dag-rerun.js +55 -1
  14. package/dist/commands/init-model-catalog.js +464 -0
  15. package/dist/commands/init-upgrade.js +265 -97
  16. package/dist/commands/init.js +428 -27
  17. package/dist/commands/inspect-next.js +8 -0
  18. package/dist/commands/task-advance.js +31 -0
  19. package/dist/executors/dag-pi-executor.js +124 -18
  20. package/dist/executors/pi-event-serializer.js +42 -1
  21. package/dist/executors/pi-executor.js +51 -0
  22. package/dist/executors/pi-extension-resolver.js +233 -0
  23. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  24. package/dist/executors/pi-sdk-executor.js +187 -59
  25. package/dist/executors/shell-executor.js +555 -34
  26. package/dist/executors/shell-write-guard.js +7 -0
  27. package/dist/shared/dag-failure-category.js +138 -0
  28. package/dist/shared/dag-prompt-override.js +27 -0
  29. package/dist/shared/operator/capabilities.js +181 -27
  30. package/dist/shared/playwright-cli-command-policy.js +15 -0
  31. package/dist/shared/update/console-notifier.js +100 -0
  32. package/dist/{cli → shared}/update/npm-client.js +70 -15
  33. package/dist/{cli → shared}/update/state.js +41 -13
  34. package/dist/task/config-types.js +28 -0
  35. package/dist/task/contract/apply.js +11 -1
  36. package/dist/task/contract/constants.js +3 -0
  37. package/dist/task/contract/diff.js +26 -0
  38. package/dist/task/contract/hash.js +3 -0
  39. package/dist/task/contract/observe.js +25 -4
  40. package/dist/task/contract/paths.js +2 -1
  41. package/dist/task/contract/project.js +20 -1
  42. package/dist/task/contract/schema.js +8 -1
  43. package/dist/task/contract/transaction.js +12 -0
  44. package/dist/task/source-prepare/build-draft.js +15 -0
  45. package/dist/task/source-prepare/completeness.js +188 -0
  46. package/dist/task/source-prepare/fragment-inventory.js +474 -0
  47. package/dist/task/source-prepare/index.js +3 -0
  48. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  49. package/dist/task/source-prepare/ledger-review.js +214 -0
  50. package/dist/task/source-prepare/ledger.js +545 -0
  51. package/dist/task/source-prepare/parse-intent.js +24 -4
  52. package/dist/task/source-prepare/prepare.js +262 -1
  53. package/dist/task/source-prepare/semantic-intake.js +19 -3
  54. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  55. package/dist/task/source-prepare/types.js +22 -0
  56. package/dist/worker/cli.js +18 -2
  57. package/dist/worker/console/chat/deferred-turn.js +91 -0
  58. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  59. package/dist/worker/console/chat/operation-card.js +8 -1
  60. package/dist/worker/console/chat/pi-console-config.js +81 -10
  61. package/dist/worker/console/chat/pi-runtime.js +165 -83
  62. package/dist/worker/console/chat/repo-walk.js +10 -1
  63. package/dist/worker/console/chat/routes.js +480 -55
  64. package/dist/worker/console/chat/semantic-activity.js +20 -8
  65. package/dist/worker/console/chat/session-store.js +146 -2
  66. package/dist/worker/console/chat/turn-process.js +10 -10
  67. package/dist/worker/console/console-handoff.js +82 -0
  68. package/dist/worker/console/console-update-and-init.js +115 -0
  69. package/dist/worker/console/console-update-runtime.js +84 -0
  70. package/dist/worker/console/dag-execution-receipt.js +33 -0
  71. package/dist/worker/console/draft-store.js +49 -0
  72. package/dist/worker/console/inspect-split.js +18 -0
  73. package/dist/worker/console/operation-run-facts.js +19 -3
  74. package/dist/worker/console/operation-store.js +1 -1
  75. package/dist/worker/console/operator-actions.js +320 -4
  76. package/dist/worker/console/operator-surface-health.js +1 -0
  77. package/dist/worker/console/operator-user-error.js +2 -2
  78. package/dist/worker/console/pi-readiness.js +5 -4
  79. package/dist/worker/console/prd-intake-bridge.js +13 -0
  80. package/dist/worker/console/recovery-cta.js +4 -4
  81. package/dist/worker/console/recovery-error-copy.js +4 -4
  82. package/dist/worker/console/routes.js +46 -2
  83. package/dist/worker/console/security.js +63 -9
  84. package/dist/worker/console/server.js +111 -7
  85. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
  86. package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
  87. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
  88. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
  89. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
  90. package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
  91. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
  92. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
  93. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
  94. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
  95. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
  101. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
  102. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
  103. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
  104. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
  105. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
  106. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
  107. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
  108. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
  109. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
  110. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
  111. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
  112. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
  113. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
  114. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
  115. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
  116. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
  117. package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
  118. package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
  119. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
  120. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
  121. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
  122. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
  123. package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
  124. package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
  125. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
  126. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
  127. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
  128. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
  129. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
  130. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
  131. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
  132. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
  133. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
  134. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
  135. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
  136. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
  137. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
  138. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
  139. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
  140. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
  141. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
  142. package/dist/worker/console/static/index.html +2 -2
  143. package/dist/worker/console/static-src/app/console-types.js +40 -1
  144. package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
  145. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  146. package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
  147. package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
  148. package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
  149. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  150. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
  151. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  152. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  153. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  154. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  155. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  156. package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
  157. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
  158. package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
  159. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  160. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  161. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  162. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  163. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  164. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  165. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  166. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  167. package/dist/worker/observability/init-runtime-activity.js +26 -0
  168. package/dist/worker/observability/read-model.js +22 -0
  169. package/dist/worker/observe/node-input.js +160 -3
  170. package/dist/worker/observe/node-process.js +377 -0
  171. package/dist/worker/observe/routes.js +224 -3
  172. package/dist/worker/observe/shell-handler-keys.js +34 -0
  173. package/dist/worker/observe/static/api.js +67 -1
  174. package/dist/worker/observe/static/constants.js +8 -1
  175. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  176. package/dist/worker/observe/static/format.js +60 -0
  177. package/dist/worker/observe/static/index.html +5 -0
  178. package/dist/worker/observe/static/markdown-render.js +20 -1
  179. package/dist/worker/observe/static/operator-chrome.js +4 -0
  180. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  181. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  182. package/dist/worker/observe/static/state.js +13 -2
  183. package/dist/worker/observe/static/styles.css +85 -0
  184. package/dist/worker/observe/static/views/dag-inspector.js +912 -8
  185. package/dist/worker/observe/static/views/dag.js +8 -1
  186. package/dist/worker/observe/static/views/failures.js +5 -2
  187. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  188. package/dist/worker/observe/static/views/task.js +55 -0
  189. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
  190. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  191. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  192. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  193. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  194. package/dist/workflows/dag/backend-test-layout.js +133 -0
  195. package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
  196. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  197. package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
  198. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  199. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  200. package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
  201. package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
  202. package/dist/workflows/dag/contract-output-registry.js +15 -0
  203. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  204. package/dist/workflows/dag/convergence/controller.js +141 -0
  205. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  206. package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
  207. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  208. package/dist/workflows/dag/failure-category.js +7 -116
  209. package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
  210. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  211. package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
  212. package/dist/workflows/dag/frontend-repair.js +14 -0
  213. package/dist/workflows/dag/frontend-review-context.js +92 -10
  214. package/dist/workflows/dag/frontend-risk.js +15 -2
  215. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  216. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  217. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  218. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  219. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  220. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  221. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  222. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  223. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  224. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  225. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  226. package/dist/workflows/dag/init-hybrid.js +1019 -209
  227. package/dist/workflows/dag/node-execution.js +228 -15
  228. package/dist/workflows/dag/prompt.js +60 -1
  229. package/dist/workflows/dag/rerun-feedback.js +212 -0
  230. package/dist/workflows/dag/rerun-plan.js +224 -27
  231. package/dist/workflows/dag/rerun-run.js +61 -6
  232. package/dist/workflows/dag/rerun-task.js +61 -2
  233. package/dist/workflows/dag/retry-policy.js +109 -0
  234. package/dist/workflows/dag/structured-output-repair.js +712 -0
  235. package/dist/workflows/dag/types.js +185 -12
  236. package/dist/workflows/dag/validate.js +64 -20
  237. package/docs/README.md +1 -0
  238. package/docs/architecture/evolution.md +45 -0
  239. package/docs/init-surface.manifest.json +6 -0
  240. package/docs/operations/README.md +1 -1
  241. package/docs/operations/local-development-environment.md +1 -5
  242. package/docs/skills/vetted-skill-registry.md +14 -0
  243. package/docs/templates/README.md +2 -2
  244. package/docs/templates/agent-dag.schema.json +44 -0
  245. package/docs/templates/backend-test-dag.json +60 -35
  246. package/docs/templates/frontend-implementation-contract.schema.json +681 -24
  247. package/docs/templates/frontend-test-case-checklist.md +2 -2
  248. package/docs/templates/frontend-test-dag.json +8 -10
  249. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  250. package/docs/templates/init-managed-agents.md +13 -3
  251. package/harness.json +6 -4
  252. package/package.json +12 -4
  253. package/scripts/next-info.mjs +356 -0
  254. package/scripts/next-publish-gate.mjs +238 -0
  255. package/scripts/release-source-binding.mjs +251 -0
  256. package/skills/analyze-product-requirements/SKILL.md +8 -5
  257. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  258. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  259. package/skills/analyze-product-requirements/references/example.md +50 -0
  260. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  261. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  262. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  263. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  264. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  265. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  266. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  267. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  268. package/skills/codebase-scout/SKILL.md +1 -1
  269. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  270. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  271. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  272. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  273. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  274. package/skills/improve-codebase-architecture/deepening.md +37 -0
  275. package/skills/improve-codebase-architecture/html-report.md +123 -0
  276. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  277. package/skills/improve-codebase-architecture/language.md +53 -0
  278. package/skills/loop-agent/references/command-reference.md +11 -4
  279. package/skills/loop-agent/references/hybrid-dag.md +7 -0
  280. package/skills/playwright-cli/SKILL.md +23 -1
  281. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  282. package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
  283. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
  284. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
  285. package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
  286. package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
  287. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
  288. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
@@ -1,13 +1,18 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { readFile, stat } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { z } from "zod";
5
+ import { isDagSourceBindingV2 } from "./types.js";
4
6
  import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
5
7
  import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-store.js";
6
8
  import { countExcludedGovernanceSpecFiles, resolveOpenspecGovernanceRoot, } from "../../task/frontend-project-capability.js";
7
- import { analyzeFrontendImplementationContract, writeFrontendImplementationContractArtifact, writeDeterministicJsonArtifact, assertFrontendSourceBindingFresh, deterministicSha256, sha256Hex, serializeDeterministicJson, applyFrontendImplementationContractPatch, extractFrontendImplementationJson, FrontendContractFailure, frontendNormalizationActionSchema, } from "./frontend-implementation-contract.js";
9
+ import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
10
+ import { parseLedgerJson, validateRequirementLedger, } from "../../task/source-prepare/ledger.js";
11
+ import { analyzeFrontendImplementationContract, writeFrontendImplementationContractArtifact, writeDeterministicJsonArtifact, assertFrontendSourceBindingFresh, deterministicSha256, sha256Hex, serializeDeterministicJson, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, FrontendContractFailure, frontendNormalizationActionSchema, } from "./frontend-implementation-contract.js";
8
12
  import { renderFrontendPlanMarkdown } from "./frontend-plan-render.js";
9
13
  import { captureFrontendWorktreeBaseline } from "./frontend-worktree-diff.js";
10
14
  import { pathMatchesPattern } from "../../shared/git-progress.js";
15
+ import { normalizeVerdictCandidateLine } from "./dynamic-runtime/shared.js";
11
16
  export const FRONTEND_PREWRITE_RESULT_SCHEMA_ID = "frontend-prewrite-result-v1";
12
17
  export const FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME = "frontend-prewrite-result.json";
13
18
  /** Canonical contract serialization must stay at or below this byte size. */
@@ -39,6 +44,15 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
39
44
  "component-spec-not-cited",
40
45
  "component-choices-missing",
41
46
  "contract-too-large",
47
+ "revision-patch-unmergeable",
48
+ // Source fidelity ledger 类目(SOURCE_FIDELITY_*/source-* 稳定错误码)。
49
+ "source-fidelity-ledger-missing",
50
+ "source-fidelity-ledger-stale",
51
+ "source-fidelity-unresolved",
52
+ "source-fidelity-conflict",
53
+ "source-fidelity-high-risk-uncovered",
54
+ "source-fidelity-requirement-not-covered",
55
+ "source-fidelity-source-ref-invalid",
42
56
  ]);
43
57
  export const frontendPrewriteResultV1Schema = z
44
58
  .object({
@@ -155,32 +169,80 @@ async function selectNode(runDir, primary, fallbacks) {
155
169
  }
156
170
  throw new Error(`frontend prewrite gate no FINISHED candidate: ${rejections.join("; ")}`);
157
171
  }
158
- async function readNodeText(runDir, nodeId) {
159
- const record = JSON.parse(await readFile(path.join(runDir, `${nodeId}.json`), "utf8"));
172
+ async function readFinishedNodeRecord(runDir, nodeId) {
173
+ let record;
174
+ try {
175
+ record = JSON.parse(await readFile(path.join(runDir, `${nodeId}.json`), "utf8"));
176
+ }
177
+ catch (error) {
178
+ throw new Error(`frontend prewrite gate cannot parse node record ${nodeId}: ${error instanceof Error ? error.message : String(error)}`);
179
+ }
160
180
  if (record.status !== "FINISHED") {
161
181
  throw new Error(`frontend prewrite gate requires FINISHED node ${nodeId} (got ${String(record.status)})`);
162
182
  }
163
- const text = [record.assistantText, record.stdout]
164
- .filter((value) => Boolean(value?.trim()))
165
- .join("\n");
183
+ return record;
184
+ }
185
+ async function readNodeText(runDir, nodeId) {
186
+ const record = await readFinishedNodeRecord(runDir, nodeId);
187
+ const text = record.assistantText?.trim() || record.stdout?.trim() || "";
166
188
  if (!text)
167
189
  throw new Error(`frontend prewrite gate empty output from ${nodeId}`);
168
190
  return text;
169
191
  }
170
- async function readNodeRawText(runDir, nodeId) {
171
- const record = JSON.parse(await readFile(path.join(runDir, `${nodeId}.json`), "utf8"));
172
- return record.assistantText ?? record.stdout ?? "";
192
+ async function readCanonicalContractFromNode(runDir, nodeId) {
193
+ const record = await readFinishedNodeRecord(runDir, nodeId);
194
+ if (!record.structuredArtifactPath) {
195
+ throw new Error(`frontend prewrite gate missing structured artifact for ${nodeId}`);
196
+ }
197
+ const artifactPath = path.isAbsolute(record.structuredArtifactPath)
198
+ ? record.structuredArtifactPath
199
+ : path.join(runDir, record.structuredArtifactPath);
200
+ const resolvedRun = path.resolve(runDir);
201
+ const resolvedArtifact = path.resolve(artifactPath);
202
+ const relative = path.relative(resolvedRun, resolvedArtifact);
203
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
204
+ throw new Error(`frontend prewrite gate structured artifact escapes runDir: ${record.structuredArtifactPath}`);
205
+ }
206
+ const bytes = await readFile(resolvedArtifact);
207
+ const digest = createHash("sha256").update(bytes).digest("hex");
208
+ if (record.structuredArtifactSha256 &&
209
+ record.structuredArtifactSha256 !== digest) {
210
+ throw new Error(`frontend prewrite gate structured artifact hash mismatch for ${nodeId}`);
211
+ }
212
+ if (record.structuredArtifactSchemaId &&
213
+ record.structuredArtifactSchemaId !== FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID) {
214
+ throw new Error(`frontend prewrite gate structured artifact schema mismatch for ${nodeId}: ${record.structuredArtifactSchemaId}`);
215
+ }
216
+ const contractText = bytes.toString("utf8");
217
+ let citationsText = "";
218
+ try {
219
+ const citationsRaw = await readFile(path.join(path.dirname(resolvedArtifact), "openspec-citations.json"), "utf8");
220
+ try {
221
+ const parsed = JSON.parse(citationsRaw);
222
+ citationsText =
223
+ typeof parsed.block === "string" ? parsed.block : citationsRaw;
224
+ }
225
+ catch {
226
+ citationsText = citationsRaw;
227
+ }
228
+ }
229
+ catch (error) {
230
+ if (error.code !== "ENOENT")
231
+ throw error;
232
+ }
233
+ return { contractText, citationsText, rawSha256: digest };
173
234
  }
174
235
  function firstNonEmptyVerdictLine(text) {
175
236
  const lines = text
176
237
  .split(/\r?\n/)
177
238
  .map((line) => line.trim())
178
239
  .filter(Boolean);
179
- const normalize = (line) => {
180
- const emphasized = line.match(/^(?:`{1,3}|\*{1,3})\s*(VERDICT:[^`*]+?)\s*(?:`{1,3}|\*{1,3})$/);
181
- return (emphasized?.[1] ?? line).trim();
182
- };
183
- const normalizedLines = lines.map(normalize);
240
+ // Emphasis stripping (backtick + asterisk) is shared with the runIf
241
+ // selector and node-level output-protocol validation via
242
+ // normalizeVerdictCandidateLine. The gate keeps its own loose
243
+ // pass/request-revision normalization below (prefix match + trailing
244
+ // prose tolerance) for deterministic fail-closed behavior.
245
+ const normalizedLines = lines.map(normalizeVerdictCandidateLine);
184
246
  const first = normalizedLines[0] ?? "";
185
247
  // The prompt requires VERDICT to be the first non-empty line, but model
186
248
  // output can still prepend a summary. Keep the protocol strict in the
@@ -391,13 +453,192 @@ async function finalizePrewrite(input, pending) {
391
453
  openspecPolicy: pending.openspecPolicy,
392
454
  };
393
455
  }
456
+ /**
457
+ * Source fidelity ledger 门禁检查(AC-FIDELITY-002/003/006):绑定携带 ledger 指针时,
458
+ * 依次核验 ledger 存在、绑定 hash 一致、inputDigest 新鲜、无 unresolved/conflict/
459
+ * 高风险未覆盖、canonical requirement 全覆盖、来源引用有效且非伪造。任一失败 fail closed。
460
+ */
461
+ export async function checkFrontendSourceFidelityLedger(input) {
462
+ const { binding } = input;
463
+ // v1 绑定不携带 ledger 指针:非 frontend task / 无可解析引用 → 无 v2 校验路径。
464
+ if (!isDagSourceBindingV2(binding)) {
465
+ return { ok: true, failureCode: null, failureReason: null, ledger: null };
466
+ }
467
+ const absolute = resolveDagTaskSourcePath({
468
+ workspaceRoot: input.workspaceRoot,
469
+ taskId: binding.taskId,
470
+ sourcePath: binding.ledgerPath,
471
+ });
472
+ let raw;
473
+ try {
474
+ raw = await readFile(absolute, "utf8");
475
+ }
476
+ catch (error) {
477
+ return {
478
+ ok: false,
479
+ failureCode: "source-fidelity-ledger-missing",
480
+ failureReason: `frontend source fidelity ledger missing: ${binding.ledgerPath} (${error instanceof Error ? error.message : String(error)})`,
481
+ ledger: null,
482
+ };
483
+ }
484
+ const actualSha = createHash("sha256").update(raw).digest("hex");
485
+ if (actualSha !== binding.ledgerSha256) {
486
+ return {
487
+ ok: false,
488
+ failureCode: "source-fidelity-ledger-stale",
489
+ failureReason: `frontend source fidelity ledger stale or tampered: ${binding.ledgerPath} (bound ${binding.ledgerSha256}, observed ${actualSha})`,
490
+ ledger: null,
491
+ };
492
+ }
493
+ let ledger;
494
+ try {
495
+ ledger = parseLedgerJson(raw);
496
+ }
497
+ catch (error) {
498
+ return {
499
+ ok: false,
500
+ failureCode: "source-fidelity-ledger-stale",
501
+ failureReason: `frontend source fidelity ledger is not a valid v1 ledger: ${error instanceof Error ? error.message : String(error)}`,
502
+ ledger: null,
503
+ };
504
+ }
505
+ if (ledger.inputDigest !== binding.inputDigest) {
506
+ return {
507
+ ok: false,
508
+ failureCode: "source-fidelity-ledger-stale",
509
+ failureReason: `frontend source fidelity ledger inputDigest mismatch: bound ${binding.inputDigest}, ledger ${ledger.inputDigest}`,
510
+ ledger,
511
+ };
512
+ }
513
+ const validation = validateRequirementLedger(ledger);
514
+ const firstError = (code) => validation.find((error) => error.code === code)?.message ?? null;
515
+ if (ledger.stats.unresolvedCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_UNRESOLVED")) {
516
+ return {
517
+ ok: false,
518
+ failureCode: "source-fidelity-unresolved",
519
+ failureReason: firstError("SOURCE_FIDELITY_UNRESOLVED") ??
520
+ `frontend source fidelity ledger has ${ledger.stats.unresolvedCount} unresolved fragment(s)`,
521
+ ledger,
522
+ };
523
+ }
524
+ if (ledger.stats.conflictCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_CONFLICT")) {
525
+ return {
526
+ ok: false,
527
+ failureCode: "source-fidelity-conflict",
528
+ failureReason: firstError("SOURCE_FIDELITY_CONFLICT") ??
529
+ `frontend source fidelity ledger has ${ledger.stats.conflictCount} conflict(s)`,
530
+ ledger,
531
+ };
532
+ }
533
+ if (ledger.stats.highRiskUncoveredCount > 0 ||
534
+ validation.some((e) => e.code === "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED")) {
535
+ return {
536
+ ok: false,
537
+ failureCode: "source-fidelity-high-risk-uncovered",
538
+ failureReason: firstError("SOURCE_FIDELITY_HIGH_RISK_UNCOVERED") ??
539
+ `frontend source fidelity ledger has ${ledger.stats.highRiskUncoveredCount} high-risk uncovered fragment(s)`,
540
+ ledger,
541
+ };
542
+ }
543
+ // AC-BLOCK-001:canonical requirement 缺少 fragment/sourceRef 证据(ledger 侧
544
+ // REQUIREMENT_NOT_COVERED)与来源引用/片段无效(SOURCE_REF_INVALID /
545
+ // FRAGMENT_INVALID)均为阻断性失败,结构化失败结果不得产生可继续执行的成功。
546
+ const notCoveredError = validation.find((e) => e.code === "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED");
547
+ if (notCoveredError) {
548
+ return {
549
+ ok: false,
550
+ failureCode: "source-fidelity-requirement-not-covered",
551
+ failureReason: notCoveredError.message,
552
+ ledger,
553
+ };
554
+ }
555
+ const refInvalidError = validation.find((e) => e.code === "SOURCE_FIDELITY_SOURCE_REF_INVALID" ||
556
+ e.code === "SOURCE_FIDELITY_FRAGMENT_INVALID");
557
+ if (refInvalidError) {
558
+ return {
559
+ ok: false,
560
+ failureCode: "source-fidelity-source-ref-invalid",
561
+ failureReason: refInvalidError.message,
562
+ ledger,
563
+ };
564
+ }
565
+ const ledgerIds = new Set(ledger.canonicalRequirements.map((requirement) => requirement.id));
566
+ const contractIds = new Set(input.contractRequirements.map((r) => r.id));
567
+ const uncoveredRequirements = [...ledgerIds].filter((id) => !contractIds.has(id));
568
+ if (uncoveredRequirements.length > 0) {
569
+ return {
570
+ ok: false,
571
+ failureCode: "source-fidelity-requirement-not-covered",
572
+ failureReason: `frontend contract requirements do not cover ledger canonical requirement ids: ${uncoveredRequirements.join(", ")}`,
573
+ ledger,
574
+ };
575
+ }
576
+ const fragmentById = new Map(ledger.fragments.map((fragment) => [fragment.id, fragment]));
577
+ for (const requirement of input.contractRequirements) {
578
+ if (!ledgerIds.has(requirement.id))
579
+ continue;
580
+ const declared = requirement.sourceFragmentIds ?? [];
581
+ if (declared.length === 0) {
582
+ return {
583
+ ok: false,
584
+ failureCode: "source-fidelity-source-ref-invalid",
585
+ failureReason: `frontend contract requirement ${requirement.id} lacks sourceFragmentIds for the bound ledger`,
586
+ ledger,
587
+ };
588
+ }
589
+ const boundFragments = new Set(ledger.canonicalRequirements.find((r) => r.id === requirement.id)
590
+ ?.sourceFragmentIds ?? []);
591
+ for (const fragmentId of declared) {
592
+ if (!fragmentById.has(fragmentId)) {
593
+ return {
594
+ ok: false,
595
+ failureCode: "source-fidelity-source-ref-invalid",
596
+ failureReason: `frontend contract requirement ${requirement.id} references unknown fragment ${fragmentId}`,
597
+ ledger,
598
+ };
599
+ }
600
+ if (!boundFragments.has(fragmentId)) {
601
+ return {
602
+ ok: false,
603
+ failureCode: "source-fidelity-source-ref-invalid",
604
+ failureReason: `frontend contract requirement ${requirement.id} declares fabricated fragment reference ${fragmentId} (not bound in ledger)`,
605
+ ledger,
606
+ };
607
+ }
608
+ }
609
+ }
610
+ return { ok: true, failureCode: null, failureReason: null, ledger };
611
+ }
394
612
  export async function runFrontendPrewriteGate(input) {
395
613
  const workspaceRoot = input.workspaceRoot ?? input.repoRoot;
396
614
  // selectNode throws for a scheduling defect (no FINISHED plan/review). That
397
615
  // remains an ERROR and is intentionally not classified into prewrite-result.
398
616
  const planNodeId = await selectNode(input.runDir, input.config.planFromNodeId, input.config.planFallbackFromNodeIds);
399
617
  const reviewNodeId = await selectNode(input.runDir, input.config.reviewFromNodeId, input.config.reviewFallbackFromNodeIds);
400
- const candidateRawSha256 = sha256Hex(await readNodeRawText(input.runDir, planNodeId));
618
+ let planArtifact;
619
+ try {
620
+ planArtifact = await readCanonicalContractFromNode(input.runDir, planNodeId);
621
+ }
622
+ catch (error) {
623
+ return finalizePrewrite(input, {
624
+ planNodeId,
625
+ reviewNodeId,
626
+ verdict: "",
627
+ candidateRawSha256: sha256Hex(""),
628
+ candidateJsonSha256: null,
629
+ canonicalSha256: null,
630
+ normalizationActions: [],
631
+ mockStrategy: undefined,
632
+ artifact: undefined,
633
+ failureReason: error instanceof Error ? error.message : String(error),
634
+ failureCode: "plan-text-unreadable",
635
+ classification: "retryable-invalid",
636
+ openspecReadPaths: [],
637
+ openspecCandidatePaths: input.config.openspecCandidatePaths ?? [],
638
+ openspecPolicy: input.config.openspecPolicy ?? "scan-strict",
639
+ });
640
+ }
641
+ const candidateRawSha256 = planArtifact.rawSha256;
401
642
  const openspecPolicy = input.config.openspecPolicy ?? "scan-strict";
402
643
  const basePending = {
403
644
  planNodeId,
@@ -416,18 +657,9 @@ export async function runFrontendPrewriteGate(input) {
416
657
  openspecCandidatePaths: input.config.openspecCandidatePaths ?? [],
417
658
  openspecPolicy,
418
659
  };
419
- let planText;
420
- try {
421
- planText = await readNodeText(input.runDir, planNodeId);
422
- }
423
- catch (error) {
424
- return finalizePrewrite(input, {
425
- ...basePending,
426
- classification: "retryable-invalid",
427
- failureReason: error instanceof Error ? error.message : String(error),
428
- failureCode: "plan-text-unreadable",
429
- });
430
- }
660
+ const planText = [planArtifact.contractText, planArtifact.citationsText]
661
+ .filter(Boolean)
662
+ .join("\n");
431
663
  let reviewText;
432
664
  try {
433
665
  reviewText = await readNodeText(input.runDir, reviewNodeId);
@@ -450,59 +682,7 @@ export async function runFrontendPrewriteGate(input) {
450
682
  failureCode: "verdict-not-pass",
451
683
  });
452
684
  }
453
- // revisionPatch mode: the effective plan node emits an RFC 7386 merge-patch
454
- // delta against the original contract node (planFallbackFromNodeIds[0]).
455
- // Legacy DAGs without revisionPatch (undefined) keep full-contract semantics.
456
- const revisionPatchMode = input.config.revisionPatch === true &&
457
- planNodeId === input.config.planFromNodeId &&
458
- input.config.planFallbackFromNodeIds.length > 0;
459
- let effectiveContractText = null;
460
- if (revisionPatchMode) {
461
- const originalNodeId = input.config.planFallbackFromNodeIds[0];
462
- let originalText;
463
- try {
464
- originalText = await readNodeText(input.runDir, originalNodeId);
465
- }
466
- catch (error) {
467
- return finalizePrewrite(input, {
468
- ...basePending,
469
- verdict,
470
- classification: "retryable-invalid",
471
- failureReason: `frontend prewrite gate revisionPatch mode requires original contract node ${originalNodeId}: ${error instanceof Error ? error.message : String(error)}`,
472
- failureCode: "contract-invalid",
473
- });
474
- }
475
- let originalValue;
476
- let patchValue;
477
- try {
478
- originalValue = extractFrontendImplementationJson(originalText);
479
- patchValue = extractFrontendImplementationJson(planText);
480
- }
481
- catch (error) {
482
- return finalizePrewrite(input, {
483
- ...basePending,
484
- verdict,
485
- classification: "retryable-invalid",
486
- failureReason: `frontend prewrite gate revisionPatch mode failed to parse contract/patch: ${error instanceof Error ? error.message : String(error)}`,
487
- failureCode: "contract-invalid",
488
- });
489
- }
490
- let merged;
491
- try {
492
- merged = applyFrontendImplementationContractPatch(originalValue, patchValue);
493
- }
494
- catch (error) {
495
- return finalizePrewrite(input, {
496
- ...basePending,
497
- verdict,
498
- classification: "retryable-invalid",
499
- failureReason: `frontend prewrite gate revisionPatch apply failed: ${error instanceof Error ? error.message : String(error)}`,
500
- failureCode: "contract-invalid",
501
- });
502
- }
503
- effectiveContractText = serializeDeterministicJson(merged);
504
- }
505
- const requirementIdSearchText = effectiveContractText ?? planText;
685
+ const requirementIdSearchText = planArtifact.contractText;
506
686
  const missingIds = input.config.requiredRequirementIds.filter((id) => !requirementIdSearchText.includes(id));
507
687
  if (missingIds.length > 0) {
508
688
  return finalizePrewrite(input, {
@@ -550,14 +730,13 @@ export async function runFrontendPrewriteGate(input) {
550
730
  workspaceRoot,
551
731
  });
552
732
  }
553
- // Candidate -> canonical conversion. The gate consumes the selected
554
- // plan/revision assistant output directly and is the only conversion point.
733
+ // Candidate -> canonical conversion. The gate consumes the producing node's
734
+ // hash-bound canonical artifact and never re-merges plan/revision stdout.
555
735
  let analysis;
556
736
  try {
557
737
  analysis = await analyzeFrontendImplementationContract({
558
738
  runDir: input.runDir,
559
- fromNodeId: effectiveContractText ? undefined : planNodeId,
560
- rawContractText: effectiveContractText ?? undefined,
739
+ rawContractText: planArtifact.contractText,
561
740
  sourceBinding: input.sourceBinding,
562
741
  });
563
742
  }
@@ -577,6 +756,33 @@ export async function runFrontendPrewriteGate(input) {
577
756
  }
578
757
  throw error;
579
758
  }
759
+ // Source fidelity ledger gate (20260824-frontend-source-fidelity-ledger-v1):
760
+ // 在 verdict/missing-requirement-ids/source-binding-not-fresh 之后插入 ledger
761
+ // 检查。缺失即 fail closed 且 writer 不获授权;所有检查通过才继续物化契约。
762
+ if (input.sourceBinding) {
763
+ const ledgerCheck = await checkFrontendSourceFidelityLedger({
764
+ workspaceRoot: workspaceRoot ?? "",
765
+ binding: input.sourceBinding,
766
+ contractRequirements: analysis.canonical.requirements.map((requirement) => ({
767
+ id: requirement.id,
768
+ sourceFragmentIds: requirement.sourceFragmentIds,
769
+ sourceRefs: requirement.sourceRefs,
770
+ })),
771
+ });
772
+ if (!ledgerCheck.ok && ledgerCheck.failureCode) {
773
+ return finalizePrewrite(input, {
774
+ ...basePending,
775
+ verdict,
776
+ candidateJsonSha256: analysis.candidateJsonSha256,
777
+ normalizationActions: [],
778
+ mockStrategy: analysis.canonical.mockApi.strategy,
779
+ classification: "blocked",
780
+ failureReason: ledgerCheck.failureReason ??
781
+ "frontend source fidelity ledger gate failed",
782
+ failureCode: ledgerCheck.failureCode,
783
+ });
784
+ }
785
+ }
580
786
  // Size guard: a canonical contract above 64KB is a retryable-invalid defect
581
787
  // (compress the contract) rather than a writer authorization failure.
582
788
  const contractSizeBytes = Buffer.byteLength(serializeDeterministicJson(analysis.canonical), "utf8");
@@ -935,6 +1141,9 @@ export function formatFrontendPrewriteGateStdout(result) {
935
1141
  }
936
1142
  lines.push(`Prewrite result: ${result.prewriteResult.path}`);
937
1143
  lines.push(`Openspec policy: ${result.openspecPolicy}`);
1144
+ if (result.failureCode?.startsWith("source-fidelity-")) {
1145
+ lines.push(`Source fidelity ledger: blocked (${result.failureCode})`);
1146
+ }
938
1147
  if (result.failureReason)
939
1148
  lines.push(`Failure reason: ${result.failureReason}`);
940
1149
  if (result.openspecReadPaths.length > 0) {
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { z } from "zod";
4
4
  import { pathMatchesPattern } from "../../shared/git-progress.js";
5
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";
6
7
  export const FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID = "frontend-repair-assessment-v1";
7
8
  export const frontendRepairFailureClassSchema = z.enum([
8
9
  "typecheck",
@@ -82,6 +83,12 @@ export function classifyFrontendFailure(input) {
82
83
  if (input.failureCategory === "write-guard") {
83
84
  return "path";
84
85
  }
86
+ if (`${input.stderr ?? ""}\n${input.stdout ?? ""}`.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT)) {
87
+ // Verification unavailability is a generation-time contract gap: no
88
+ // authorized verification command exists. Bounded repair cannot fix it;
89
+ // the operator must declare commands and regenerate the DAG.
90
+ return "contract";
91
+ }
85
92
  if (input.traceTarget || input.commandCategory === "trace") {
86
93
  return "trace";
87
94
  }
@@ -424,6 +431,13 @@ export async function runFrontendFailureAssessGate(input) {
424
431
  : !baseEligible
425
432
  ? `non-repairable or ineligible: failureClass=${failureClass} attempt=${attempt}`
426
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
+ }
427
441
  }
428
442
  // fixScope and changedPaths must stay inside the implement writeSet.
429
443
  if (evidence.fixScope?.length) {
@@ -1,17 +1,39 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { readFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
4
5
  import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "./frontend-worktree-diff.js";
5
6
  import { frontendLintAssessmentArtifactSchema } from "./frontend-lint-baseline.js";
6
- import { frontendImplementationContractSchema } from "./frontend-implementation-contract.js";
7
+ import { frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
7
8
  import { frontendRepairAssessmentSchema } from "./frontend-repair.js";
9
+ import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
10
+ import { ledgerStatsLine, parseLedgerJson, } from "../../task/source-prepare/ledger.js";
8
11
  export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v1";
12
+ /**
13
+ * review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
14
+ * 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
15
+ * 匹配裸 Error 文本。message 保持人类可读(shell stderr 兼容),code 供机械化路由。
16
+ */
17
+ export const FRONTEND_REVIEW_CONTEXT_FAILURE_CODES = [
18
+ "review-context-invalid-contract",
19
+ "review-context-verification-unbound",
20
+ "review-context-ledger-drift",
21
+ "review-context-manifest-missing",
22
+ ];
23
+ export class FrontendReviewContextFailure extends Error {
24
+ code;
25
+ constructor(code, message, options) {
26
+ super(message, options);
27
+ this.name = "FrontendReviewContextFailure";
28
+ this.code = code;
29
+ }
30
+ }
9
31
  async function readRequiredJson(runDir, relativePath) {
10
32
  try {
11
33
  return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
12
34
  }
13
- catch {
14
- throw new Error(`frontend review context missing or invalid ${relativePath}`);
35
+ catch (error) {
36
+ throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
15
37
  }
16
38
  }
17
39
  async function readOptionalLintAssessment(runDir) {
@@ -32,8 +54,9 @@ async function readOptionalLintAssessment(runDir) {
32
54
  }
33
55
  function assertReviewEvidence(input) {
34
56
  const contract = frontendImplementationContractSchema.safeParse(input.contract);
35
- if (!contract.success)
36
- throw new Error("frontend review context invalid implementation contract");
57
+ if (!contract.success) {
58
+ throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
59
+ }
37
60
  const trace = input.verificationTrace;
38
61
  const expectedTargets = new Map(contract.data.verificationTargets.map((target) => [target.id, target]));
39
62
  const actualTargets = Array.isArray(trace?.targets) ? trace.targets : [];
@@ -69,17 +92,19 @@ function assertReviewEvidence(input) {
69
92
  actualIds.size !== expectedTargets.size ||
70
93
  !mockEvidenceBound ||
71
94
  trace.mockStrategy !== contract.data.mockApi.strategy) {
72
- throw new Error("frontend review context verification trace is not bound to a passing contract");
95
+ throw new FrontendReviewContextFailure("review-context-verification-unbound", "frontend review context verification trace is not bound to a passing contract");
73
96
  }
74
97
  const assessment = frontendRepairAssessmentSchema.safeParse(input.repairAssessment);
75
- if (!assessment.success)
76
- throw new Error("frontend review context invalid repair assessment");
98
+ if (!assessment.success) {
99
+ throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid repair assessment");
100
+ }
77
101
  }
78
102
  export async function runFrontendReviewContextGate(input) {
79
103
  const contract = await readRequiredJson(input.runDir, "contracts/frontend-implementation-contract.json");
80
104
  const parsedContract = frontendImplementationContractSchema.safeParse(contract);
81
- if (!parsedContract.success)
82
- throw new Error("frontend review context invalid implementation contract");
105
+ if (!parsedContract.success) {
106
+ throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
107
+ }
83
108
  const diff = await runFrontendWorktreeDiffGate({
84
109
  ...input,
85
110
  authorizedChangedPaths: parsedContract.data.targets.files,
@@ -88,6 +113,9 @@ export async function runFrontendReviewContextGate(input) {
88
113
  const repairAssessment = await readRequiredJson(input.runDir, "contracts/frontend-repair-assessment.json");
89
114
  const lintAssessment = await readOptionalLintAssessment(input.runDir);
90
115
  assertReviewEvidence({ contract, verificationTrace, repairAssessment });
116
+ // Source fidelity ledger 核对(AC-008 / AC-FIDELITY-008):ledger 缺失/漂移即阻断;
117
+ // 同时把核对清单注入 review-context 供最终 reviewer 机械化核对。
118
+ const sourceFidelity = await buildReviewSourceFidelityChecklist(input, parsedContract.data);
91
119
  const payload = {
92
120
  schemaVersion: 1,
93
121
  schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
@@ -95,6 +123,7 @@ export async function runFrontendReviewContextGate(input) {
95
123
  verificationTrace,
96
124
  repairAssessment,
97
125
  ...(lintAssessment ? { lintAssessment } : {}),
126
+ sourceFidelity,
98
127
  diff: {
99
128
  schemaId: diff.schemaId,
100
129
  patchPath: diff.patchPath,
@@ -107,6 +136,59 @@ export async function runFrontendReviewContextGate(input) {
107
136
  const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
108
137
  return { ok: true, artifactPath, diff };
109
138
  }
139
+ /**
140
+ * 最终 review 核对清单(机械化):ledger 缺失/漂移、canonical requirement 漏项、
141
+ * diff 越界、验证证据缺失均阻断 pass。清单以有界安全摘要注入提示词,不泄漏原文。
142
+ */
143
+ async function buildReviewSourceFidelityChecklist(input, contract) {
144
+ const binding = contract.sourceBinding;
145
+ const checklist = [];
146
+ // 契约级 sourceBinding 的 v2 语义:四个 ledger 字段必须齐全(缺失即 v1 遗留绑定)。
147
+ const boundV2 = Boolean(binding.ledgerPath) &&
148
+ Boolean(binding.ledgerSha256) &&
149
+ Boolean(binding.inputDigest) &&
150
+ binding.requirementToFragments !== undefined;
151
+ if (!boundV2) {
152
+ return {
153
+ bound: false,
154
+ ledger: null,
155
+ checklist: [
156
+ "source fidelity ledger: not bound (v1 binding); 按现有语义评审",
157
+ "canonical contract 与实际 diff 逐项核对,验证证据齐全后 verdict 可为 pass",
158
+ ],
159
+ };
160
+ }
161
+ const absolute = resolveDagTaskSourcePath({
162
+ workspaceRoot: input.workspaceRoot,
163
+ taskId: contract.sourceBinding.taskId,
164
+ sourcePath: binding.ledgerPath,
165
+ });
166
+ let ledger = null;
167
+ try {
168
+ const raw = await readFile(absolute, "utf8");
169
+ const actualSha = createHash("sha256").update(raw).digest("hex");
170
+ if (actualSha !== binding.ledgerSha256) {
171
+ throw new Error(`ledger sha256 mismatch (bound ${binding.ledgerSha256}, observed ${actualSha})`);
172
+ }
173
+ ledger = parseLedgerJson(raw);
174
+ }
175
+ catch (error) {
176
+ // AC-011 / AC-HARD-005:v2 drift 返回结构化 failure code(稳定枚举 + reason),
177
+ // 不再裸 throw new Error;message 保持人类可读供 shell stderr。
178
+ throw new FrontendReviewContextFailure("review-context-ledger-drift", `frontend review context source fidelity ledger unavailable or drifted: ${error instanceof Error ? error.message : String(error)}`);
179
+ }
180
+ checklist.push("ledger: 存在且绑定 sha256 一致(无缺失/漂移)", `ledger 覆盖统计: ${ledgerStatsLine(ledger)}`, "canonical contract requirements 必须覆盖 ledger 全部 canonical requirement IDs", "实际 diff 必须限制在契约 targets 内(越界即阻断 pass)", "验证证据必须绑定契约 verificationTargets(缺失即阻断 pass)", "仅满足派生摘要而遗漏 ledger requirement 时 verdict 不得为 pass");
181
+ return {
182
+ bound: true,
183
+ ledger: {
184
+ path: binding.ledgerPath,
185
+ sha256: binding.ledgerSha256,
186
+ inputDigest: binding.inputDigest,
187
+ stats: ledgerStatsLine(ledger),
188
+ },
189
+ checklist,
190
+ };
191
+ }
110
192
  export function formatFrontendReviewContextStdout(result) {
111
193
  return [
112
194
  "Frontend review context: pass",