@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.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 (331) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +248 -95
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/application/task-lifecycle/recommendations.js +13 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +23 -5
  9. package/dist/cli/program.js +21 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli/update/runtime-activity.js +1 -29
  12. package/dist/cli.js +2 -1
  13. package/dist/commands/client-recovery.js +3 -0
  14. package/dist/commands/dag-follow-up.js +138 -0
  15. package/dist/commands/dag-rerun.js +55 -1
  16. package/dist/commands/init-model-catalog.js +464 -0
  17. package/dist/commands/init-upgrade.js +265 -97
  18. package/dist/commands/init.js +455 -28
  19. package/dist/commands/inspect-next.js +8 -0
  20. package/dist/commands/task-advance.js +19 -0
  21. package/dist/executors/dag-pi-executor.js +2306 -61
  22. package/dist/executors/pi-executor.js +7 -0
  23. package/dist/executors/pi-extension-resolver.js +233 -0
  24. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  25. package/dist/executors/pi-read-budget-policy.js +239 -0
  26. package/dist/executors/pi-sdk-executor.js +180 -33
  27. package/dist/executors/shell-executor.js +1249 -224
  28. package/dist/executors/shell-write-guard.js +14 -0
  29. package/dist/executors/workspace-write-snapshot.js +68 -0
  30. package/dist/infrastructure/harness/atomic-write.js +23 -0
  31. package/dist/shared/dag-failure-category.js +150 -0
  32. package/dist/shared/dag-prompt-override.js +27 -0
  33. package/dist/shared/openspec-spec.js +70 -4
  34. package/dist/shared/operator/capabilities.js +39 -0
  35. package/dist/shared/playwright-cli-command-policy.js +15 -0
  36. package/dist/shared/update/console-notifier.js +100 -0
  37. package/dist/{cli → shared}/update/npm-client.js +70 -15
  38. package/dist/{cli → shared}/update/state.js +41 -13
  39. package/dist/task/config-types.js +50 -5
  40. package/dist/task/contract/apply.js +11 -1
  41. package/dist/task/contract/constants.js +2 -0
  42. package/dist/task/contract/hash.js +3 -0
  43. package/dist/task/contract/observe.js +25 -4
  44. package/dist/task/contract/paths.js +2 -1
  45. package/dist/task/contract/project.js +18 -1
  46. package/dist/task/contract/schema.js +4 -1
  47. package/dist/task/contract/transaction.js +12 -0
  48. package/dist/task/frontend-project-capability.js +199 -18
  49. package/dist/task/source-prepare/completeness.js +188 -0
  50. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  51. package/dist/task/source-prepare/index.js +3 -0
  52. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  53. package/dist/task/source-prepare/ledger-review.js +214 -0
  54. package/dist/task/source-prepare/ledger.js +545 -0
  55. package/dist/task/source-prepare/parse-intent.js +24 -4
  56. package/dist/task/source-prepare/prepare.js +298 -1
  57. package/dist/task/source-prepare/semantic-intake.js +25 -5
  58. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  59. package/dist/task/source-prepare/types.js +22 -0
  60. package/dist/task/source-references.js +22 -1
  61. package/dist/worker/cli.js +18 -2
  62. package/dist/worker/console/chat/chat-event-store.js +62 -4
  63. package/dist/worker/console/chat/context-panel.js +7 -0
  64. package/dist/worker/console/chat/deferred-turn.js +12 -0
  65. package/dist/worker/console/chat/operation-card.js +8 -1
  66. package/dist/worker/console/chat/pi-console-config.js +81 -10
  67. package/dist/worker/console/chat/pi-runtime.js +264 -16
  68. package/dist/worker/console/chat/provider-error.js +98 -0
  69. package/dist/worker/console/chat/routes.js +298 -32
  70. package/dist/worker/console/chat/session-stats.js +179 -0
  71. package/dist/worker/console/chat/session-store.js +93 -63
  72. package/dist/worker/console/chat/todos.js +115 -0
  73. package/dist/worker/console/chat/tool-preview.js +42 -0
  74. package/dist/worker/console/chat/turn-process.js +20 -55
  75. package/dist/worker/console/chat/user-questions.js +266 -0
  76. package/dist/worker/console/chat/workspace-landing.js +14 -0
  77. package/dist/worker/console/console-handoff.js +82 -0
  78. package/dist/worker/console/console-update-and-init.js +115 -0
  79. package/dist/worker/console/console-update-runtime.js +84 -0
  80. package/dist/worker/console/dag-execution-receipt.js +33 -0
  81. package/dist/worker/console/draft-store.js +49 -0
  82. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  83. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  84. package/dist/worker/console/index.js +3 -0
  85. package/dist/worker/console/inspect-split.js +18 -0
  86. package/dist/worker/console/native-directory-picker.js +13 -0
  87. package/dist/worker/console/operation-run-facts.js +19 -3
  88. package/dist/worker/console/operation-store.js +227 -7
  89. package/dist/worker/console/operator-actions.js +388 -0
  90. package/dist/worker/console/operator-surface-health.js +1 -0
  91. package/dist/worker/console/operator-user-error.js +2 -2
  92. package/dist/worker/console/prd-intake-bridge.js +13 -0
  93. package/dist/worker/console/routes.js +355 -3
  94. package/dist/worker/console/security.js +67 -13
  95. package/dist/worker/console/server.js +609 -164
  96. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
  97. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
  98. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
  99. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
  100. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
  101. package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
  102. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
  112. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
  113. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
  114. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
  115. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
  117. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
  118. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
  123. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
  124. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
  125. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
  126. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
  127. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
  128. package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
  129. package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
  130. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
  131. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
  132. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
  133. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
  134. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
  135. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
  136. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
  137. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
  138. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
  139. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
  140. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
  141. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
  142. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
  143. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
  144. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
  145. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
  147. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
  148. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
  149. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
  150. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
  151. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
  152. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.js} +1 -1
  153. package/dist/worker/console/static/index.html +2 -2
  154. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  155. package/dist/worker/console/static-src/app/console-types.js +68 -4
  156. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  157. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  158. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  159. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  160. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  161. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  162. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  163. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
  164. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  165. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  166. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  167. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  168. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  169. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  170. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
  171. package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
  172. package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
  173. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  174. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  175. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  176. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  177. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  178. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  179. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  180. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  181. package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
  182. package/dist/worker/console/workspace-context.js +344 -0
  183. package/dist/worker/console/workspace-registry.js +214 -0
  184. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  185. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  186. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  187. package/dist/worker/observability/init-runtime-activity.js +26 -0
  188. package/dist/worker/observability/read-model.js +22 -0
  189. package/dist/worker/observe/node-input.js +160 -3
  190. package/dist/worker/observe/node-process.js +377 -0
  191. package/dist/worker/observe/routes.js +224 -3
  192. package/dist/worker/observe/server.js +4 -0
  193. package/dist/worker/observe/shell-handler-keys.js +34 -0
  194. package/dist/worker/observe/static/api.js +90 -4
  195. package/dist/worker/observe/static/app.js +18 -70
  196. package/dist/worker/observe/static/constants.js +8 -1
  197. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  198. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  199. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  200. package/dist/worker/observe/static/format.js +60 -0
  201. package/dist/worker/observe/static/index.html +6 -1
  202. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  203. package/dist/worker/observe/static/markdown-render.js +20 -1
  204. package/dist/worker/observe/static/operator-chrome.css +145 -20
  205. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  206. package/dist/worker/observe/static/operator-chrome.js +330 -96
  207. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  208. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  209. package/dist/worker/observe/static/router.d.ts +46 -0
  210. package/dist/worker/observe/static/router.js +25 -1
  211. package/dist/worker/observe/static/state.d.ts +50 -0
  212. package/dist/worker/observe/static/state.js +184 -1
  213. package/dist/worker/observe/static/styles.css +395 -0
  214. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  215. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  216. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  217. package/dist/worker/observe/static/views/dag.js +14 -5
  218. package/dist/worker/observe/static/views/dashboard.js +6 -4
  219. package/dist/worker/observe/static/views/failures.js +5 -2
  220. package/dist/worker/observe/static/views/night.js +2 -2
  221. package/dist/worker/observe/static/views/pool.js +1 -0
  222. package/dist/worker/observe/static/views/run.js +3 -1
  223. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  224. package/dist/worker/observe/static/views/task.js +56 -1
  225. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  226. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  227. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  228. package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
  229. package/dist/workflows/dag/contract-output-registry.js +15 -0
  230. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  231. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  232. package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
  233. package/dist/workflows/dag/failure-category.js +7 -116
  234. package/dist/workflows/dag/frontend-closeout.js +221 -0
  235. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  236. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  237. package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
  238. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  239. package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
  240. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  241. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  242. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  243. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  244. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  245. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  246. package/dist/workflows/dag/frontend-repair.js +7 -424
  247. package/dist/workflows/dag/frontend-review-context.js +288 -14
  248. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  249. package/dist/workflows/dag/frontend-risk.js +15 -2
  250. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  251. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  252. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  253. package/dist/workflows/dag/frontend-shape.js +427 -0
  254. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  255. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  256. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  257. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  258. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  259. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  260. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  261. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  262. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  263. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  264. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  265. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  266. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  267. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  268. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  269. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  270. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  271. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  272. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  273. package/dist/workflows/dag/init-hybrid.js +984 -613
  274. package/dist/workflows/dag/interrupt-request.js +10 -3
  275. package/dist/workflows/dag/lifecycle.js +3 -3
  276. package/dist/workflows/dag/node-execution.js +468 -58
  277. package/dist/workflows/dag/prompt.js +44 -19
  278. package/dist/workflows/dag/recovery-lease.js +80 -0
  279. package/dist/workflows/dag/report.js +37 -1
  280. package/dist/workflows/dag/rerun-feedback.js +124 -1
  281. package/dist/workflows/dag/rerun-plan.js +144 -5
  282. package/dist/workflows/dag/rerun-run.js +81 -6
  283. package/dist/workflows/dag/rerun-task.js +29 -0
  284. package/dist/workflows/dag/retry-policy.js +318 -8
  285. package/dist/workflows/dag/runner.js +429 -122
  286. package/dist/workflows/dag/scheduler.js +114 -16
  287. package/dist/workflows/dag/structured-output-repair.js +712 -0
  288. package/dist/workflows/dag/types.js +321 -31
  289. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  290. package/dist/workflows/dag/validate.js +92 -32
  291. package/docs/README.md +2 -0
  292. package/docs/architecture/evolution.md +45 -0
  293. package/docs/init-surface.manifest.json +9 -0
  294. package/docs/operations/README.md +1 -1
  295. package/docs/operations/local-development-environment.md +21 -0
  296. package/docs/templates/README.md +2 -1
  297. package/docs/templates/agent-dag-report.schema.json +8 -2
  298. package/docs/templates/agent-dag.schema.json +45 -1
  299. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  300. package/docs/templates/frontend-test-case-checklist.md +2 -2
  301. package/docs/templates/frontend-test-dag.json +8 -10
  302. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  303. package/docs/templates/init-managed-agents.md +13 -3
  304. package/docs/templates/spec-registry.schema.json +45 -0
  305. package/harness.json +3 -3
  306. package/package.json +7 -1
  307. package/scripts/next-info.mjs +356 -0
  308. package/scripts/next-publish-gate.mjs +238 -0
  309. package/scripts/release-source-binding.mjs +251 -0
  310. package/skills/codebase-scout/SKILL.md +1 -1
  311. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  312. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  313. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  314. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  315. package/skills/frontend-design-review/SKILL.md +20 -41
  316. package/skills/frontend-implementation/SKILL.md +3 -4
  317. package/skills/frontend-implementation/references/design-spec.md +15 -10
  318. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  319. package/skills/frontend-review/SKILL.md +22 -14
  320. package/skills/frontend-review/references/review-findings.md +6 -7
  321. package/skills/loop-agent/references/command-reference.md +12 -4
  322. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  323. package/skills/playwright-cli/SKILL.md +23 -1
  324. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  325. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  326. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  327. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  328. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  329. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  330. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  331. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -2,6 +2,18 @@ import { readFile, stat, writeFile, mkdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
4
4
  export const FRONTEND_VERIFICATION_TRACE_SCHEMA_ID = "frontend-verification-trace-v1";
5
+ /**
6
+ * Shared marker for a project that exposes no usable verification command at
7
+ * generation time. The marker command runs (and logs not-run honestly), but it
8
+ * must never count as successful verification evidence: labels carrying this
9
+ * text are excluded from successful evidence and targets bound to it fail the
10
+ * trace gate instead of passing it.
11
+ */
12
+ export const FRONTEND_NO_VERIFICATION_MARKER_TEXT = "No frontend verification command configured";
13
+ export function isFrontendNoVerificationMarkerLabel(label) {
14
+ return label.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT);
15
+ }
16
+ export const FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE = `frontend verification unavailable: the only frozen static command is the no-op not-run marker (${FRONTEND_NO_VERIFICATION_MARKER_TEXT}); declare verification commands via task.json --verify or 执行约束.md and regenerate the DAG`;
5
17
  async function readJsonFile(filePath) {
6
18
  return JSON.parse(await readFile(filePath, "utf8"));
7
19
  }
