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

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 +31 -2309
  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
@@ -97,8 +97,6 @@ export function renderFrontendPlanMarkdown(contract) {
97
97
  "## Source Fidelity Ledger",
98
98
  sourceFidelityLedger(contract),
99
99
  "",
100
- "## Implementation Steps",
101
- bulletList(contract.implementationSteps) || "_(not specified)_",
102
100
  "",
103
101
  "## Target Files",
104
102
  bulletList(contract.targets.files) || "_(none)_",
@@ -1,8 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { TYPED_EVENT_FACT_KINDS, stageTypedEventRecord, } from "./frontend-typed-event-store.js";
3
- import { createDagEventObserver } from "./event-observer.js";
4
3
  /**
5
- * M1 shadow-only provider/model capability matrix (AC-003).
4
+ * Provider/model capability fact matrix (AC-003).
6
5
  *
7
6
  * Records seven provider/model capability facts as read-only observations:
8
7
  * - `terminal-commit`
@@ -13,15 +12,12 @@ import { createDagEventObserver } from "./event-observer.js";
13
12
  * - `provider-ended-without-terminal`
14
13
  * - `read-only-fact-channel`
15
14
  *
16
- * This matrix is intentionally shadow-only: `recordProviderCapabilityFact`
17
- * returns a record for audit, never a branch-control signal, and this module
18
- * must never be imported into `node-execution.ts`'s model-selection / branch
19
- * control path. It is deliberately distinct from `frontend-project-capability`
20
- * (framework / mock discovery).
15
+ * `recordProviderCapabilityFact` returns a staged record for audit, never a
16
+ * branch-control signal, and this module is deliberately distinct from
17
+ * `frontend-project-capability` (framework / mock discovery).
21
18
  *
22
- * The read-only fact channel composes the existing `createDagEventObserver`
23
- * (which already swallows its own write errors) so that a shadow-channel
24
- * failure can never affect canonical DAG execution.
19
+ * These facts are staged independently from the canonical DAG event stream;
20
+ * recording them never participates in model selection or branch control.
25
21
  */
26
22
  export const PROVIDER_CAPABILITY_FACT_KINDS = TYPED_EVENT_FACT_KINDS;
27
23
  /** Terminal finish-reason shapes, modeled after
@@ -55,8 +51,8 @@ function readExplicitFinishReason(source) {
55
51
  function isRecord(value) {
56
52
  return typeof value === "object" && value !== null && !Array.isArray(value);
57
53
  }
58
- /** True when an observed provider event is terminal-shaped. Shadow-only: the
59
- * result is a fact, not a control decision. */
54
+ /** True when an observed provider event is terminal-shaped. The result is a
55
+ * fact, not a control decision. */
60
56
  export function isTerminalProviderEventShape(event) {
61
57
  const type = typeof event.type === "string" ? event.type : undefined;
62
58
  return type !== undefined && TERMINAL_FINISH_REASON_TYPES.has(type);
@@ -108,52 +104,3 @@ export function recordProviderCapabilityFact(options) {
108
104
  });
109
105
  return { eventId, record };
110
106
  }
