@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
@@ -0,0 +1,13 @@
1
+ export interface DagNodeRemark {
2
+ failed: boolean;
3
+ text: string;
4
+ isErrorSummary: boolean;
5
+ }
6
+
7
+ export function resolveNodeRemark(node: {
8
+ status?: string | null;
9
+ errorPreview?: string | null;
10
+ label?: string | null;
11
+ nodeId: string;
12
+ rank?: string | number | null;
13
+ } | null | undefined): DagNodeRemark;
@@ -70,6 +70,7 @@ import {
70
70
  dashboardVisibleFeatures,
71
71
  findDagForRun,
72
72
  } from "../dag-model.js";
73
+ import { describeDagNodePurpose } from "../dag-node-purpose.js";
73
74
  import { layoutDag } from "../dag-layout.js";
74
75
  import {
75
76
  showView,
@@ -90,6 +91,7 @@ import {
90
91
  closeDagInspector,
91
92
  selectDagNode,
92
93
  renderDagInspector,
94
+ mergeNodeProcess,
93
95
  } from "./dag-inspector.js";
94
96
  import { mergeSessionEvents } from "./session-timeline.js";
95
97
 
@@ -135,7 +137,7 @@ export function extractFirstErrorSummary(errorPreview) {
135
137
  return effective ?? "";
136
138
  }
137
139
 
138
- /** Resolve 备注 cell content for a DAG node (failed → summary; else label). */
140
+ /** Resolve 备注 cell content for a DAG node (failed → summary; else purpose). */
139
141
  export function resolveNodeRemark(node) {
140
142
  const failed = isNodeFailed(node?.status);
141
143
  if (failed) {
@@ -146,10 +148,10 @@ export function resolveNodeRemark(node) {
146
148
  isErrorSummary: Boolean(summary),
147
149
  };
148
150
  }
149
- const label = typeof node?.label === "string" ? node.label.trim() : "";
151
+ const purpose = describeDagNodePurpose(node?.nodeId, node);
150
152
  return {
151
153
  failed: false,
152
- text: label || "—",
154
+ text: purpose || "—",
153
155
  isErrorSummary: false,
154
156
  };
155
157
  }
@@ -362,7 +364,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
362
364
  "page-action-btn",
363
365
  "在操作台中止此运行 →",
364
366
  );
365
- interruptLink.href = `/#/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
367
+ interruptLink.href = `/#/operate/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
366
368
  interruptLink.setAttribute("data-testid", "inspect-dag-interrupt-link");
367
369
  interruptLink.title = "只跳转到操作台 Recovery,不会在观测页执行中止";
368
370
  primaryEntries.push(["协作中止", interruptLink]);
