@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11

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 (338) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +349 -97
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/application/task-lifecycle/recommendations.js +13 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +23 -5
  9. package/dist/cli/program.js +21 -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/client-recovery.js +3 -0
  14. package/dist/commands/dag-follow-up.js +138 -0
  15. package/dist/commands/dag-rerun.js +55 -1
  16. package/dist/commands/init-model-catalog.js +464 -0
  17. package/dist/commands/init-upgrade.js +265 -97
  18. package/dist/commands/init.js +455 -28
  19. package/dist/commands/inspect-next.js +8 -0
  20. package/dist/commands/task-advance.js +19 -0
  21. package/dist/executors/dag-pi-executor.js +2370 -64
  22. package/dist/executors/pi-executor.js +11 -2
  23. package/dist/executors/pi-extension-resolver.js +233 -0
  24. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  25. package/dist/executors/pi-read-budget-policy.js +239 -0
  26. package/dist/executors/pi-sdk-executor.js +225 -33
  27. package/dist/executors/pi-writer-tool-policy.js +57 -1
  28. package/dist/executors/shell-executor.js +1258 -224
  29. package/dist/executors/shell-write-guard.js +14 -0
  30. package/dist/executors/workspace-write-snapshot.js +68 -0
  31. package/dist/infrastructure/harness/atomic-write.js +23 -0
  32. package/dist/shared/dag-failure-category.js +150 -0
  33. package/dist/shared/dag-prompt-override.js +27 -0
  34. package/dist/shared/openspec-spec.js +70 -4
  35. package/dist/shared/operator/capabilities.js +39 -0
  36. package/dist/shared/playwright-cli-command-policy.js +15 -0
  37. package/dist/shared/update/console-notifier.js +100 -0
  38. package/dist/{cli → shared}/update/npm-client.js +70 -15
  39. package/dist/{cli → shared}/update/state.js +41 -13
  40. package/dist/task/config-types.js +50 -5
  41. package/dist/task/contract/apply.js +11 -1
  42. package/dist/task/contract/constants.js +2 -0
  43. package/dist/task/contract/hash.js +3 -0
  44. package/dist/task/contract/observe.js +25 -4
  45. package/dist/task/contract/paths.js +2 -1
  46. package/dist/task/contract/project.js +18 -1
  47. package/dist/task/contract/schema.js +4 -1
  48. package/dist/task/contract/transaction.js +12 -0
  49. package/dist/task/frontend-project-capability.js +199 -18
  50. package/dist/task/source-prepare/completeness.js +188 -0
  51. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  52. package/dist/task/source-prepare/index.js +3 -0
  53. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  54. package/dist/task/source-prepare/ledger-review.js +214 -0
  55. package/dist/task/source-prepare/ledger.js +545 -0
  56. package/dist/task/source-prepare/parse-intent.js +24 -4
  57. package/dist/task/source-prepare/prepare.js +298 -1
  58. package/dist/task/source-prepare/semantic-intake.js +25 -5
  59. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  60. package/dist/task/source-prepare/types.js +22 -0
  61. package/dist/task/source-references.js +22 -1
  62. package/dist/worker/cli.js +18 -2
  63. package/dist/worker/console/chat/chat-event-store.js +75 -5
  64. package/dist/worker/console/chat/context-panel.js +7 -0
  65. package/dist/worker/console/chat/deferred-turn.js +12 -0
  66. package/dist/worker/console/chat/operation-card.js +8 -1
  67. package/dist/worker/console/chat/pi-console-config.js +81 -10
  68. package/dist/worker/console/chat/pi-runtime.js +331 -18
  69. package/dist/worker/console/chat/provider-error.js +98 -0
  70. package/dist/worker/console/chat/routes.js +395 -59
  71. package/dist/worker/console/chat/session-stats.js +179 -0
  72. package/dist/worker/console/chat/session-store.js +93 -63
  73. package/dist/worker/console/chat/todos.js +115 -0
  74. package/dist/worker/console/chat/tool-preview.js +42 -0
  75. package/dist/worker/console/chat/turn-process.js +20 -55
  76. package/dist/worker/console/chat/user-questions.js +266 -0
  77. package/dist/worker/console/chat/workspace-landing.js +79 -0
  78. package/dist/worker/console/console-handoff.js +82 -0
  79. package/dist/worker/console/console-update-and-init.js +115 -0
  80. package/dist/worker/console/console-update-runtime.js +84 -0
  81. package/dist/worker/console/dag-execution-receipt.js +33 -0
  82. package/dist/worker/console/draft-store.js +49 -0
  83. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  84. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  85. package/dist/worker/console/index.js +3 -0
  86. package/dist/worker/console/inspect-split.js +18 -0
  87. package/dist/worker/console/native-directory-picker.js +13 -0
  88. package/dist/worker/console/operation-run-facts.js +19 -3
  89. package/dist/worker/console/operation-store.js +227 -7
  90. package/dist/worker/console/operator-actions.js +388 -0
  91. package/dist/worker/console/operator-surface-health.js +1 -0
  92. package/dist/worker/console/operator-user-error.js +2 -2
  93. package/dist/worker/console/prd-intake-bridge.js +13 -0
  94. package/dist/worker/console/routes.js +355 -3
  95. package/dist/worker/console/security.js +67 -13
  96. package/dist/worker/console/server.js +609 -164
  97. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
  98. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
  99. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
  100. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
  101. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
  102. package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
  103. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
  112. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
  113. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
  114. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
  115. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
  117. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
  118. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
  123. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
  124. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
  125. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
  126. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
  127. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
  128. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
  129. package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
  130. package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
  131. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
  132. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
  133. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
  134. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
  135. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
  136. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
  137. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
  138. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
  139. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
  140. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
  141. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
  142. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
  143. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
  144. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
  145. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
  146. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
  147. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
  148. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
  149. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
  150. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
  151. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
  152. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
  153. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
  154. package/dist/worker/console/static/index.html +2 -2
  155. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  156. package/dist/worker/console/static-src/app/console-types.js +68 -4
  157. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  158. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  159. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  160. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  161. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  162. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  163. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  164. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  165. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  166. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  167. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  168. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  169. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  170. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  171. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  172. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  173. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  174. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  175. package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
  176. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  177. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  178. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  179. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  180. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  181. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  182. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  183. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  184. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  185. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  186. package/dist/worker/console/workspace-context.js +344 -0
  187. package/dist/worker/console/workspace-registry.js +214 -0
  188. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  189. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  190. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  191. package/dist/worker/observability/init-runtime-activity.js +26 -0
  192. package/dist/worker/observability/read-model.js +22 -0
  193. package/dist/worker/observe/node-input.js +160 -3
  194. package/dist/worker/observe/node-process.js +377 -0
  195. package/dist/worker/observe/routes.js +224 -3
  196. package/dist/worker/observe/server.js +4 -0
  197. package/dist/worker/observe/shell-handler-keys.js +34 -0
  198. package/dist/worker/observe/static/api.js +90 -4
  199. package/dist/worker/observe/static/app.js +18 -70
  200. package/dist/worker/observe/static/constants.js +8 -1
  201. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  202. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  203. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  204. package/dist/worker/observe/static/format.js +60 -0
  205. package/dist/worker/observe/static/index.html +6 -1
  206. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  207. package/dist/worker/observe/static/markdown-render.js +20 -1
  208. package/dist/worker/observe/static/operator-chrome.css +145 -20
  209. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  210. package/dist/worker/observe/static/operator-chrome.js +330 -96
  211. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  212. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  213. package/dist/worker/observe/static/router.d.ts +46 -0
  214. package/dist/worker/observe/static/router.js +25 -1
  215. package/dist/worker/observe/static/state.d.ts +50 -0
  216. package/dist/worker/observe/static/state.js +184 -1
  217. package/dist/worker/observe/static/styles.css +395 -0
  218. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  219. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  220. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  221. package/dist/worker/observe/static/views/dag.js +14 -5
  222. package/dist/worker/observe/static/views/dashboard.js +6 -4
  223. package/dist/worker/observe/static/views/failures.js +5 -2
  224. package/dist/worker/observe/static/views/night.js +2 -2
  225. package/dist/worker/observe/static/views/pool.js +1 -0
  226. package/dist/worker/observe/static/views/run.js +3 -1
  227. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  228. package/dist/worker/observe/static/views/task.js +56 -1
  229. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  230. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  231. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  232. package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
  233. package/dist/workflows/dag/contract-output-registry.js +15 -0
  234. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  235. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  236. package/dist/workflows/dag/dynamic-runtime/map.js +2 -1
  237. package/dist/workflows/dag/failure-category.js +7 -116
  238. package/dist/workflows/dag/frontend-closeout.js +221 -0
  239. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  240. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  241. package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
  242. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  243. package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
  244. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  245. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  246. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  247. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  248. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  249. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  250. package/dist/workflows/dag/frontend-repair.js +7 -424
  251. package/dist/workflows/dag/frontend-review-context.js +288 -14
  252. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  253. package/dist/workflows/dag/frontend-risk.js +15 -2
  254. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  255. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  256. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  257. package/dist/workflows/dag/frontend-shape.js +427 -0
  258. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  259. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  260. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  261. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  262. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  263. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  264. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  265. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  266. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  267. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  268. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  269. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  270. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  271. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  272. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  273. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  274. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  275. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  276. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  277. package/dist/workflows/dag/init-hybrid.js +1072 -660
  278. package/dist/workflows/dag/interrupt-request.js +10 -3
  279. package/dist/workflows/dag/lifecycle.js +3 -3
  280. package/dist/workflows/dag/node-execution.js +552 -58
  281. package/dist/workflows/dag/prompt.js +44 -19
  282. package/dist/workflows/dag/recovery-lease.js +80 -0
  283. package/dist/workflows/dag/report.js +37 -1
  284. package/dist/workflows/dag/rerun-feedback.js +124 -1
  285. package/dist/workflows/dag/rerun-plan.js +144 -5
  286. package/dist/workflows/dag/rerun-run.js +81 -6
  287. package/dist/workflows/dag/rerun-task.js +29 -0
  288. package/dist/workflows/dag/retry-policy.js +318 -8
  289. package/dist/workflows/dag/runner.js +429 -122
  290. package/dist/workflows/dag/scheduler.js +114 -16
  291. package/dist/workflows/dag/structured-output-repair.js +712 -0
  292. package/dist/workflows/dag/types.js +338 -31
  293. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  294. package/dist/workflows/dag/validate.js +92 -32
  295. package/docs/README.md +2 -0
  296. package/docs/architecture/evolution.md +45 -0
  297. package/docs/init-surface.manifest.json +9 -0
  298. package/docs/operations/README.md +1 -1
  299. package/docs/operations/local-development-environment.md +21 -0
  300. package/docs/templates/README.md +2 -1
  301. package/docs/templates/agent-dag-report.schema.json +8 -2
  302. package/docs/templates/agent-dag.schema.json +63 -1
  303. package/docs/templates/backend-test-dag.json +265 -237
  304. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  305. package/docs/templates/frontend-test-case-checklist.md +2 -2
  306. package/docs/templates/frontend-test-dag.json +8 -10
  307. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  308. package/docs/templates/init-managed-agents.md +13 -3
  309. package/docs/templates/spec-registry.schema.json +45 -0
  310. package/harness.json +1 -1
  311. package/package.json +7 -1
  312. package/scripts/next-info.mjs +356 -0
  313. package/scripts/next-publish-gate.mjs +238 -0
  314. package/scripts/release-source-binding.mjs +251 -0
  315. package/skills/codebase-scout/SKILL.md +1 -1
  316. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  317. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  318. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  319. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  320. package/skills/frontend-design-review/SKILL.md +20 -41
  321. package/skills/frontend-implementation/SKILL.md +3 -4
  322. package/skills/frontend-implementation/references/design-spec.md +15 -10
  323. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  324. package/skills/frontend-review/SKILL.md +22 -14
  325. package/skills/frontend-review/references/review-findings.md +6 -7
  326. package/skills/loop-agent/references/command-reference.md +12 -4
  327. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  328. package/skills/playwright-cli/SKILL.md +23 -1
  329. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  330. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  331. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  332. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  333. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  334. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  335. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  336. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
  337. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  338. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -3,43 +3,77 @@ import { lstat, readFile, realpath } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import semver from "semver";