111
- /**
112
- * Attach a shadow observer that records `read-only-fact-channel` facts while
113
- * delegating the canonical event JSONL to the existing `createDagEventObserver`.
114
- * The shadow channel never throws into the caller: any recording failure is
115
- * swallowed so canonical execution is unaffected.
116
- */
117
- export function createProviderCapabilityShadowObserver(options) {
118
- const base = createDagEventObserver({
119
- eventsJsonlPath: options.eventsJsonlPath,
120
- dagRunId: options.dagRunId,
121
- spec: options.spec,
122
- });
123
- const recordReadOnlyFact = (eventType) => {
124
- try {
125
- recordProviderCapabilityFact({
126
- store: options.store,
127
- attemptId: options.attemptId,
128
- requestId: options.requestId,
129
- fact: { kind: "read-only-fact-channel", eventType },
130
- });
131
- }
132
- catch {
133
- // Shadow channel must never affect canonical execution.
134
- }
135
- };
136
- const observer = {
137
- onRunStart: async (state) => {
138
- recordReadOnlyFact("dag.run.started");
139
- await base.observer.onRunStart?.(state);
140
- },
141
- onNodeStart: async (nodeId, state) => {
142
- recordReadOnlyFact("dag.node.started");
143
- await base.observer.onNodeStart?.(nodeId, state);
144
- },
145
- onNodeOutput: async (nodeId, chunk, state) => {
146
- recordReadOnlyFact("dag.node.output");
147
- await base.observer.onNodeOutput?.(nodeId, chunk, state);
148
- },
149
- onNodeFinish: async (nodeId, state) => {
150
- recordReadOnlyFact("dag.node.finished");
151
- await base.observer.onNodeFinish?.(nodeId, state);
152
- },
153
- onRunFinish: async (state) => {
154
- recordReadOnlyFact("dag.run.finished");
155
- await base.observer.onRunFinish?.(state);
156
- },
157
- };
158
- return { observer, flush: base.flush };
159
- }
@@ -25,6 +25,27 @@ import { topoSortToRanks } from "./topo.js";
25
25
  /** Canonical contract artifact default location; overridden by the design
26
26
  * policy shell's configured outputDir/artifactName when present. */
27
27
  const FRONTEND_CANONICAL_CONTRACT_DEFAULT_REL_PATH = "contracts/frontend-implementation-contract.json";
28
+ /**
29
+ * Run-owned artifacts a frontend node writes *outside* its own node directory.
30
+ *
31
+ * An imported node keeps its parent FINISHED record in the child run, so the
32
+ * child never executes the body that produced these files. Any downstream node
33
+ * that reads them must still find them in the child run directory: smoke r31
34
+ * saw a review-triggered recovery die at `frontend-review-context-shell` with
35
+ * `ENOENT contracts/frontend-worktree-baseline.json`, because
36
+ * `frontend-writer-admission-shell` is imported (its baseline capture is a side
37
+ * effect of the node body) while the review context requires that file.
38
+ *
39
+ * Entries listed as required fail the staging closed when the parent lacks
40
+ * them; optional entries (for example the lint baseline, which only exists when
41
+ * lint is configured) are copied when present.
42
+ */
43
+ const FRONTEND_IMPORTED_NODE_RUN_ARTIFACTS = {
44
+ "frontend-writer-admission-shell": {
45
+ required: ["contracts/frontend-worktree-baseline.json"],
46
+ optional: ["contracts/frontend-lint-baseline.json"],
47
+ },
48
+ };
28
49
  function resolveDeps(deps) {
29
50
  return {
30
51
  readParentState: deps?.readParentState ?? readDagRunState,
@@ -397,6 +418,41 @@ async function materializeChildStaging(input) {
397
418
  newRunDir: stagingDir,
398
419
  relativePath: FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT,
399
420
  });
421
+ // Imported nodes keep their parent FINISHED record, so the run-owned files
422
+ // their bodies wrote outside the node directory travel with them (smoke r31:
423
+ // a review-triggered recovery reached review-context with no worktree
424
+ // baseline and hard-failed on ENOENT).
425
+ const importedRunArtifacts = [];
426
+ for (const nodeId of plan.importedNodeIds) {
427
+ const artifactSpec = FRONTEND_IMPORTED_NODE_RUN_ARTIFACTS[nodeId];
428
+ if (!artifactSpec)
429
+ continue;
430
+ for (const relativePath of artifactSpec.required) {
431
+ const copied = await copyArtifactVerified({
432
+ parentRunDir,
433
+ newRunDir: stagingDir,
434
+ relativePath,
435
+ });
436
+ importedRunArtifacts.push({
437
+ sourceRelativePath: relativePath,
438
+ ...copied,
439
+ });
440
+ }
441
+ for (const relativePath of artifactSpec.optional) {
442
+ const sourcePath = path.join(parentRunDir, ...relativePath.split("/"));
443
+ if (!(await fileExists(sourcePath)))
444
+ continue;
445
+ const copied = await copyArtifactVerified({
446
+ parentRunDir,
447
+ newRunDir: stagingDir,
448
+ relativePath,
449
+ });
450
+ importedRunArtifacts.push({
451
+ sourceRelativePath: relativePath,
452
+ ...copied,
453
+ });
454
+ }
455
+ }
400
456
  // The scheduler gates every shape boundary by its committed fact once the
401
457
  // boundary's source node is FINISHED. Imported nodes stay FINISHED in the
402
458
  // child, so their boundaries must inherit the parent's committed facts or
@@ -470,6 +526,7 @@ async function materializeChildStaging(input) {
470
526
  relativePath: writerAdmissionResult.destinationRelativePath,
471
527
  sha256: writerAdmissionResult.sha256,
472
528
  },
529
+ importedRunArtifacts,
473
530
  importedFacts,
474
531
  };
