@tea-agent/loop-agent 0.42.0-next.1 → 0.42.0-next.10

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 (238) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +319 -1
  3. package/dist/application/dag/generate-task-dag.js +75 -19
  4. package/dist/application/dag/run-dag.js +41 -0
  5. package/dist/application/task-lifecycle/advance.js +24 -5
  6. package/dist/application/task-lifecycle/observe.js +171 -17
  7. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  8. package/dist/application/task-lifecycle/recommendations.js +13 -2
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +7 -0
  11. package/dist/cli/program.js +6 -1
  12. package/dist/commands/client-recovery.js +3 -0
  13. package/dist/commands/dag-follow-up.js +138 -0
  14. package/dist/commands/dag-rerun-task.js +2 -0
  15. package/dist/commands/init.js +27 -1
  16. package/dist/commands/task-advance.js +19 -0
  17. package/dist/executors/dag-pi-executor.js +3976 -76
  18. package/dist/executors/pi-executor.js +15 -4
  19. package/dist/executors/pi-read-budget-policy.js +239 -0
  20. package/dist/executors/shell-executor.js +978 -188
  21. package/dist/executors/shell-write-guard.js +7 -0
  22. package/dist/infrastructure/console/operation-store.js +226 -6
  23. package/dist/shared/dag-failure-category.js +12 -0
  24. package/dist/shared/openspec-spec.js +70 -4
  25. package/dist/shared/operator/capabilities.js +7 -0
  26. package/dist/task/config-types.js +105 -5
  27. package/dist/task/contract/adopt.js +4 -0
  28. package/dist/task/contract/import-revision.js +4 -0
  29. package/dist/task/contract/project.js +3 -0
  30. package/dist/task/contract/schema.js +2 -1
  31. package/dist/task/frontend-project-capability.js +203 -20
  32. package/dist/task/runtime.js +5 -2
  33. package/dist/task/source-prepare/build-draft.js +3 -3
  34. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  35. package/dist/task/source-prepare/prepare.js +126 -21
  36. package/dist/task/source-prepare/semantic-intake.js +6 -2
  37. package/dist/task/source-references.js +22 -1
  38. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  39. package/dist/worker/console/chat/model-resolver.js +7 -4
  40. package/dist/worker/console/chat/pi-runtime.js +258 -11
  41. package/dist/worker/console/chat/repo-browser.js +10 -3
  42. package/dist/worker/console/chat/routes.js +165 -71
  43. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  44. package/dist/worker/console/chat/session-catalog.js +2 -0
  45. package/dist/worker/console/chat/session-store.js +159 -70
  46. package/dist/worker/console/chat/shortcuts.js +19 -3
  47. package/dist/worker/console/chat/turn-process.js +111 -56
  48. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  49. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  50. package/dist/worker/console/index.js +3 -0
  51. package/dist/worker/console/operator-actions.js +169 -7
  52. package/dist/worker/console/pi-readiness.js +3 -2
  53. package/dist/worker/console/server.js +23 -1
  54. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  55. package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
  56. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  57. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  58. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  59. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  60. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  70. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  71. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  72. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  73. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  74. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  75. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  81. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  82. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  83. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  84. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  85. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  86. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  87. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  88. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  89. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  90. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  91. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  92. package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
  93. package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
  94. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  95. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  96. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  97. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  98. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  99. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  100. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  101. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  102. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  103. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  104. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  105. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  106. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  107. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  108. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  109. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  110. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  111. package/dist/worker/console/static/index.html +2 -2
  112. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  113. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  114. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  115. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  116. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  117. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  118. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  119. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  120. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  121. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  122. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  123. package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
  124. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  125. package/dist/worker/console/workspace-context.js +115 -1
  126. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  127. package/dist/worker/observe/routes.js +4 -0
  128. package/dist/worker/observe/static/dag-helpers.js +4 -2
  129. package/dist/worker/observe/static/dag-node-purpose.js +5 -0
  130. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  131. package/dist/worker/observe/static/kpi.js +2 -2
  132. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  133. package/dist/worker/observe/static/operator-chrome.js +37 -23
  134. package/dist/worker/observe/static/relations.js +2 -2
  135. package/dist/worker/observe/static/router.d.ts +12 -1
  136. package/dist/worker/observe/static/router.js +48 -6
  137. package/dist/worker/observe/static/run-processing.js +4 -2
  138. package/dist/worker/observe/static/shell-chrome.js +2 -2
  139. package/dist/worker/observe/static/state.js +5 -0
  140. package/dist/worker/observe/static/styles.css +95 -0
  141. package/dist/worker/observe/static/views/dag-inspector.js +14 -7
  142. package/dist/worker/observe/static/views/dag.js +10 -2
  143. package/dist/worker/observe/static/views/dags.js +3 -1
  144. package/dist/worker/observe/static/views/dashboard.js +14 -6
  145. package/dist/worker/observe/static/views/pool.js +7 -2
  146. package/dist/worker/observe/static/views/run.js +12 -3
  147. package/dist/worker/observe/static/views/session-timeline.js +69 -10
  148. package/dist/worker/observe/static/views/task.js +7 -2
  149. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  150. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  151. package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
  152. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  153. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  154. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  155. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  156. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  157. package/dist/workflows/dag/frontend-closeout.js +221 -0
  158. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  159. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  160. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  161. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  162. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  163. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  164. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  165. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  166. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  167. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  168. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  169. package/dist/workflows/dag/frontend-repair.js +1 -432
  170. package/dist/workflows/dag/frontend-review-context.js +261 -15
  171. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  172. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  173. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  174. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  175. package/dist/workflows/dag/frontend-shape.js +435 -0
  176. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  177. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  178. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  179. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  180. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  181. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  182. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  183. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  184. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  185. package/dist/workflows/dag/init-hybrid.js +1028 -562
  186. package/dist/workflows/dag/interrupt-request.js +7 -0
  187. package/dist/workflows/dag/node-execution.js +828 -4
  188. package/dist/workflows/dag/prompt.js +70 -3
  189. package/dist/workflows/dag/recovery-lease.js +170 -0
  190. package/dist/workflows/dag/report.js +37 -1
  191. package/dist/workflows/dag/rerun-feedback.js +315 -1
  192. package/dist/workflows/dag/rerun-plan.js +17 -6
  193. package/dist/workflows/dag/rerun-run.js +44 -6
  194. package/dist/workflows/dag/rerun-task.js +251 -13
  195. package/dist/workflows/dag/retry-policy.js +219 -104
  196. package/dist/workflows/dag/runner.js +580 -124
  197. package/dist/workflows/dag/scheduler.js +133 -20
  198. package/dist/workflows/dag/types.js +244 -16
  199. package/dist/workflows/dag/validate.js +28 -12
  200. package/docs/examples/README.md +5 -0
  201. package/docs/init-surface.manifest.json +30 -12
  202. package/docs/skills/vetted-skill-registry.md +4 -2
  203. package/docs/templates/README.md +2 -0
  204. package/docs/templates/agent-dag-report.schema.json +8 -2
  205. package/docs/templates/agent-dag.schema.json +1 -1
  206. package/docs/templates/backend-test-dag.json +23 -20
  207. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  208. package/docs/templates/frontend-implementation-dag.json +89 -0
  209. package/docs/templates/spec-registry.schema.json +45 -0
  210. package/harness.json +1 -1
  211. package/package.json +4 -3
  212. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  213. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  214. package/skills/frontend-contract/SKILL.md +23 -0
  215. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  216. package/skills/frontend-design-review/SKILL.md +22 -41
  217. package/skills/frontend-plan/SKILL.md +26 -0
  218. package/skills/frontend-plan/references/decision-contract.md +37 -0
  219. package/skills/frontend-plan/references/design-decisions.md +17 -0
  220. package/skills/frontend-review/SKILL.md +20 -15
  221. package/skills/frontend-review/references/review-findings.md +6 -7
  222. package/skills/frontend-scout/SKILL.md +25 -0
  223. package/skills/frontend-scout/references/design-evidence.md +16 -0
  224. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  225. package/skills/frontend-verification/SKILL.md +1 -1
  226. package/skills/loop-agent/references/command-reference.md +1 -0
  227. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  228. package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
  229. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
  230. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
  231. package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
  232. package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
  233. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
  234. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
  235. package/skills/frontend-implementation/SKILL.md +0 -52
  236. package/skills/frontend-implementation/references/code-standards.md +0 -33
  237. package/skills/frontend-implementation/references/design-spec.md +0 -56
  238. package/skills/frontend-implementation/references/node-contracts.md +0 -31
