@tea-agent/loop-agent 0.43.0-next.9 → 0.44.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +108 -2308
  3. package/README.md +7 -0
  4. package/dist/application/dag/args.js +8 -0
  5. package/dist/application/task-lifecycle/advance.js +38 -7
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +2 -0
  8. package/dist/cli/program.js +3 -1
  9. package/dist/cli/update/init-surface-notifier.js +2 -0
  10. package/dist/cli/update/policy.js +2 -0
  11. package/dist/commands/client-recovery.js +20 -55
  12. package/dist/commands/cursor-prompt.js +2 -0
  13. package/dist/commands/dag-approve.js +2 -0
  14. package/dist/commands/dag-final-verification.js +2 -0
  15. package/dist/commands/dag-init-hybrid.js +2 -0
  16. package/dist/commands/dag-reconcile-tasks.js +2 -0
  17. package/dist/commands/dag-reject.js +2 -0
  18. package/dist/commands/dag-report.js +2 -0
  19. package/dist/commands/dag-rerun.js +2 -0
  20. package/dist/commands/dag-resume.js +2 -0
  21. package/dist/commands/dag-workflow-compile.js +2 -0
  22. package/dist/commands/dag-workflow-plan.js +2 -0
  23. package/dist/commands/dag-workflow-validate.js +2 -0
  24. package/dist/commands/delegate.js +2 -0
  25. package/dist/commands/examples.js +2 -0
  26. package/dist/commands/import-prd.js +2 -0
  27. package/dist/commands/init-model-catalog.js +2 -0
  28. package/dist/commands/init.js +19 -13
  29. package/dist/commands/instructions.js +2 -0
  30. package/dist/commands/knowledge.js +2 -0
  31. package/dist/commands/loop-benchmark.js +2 -0
  32. package/dist/commands/pi-prompt.js +2 -0
  33. package/dist/commands/pi-reuse-benchmark.js +2 -0
  34. package/dist/commands/promote-run.js +2 -0
  35. package/dist/commands/stats.js +1 -1
  36. package/dist/commands/study-init.js +2 -0
  37. package/dist/commands/task-advance.js +34 -0
  38. package/dist/commands/task-source-prepare.js +2 -0
  39. package/dist/commands/worktree-create.js +2 -0
  40. package/dist/commands/worktree-remove.js +2 -0
  41. package/dist/executors/dag-pi-executor.js +1650 -198
  42. package/dist/executors/pi-executor.js +85 -3
  43. package/dist/executors/pi-sdk-executor.js +18 -0
  44. package/dist/executors/shell-executor.js +93 -7
  45. package/dist/executors/shell-verification.js +3 -0
  46. package/dist/executors/shell-write-guard.js +27 -32
  47. package/dist/governance/exec-plans.js +6 -3
  48. package/dist/infrastructure/console/app-data.js +6 -0
  49. package/dist/shared/artifacts-core.js +2 -0
  50. package/dist/shared/backend-dogfood-preflight.js +47 -0
  51. package/dist/shared/dag-failure-category.js +3 -0
  52. package/dist/shared/git-progress.js +2 -0
  53. package/dist/shared/one-shot-prompt-args.js +2 -0
  54. package/dist/shared/operator/capabilities.js +180 -0
  55. package/dist/shared/package-metadata.js +6 -4
  56. package/dist/shared/pi-context-pressure/extension.js +26 -16
  57. package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
  58. package/dist/shared/pi-provider-recovery.js +182 -0
  59. package/dist/shared/pi-retry-settings.js +40 -0
  60. package/dist/shared/reference-context.js +2 -0
  61. package/dist/task/config-types.js +29 -0
  62. package/dist/task/contract/project.js +9 -0
  63. package/dist/task/contract/recover.js +6 -0
  64. package/dist/task/contract/schema.js +17 -0
  65. package/dist/task/source-prepare/artifact-meta.js +9 -2
  66. package/dist/task/source-prepare/build-draft.js +60 -0
  67. package/dist/task/source-prepare/fragment-inventory.js +4 -1
  68. package/dist/task/source-prepare/parse-intent.js +36 -11
  69. package/dist/task/task-demand-routing.js +5 -2
  70. package/dist/worker/console/chat/browser-automation.js +523 -0
  71. package/dist/worker/console/chat/browser-policy.js +6 -36
  72. package/dist/worker/console/chat/browser-routes.js +188 -56
  73. package/dist/worker/console/chat/explore-tools.js +5 -2
  74. package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
  75. package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
  76. package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +184 -0
  77. package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
  78. package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
  79. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +94 -0
  80. package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
  81. package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
  82. package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
  83. package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
  84. package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
  85. package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
  86. package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
  87. package/dist/worker/console/chat/pi-runtime.js +208 -1138
  88. package/dist/worker/console/chat/routes.js +27 -7
  89. package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
  90. package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
  91. package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
  92. package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
  93. package/dist/worker/console/chat/session-mode-view.js +5 -4
  94. package/dist/worker/console/chat/session-mode.js +18 -12
  95. package/dist/worker/console/chat/session-store.js +42 -9
  96. package/dist/worker/console/chat/tool-preview.js +115 -0
  97. package/dist/worker/console/chat/tools.js +10 -0
  98. package/dist/worker/console/chat/turn-order.js +13 -0
  99. package/dist/worker/console/chat/turn-process.js +32 -30
  100. package/dist/worker/console/console-update-and-init.js +1 -19
  101. package/dist/worker/console/console-update-runtime.js +5 -20
  102. package/dist/worker/console/operator-actions.js +63 -1
  103. package/dist/worker/console/prd-intake-bridge.js +54 -1
  104. package/dist/worker/console/routes.js +8 -0
  105. package/dist/worker/console/scheduled-goal-host.js +98 -0
  106. package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
  107. package/dist/worker/console/server.js +24 -1
  108. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
  109. package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-eoQiMvuk.js} +1 -1
  110. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
  111. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
  112. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
  113. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
  114. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
  115. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
  116. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
  117. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
  118. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
  119. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
  120. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
  121. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
  122. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
  123. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
  124. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
  125. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
  126. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
  127. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
  128. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
  129. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
  130. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
  131. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
  132. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
  133. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
  134. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
  135. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
  136. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
  137. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
  138. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
  139. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
  140. package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
  141. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
  142. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
  143. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
  144. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
  145. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
  146. package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-BDZ9riWi.js} +1 -1
  147. package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-7pKqYtpZ.js} +5 -5
  148. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
  149. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
  150. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
  151. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
  152. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
  153. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
  154. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
  155. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
  156. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
  157. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
  158. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
  159. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
  160. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
  161. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
  162. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
  163. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
  164. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
  165. package/dist/worker/console/static/index.html +2 -2
  166. package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
  167. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
  168. package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
  169. package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
  170. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
  171. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
  172. package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
  173. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +92 -38
  174. package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
  175. package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -55
  176. package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
  177. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
  178. package/dist/worker/console/static-src/prd-file-import.js +3 -1
  179. package/dist/worker/console/workspace-context.js +22 -0
  180. package/dist/worker/console/workspace-initialization.js +209 -0
  181. package/dist/worker/delivery/git-transaction.js +2 -0
  182. package/dist/worker/feature/fullstack-validate.js +2 -0
  183. package/dist/worker/loop-agent/loop-agent-client.js +8 -2
  184. package/dist/worker/materialize/harness-task-materializer.js +4 -1
  185. package/dist/worker/metrics/projector.js +1 -1
  186. package/dist/worker/observability/event-store.js +4 -1
  187. package/dist/worker/observability/read-model.js +4 -1
  188. package/dist/worker/observe/node-transparency.js +23 -1
  189. package/dist/worker/observe/routes.js +4 -0
  190. package/dist/worker/observe/static/operator-chrome.js +3 -1
  191. package/dist/worker/observe/static/styles.css +7 -4
  192. package/dist/worker/observe/static/views/dag-inspector.js +5 -19
  193. package/dist/worker/outcomes/declared-artifacts.js +2 -0
  194. package/dist/worker/preflight.js +3 -0
  195. package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
  196. package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
  197. package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
  198. package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
  199. package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
  200. package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
  201. package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
  202. package/dist/worker/task-spec/validate.js +4 -1
  203. package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
  204. package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
  205. package/dist/workflows/dag/dag-retry-schema.js +11 -0
  206. package/dist/workflows/dag/decision-envelope.js +2 -0
  207. package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
  208. package/dist/workflows/dag/frontend-closeout.js +3 -1
  209. package/dist/workflows/dag/frontend-committed-facts.js +461 -0
  210. package/dist/workflows/dag/frontend-durable-tools.js +15 -3
  211. package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
  212. package/dist/workflows/dag/frontend-plan-canary.js +53 -0
  213. package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
  214. package/dist/workflows/dag/frontend-plan-render.js +0 -2
  215. package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
  216. package/dist/workflows/dag/frontend-recovery-run.js +57 -0
  217. package/dist/workflows/dag/frontend-repair.js +55 -76
  218. package/dist/workflows/dag/frontend-review-context.js +42 -68
  219. package/dist/workflows/dag/frontend-review-scopes.js +2 -2
  220. package/dist/workflows/dag/frontend-risk.js +92 -10
  221. package/dist/workflows/dag/frontend-session-budget.js +117 -3
  222. package/dist/workflows/dag/frontend-shape.js +11 -55
  223. package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
  224. package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
  225. package/dist/workflows/dag/frontend-verification-trace.js +33 -54
  226. package/dist/workflows/dag/frontend-writer-admission.js +3 -47
  227. package/dist/workflows/dag/frontend-writer-status.js +0 -23
  228. package/dist/workflows/dag/init-hybrid.js +47 -19
  229. package/dist/workflows/dag/interrupt-request.js +2 -0
  230. package/dist/workflows/dag/lifecycle.js +11 -2
  231. package/dist/workflows/dag/node-execution.js +50 -13
  232. package/dist/workflows/dag/reconcile-run.js +2 -0
  233. package/dist/workflows/dag/repair-artifact.js +3 -1
  234. package/dist/workflows/dag/report.js +2 -0
  235. package/dist/workflows/dag/rerun-plan.js +10 -0
  236. package/dist/workflows/dag/rerun-task.js +79 -1
  237. package/dist/workflows/dag/retry-policy.js +18 -0
  238. package/dist/workflows/dag/scheduler.js +2 -4
  239. package/dist/workflows/dag/types.js +44 -6
  240. package/dist/workflows/dag/validate.js +4 -0
  241. package/docs/README.md +1 -0
  242. package/docs/architecture/runtime-boundaries.md +3 -3
  243. package/docs/governance/README.md +1 -0
  244. package/docs/init-surface.manifest.json +1 -0
  245. package/docs/operations/README.md +2 -0
  246. package/docs/templates/README.md +1 -1
  247. package/docs/templates/agent-dag.schema.json +2 -2
  248. package/docs/templates/backend-test-dag.json +14 -10
  249. package/docs/templates/frontend-implementation-contract.schema.json +0 -7
  250. package/package.json +9 -3
  251. package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
  252. package/skills/frontend-contract/references/contract-protocol.md +3 -1
  253. package/skills/frontend-plan/SKILL.md +8 -6
  254. package/skills/frontend-plan/references/decision-contract.md +3 -3
  255. package/skills/frontend-review/SKILL.md +18 -28
  256. package/skills/frontend-review/references/review-findings.md +8 -3
  257. package/dist/commands/new-task.js +0 -5
  258. package/dist/commands/status.js +0 -57
  259. package/dist/commands/task-contract.js +0 -269
  260. package/dist/shared/prompts.js +0 -26
  261. package/dist/task/config.js +0 -2
  262. package/dist/task/contract/validate-draft.js +0 -106
  263. package/dist/task/goal.js +0 -3
  264. package/dist/task/index.js +0 -12
  265. package/dist/task/lifecycle.js +0 -1
  266. package/dist/task/operator/capabilities.js +0 -6
  267. package/dist/task/operator/envelope.js +0 -2
  268. package/dist/task/operator/index.js +0 -5
  269. package/dist/task/operator/registry.js +0 -2
  270. package/dist/task/operator/types.js +0 -1
  271. package/dist/task/paths.js +0 -1
  272. package/dist/task/source-state.js +0 -1
  273. package/dist/task/subagent-guidance.js +0 -1
  274. package/dist/worker/console/chat/sift-bridge.js +0 -1
  275. package/dist/worker/console/index.js +0 -20
  276. package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
  277. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
  278. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
  279. package/dist/worker/console/static/assets/index-24jp3aBh.js +0 -468
  280. package/dist/worker/console/static/assets/index-CY6y6I8M.css +0 -1
  281. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
  282. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
  283. package/dist/worker/scheduler/index.js +0 -25
  284. package/dist/workflows/dag/facts.js +0 -4
  285. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
  286. package/dist/workflows/dag/index.js +0 -6
  287. package/dist/workflows/dynamic/index.js +0 -7