475
532
  await writeJsonVerified(stagingDir, FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, importManifest);
@@ -24,6 +24,50 @@ export const frontendCommandCategorySchema = z.enum([
24
24
  "unit-test",
25
25
  "trace",
26
26
  ]);
27
+ /**
28
+ * Ordered string-blob rules. Order is the contract: the first matching rule wins,
29
+ * so rules that share keywords keep their original precedence. `nodeIdIncludes`
30
+ * and `blobIncludes` are alternatives within one rule (logical OR).
31
+ */
32
+ const BLOB_RULES = [
33
+ {
34
+ result: "contract",
35
+ nodeIdIncludes: ["contract"],
36
+ blobIncludes: ["contract mismatch", "source binding"],
37
+ },
38
+ { result: "trace", nodeIdIncludes: ["trace"], blobIncludes: ["trace:"] },
39
+ { result: "review", nodeIdIncludes: ["review"] },
40
+ {
41
+ result: "unit-test",
42
+ nodeIdIncludes: ["behavior"],
43
+ blobIncludes: ["vitest", "jest", "npm test"],
44
+ },
45
+ {
46
+ result: "typecheck",
47
+ blobIncludes: ["typecheck", "tsc"],
48
+ blobPatterns: [/error ts\d+/i],
49
+ },
50
+ { result: "lint", blobIncludes: ["eslint", "lint"] },
51
+ // "vite build"/"next build" are subsumed by "build".
52
+ { result: "build", blobIncludes: ["build"] },
53
+ {
54
+ result: "component-test",
55
+ blobIncludes: ["testing-library", "component", "render("],
56
+ },
57
+ {
58
+ result: "dependency",
59
+ blobIncludes: ["package.json", "unapproved dependency", "npm install"],
60
+ },
61
+ {
62
+ result: "credential",
63
+ blobIncludes: ["credential", "api key", "secret"],
64
+ },
65
+ { result: "deploy", blobIncludes: ["deploy", "production release"] },
66
+ {
67
+ result: "spec-unclear",
68
+ blobIncludes: ["spec unclear", "requirement ambiguous", "needs human"],
69
+ },
70
+ ];
27
71
  export function classifyFrontendFailure(input) {
28
72
  // AC-2: structured evidence takes priority over string-blob heuristics.
29
73
  if (input.failureCategory === "write-guard") {
@@ -39,86 +83,21 @@ export function classifyFrontendFailure(input) {
39
83
  return "trace";
40
84
  }
41
85
  if (input.commandCategory) {
42
- switch (input.commandCategory) {
43
- case "typecheck":
44
- return "typecheck";
45
- case "build":
46
- return "build";
47
- case "lint":
48
- return "lint";
49
- case "component-test":
50
- return "component-test";
51
- case "unit-test":
52
- return "unit-test";
53
- }
86
+ // "trace" already returned above, so the remaining categories map 1:1.
87
+ return input.commandCategory;
54
88
  }
55
- // Fallback: string-blob heuristic, preserved in its original branch order so
56
- // legacy assertions (error TS2304 → typecheck, write-guard → path, etc.) stay green.
89
+ // Fallback: ordered string-blob heuristics. Only the shell executor's
90
+ // structured category proves a write-boundary failure, so blob text never
91
+ // overrides the structured checks above.
57
92
  const blob = `${input.nodeId}\n${input.failureCategory ?? ""}\n${input.stdout ?? ""}\n${input.stderr ?? ""}`.toLowerCase();
58
- if (input.nodeId.includes("contract") ||
59
- blob.includes("contract mismatch") ||
60
- blob.includes("source binding")) {
61
- return "contract";
62
- }
63
- // Shell verification may contain test-runner logs from unrelated tests. Only
64
- // the shell executor's structured category proves a write-boundary failure.
65
- if (input.failureCategory === "write-guard") {
66
- return "path";
67
- }
68
- if (input.nodeId.includes("trace") || blob.includes("trace:")) {
69
- return "trace";
70
- }
71
- if (input.nodeId.includes("review")) {
72
- return "review";
73
- }
74
- // Classify the failed verification command before inspecting its output.
75
- // Full test output includes unrelated passing-test titles such as
76
- // "secret-shaped" and "write-guard failure".
77
- if (blob.includes("vitest") ||
78
- blob.includes("jest") ||
79
- blob.includes("npm test") ||
80
- input.nodeId.includes("behavior")) {
81
- return "unit-test";
82
- }
83
- if (blob.includes("typecheck") ||
84
- blob.includes("tsc") ||
85
- /error ts\d+/i.test(blob)) {
86
- return "typecheck";
87
- }
88
- if (blob.includes("eslint") || blob.includes("lint")) {
89
- return "lint";
90
- }
91
- if (blob.includes("build") ||
92
- blob.includes("vite build") ||
93
- blob.includes("next build")) {
94
- return "build";
95
- }
96
- if (blob.includes("testing-library") ||
97
- blob.includes("component") ||
98
- blob.includes("render(")) {
99
- return "component-test";
100
- }
101
- if (blob.includes("package.json") ||
102
- blob.includes("unapproved dependency") ||
103
- blob.includes("npm install")) {
104
- return "dependency";
105
- }
106
- if (blob.includes("credential") ||
107
- blob.includes("api key") ||
108
- blob.includes("secret")) {
109
- return "credential";
110
- }
111
- if (blob.includes("deploy") || blob.includes("production release")) {
112
- return "deploy";
113
- }
114
- if (blob.includes("spec unclear") ||
115
- blob.includes("requirement ambiguous") ||
116
- blob.includes("needs human")) {
117
- return "spec-unclear";
93
+ for (const rule of BLOB_RULES) {
94
+ if (rule.nodeIdIncludes?.some((keyword) => input.nodeId.includes(keyword)) ||
95
+ rule.blobIncludes?.some((keyword) => blob.includes(keyword)) ||
96
+ rule.blobPatterns?.some((pattern) => pattern.test(blob))) {
97
+ return rule.result;
98
+ }
118
99
  }
119
100
  if (input.failureCategory === "invalid-output")
120
101
  return "contract";
121
- if (input.failureCategory === "write-guard")
122
- return "path";
123
102
  return "unknown";
124
103
  }
@@ -96,74 +96,6 @@ export async function captureCumulativeDiffContext(input) {
96
96
  }
97
97
  return assembleCumulativeDiffContext({ baselineEntries, currentEntries });
98
98
  }
99
- const TYPED_TO_LEGACY = {
100
- approve_review: "pass",
101
- request_review_changes: "request-revision",
102
- };
103
- function isTypedReviewTerminalVerdict(value) {
104
- return value === "approve_review" || value === "request_review_changes";
105
- }
106
- function isLegacyReviewVerdict(value) {
107
- return value === "pass" || value === "request-revision";
108
- }
109
- /**
110
- * Deterministic, fail-closed equivalence between the typed review terminal
111
- * verdict(s) observed in the attempt and the legacy JSON verdict.
112
- *
113
- * Fail-closed cases (all yield `match: false` with an explicit reason):
114
- * - no typed terminal fact;
115
- * - more than one typed terminal fact (conflicting);
116
- * - an unknown typed terminal kind;
117
- * - a missing/illegal legacy JSON verdict.
118
- */
119
- export function compareTypedReviewToLegacyJsonVerdict(input) {
120
- const kinds = [...input.typedKinds];
121
- const legacyVerdict = isLegacyReviewVerdict(input.legacyVerdict ?? "")
122
- ? input.legacyVerdict
123
- : undefined;
124
- if (kinds.length === 0) {
125
- return {
126
- typedVerdict: undefined,
127
- legacyVerdict,
128
- match: false,
129
- reason: "typed review terminal fact missing",
130
- };
131
- }
132
- if (kinds.length > 1) {
133
- return {
134
- typedVerdict: undefined,
135
- legacyVerdict,
136
- match: false,
137
- reason: `conflicting typed review terminal facts: ${[...new Set(kinds)].join(",")}`,
138
- };
139
- }
140
- const typedVerdict = kinds[0];
141
- if (!isTypedReviewTerminalVerdict(typedVerdict)) {
142
- return {
143
- typedVerdict: undefined,
144
- legacyVerdict,
145
- match: false,
146
- reason: `unknown typed review terminal fact kind: ${typedVerdict}`,
147
- };
148
- }
149
- if (!legacyVerdict) {
150
- return {
151
- typedVerdict,
152
- legacyVerdict: undefined,
153
- match: false,
154
- reason: "legacy JSON verdict missing or not pass|request-revision",
155
- };
156
- }
157
- const expected = TYPED_TO_LEGACY[typedVerdict];
158
- return {
159
- typedVerdict,
160
- legacyVerdict,
161
- match: expected === legacyVerdict,
162
- reason: expected === legacyVerdict
163
- ? "typed review verdict matches legacy JSON verdict"
164
- : `typed review verdict ${typedVerdict} maps to ${expected} but legacy JSON verdict is ${legacyVerdict}`,
165
- };
166
- }
167
99
  /**
168
100
  * review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
169
101
  * 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
@@ -191,6 +123,37 @@ async function readRequiredJson(runDir, relativePath) {
191
123
  throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
192
124
  }
193
125
  }
126
+ /**
127
+ * Per-command verification evidence written by the verify shell.
128
+ *
129
+ * The review protocol treats shell exit status as authoritative ("typecheck,
130
+ * build, and test are required successful final exits"), but the verification
131
+ * trace only proves command/file/target-id *binding*. Smoke r31: with no exit
132
+ * evidence in the context, a compliant reviewer had to request changes for a
133
+ * missing check it could not perform. Absent the artifact the context says so
134
+ * explicitly instead of leaving the reviewer to guess.
135
+ */
136
+ async function readVerificationEvidence(runDir) {
137
+ const relativePath = "contracts/frontend-verification-evidence.json";
138
+ try {
139
+ const decoded = JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
140
+ if (decoded?.schemaId !== "frontend-verification-evidence-v1") {
141
+ return {
142
+ schemaId: "frontend-verification-evidence-v1",
143
+ status: "unavailable",
144
+ reason: `${relativePath} carries an unexpected schemaId`,
145
+ };
146
+ }
147
+ return { ...decoded, status: "available" };
148
+ }
149
+ catch (error) {
150
+ return {
151
+ schemaId: "frontend-verification-evidence-v1",
152
+ status: "unavailable",
153
+ reason: `${relativePath} unavailable: ${error instanceof Error ? error.message : String(error)}`,
154
+ };
155
+ }
156
+ }
194
157
  async function readOptionalLintAssessment(runDir) {
195
158
  const relativePath = "contracts/frontend-lint-assessment.json";
196
159
  try {
@@ -320,6 +283,7 @@ export async function runFrontendReviewContextGate(input) {
320
283
  const repairAssessment = await readOptionalRepairAssessment(input.runDir);
321
284
  const verifyFailure = await readOptionalVerifyFailure(input.runDir);
322
285
  const lintAssessment = await readOptionalLintAssessment(input.runDir);
286
+ const verificationEvidence = await readVerificationEvidence(input.runDir);
323
287
  const cumulativeDiff = await captureCumulativeDiffContext({
324
288
  runDir: input.runDir,
325
289
  workspaceRoot: input.workspaceRoot,
@@ -355,6 +319,16 @@ export async function runFrontendReviewContextGate(input) {
355
319
  sections: contractReviewArtifacts.index.sections,
356
320
  },
357
321
  verificationTrace,
322
+ verificationEvidence,
323
+ // Protocol-legal lint vocabulary; `lintConfigured: false` tells the
324
+ // reviewer that this task declares no lint surface at all, so
325
+ // `unavailable` reads as "not applicable", not "missing evidence".
326
+ lintStatus: lintAssessment
327
+ ? lintAssessment.status
328
+ : typeof verificationEvidence.lintStatus === "string"
329
+ ? verificationEvidence.lintStatus
330
+ : "unavailable",
331
+ lintConfigured: verificationEvidence.lintConfigured === true,
358
332
  ...(repairAssessment ? { repairAssessment } : {}),
359
333
  ...(verifyFailure ? { verifyFailure } : {}),
360
334
  ...(lintAssessment ? { lintAssessment } : {}),
@@ -121,11 +121,11 @@ export function createFrontendReviewScopeProtocol(input) {
121
121
  committedFacts: () => readCommittedEvents(input.getStore(), input.attemptId),
122
122
  assertComplete() { const missing = input.inventory?.scopes.filter(s => !completedScopeIds().has(s.id)) ?? []; if (missing.length)
123
123
  throw Object.assign(Error(`FRONTEND_REVIEW_SCOPE_INCOMPLETE: ${missing.map(s => s.id).join(", ")}`), { code: "FRONTEND_REVIEW_SCOPE_INCOMPLETE" }); },
124
- customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1 } }, required: ["id"], additionalProperties: false },
124
+ customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1, enum: input.inventory.scopes.map(scope => scope.id) } }, required: ["id"], additionalProperties: false },
125
125
  async execute(callId, params) {
126
126
  const scope = input.inventory.scopes.find(s => s.id === params.id);
127
127
  if (!scope || !active.has(params.id))
128
- return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed" });
128
+ return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed; use an exact activeScopeIds entry, not the node name", activeScopeIds: [...active], completedScopeIds: [...completedScopeIds()] });
129
129
  await input.inventory.validate();
130
130
  const store = input.getStore();
131
131
  const requestId = `${input.attemptId}:${callId}:${randomUUID()}`;
@@ -7,8 +7,10 @@
7
7
  * allowing its path is NOT dependency introduction by itself; the manifest
8
8
  * path check in classifyFrontendRisk requires this language to escalate.
9
9
  */
10
- const DEPENDENCY_INSTALL_LANGUAGE = /new\s+dependency|add\s+dependencies?|install\s+dependencies?|npm\s+(?:install|i)\b|yarn\s+(?:add|install)\b|pnpm\s+(?:add|install)\b|bun\s+(?:add|install)\b|新增依赖|添加依赖|安装依赖|引入依赖/i;
11
- const HIGH_RISK_PATTERNS = [
10
+ const DEPENDENCY_INSTALL_LANGUAGE = /new\s+dependency|add\s+dependenc(?:y|ies)|install\s+dependenc(?:y|ies)|npm\s+(?:install|i)\b|yarn\s+(?:add|install)\b|pnpm\s+(?:add|install)\b|bun\s+(?:add|install)\b|新增依赖|添加依赖|安装依赖|引入依赖/i;
11
+ /** Shared vocabulary for risk and topology routing. Keep policy-specific
12
+ * signals (design conflict/state infrastructure) below this list. */
13
+ export const FRONTEND_SHARED_ESCALATION_PATTERNS = [
12
14
  {
13
15
  id: "new-route",
14
16
  re: /new\s+route|新增路由|createBrowserRouter|add\s+route|新建页面路由/i,
@@ -45,6 +47,48 @@ const HIGH_RISK_PATTERNS = [
45
47
  id: "cross-domain-paths",
46
48
  re: /多个领域|cross-module|multiple\s+packages/i,
47
49
  },
50
+ ];
51
+ function isExplicitSharedExclusion(clause, pattern) {
52
+ const match = new RegExp(pattern.re.source, "i").exec(clause);
53
+ if (!match)
54
+ return false;
55
+ const prefix = clause.slice(0, match.index).trim();
56
+ const suffix = clause.slice(match.index + match[0].length).trim();
57
+ const actionInSignal = ["new-route", "new-dependency"].includes(pattern.id);
58
+ if (suffix && !(actionInSignal && /^no$/i.test(prefix) && /^is\s+(?:required|needed)$/i.test(suffix)))
59
+ return false;
60
+ if (/^(?:不涉及|不(?:修改|变更|调整|改变|新增|添加)|无需(?:修改|变更|调整|改变|新增|添加)|no\s+changes?\s+to|(?:do\s+not|don't)\s+(?:change|modify))$/i.test(prefix))
61
+ return true;
62
+ return actionInSignal && /^(?:不|禁止|无需|无须|不需要|no|without|do\s+not|don't)$/i.test(prefix);
63
+ }
64
+ /** Only a complete, independent scope exclusion can suppress a risk signal.
65
+ * Nested negation, conditions and trailing consequences remain risk evidence;
66
+ * an excluded occurrence never cancels another occurrence elsewhere. */
67
+ export function hasAffirmativeSharedSignal(blob, pattern) {
68
+ for (const sentence of blob.split(/[\n。.;;!?!?]/)) {
69
+ if (!pattern.re.test(sentence))
70
+ continue;
71
+ // Commas can attach a condition to an exclusion. A comma-separated list
72
+ // is independent only when every item is itself an explicit exclusion.
73
+ const clauses = sentence.split(/[,,、]/).map(part => part.trim().replace(/^(?:[-*+]|\d+\))\s+/, ""));
74
+ if (clauses.every(clause => FRONTEND_SHARED_ESCALATION_PATTERNS.some(candidate => isExplicitSharedExclusion(clause, candidate))))
75
+ continue;
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+ /** Extract the shared escalation vocabulary once. Shape routing reuses this
81
+ * exact result so risk and topology cannot drift on the same input. */
82
+ export function collectFrontendSharedSignals(blob, supervised = false) {
83
+ const signals = FRONTEND_SHARED_ESCALATION_PATTERNS
84
+ .filter((pattern) => hasAffirmativeSharedSignal(blob, pattern))
85
+ .map((pattern) => pattern.id);
86
+ if (supervised)
87
+ signals.push("supervised-profile");
88
+ return [...new Set(signals)];
89
+ }
90
+ const HIGH_RISK_PATTERNS = [
91
+ ...FRONTEND_SHARED_ESCALATION_PATTERNS,
48
92
  {
49
93
  id: "new-state-infra",
50
94
  re: /\bredux\b|\bzustand\b|\bpinia\b|\bjotai\b|\brecoil\b|新状态库|state\s+management\s+introduc/i,
@@ -77,6 +121,41 @@ function pathSpreadScore(paths) {
77
121
  .filter(Boolean));
78
122
  return tops.size;
79
123
  }
124
+ /**
125
+ * Narrow-product-surface test for genuinely small topology. A task qualifies
126
+ * only when its non-auxiliary (product) paths are at most two and all share the
127
+ * same two-segment prefix — `src/components/Button.tsx` + `test/components/*`
128
+ * is narrow, while `src/components/**` + `src/pages/**` spans two product
129
+ * domains and is not. Direct source files (`src/a.tsx`) collapse to their
130
+ * directory so siblings stay narrow. This is the structured replacement for the
131
+ * fragile "single component / local style" keyword gate: it measures the actual
132
+ * delivery surface instead of relying on how the author worded the request.
133
+ */
134
+ export function isNarrowProductSurface(paths) {
135
+ const auxiliaryRoot = /^(?:test|tests|__tests__|e2e|cypress|spec|specs|docs|documentation)$/i;
136
+ const product = paths
137
+ .map((raw) => raw.replace(/\\/g, "/").split("/").filter(Boolean))
138
+ .filter((segs) => segs.length > 0 && !auxiliaryRoot.test(segs[0] ?? ""));
139
+ if (product.length === 0 || product.length > 2)
140
+ return false;
141
+ // Every product path must name a concrete file or a concrete second-level
142
+ // directory. A top-level wide glob (`src/**`, `src/*`) covers the whole
143
+ // source tree and is NOT a small delivery surface.
144
+ for (const segs of product) {
145
+ const second = segs[1] ?? "";
146
+ if (!second || second === "*" || second === "**")
147
+ return false;
148
+ }
149
+ const prefixOf = (segs) => {
150
+ const second = segs[1] ?? "";
151
+ const secondIsFile = /\.[A-Za-z0-9]+$/.test(second);
152
+ return segs.length >= 2 && second && !secondIsFile
153
+ ? `${segs[0] ?? ""}/${segs[1]}`
154
+ : segs[0] ?? "";
155
+ };
156
+ const prefix = prefixOf(product[0] ?? []);
157
+ return product.every((segs) => prefixOf(segs) === prefix);
158
+ }
80
159
  /**
81
160
  * Classify frontend task risk. High-risk signals always win over small keywords.
82
161
  * supervised / explicit high governance never selects small topology.
@@ -116,10 +195,14 @@ export function classifyFrontendRisk(input) {
116
195
  if (pattern.re.test(blob))
117
196
  smallHits.push(pattern.id);
118
197
  }
119
- // small only if: no high-risk, paths concentrated, no remote API signals, not supervised
198
+ // small only if: no high-risk, non-empty concentrated paths, no remote API
199
+ // signals, not supervised, and not an explicitly large task. Keyword and
200
+ // explicit "small/low" complexity hints are no longer required — they were
201
+ // a fragile proxy for "this is genuinely a small change" and silently
202
+ // forced genuinely-small tasks with different wording into full gates.
120
203
  const hasApiSignal = /\b(api|fetch|axios|graphql|endpoint|远程|接口)\b/i.test(blob) &&
121
204
  !/\b(no\s+api|无接口|not-needed)\b/i.test(blob);
122
- const concentrated = pathSpreadScore(paths) <= 1 && paths.length <= 4;
205
+ const narrowSurface = isNarrowProductSurface(paths);
123
206
  if (supervised) {
124
207
  rejectedSignals.push(...smallHits.map((id) => `small:${id}:blocked-by-supervised`));
125
208
  return {
@@ -142,19 +225,18 @@ export function classifyFrontendRisk(input) {
142
225
  forceFullGates: true,
143
226
  };
144
227
  }
145
- if (smallHits.length > 0 &&
146
- concentrated &&
228
+ if (narrowSurface &&
147
229
  !hasApiSignal &&
148
- (input.complexity === "small" || smallHits.length >= 1)) {
230
+ (input.complexity ?? "").toLowerCase() !== "large") {
149
231
  return {
150
232
  selectedRisk: "small",
151
- signals: smallHits,
233
+ signals: smallHits.length > 0 ? smallHits : ["structured-small"],
152
234
  rejectedSignals,
153
- reason: `small signals without high-risk: ${smallHits.join(", ")}`,
235
+ reason: "narrow single-domain delivery surface without remote API, dependency, or escalation signals; small topology",
154
236
  forceFullGates: false,
155
237
  };
156
238
  }
157
- if (smallHits.length > 0 && (hasApiSignal || !concentrated)) {
239
+ if (smallHits.length > 0 && (hasApiSignal || !narrowSurface)) {
158
240
  rejectedSignals.push(...smallHits.map((id) => `small:${id}:blocked-by-${hasApiSignal ? "api-signal" : "path-spread"}`));
159
241
  return {
160
242
  selectedRisk: "standard",