@@ -451,13 +453,19 @@ export async function renderDagDetail(dagRunId, initial = true) {
451
453
  nodesEl.scrollLeft = nodesScrollLeft;
452
454
 
453
455
  if (uiState.dagInspectorOpen && uiState.selectedDagNodeId) {
456
+ const selectedNode = nodes.find(
457
+ (node) => node.nodeId === uiState.selectedDagNodeId,
458
+ );
454
459
  if (!uiState.sessionEventIdentity) {
455
460
  uiState.sessionEventIdentity = makeSessionEventIdentity(
456
461
  dagRunId,
457
462
  uiState.selectedDagNodeId,
458
463
  );
459
464
  }
460
- await mergeSessionEvents(dagRunId, uiState.selectedDagNodeId);
465
+ await Promise.all([
466
+ mergeSessionEvents(dagRunId, uiState.selectedDagNodeId),
467
+ selectedNode ? mergeNodeProcess(dagRunId, selectedNode) : Promise.resolve(),
468
+ ]);
461
469
  }
462
470
  renderDagInspector(
463
471
  dagRunId,
@@ -474,6 +482,7 @@ export function startDagPolling(dagRunId, initial = true) {
474
482
  const active = await renderDagDetail(dagRunId, isInitial);
475
483
  if (generation !== uiState.pollingGeneration || !active) return;
476
484
  const delay = detailPollDelay("running", isPageVisible());
485
+ if (delay == null) return;
477
486
  uiState.dagPollTimer = setTimeout(() => {
478
487
  uiState.dagPollTimer = null;
479
488
  void poll(false);
@@ -84,7 +84,7 @@ let dashboardLoadingNode = null;
84
84
 
85
85
  /**
86
86
  * 「昨夜班次」条带:夜间结果在观测侧的唯一一级呈现(替代旧「夜间任务」tab)。
87
- * 管理动作(晨审处置)始终落在操作侧 /#/night,保持 Inspect 只读不变式。
87
+ * 管理动作(晨审处置)始终落在操作侧 /#/operate/night,保持 Inspect 只读不变式。
88
88
  */
89
89
  async function renderNightStrip() {
90
90
  const strip = document.getElementById("dashboard-night");
@@ -118,14 +118,14 @@ async function renderNightStrip() {
118
118
 
119
119
  const actions = el("div", "night-strip-actions");
120
120
  const detail = el("a", "page-action-btn", "夜间详情");
121
- detail.href = "#/night";
121
+ detail.href = "/#/inspect/night";
122
122
  detail.addEventListener("click", (e) => {
123
123
  e.preventDefault();
124
124
  navigate("/night");
125
125
  });
126
126
  actions.appendChild(detail);
127
127
  const manage = el("a", "page-action-btn night-strip-manage", "晨审处置 →");
128
- manage.href = "/#/night";
128
+ manage.href = "/#/operate/night";
129
129
  manage.title = "夜间计划的管理与晨审处置在操作台完成(Inspect 保持只读)";
130
130
  actions.appendChild(manage);
131
131
  strip.appendChild(actions);
@@ -738,10 +738,12 @@ export function startDashboardPolling(scrollTo) {
738
738
  const poll = async (initialScrollTo) => {
739
739
  await renderDashboard(initialScrollTo);
740
740
  if (generation !== uiState.pollingGeneration) return;
741
+ const delay = dashboardPollDelay();
742
+ if (delay == null) return;
741
743
  uiState.dashboardTimer = setTimeout(() => {
742
744
  uiState.dashboardTimer = null;
743
745
  void poll();
744
- }, dashboardPollDelay());
746
+ }, delay);
745
747
  };
746
748
  void poll(scrollTo);
747
749
  }
@@ -101,11 +101,14 @@ export async function renderFailures() {
101
101
 
102
102
  if (tasks.length === 0) {
103
103
  clearNode(listEl);
104
+ // 2026-08-22 (B-FIND-02): 空态文案与静态 #failures-empty / 页面标题
105
+ // 「异常 Task」保持一致;旧文案「当前没有失败或阻塞的 Task。」与静态
106
+ // HTML 及回归目录 AC-FE-INS-004 漂移,导致 frontend-test 字面 find 0 命中。
104
107
  if (emptyEl) {
105
108
  emptyEl.hidden = false;
106
- mountViewState(emptyEl, "empty", "当前没有失败或阻塞的 Task。");
109
+ mountViewState(emptyEl, "empty", "当前没有异常 Task");
107
110
  } else {
108
- mountViewState(listEl, "empty", "当前没有失败或阻塞的 Task。");
111
+ mountViewState(listEl, "empty", "当前没有异常 Task");
109
112
  }
110
113
  return;
111
114
  }
@@ -46,8 +46,8 @@ export async function renderNightJobs(scheduleId) {
46
46
  showView("night");
47
47
  bindNightToolbar();
48
48
  const crumbs = [
49
- { label: UI_TEXT.dashboard, href: "#/" },
50
- { label: UI_TEXT.nightJobs, href: "#/night" },
49
+ { label: UI_TEXT.dashboard, href: "/#/inspect" },
50
+ { label: UI_TEXT.nightJobs, href: "/#/inspect/night" },
51
51
  ];
52
52
  if (scheduleId) crumbs.push({ label: scheduleId });
53
53
  setBreadcrumb(crumbs);
@@ -93,6 +93,7 @@ function startPoolAutoPolling() {
93
93
  if (generation !== uiState.pollingGeneration) return;
94
94
  if (!poolAutoRefreshEnabled()) return;
95
95
  const delay = isPageVisible() ? POOL_POLL_MS : dashboardPollDelay();
96
+ if (delay == null) return;
96
97
  uiState.poolTimer = setTimeout(() => {
97
98
  uiState.poolTimer = null;
98
99
  void poll();
@@ -50,6 +50,7 @@ import {
50
50
  } from "../relations.js";
51
51
  import { copyText, renderRecommendedCommand } from "../copy.js";
52
52
  import {
53
+ fetchWithWorkspaceScope,
53
54
  isPageVisible,
54
55
  detailPollDelay,
55
56
  dashboardPollDelay,
@@ -126,7 +127,7 @@ export function renderInlineArtifactList(container, artifacts) {
126
127
  list.appendChild(li);
127
128
  void (async () => {
128
129
  try {
129
- const res = await fetch(artifactUrl(artifactPath));
130
+ const res = await fetchWithWorkspaceScope(artifactUrl(artifactPath));
130
131
  const text = await res.text();
131
132
  if (!res.ok) {
132
133
  let message = text;
@@ -444,6 +445,7 @@ export function startRunPolling(workerRunId, initial = true) {
444
445
  const active = await renderRun(workerRunId, isInitial);
445
446
  if (generation !== uiState.pollingGeneration || !active) return;
446
447
  const delay = detailPollDelay("running", isPageVisible());
448
+ if (delay == null) return;
447
449
  uiState.runPollTimer = setTimeout(() => {
448
450
  uiState.runPollTimer = null;
449
451
  void poll(false);
@@ -250,6 +250,54 @@ export function buildSessionTimelineItems(events) {
250
250
  return items;
251
251
  }
252
252
 
253
+ /**
254
+ * Split a node's shared session-events log into per-attempt segments. Every
255
+ * attempt appends one agent run beginning with agent_start; events recorded
256
+ * before the first agent_start (defensive) stay in an implicit first segment.
257
+ */
258
+ export function splitSessionEventsByAttempt(events) {
259
+ const segments = [];
260
+ let current = null;
261
+ for (const event of events) {
262
+ if (typeof event?.type === "string" && event.type === "agent_start") {
263
+ current = [];
264
+ segments.push(current);
265
+ }
266
+ if (current === null) {
267
+ current = [];
268
+ segments.push(current);
269
+ }
270
+ current.push(event);
271
+ }
272
+ return segments;
273
+ }
274
+
275
+ /**
276
+ * Resolve the events visible in the「执行过程」tab for the selected execution
277
+ * key. attempt-N shows only that attempt's segment when the shared log carries
278
+ * multiple attempts; other keys (pass-N, null) keep the full stream.
279
+ */
280
+ export function resolveTimelineVisibleEvents(events, executionKey) {
281
+ const match =
282
+ typeof executionKey === "string"
283
+ ? /^attempt-(\d+)$/.exec(executionKey)
284
+ : null;
285
+ if (!match) return { events, note: null };
286
+ const segments = splitSessionEventsByAttempt(events);
287
+ if (segments.length <= 1) return { events, note: null };
288
+ const requested = Number(match[1]);
289
+ if (requested >= 1 && requested <= segments.length) {
290
+ return {
291
+ events: segments[requested - 1],
292
+ note: `已按第 ${requested} 次执行过滤 · 共 ${segments.length} 次执行`,
293
+ };
294
+ }
295
+ return {
296
+ events,
297
+ note: `未检测到第 ${requested} 次执行的边界(共 ${segments.length} 次执行),暂显示全部事件`,
298
+ };
299
+ }
300
+
253
301
  /**
254
302
  * Build rich timeline items: merge tool start/end by toolCallId, collapse
255
303
  * protocol noise, surface turn thinking/text without dumping agent_end.messages.
@@ -439,12 +487,25 @@ function ensureOpenDetailsSet() {
439
487
  return uiState.sessionTimelineOpenDetails;
440
488
  }
441
489
 
442
- function bindDetailsToggle(details, detailKey) {
490
+ function closedDetailKey(detailKey) {
491
+ return `${detailKey}::user-closed`;
492
+ }
493
+
494
+ function bindDetailsToggle(details, detailKey, defaultOpen = false) {
443
495
  const openSet = ensureOpenDetailsSet();
444
- if (openSet.has(detailKey)) details.open = true;
496
+ if (defaultOpen) {
497
+ if (!openSet.has(closedDetailKey(detailKey))) details.open = true;
498
+ } else if (openSet.has(detailKey)) {
499
+ details.open = true;
500
+ }
445
501
  details.addEventListener("toggle", () => {
446
- if (details.open) openSet.add(detailKey);
447
- else openSet.delete(detailKey);
502
+ if (details.open) {
503
+ openSet.add(detailKey);
504
+ openSet.delete(closedDetailKey(detailKey));
505
+ } else {
506
+ openSet.delete(detailKey);
507
+ openSet.add(closedDetailKey(detailKey));
508
+ }
448
509
  });
449
510
  }
450
511
 
@@ -599,7 +660,7 @@ function renderProtocolCard(item) {
599
660
  const details = document.createElement("details");
600
661
  details.className = "process-timeline-details";
601
662
  details.dataset.detailKey = item.detailKey;
602
- bindDetailsToggle(details, item.detailKey);
663
+ bindDetailsToggle(details, item.detailKey, true);
603
664
  const summary = document.createElement("summary");
604
665
  summary.className = "process-timeline-summary";
605
666
  summary.textContent = `消息收发 · ${item.protocolCount} 条协议事件`;
@@ -644,7 +705,7 @@ export async function mergeSessionEvents(dagRunId, nodeId) {
644
705
  }
645
706
  }
646
707
 
647
- export function renderSessionTimeline(content, nodeId) {
708
+ export function renderSessionTimeline(content, nodeId, options = {}) {
648
709
  content.dataset.dagRunId = uiState.currentDagRunId ?? "";
649
710
  content.dataset.nodeId = nodeId ?? "";
650
711
  content.classList.add("process-timeline-scroll");
@@ -653,6 +714,14 @@ export function renderSessionTimeline(content, nodeId) {
653
714
  return;
654
715
  }
655
716
 
717
+ const visible = resolveTimelineVisibleEvents(
718
+ uiState.sessionEvents,
719
+ options.executionKey,
720
+ );
721
+ if (visible.note) {
722
+ content.appendChild(el("p", "process-timeline-note", visible.note));
723
+ }
724
+
656
725
  const showProtocol = Boolean(uiState.sessionTimelineShowProtocol);
657
726
  const toolbar = el("div", "process-timeline-toolbar");
658
727
  const protocolToggle = document.createElement("button");
@@ -668,17 +737,17 @@ export function renderSessionTimeline(content, nodeId) {
668
737
  const scrollTop = parent.scrollTop;
669
738
  const scrollLeft = parent.scrollLeft;
670
739
  while (parent.firstChild) parent.removeChild(parent.firstChild);
671
- renderSessionTimeline(parent, nodeId);
740
+ renderSessionTimeline(parent, nodeId, options);
672
741
  parent.scrollTop = scrollTop;
673
742
  parent.scrollLeft = scrollLeft;
674
743
  });
675
744
  toolbar.appendChild(protocolToggle);
676
745
  content.appendChild(toolbar);
677
746
 
678
- const timelineItems = buildRichTimelineItems(uiState.sessionEvents, {
747
+ const timelineItems = buildRichTimelineItems(visible.events, {
679
748
  showProtocol,
680
749
  });
681
- const hasEventTimes = uiState.sessionEvents.some((event) =>
750
+ const hasEventTimes = visible.events.some((event) =>
682
751
  Boolean(sessionEventTimestamp(event)),
683
752
  );
684
753
  if (!hasEventTimes) {
@@ -34,6 +34,7 @@ import {
34
34
  repoBaseName,
35
35
  parseMarkdownBlocks,
36
36
  markdownLinkHref,
37
+ formatLedgerSummary,
37
38
  } from "../format.js";
38
39
  import { parseRoute, navigate, buildHashPath } from "../router.js";
39
40
  import {
@@ -85,7 +86,7 @@ export async function renderTaskDetail(featureId, taskId) {
85
86
  showView("task");
86
87
  setBreadcrumb([
87
88
  { label: UI_TEXT.dashboard, href: "#/" },
88
- { label: "资源池", href: "#/pool" },
89
+ { label: "资源池", href: "/#/inspect/pool" },
89
90
  { label: displayLabel },
90
91
  ]);
91
92
 
@@ -94,6 +95,7 @@ export async function renderTaskDetail(featureId, taskId) {
94
95
  const linksEl = document.getElementById("task-links");
95
96
  const evidenceEl = document.getElementById("task-evidence");
96
97
  const runsEl = document.getElementById("task-runs");
98
+ const ledgerEl = document.getElementById("task-ledger");
97
99
  if (!metaEl || !linksEl || !evidenceEl || !runsEl) return false;
98
100
  if (titleEl) titleEl.textContent = displayLabel;
99
101
  mountViewState(metaEl, "loading", "加载 Task…");
@@ -252,9 +254,62 @@ export async function renderTaskDetail(featureId, taskId) {
252
254
  const active = ["running", "pending", "blocked", "stale"].includes(
253
255
  (task.status ?? "").toLowerCase(),
254
256
  );
257
+ if (ledgerEl) {
258
+ void renderTaskLedger(ledgerEl, taskId);
259
+ }
255
260
  return active;
256
261
  }
257
262
 
263
+ /**
264
+ * Task 详情页 ledger 安全摘要(AC-009):从只读 GET /api/tasks/:id/ledger 拉取,
265
+ * 计数/状态/failure code + 有界排除理由;无 ledger 显示空态,失败安全降级。
266
+ */
267
+ async function renderTaskLedger(ledgerEl, taskId) {
268
+ clearNode(ledgerEl);
269
+ ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
270
+ mountViewState(ledgerEl, "loading", "加载 ledger…");
271
+ let body = null;
272
+ try {
273
+ body = await fetchJson(`/api/tasks/${encodeURIComponent(taskId)}/ledger`);
274
+ } catch {
275
+ body = null;
276
+ }
277
+ if (!body) {
278
+ mountViewState(ledgerEl, "error", "ledger 只读接口失败(安全降级)。");
279
+ return;
280
+ }
281
+ if (body.present !== true) {
282
+ mountViewState(
283
+ ledgerEl,
284
+ body.error ? "error" : "empty",
285
+ body.error
286
+ ? `ledger 不可用(${body.error})。`
287
+ : "无 source fidelity ledger(任务无引用或 ledger 缺失,不伪造计数)。",
288
+ );
289
+ return;
290
+ }
291
+ const pre = el(
292
+ "pre",
293
+ "cmd",
294
+ formatLedgerSummary({
295
+ present: true,
296
+ fragmentCount: body.fragmentCount,
297
+ canonicalRequirementCount: body.canonicalRequirementCount,
298
+ contextOnlyCount: body.contextOnlyCount,
299
+ duplicateCount: body.duplicateCount,
300
+ unresolvedCount: body.unresolvedCount,
301
+ conflictCount: body.conflictCount,
302
+ gateStatus: body.gateStatus,
303
+ failureCode: body.failureCode,
304
+ exclusions: body.exclusions ?? [],
305
+ }),
306
+ );
307
+ pre.dataset.testid = "task-ledger-summary";
308
+ clearNode(ledgerEl);
309
+ ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
310
+ ledgerEl.appendChild(pre);
311
+ }
312
+
258
313
  export async function loadOlderTaskRuns(featureId, taskId, before, runsEl) {
259
314
  if (runsEl === undefined) {
260
315
  runsEl = before;
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { z } from "zod";
6
6
  import YAML from "yaml";
7
7
  import { resolveBackendTestLayout, } from "./backend-test-layout.js";
8
+ import { resolveBackendTestManifestModules, backendManifestStatusFinding } from "./backend-test-markdown-workflow.js";
8
9
  import { applyOpenApiPartitionDomainPolicy, assessScenarioPartitionCoverage, parseScenarioPartitions, } from "./backend-test-scenario-partitions.js";
9
10
  import { backendTestCaseManifestSchema, computeCaseManifestCoverageSummary, } from "./backend-test-case-manifest.js";
10
11
  const CASE_HEADING = /^##\s+(BE-[A-Z0-9_-]+-\d{3})\b.*$/gm;
@@ -659,10 +660,18 @@ async function markdownModuleFiles(workspaceRoot, layout) {
659
660
  const resolved = layout ?? resolveBackendTestLayout(undefined);
660
661
  const dir = path.join(workspaceRoot, ...resolved.markdownDir.split("/"));
661
662
  const entries = await readdir(dir, { withFileTypes: true });
662
- return entries
663
+ const all = entries
663
664
  .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md") && entry.name.toLowerCase() !== "readme.md")
664
665
  .map((entry) => path.join(dir, entry.name))
665
666
  .sort();
667
+ // Current-round Markdown scope is the README Module Index manifest: unmanifested
668
+ // legacy .md files must not enter Case/coverage/partition statistics. Workspaces
669
+ // without a Module Index fall back to the whole directory (prior behaviour).
670
+ const manifest = await resolveBackendTestManifestModules(workspaceRoot, resolved);
671
+ if (manifest.manifestStatus !== "valid")
672
+ return [];
673
+ const declaredAbs = new Set(manifest.declaredMdFiles.map((rel) => path.join(workspaceRoot, ...rel.split("/")).toLowerCase()));
674
+ return all.filter((file) => declaredAbs.has(file.toLowerCase())).sort();
666
675
  }
667
676
  async function parseMarkdownCases(workspaceRoot, layout) {
668
677
  const result = [];
@@ -874,6 +883,16 @@ export async function analyzeBackendTestCaseCoverage(input) {
874
883
  findings.push(...coverageScope.findings);
875
884
  const matrix = parseCoverageMatrix(readme);
876
885
  findings.push(...matrix.findings);
886
+ const manifest = await resolveBackendTestManifestModules(input.workspaceRoot, layout);
887
+ // Fail closed on README Module Index entries declared but never materialized.
888
+ if (manifest.declaredMissing.length > 0) {
889
+ findings.push(`declared Markdown module is missing (Module Index): ${manifest.declaredMissing.join(", ")}`);
890
+ }
891
+ // Fail closed on every non-valid Module Index state: without a valid unique
892
+ // Module Index the current round must not re-scan historical `.md` files.
893
+ if (manifest.manifestStatus !== "valid") {
894
+ findings.push(backendManifestStatusFinding(manifest.manifestStatus, manifest.moduleIndexHeadingCount));
895
+ }
877
896
  const cases = await parseMarkdownCases(input.workspaceRoot, layout);
878
897
  for (const file of await markdownModuleFiles(input.workspaceRoot, layout)) {
879
898
  const markdown = await readFile(file, "utf8");
@@ -1109,6 +1128,15 @@ export async function analyzeBackendTestCaseCoverage(input) {
1109
1128
  "",
1110
1129
  facts.status,
1111
1130
  "",
1131
+ `- Manifest scope: ${manifest.manifestStatus === "valid" ? "declared-modules" : `fails-closed:${manifest.manifestStatus}`} (declared-missing=${manifest.declaredMissing.length}, unmanifested-advisory=${manifest.unmanifested.length}, module-index-heading-count=${manifest.moduleIndexHeadingCount})`,
1132
+ "",
1133
+ "## Unmanifested Markdown Modules",
1134
+ "",
1135
+ "- Advisory: existing .md files not declared by the README Module Index are excluded from Case/coverage/partition statistics for this round.",
1136
+ ...(manifest.unmanifested.length > 0
1137
+ ? manifest.unmanifested.map((item) => `- ${item}`)
1138
+ : ["- None"]),
1139
+ "",
1112
1140
  "## Coverage Scope",
1113
1141
  "",
1114
1142
  `- Change Classification: ${facts.coverageScope.changeClassification}`,
@@ -1500,8 +1528,26 @@ print(json.dumps(out,ensure_ascii=True))
1500
1528
  // Windows Python may expose stdin as GBK; decoding generated Chinese/emoji
1501
1529
  // source through that locale can create surrogateescape code points and make
1502
1530
  // ast.parse fail with "surrogates not allowed".
1503
- const result = spawnSync("python", ["-c", program], { input: Buffer.from(source, "utf8"), encoding: "utf8", maxBuffer: 4 * 1024 * 1024, windowsHide: true });
1504
- if (result.status !== 0 || !result.stdout.trim())
1531
+ // POSIX images usually expose only `python3` (modern macOS has no `python`);
1532
+ // Windows often has a real `python` plus a Microsoft Store `python3` stub.
1533
+ // A missing interpreter must not silently degrade to empty payload shapes.
1534
+ const interpreterCandidates = process.platform === "win32" ? ["python", "python3"] : ["python3", "python"];
1535
+ let result;
1536
+ for (const interpreter of interpreterCandidates) {
1537
+ const attempt = spawnSync(interpreter, ["-c", program], {
1538
+ input: Buffer.from(source, "utf8"),
1539
+ encoding: "utf8",
1540
+ maxBuffer: 4 * 1024 * 1024,
1541
+ windowsHide: true,
1542
+ });
1543
+ // Interpreter missing/unspawnable → try the next candidate. A spawned
1544
+ // interpreter that exits nonzero analyzed real input; do not retry then.
1545
+ if (attempt.error)
1546
+ continue;
1547
+ result = attempt;
1548
+ break;
1549
+ }
1550
+ if (!result || result.status !== 0 || !result.stdout.trim())
1505
1551
  return new Map();
1506
1552
  try {
1507
1553
  const parsed = JSON.parse(result.stdout);