@tea-agent/loop-agent 0.41.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 (302) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +397 -5
  3. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  4. package/dist/adapters/context-transfer/pi-session.js +61 -0
  5. package/dist/application/dag/generate-task-dag.js +75 -19
  6. package/dist/application/dag/run-dag.js +41 -0
  7. package/dist/application/task-lifecycle/advance.js +24 -5
  8. package/dist/application/task-lifecycle/observe.js +171 -17
  9. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  10. package/dist/application/task-lifecycle/recommendations.js +13 -2
  11. package/dist/build-stamp.json +3 -3
  12. package/dist/cli/command-definitions.js +19 -0
  13. package/dist/cli/program.js +29 -2
  14. package/dist/commands/client-recovery.js +3 -0
  15. package/dist/commands/dag-artifact.js +284 -0
  16. package/dist/commands/dag-context.js +184 -0
  17. package/dist/commands/dag-follow-up.js +138 -0
  18. package/dist/commands/dag-rerun-task.js +2 -0
  19. package/dist/commands/dag-rerun.js +206 -1
  20. package/dist/commands/init.js +27 -1
  21. package/dist/commands/task-advance.js +19 -0
  22. package/dist/executors/dag-pi-executor.js +4013 -86
  23. package/dist/executors/pi-executor.js +15 -4
  24. package/dist/executors/pi-read-budget-policy.js +239 -0
  25. package/dist/executors/pi-sdk-executor.js +3 -0
  26. package/dist/executors/shell-executor.js +978 -188
  27. package/dist/executors/shell-write-guard.js +7 -0
  28. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  29. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  30. package/dist/infrastructure/console/context-export-store.js +160 -0
  31. package/dist/infrastructure/console/dir-lock.js +132 -0
  32. package/dist/infrastructure/console/operation-store.js +417 -0
  33. package/dist/infrastructure/harness/artifact-store.js +10 -1
  34. package/dist/infrastructure/harness/atomic-write.js +12 -2
  35. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  36. package/dist/shared/context-transfer.js +418 -0
  37. package/dist/shared/dag-failure-category.js +12 -0
  38. package/dist/shared/openspec-spec.js +70 -4
  39. package/dist/shared/operator/capabilities.js +21 -0
  40. package/dist/shared/operator/safe-run-summary.js +1 -0
  41. package/dist/shared/path-safety.js +93 -0
  42. package/dist/shared/preview.js +28 -4
  43. package/dist/task/config-types.js +107 -6
  44. package/dist/task/contract/adopt.js +4 -0
  45. package/dist/task/contract/import-revision.js +4 -0
  46. package/dist/task/contract/project.js +3 -0
  47. package/dist/task/contract/schema.js +2 -1
  48. package/dist/task/frontend-project-capability.js +203 -20
  49. package/dist/task/runtime.js +5 -2
  50. package/dist/task/source-prepare/build-draft.js +3 -3
  51. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  52. package/dist/task/source-prepare/prepare.js +126 -21
  53. package/dist/task/source-prepare/semantic-intake.js +6 -2
  54. package/dist/task/source-references.js +22 -1
  55. package/dist/task/task-demand-routing.js +3 -0
  56. package/dist/worker/console/chat/chat-event-store.js +2 -2
  57. package/dist/worker/console/chat/model-resolver.js +7 -4
  58. package/dist/worker/console/chat/pi-runtime.js +86 -5
  59. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  60. package/dist/worker/console/chat/routes.js +136 -53
  61. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  62. package/dist/worker/console/chat/session-catalog.js +2 -0
  63. package/dist/worker/console/chat/session-store.js +43 -1
  64. package/dist/worker/console/chat/shortcuts.js +18 -2
  65. package/dist/worker/console/chat/user-questions.js +1 -1
  66. package/dist/worker/console/console-update-runtime.js +1 -1
  67. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  68. package/dist/worker/console/dag-confirmation.js +1 -1
  69. package/dist/worker/console/dag-execution-receipt.js +1 -1
  70. package/dist/worker/console/doctor.js +1 -1
  71. package/dist/worker/console/draft-store.js +1 -1
  72. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  73. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  74. package/dist/worker/console/human-gate-token.js +1 -1
  75. package/dist/worker/console/index.js +6 -3
  76. package/dist/worker/console/interview/assessment.js +1 -1
  77. package/dist/worker/console/interview/session.js +1 -1
  78. package/dist/worker/console/operation-runner.js +45 -4
  79. package/dist/worker/console/operation-sse.js +1 -1
  80. package/dist/worker/console/operation-wait.js +1 -1
  81. package/dist/worker/console/operator-actions.js +273 -10
  82. package/dist/worker/console/operator-surface-health.js +1 -1
  83. package/dist/worker/console/operator-user-error.js +169 -0
  84. package/dist/worker/console/pi-plugins.js +57 -0
  85. package/dist/worker/console/pi-readiness.js +3 -2
  86. package/dist/worker/console/routes.js +637 -8
  87. package/dist/worker/console/security.js +35 -0
  88. package/dist/worker/console/server.js +29 -4
  89. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D7oU983t.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  90. package/dist/worker/console/static/assets/{arc-BrKSRdFS.js → arc-COptKq2S.js} +1 -1
  91. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CiXV7L56.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  92. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D_Hs_gIG.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  93. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DbM-Lqqh.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  94. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  95. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D1M8uqJw.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  96. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-4D3RFHT6.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CdwpY_n9.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BLeLZ9PG.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CeRNEMvc.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjkHP8Vy.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  101. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B54ZUQY-.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  102. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-4bKaMVmx.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Cyjfznu_.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CDN-zGFV.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  105. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  106. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  107. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-HxJmz411.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  108. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DtSt5K2w.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  109. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-VtkSpRmG.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  110. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bj4DT4du.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  111. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Bpuk6BmP.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  112. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BHYGv_yO.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  113. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DEx3WwnT.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  114. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cj0dS5e8.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  115. package/dist/worker/console/static/assets/{diagram-UB23O5K3-B97h8l21.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  116. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DMSFAhBd.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  117. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DhVOCbt6.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  118. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-GlakITWL.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  119. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-VxPpu9eQ.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  120. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  121. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  122. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  123. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXYuU4Uw.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  124. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C34erCbU.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  125. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DZCAiOU3.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  126. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C9gaTdUm.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  127. package/dist/worker/console/static/assets/{linear-CvI0Z17U.js → linear-D59YJ9kB.js} +1 -1
  128. package/dist/worker/console/static/assets/{mermaid.core-DMMSIx7j.js → mermaid.core-Bl13LpNa.js} +5 -5
  129. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BKKP0EO6.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  130. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-C3LiFbl5.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  131. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  132. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DNthMjzc.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  133. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-miFsr4Tv.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  134. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-yXUncmn6.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  135. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DLeWKLak.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  136. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CI3he1R3.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  137. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B7jfDkS5.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  138. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BmrZod7J.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  139. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  140. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CDOlHVSY.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  141. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  142. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cz-XX1gy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  143. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-caV4cI8C.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  144. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-dImPlnIO.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  145. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CtRYVJcn.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  146. package/dist/worker/console/static/index.html +7 -2
  147. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  148. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  149. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  150. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  151. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  152. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  153. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  154. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  155. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  156. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  157. package/dist/worker/console/static-src/shell/console-update-reload.js +52 -0
  158. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  159. package/dist/worker/console/workspace-context.js +142 -2
  160. package/dist/worker/console/workspace-registry.js +2 -2
  161. package/dist/worker/continuation/worker-continuation.js +278 -0
  162. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  163. package/dist/worker/observe/health.js +1 -1
  164. package/dist/worker/observe/node-transparency.js +572 -0
  165. package/dist/worker/observe/routes.js +55 -1
  166. package/dist/worker/observe/static/api.js +69 -5
  167. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  168. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  169. package/dist/worker/observe/static/dag-helpers.js +4 -2
  170. package/dist/worker/observe/static/dag-node-purpose.js +5 -0
  171. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  172. package/dist/worker/observe/static/kpi.js +2 -2
  173. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  174. package/dist/worker/observe/static/operator-chrome.js +37 -23
  175. package/dist/worker/observe/static/relations.js +2 -2
  176. package/dist/worker/observe/static/router.d.ts +12 -1
  177. package/dist/worker/observe/static/router.js +48 -6
  178. package/dist/worker/observe/static/run-processing.js +4 -2
  179. package/dist/worker/observe/static/shell-chrome.js +2 -2
  180. package/dist/worker/observe/static/state.js +8 -1
  181. package/dist/worker/observe/static/styles.css +1092 -86
  182. package/dist/worker/observe/static/views/dag-inspector.js +1228 -64
  183. package/dist/worker/observe/static/views/dag.js +10 -2
  184. package/dist/worker/observe/static/views/dags.js +3 -1
  185. package/dist/worker/observe/static/views/dashboard.js +14 -6
  186. package/dist/worker/observe/static/views/pool.js +7 -2
  187. package/dist/worker/observe/static/views/run.js +12 -3
  188. package/dist/worker/observe/static/views/session-timeline.js +142 -18
  189. package/dist/worker/observe/static/views/task.js +7 -2
  190. package/dist/worker/outcomes/adapters.js +19 -0
  191. package/dist/worker/outcomes/types.js +1 -0
  192. package/dist/worker/pool/attempt-lease.js +97 -66
  193. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  194. package/dist/worker/pool/reconcile.js +46 -1
  195. package/dist/worker/pool/run-store.js +2 -0
  196. package/dist/worker/pool/state-projection.js +2 -0
  197. package/dist/worker/task-spec/workflow-routing.js +8 -3
  198. package/dist/workflows/dag/artifact-bindings.js +149 -0
  199. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  200. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  201. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  202. package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
  203. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  204. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  205. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  206. package/dist/workflows/dag/budget-enforcement.js +10 -0
  207. package/dist/workflows/dag/context-receipt.js +305 -0
  208. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  209. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  210. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  211. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  212. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  213. package/dist/workflows/dag/frontend-closeout.js +221 -0
  214. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  215. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  216. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  217. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  218. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  219. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  220. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  221. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  222. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  223. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  224. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  225. package/dist/workflows/dag/frontend-repair.js +1 -432
  226. package/dist/workflows/dag/frontend-review-context.js +261 -15
  227. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  228. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  229. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  230. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  231. package/dist/workflows/dag/frontend-shape.js +435 -0
  232. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  233. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  234. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  235. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  236. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  237. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  238. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  239. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  240. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  241. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  242. package/dist/workflows/dag/init-hybrid.js +1174 -562
  243. package/dist/workflows/dag/interrupt-request.js +7 -0
  244. package/dist/workflows/dag/node-execution.js +940 -31
  245. package/dist/workflows/dag/path-safety.js +1 -0
  246. package/dist/workflows/dag/prompt.js +99 -7
  247. package/dist/workflows/dag/recovery-lease.js +170 -0
  248. package/dist/workflows/dag/report.js +37 -1
  249. package/dist/workflows/dag/rerun-feedback.js +315 -1
  250. package/dist/workflows/dag/rerun-plan.js +568 -3
  251. package/dist/workflows/dag/rerun-run.js +642 -18
  252. package/dist/workflows/dag/rerun-task.js +251 -13
  253. package/dist/workflows/dag/retry-policy.js +219 -104
  254. package/dist/workflows/dag/runner.js +596 -121
  255. package/dist/workflows/dag/scheduler.js +133 -20
  256. package/dist/workflows/dag/skill-snapshot.js +17 -0
  257. package/dist/workflows/dag/types.js +426 -22
  258. package/dist/workflows/dag/validate.js +28 -12
  259. package/docs/architecture/runtime-boundaries.md +6 -6
  260. package/docs/architecture/worker-and-feature.md +1 -1
  261. package/docs/examples/README.md +5 -0
  262. package/docs/init-surface.manifest.json +30 -12
  263. package/docs/skills/vetted-skill-registry.md +4 -2
  264. package/docs/templates/README.md +2 -0
  265. package/docs/templates/agent-dag-report.schema.json +8 -2
  266. package/docs/templates/agent-dag.schema.json +33 -1
  267. package/docs/templates/backend-test-dag.json +23 -20
  268. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  269. package/docs/templates/frontend-implementation-dag.json +89 -0
  270. package/docs/templates/product-line/task.yaml +1 -1
  271. package/docs/templates/spec-registry.schema.json +45 -0
  272. package/harness.json +1 -1
  273. package/package.json +6 -3
  274. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  275. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  276. package/skills/frontend-contract/SKILL.md +23 -0
  277. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  278. package/skills/frontend-design-review/SKILL.md +22 -41
  279. package/skills/frontend-plan/SKILL.md +26 -0
  280. package/skills/frontend-plan/references/decision-contract.md +37 -0
  281. package/skills/frontend-plan/references/design-decisions.md +17 -0
  282. package/skills/frontend-review/SKILL.md +20 -15
  283. package/skills/frontend-review/references/review-findings.md +6 -7
  284. package/skills/frontend-scout/SKILL.md +25 -0
  285. package/skills/frontend-scout/references/design-evidence.md +16 -0
  286. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  287. package/skills/frontend-verification/SKILL.md +1 -1
  288. package/skills/loop-agent/references/command-reference.md +7 -0
  289. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  290. package/dist/worker/console/operation-store.js +0 -169
  291. package/dist/worker/console/static/assets/channel-D1xajGpe.js +0 -1
  292. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +0 -1
  293. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +0 -1
  294. package/dist/worker/console/static/assets/index-BLzcAykx.js +0 -451
  295. package/dist/worker/console/static/assets/index-Cs9JUSPs.css +0 -1
  296. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +0 -1
  297. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +0 -8
  298. package/skills/frontend-implementation/SKILL.md +0 -52
  299. package/skills/frontend-implementation/references/code-standards.md +0 -33
  300. package/skills/frontend-implementation/references/design-spec.md +0 -56
  301. package/skills/frontend-implementation/references/node-contracts.md +0 -31
  302. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -12,6 +12,19 @@ export const OPENSPEC_SPEC_DIRS = [
12
12
  "openspec/project-specs",
13
13
  "ai_workspace",
14
14
  ];
15
+ /** Default roots used when a frontend task does not declare specRoots. */
16
+ export const DEFAULT_FRONTEND_SPEC_ROOTS = OPENSPEC_SPEC_DIRS;
17
+ /** A task-owned spec root must be a normalized, repo-relative POSIX directory. */
18
+ export function isValidFrontendSpecRoot(value) {
19
+ return (Boolean(value) &&
20
+ !value.includes("\\") &&
21
+ !value.startsWith("/") &&
22
+ !value.includes("//") &&
23
+ !value.split("/").some((segment) => !segment ||
24
+ segment === "." ||
25
+ segment === ".." ||
26
+ /\s/.test(segment)));
27
+ }
15
28
  /** Allowed spec file extensions (case-insensitive). */
16
29
  export const OPENSPEC_SPEC_EXT_RE = /\.(md|mdx|json|yaml|yml)$/i;
17
30
  /**
@@ -43,14 +56,17 @@ export function isGovernanceSubtreePath(filePath, governanceRoot) {
43
56
  * directories. `filePath` must be a repo-relative POSIX path (backslashes are
44
57
  * normalised internally). Directory names are case-sensitive.
45
58
  */
46
- export function isOpenspecSpecPath(filePath) {
59
+ export function isFrontendSpecPath(filePath, specRoots = DEFAULT_FRONTEND_SPEC_ROOTS) {
47
60
  const normalized = filePath.replaceAll("\\", "/");
48
- for (const dir of OPENSPEC_SPEC_DIRS) {
61
+ for (const dir of specRoots) {
49
62
  if (normalized === dir || normalized.startsWith(dir + "/"))
50
63
  return true;
51
64
  }
52
65
  return false;
53
66
  }
67
+ export function isOpenspecSpecPath(filePath) {
68
+ return isFrontendSpecPath(filePath);
69
+ }
54
70
  /**
55
71
  * Returns `true` when `filename` (the basename only, not a full path) has an
56
72
  * allowed frontend specification extension.
@@ -59,10 +75,13 @@ export function isValidOpenspecExtension(filename) {
59
75
  return OPENSPEC_SPEC_EXT_RE.test(filename);
60
76
  }
61
77
  /** Returns `true` for a supported file inside a canonical openspec spec dir. */
62
- export function isOpenspecSpecFilePath(filePath) {
63
- return (isOpenspecSpecPath(filePath) &&
78
+ export function isFrontendSpecFilePath(filePath, specRoots = DEFAULT_FRONTEND_SPEC_ROOTS) {
79
+ return (isFrontendSpecPath(filePath, specRoots) &&
64
80
  isValidOpenspecExtension(filePath.replaceAll("\\", "/")));
65
81
  }
82
+ export function isOpenspecSpecFilePath(filePath) {
83
+ return isFrontendSpecFilePath(filePath);
84
+ }
66
85
  /**
67
86
  * Returns `true` when free-form search input references a canonical frontend
68
87
  * specification directory with exact lowercase directory names.
@@ -123,3 +142,50 @@ export function extractTaskSourceOpenspecPaths(markdown, governanceRoot = DEFAUL
123
142
  !isGovernanceSubtreePath(token, governanceRoot))
124
143
  .sort();
125
144
  }
145
+ /**
146
+ * Extract explicit specification-file citations for a task's frozen spec roots.
147
+ * This is intentionally path-only: source preparation decides which roots are
148
+ * normative, while DAG execution only consumes that frozen configuration.
149
+ */
150
+ export function extractTaskSourceFrontendSpecPaths(markdown, specRoots, governanceRoot = DEFAULT_OPENSPEC_GOVERNANCE_ROOT) {
151
+ if (!markdown)
152
+ return [];
153
+ const raw = new Set();
154
+ const collect = (token) => {
155
+ const normalized = normalizeCitationToken(token);
156
+ if (normalized)
157
+ raw.add(normalized);
158
+ };
159
+ for (const match of markdown.matchAll(/`([^`\n]+)`/g))
160
+ collect(match[1] ?? "");
161
+ for (const match of markdown.matchAll(/\[[^\]]*\]\(([^)\s]+)\)/g))
162
+ collect(match[1] ?? "");
163
+ for (const root of specRoots) {
164
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
165
+ for (const match of markdown.matchAll(new RegExp(`(?:^|[\\s\"'([>])(${escaped}/[A-Za-z0-9._/-]+)`, "g"))) {
166
+ collect(match[1] ?? "");
167
+ }
168
+ }
169
+ return [...raw]
170
+ .filter((token) => isFrontendSpecFilePath(token, specRoots) && !isGovernanceSubtreePath(token, governanceRoot))
171
+ .sort();
172
+ }
173
+ /**
174
+ * Source-preparation helper for explicitly named normative directories. A root
175
+ * is inferred only on a line that declares a spec/规范/design-system intent;
176
+ * arbitrary Markdown links never silently broaden task configuration.
177
+ */
178
+ export function extractDeclaredFrontendSpecRoots(markdown) {
179
+ const roots = new Set();
180
+ for (const line of markdown.split(/\r?\n/)) {
181
+ if (!/(?:规范|规约|标准|设计系统|\bspec(?:ification)?\b|\bdesign[ -]?system\b|\bguidelines?\b|\brules?\b)/i.test(line))
182
+ continue;
183
+ for (const match of line.matchAll(/`([^`]+)`|\[[^\]]+\]\(([^)\s]+)\)/g)) {
184
+ const raw = (match[1] ?? match[2] ?? "").replaceAll("\\", "/").replace(/\/+$/, "");
185
+ if (!raw || !isValidFrontendSpecRoot(raw))
186
+ continue;
187
+ roots.add(isValidOpenspecExtension(raw) ? raw.split("/").slice(0, -1).join("/") : raw);
188
+ }
189
+ }
190
+ return [...roots].filter(isValidFrontendSpecRoot).sort();
191
+ }
@@ -2240,6 +2240,13 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2240
2240
  source: "loop-agent",
2241
2241
  note: "server-side read-only run-facts check (ineligible plan / explicit rerun-task recommendation) replaces the browser Human Gate (2026-08-11).",
2242
2242
  },
2243
+ {
2244
+ command: "loop-agent dag follow-up",
2245
+ coverage: "excluded",
2246
+ action: null,
2247
+ source: "loop-agent",
2248
+ exclusionReason: "Frontend review recovery helper is a direct advanced CLI composition (create/import task and optional advance), not an Operator action endpoint.",
2249
+ },
2243
2250
  {
2244
2251
  command: "loop-agent dag reconcile-tasks",
2245
2252
  coverage: "human-gated-required",
@@ -2252,6 +2259,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2252
2259
  action: "dagFinalVerification",
2253
2260
  source: "loop-agent",
2254
2261
  },
2262
+ {
2263
+ command: "loop-agent dag context",
2264
+ coverage: "excluded",
2265
+ action: null,
2266
+ source: "loop-agent",
2267
+ exclusionReason: "Context export can disclose raw execution bytes and requires a human-selected output path; Console uses dedicated gated actions instead of the generic CLI wrapper.",
2268
+ },
2269
+ {
2270
+ command: "loop-agent dag artifact",
2271
+ coverage: "excluded",
2272
+ action: null,
2273
+ source: "loop-agent",
2274
+ exclusionReason: "Artifact revision staging consumes a human-selected local file and remains on dedicated Console/CLI mutation gates.",
2275
+ },
2255
2276
  {
2256
2277
  command: "loop-agent workflow list",
2257
2278
  coverage: "advanced",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat, open, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ function assertContained(root, target, label) {
5
+ if (target === root || !target.startsWith(`${root}${path.sep}`)) {
6
+ throw new Error(`${label}: path escapes boundary: ${target}`);
7
+ }
8
+ }
9
+ /**
10
+ * Reject any symlink on the path between `boundaryRoot` and `targetPath`.
11
+ * This is the lexical/path-walk phase; callers that consume file bytes must
12
+ * still use readFileBoundarySafe so the checked object is the opened object.
13
+ */
14
+ export async function assertRealPathInsideBoundary(input) {
15
+ const root = path.resolve(input.boundaryRoot);
16
+ const target = path.resolve(input.targetPath);
17
+ assertContained(root, target, input.label);
18
+ const rootStat = await lstat(root).catch(() => undefined);
19
+ if (!rootStat) {
20
+ throw new Error(`${input.label}: boundary root is not a real directory`);
21
+ }
22
+ if (rootStat.isSymbolicLink()) {
23
+ throw new Error(`${input.label}: boundary root must not be a symlink`);
24
+ }
25
+ if (!rootStat.isDirectory()) {
26
+ throw new Error(`${input.label}: boundary root is not a real directory`);
27
+ }
28
+ const segments = path.relative(root, target).split(path.sep).filter(Boolean);
29
+ let current = root;
30
+ for (let index = 0; index < segments.length; index += 1) {
31
+ current = path.join(current, segments[index]);
32
+ const stat = await lstat(current).catch(() => undefined);
33
+ if (!stat) {
34
+ throw new Error(`${input.label}: path component missing: ${segments[index]}`);
35
+ }
36
+ if (stat.isSymbolicLink()) {
37
+ throw new Error(`${input.label}: symlink is forbidden on path: ${segments[index]}`);
38
+ }
39
+ if (index < segments.length - 1 && !stat.isDirectory()) {
40
+ throw new Error(`${input.label}: path ancestor is not a directory: ${segments[index]}`);
41
+ }
42
+ }
43
+ return target;
44
+ }
45
+ /** Reject a non-regular, symlinked, or hardlinked final entry before opening. */
46
+ export async function assertReadableRegularFile(input) {
47
+ const target = await assertRealPathInsideBoundary(input);
48
+ const stat = await lstat(target).catch(() => undefined);
49
+ if (!stat)
50
+ throw new Error(`${input.label}: path component missing`);
51
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
52
+ throw new Error(`${input.label}: path must be a regular single-link file`);
53
+ }
54
+ return target;
55
+ }
56
+ /**
57
+ * Read one owned regular file without reopening an unchecked pathname.
58
+ *
59
+ * The preflight rejects normal FIFO/device/hardlink cases without opening
60
+ * them. O_NOFOLLOW rejects a final symlink introduced after preflight and
61
+ * O_NONBLOCK prevents a swapped FIFO from blocking. The post-open identity and
62
+ * realpath checks make an ancestor swap detectable before bytes are consumed.
63
+ */
64
+ export async function readFileBoundarySafe(input) {
65
+ const target = await assertReadableRegularFile(input);
66
+ const flags = fsConstants.O_RDONLY |
67
+ (fsConstants.O_NOFOLLOW ?? 0) |
68
+ (fsConstants.O_NONBLOCK ?? 0);
69
+ const handle = await open(target, flags);
70
+ try {
71
+ const opened = await handle.stat();
72
+ if (!opened.isFile() || opened.nlink !== 1) {
73
+ throw new Error(`${input.label}: path must be a regular single-link file`);
74
+ }
75
+ const [resolvedRoot, resolvedTarget, current] = await Promise.all([
76
+ realpath(path.resolve(input.boundaryRoot)),
77
+ realpath(target),
78
+ lstat(target),
79
+ ]);
80
+ assertContained(resolvedRoot, resolvedTarget, input.label);
81
+ if (current.isSymbolicLink() ||
82
+ !current.isFile() ||
83
+ current.nlink !== 1 ||
84
+ current.dev !== opened.dev ||
85
+ current.ino !== opened.ino) {
86
+ throw new Error(`${input.label}: path changed while opening`);
87
+ }
88
+ return await handle.readFile();
89
+ }
90
+ finally {
91
+ await handle.close().catch(() => { });
92
+ }
93
+ }
@@ -1,10 +1,34 @@
1
1
  const ENCODER = new TextEncoder();
2
2
  const DECODER = new TextDecoder();
3
+ export function redactSecretsWithFindings(text) {
4
+ let findingCount = 0;
5
+ const replaceChanged = (match, replacement) => {
6
+ if (match !== replacement)
7
+ findingCount += 1;
8
+ return replacement;
9
+ };
10
+ let redacted = text.replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)((?:Bearer\s+)?[^"'\s,}]+)/gi, (match, prefix) => replaceChanged(match, `${prefix}***`));
11
+ redacted = redacted.replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, (match, prefix) => replaceChanged(match, `${prefix}***`));
12
+ redacted = redacted.replace(/\bBearer\s+[^\s"']+/gi, (match) => replaceChanged(match, "Bearer ***"));
13
+ // High-confidence provider-style tokens (OpenAI sk-, GitHub ghp_/gho_,
14
+ // Slack xox*, AWS AKIA) are credential-like even without a key=value
15
+ // prefix (exec-plan 2026-08-25 D4: credential-like 默认阻断).
16
+ redacted = redacted.replace(/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pos]_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16})\b/g, (match) => replaceChanged(match, "***"));
17
+ return { redacted, findingCount };
18
+ }
19
+ export function redactSensitivePathsWithFindings(text) {
20
+ let findingCount = 0;
21
+ const replaceHome = (match) => {
22
+ if (match !== "<redacted-home>")
23
+ findingCount += 1;
24
+ return "<redacted-home>";
25
+ };
26
+ let redacted = text.replace(/\/(?:Users|home)\/[^/\s"'\\]+/g, replaceHome);
27
+ redacted = redacted.replace(/\b[A-Za-z]:\\Users\\[^\\/\s"']+/gi, replaceHome);
28
+ return { redacted, findingCount };
29
+ }
3
30
  export function redactSecrets(text) {
4
- return text
5
- .replace(/\bBearer\s+[^\s"']+/gi, "Bearer ***")
6
- .replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, "$1***")
7
- .replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)([^"'\s,}]+)/gi, "$1***");
31
+ return redactSecretsWithFindings(text).redacted;
8
32
  }
9
33
  export function truncateUtf8Preview(text, maxBytes = 4096) {
10
34
  if (maxBytes <= 0)
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { isOpenspecSpecFilePath } from "../shared/openspec-spec.js";
2
+ import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, } from "../shared/openspec-spec.js";
3
3
  export const taskFlowSchema = z.enum([
4
4
  "auto",
5
5
  "micro",
@@ -14,6 +14,7 @@ export const taskKindSchema = z.enum([
14
14
  "frontend-implementation",
15
15
  "frontend-test",
16
16
  "backend-test",
17
+ "backend-test-analysis",
17
18
  "knowledge-sync",
18
19
  "knowledge-graph-bootstrap",
19
20
  ]);
@@ -76,13 +77,27 @@ export const frontendOpenspecPolicySchema = z.enum(["cited", "scan-strict"]);
76
77
  */
77
78
  export const frontendOpenspecConfigSchema = z.object({
78
79
  policy: frontendOpenspecPolicySchema.optional().default("cited"),
80
+ /**
81
+ * Repo-relative directories containing frontend normative files. These are
82
+ * frozen in task.json during contract preparation, before DAG generation.
83
+ */
84
+ specRoots: z
85
+ .array(z.string().refine((candidate) => isValidFrontendSpecRoot(candidate), "frontendOpenspec.specRoots entries must be normalized repo-relative directories"))
86
+ .optional(),
79
87
  requiredReadPaths: z
80
- .array(z
81
- .string()
82
- .refine((candidate) => !candidate.includes("\\") &&
83
- isOpenspecSpecFilePath(candidate), "frontendOpenspec.requiredReadPaths entries must be repo-relative supported files under openspec/schemas/, openspec/project-specs/, or ai_workspace/"))
88
+ .array(z.string())
84
89
  .optional()
85
90
  .default([]),
91
+ }).superRefine((value, ctx) => {
92
+ const roots = value.specRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
93
+ if (new Set(roots).size !== roots.length) {
94
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "frontendOpenspec.specRoots must not contain duplicates", path: ["specRoots"] });
95
+ }
96
+ for (const required of value.requiredReadPaths) {
97
+ if (!isFrontendSpecFilePath(required, roots)) {
98
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "frontendOpenspec.requiredReadPaths entries must be repo-relative supported files under frontendOpenspec.specRoots", path: ["requiredReadPaths"] });
99
+ }
100
+ }
86
101
  });
87
102
  /** Batch limits and gate profile for the browser-driven frontend test DAG.
88
103
  * Token fields are post-case stop thresholds, not model-provider hard caps. */
@@ -130,6 +145,75 @@ export const frontendTestConfigSchema = z.object({
130
145
  .strict()
131
146
  .optional(),
132
147
  });
148
+ const backendTestBudgetProofSchema = z
149
+ .object({
150
+ groupId: z.string().min(1),
151
+ estimatedCaseCount: z.number().int().positive(),
152
+ estimatedTestPointCount: z.number().int().positive(),
153
+ estimatedOutputChars: z.number().int().positive(),
154
+ maxOutputCharsPerWriter: z.number().int().positive(),
155
+ estimatorVersion: z.literal("backend-test-output-estimator-v1"),
156
+ inputSha256: z.string().regex(/^[a-f0-9]{64}$/),
157
+ })
158
+ .strict();
159
+ const backendTestModuleLayoutEntrySchema = z
160
+ .object({
161
+ stem: z.string().regex(/^[a-z][a-z0-9_]*$/),
162
+ businessResource: z.string().min(1),
163
+ markdownPath: z.string().min(1),
164
+ pytestPath: z.string().min(1),
165
+ ownedOperations: z.array(z.string().regex(/^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS) \/\S+$/)).min(1),
166
+ splitReason: z.enum([
167
+ "explicit-user-layout",
168
+ "primary-business-resource",
169
+ "independent-business-resource",
170
+ "output-budget",
171
+ ]),
172
+ budgetProof: backendTestBudgetProofSchema.optional(),
173
+ })
174
+ .strict()
175
+ .superRefine((entry, ctx) => {
176
+ const safe = (value) => !value.startsWith("/") &&
177
+ !value.startsWith("\\") &&
178
+ !value.includes("..") &&
179
+ !value.includes("\\");
180
+ if (!safe(entry.markdownPath) || !entry.markdownPath.endsWith(".md")) {
181
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["markdownPath"], message: "must be a safe repository-relative .md path" });
182
+ }
183
+ if (!safe(entry.pytestPath) || !entry.pytestPath.endsWith(".py")) {
184
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["pytestPath"], message: "must be a safe repository-relative .py path" });
185
+ }
186
+ if (entry.splitReason === "output-budget" && !entry.budgetProof) {
187
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["budgetProof"], message: "is required for output-budget split" });
188
+ }
189
+ if (entry.splitReason !== "output-budget" && entry.budgetProof) {
190
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["budgetProof"], message: "is only valid for output-budget split" });
191
+ }
192
+ });
193
+ const backendTestModuleLayoutSchema = z
194
+ .object({
195
+ schemaId: z.literal("backend-test-module-layout-v1"),
196
+ mode: z.enum(["explicit-file-layout", "business-resource-layout"]),
197
+ modules: z.array(backendTestModuleLayoutEntrySchema).min(1).max(8),
198
+ })
199
+ .strict()
200
+ .superRefine((layout, ctx) => {
201
+ for (const key of ["stem", "markdownPath", "pytestPath"]) {
202
+ const seen = new Set();
203
+ for (let index = 0; index < layout.modules.length; index += 1) {
204
+ const value = layout.modules[index][key];
205
+ if (seen.has(value))
206
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["modules", index, key], message: `duplicate ${key}` });
207
+ seen.add(value);
208
+ }
209
+ }
210
+ if (layout.mode === "explicit-file-layout") {
211
+ layout.modules.forEach((entry, index) => {
212
+ if (entry.splitReason !== "explicit-user-layout")
213
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["modules", index, "splitReason"], message: "explicit-file-layout requires explicit-user-layout" });
214
+ });
215
+ }
216
+ });
133
217
  /** Backend-test artifact layout and pipeline mode configuration (plan A/B/D). */
134
218
  export const backendTestConfigSchema = z
135
219
  .object({
@@ -151,6 +235,8 @@ export const backendTestConfigSchema = z
151
235
  workspaceControl: z.enum(["git", "filesystem-only"]).optional(),
152
236
  /** Per-command timeout for the readiness-authorized pytest/HTML execution node. */
153
237
  executeTimeoutMs: z.number().int().min(60_000).max(7_200_000).optional(),
238
+ /** Optional strict physical/business module layout; authoritative over model-derived layout. */
239
+ moduleLayout: backendTestModuleLayoutSchema.optional(),
154
240
  })
155
241
  .strict()
156
242
  .optional();
@@ -165,7 +251,7 @@ const taskConfigObjectSchema = z.object({
165
251
  taskId: z.string(),
166
252
  title: z.string(),
167
253
  sourceFiles: z.array(z.string()),
168
- /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
254
+ /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;frontend-test: 前端浏览器测试 DAG;backend-test-analysis: 只读后端测试分析 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
169
255
  taskKind: taskKindSchema.optional().default("standard"),
170
256
  /** true = taskKind 由人类显式选择(含显式 standard);DAG 路由必须尊重,不做语义改写。 */
171
257
  taskKindExplicit: z.boolean().optional(),
@@ -209,6 +295,21 @@ const taskConfigObjectSchema = z.object({
209
295
  maxFixLoops: z.number().int().min(0).optional().default(2),
210
296
  /** Supervised DAG convergence is opt-in until runtime smoke evidence is stronger. */
211
297
  convergence: convergenceConfigSchema.optional().default({ enabled: false }),
298
+ /** Frontend recovery continuation quota. Bounds how many times a failed
299
+ * frontend run may auto-recover via M6 recovery operations (1 = current
300
+ * default; raise to 2-3 for more tolerance of transient verify failures). */
301
+ frontendRecovery: z
302
+ .object({
303
+ maxContinuations: z
304
+ .number()
305
+ .int()
306
+ .min(1)
307
+ .max(5)
308
+ .optional()
309
+ .default(1),
310
+ })
311
+ .strict()
312
+ .optional(),
212
313
  /** Outer loop auto mode never writes by default; DAG execute requires this policy plus path/writeSet gates. */
213
314
  loopAutoExecutionPolicy: loopAutoExecutionPolicySchema
214
315
  .optional()
@@ -90,6 +90,10 @@ export async function adoptTaskContract(input) {
90
90
  requirementBytes: bytes.requirementBytes,
91
91
  constraintsBytes: bytes.constraintsBytes,
92
92
  referenceManifestBytes: bytes.referenceManifestBytes,
93
+ ...(state.ref?.sourceHashes.requirementLedgerSha256 &&
94
+ bytes.requirementLedgerBytes !== null
95
+ ? { requirementLedgerBytes: bytes.requirementLedgerBytes }
96
+ : {}),
93
97
  });
94
98
  const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
95
99
  const canonicalHash = computeCanonicalHash({
@@ -45,6 +45,10 @@ export async function refreshManagedContractAfterImport(input) {
45
45
  requirementBytes: bytes.requirementBytes,
46
46
  constraintsBytes: bytes.constraintsBytes,
47
47
  referenceManifestBytes: bytes.referenceManifestBytes,
48
+ ...(state2.ref.sourceHashes.requirementLedgerSha256 &&
49
+ bytes.requirementLedgerBytes !== null
50
+ ? { requirementLedgerBytes: bytes.requirementLedgerBytes }
51
+ : {}),
48
52
  });
49
53
  const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
50
54
  const canonicalHash = computeCanonicalHash({
@@ -184,6 +184,9 @@ export function mergeManagedTaskConfigFields(existing, draft) {
184
184
  command: cmd.command,
185
185
  ...(cmd.timeoutMs !== undefined ? { timeoutMs: cmd.timeoutMs } : {}),
186
186
  })),
187
+ ...(draft.frontendOpenspec !== undefined
188
+ ? { frontendOpenspec: draft.frontendOpenspec }
189
+ : {}),
187
190
  };
188
191
  if (draft.featureId !== undefined) {
189
192
  next.featureId = draft.featureId;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
2
+ import { backendTestConfigSchema, frontendOpenspecConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
3
3
  import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
4
4
  const sha256Schema = z
5
5
  .string()
@@ -74,6 +74,7 @@ export const taskContractDraftSchema = z
74
74
  openQuestions: z.array(z.string()).optional(),
75
75
  assumptions: z.array(z.string()).optional(),
76
76
  hardConstraints: z.array(z.string()).optional(),
77
+ frontendOpenspec: frontendOpenspecConfigSchema.optional(),
77
78
  interviewAssessmentRef: z
78
79
  .object({
79
80
  draftSha256: z.string().min(1),