@@ -37,7 +49,7 @@ function symbolEvidenceCandidates(symbol) {
37
49
  return [symbol, describeTitle, "describe("];
38
50
  return [symbol];
39
51
  }
40
- function isConfigurationVerificationFile(file) {
52
+ export function isConfigurationVerificationFile(file) {
41
53
  return /(?:^|\/)(?:package\.json|tsconfig(?:\.[^/]+)?\.json|(?:vite|webpack|rollup|docusaurus)\.config\.[cm]?[jt]s)$/.test(file.replace(/\\/g, "/"));
42
54
  }
43
55
  /**
@@ -52,7 +64,14 @@ export async function evaluateVerificationTargets(input) {
52
64
  const hardIssues = [];
53
65
  for (const target of input.contract.verificationTargets) {
54
66
  const issues = [];
55
- const owners = input.labelOwners.get(target.commandLabel);
67
+ if (isFrontendNoVerificationMarkerLabel(target.commandLabel)) {
68
+ // A target bound to the not-run marker has no verification behind it;
69
+ // passing it would let the run finish green without any check.
70
+ issues.push(`${target.id}: ${FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE}`);
71
+ }
72
+ const owners = isFrontendNoVerificationMarkerLabel(target.commandLabel)
73
+ ? []
74
+ : (input.labelOwners.get(target.commandLabel) ?? []);
56
75
  if (!owners?.length) {
57
76
  issues.push(`commandLabel not executed in current-run frozen evidence: ${target.commandLabel}`);
58
77
  }
@@ -82,6 +101,26 @@ export async function evaluateVerificationTargets(input) {
82
101
  }
83
102
  return { targets, hardIssues };
84
103
  }
104
+ /**
105
+ * Shared deterministic symbol resolvability check used by BOTH the verify
106
+ * trace gate and the plan-time canonical drop (`dropUnresolvedVerificationSymbols`):
107
+ * whether `symbol` can be matched inside `content`. Handles `all describe
108
+ * blocks`, `describe('title')` extraction, and slash-delimited multi-symbol
109
+ * labels the same way everywhere, so plan-time normalization and the final
110
+ * trace gate can never disagree.
111
+ */
112
+ export function verificationSymbolMatchesContent(symbol, content) {
113
+ if (symbol.trim().toLowerCase() === "all describe blocks")
114
+ return content.includes("describe(");
115
+ const symbols = symbol.includes(" / ")
116
+ ? symbol
117
+ .split(/[((]/, 1)[0]
118
+ .split("/")
119
+ .map((item) => item.trim())
120
+ .filter(Boolean)
121
+ : [symbol];
122
+ return symbols.every((item) => symbolEvidenceCandidates(item).some((candidate) => content.includes(candidate)));
123
+ }
85
124
  async function assertFileAndSymbol(input) {
86
125
  const issues = [];
87
126
  const absolute = path.resolve(input.workspaceRoot, input.file);
@@ -105,18 +144,8 @@ async function assertFileAndSymbol(input) {
105
144
  }
106
145
  if (input.symbol) {
107
146
  const content = await readFile(absolute, "utf8");
108
- const symbols = input.symbol.includes(" / ")
109
- ? input.symbol
110
- .split(/[((]/, 1)[0]
111
- .split("/")
112
- .map((symbol) => symbol.trim())
113
- .filter(Boolean)
114
- : [input.symbol];
115
- for (const symbol of symbols) {
116
- const matched = symbolEvidenceCandidates(symbol).some((candidate) => content.includes(candidate));
117
- if (!matched) {
118
- issues.push(`symbol not found: ${symbol} in ${input.file}`);
119
- }
147
+ if (!verificationSymbolMatchesContent(input.symbol, content)) {
148
+ issues.push(`symbol not found: ${input.symbol} in ${input.file}`);
120
149
  }
121
150
  }
122
151
  return issues;
@@ -189,11 +218,17 @@ export async function runFrontendVerificationTraceGate(input) {
189
218
  throw new Error("trace: selected Mock strategy requires successful Mock verification commands");
190
219
  }
191
220
  if (input.evidence) {
192
- if (input.evidence.static.commandLabels.length === 0) {
193
- throw new Error("trace: no successful static verification commands in current run");
221
+ // The no-op not-run marker never counts as successful verification
222
+ // evidence, even if it executed with exit code 0.
223
+ const effectiveStaticLabels = input.evidence.static.commandLabels.filter((label) => !isFrontendNoVerificationMarkerLabel(label));
224
+ const effectiveBehaviorLabels = input.evidence.behavior.commandLabels.filter((label) => !isFrontendNoVerificationMarkerLabel(label));
225
+ if (effectiveStaticLabels.length === 0) {
226
+ throw new Error(input.evidence.static.commandLabels.length > 0
227
+ ? `trace: ${FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE}`
228
+ : "trace: no successful static verification commands in current run");
194
229
  }
195
230
  if (requiresBehaviorVerification &&
196
- input.evidence.behavior.commandLabels.length === 0) {
231
+ effectiveBehaviorLabels.length === 0) {
197
232
  throw new Error("trace: no successful behavior verification commands in current run");
198
233
  }
199
234
  }
@@ -5,25 +5,152 @@ import { runResilientGitCommand } from "../../shared/resilient-git.js";
5
5
  import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
6
6
  import { pathMatchesPattern } from "../../shared/git-progress.js";
7
7
  export const FRONTEND_WORKTREE_DIFF_SCHEMA_ID = "frontend-worktree-diff-v1";
8
+ export const FRONTEND_REVIEW_DIFF_INDEX_SCHEMA_ID = "frontend-review-diff-index-v1";
9
+ const MAX_REVIEW_DIFF_SUMMARY_ENTRIES = 48;
10
+ /** Maximum diff payload per review artifact; metadata adds a small fixed header. */
11
+ const MAX_REVIEW_DIFF_FRAGMENT_BYTES = 20 * 1024;
8
12
  export const FRONTEND_WORKTREE_BASELINE_SCHEMA_ID = "frontend-worktree-baseline-v1";
9
13
  async function runGit(cwd, args) {
10
14
  const result = await runResilientGitCommand({ cwd, args, readOnly: true });
11
15
  return { code: result.code, stdout: result.stdout, stderr: result.stderr };
12
16
  }
17
+ /**
18
+ * Ephemeral interpreter/tool caches that writers may create while drafting
19
+ * tests (kept in sync with `isEphemeralToolCachePath` in
20
+ * src/executors/shell-write-guard.ts). They are not product evidence: tool
21
+ * cache writes must neither fail the write guard nor appear in the review
22
+ * diff as pre-existing changes.
23
+ */
24
+ export function isEphemeralToolCachePath(filePath) {
25
+ const normalized = filePath.replace(/\\/g, "/");
26
+ if (!normalized)
27
+ return false;
28
+ if (normalized === ".pytest_cache" ||
29
+ normalized.startsWith(".pytest_cache/")) {
30
+ return true;
31
+ }
32
+ if (normalized === ".mypy_cache" || normalized.startsWith(".mypy_cache/")) {
33
+ return true;
34
+ }
35
+ if (normalized === ".ruff_cache" || normalized.startsWith(".ruff_cache/")) {
36
+ return true;
37
+ }
38
+ if (normalized === ".playwright-cli" ||
39
+ normalized.startsWith(".playwright-cli/")) {
40
+ return true;
41
+ }
42
+ if (normalized === ".coverage" || normalized.startsWith(".coverage.")) {
43
+ return true;
44
+ }
45
+ // vite/vitest runtime caches live under node_modules/.vite (e.g.
46
+ // node_modules/.vite/vitest/results.json). node_modules is the gitignored
47
+ // dependency tree — tool cache writes there are never a product change.
48
+ if (normalized.startsWith("node_modules/.vite/")) {
49
+ return true;
50
+ }
51
+ if (/(?:^|\/)__pycache__(?:\/|$)/.test(normalized))
52
+ return true;
53
+ if (/\.(?:pyc|pyo)$/.test(normalized))
54
+ return true;
55
+ return false;
56
+ }
13
57
  function splitLines(text) {
14
58
  return text
15
59
  .split(/\r?\n/)
16
60
  .map((line) => line.trim())
17
61
  .filter(Boolean);
18
62
  }
19
- function statusPaths(status) {
20
- return status
21
- .split(/\r?\n/)
22
- .map((line) => line.trimEnd())
23
- .filter((line) => line.length >= 4)
24
- .map((line) => line.slice(3).split(" -> ").at(-1) ?? "")
25
- .filter(Boolean)
26
- .sort();
63
+ function buildReviewDiffSummary(input) {
64
+ const files = [...input.hunks.reduce((grouped, hunk) => {
65
+ const existing = grouped.get(hunk.path);
66
+ if (existing) {
67
+ existing.bytes += hunk.bytes;
68
+ existing.lastPatchPath = hunk.patchPath;
69
+ return grouped;
70
+ }
71
+ grouped.set(hunk.path, {
72
+ path: hunk.path,
73
+ bytes: hunk.bytes,
74
+ parts: hunk.parts,
75
+ firstPatchPath: hunk.patchPath,
76
+ lastPatchPath: hunk.patchPath,
77
+ });
78
+ return grouped;
79
+ }, new Map()).values()];
80
+ const listedFiles = files.slice(0, MAX_REVIEW_DIFF_SUMMARY_ENTRIES);
81
+ const remainingFiles = files.length - listedFiles.length;
82
+ const listedUntracked = input.untrackedFiles.slice(0, MAX_REVIEW_DIFF_SUMMARY_ENTRIES);
83
+ const remainingUntracked = input.untrackedFiles.length - listedUntracked.length;
84
+ return [
85
+ "# Frontend review diff summary",
86
+ "",
87
+ "Read this bounded summary first. Do not read artifacts/diff_patch.patch in full; it is retained only as the canonical audit artifact.",
88
+ "For a semantic finding, read only the matching per-file diff fragment below and then the current source file when necessary. Read fragments in part order when a file has more than one.",
89
+ "",
90
+ `Canonical patch SHA-256: ${input.patchSha256}`,
91
+ `Canonical patch bytes: ${input.patchBytes}`,
92
+ `Tracked changed files: ${files.length}`,
93
+ `Review diff fragments: ${input.hunks.length}`,
94
+ `Untracked files (inventory only): ${input.untrackedFiles.length}`,
95
+ "",
96
+ "## Tracked file diff fragment references",
97
+ ...(listedFiles.length > 0
98
+ ? listedFiles.map((file) => file.parts === 1
99
+ ? `- ${file.path} — ${file.bytes} bytes — read ${file.firstPatchPath} only if this file needs review`
100
+ : `- ${file.path} — ${file.bytes} bytes across ${file.parts} ordered fragments — read ${file.firstPatchPath} through ${file.lastPatchPath} only if this file needs review`)
101
+ : ["- (none)"]),
102
+ ...(remainingFiles > 0
103
+ ? [`- … ${remainingFiles} additional tracked files are recorded in contracts/frontend-review-diff-index.json; do not bulk-read them.`]
104
+ : []),
105
+ "",
106
+ "## Untracked inventory",
107
+ ...(listedUntracked.length > 0
108
+ ? listedUntracked.map((file) => `- ${file}`)
109
+ : ["- (none)"]),
110
+ ...(remainingUntracked > 0
111
+ ? [`- … ${remainingUntracked} additional untracked files are recorded in contracts/frontend-review-diff-index.json.`]
112
+ : []),
113
+ "",
114
+ ].join("\n");
115
+ }
116
+ function splitReviewDiffFragments(text) {
117
+ const fragments = [];
118
+ let current = "";
119
+ for (const line of text.match(/[^\n]*\n|[^\n]+/g) ?? []) {
120
+ const lineBytes = Buffer.byteLength(line);
121
+ if (current.length > 0 &&
122
+ Buffer.byteLength(current) + lineBytes >
123
+ MAX_REVIEW_DIFF_FRAGMENT_BYTES) {
124
+ fragments.push(current);
125
+ current = "";
126
+ }
127
+ if (lineBytes <= MAX_REVIEW_DIFF_FRAGMENT_BYTES) {
128
+ current += line;
129
+ continue;
130
+ }
131
+ // Generated/minified files can produce a single diff line larger than the
132
+ // review budget. Split by Unicode code point so no fragment exceeds the
133
+ // byte limit while preserving the original patch bytes when concatenated.
134
+ let oversizedLineFragment = "";
135
+ let oversizedLineFragmentBytes = 0;
136
+ for (const codePoint of line) {
137
+ const codePointBytes = Buffer.byteLength(codePoint);
138
+ if (oversizedLineFragmentBytes > 0 &&
139
+ oversizedLineFragmentBytes + codePointBytes >
140
+ MAX_REVIEW_DIFF_FRAGMENT_BYTES) {
141
+ fragments.push(oversizedLineFragment);
142
+ oversizedLineFragment = "";
143
+ oversizedLineFragmentBytes = 0;
144
+ }
145
+ oversizedLineFragment += codePoint;
146
+ oversizedLineFragmentBytes += codePointBytes;
147
+ }
148
+ if (oversizedLineFragment)
149
+ fragments.push(oversizedLineFragment);
150
+ }
151
+ if (current.length > 0)
152
+ fragments.push(current);
153
+ return fragments.length > 0 ? fragments : [""];
27
154
  }
28
155
  async function sha256File(filePath) {
29
156
  try {
@@ -37,15 +164,26 @@ async function sha256File(filePath) {
37
164
  export async function captureFrontendWorktreeBaseline(input) {
38
165
  const run = input.dependencies?.runGit ?? runGit;
39
166
  const root = path.resolve(input.workspaceRoot);
40
- const status = await run(root, [
41
- "status",
42
- "--porcelain=v1",
43
- "--untracked-files=all",
167
+ // Baseline = the full concrete workspace inventory the writer may touch
168
+ // (tracked + untracked, gitignored excluded), NOT just currently-changed
169
+ // files. Using `git status` here made a clean repository baseline-empty
170
+ // (status has no entries when nothing changed), which wrongly blocked
171
+ // writer admission with baseline-empty.
172
+ const ls = await run(root, [
173
+ "ls-files",
174
+ "-co",
175
+ "--exclude-standard",
176
+ "-z",
44
177
  ]);
45
- if (status.code !== 0) {
46
- throw new Error(`frontend worktree baseline failed: ${status.stderr.trim() || status.stdout.trim()}`);
178
+ if (ls.code !== 0) {
179
+ throw new Error(`frontend worktree baseline failed: ${ls.stderr.trim() || ls.stdout.trim()}`);
47
180
  }
48
- const files = statusPaths(status.stdout);
181
+ const files = ls.stdout
182
+ .split("\0")
183
+ .map((line) => line.trim())
184
+ .filter(Boolean)
185
+ .filter((file) => !isEphemeralToolCachePath(file))
186
+ .sort();
49
187
  const hashes = {};
50
188
  for (const file of files)
51
189
  hashes[file] = await sha256File(path.join(root, file));
@@ -112,8 +250,12 @@ export async function runFrontendWorktreeDiffGate(input) {
112
250
  if (untracked.code !== 0) {
113
251
  throw new Error(`frontend worktree diff gate ls-files failed: ${untracked.stderr.trim() || untracked.stdout.trim()}`);
114
252
  }
115
- const changedFilesAll = splitLines(nameOnly.stdout).sort();
116
- const untrackedFilesAll = splitLines(untracked.stdout).sort();
253
+ const changedFilesAll = splitLines(nameOnly.stdout)
254
+ .filter((file) => !isEphemeralToolCachePath(file))
255
+ .sort();
256
+ const untrackedFilesAll = splitLines(untracked.stdout)
257
+ .filter((file) => !isEphemeralToolCachePath(file))
258
+ .sort();
117
259
  const authorizedChangedPaths = input.authorizedChangedPaths ?? [];
118
260
  if (baseline) {
119
261
  for (const file of baseline.files) {
@@ -160,11 +302,65 @@ export async function runFrontendWorktreeDiffGate(input) {
160
302
  const absolutePatch = path.join(input.runDir, ...relativePatch.split("/"));
161
303
  await mkdir(path.dirname(absolutePatch), { recursive: true });
162
304
  await writeFile(absolutePatch, patchText, "utf8");
305
+ // Keep the complete patch as immutable audit evidence, but create a bounded
306
+ // reviewer entrypoint plus one file-scoped artifact per tracked change. This
307
+ // prevents final review from loading a multi-thousand-line patch into a
308
+ // single Pi session merely to inspect one or two relevant files.
309
+ const reviewHunks = [];
310
+ const reviewHunksDir = path.join(input.runDir, "artifacts", "diff-hunks");
311
+ await mkdir(reviewHunksDir, { recursive: true });
312
+ for (const [index, file] of changedFiles.entries()) {
313
+ const filePatch = await run(root, ["diff", "--binary", "HEAD", "--", file]);
314
+ if (filePatch.code !== 0) {
315
+ throw new Error(`frontend worktree diff hunk failed for ${file}: ${filePatch.stderr.trim() || filePatch.stdout.trim()}`);
316
+ }
317
+ const text = filePatch.stdout.endsWith("\n") || filePatch.stdout.length === 0
318
+ ? filePatch.stdout
319
+ : `${filePatch.stdout}\n`;
320
+ const fragments = splitReviewDiffFragments(text);
321
+ for (const [partIndex, fragment] of fragments.entries()) {
322
+ const part = partIndex + 1;
323
+ const patchPath = `artifacts/diff-hunks/${String(index + 1).padStart(3, "0")}-${String(part).padStart(3, "0")}.patch`;
324
+ const artifact = [
325
+ "# Frontend review diff fragment",
326
+ `# path: ${file}`,
327
+ `# part: ${part}/${fragments.length}`,
328
+ "",
329
+ fragment,
330
+ ].join("\n");
331
+ await writeFile(path.join(input.runDir, ...patchPath.split("/")), artifact, "utf8");
332
+ reviewHunks.push({
333
+ path: file,
334
+ patchPath,
335
+ sha256: createHash("sha256").update(artifact).digest("hex"),
336
+ bytes: Buffer.byteLength(artifact),
337
+ part,
338
+ parts: fragments.length,
339
+ });
340
+ }
341
+ }
342
+ const reviewIndexPath = "contracts/frontend-review-diff-index.json";
343
+ await writeDagRunJsonArtifact(input.runDir, reviewIndexPath, {
344
+ schemaVersion: 1,
345
+ schemaId: FRONTEND_REVIEW_DIFF_INDEX_SCHEMA_ID,
346
+ patchSha256,
347
+ hunks: reviewHunks,
348
+ untrackedFiles,
349
+ });
350
+ const reviewSummaryPath = "artifacts/diff_summary.md";
351
+ await writeFile(path.join(input.runDir, ...reviewSummaryPath.split("/")), buildReviewDiffSummary({
352
+ patchSha256,
353
+ patchBytes: Buffer.byteLength(patchText),
354
+ hunks: reviewHunks,
355
+ untrackedFiles,
356
+ }), "utf8");
163
357
  const payload = {
164
358
  schemaVersion: 1,
165
359
  schemaId: FRONTEND_WORKTREE_DIFF_SCHEMA_ID,
166
360
  patchPath: relativePatch,
167
361
  patchSha256,
362
+ reviewSummaryPath,
363
+ reviewIndexPath,
168
364
  changedFiles,
169
365
  untrackedFiles,
170
366
  empty: changedFiles.length === 0 && untrackedFiles.length === 0,
@@ -177,6 +373,8 @@ export async function runFrontendWorktreeDiffGate(input) {
177
373
  patchPath: relativePatch,
178
374
  patchAbsolutePath: absolutePatch,
179
375
  patchSha256,
376
+ reviewSummaryPath,
377
+ reviewIndexPath,
180
378
  changedFiles,
181
379
  untrackedFiles,
182
380
  empty: payload.empty,
@@ -189,6 +387,8 @@ export function formatFrontendWorktreeDiffStdout(result) {
189
387
  `schemaId=${result.schemaId}`,
190
388
  `patchPath=${result.patchPath}`,
191
389
  `patchSha256=${result.patchSha256}`,
390
+ `reviewSummaryPath=${result.reviewSummaryPath}`,
391
+ `reviewIndexPath=${result.reviewIndexPath}`,
192
392
  `changedFiles=${result.changedFiles.length}`,
193
393
  `untrackedFiles=${result.untrackedFiles.length}`,
194
394
  `empty=${result.empty ? "true" : "false"}`,
@@ -199,3 +399,36 @@ export function formatFrontendWorktreeDiffStdout(result) {
199
399
  .filter(Boolean)
200
400
  .join("\n");
201
401
  }
402
+ /**
403
+ * Expand glob-shaped contract paths into concrete workspace file paths.
404
+ *
405
+ * The frontend contract schema allows `targets.files` entries such as
406
+ * `src/**` (skeleton inherits the task allowedPaths), but writer admission
407
+ * derives the concrete writeSet and requires real file paths. This helper
408
+ * matches every candidate against the enumerated workspace files and returns
409
+ * the concrete paths. Candidates that are already concrete are kept as-is;
410
+ * globs that match nothing are dropped (the derived writeSet only contains
411
+ * paths the writer can actually touch).
412
+ */
413
+ export function expandFrontendGlobPaths(input) {
414
+ const concrete = new Set();
415
+ const patterns = [];
416
+ for (const candidate of input.candidates) {
417
+ const normalized = candidate.replace(/\\/g, "/");
418
+ if (normalized.includes("*")) {
419
+ patterns.push(normalized);
420
+ }
421
+ else {
422
+ concrete.add(normalized);
423
+ }
424
+ }
425
+ if (patterns.length === 0)
426
+ return [...concrete].sort();
427
+ const workspace = [...new Set(input.workspaceFiles.map((f) => f.replace(/\\/g, "/")))];
428
+ for (const file of workspace) {
429
+ if (patterns.some((pattern) => pathMatchesPattern(file, pattern))) {
430
+ concrete.add(file);
431
+ }
432
+ }
433
+ return [...concrete].sort();
434
+ }