@@ -1,975 +0,0 @@
1
- import { readCommittedEvents, readTypedEventStoreFromJsonl, writeTypedEventStoreJsonl, } from "./frontend-typed-event-store.js";
2
- import { adoptTypedEventFact, stageTypedEventFact, } from "./frontend-typed-event-transaction.js";
3
- import { canonicalNodeOutput } from "./node-execution.js";
4
- import { createDagEventObserver } from "./event-observer.js";
5
- import { extractFrontendImplementationJson } from "./frontend-implementation-contract.js";
6
- /**
7
- * M2 shadow dual write (AC-001 / AC-002).
8
- *
9
- * The three read-only frontend nodes keep their authoritative text/JSON output
10
- * protocols untouched. This module derives schema-valid shadow facts from those
11
- * outputs and submits them through the M1 typed event store for audit and
12
- * deterministic difference comparison only. Shadow facts never participate in
13
- * writer admission and never control a DAG branch (M3 is the authority switch).
14
- *
15
- * Failure isolation (AC-001): every parse, stage, and adopt is swallowed, so a
16
- * shadow-channel failure can never fail the producing node. The canonical event
17
- * JSONL, when requested, is delegated to `createDagEventObserver`.
18
- */
19
- export const SHADOW_FACT_ORIGINS = ["contract", "scout", "plan"];
20
- function isRecord(value) {
21
- return typeof value === "object" && value !== null && !Array.isArray(value);
22
- }
23
- function asString(value) {
24
- return typeof value === "string" ? value.trim() : "";
25
- }
26
- function asStringArray(value) {
27
- if (!Array.isArray(value))
28
- return [];
29
- return value
30
- .map((item) => asString(item))
31
- .filter((item) => item.length > 0);
32
- }
33
- /** Deterministic, sorted, de-duplicated string set. Lexicographic order only;
34
- * never locale/Date-dependent so diff output is reproducible. */
35
- function canonicalStringSet(values) {
36
- return [
37
- ...new Set(values
38
- .map((item) => item.trim())
39
- .filter((item) => item.length > 0)),
40
- ].sort();
41
- }
42
- /* ------------------------------------------------------------------ *
43
- * Contract Markdown parser (authoritative Markdown contract). *
44
- * ------------------------------------------------------------------ */
45
- const REQUIREMENT_ID_PATTERN = /(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*/i;
46
- function classifyContractHeading(heading) {
47
- const normalized = heading.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ");
48
- if (normalized.includes("non goal"))
49
- return "non-goal";
50
- if (normalized.includes("acceptance"))
51
- return "acceptance";
52
- if (normalized.includes("open question"))
53
- return "open-question";
54
- if (normalized.includes("risk"))
55
- return "risk";
56
- if (normalized.includes("constraint"))
57
- return "constraint";
58
- if (normalized.includes("split"))
59
- return "split";
60
- if (normalized.includes("scope"))
61
- return "scope";
62
- return undefined;
63
- }
64
- function extractRequirementId(text) {
65
- const match = REQUIREMENT_ID_PATTERN.exec(text);
66
- return match ? match[0].toUpperCase() : undefined;
67
- }
68
- function extractBulletItems(sectionText) {
69
- const items = [];
70
- for (const rawLine of sectionText.split("\n")) {
71
- const line = rawLine.trim();
72
- if (!line)
73
- continue;
74
- const stripped = line.replace(/^(?:[-*+]|\d+[.)])\s*/, "").trim();
75
- if (!stripped)
76
- continue;
77
- items.push(stripped);
78
- }
79
- return items;
80
- }
81
- function buildContractFact(section, item) {
82
- switch (section) {
83
- case "scope":
84
- case "acceptance":
85
- return {
86
- kind: "requirement",
87
- origin: "contract",
88
- id: extractRequirementId(item) ?? item,
89
- text: item,
90
- };
91
- case "non-goal":
92
- return {
93
- kind: "constraint",
94
- origin: "contract",
95
- category: "non-goal",
96
- text: item,
97
- };
98
- case "constraint":
99
- return {
100
- kind: "constraint",
101
- origin: "contract",
102
- category: "constraint",
103
- text: item,
104
- };
105
- case "risk":
106
- return {
107
- kind: "constraint",
108
- origin: "contract",
109
- category: "risk",
110
- text: item,
111
- };
112
- case "open-question":
113
- return { kind: "open-question", origin: "contract", text: item };
114
- case "split":
115
- return { kind: "split-proposal", origin: "contract", text: item };
116
- }
117
- }
118
- export function parseContractFacts(text) {
119
- const facts = [];
120
- const lines = text.split("\n");
121
- let current;
122
- let buffer = [];
123
- const flush = () => {
124
- if (!current)
125
- return;
126
- for (const item of extractBulletItems(buffer.join("\n"))) {
127
- facts.push(buildContractFact(current, item));
128
- }
129
- buffer = [];
130
- };
131
- for (const rawLine of lines) {
132
- const line = rawLine.trimEnd();
133
- const heading = /^#{1,6}\s+(.+?)\s*$/.exec(line.trim());
134
- if (heading) {
135
- flush();
136
- current = classifyContractHeading(heading[1]);
137
- continue;
138
- }
139
- if (current)
140
- buffer.push(line);
141
- }
142
- flush();
143
- return facts;
144
- }
145
- /* ------------------------------------------------------------------ *
146
- * TARGET_SURFACE parser (authoritative scout text). *
147
- * ------------------------------------------------------------------ */
148
- const TARGET_SURFACE_LABELS = [
149
- "entrypoint",
150
- "routeOrMount",
151
- "implementationPaths",
152
- "testPaths",
153
- "dataSource",
154
- "allowedPathConflicts",
155
- ];
156
- function escapeRegex(value) {
157
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
158
- }
159
- function splitListValue(raw) {
160
- const cleaned = raw.replace(/^\[|\]$/g, "").trim();
161
- if (!cleaned)
162
- return [];
163
- return cleaned
164
- .split(/[,\n]/)
165
- .map((item) => item
166
- .trim()
167
- .replace(/^[-*+]\s+/, "")
168
- .replace(/^["']|["']$/g, ""))
169
- .filter((item) => item.length > 0);
170
- }
171
- function parseTargetSurfaceLabels(text) {
172
- const result = {};
173
- const sectionStart = text.indexOf("TARGET_SURFACE");
174
- if (sectionStart < 0) {
175
- for (const label of TARGET_SURFACE_LABELS)
176
- result[label] = "";
177
- return result;
178
- }
179
- const section = text.slice(sectionStart);
180
- const positions = [];
181
- for (const label of TARGET_SURFACE_LABELS) {
182
- const match = new RegExp(`\\b${escapeRegex(label)}\\s*(?::|\\s)`).exec(section);
183
- if (match && match.index !== undefined) {
184
- positions.push({ label, index: match.index });
185
- }
186
- }
187
- positions.sort((a, b) => a.index - b.index);
188
- for (let i = 0; i < positions.length; i += 1) {
189
- const current = positions[i];
190
- const colon = section.indexOf(":", current.index);
191
- const valueStart = colon >= 0 && colon < (positions[i + 1]?.index ?? section.length)
192
- ? colon + 1
193
- : current.index + current.label.length;
194
- const valueEnd = i + 1 < positions.length ? positions[i + 1].index : section.length;
195
- result[current.label] = section.slice(valueStart, valueEnd).trim();
196
- }
197
- for (const label of TARGET_SURFACE_LABELS) {
198
- result[label] ??= "";
199
- }
200
- return result;
201
- }
202
- export function parseTargetSurfaceFacts(text) {
203
- const values = parseTargetSurfaceLabels(text);
204
- const implementationPaths = canonicalStringSet(splitListValue(values.implementationPaths));
205
- const testPaths = canonicalStringSet(splitListValue(values.testPaths));
206
- const allowedPathConflicts = canonicalStringSet(splitListValue(values.allowedPathConflicts));
207
- return [
208
- {
209
- kind: "target-surface",
210
- origin: "scout",
211
- entrypoint: asString(values.entrypoint),
212
- routeOrMount: asString(values.routeOrMount),
213
- implementationPaths,
214
- testPaths,
215
- dataSource: asString(values.dataSource),
216
- allowedPathConflicts,
217
- },
218
- {
219
- kind: "design-evidence",
220
- origin: "scout",
221
- source: "TARGET_SURFACE",
222
- paths: canonicalStringSet([...implementationPaths, ...testPaths]),
223
- conflicts: allowedPathConflicts,
224
- },
225
- ];
226
- }
227
- export const PLAN_LEDGER_FACT_KINDS = [
228
- "target-surface",
229
- "state-registry",
230
- "component-choice",
231
- "state-flow",
232
- "data-flow",
233
- "mock-api",
234
- "design-deviation",
235
- "dependency",
236
- "plan-requirement",
237
- "plan-verification-target",
238
- "plan-evidence-gap",
239
- "mock-endpoint",
240
- ];
241
- function skeletonTargetFiles(skeleton) {
242
- if (!isRecord(skeleton))
243
- return [];
244
- const targets = isRecord(skeleton.targets) ? skeleton.targets : undefined;
245
- return canonicalStringSet(asStringArray(targets?.files));
246
- }
247
- /** Restore the editable RFC 7386 plan patch from a committed plan ledger.
248
- * Production compile authority is the tool payload (`patch` on any committed
249
- * plan fact, else a `target-surface.patch`). Text JSON is never the compile
250
- * authority. */
251
- export function restorePlanPatchFromCommittedFacts(records) {
252
- const planFacts = records
253
- .filter((record) => record.phase === "committed")
254
- .map((record) => record.fact)
255
- .filter((fact) => isRecord(fact) &&
256
- fact.origin === "plan" &&
257
- typeof fact.kind === "string" &&
258
- PLAN_LEDGER_FACT_KINDS.includes(fact.kind));
259
- if (planFacts.length === 0)
260
- return undefined;
261
- for (const fact of planFacts) {
262
- if (isRecord(fact.patch))
263
- return fact.patch;
264
- }
265
- // No finalize-published snapshot: the receipt fix loop can end an attempt
266
- // before any finalize_plan succeeds while dozens of record_* facts are
267
- // already committed. Assemble the patch from those record facts instead of
268
- // declaring the ledger missing (r19: "ledger missing" discarded a ledger
269
- // with 92 committed facts).
270
- return assemblePlanPatchFromCommittedFacts(records);
271
- }
272
- /**
273
- * A+B (AC-004): reverse of `planLedgerFactsFromPatch` for the incremental
274
- * `record_*` tool flow. Reconstructs the canonical editable patch from the raw
275
- * fields each `record_*` tool committed. `finalize_plan` calls this and then
276
- * re-publishes the assembled patch onto a `target-surface` fact so the
277
- * writeSet-external compile authority (`frontend-implementation-contract.ts`)
278
- * can still restore a plain `patch` object.
279
- */
280
- export function assemblePlanPatchFromCommittedFacts(records, contractRequirements) {
281
- const facts = records
282
- .filter((record) => record.phase === "committed")
283
- .map((record) => record.fact)
284
- .filter((fact) => isRecord(fact) &&
285
- fact.origin === "plan" &&
286
- typeof fact.kind === "string" &&
287
- PLAN_LEDGER_FACT_KINDS.includes(fact.kind));
288
- if (facts.length === 0)
289
- return undefined;
290
- // Singleton policy facts: the LAST committed fact wins. The model corrects a
291
- // rejected plan by re-recording the policy (r18). Requirements / verification
292
- // targets / evidence gaps stay additive; component choices accumulate; and
293
- // state-flow is merged below by item name so UX-local batches do not erase one
294
- // another.
295
- const lastByKind = (kind) => {
296
- let found;
297
- for (const fact of facts) {
298
- if (fact.kind === kind)
299
- found = fact;
300
- }
301
- return found;
302
- };
303
- // Patch snapshots (published by earlier finalize calls) carry no routes;
304
- // exclude them so a snapshot cannot shadow the model's route selection.
305
- const routeSurface = (() => {
306
- let found;
307
- for (const fact of facts) {
308
- if (fact.kind !== "target-surface" || isRecord(fact.patch))
309
- continue;
310
- found = fact;
311
- }
312
- return found;
313
- })();
314
- const patch = {};
315
- const targetSurface = routeSurface;
316
- if (targetSurface) {
317
- const routes = canonicalStringSet(asStringArray(targetSurface.routes));
318
- if (routes.length > 0)
319
- patch.targets = { routes };
320
- }
321
- const componentChoices = facts.filter((fact) => fact.kind === "component-choice");
322
- const collectedChoices = componentChoices.flatMap((fact) => Array.isArray(fact.uiComponentChoices)
323
- ? fact.uiComponentChoices.filter(isRecord)
324
- : []);
325
- // Later corrections supersede earlier submissions per purpose: without
326
- // this, a re-recorded choice (fixing a wrong specReference) would appear
327
- // twice and the reviewer would still audit the stale entry (r19).
328
- const choicesByPurpose = new Map();
329
- const dedupedChoices = [];
330
- for (const choice of collectedChoices) {
331
- const purpose = asString(choice.purpose);
332
- if (!purpose) {
333
- dedupedChoices.push(choice);
334
- continue;
335
- }
336
- if (choicesByPurpose.has(purpose)) {
337
- const at = dedupedChoices.findIndex((existing) => asString(existing.purpose) === purpose);
338
- dedupedChoices[at] = choice;
339
- }
340
- else {
341
- choicesByPurpose.set(purpose, choice);
342
- dedupedChoices.push(choice);
343
- }
344
- }
345
- const stylingStrategy = componentChoices
346
- .map((fact) => asString(fact.stylingStrategy))
347
- .find((value) => value.length > 0);
348
- if (dedupedChoices.length > 0)
349
- patch.uiComponentChoices = dedupedChoices;
350
- if (stylingStrategy)
351
- patch.stylingStrategy = stylingStrategy;
352
- // UX-local batches can emit several state-flow facts. Aggregate by the
353
- // stable state/interaction name so a later correction replaces only that
354
- // item; last-wins at the whole-fact level silently discarded earlier local
355
- // batches when Segment 2 was split (r21).
356
- const stateFlows = facts.filter((fact) => fact.kind === "state-flow");
357
- const stateByName = new Map();
358
- const unnamedStates = [];
359
- const interactionByName = new Map();
360
- const unnamedInteractions = [];
361
- for (const flow of stateFlows) {
362
- for (const name of asStringArray(flow.removeUiStateNames))
363
- stateByName.delete(name);
364
- for (const name of asStringArray(flow.removeInteractionNames))
365
- interactionByName.delete(name);
366
- for (const state of Array.isArray(flow.uiStates) ? flow.uiStates : []) {
367
- if (!isRecord(state))
368
- continue;
369
- const name = asString(state.name);
370
- if (name)
371
- stateByName.set(name, state);
372
- else
373
- unnamedStates.push(state);
374
- }
375
- for (const interaction of Array.isArray(flow.interactions) ? flow.interactions : []) {
376
- if (!isRecord(interaction))
377
- continue;
378
- const name = asString(interaction.name);
379
- if (name)
380
- interactionByName.set(name, interaction);
381
- else
382
- unnamedInteractions.push(interaction);
383
- }
384
- }
385
- if (stateFlows.length > 0) {
386
- patch.uiStates = [...stateByName.values(), ...unnamedStates];
387
- patch.interactions = [...interactionByName.values(), ...unnamedInteractions];
388
- }
389
- const mockApi = lastByKind("mock-api");
390
- if (!mockApi && facts.some(f => f.kind === "mock-endpoint" && f.removed !== true))
391
- throw Error("MOCK_POLICY_MISSING: endpoint has no policy");
392
- if (mockApi && isRecord(mockApi.mockApi)) {
393
- const endpoints = new Map();
394
- for (const endpoint of Array.isArray(mockApi.mockApi.endpoints) ? mockApi.mockApi.endpoints : []) {
395
- if (isRecord(endpoint))
396
- endpoints.set(`${endpoint.method}:${endpoint.path}`, endpoint);
397
- }
398
- for (const fact of facts)
399
- if (fact.kind === "mock-endpoint" && isRecord(fact.endpoint)) {
400
- const key = `${fact.endpoint.method}:${fact.endpoint.path}`;
401
- if (fact.removed === true)
402
- endpoints.delete(key);
403
- else
404
- endpoints.set(key, fact.endpoint);
405
- }
406
- if (mockApi.mockApi.strategy === "not-needed" && endpoints.size)
407
- throw Error("MOCK_POLICY_CONFLICT: withdraw endpoints before declaring Mock/API not needed");
408
- patch.mockApi = { ...mockApi.mockApi, endpoints: [...endpoints.values()] };
409
- }
410
- const deviation = lastByKind("design-deviation");
411
- if (deviation) {
412
- const conflicts = canonicalStringSet(asStringArray(deviation.conflicts));
413
- if (conflicts.length > 0)
414
- patch.designEvidence = { conflicts };
415
- }
416
- const dependency = lastByKind("dependency");
417
- const dependencyPolicy = asString(dependency?.policy);
418
- if (dependencyPolicy)
419
- patch.dependencyPolicy = dependencyPolicy;
420
- // Requirements / verification targets / evidence gaps / implementation
421
- // steps arrive as individual facts (one per record_plan_* call) so a large
422
- // plan never exceeds a single model output budget. Aggregate them in
423
- // ledger order.
424
- const requirements = facts
425
- .filter((fact) => fact.kind === "plan-requirement")
426
- .map((fact) => fact.entry)
427
- .filter(isRecord)
428
- .map((entry) => {
429
- // The canonical contract schema requires a non-empty
430
- // expectedOutcome, but the plan should not be forced to re-write
431
- // full outcome prose for every requirement (the contract node
432
- // already captured requirement semantics). Derive a deterministic
433
- // placeholder from the requirement id when omitted.
434
- if (isRecord(entry) && !asString(entry.expectedOutcome)) {
435
- const id = asString(entry.id) || "requirement";
436
- return { ...entry, expectedOutcome: `implied by contract: ${id}` };
437
- }
438
- return entry;
439
- })
440
- // Source fidelity ledger (AC-005/AC-006): the contract node declared the
441
- // authoritative sourceFragmentIds/sourceRefs for each requirement from
442
- // the ledger's requirement→fragment mapping. The plan inherits them by
443
- // requirement id so the compiled canonical contract carries the same
444
- // provenance evidence without the plan re-deriving (or fabricating) it.
445
- .map((entry) => {
446
- if (!contractRequirements)
447
- return entry;
448
- const id = asString(entry.id);
449
- if (!id)
450
- return entry;
451
- const inherited = contractRequirements.get(id);
452
- if (!inherited)
453
- return entry;
454
- const merged = { ...entry, ...(inherited.text ? { expectedOutcome: inherited.text } : {}), ...(inherited.execution ? { execution: inherited.execution } : {}) };
455
- // Inherit the contract-declared provenance only when the plan did
456
- // not already declare it (model override wins; missing inherits).
457
- if (!Array.isArray(entry.sourceFragmentIds)) {
458
- if (inherited.sourceFragmentIds) {
459
- merged.sourceFragmentIds = inherited.sourceFragmentIds;
460
- }
461
- }
462
- if (!Array.isArray(entry.sourceRefs)) {
463
- if (inherited.sourceRefs) {
464
- merged.sourceRefs = inherited.sourceRefs;
465
- }
466
- }
467
- return merged;
468
- })
469
- .map((entry) => {
470
- if (isRecord(entry) && !isRecord(entry.evidenceGap)) {
471
- const requirementId = asString(entry.id);
472
- const standaloneGap = facts
473
- .filter((fact) => fact.kind === "plan-evidence-gap")
474
- .map((fact) => fact.entry)
475
- .filter(isRecord)
476
- .filter((gap) => asString(gap.requirementId) === requirementId)
477
- .at(-1);
478
- if (standaloneGap && asString(standaloneGap.description)) {
479
- return { ...entry, evidenceGap: standaloneGap };
480
- }
481
- }
482
- return entry;
483
- })
484
- // A requirement id is a canonical identity; drop duplicates so the
485
- // compiled patch never carries the same requirement twice (the
486
- // record_plan_requirement tool rejects re-records, this dedupes any
487
- // legacy/edge duplicates at the compile authority).
488
- .filter((entry, index, all) => all.map((other) => asString(other.id)).lastIndexOf(asString(entry.id)) === index);
489
- if (requirements.length > 0)
490
- patch.requirements = requirements;
491
- const verificationTargetOrder = [];
492
- const verificationTargetById = new Map();
493
- for (const fact of facts.filter((item) => item.kind === "plan-verification-target")) {
494
- const entry = fact.entry;
495
- if (!isRecord(entry))
496
- continue;
497
- const id = asString(entry.id);
498
- if (!id)
499
- continue;
500
- if (!verificationTargetById.has(id))
501
- verificationTargetOrder.push(id);
502
- verificationTargetById.set(id, entry);
503
- }
504
- const verificationTargets = verificationTargetOrder
505
- .map((id) => verificationTargetById.get(id))
506
- .filter((entry) => Boolean(entry))
507
- .map((entry) => {
508
- // Coverage precedes UX. A later state-flow fact explicitly naming
509
- // this VT establishes the reverse binding without another model call.
510
- // No path/name heuristics: unbound or ambiguous decisions stay subject
511
- // to canonical validation. Recompute from live states after removals.
512
- const boundStates = [...stateByName.values()]
513
- .filter((state) => state.applicable === true &&
514
- asStringArray(state.verificationTargetIds).includes(asString(entry.id)))
515
- .map((state) => asString(state.name));
516
- if (boundStates.length === 0)
517
- return entry;
518
- return { ...entry, uiStates: [...new Set([
519
- ...asStringArray(entry.uiStates), ...boundStates,
520
- ])].sort() };
521
- });
522
- if (verificationTargets.length > 0) {
523
- patch.verificationTargets = verificationTargets;
524
- }
525
- const evidenceGaps = facts
526
- .filter((fact) => fact.kind === "plan-evidence-gap")
527
- .map((fact) => fact.entry)
528
- .filter(isRecord);
529
- if (evidenceGaps.length > 0)
530
- patch.evidenceGaps = evidenceGaps;
531
- return Object.keys(patch).length > 0 ? patch : undefined;
532
- }
533
- /** Derive the 7 origin=plan ledger facts from an editable patch. Skeleton
534
- * `targets.files` fills `target-surface.files` because the patch must not
535
- * write that protected path. */
536
- export function planLedgerFactsFromPatch(patch, skeleton) {
537
- const record = isRecord(patch) ? patch : undefined;
538
- if (!record) {
539
- throw new Error("frontend plan ledger requires an editable RFC 7386 patch object");
540
- }
541
- const facts = [];
542
- const targets = isRecord(record.targets) ? record.targets : undefined;
543
- const files = canonicalStringSet([
544
- ...asStringArray(targets?.files),
545
- ...skeletonTargetFiles(skeleton),
546
- ]);
547
- const routes = canonicalStringSet(asStringArray(targets?.routes));
548
- facts.push({
549
- kind: "target-surface",
550
- origin: "plan",
551
- files,
552
- routes,
553
- patch: record,
554
- });
555
- const uiComponentChoices = Array.isArray(record.uiComponentChoices)
556
- ? record.uiComponentChoices.filter(isRecord)
557
- : [];
558
- const components = canonicalStringSet(uiComponentChoices.map((choice) => asString(choice.component)));
559
- const stylingStrategy = asString(record.stylingStrategy);
560
- if (components.length > 0 || stylingStrategy) {
561
- facts.push({
562
- kind: "component-choice",
563
- origin: "plan",
564
- components,
565
- ...(stylingStrategy ? { stylingStrategy } : {}),
566
- });
567
- }
568
- const uiStates = Array.isArray(record.uiStates)
569
- ? record.uiStates.filter(isRecord)
570
- : [];
571
- const interactions = Array.isArray(record.interactions)
572
- ? record.interactions.filter(isRecord)
573
- : [];
574
- const stateNames = canonicalStringSet(uiStates.map((state) => asString(state.name)));
575
- const interactionNames = canonicalStringSet(interactions.map((interaction) => asString(interaction.name)));
576
- if (stateNames.length > 0 || interactionNames.length > 0) {
577
- facts.push({
578
- kind: "state-flow",
579
- origin: "plan",
580
- states: stateNames,
581
- interactions: interactionNames,
582
- });
583
- }
584
- const mockApi = isRecord(record.mockApi) ? record.mockApi : undefined;
585
- const endpoints = Array.isArray(mockApi?.endpoints)
586
- ? mockApi.endpoints.filter(isRecord)
587
- : [];
588
- const endpointPaths = canonicalStringSet(endpoints.map((endpoint) => `${asString(endpoint.method)} ${asString(endpoint.path)}`.trim()));
589
- if (interactionNames.length > 0 || endpointPaths.length > 0) {
590
- facts.push({
591
- kind: "data-flow",
592
- origin: "plan",
593
- interactions: interactionNames,
594
- endpoints: endpointPaths,
595
- });
596
- }
597
- if (mockApi) {
598
- facts.push({
599
- kind: "mock-api",
600
- origin: "plan",
601
- strategy: asString(mockApi.strategy),
602
- endpoints: endpointPaths,
603
- });
604
- }
605
- const designEvidence = isRecord(record.designEvidence)
606
- ? record.designEvidence
607
- : undefined;
608
- const conflicts = canonicalStringSet(asStringArray(designEvidence?.conflicts));
609
- if (conflicts.length > 0) {
610
- facts.push({ kind: "design-deviation", origin: "plan", conflicts });
611
- }
612
- const dependencyPolicy = asString(record.dependencyPolicy);
613
- if (dependencyPolicy) {
614
- facts.push({ kind: "dependency", origin: "plan", policy: dependencyPolicy });
615
- }
616
- return facts;
617
- }
618
- /** Map committed plan facts, else a test-helper JSON patch, into the 7
619
- * origin=plan kinds. Production compile authority is the committed ledger;
620
- * a missing ledger/patch throws instead of returning a fake empty book. */
621
- export function parsePlanLedgerFacts(textOrRecords, skeleton) {
622
- if (typeof textOrRecords !== "string") {
623
- const fromFacts = planLedgerFactsFromCommittedRecords(textOrRecords);
624
- if (fromFacts.length > 0)
625
- return fromFacts;
626
- throw new Error("frontend plan ledger missing: no committed origin=plan facts");
627
- }
628
- const text = textOrRecords;
629
- const jsonlLines = text
630
- .split("\n")
631
- .map((line) => line.trim())
632
- .filter(Boolean);
633
- const looksLikeJsonlLedger = jsonlLines.length > 0 &&
634
- jsonlLines.every((line) => {
635
- try {
636
- const record = JSON.parse(line);
637
- return (isRecord(record) &&
638
- record.phase === "committed" &&
639
- isRecord(record.fact) &&
640
- typeof record.eventId === "string");
641
- }
642
- catch {
643
- return false;
644
- }
645
- });
646
- if (looksLikeJsonlLedger) {
647
- const records = jsonlLines.map((line) => JSON.parse(line));
648
- const fromFacts = planLedgerFactsFromCommittedRecords(records);
649
- if (fromFacts.length > 0)
650
- return fromFacts;
651
- }
652
- let value;
653
- try {
654
- value = extractFrontendImplementationJson(text);
655
- }
656
- catch {
657
- throw new Error("frontend plan ledger missing: no committed plan facts");
658
- }
659
- return planLedgerFactsFromPatch(value, skeleton);
660
- }
661
- function planLedgerFactsFromCommittedRecords(records) {
662
- const restored = restorePlanPatchFromCommittedFacts(records);
663
- if (restored)
664
- return planLedgerFactsFromPatch(restored);
665
- return records
666
- .filter((record) => record.phase === "committed")
667
- .map((record) => record.fact)
668
- .filter((fact) => isRecord(fact) &&
669
- fact.origin === "plan" &&
670
- typeof fact.kind === "string" &&
671
- PLAN_LEDGER_FACT_KINDS.includes(fact.kind))
672
- .map((fact) => fact);
673
- }
674
- export async function loadCommittedPlanLedgerFacts(runDir, nodeId = "frontend-plan-pi") {
675
- const { readTypedEventStoreFromJsonl } = await import("./frontend-typed-event-store.js");
676
- const pathMod = await import("node:path");
677
- return readTypedEventStoreFromJsonl(pathMod.join(runDir, nodeId, "plan-typed-facts.jsonl"));
678
- }
679
- /** A+B (AC-001/003): read only the committed-phase origin facts from a node's
680
- * typed event store JSONL. Staged/quarantined records never leak downstream. */
681
- export async function readCommittedOriginFacts(runDir, nodeId, fileName) {
682
- const pathMod = await import("node:path");
683
- const records = await readTypedEventStoreFromJsonl(pathMod.join(runDir, nodeId, fileName));
684
- return records.filter((record) => record.phase === "committed");
685
- }
686
- function latestCommittedScoutTargetSurface(records) {
687
- return records
688
- .filter((record) => record.phase === "committed" &&
689
- isRecord(record.fact) &&
690
- record.fact.origin === "scout" &&
691
- record.fact.kind === "target-surface")
692
- .at(-1)?.fact;
693
- }
694
- /**
695
- * Scout owns discovery. A complete fact must explicitly say so, name at least
696
- * one target candidate and leave no unresolved paths. Existing surfaces must
697
- * include fresh runtime evidence for a named target path. Greenfield
698
- * surfaces are the one explicit exception: when runtime-injected source
699
- * declarations prove that every named path was specified by the task source,
700
- * all paths may be future (not yet on disk). This keeps create-new work
701
- * possible without allowing Scout to invent an arbitrary path under the
702
- * writer boundary. An empty test path list remains valid.
703
- */
704
- export function readCompleteScoutTargetSurface(records) {
705
- const fact = latestCommittedScoutTargetSurface(records);
706
- if (!fact)
707
- return {
708
- ok: false,
709
- reason: "frontend scout facts missing: no committed target-surface fact",
710
- };
711
- if (fact.completeness !== "complete")
712
- return {
713
- ok: false,
714
- reason: "frontend scout target surface is not complete; resolve discovery gaps before Plan",
715
- };
716
- const unresolvedPaths = asStringArray(fact.unresolvedPaths);
717
- if (unresolvedPaths.length > 0)
718
- return {
719
- ok: false,
720
- reason: `frontend scout target surface has unresolved paths: ${unresolvedPaths.join(", ")}`,
721
- };
722
- const surface = {
723
- entrypoint: asString(fact.entrypoint),
724
- implementationPaths: canonicalStringSet(asStringArray(fact.implementationPaths)),
725
- testPaths: canonicalStringSet(asStringArray(fact.testPaths)),
726
- };
727
- const namedPaths = canonicalStringSet([
728
- ...(surface.entrypoint ? [surface.entrypoint] : []),
729
- ...surface.implementationPaths,
730
- ...surface.testPaths,
731
- ]);
732
- if (namedPaths.length === 0)
733
- return {
734
- ok: false,
735
- reason: "frontend scout target surface is incomplete: name an entrypoint, implementation path, or test path",
736
- };
737
- const freshPaths = new Set(Array.isArray(fact.pathEvidence)
738
- ? fact.pathEvidence
739
- .filter(isRecord)
740
- .filter((item) => item.fresh === true)
741
- .map((item) => asString(item.path))
742
- .filter(Boolean)
743
- : []);
744
- const pathEvidence = Array.isArray(fact.pathEvidence)
745
- ? fact.pathEvidence.filter(isRecord)
746
- : [];
747
- const hasSourceDeclarationLedger = Array.isArray(fact.sourceDeclaredPaths);
748
- const undeclaredFuturePaths = hasSourceDeclarationLedger
749
- ? namedPaths.filter((candidate) => {
750
- const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
751
- return evidence?.fresh !== true && evidence?.sourceDeclared !== true;
752
- })
753
- : [];
754
- if (undeclaredFuturePaths.length > 0)
755
- return {
756
- ok: false,
757
- reason: `frontend scout target surface has future paths not declared by the task source: ${undeclaredFuturePaths.join(", ")}; test path is not declared by the task source, so do not guess test/ vs tests/ or other target directories`,
758
- };
759
- if (!namedPaths.some((candidate) => freshPaths.has(candidate))) {
760
- const allPathsAreSourceDeclaredFuturePaths = hasSourceDeclarationLedger &&
761
- namedPaths.length > 0 &&
762
- namedPaths.every((candidate) => {
763
- const evidence = pathEvidence.find((item) => asString(item.path) === candidate);
764
- return evidence?.fresh !== true && evidence?.sourceDeclared === true;
765
- });
766
- if (allPathsAreSourceDeclaredFuturePaths)
767
- return { ok: true, surface };
768
- return {
769
- ok: false,
770
- reason: "frontend scout target surface lacks fresh runtime evidence for a named target path; greenfield exceptions require runtime-injected source declarations for every future path",
771
- };
772
- }
773
- return { ok: true, surface };
774
- }
775
- /** Paths with Scout ownership that Plan may read for a specific decision. */
776
- export function scoutConfirmedReadPaths(records) {
777
- const checked = readCompleteScoutTargetSurface(records);
778
- if (!checked.ok)
779
- return [];
780
- return canonicalStringSet([
781
- ...(checked.surface.entrypoint ? [checked.surface.entrypoint] : []),
782
- ...checked.surface.implementationPaths,
783
- ...checked.surface.testPaths,
784
- ]);
785
- }
786
- /** A+B (AC-001/003): fail-closed committed contract/scout fact prerequisites.
787
- * `contract` requires a committed `contract-finalized` terminal fact; `scout`
788
- * requires a complete target-surface fact. Missing or incomplete facts block
789
- * downstream consumption — never fall back to text parsing. */
790
- export function checkCommittedOriginFacts(input) {
791
- const kinds = new Set(input.records
792
- .filter((record) => record.phase === "committed")
793
- .map((record) => record.fact.kind)
794
- .filter((kind) => typeof kind === "string"));
795
- if (input.origin === "contract") {
796
- return kinds.has("contract-finalized")
797
- ? { ok: true }
798
- : {
799
- ok: false,
800
- reason: "frontend contract facts missing: no committed contract-finalized terminal fact",
801
- };
802
- }
803
- if (input.origin === "scout") {
804
- if (!kinds.has("target-surface"))
805
- return {
806
- ok: false,
807
- reason: "frontend scout facts missing: no committed target-surface fact",
808
- };
809
- const checked = readCompleteScoutTargetSurface(input.records);
810
- return checked.ok ? { ok: true } : { ok: false, reason: checked.reason };
811
- }
812
- return { ok: true };
813
- }
814
- /* ------------------------------------------------------------------ *
815
- * Shadow observer factory. *
816
- * ------------------------------------------------------------------ */
817
- const SHADOW_NODE_PARSERS = {
818
- "frontend-contract-pi": parseContractFacts,
819
- "frontend-scout-pi": parseTargetSurfaceFacts,
820
- "frontend-plan-pi": parsePlanLedgerFacts,
821
- };
822
- function isShadowNodeId(nodeId) {
823
- return nodeId in SHADOW_NODE_PARSERS;
824
- }
825
- function reportError(context, error) {
826
- const message = error instanceof Error ? error.message : String(error);
827
- process.stderr.write(`[frontend-shadow-dual-write] ${context} failed: ${message}\n`);
828
- }
829
- export function createShadowDualWriteObserver(options) {
830
- const base = options.eventsJsonlPath
831
- ? createDagEventObserver({
832
- eventsJsonlPath: options.eventsJsonlPath,
833
- dagRunId: options.dagRunId,
834
- spec: options.spec,
835
- })
836
- : null;
837
- const recordNodeFacts = async (nodeId, state) => {
838
- if (!isShadowNodeId(nodeId))
839
- return;
840
- const text = canonicalNodeOutput(state.nodes[nodeId]);
841
- let facts;
842
- try {
843
- facts = options.parseNodeFacts
844
- ? options.parseNodeFacts(nodeId, text)
845
- : SHADOW_NODE_PARSERS[nodeId](text);
846
- }
847
- catch {
848
- return;
849
- }
850
- for (const [index, fact] of facts.entries()) {
851
- try {
852
- // Deterministic requestId keeps replay idempotent within a node.
853
- const requestId = `${options.requestId}:${nodeId}:${fact.kind}:${index}`;
854
- const staged = stageTypedEventFact({
855
- store: options.store,
856
- requestId,
857
- attemptId: options.attemptId,
858
- fact,
859
- });
860
- await adoptTypedEventFact({
861
- store: options.store,
862
- requestId,
863
- attemptId: options.attemptId,
864
- fact,
865
- eventId: staged.eventId,
866
- expectedRevision: options.store.revision,
867
- });
868
- }
869
- catch {
870
- // A shadow commit failure never blocks the producing node (AC-001).
871
- }
872
- }
873
- };
874
- const observer = {
875
- onRunStart: async (state) => {
876
- await base?.observer.onRunStart?.(state);
877
- },
878
- onNodeStart: async (nodeId, state) => {
879
- await base?.observer.onNodeStart?.(nodeId, state);
880
- },
881
- onNodeOutput: async (nodeId, chunk, state) => {
882
- await base?.observer.onNodeOutput?.(nodeId, chunk, state);
883
- },
884
- onNodeFinish: async (nodeId, state) => {
885
- try {
886
- await recordNodeFacts(nodeId, state);
887
- }
888
- catch {
889
- // Derived view; never affect canonical execution.
890
- }
891
- await base?.observer.onNodeFinish?.(nodeId, state);
892
- },
893
- onRunFinish: async (state) => {
894
- await base?.observer.onRunFinish?.(state);
895
- },
896
- };
897
- return {
898
- observer,
899
- flush: async () => {
900
- try {
901
- const committed = readCommittedEvents(options.store, options.attemptId);
902
- await writeTypedEventStoreJsonl(options.shadowStoreJsonlPath, committed);
903
- }
904
- catch (error) {
905
- reportError("flush", error);
906
- }
907
- await base?.flush();
908
- },
909
- };
910
- }
911
- function compareDimension(factValues, authorityValues) {
912
- const facts = canonicalStringSet(factValues);
913
- const authority = canonicalStringSet(authorityValues);
914
- const matched = facts.filter((item) => authority.includes(item));
915
- const factsOnly = facts.filter((item) => !authority.includes(item));
916
- const authorityOnly = authority.filter((item) => !facts.includes(item));
917
- return { facts, authority, matched, factsOnly, authorityOnly };
918
- }
919
- function factFieldStrings(facts, kinds, fields) {
920
- const out = [];
921
- for (const fact of facts) {
922
- if (!kinds.includes(fact.kind))
923
- continue;
924
- for (const field of fields) {
925
- const value = fact[field];
926
- if (typeof value === "string")
927
- out.push(value);
928
- else if (Array.isArray(value))
929
- out.push(...asStringArray(value));
930
- }
931
- }
932
- return out;
933
- }
934
- export function compareShadowFactsToAuthority(input) {
935
- const contractFacts = parseContractFacts(input.contractText);
936
- const scoutFacts = parseTargetSurfaceFacts(input.scoutText);
937
- const planFacts = parsePlanLedgerFacts(input.planText);
938
- const requirementCoverage = compareDimension(factFieldStrings(input.facts, ["requirement"], ["id"]), factFieldStrings(contractFacts, ["requirement"], ["id"]));
939
- const targetPaths = compareDimension([
940
- ...factFieldStrings(input.facts, ["target-surface"], ["implementationPaths", "testPaths", "files", "routes"]),
941
- ], [
942
- ...factFieldStrings(scoutFacts, ["target-surface"], ["implementationPaths", "testPaths"]),
943
- ...factFieldStrings(planFacts, ["target-surface"], ["files", "routes"]),
944
- ]);
945
- const componentDecisions = compareDimension(factFieldStrings(input.facts, ["component-choice", "state-flow", "data-flow", "mock-api"], ["components", "stylingStrategy", "states", "interactions", "endpoints", "strategy"]), factFieldStrings(planFacts, ["component-choice", "state-flow", "data-flow", "mock-api"], ["components", "stylingStrategy", "states", "interactions", "endpoints", "strategy"]));
946
- const blockers = compareDimension(factFieldStrings(input.facts, ["constraint", "open-question", "design-deviation"], ["text", "conflicts"]), [
947
- ...factFieldStrings(contractFacts, ["constraint", "open-question"], ["text"]),
948
- ...factFieldStrings(planFacts, ["design-deviation"], ["conflicts"]),
949
- ...factFieldStrings(scoutFacts, ["design-evidence"], ["conflicts"]),
950
- ]);
951
- return { requirementCoverage, targetPaths, componentDecisions, blockers };
952
- }
953
- export function renderShadowDiff(result) {
954
- const lines = ["# Shadow Dual Write Diff", ""];
955
- const sections = [
956
- ["Requirement Coverage", result.requirementCoverage],
957
- ["Target Paths", result.targetPaths],
958
- ["Component Decisions", result.componentDecisions],
959
- ["Blockers", result.blockers],
960
- ];
961
- for (const [title, dimension] of sections) {
962
- lines.push(`## ${title}`);
963
- lines.push(`- matched: ${dimension.matched.length}`);
964
- lines.push(`- factsOnly: ${dimension.factsOnly.length}`);
965
- lines.push(`- authorityOnly: ${dimension.authorityOnly.length}`);
966
- for (const item of dimension.matched)
967
- lines.push(` - [=] ${item}`);
968
- for (const item of dimension.factsOnly)
969
- lines.push(` - [+] ${item}`);
970
- for (const item of dimension.authorityOnly)
971
- lines.push(` - [-] ${item}`);
972
- lines.push("");
973
- }
974
- return lines.join("\n");
975
- }