@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,13 +3,13 @@
3
3
  * Strong evidence only: package.json deps, config files, source imports.
4
4
  * Lockfile-only or directory-name-only must NOT activate adapters.
5
5
  */
6
- import { readFile, readdir, stat } from "node:fs/promises";
6
+ import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { z } from "zod";
9
- import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, MAX_OPENSPEC_CANDIDATE_PATHS, OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isGovernanceSubtreePath, isOpenspecSpecFilePath, } from "../shared/openspec-spec.js";
9
+ import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, MAX_OPENSPEC_CANDIDATE_PATHS, OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isGovernanceSubtreePath, isValidFrontendSpecRoot, } from "../shared/openspec-spec.js";
10
10
  const classifiedPathSchema = z
11
11
  .string()
12
- .refine((candidate) => isOpenspecSpecFilePath(candidate), "classified path must be a repo-relative supported spec file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
12
+ .refine((candidate) => OPENSPEC_SPEC_EXT_RE.test(candidate), "classified path must be a repo-relative supported spec file");
13
13
  /**
14
14
  * Stable semantic partition of `designEvidence.normativePaths`. Keys are a
15
15
  * generic directory convention (not project-specific file names); the same
@@ -81,6 +81,105 @@ async function exists(filePath) {
81
81
  return false;
82
82
  }
83
83
  }
84
+ export const FRONTEND_SPEC_REGISTRY_PATH = "ai_workspace/loop-agent/spec-registry.json";
85
+ const SPEC_SIGNAL_RE = /(?:规范|规约|标准|约定|guideline|standard|convention|design[- ]?system|rule|token|component|style|theme)/i;
86
+ const SPEC_EXCLUDE_RE = /(?:^|\/)(?:archive|history|changelog|changes|proposals?|release[-_ ]notes?)(?:\/|$)/i;
87
+ /** Stable, bounded heuristic used only for candidate ordering/classification. */
88
+ export function scoreFrontendSpecCandidate(filePath, contentPreview = "", options = {}) {
89
+ const normalized = filePath.replaceAll("\\", "/");
90
+ const lower = normalized.toLowerCase();
91
+ const reasons = [];
92
+ let score = 0;
93
+ if (options.registryMode === "normative") {
94
+ score += 100;
95
+ reasons.push("registry-normative");
96
+ }
97
+ else if (options.registryMode === "advisory") {
98
+ score += 30;
99
+ reasons.push("registry-advisory");
100
+ }
101
+ if (options.taskRelated) {
102
+ score += 25;
103
+ reasons.push("task-related");
104
+ }
105
+ if (/(?:design-system|guidelines?|standards?|conventions?|rules?|specs?)(?:\/|$)/i.test(lower)) {
106
+ score += 20;
107
+ reasons.push("spec-directory");
108
+ }
109
+ if (/(?:guidelines?|standards?|conventions?|rules?|tokens?|components?|theme|style)/i.test(path.basename(lower))) {
110
+ score += 15;
111
+ reasons.push("spec-filename");
112
+ }
113
+ if (SPEC_SIGNAL_RE.test(contentPreview)) {
114
+ score += 20;
115
+ reasons.push("spec-content");
116
+ }
117
+ if (/(?:^|\n)\s*(?:#{1,6}\s*)?(?:rules?|do|don't|必须|禁止|约定|规范)\b/im.test(contentPreview)) {
118
+ score += 15;
119
+ reasons.push("normative-structure");
120
+ }
121
+ if (SPEC_EXCLUDE_RE.test(lower)) {
122
+ score -= 80;
123
+ reasons.push("excluded-history-path");
124
+ }
125
+ const kind = options.registryMode === "normative" || score >= 80
126
+ ? "normative"
127
+ : score >= 35
128
+ ? "advisory"
129
+ : "unknown";
130
+ return { path: normalized, kind, score, reasons, source: options.registryMode ? "registry" : "auto-discovered" };
131
+ }
132
+ /** Read the optional project-level registry without making it mandatory. */
133
+ export async function readFrontendSpecRegistry(repoRoot) {
134
+ const raw = await readJson(path.join(repoRoot, FRONTEND_SPEC_REGISTRY_PATH));
135
+ if (!raw || typeof raw !== "object")
136
+ return null;
137
+ const value = raw;
138
+ if (value.version !== 1 || !Array.isArray(value.roots))
139
+ return null;
140
+ const roots = value.roots
141
+ .filter((item) => Boolean(item) && typeof item === "object")
142
+ .map((item) => ({
143
+ path: typeof item.path === "string" ? item.path.replaceAll("\\", "/").replace(/\/+$/, "") : "",
144
+ scope: Array.isArray(item.scope) ? item.scope.filter((v) => typeof v === "string") : undefined,
145
+ mode: (item.mode === "normative" || item.mode === "advisory" ? item.mode : undefined),
146
+ description: typeof item.description === "string" ? item.description : undefined,
147
+ }))
148
+ .filter((item) => isValidFrontendSpecRoot(item.path));
149
+ return {
150
+ version: 1,
151
+ roots,
152
+ discovery: value.discovery && typeof value.discovery === "object" ? value.discovery : undefined,
153
+ };
154
+ }
155
+ /**
156
+ * Resolve only unambiguous symlink-root aliases. The physical target is never
157
+ * emitted as a candidate path: it merely lets task-source wording such as
158
+ * `ai_doc/rules.md` resolve to the stable repo-local path `ai_workspace/rules.md`.
159
+ */
160
+ export async function resolveFrontendSpecRootAliases(repoRoot, specRoots) {
161
+ const candidates = [];
162
+ for (const logicalRoot of specRoots) {
163
+ const full = path.join(repoRoot, logicalRoot);
164
+ try {
165
+ if (!(await lstat(full)).isSymbolicLink())
166
+ continue;
167
+ const alias = path.basename(await realpath(full));
168
+ if (alias && alias !== path.basename(logicalRoot)) {
169
+ candidates.push({ alias, logicalRoot });
170
+ }
171
+ }
172
+ catch {
173
+ // Missing/broken links are handled by normal candidate discovery drift.
174
+ }
175
+ }
176
+ const counts = new Map();
177
+ for (const item of candidates)
178
+ counts.set(item.alias, (counts.get(item.alias) ?? 0) + 1);
179
+ return candidates
180
+ .filter((item) => counts.get(item.alias) === 1)
181
+ .sort((a, b) => a.alias.localeCompare(b.alias));
182
+ }
84
183
  /**
85
184
  * Resolve the governance subtree root from harness.json.governanceRoot.
86
185
  * Falls back to DEFAULT_OPENSPEC_GOVERNANCE_ROOT when the manifest is missing,
@@ -105,27 +204,52 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
105
204
  .filter(Boolean);
106
205
  const collected = [];
107
206
  let excludedGovernanceCount = 0;
207
+ let scannedFiles = 0;
208
+ let scannedBytes = 0;
209
+ const warnings = [];
210
+ const maxDepth = options.maxDepth ?? 4;
211
+ const maxFiles = options.maxFiles ?? MAX_OPENSPEC_CANDIDATE_PATHS;
212
+ const maxBytes = options.maxBytes ?? 1024 * 1024;
213
+ const timeoutMs = options.timeoutMs ?? 2000;
214
+ const startedAt = Date.now();
215
+ // Metadata / bookkeeping directories that are not normative design specs:
216
+ // changelogs, ADR/history archives, proposals under review, and scratch
217
+ // state. Reading them inflates the candidate list (and the plan prompt)
218
+ // with files no component selection should cite. A task that genuinely
219
+ // needs one can still reference it explicitly — the prewrite gate
220
+ // candidates come from this discovery, but explicit task-source citations
221
+ // flow through openspecMandatoryPaths independently.
222
+ const DEFAULT_EXCLUDED_SPEC_PREFIXES = [
223
+ "openspec/changes",
224
+ "openspec/project-specs/changes",
225
+ "openspec/project-specs/changelog",
226
+ "openspec/project-specs/history",
227
+ "openspec/project-specs/archive",
228
+ "openspec/project-specs/decisions",
229
+ "openspec/project-specs/proposals",
230
+ ];
108
231
  const isExcluded = (rel) => {
109
232
  if (isGovernanceSubtreePath(rel, governanceRoot))
110
233
  return true;
234
+ if (DEFAULT_EXCLUDED_SPEC_PREFIXES.some((prefix) => rel === prefix || rel.startsWith(prefix + "/"))) {
235
+ return true;
236
+ }
111
237
  return extraExcludedPrefixes.some((prefix) => rel === prefix || rel.startsWith(prefix + "/"));
112
238
  };
113
- for (const specDir of OPENSPEC_SPEC_DIRS) {
239
+ for (const specDir of options.specRoots ?? [...OPENSPEC_SPEC_DIRS]) {
114
240
  const root = path.join(repoRoot, specDir);
115
241
  if (!(await exists(root)))
116
242
  continue;
117
243
  await walk(root, 0);
118
244
  }
119
- // openspec/schemas/** and openspec/project-specs/** sort first; remaining
120
- // ai_workspace/** business files follow. Dedupe preserves discovery order.
245
+ // Keep the established default-root priority for deterministic compatibility;
246
+ // configured extra roots are appended in lexical order.
121
247
  const unique = [...new Set(collected)];
122
- const openspecPaths = unique
123
- .filter((candidate) => candidate.startsWith("openspec/"))
124
- .sort();
125
- const aiWorkspacePaths = unique
126
- .filter((candidate) => candidate.startsWith("ai_workspace/"))
248
+ const defaultPaths = OPENSPEC_SPEC_DIRS.flatMap((root) => unique.filter((candidate) => candidate.startsWith(root + "/")).sort());
249
+ const customPaths = unique
250
+ .filter((candidate) => !defaultPaths.includes(candidate))
127
251
  .sort();
128
- const deduped = [...openspecPaths, ...aiWorkspacePaths];
252
+ const deduped = [...defaultPaths, ...customPaths];
129
253
  const truncated = deduped.length > MAX_OPENSPEC_CANDIDATE_PATHS;
130
254
  const paths = truncated
131
255
  ? deduped.slice(0, MAX_OPENSPEC_CANDIDATE_PATHS)
@@ -136,10 +260,19 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
136
260
  excludedGovernanceCount,
137
261
  truncated,
138
262
  maxCandidates: MAX_OPENSPEC_CANDIDATE_PATHS,
263
+ warnings,
139
264
  };
140
265
  async function walk(dir, depth) {
141
- if (depth > 4)
266
+ if (Date.now() - startedAt >= timeoutMs) {
267
+ if (!warnings.includes("timeout-exceeded"))
268
+ warnings.push("timeout-exceeded");
269
+ return;
270
+ }
271
+ if (depth > maxDepth) {
272
+ if (!warnings.includes("max-depth-exceeded"))
273
+ warnings.push("max-depth-exceeded");
142
274
  return;
275
+ }
143
276
  let entries = [];
144
277
  try {
145
278
  entries = (await readdir(dir)).sort();
@@ -148,12 +281,30 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
148
281
  return;
149
282
  }
150
283
  for (const name of entries) {
284
+ if (Date.now() - startedAt >= timeoutMs) {
285
+ if (!warnings.includes("timeout-exceeded"))
286
+ warnings.push("timeout-exceeded");
287
+ return;
288
+ }
289
+ if (scannedFiles >= maxFiles) {
290
+ if (!warnings.includes("max-files-exceeded"))
291
+ warnings.push("max-files-exceeded");
292
+ return;
293
+ }
151
294
  const full = path.join(dir, name);
152
295
  try {
153
296
  const info = await stat(full);
154
297
  if (info.isDirectory())
155
298
  await walk(full, depth + 1);
156
299
  else if (OPENSPEC_SPEC_EXT_RE.test(name)) {
300
+ const size = info.size;
301
+ if (scannedBytes + size > maxBytes) {
302
+ if (!warnings.includes("max-bytes-exceeded"))
303
+ warnings.push("max-bytes-exceeded");
304
+ return;
305
+ }
306
+ scannedFiles++;
307
+ scannedBytes += size;
157
308
  const rel = path.relative(repoRoot, full).replace(/\\/g, "/");
158
309
  if (isExcluded(rel)) {
159
310
  excludedGovernanceCount++;
@@ -241,6 +392,14 @@ function emptyClassified() {
241
392
  * project-specific file names. Basename stems use word-boundary matching so
242
393
  * e.g. `*api*` (case-insensitive) maps to `rule.api` without hardcoding names.
243
394
  */
395
+ /** True when any path segment (other than the file itself) carries a
396
+ * component/theme kind marker. Directory-organized libraries such as
397
+ * `ui/ucp-components-md/AdvancedSearch.md` rely on this; templates/ and
398
+ * rules/ are handled by their own branches before this fallback runs. */
399
+ function segmentMatchesComponentKind(segments) {
400
+ const dirSegments = segments.slice(0, -1);
401
+ return dirSegments.some((segment) => /\bcomponents?\b/.test(segment.toLowerCase()));
402
+ }
244
403
  function classifyOpenspecPaths(paths) {
245
404
  const classified = emptyClassified();
246
405
  const ruleStem = (basename) => {
@@ -260,7 +419,7 @@ function classifyOpenspecPaths(paths) {
260
419
  return "other";
261
420
  };
262
421
  for (const candidate of paths) {
263
- if (!isOpenspecSpecFilePath(candidate))
422
+ if (!OPENSPEC_SPEC_EXT_RE.test(candidate))
264
423
  continue;
265
424
  const segments = candidate.split("/");
266
425
  const basename = segments[segments.length - 1] ?? "";
@@ -279,8 +438,17 @@ function classifyOpenspecPaths(paths) {
279
438
  classified.theme.push(candidate);
280
439
  else if (/\bcomponents?\b/.test(lower))
281
440
  classified.component.push(candidate);
282
- else
441
+ else if (segmentMatchesComponentKind(segments)) {
442
+ // Directory-organized component libraries (e.g.
443
+ // ui/ucp-components-md/AdvancedSearch.md) carry the kind in the
444
+ // parent directory name, not the file name. The parent-dir rule
445
+ // is a fallback so such layouts still land in the component
446
+ // bucket instead of being dropped into uiOther.
447
+ classified.component.push(candidate);
448
+ }
449
+ else {
283
450
  classified.uiOther.push(candidate);
451
+ }
284
452
  }
285
453
  else {
286
454
  classified.advisoryOther.push(candidate);
@@ -325,6 +493,7 @@ function toOpenspecDiscovery(discovery, designEvidence) {
325
493
  excludedGovernanceCount: discovery.excludedGovernanceCount,
326
494
  truncated: discovery.truncated,
327
495
  maxCandidates: discovery.maxCandidates,
496
+ warnings: discovery.warnings,
328
497
  };
329
498
  }
330
499
  export function buildAdapterGuidance(capability) {
@@ -361,13 +530,25 @@ export function buildAdapterGuidance(capability) {
361
530
  * Discover frontend project capability from repo root.
362
531
  * Strong evidence: package.json direct deps + config/bootstrap files.
363
532
  */
364
- export async function discoverFrontendProjectCapability(repoRoot) {
533
+ export async function discoverFrontendProjectCapability(repoRoot, options = {}) {
534
+ const registry = await readFrontendSpecRegistry(repoRoot);
535
+ const registryRoots = registry?.roots
536
+ .filter((root) => !root.scope || root.scope.includes("frontend"))
537
+ .map((root) => root.path) ?? [];
538
+ const discoveryOptions = {
539
+ ...options,
540
+ specRoots: [...new Set([...OPENSPEC_SPEC_DIRS, ...(options.specRoots ?? []), ...registryRoots])],
541
+ maxDepth: registry?.discovery?.maxDepth ?? options.maxDepth,
542
+ maxFiles: registry?.discovery?.maxFiles ?? options.maxFiles,
543
+ maxBytes: registry?.discovery?.maxBytes ?? options.maxBytes,
544
+ timeoutMs: registry?.discovery?.timeoutMs ?? options.timeoutMs,
545
+ };
365
546
  const evidencePaths = [];
366
547
  const reasons = [];
367
548
  const pkgPath = path.join(repoRoot, "package.json");
368
549
  const pkgRaw = await readJson(pkgPath);
369
550
  if (!pkgRaw || typeof pkgRaw !== "object") {
370
- const discovery = await listOpenspecDiscovery(repoRoot);
551
+ const discovery = await listOpenspecDiscovery(repoRoot, discoveryOptions);
371
552
  const designEvidence = buildDesignEvidence(discovery.paths);
372
553
  const base = {
373
554
  schemaVersion: 1,
@@ -540,7 +721,7 @@ export async function discoverFrontendProjectCapability(repoRoot) {
540
721
  router = router ?? "next-router";
541
722
  if (hasDep(deps, "vue-router"))
542
723
  router = "vue-router";
543
- const discovery = await listOpenspecDiscovery(repoRoot);
724
+ const discovery = await listOpenspecDiscovery(repoRoot, discoveryOptions);
544
725
  const designEvidence = buildDesignEvidence(discovery.paths);
545
726
  if (discovery.paths.length)
546
727
  evidencePaths.push(...discovery.paths.slice(0, 5));
@@ -55,6 +55,25 @@ export function observationBlocksPrepare(state) {
55
55
  }
56
56
  return null;
57
57
  }
58
+ /**
59
+ * 精确 identity 绑定的人类 resolution 判定(AC-001 / AC-005):decidedBy==='human'、
60
+ * decision trim 后非空、sourceRef 与冲突 id 精确匹配三者同时成立才算有效;model/
61
+ * system 伪造判定、stale sourceRef(不匹配当前冲突 identity)或空/空白 decision 一律
62
+ * 不解锁。resolution 信息取自 draft 层(不重排流水线,listPrepareGaps 仍先于 ledger
63
+ * 构建执行)。
64
+ */
65
+ export function hasValidHumanConflictResolution(draft, conflictId) {
66
+ for (const resolution of draft.sourceResolutions ?? []) {
67
+ if (resolution.sourceRef !== conflictId)
68
+ continue;
69
+ if (resolution.decidedBy !== "human")
70
+ continue;
71
+ if (!resolution.decision?.trim())
72
+ continue;
73
+ return true;
74
+ }
75
+ return false;
76
+ }
58
77
  export function listPrepareGaps(input) {
59
78
  const gaps = [];
60
79
  const risks = [];
@@ -108,6 +127,12 @@ export function listPrepareGaps(input) {
108
127
  }
109
128
  if (input.acceptanceConflicts && input.acceptanceConflicts.length > 0) {
110
129
  for (const conflict of input.acceptanceConflicts) {
130
+ // AC-001:有效 human resolution(human + 非空 decision + sourceRef 与冲突 id
131
+ // 精确匹配)解除该 id 的 legacy blocking gap;无 resolution 或 resolution 不
132
+ // 满足三条件(AC-005:伪造判定 / stale sourceRef / 空 decision)仍发射阻断。
133
+ if (hasValidHumanConflictResolution(input.draft, conflict.id)) {
134
+ continue;
135
+ }
111
136
  gaps.push({
112
137
  code: "ACCEPTANCE_ID_CONFLICT",
113
138
  level: "blocking",
@@ -246,3 +271,166 @@ export function hasBlockingGaps(gaps) {
246
271
  export function hasBlockingRisks(risks) {
247
272
  return risks.some((risk) => risk.level === "blocking");
248
273
  }
274
+ /**
275
+ * Source fidelity ledger 分级 fail-closed 阻断(AC-003 / AC-FIDELITY-003):
276
+ * 高风险未映射/排除、unresolved、冲突返回稳定错误并阻断;低风险 context-only 仅当
277
+ * 理由完整且独立 reviewer pass 后继续;duplicate 保留全部来源(warning 可见)。
278
+ * 仅 taskKind 为 frontend-implementation 时全量阻断,其他 taskKind 保持 warning。
279
+ */
280
+ export function appendLedgerGaps(input) {
281
+ const { gaps, risks } = input;
282
+ if (!input.ledgerSummary?.present)
283
+ return;
284
+ const blocking = input.taskKind === "frontend-implementation";
285
+ const level = blocking ? "blocking" : "warning";
286
+ const summary = input.ledgerSummary;
287
+ if (summary.unresolvedCount > 0) {
288
+ const gap = {
289
+ code: "SOURCE_FIDELITY_UNRESOLVED",
290
+ level,
291
+ message: `ledger 有 ${summary.unresolvedCount} 个 unresolved fragment(需人工 sourceResolutions 确认后才可继续)`,
292
+ details: {
293
+ fragmentIds: summary.exclusions
294
+ .filter((e) => /^REQ-SRC-/.test(e.fragmentId) || e.fragmentId.startsWith("AC-"))
295
+ .slice(0, 10)
296
+ .map((e) => e.fragmentId),
297
+ },
298
+ selfHealing: blocking
299
+ ? {
300
+ action: "reimport-prd",
301
+ steps: [
302
+ "检查原始 PRD 中无法映射到 canonical AC 的可执行片段(unresolved)",
303
+ "补齐/修正 PRD 使每个可执行片段都能绑定 AC,或由人工在 sourceResolutions 中确认排除理由",
304
+ "重新 importPrd(同 taskId)后 task advance 重建 ledger",
305
+ ],
306
+ }
307
+ : undefined,
308
+ };
309
+ gaps.push(gap);
310
+ risks.push({
311
+ code: "SOURCE_FIDELITY_UNRESOLVED",
312
+ level: blocking ? "blocking" : "elevated",
313
+ message: gap.message,
314
+ });
315
+ }
316
+ if (summary.conflictCount > 0) {
317
+ const gap = {
318
+ code: "SOURCE_FIDELITY_CONFLICT",
319
+ level,
320
+ message: `ledger 检测到 ${summary.conflictCount} 个 AC 文本冲突(同一 ID 不同文本)`,
321
+ selfHealing: blocking
322
+ ? {
323
+ action: "reimport-prd",
324
+ steps: [
325
+ "统一同一 AC ID 的文本后重新 importPrd(同 taskId)",
326
+ "或由人工在 sourceResolutions 中声明冲突取舍(conflict → decision)",
327
+ ],
328
+ }
329
+ : undefined,
330
+ };
331
+ gaps.push(gap);
332
+ risks.push({
333
+ code: "SOURCE_FIDELITY_CONFLICT",
334
+ level: blocking ? "blocking" : "elevated",
335
+ message: gap.message,
336
+ });
337
+ }
338
+ if (summary.failureCode === "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED") {
339
+ const gap = {
340
+ code: "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
341
+ level,
342
+ message: "ledger 存在高风险 acceptance 片段未映射到 canonical requirement",
343
+ selfHealing: blocking
344
+ ? {
345
+ action: "reimport-prd",
346
+ steps: [
347
+ "为每个未覆盖的高风险片段补齐 canonical AC(或人工 sourceResolutions 确认)",
348
+ "重新 importPrd(同 taskId)后 task advance 重建 ledger",
349
+ ],
350
+ }
351
+ : undefined,
352
+ };
353
+ gaps.push(gap);
354
+ risks.push({
355
+ code: "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
356
+ level: blocking ? "blocking" : "elevated",
357
+ message: gap.message,
358
+ });
359
+ }
360
+ for (const error of input.ledgerErrors ?? []) {
361
+ if (error.code === "SOURCE_FIDELITY_LEDGER_MISSING") {
362
+ const gap = {
363
+ code: "SOURCE_FIDELITY_LEDGER_MISSING",
364
+ level,
365
+ message: error.message,
366
+ };
367
+ gaps.push(gap);
368
+ }
369
+ // AC-BLOCK-001:canonical requirement 缺少 fragment/sourceRef 证据、来源引用
370
+ // 无效、fragment 无效均为 blocking gap(frontend task),保证 prepare/apply
371
+ // 以稳定码 fail closed(appendLedgerGaps 是 prepare 补洞的唯一入口)。
372
+ if (error.code === "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED" ||
373
+ error.code === "SOURCE_FIDELITY_SOURCE_REF_INVALID" ||
374
+ error.code === "SOURCE_FIDELITY_FRAGMENT_INVALID") {
375
+ const gap = {
376
+ code: error.code,
377
+ level,
378
+ message: error.message,
379
+ selfHealing: blocking
380
+ ? {
381
+ action: "reimport-prd",
382
+ steps: [
383
+ "检查 PRD 中每个 canonical requirement 是否都有可审计的 fragment/sourceRef 证据",
384
+ "补齐缺失的验收标准映射或修正引用后重新 importPrd(同 taskId)",
385
+ "重新 importPrd(同 taskId)后 task advance 重建 ledger",
386
+ ],
387
+ }
388
+ : undefined,
389
+ };
390
+ gaps.push(gap);
391
+ risks.push({
392
+ code: error.code,
393
+ level: blocking ? "blocking" : "elevated",
394
+ message: error.message,
395
+ });
396
+ }
397
+ if (error.code === "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED" &&
398
+ blocking) {
399
+ // 低风险 context-only 仅独立 reviewer pass 放行;未通过则 frontend 任务阻断。
400
+ const gap = {
401
+ code: "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED",
402
+ level: "blocking",
403
+ message: error.message,
404
+ selfHealing: {
405
+ action: "reimport-prd",
406
+ steps: [
407
+ "检查 context-only 片段是否缺少完整排除理由,或由人工在 sourceResolutions 中确认",
408
+ "重新 importPrd(同 taskId)后 task advance 重建 ledger 并复核 reviewer 判定",
409
+ ],
410
+ },
411
+ };
412
+ gaps.push(gap);
413
+ }
414
+ }
415
+ // 低风险 context-only:记录并提示排除理由/独立 reviewer 要求(不阻断普通任务)。
416
+ if (summary.contextOnlyCount > 0) {
417
+ gaps.push({
418
+ code: "SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED",
419
+ level: "warning",
420
+ message: `ledger 有 ${summary.contextOnlyCount} 个 context-only fragment(低风险排除;frontend 任务需完整理由且独立 reviewer pass)`,
421
+ details: {
422
+ fragmentIds: summary.exclusions
423
+ .filter((e) => e.fragmentId.startsWith("REQ-SRC-"))
424
+ .slice(0, 10)
425
+ .map((e) => e.fragmentId),
426
+ },
427
+ });
428
+ }
429
+ if (summary.duplicateCount > 0) {
430
+ gaps.push({
431
+ code: "SOURCE_FIDELITY_DUPLICATE_PRESERVED",
432
+ level: "warning",
433
+ message: `ledger 保留 ${summary.duplicateCount} 个 duplicate fragment(duplicate 保留全部来源)`,
434
+ });
435
+ }
436
+ }