@@ -13,6 +13,8 @@ import { runDagUseCase } from "./run-dag.js";
13
13
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
14
14
  import { writeTaskConfig } from "../../infrastructure/harness/task-store.js";
15
15
  import { runFrontendPreflight } from "../../task/frontend-preflight.js";
16
+ import { discoverFrontendProjectCapability } from "../../task/frontend-project-capability.js";
17
+ import { classifyTaskDemand } from "../../task/task-demand-routing.js";
16
18
  import { observeTaskContract } from "../../task/contract/observe.js";
17
19
  import { adoptTaskContract } from "../../task/contract/adopt.js";
18
20
  const PLACEHOLDER_WRITESET_MARKER = "REPLACE/WITH";
@@ -88,6 +90,63 @@ function buildBasicValidationLevel(outputPath) {
88
90
  note: "Internal generation validation is basic only; standalone strict governance validation must be confirmed separately.",
89
91
  };
90
92
  }
93
+ async function promoteAutomaticFrontendTaskKind(input) {
94
+ if (input.taskConfig.taskKind !== "standard" ||
95
+ input.taskConfig.taskKindExplicit) {
96
+ return false;
97
+ }
98
+ let requirementMarkdown = "";
99
+ try {
100
+ requirementMarkdown = await readFile(path.join(getTaskPaths(input.repoRoot, input.taskId).sourceDir, "需求.md"), "utf8");
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ if (!requirementMarkdown.trim())
106
+ return false;
107
+ let frontendProject;
108
+ try {
109
+ frontendProject = await discoverFrontendProjectCapability(input.repoRoot);
110
+ }
111
+ catch {
112
+ // The existing standard route remains the safe fallback when capability
113
+ // discovery is unavailable.
114
+ }
115
+ const classification = classifyTaskDemand({
116
+ title: input.taskConfig.title,
117
+ requirementMarkdown,
118
+ allowedPaths: input.taskConfig.allowedPaths,
119
+ frontendProject: frontendProject
120
+ ? {
121
+ framework: frontendProject.framework,
122
+ evidencePaths: frontendProject.evidencePaths,
123
+ }
124
+ : undefined,
125
+ });
126
+ if (classification.kind !== "frontend")
127
+ return false;
128
+ await writeTaskConfig(input.repoRoot, input.taskId, {
129
+ ...input.taskConfig,
130
+ taskKind: "frontend-implementation",
131
+ dagFallbackReason: "Automatically classified as a high-confidence frontend implementation task.",
132
+ });
133
+ const state = await observeTaskContract({
134
+ repoRoot: input.repoRoot,
135
+ taskId: input.taskId,
136
+ });
137
+ if (!state.ref || !state.observedCanonicalHash) {
138
+ throw new Error("automatic frontend routing could not reconcile the managed Task Contract");
139
+ }
140
+ await adoptTaskContract({
141
+ repoRoot: input.repoRoot,
142
+ taskId: input.taskId,
143
+ expectedRevision: state.ref.revision,
144
+ expectedObservedHash: state.observedCanonicalHash,
145
+ requestId: `automatic-frontend-route-${input.taskId}-${randomUUID()}`,
146
+ requestPayloadSha256: randomUUID().replaceAll("-", "").padEnd(64, "0"),
147
+ });
148
+ return true;
149
+ }
91
150
  function isBroadWriteSetEntryForPacket(entry) {
92
151
  const normalized = entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
93
152
  if (!normalized || normalized === "." || normalized === "./")
@@ -280,12 +339,25 @@ export async function generateTaskDagUseCase(input) {
280
339
  requestPayloadSha256: randomUUID().replaceAll("-", "").padEnd(64, "0"),
281
340
  });
282
341
  }