5
5
  const PACKAGE_NAME = "@tea-agent/loop-agent";
6
+ export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
6
7
  export function createNpmUpdateClient(options) {
7
8
  const npmCommand = options.npmCommand ?? "npm";
8
9
  const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
9
10
  const runner = options.runner ?? runCommand;
10
- const timeoutMs = options.timeoutMs ?? 1500;
11
+ const timeoutMs = options.timeoutMs ?? DEFAULT_NPM_QUERY_TIMEOUT_MS;
12
+ const cwd = options.cwd;
11
13
  const env = options.env;
14
+ const distTagVersionCheck = async (channel) => {
15
+ let result;
16
+ try {
17
+ result = await runner(npmCommand, ["view", PACKAGE_NAME, `dist-tags.${channel}`, "--json"], { timeoutMs, cwd, env });
18
+ }
19
+ catch {
20
+ return { ok: false, reason: "spawn-failure" };
21
+ }
22
+ if (result.timedOut)
23
+ return { ok: false, reason: "timeout" };
24
+ if (result.exitCode === null) {
25
+ return { ok: false, reason: "spawn-failure" };
26
+ }
27
+ if (result.exitCode !== 0) {
28
+ return { ok: false, reason: "command-failed" };
29
+ }
30
+ const version = parseNpmViewDistTag(result.stdout, channel);
31
+ return version
32
+ ? { ok: true, version }
33
+ : { ok: false, reason: "invalid-response" };
34
+ };
35
+ const latestVersionCheck = () => distTagVersionCheck("latest");
12
36
  return {
13
37
  async latestVersion() {
14
- const result = await runner(npmCommand, ["view", PACKAGE_NAME, "dist-tags.latest", "--json"], { timeoutMs, env });
15
- if (result.exitCode !== 0 || result.timedOut)
16
- return undefined;
17
- return parseNpmViewLatest(result.stdout);
38
+ const result = await latestVersionCheck();
39
+ return result.ok ? result.version : undefined;
18
40
  },
41
+ latestVersionCheck,
42
+ distTagVersionCheck,
19
43
  async canAutoInstall() {
20
44
  return proveGlobalNpmInstall({
21
45
  currentVersion: options.currentVersion,
22
46
  packageRoot: options.packageRoot,
23
47
  npmCommand,
24
48
  runner,
49
+ cwd,
25
50
  env,
26
51
  timeoutMs,
27
52
  });
28
53
  },
29
- async installExact(version) {
30
- if (!isStrictRelease(version))
54
+ async installExact(version, channel = "latest") {
55
+ if (!isAllowedChannelVersion(version, channel)) {
31
56
  return { ok: false, reason: "invalid-version" };
32
- const result = await runner(npmCommand, buildExactInstallArgs(version), { timeoutMs: Math.max(timeoutMs, 120000), env });
57
+ }
58
+ const result = await runner(npmCommand, buildExactInstallArgs(version, channel), {
59
+ timeoutMs: Math.max(timeoutMs, 120000),
60
+ cwd,
61
+ env,
62
+ });
33
63
  if (result.exitCode !== 0 || result.timedOut)
34
64
  return { ok: false, reason: "install-failed" };
35
65
  return { ok: true };
36
66
  },
37
67
  async verifyInstalled(version) {
38
- const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, env });
68
+ const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, cwd, env });
39
69
  if (list.exitCode !== 0 || !verifyNpmListVersion(list.stdout, version)) {
40
70
  return { ok: false, reason: "npm-list-version-mismatch" };
41
71
  }