283
- const candidateResult = await initHybridDagFromTask(repoRoot, parsed.taskId, {
342
+ const routingTaskConfig = await loadTaskConfig(repoRoot, parsed.taskId);
343
+ let initialTemplate = routingTaskConfig.taskKind === "frontend-implementation"
344
+ ? "frontend-implementation"
345
+ : "standard-dag";
346
+ if (initialTemplate === "standard-dag") {
347
+ const promoted = await promoteAutomaticFrontendTaskKind({
348
+ repoRoot,
349
+ taskId: parsed.taskId,
350
+ taskConfig: routingTaskConfig,
351
+ });
352
+ if (promoted)
353
+ initialTemplate = "frontend-implementation";
354
+ }
355
+ let candidateResult = await initHybridDagFromTask(repoRoot, parsed.taskId, {
284
356
  outputPath: parsed.outputPath,
285
- template: "standard-dag",
357
+ template: initialTemplate,
286
358
  rerunFeedback: parsed.rerunFeedback,
287
359
  });
288
- const candidateValidateSummary = await validateDagUseCase(buildValidateInput(repoRoot, candidateResult.outputPath, parsed));
360
+ let candidateValidateSummary = await validateDagUseCase(buildValidateInput(repoRoot, candidateResult.outputPath, parsed));
289
361
  const profileRouting = await resolveProfileRouting(repoRoot, parsed, candidateValidateSummary.governanceProfile ?? {
290
362
  profile: "standard",
291
363
  process: [],
@@ -308,22 +380,6 @@ export async function generateTaskDagUseCase(input) {
308
380
  profileRouting.selectedByProfile = parsed.profile;
309
381
  }
310
382
  }
311
- // Persist the automatic business workflow route while preserving the
312
- // governance profile as a separate routing dimension.
313
- if (hasAutomaticTaskSourceRoute &&
314
- candidateResult.template === "frontend-implementation") {
315
- const taskConfig = await loadTaskConfig(repoRoot, parsed.taskId);
316
- // Human-explicit taskKind (incl. explicit standard) is a hard contract:
317
- // never rewrite task.json taskKind via semantic classification.
318
- if (!taskConfig.taskKindExplicit &&
319
- (!taskConfig.taskKind || taskConfig.taskKind === "standard")) {
320
- await writeTaskConfig(repoRoot, parsed.taskId, {
321
- ...taskConfig,
322
- taskKind: "frontend-implementation",
323
- dagFallbackReason: "Automatically classified as a high-confidence frontend implementation task.",
324
- });
325
- }
326
- }
327
383
  const initResult = profileRouting.selectedTemplate === candidateResult.template
328
384
  ? candidateResult
329
385
  : await initHybridDagFromTask(repoRoot, parsed.taskId, {
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { createDagEventObserver } from "../../workflows/dag/event-observer.js";
2
3
  import { composeDagRunObservers } from "../../workflows/dag/observer-compose.js";
3
4
  import { createDagCanvasObserver, resolveCanvasPath, } from "../../workflows/dag/canvas-observer.js";
@@ -28,6 +29,41 @@ function buildRunDagNextSteps(runId) {
28
29
  `loop-agent dag report --run-id ${runId} --markdown # advanced forensic`,
29
30
  ];
30
31
  }
32
+ /** Shared production recovery seam for fresh runs and recovery continuations. */
33
+ export async function buildDefaultFrontendRecoveryDeps(repoRoot) {
34
+ // Keep the application layer free of a static worker→application cycle. The
35
+ // recovery controller is only needed after a frontend terminal failure.
36
+ const [{ openConsoleAppData }, { OperationStore }, { createOperationEventStore }, { LoopAgentClient }, { resolveSiblingLoopAgentBin },] = await Promise.all([
37
+ import("../../infrastructure/console/app-data.js"),
38
+ import("../../infrastructure/console/operation-store.js"),
39
+ import("../../worker/console/operation-sse.js"),
40
+ import("../../worker/loop-agent/loop-agent-client.js"),
41
+ import("../../worker/console/sibling-controller.js"),
42
+ ]);
43
+ const appData = openConsoleAppData({ repoRoot });
44
+ const store = new OperationStore(appData);
45
+ const events = createOperationEventStore({
46
+ persistenceDir: path.join(appData.operations, "events"),
47
+ });
48
+ const client = new LoopAgentClient({
49
+ loopAgentBin: resolveSiblingLoopAgentBin(),
50
+ artifactRoot: path.join(appData.root, "client-artifacts", appData.fingerprint),
51
+ resolveIdentity: true,
52
+ });
53
+ return {
54
+ store,
55
+ operationRunnerDeps: {
56
+ store,
57
+ events,
58
+ client,
59
+ repoRoot,
60
+ env: {
61
+ ...process.env,
62
+ LOOP_CONSOLE_APP_DATA: appData.root,
63
+ },
64
+ },
65
+ };
66
+ }
31
67
  export async function runDagUseCase(input) {
32
68
  const spec = await loadDagSpecFromFile(input.dagPath);
33
69
  assertValidDagSpec(spec);
@@ -80,6 +116,10 @@ export async function runDagUseCase(input) {
80
116
  ...(canvasError ? { canvasError } : {}),
81
117
  };
82
118
  }
119
+ const recovery = input.recovery ??
120
+ (!input.initOnly && spec.tasks.some((task) => task.id === "frontend-implement-pi")
121
+ ? await buildDefaultFrontendRecoveryDeps(input.cwd)
122
+ : undefined);
83
123
  const summary = await runDag(spec, {
84
124
  cwd: input.cwd,
85
125
  initOnly: input.initOnly,
@@ -90,6 +130,7 @@ export async function runDagUseCase(input) {
90
130
  ...(input.workerAssociation
91
131
  ? { workerAssociation: input.workerAssociation }
92
132
  : {}),
133
+ ...(recovery ? { recovery } : {}),
93
134
  });
94
135
  const canvasError = await flushCanvasSafely(canvas);
95
136
  if (resolvedCanvasPath) {
@@ -260,6 +260,10 @@ async function monitorRun(input) {
260
260
  failureCategory: "monitor-timeout",
261
261
  });
262
262
  }
263
+ function isStartupGateEligible(snapshot) {
264
+ const status = snapshot.source?.sourceFidelityStartup?.status;
265
+ return status === undefined || status === "complete" || status === "not-applicable";
266
+ }
263
267
  function nextMutationAction(actions) {
264
268
  for (const action of actions) {
265
269
  if (action.type !== "stop")
@@ -379,7 +383,10 @@ export async function advanceTaskLifecycle(input) {
379
383
  };
380
384
  }
381
385
  const token = parseGateToken(input.rejectGateToken);
382
- const currentGate = snapshot.gate ?? (await buildCurrentGate(input.repoRoot, input.taskId));
386
+ const currentGate = snapshot.gate ??
387
+ (isStartupGateEligible(snapshot)
388
+ ? await buildCurrentGate(input.repoRoot, input.taskId)
389
+ : null);
383
390
  if (!currentGate || !gateMatches(currentGate, token)) {
384
391
  return {
385
392
  taskId: input.taskId,
@@ -443,11 +450,12 @@ export async function advanceTaskLifecycle(input) {
443
450
  dryRun,
444
451
  skipFinalize: Boolean(input.skipFinalize),
445
452
  forcePrepareContract: Boolean(input.fromDraftPath) && !preparedContractThisCall,
453
+ startupRepairAttempted: preparedContractThisCall,
446
454
  });
447
455
  if (dryRun) {
448
456
  const plan = planOnce(snapshot);
449
457
  const plannedGate = snapshot.gate ??
450
- (snapshot.dagDraft?.exists
458
+ (isStartupGateEligible(snapshot) && snapshot.dagDraft?.exists
451
459
  ? await buildCurrentGate(input.repoRoot, input.taskId)
452
460
  : null);
453
461
  return {
@@ -499,7 +507,10 @@ export async function advanceTaskLifecycle(input) {
499
507
  }
500
508
  if (action.type === "create-task") {
501
509
  if (!snapshot.exists) {
502
- await createTask(input.repoRoot, input.taskId, input.title);
510
+ await createTask(input.repoRoot, input.taskId, input.title, {
511
+ taskKind: input.taskKind,
512
+ taskKindExplicit: input.taskKindExplicit,
513
+ });
503
514
  record = appendTransition(record, "task-created");
504
515
  completed.push("task-created");
505
516
  await saveLifecycleRecord(input.repoRoot, record);
@@ -586,6 +597,10 @@ export async function advanceTaskLifecycle(input) {
586
597
  continue;
587
598
  }
588
599
  if (action.type === "prepare-contract") {
600
+ const startupStatus = snapshot.source?.sourceFidelityStartup?.status;
601
+ const startupRepairAction = startupStatus === "missing" ||
602
+ startupStatus === "invalid" ||
603
+ startupStatus === "coverage-incomplete";
589
604
  // 生产 source fidelity Pi 接线(AC-PROD-001/005):仅 frontend-implementation
590
605
  // 任务注入 reconcile executor + 独立 reviewer;ineligible 返回 undefined 不接线。
591
606
  // 主路径与 semantic-intake retry 路径共用同一接线,保证任意一条生产路径都实际
@@ -731,7 +746,9 @@ export async function advanceTaskLifecycle(input) {
731
746
  message: "structured requirement draft via semantic intake; projected managed source",
732
747
  });
733
748
  preparedContractThisCall = true;
734
- if (refreshManagedContractThisCall || input.fromDraftPath) {
749
+ if (startupRepairAction ||
750
+ refreshManagedContractThisCall ||
751
+ input.fromDraftPath) {
735
752
  refreshManagedContractThisCall = false;
736
753
  regenerateDagThisCall = true;
737
754
  }
@@ -825,7 +842,9 @@ export async function advanceTaskLifecycle(input) {
825
842
  break;
826
843
  }
827
844
  preparedContractThisCall = true;
828
- if (refreshManagedContractThisCall || input.fromDraftPath) {
845
+ if (startupRepairAction ||
846
+ refreshManagedContractThisCall ||
847
+ input.fromDraftPath) {
829
848
  // The old DAG and gate were bound to the pre-mutation contract.
830
849
  refreshManagedContractThisCall = false;
831
850
  regenerateDagThisCall = true;
@@ -1,9 +1,11 @@
1
- import { access, readFile } from "node:fs/promises";
1
+ import { access, readFile, readdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { getTaskDir, getTaskPaths, getTaskStatus, loadTaskConfig, } from "../../task/runtime.js";
4
4
  import { logicalRevisionForState, observeTaskContractState, } from "../../task/contract/index.js";
5
5
  import { assessDagRunLiveness, listAllDagRunEntries, readDagRunSpec, readDagRunState, } from "../../workflows/dag/lifecycle.js";
6
6
  import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
7
+ import { parseLedgerJson, validateRequirementLedger, } from "../../task/source-prepare/ledger.js";
8
+ import { REQUIREMENT_LEDGER_FILE_NAME } from "../../task/source-prepare/types.js";
7
9
  import { loadLifecycleRecord } from "./record.js";
8
10
  import { buildGateBinding, buildWriteSetGate, buildWriteSetGateDelta, controllerIdentityAnchor, hashCanonicalPayload, normalizeWriteSet, sha256Hex, } from "./gates.js";
9
11
  async function exists(filePath) {
@@ -47,6 +49,140 @@ async function taskExists(repoRoot, taskId) {
47
49
  const paths = getTaskPaths(repoRoot, taskId);
48
50
  return exists(paths.taskConfigPath);
49
51
  }
52
+ async function assessReferenceEvidence(referencesDir) {
53
+ try {
54
+ const entries = await readdir(referencesDir);
55
+ const importedPrdCount = entries.filter((name) => name.endsWith(".md")).length;
56
+ return importedPrdCount > 0
57
+ ? { status: "present", importedPrdCount }
58
+ : { status: "absent-or-empty", importedPrdCount: 0 };
59
+ }
60
+ catch (error) {
61
+ if (error &&
62
+ typeof error === "object" &&
63
+ "code" in error &&
64
+ error.code === "ENOENT") {
65
+ return { status: "absent-or-empty", importedPrdCount: 0 };
66
+ }
67
+ return {
68
+ status: "unknown",
69
+ importedPrdCount: 0,
70
+ error: error instanceof Error ? error.message : String(error),
71
+ };
72
+ }
73
+ }
74
+ function hasLedgerStructure(value) {
75
+ if (!value || typeof value !== "object")
76
+ return false;
77
+ const ledger = value;
78
+ return (typeof ledger.taskId === "string" &&
79
+ typeof ledger.inputDigest === "string" &&
80
+ typeof ledger.requirementSha256 === "string" &&
81
+ Array.isArray(ledger.sourcePaths) &&
82
+ Array.isArray(ledger.fragments) &&
83
+ Array.isArray(ledger.canonicalRequirements) &&
84
+ Boolean(ledger.stats && typeof ledger.stats === "object") &&
85
+ Array.isArray(ledger.unresolved) &&
86
+ Array.isArray(ledger.conflicts) &&
87
+ Array.isArray(ledger.sourceResolutions) &&
88
+ typeof ledger.schemaReconcilerVersion === "string" &&
89
+ typeof ledger.builtAt === "string");
90
+ }
91
+ async function assessSourceFidelityStartup(input) {
92
+ if (!input.applicable) {
93
+ return {
94
+ status: "not-applicable",
95
+ ledgerPath: input.ledgerPath,
96
+ diagnostics: [],
97
+ };
98
+ }
99
+ if (input.referenceEvidenceError) {
100
+ return {
101
+ status: "invalid",
102
+ ledgerPath: input.ledgerPath,
103
+ diagnostics: [
104
+ {
105
+ code: "SOURCE_FIDELITY_LEDGER_STALE",
106
+ message: `imported PRD reference evidence is unreadable: ${input.referenceEvidenceError}`,
107
+ },
108
+ ],
109
+ };
110
+ }
111
+ let raw;
112
+ try {
113
+ raw = await readFile(input.ledgerPath, "utf-8");
114
+ }
115
+ catch (error) {
116
+ const missing = !(await exists(input.ledgerPath));
117
+ return {
118
+ status: missing ? "missing" : "invalid",
119
+ ledgerPath: input.ledgerPath,
120
+ diagnostics: [
121
+ {
122
+ code: missing
123
+ ? "SOURCE_FIDELITY_LEDGER_MISSING"
124
+ : "SOURCE_FIDELITY_LEDGER_STALE",
125
+ message: missing
126
+ ? "managed frontend task has no requirement-ledger.json"
127
+ : `requirement ledger is unreadable: ${error instanceof Error ? error.message : String(error)}`,
128
+ },
129
+ ],
130
+ };
131
+ }
132
+ try {
133
+ const ledger = parseLedgerJson(raw);
134
+ if (!hasLedgerStructure(ledger)) {
135
+ return {
136
+ status: "invalid",
137
+ ledgerPath: input.ledgerPath,
138
+ diagnostics: [
139
+ {
140
+ code: "SOURCE_FIDELITY_LEDGER_STALE",
141
+ message: "requirement ledger is missing required structural fields",
142
+ },
143
+ ],
144
+ };
145
+ }
146
+ const nonDecisionErrors = validateRequirementLedger(ledger, {
147
+ allowedUnresolved: true,
148
+ allowedConflicts: true,
149
+ });
150
+ if (nonDecisionErrors.length > 0) {
151
+ const coverageCodes = new Set([
152
+ "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
153
+ "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED",
154
+ ]);
155
+ return {
156
+ status: nonDecisionErrors.every((error) => coverageCodes.has(error.code))
157
+ ? "coverage-incomplete"
158
+ : "invalid",
159
+ ledgerPath: input.ledgerPath,
160
+ diagnostics: nonDecisionErrors,
161
+ };
162
+ }
163
+ const decisionErrors = validateRequirementLedger(ledger);
164
+ if (decisionErrors.length > 0) {
165
+ return {
166
+ status: "human-decision-blocked",
167
+ ledgerPath: input.ledgerPath,
168
+ diagnostics: decisionErrors,
169
+ };
170
+ }
171
+ return { status: "complete", ledgerPath: input.ledgerPath, diagnostics: [] };
172
+ }
173
+ catch (error) {
174
+ return {
175
+ status: "invalid",
176
+ ledgerPath: input.ledgerPath,
177
+ diagnostics: [
178
+ {
179
+ code: "SOURCE_FIDELITY_LEDGER_STALE",
180
+ message: error instanceof Error ? error.message : String(error),
181
+ },
182
+ ],
183
+ };
184
+ }
185
+ }
50
186
  /**
51
187
  * Explicit Task↔DAG association only:
52
188
  * - lifecycle.json recorded runAssociations
@@ -182,10 +318,12 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
182
318
  const record = await loadLifecycleRecord(repoRoot, taskId);
183
319
  const contractState = await observeTaskContractState(repoRoot, taskId);
184
320
  let title;
321
+ let taskKind;
185
322
  let taskConfigAllowed = [];
186
323
  try {
187
324
  const config = await loadTaskConfig(repoRoot, taskId);
188
325
  title = config.title;
326
+ taskKind = config.taskKind;
189
327
  taskConfigAllowed = config.allowedPaths ?? [];
190
328
  }
191
329
  catch {
@@ -199,19 +337,22 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
199
337
  ...(requirementReady ? [] : [requirementPath]),
200
338
  ...(constraintsReady ? [] : [constraintsPath]),
201
339
  ];
202
- let importedPrdCount = 0;
340
+ const contractManaged = contractState.effectiveStatus === "managed" || Boolean(contractState.ref);
203
341
  const referencesDir = path.join(paths.sourceDir, "references");
204
- if (await exists(referencesDir)) {
205
- // Count is best-effort; absence is fine.
206
- try {
207
- const { readdir } = await import("node:fs/promises");
208
- const entries = await readdir(referencesDir);
209
- importedPrdCount = entries.filter((name) => name.endsWith(".md")).length;
210
- }
211
- catch {
212
- importedPrdCount = 0;
213
- }
214
- }
342
+ const referenceEvidence = await assessReferenceEvidence(referencesDir);
343
+ const importedPrdCount = referenceEvidence.importedPrdCount;
344
+ const ledgerPath = path.join(paths.sourceDir, REQUIREMENT_LEDGER_FILE_NAME);
345
+ const sourceFidelityStartup = await assessSourceFidelityStartup({
346
+ ledgerPath,
347
+ applicable: contractManaged &&
348
+ taskKind === "frontend-implementation" &&
349
+ referenceEvidence.status !== "absent-or-empty",
350
+ referenceEvidenceError: referenceEvidence.status === "unknown"
351
+ ? referenceEvidence.error
352
+ : undefined,
353
+ });
354
+ const sourceFidelityReady = sourceFidelityStartup.status === "complete" ||
355
+ sourceFidelityStartup.status === "not-applicable";
215
356
  const dagDraftPath = paths.dagDraftPath;
216
357
  const dagDraftExists = await exists(dagDraftPath);
217
358
  let specHash = null;
@@ -285,7 +426,11 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
285
426
  // Open gate only when draft is strict-validated and the current binding is
286
427
  // not already approved/rejected. Rejected same-digest gates stay closed
287
428
  // until contract/source/DAG/writeSet/controller binding changes.
288
- const gate = gateBound && strictValidated && !approvedCurrent && !rejectedCurrent
429
+ const gate = gateBound &&
430
+ strictValidated &&
431
+ sourceFidelityReady &&
432
+ !approvedCurrent &&
433
+ !rejectedCurrent
289
434
  ? buildWriteSetGate({ taskId, bound: gateBound, delta: gateDelta })
290
435
  : null;
291
436
  const gateOpen = Boolean(gate);
@@ -310,11 +455,10 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
310
455
  activeRun.failureCategory === "needs-attention" ||
311
456
  activeRun.failureCategory === "suspected-stall" ||
312
457
  activeRun.failureCategory === "monitor-timeout"));
313
- const contractManaged = contractState.effectiveStatus === "managed" || Boolean(contractState.ref);
314
458
  const lifecycleState = deriveLifecycleState({
315
459
  exists: true,
316
460
  contractManaged,
317
- sourceReady: requirementReady,
461
+ sourceReady: requirementReady && sourceFidelityReady,
318
462
  dagValidated: strictValidated,
319
463
  gateOpen: Boolean(gate),
320
464
  activeRun,
@@ -352,6 +496,7 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
352
496
  constraintsReady,
353
497
  importedPrdCount,
354
498
  missing,
499
+ sourceFidelityStartup,
355
500
  },
356
501
  dagDraft: {
357
502
  path: dagDraftPath,
@@ -375,7 +520,16 @@ export async function observeTaskLifecycle(repoRoot, taskId) {
375
520
  exists: closeoutExists || Boolean(record?.closeout),
376
521
  },
377
522
  completedTransitions: record?.completedTransitions ?? [],
378
- blockers: [],
523
+ blockers: sourceFidelityReady
524
+ ? []
525
+ : sourceFidelityStartup.diagnostics.map((diagnostic) => ({
526
+ code: diagnostic.code,
527
+ message: diagnostic.message,
528
+ details: {
529
+ startupStatus: sourceFidelityStartup.status,
530
+ ledgerPath: sourceFidelityStartup.ledgerPath,
531
+ },
532
+ })),
379
533
  warnings: [],
380
534
  next,
381
535
  };
@@ -46,6 +46,37 @@ export function planTransitions(input) {
46
46
  actions.push({ type: "recover-contract" });
47
47
  expectedTransitions.push("contract-recovered");
48
48
  }
49
+ const startupStatus = snapshot.source?.sourceFidelityStartup?.status;
50
+ const startupRepairDetected = startupStatus === "missing" ||
51
+ startupStatus === "invalid" ||
52
+ startupStatus === "coverage-incomplete";
53
+ if (startupRepairDetected && input.startupRepairAttempted) {
54
+ actions.push({ type: "stop", reason: "blocker" });
55
+ return {
56
+ actions,
57
+ gate: null,
58
+ blockers,
59
+ next: {
60
+ kind: "advance",
61
+ command: `loop-agent task advance ${snapshot.taskId} --json`,
62
+ },
63
+ expectedTransitions,
64
+ };
65
+ }
66
+ const startupRepairRequired = startupRepairDetected;
67
+ if (startupStatus === "human-decision-blocked") {
68
+ actions.push({ type: "stop", reason: "blocker" });
69
+ return {
70
+ actions,
71
+ gate: null,
72
+ blockers,
73
+ next: {
74
+ kind: "external-action",
75
+ description: "Requirement ledger requires an existing human source decision; preserve sourceResolutions, resolve the blocker, then advance again.",
76
+ },
77
+ expectedTransitions,
78
+ };
79
+ }
49
80
  const sourceReady = snapshot.source?.requirementReady === true;
50
81
  const contractManaged = snapshot.contract?.effectiveStatus === "managed" ||
51
82
  Boolean(snapshot.contract?.revision && snapshot.contract.revision > 0);
@@ -53,7 +84,9 @@ export function planTransitions(input) {
53
84
  // Prefer prepare once PRDs are on disk; re-import only when caller still
54
85
  // signals hasPrdInput (advance.ts latches this after one import per call).
55
86
  const shouldImportPrd = input.hasPrdInput;
56
- if (input.forcePrepareContract || input.refreshManagedContract) {
87
+ if (input.forcePrepareContract ||
88
+ input.refreshManagedContract ||
89
+ startupRepairRequired) {
57
90
  if (shouldImportPrd) {
58
91
  actions.push({ type: "import-prd" });
59
92
  expectedTransitions.push("prd-imported");
@@ -124,7 +157,8 @@ export function planTransitions(input) {
124
157
  const strictValidated = snapshot.dagDraft?.strictValidated === true;
125
158
  const dagRefreshRequired = Boolean(input.forceDagRefresh ||
126
159
  input.refreshManagedContract ||
127
- input.forcePrepareContract);
160
+ input.forcePrepareContract ||
161
+ startupRepairRequired);
128
162
  if (!dagExists || dagRefreshRequired) {
129
163
  actions.push({ type: "generate-dag" });
130
164
  expectedTransitions.push("dag-generated");
@@ -145,10 +179,11 @@ export function planTransitions(input) {
145
179
  snapshot.lifecycleState === "evidence-closed" ||
146
180
  snapshot.lifecycleState === "needs-attention" ||
147
181
  snapshot.lifecycleState === "awaiting-decision";
148
- const approvedForCurrent = postRunState || (input.approveGate && gate !== null);
182
+ const approveCurrentGate = input.approveGate && !dagRefreshRequired;
183
+ const approvedForCurrent = postRunState || (approveCurrentGate && gate !== null);
149
184
  if (!approvedForCurrent && (strictValidated || !dagExists || dagRefreshRequired)) {
150
185
  // Gate opens after generate+validate in the advance loop; plan includes open.
151
- if (!input.approveGate) {
186
+ if (!approveCurrentGate) {
152
187
  actions.push({ type: "open-write-set-gate" });
153
188
  expectedTransitions.push("write-set-gate-opened");
154
189
  if (input.dryRun) {
@@ -177,14 +212,14 @@ export function planTransitions(input) {
177
212
  (snapshot.lifecycleState === "run-failed" && !failedRecoverable) ||
178
213
  snapshot.lifecycleState === "evidence-closed";
179
214
  if (!alreadyTerminalRun) {
180
- if (input.approveGate && !postRunState) {
215
+ if (approveCurrentGate && !postRunState) {
181
216
  expectedTransitions.push("write-set-approved");
182
217
  actions.push({ type: "start-or-monitor-run" });
183
218
  expectedTransitions.push("dag-run-started", "dag-run-monitored");
184
219
  }
185
220
  else if (snapshot.lifecycleState === "running" ||
186
221
  snapshot.activeRun ||
187
- (input.approveGate && Boolean(snapshot.activeRun))) {
222
+ (approveCurrentGate && Boolean(snapshot.activeRun))) {
188
223
  actions.push({ type: "start-or-monitor-run" });
189
224
  expectedTransitions.push("dag-run-monitored");
190
225
  }
@@ -218,7 +253,7 @@ export function planTransitions(input) {
218
253
  actions,
219
254
  gate,
220
255
  blockers,
221
- next: deriveNext(snapshot, gate, input.approveGate),
256
+ next: deriveNext(snapshot, gate, approveCurrentGate),
222
257
  expectedTransitions,
223
258
  };
224
259
  }
@@ -42,6 +42,7 @@ export async function buildTaskLifecycleRecommendations(input) {
42
42
  const allowedPaths = [];
43
43
  const verifyCommands = [];
44
44
  // 1) managed task config
45
+ let hasDeclaredManagedVerify = false;
45
46
  try {
46
47
  const config = await loadTaskConfig(input.repoRoot, input.taskId);
47
48
  for (const glob of config.allowedPaths ?? []) {
@@ -53,7 +54,9 @@ export async function buildTaskLifecycleRecommendations(input) {
53
54
  source: "managed-task-config",
54
55
  });
55
56
  }
56
- for (const cmd of config.verifyCommands ?? []) {
57
+ const managedVerify = (config.verifyCommands ?? []).filter((cmd) => Boolean(cmd.command?.trim()));
58
+ hasDeclaredManagedVerify = managedVerify.length > 0;
59
+ for (const cmd of managedVerify) {
57
60
  const label = cmd.label?.trim() || "verify";
58
61
  const command = cmd.command?.trim();
59
62
  if (!command)
@@ -70,14 +73,22 @@ export async function buildTaskLifecycleRecommendations(input) {
70
73
  // task may not exist yet
71
74
  }
72
75
  // 2) package.json scripts (only if not already covered by managed config)
76
+ // The full-suite `test` script is recommended only when the task declares
77
+ // NO managed verifyCommands at all. Once a task configures a targeted
78
+ // verify set (e.g. per-change typecheck + a scoped vitest run), the full
79
+ // `npm run test` must never be auto-added: it validates the whole repo,
80
+ // costs far more than the change under review, and can fail on unrelated
81
+ // pre-existing breakage, turning a targeted verify green into a hard fail.
73
82
  const scripts = await readPackageScripts(input.repoRoot);
74
83
  const hasVerifyLabel = (label) => verifyCommands.some((entry) => entry.label === label);
75
84
  const scriptCandidates = [
76
85
  { label: "typecheck", script: "typecheck" },
77
- { label: "test", script: "test" },
78
86
  { label: "check-repo", script: "check:repo" },
79
87
  { label: "check-repo", script: "check-repo" },
80
88
  ];
89
+ if (!hasDeclaredManagedVerify) {
90
+ scriptCandidates.push({ label: "test", script: "test" });
91
+ }
81
92
  for (const candidate of scriptCandidates) {
82
93
  const scriptBody = scripts[candidate.script];
83
94
  if (!scriptBody)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.42.0-next.1",
4
- "gitSha": "fdc98f1393f69abd9a052de266f7199f580e4061",
5
- "builtAt": "2026-08-31T11:04:06.136Z"
3
+ "version": "0.42.0-next.10",
4
+ "gitSha": "610477f8b0bb4b8e4972bb540abdf5ad438c236a",
5
+ "builtAt": "2026-09-03T14:48:16.726Z"
6
6
  }