42
- const pathVersion = await runner(loopAgentCommand, ["--version"], { timeoutMs, env });
72
+ const pathVersion = await runner(loopAgentCommand, ["--version"], {
73
+ timeoutMs,
74
+ cwd,
75
+ env,
76
+ });
43
77
  if (pathVersion.exitCode !== 0 || !verifyPathVersion(pathVersion.stdout, version)) {
44
78
  return { ok: false, reason: "path-version-mismatch" };
45
79
  }
@@ -48,6 +82,13 @@ export function createNpmUpdateClient(options) {
48
82
  };
49
83
  }
50
84
  export function parseNpmViewLatest(stdout) {
85
+ return parseNpmViewDistTag(stdout, "latest");
86
+ }
87
+ /**
88
+ * `latest` stays stable-only. `next` is explicitly selected by a human and
89
+ * may legitimately be a SemVer prerelease, but remains an exact version.
90
+ */
91
+ export function parseNpmViewDistTag(stdout, channel) {
51
92
  const trimmed = stdout.trim();
52
93
  let value = trimmed;
53
94
  try {
@@ -58,11 +99,12 @@ export function parseNpmViewLatest(stdout) {
58
99
  }
59
100
  if (typeof value !== "string")
60
101
  return undefined;
61
- return isStrictRelease(value) ? value : undefined;
102
+ return isAllowedChannelVersion(value, channel) ? value : undefined;
62
103
  }
63
- export function buildExactInstallArgs(version) {
64
- if (!isStrictRelease(version))
65
- throw new Error(`invalid release version: ${version}`);
104
+ export function buildExactInstallArgs(version, channel = "latest") {
105
+ if (!isAllowedChannelVersion(version, channel)) {
106
+ throw new Error(`invalid ${channel} version: ${version}`);
107
+ }
66
108
  return ["install", "-g", `${PACKAGE_NAME}@${version}`, "--no-fund", "--no-audit"];
67
109
  }
68
110
  export function verifyNpmListVersion(stdout, expectedVersion) {
@@ -75,11 +117,18 @@ export function verifyNpmListVersion(stdout, expectedVersion) {
75
117
  }
76
118
  }
77
119
  export function verifyPathVersion(stdout, expectedVersion) {
78
- return stdout.trim() === expectedVersion;
120
+ // `loop-agent --version` may append a build provenance marker. The release
121
+ // version itself must still match exactly; accepting no other trailing form
122
+ // keeps PATH verification bound to the requested stable package.
123
+ return new RegExp(`^${escapeRegExp(expectedVersion)}(?: \\(build [^)]+\\))?$`).test(stdout.trim());
124
+ }
125
+ function escapeRegExp(value) {
126
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
79
127
  }
80
128
  async function proveGlobalNpmInstall(options) {
81
129
  const root = await options.runner(options.npmCommand, ["root", "-g"], {
82
130
  timeoutMs: options.timeoutMs,
131
+ cwd: options.cwd,
83
132
  env: options.env,
84
133
  });
85
134
  if (root.exitCode !== 0 || root.timedOut)
@@ -110,9 +159,15 @@ function isStrictRelease(version) {
110
159
  const parsed = semver.parse(version);
111
160
  return Boolean(parsed && parsed.prerelease.length === 0);
112
161
  }
162
+ function isAllowedChannelVersion(version, channel) {
163
+ if (channel === "latest")
164
+ return isStrictRelease(version);
165
+ return Boolean(semver.valid(version));
166
+ }
113
167
  async function runCommand(command, args, options) {
114
168
  return new Promise((resolve) => {
115
169
  const child = spawn(command, [...args], {
170
+ cwd: options.cwd,
116
171
  env: options.env ? { ...process.env, ...options.env } : process.env,
117
172
  stdio: ["ignore", "pipe", "pipe"],
118
173
  shell: false,
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  export function updateStatePath(homeDir) {
4
4
  return path.join(homeDir, ".loop-agent", "update-state.json");
@@ -18,6 +18,7 @@ export async function readUpdateState(statePath) {
18
18
  latestVersion: stringOrUndefined(parsed.latestVersion),
19
19
  dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
20
20
  retryAfter: stringOrUndefined(parsed.retryAfter),
21
+ lastPromptedLocalDate: stringOrUndefined(parsed.lastPromptedLocalDate),
21
22
  };
22
23
  }
23
24
  catch {
@@ -36,28 +37,55 @@ export async function acquireUpdateLock(lockPath, options = {}) {
36
37
  await mkdir(path.dirname(lockPath), { recursive: true });
37
38
  try {
38
39
  await mkdir(lockPath);
40
+ return await claimLock(lockPath, now);
41
+ }
42
+ catch {
43
+ if (await isFreshLock(lockPath, now, staleMs))
44
+ return undefined;
45
+ try {
46
+ await rm(lockPath, { recursive: true, force: true });
47
+ await mkdir(lockPath);
48
+ return await claimLock(lockPath, now);
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ }
54
+ }
55
+ async function claimLock(lockPath, now) {
56
+ try {
39
57
  await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
40
58
  return async () => {
41
59
  await rm(lockPath, { recursive: true, force: true });
42
60
  };
43
61
  }
62
+ catch {
63
+ await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
64
+ return undefined;
65
+ }
66
+ }
67
+ async function isFreshLock(lockPath, now, staleMs) {
68
+ try {
69
+ const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
70
+ const owner = JSON.parse(ownerRaw);
71
+ if (typeof owner.at !== "string")
72
+ return false;
73
+ const at = Date.parse(owner.at);
74
+ if (!Number.isFinite(at))
75
+ return false;
76
+ return now().getTime() - at <= staleMs;
77
+ }
44
78
  catch {
45
79
  try {
46
- const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
47
- const owner = JSON.parse(ownerRaw);
48
- if (owner.at && now().getTime() - Date.parse(owner.at) > staleMs) {
49
- await rm(lockPath, { recursive: true, force: true });
50
- await mkdir(lockPath);
51
- await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
52
- return async () => {
53
- await rm(lockPath, { recursive: true, force: true });
54
- };
55
- }
80
+ const info = await stat(lockPath);
81
+ const mtime = Number.isFinite(info.mtimeMs)
82
+ ? info.mtimeMs
83
+ : info.mtime.getTime();
84
+ return now().getTime() - mtime <= staleMs;
56
85
  }
57
86
  catch {
58
- return undefined;
87
+ return false;
59
88
  }
60
- return undefined;
61
89
  }
62
90
  }
63
91
  function emptyState() {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { isOpenspecSpecFilePath } from "../shared/openspec-spec.js";
2
+ import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, } from "../shared/openspec-spec.js";
3
3
  export const taskFlowSchema = z.enum([
4
4
  "auto",
5
5
  "micro",
@@ -76,17 +76,37 @@ export const frontendOpenspecPolicySchema = z.enum(["cited", "scan-strict"]);
76
76
  */
77
77
  export const frontendOpenspecConfigSchema = z.object({
78
78
  policy: frontendOpenspecPolicySchema.optional().default("cited"),
79
+ /**
80
+ * Repo-relative directories containing frontend normative files. These are
81
+ * frozen in task.json during contract preparation, before DAG generation.
82
+ */
83
+ specRoots: z
84
+ .array(z.string().refine((candidate) => isValidFrontendSpecRoot(candidate), "frontendOpenspec.specRoots entries must be normalized repo-relative directories"))
85
+ .optional(),
79
86
  requiredReadPaths: z
80
- .array(z
81
- .string()
82
- .refine((candidate) => !candidate.includes("\\") &&
83
- isOpenspecSpecFilePath(candidate), "frontendOpenspec.requiredReadPaths entries must be repo-relative supported files under openspec/schemas/, openspec/project-specs/, or ai_workspace/"))
87
+ .array(z.string())
84
88
  .optional()
85
89
  .default([]),
90
+ }).superRefine((value, ctx) => {
91
+ const roots = value.specRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
92
+ if (new Set(roots).size !== roots.length) {
93
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "frontendOpenspec.specRoots must not contain duplicates", path: ["specRoots"] });
94
+ }
95
+ for (const required of value.requiredReadPaths) {
96
+ if (!isFrontendSpecFilePath(required, roots)) {
97
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "frontendOpenspec.requiredReadPaths entries must be repo-relative supported files under frontendOpenspec.specRoots", path: ["requiredReadPaths"] });
98
+ }
99
+ }
86
100
  });
87
101
  /** Batch limits and gate profile for the browser-driven frontend test DAG.
88
102
  * Token fields are post-case stop thresholds, not model-provider hard caps. */
89
103
  export const frontendTestConfigSchema = z.object({
104
+ /**
105
+ * Repository-relative frontend-test artifact root.
106
+ * Default "testcase/frontend". Semantic validation lives in
107
+ * resolveFrontendTestLayout (fail-closed), not in this schema.
108
+ */
109
+ testRoot: z.string().min(1).optional(),
90
110
  maxCasesPerBatch: z.number().int().min(1).max(50).optional(),
91
111
  maxTokensPerCase: z.number().int().positive().optional(),
92
112
  maxTotalTokens: z.number().int().positive().optional(),
@@ -141,6 +161,8 @@ export const backendTestConfigSchema = z
141
161
  gapDoc: z.string().min(1).optional(),
142
162
  /** gap-fill pytest execution scope: only affected items (default) or all eligible. */
143
163
  executeScope: z.enum(["affected", "all"]).optional(),
164
+ /** Backend-test-only workspace mutation control; omitted means Git guard. */
165
+ workspaceControl: z.enum(["git", "filesystem-only"]).optional(),
144
166
  })
145
167
  .strict()
146
168
  .optional();
@@ -199,6 +221,21 @@ const taskConfigObjectSchema = z.object({
199
221
  maxFixLoops: z.number().int().min(0).optional().default(2),
200
222
  /** Supervised DAG convergence is opt-in until runtime smoke evidence is stronger. */
201
223
  convergence: convergenceConfigSchema.optional().default({ enabled: false }),
224
+ /** Frontend recovery continuation quota. Bounds how many times a failed
225
+ * frontend run may auto-recover via M6 recovery operations (1 = current
226
+ * default; raise to 2-3 for more tolerance of transient verify failures). */
227
+ frontendRecovery: z
228
+ .object({
229
+ maxContinuations: z
230
+ .number()
231
+ .int()
232
+ .min(1)
233
+ .max(5)
234
+ .optional()
235
+ .default(1),
236
+ })
237
+ .strict()
238
+ .optional(),
202
239
  /** Outer loop auto mode never writes by default; DAG execute requires this policy plus path/writeSet gates. */
203
240
  loopAutoExecutionPolicy: loopAutoExecutionPolicySchema
204
241
  .optional()
@@ -231,5 +268,13 @@ export const taskConfigSchema = z.preprocess((raw) => {
231
268
  if ("executor" in obj || "cursorModel" in obj) {
232
269
  throw new Error(CURSOR_TASK_FIELD_REMOVED_ERROR);
233
270
  }
271
+ const backendTest = obj.backendTest;
272
+ if (backendTest &&
273
+ typeof backendTest === "object" &&
274
+ !Array.isArray(backendTest) &&
275
+ "workspaceControl" in backendTest &&
276
+ obj.taskKind !== "backend-test") {
277
+ throw new Error("backendTest.workspaceControl is only valid when taskKind is backend-test");
278
+ }
234
279
  return raw;
235
280
  }, taskConfigObjectSchema);
@@ -232,8 +232,11 @@ export async function applyTaskContract(input) {
232
232
  // Re-parse through schema to ensure full TaskConfig shape for hash stability
233
233
  const fullConfig = parseTaskConfigForRuntime(mergedConfig);
234
234
  const taskConfigContent = serializeTaskConfigForDisk(fullConfig);
235
- const requirementContent = projectRequirementMarkdown(input.draft);
235
+ const requirementContent = projectRequirementMarkdown(input.draft, input.requirementLedger);
236
236
  const constraintsContent = projectConstraintsMarkdown(input.draft);
237
+ const requirementLedgerContent = input.requirementLedger !== undefined
238
+ ? `${JSON.stringify(input.requirementLedger, null, 2)}\n`
239
+ : undefined;
237
240
  // Validate references manifest hash if present (do not modify)
238
241
  const paths = getTaskPaths(input.repoRoot, input.taskId);
239
242
  const manifestText = await readFile(getSourceManifestPath(paths.sourceDir), "utf-8").catch(() => null);
@@ -241,6 +244,7 @@ export async function applyTaskContract(input) {
241
244
  requirementBytes: requirementContent,
242
245
  constraintsBytes: constraintsContent,
243
246
  referenceManifestBytes: manifestText,
247
+ requirementLedgerBytes: requirementLedgerContent,
244
248
  });
245
249
  const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
246
250
  const canonicalHash = computeCanonicalHash({
@@ -277,6 +281,9 @@ export async function applyTaskContract(input) {
277
281
  constraintsContent,
278
282
  taskConfigContent,
279
283
  contractRef: nextRef,
284
+ ...(requirementLedgerContent !== undefined
285
+ ? { requirementLedgerContent }
286
+ : {}),
280
287
  },
281
288
  });
282
289
  await appendRequestLedgerEntry({
@@ -312,6 +319,9 @@ export async function applyTaskContract(input) {
312
319
  changedFiles: [
313
320
  "source/需求.md",
314
321
  "source/执行约束.md",
322
+ ...(requirementLedgerContent !== undefined
323
+ ? ["source/requirement-ledger.json"]
324
+ : []),
315
325
  "task.json",
316
326
  "contract.json",
317
327
  ],
@@ -29,3 +29,5 @@ export const TRANSACTIONS_DIRNAME = TRANSACTIONS_DIR_NAME;
29
29
  export const REQUEST_LEDGER_BASENAME = REQUEST_LEDGER_FILE_NAME;
30
30
  export const REQUIREMENT_FILE_NAME = "需求.md";
31
31
  export const CONSTRAINTS_FILE_NAME = "执行约束.md";
32
+ /** Source fidelity ledger (20260824-frontend-source-fidelity-ledger-v1). */
33
+ export const REQUIREMENT_LEDGER_FILE_NAME = "requirement-ledger.json";
@@ -35,6 +35,9 @@ export function computeSourceHashes(input) {
35
35
  if (input.referenceManifestBytes) {
36
36
  sourceHashes.referenceManifestSha256 = sha256Utf8(input.referenceManifestBytes);
37
37
  }
38
+ if (input.requirementLedgerBytes) {
39
+ sourceHashes.requirementLedgerSha256 = sha256Utf8(input.requirementLedgerBytes);
40
+ }
38
41
  return sourceHashes;
39
42
  }
40
43
  export function refMatchesObserved(ref, observedCanonicalHash) {
@@ -1,6 +1,6 @@
1
1
  import { access, readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { CONSTRAINTS_FILE_NAME, CONTRACT_FILE_NAME, REQUIREMENT_FILE_NAME, TRANSACTIONS_DIR_NAME, } from "./constants.js";
3
+ import { CONSTRAINTS_FILE_NAME, CONTRACT_FILE_NAME, REQUIREMENT_FILE_NAME, REQUIREMENT_LEDGER_FILE_NAME, TRANSACTIONS_DIR_NAME, } from "./constants.js";
4
4
  import { computeCanonicalHash, computeSourceHashes, computeTaskConfigSha256, } from "./hash.js";
5
5
  import { parseTaskContractRef } from "./schema.js";
6
6
  import { getTaskPaths } from "../runtime.js";
@@ -33,11 +33,13 @@ export async function loadObservedTaskBytes(repoRoot, taskId) {
33
33
  const paths = getTaskPaths(repoRoot, taskId);
34
34
  const requirementPath = path.join(paths.sourceDir, REQUIREMENT_FILE_NAME);
35
35
  const constraintsPath = path.join(paths.sourceDir, CONSTRAINTS_FILE_NAME);
36
- const [requirementBytes, constraintsBytes, taskConfigText, manifestText] = await Promise.all([
36
+ const requirementLedgerPath = path.join(paths.sourceDir, REQUIREMENT_LEDGER_FILE_NAME);
37
+ const [requirementBytes, constraintsBytes, taskConfigText, manifestText, requirementLedgerBytes] = await Promise.all([
37
38
  readTextIfExists(requirementPath),
38
39
  readTextIfExists(constraintsPath),
39
40
  readTextIfExists(paths.taskConfigPath),
40
41
  readTextIfExists(getSourceManifestPath(paths.sourceDir)),
42
+ readTextIfExists(requirementLedgerPath),
41
43
  ]);
42
44
  let taskConfigRaw = null;
43
45
  let taskConfigSha256 = null;
@@ -52,6 +54,7 @@ export async function loadObservedTaskBytes(repoRoot, taskId) {
52
54
  taskConfigRaw,
53
55
  taskConfigSha256,
54
56
  referenceManifestBytes: manifestText,
57
+ requirementLedgerBytes,
55
58
  requirementPath,
56
59
  constraintsPath,
57
60
  taskConfigPath: paths.taskConfigPath,
@@ -164,7 +167,9 @@ export async function observeTaskContractState(repoRoot, taskId) {
164
167
  message: error instanceof Error ? error.message : String(error),
165
168
  });
166
169
  }
167
- const observed = await computeObservedCanonicalHashSafe(repoRoot, taskId);
170
+ const observed = await computeObservedCanonicalHashSafe(repoRoot, taskId, {
171
+ ref: ref ?? undefined,
172
+ });
168
173
  errors.push(...(observed.errors ?? []));
169
174
  if (!ref) {
170
175
  return {
@@ -226,7 +231,7 @@ export async function observeTaskContractState(repoRoot, taskId) {
226
231
  observationErrors: errors,
227
232
  };
228
233
  }
229
- async function computeObservedCanonicalHashSafe(repoRoot, taskId) {
234
+ async function computeObservedCanonicalHashSafe(repoRoot, taskId, options) {
230
235
  const errors = [];
231
236
  try {
232
237
  const bytes = await loadObservedTaskBytes(repoRoot, taskId);
@@ -256,10 +261,26 @@ async function computeObservedCanonicalHashSafe(repoRoot, taskId) {
256
261
  }
257
262
  return { hash: null, errors };
258
263
  }
264
+ // Legacy compatibility (AC-FIDELITY-004): only include the ledger bytes
265
+ // when the stored ref actually declares requirementLedgerSha256. A ref
266
+ // without the ledger field keeps the existing semantics and never
267
+ // misreports drift for tasks that predate the ledger.
268
+ const refLedgerHash = options?.ref?.sourceHashes.requirementLedgerSha256 ?? null;
269
+ if (refLedgerHash && bytes.requirementLedgerBytes === null) {
270
+ errors.push({
271
+ code: "MISSING_REQUIREMENT_LEDGER",
272
+ path: "source/requirement-ledger.json",
273
+ message: `contract records requirementLedgerSha256 but ${REQUIREMENT_LEDGER_FILE_NAME} is missing`,
274
+ });
275
+ return { hash: null, errors };
276
+ }
259
277
  const sourceHashes = computeSourceHashes({
260
278
  requirementBytes: bytes.requirementBytes,
261
279
  constraintsBytes: bytes.constraintsBytes,
262
280
  referenceManifestBytes: bytes.referenceManifestBytes,
281
+ ...(refLedgerHash
282
+ ? { requirementLedgerBytes: bytes.requirementLedgerBytes }
283
+ : {}),
263
284
  });
264
285
  const hash = computeCanonicalHash({
265
286
  sourceHashes,
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { getTaskPaths } from "../runtime.js";
3
- import { CONTRACT_JSON_BASENAME, REQUEST_LEDGER_BASENAME, TRANSACTIONS_DIRNAME, } from "./constants.js";
3
+ import { CONTRACT_JSON_BASENAME, REQUIREMENT_LEDGER_FILE_NAME, REQUEST_LEDGER_BASENAME, TRANSACTIONS_DIRNAME, } from "./constants.js";
4
4
  export function getTaskContractPaths(repoRoot, taskId) {
5
5
  const base = getTaskPaths(repoRoot, taskId);
6
6
  return {
@@ -15,5 +15,6 @@ export function getTaskContractPaths(repoRoot, taskId) {
15
15
  transactionsDir: path.join(base.taskDir, TRANSACTIONS_DIRNAME),
16
16
  requestLedgerPath: path.join(base.taskDir, REQUEST_LEDGER_BASENAME),
17
17
  lockPath: path.join(base.taskDir, ".contract.lock"),
18
+ requirementLedgerPath: path.join(base.sourceDir, REQUIREMENT_LEDGER_FILE_NAME),
18
19
  };
19
20
  }
@@ -1,8 +1,10 @@
1
1
  import { canonicalizePathArray, canonicalizeTextProjection, } from "./canonicalize.js";
2
2
  /**
3
3
  * Project Draft → source/需求.md (deterministic template, projectionVersion=1).
4
+ * 当携带 ledger 时追加 `## Source ledger` 区块:明确 需求.md 是派生导航视图,
5
+ * 语义权威为原始 requirement/acceptance 文档与受管 requirement-ledger.json。
4
6
  */
5
- export function projectRequirementMarkdown(draft) {
7
+ export function projectRequirementMarkdown(draft, ledger) {
6
8
  const lines = [
7
9
  `# 需求:${draft.title}`,
8
10
  "",
@@ -62,6 +64,18 @@ export function projectRequirementMarkdown(draft) {
62
64
  lines.push(`- [${ref.role}] ${ref.ref}`);
63
65
  }
64
66
  }
67
+ if (ledger) {
68
+ const gateStatus = ledger.stats.unresolvedCount > 0 ||
69
+ ledger.stats.conflictCount > 0 ||
70
+ ledger.stats.highRiskUncoveredCount > 0
71
+ ? "blocked"
72
+ : "pass";
73
+ const ledgerLine = ledger.sourcePaths.length > 0
74
+ ? `source/requirement-ledger.json (${ledger.sourcePaths.length} source document(s))`
75
+ : "source/requirement-ledger.json";
76
+ lines.push("", "## Source ledger", "", "> 本文件是派生导航视图,不替代原始 requirement/acceptance 文档。" +
77
+ " 语义权威为原始文档与 `source/requirement-ledger.json`(确定性切分账本)。", "", `- ledger: ${ledgerLine.length > 160 ? `${ledgerLine.slice(0, 160)}…` : ledgerLine}`, `- inputDigest: ${ledger.inputDigest}`, `- fragments: ${ledger.stats.fragmentCount}`, `- canonical requirements: ${ledger.stats.canonicalRequirementCount}`, `- status: ${gateStatus}`);
78
+ }
65
79
  lines.push("");
66
80
  return canonicalizeTextProjection(lines.join("\n"));
67
81
  }
@@ -170,6 +184,9 @@ export function mergeManagedTaskConfigFields(existing, draft) {
170
184
  command: cmd.command,
171
185
  ...(cmd.timeoutMs !== undefined ? { timeoutMs: cmd.timeoutMs } : {}),
172
186
  })),
187
+ ...(draft.frontendOpenspec !== undefined
188
+ ? { frontendOpenspec: draft.frontendOpenspec }
189
+ : {}),
173
190
  };
174
191
  if (draft.featureId !== undefined) {
175
192
  next.featureId = draft.featureId;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
2
+ import { backendTestConfigSchema, frontendOpenspecConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
3
3
  import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
4
4
  const sha256Schema = z
5
5
  .string()
@@ -74,6 +74,7 @@ export const taskContractDraftSchema = z
74
74
  openQuestions: z.array(z.string()).optional(),
75
75
  assumptions: z.array(z.string()).optional(),
76
76
  hardConstraints: z.array(z.string()).optional(),
77
+ frontendOpenspec: frontendOpenspecConfigSchema.optional(),
77
78
  interviewAssessmentRef: z
78
79
  .object({
79
80
  draftSha256: z.string().min(1),
@@ -107,6 +108,8 @@ export const taskContractSourceHashesSchema = z
107
108
  requirementSha256: sha256Schema,
108
109
  constraintsSha256: sha256Schema,
109
110
  referenceManifestSha256: sha256Schema.optional(),
111
+ /** Source fidelity ledger hash; present only when the task binds a ledger. */
112
+ requirementLedgerSha256: sha256Schema.optional(),
110
113
  })
111
114
  .strict();
112
115
  export const taskContractRefSchema = z
@@ -21,6 +21,7 @@ export async function runJournaledContractCommit(input) {
21
21
  const constraintsRel = `source/${CONSTRAINTS_FILE_NAME}`;
22
22
  const taskConfigRel = "task.json";
23
23
  const contractRel = CONTRACT_FILE_NAME;
24
+ const requirementLedgerRel = `source/requirement-ledger.json`;
24
25
  const targetSpecs = [
25
26
  {
26
27
  kind: "requirement",
@@ -46,6 +47,17 @@ export async function runJournaledContractCommit(input) {
46
47
  content: input.targets.contractRef,
47
48
  isJson: true,
48
49
  },
50
+ ...(input.targets.requirementLedgerContent !== undefined
51
+ ? [
52
+ {
53
+ kind: "requirement-ledger",
54
+ relativePath: requirementLedgerRel,
55
+ // Content is already serialized JSON text; stageText avoids double-encoding.
56
+ content: input.targets.requirementLedgerContent,
57
+ isJson: false,
58
+ },
59
+ ]
60
+ : []),
49
61
  ];
50
62
  const targetRecords = [];
51
63
  for (const spec of targetSpecs) {