@tea-agent/loop-agent 0.39.0-next.8 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +264 -67
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/dag/generate-task-dag.js +9 -0
  6. package/dist/application/task-lifecycle/advance.js +4 -0
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +16 -5
  9. package/dist/cli/program.js +16 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli.js +2 -1
  12. package/dist/commands/dag-rerun.js +55 -1
  13. package/dist/commands/init-model-catalog.js +464 -0
  14. package/dist/commands/init-upgrade.js +265 -97
  15. package/dist/commands/init.js +428 -27
  16. package/dist/commands/inspect-next.js +8 -0
  17. package/dist/commands/task-advance.js +31 -0
  18. package/dist/executors/dag-pi-executor.js +124 -18
  19. package/dist/executors/pi-event-serializer.js +42 -1
  20. package/dist/executors/pi-executor.js +51 -0
  21. package/dist/executors/pi-extension-resolver.js +233 -0
  22. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  23. package/dist/executors/pi-sdk-executor.js +187 -59
  24. package/dist/executors/shell-executor.js +554 -33
  25. package/dist/executors/shell-write-guard.js +7 -0
  26. package/dist/shared/dag-failure-category.js +138 -0
  27. package/dist/shared/dag-prompt-override.js +28 -0
  28. package/dist/shared/operator/capabilities.js +181 -27
  29. package/dist/shared/playwright-cli-command-policy.js +15 -0
  30. package/dist/shared/update/console-notifier.js +100 -0
  31. package/dist/{cli → shared}/update/npm-client.js +41 -8
  32. package/dist/{cli → shared}/update/state.js +41 -13
  33. package/dist/task/config-types.js +28 -0
  34. package/dist/task/contract/constants.js +1 -0
  35. package/dist/task/contract/diff.js +26 -0
  36. package/dist/task/contract/project.js +5 -0
  37. package/dist/task/contract/schema.js +6 -1
  38. package/dist/task/source-prepare/build-draft.js +15 -0
  39. package/dist/task/source-prepare/parse-intent.js +24 -4
  40. package/dist/worker/cli.js +31 -1
  41. package/dist/worker/console/chat/deferred-turn.js +91 -0
  42. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  43. package/dist/worker/console/chat/operation-card.js +8 -1
  44. package/dist/worker/console/chat/pi-console-config.js +81 -10
  45. package/dist/worker/console/chat/pi-runtime.js +165 -83
  46. package/dist/worker/console/chat/repo-walk.js +10 -1
  47. package/dist/worker/console/chat/routes.js +480 -55
  48. package/dist/worker/console/chat/semantic-activity.js +20 -8
  49. package/dist/worker/console/chat/session-store.js +146 -2
  50. package/dist/worker/console/chat/turn-process.js +10 -10
  51. package/dist/worker/console/dag-execution-receipt.js +33 -0
  52. package/dist/worker/console/inspect-split.js +18 -0
  53. package/dist/worker/console/operation-run-facts.js +19 -3
  54. package/dist/worker/console/operator-actions.js +195 -4
  55. package/dist/worker/console/operator-user-error.js +2 -2
  56. package/dist/worker/console/pi-readiness.js +5 -4
  57. package/dist/worker/console/recovery-cta.js +4 -4
  58. package/dist/worker/console/recovery-error-copy.js +4 -4
  59. package/dist/worker/console/routes.js +43 -2
  60. package/dist/worker/console/security.js +63 -9
  61. package/dist/worker/console/server.js +42 -6
  62. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-Br92EGb7.js} +1 -1
  63. package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-oulImtQq.js} +1 -1
  64. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-tyHc6aqO.js} +1 -1
  65. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-CY86BSpT.js} +1 -1
  66. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-BKKsDmNs.js} +1 -1
  67. package/dist/worker/console/static/assets/channel-TY36zt-i.js +1 -0
  68. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-CXZdjl3B.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-B-aIFDQ5.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-BDgAW_B2.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-DABa4UPR.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-aGbysVOx.js} +1 -1
  73. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-B8MSZDVs.js} +1 -1
  74. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-CpelV73g.js} +1 -1
  75. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-BE04ty3z.js} +1 -1
  76. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-BUxTkaaR.js} +1 -1
  77. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-CYYBVKfX.js} +1 -1
  78. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +1 -0
  79. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +1 -0
  80. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-CEQF_xmQ.js} +1 -1
  81. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-CqpxN5IT.js} +1 -1
  82. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-B3PzanVi.js} +1 -1
  83. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-BSUGCNx1.js} +1 -1
  84. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-CdLevU5T.js} +1 -1
  85. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BjpF_Kok.js} +1 -1
  86. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C4I6NJny.js} +1 -1
  87. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-C8hecCr5.js} +1 -1
  88. package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-Daod6tOS.js} +1 -1
  89. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-BvvmazGv.js} +1 -1
  90. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-DeN_vfV4.js} +1 -1
  91. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-D6dq1t9z.js} +1 -1
  92. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-DziIdNY4.js} +1 -1
  93. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js} +1 -1
  94. package/dist/worker/console/static/assets/index-C3GgHg-g.css +1 -0
  95. package/dist/worker/console/static/assets/index-Ci8ksvBm.js +406 -0
  96. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-Czv5eYx2.js} +1 -1
  97. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js} +1 -1
  98. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-DxGmNj5K.js} +1 -1
  99. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-CYCFqkO_.js} +1 -1
  100. package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-B0YzRwrd.js} +1 -1
  101. package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-t3EZtIGh.js} +5 -5
  102. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-BTXpTv0T.js} +1 -1
  103. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-DU8UkKqA.js} +1 -1
  104. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-DP4y8ohp.js} +1 -1
  105. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-B07vNaYR.js} +1 -1
  106. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-xDWktb2i.js} +1 -1
  107. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-BLNMKqeS.js} +1 -1
  108. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-n0adalxH.js} +1 -1
  109. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-BbnS-2DL.js} +1 -1
  110. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-B4uPpCLu.js} +1 -1
  111. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA--MMbB0fX.js} +1 -1
  112. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +1 -0
  113. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-BXUgp2QY.js} +2 -2
  114. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +8 -0
  115. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-CsaMxki7.js} +1 -1
  116. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-C2IuG9iH.js} +1 -1
  117. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-CmEWOXCW.js} +1 -1
  118. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-D_9LE0MG.js} +1 -1
  119. package/dist/worker/console/static/index.html +2 -2
  120. package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
  121. package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
  122. package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
  123. package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
  124. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  125. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
  126. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  127. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  128. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  129. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  130. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
  131. package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
  132. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
  133. package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
  134. package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
  135. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  136. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
  137. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  138. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  139. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  140. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  141. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  142. package/dist/worker/observability/read-model.js +22 -0
  143. package/dist/worker/observe/node-input.js +74 -3
  144. package/dist/worker/observe/node-process.js +377 -0
  145. package/dist/worker/observe/routes.js +36 -3
  146. package/dist/worker/observe/shell-handler-keys.js +34 -0
  147. package/dist/worker/observe/static/api.js +66 -0
  148. package/dist/worker/observe/static/constants.js +8 -1
  149. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  150. package/dist/worker/observe/static/format.js +23 -0
  151. package/dist/worker/observe/static/markdown-render.js +20 -1
  152. package/dist/worker/observe/static/operator-chrome.js +4 -0
  153. package/dist/worker/observe/static/state.js +13 -2
  154. package/dist/worker/observe/static/styles.css +73 -0
  155. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  156. package/dist/worker/observe/static/views/dag.js +8 -1
  157. package/dist/worker/observe/static/views/failures.js +5 -2
  158. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  159. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +827 -53
  160. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  161. package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
  162. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  163. package/dist/workflows/dag/backend-test-intake-context.js +32 -0
  164. package/dist/workflows/dag/backend-test-layout.js +133 -0
  165. package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
  166. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  167. package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
  168. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  169. package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
  170. package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
  171. package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
  172. package/dist/workflows/dag/contract-output-registry.js +15 -0
  173. package/dist/workflows/dag/contract-validator-registrations.js +3 -1
  174. package/dist/workflows/dag/convergence/controller.js +141 -0
  175. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  176. package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
  177. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
  178. package/dist/workflows/dag/failure-category.js +7 -116
  179. package/dist/workflows/dag/frontend-implementation-contract.js +377 -4
  180. package/dist/workflows/dag/frontend-plan-render.js +1 -1
  181. package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
  182. package/dist/workflows/dag/frontend-repair.js +14 -0
  183. package/dist/workflows/dag/frontend-risk.js +15 -2
  184. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  185. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  186. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  187. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  188. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  189. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  190. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  191. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  192. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  193. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  194. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  195. package/dist/workflows/dag/init-hybrid.js +894 -205
  196. package/dist/workflows/dag/node-execution.js +183 -15
  197. package/dist/workflows/dag/prompt.js +60 -1
  198. package/dist/workflows/dag/rerun-feedback.js +212 -0
  199. package/dist/workflows/dag/rerun-plan.js +224 -27
  200. package/dist/workflows/dag/rerun-run.js +61 -6
  201. package/dist/workflows/dag/rerun-task.js +61 -2
  202. package/dist/workflows/dag/retry-policy.js +109 -0
  203. package/dist/workflows/dag/structured-output-repair.js +712 -0
  204. package/dist/workflows/dag/types.js +143 -1
  205. package/dist/workflows/dag/validate.js +64 -20
  206. package/docs/init-surface.manifest.json +5 -0
  207. package/docs/operations/README.md +1 -1
  208. package/docs/operations/local-development-environment.md +1 -5
  209. package/docs/skills/vetted-skill-registry.md +14 -0
  210. package/docs/templates/README.md +1 -1
  211. package/docs/templates/agent-dag.schema.json +44 -0
  212. package/docs/templates/backend-test-dag.json +60 -35
  213. package/docs/templates/frontend-implementation-contract.schema.json +1 -1
  214. package/docs/templates/frontend-test-case-checklist.md +2 -2
  215. package/docs/templates/frontend-test-dag.json +8 -10
  216. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  217. package/docs/templates/init-managed-agents.md +13 -3
  218. package/harness.json +6 -4
  219. package/package.json +12 -4
  220. package/scripts/next-info.mjs +356 -0
  221. package/scripts/next-publish-gate.mjs +238 -0
  222. package/scripts/release-source-binding.mjs +251 -0
  223. package/skills/analyze-product-requirements/SKILL.md +8 -5
  224. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  225. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  226. package/skills/analyze-product-requirements/references/example.md +50 -0
  227. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  228. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  229. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  230. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  231. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  232. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  233. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  234. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  235. package/skills/codebase-scout/SKILL.md +1 -1
  236. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  237. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  238. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  239. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  240. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  241. package/skills/improve-codebase-architecture/deepening.md +37 -0
  242. package/skills/improve-codebase-architecture/html-report.md +123 -0
  243. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  244. package/skills/improve-codebase-architecture/language.md +53 -0
  245. package/skills/loop-agent/references/command-reference.md +11 -4
  246. package/skills/playwright-cli/SKILL.md +23 -1
  247. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  248. package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
  249. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
  250. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
  251. package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
  252. package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
  253. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
  254. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
4
- import { isSafeReadOnlyPiRetryCandidate } from "./retry-policy.js";
4
+ import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
5
5
  import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
6
+ import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
6
7
  const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
7
8
  const SAFE_SHELL_GATE_KEYS = [
8
9
  "verdictGate",
@@ -122,32 +123,56 @@ function hasDynamicRuntime(task) {
122
123
  task.dynamicCondition ||
123
124
  task.dynamicLoopUntil);
124
125
  }
125
- function shellHasDisallowedConfig(shell) {
126
- if ((shell.commands?.length ?? 0) > 0)
127
- return true;
126
+ /**
127
+ * Narrow admission for the standard DAG's generated final verification node.
128
+ * `verifyEvidence` alone is metadata, not proof that an arbitrary shell is
129
+ * read-only, so custom shell ids and non-canonical shapes stay blocked.
130
+ */
131
+ function isCanonicalGeneratedVerifyShell(task) {
132
+ if (task.executor !== "shell")
133
+ return false;
134
+ const shell = task.shell;
135
+ if (!shell)
136
+ return false;
137
+ const evidence = shell.verifyEvidence;
138
+ if (task.id !== "verify-shell")
139
+ return false;
140
+ if (task.role !== "verifier")
141
+ return false;
142
+ if (task.writePolicy !== "read-only")
143
+ return false;
144
+ if (!evidence)
145
+ return false;
146
+ if (evidence.phase !== "final")
147
+ return false;
148
+ if (evidence.quota !== "full")
149
+ return false;
150
+ if (evidence.commandSource !== "adapter")
151
+ return false;
152
+ if (evidence.finalFullRequired !== true)
153
+ return false;
154
+ if ((shell.commands?.length ?? 0) === 0)
155
+ return false;
156
+ if (evidence.commandCount !== shell.commands.length)
157
+ return false;
158
+ // Any side-effect-capable config disqualifies the node.
128
159
  if (shell.preset)
129
- return true;
160
+ return false;
130
161
  if (shell.projectGovernanceGate)
131
- return true;
162
+ return false;
132
163
  if (shell.frontendPrewriteGate)
133
- return true;
164
+ return false;
134
165
  if (shell.frontendVerificationBundle)
135
- return true;
166
+ return false;
136
167
  if (shell.frontendReviewContext)
137
- return true;
168
+ return false;
138
169
  if (shell.backendTestPipeline)
139
- return true;
140
- if (shell.verifyEvidence)
141
- return true;
142
- if (shell.nonZeroExitPolicy)
143
- return true;
170
+ return false;
171
+ if (shell.nonZeroExitPolicy === "record")
172
+ return false;
144
173
  if (shell.envAllowlist?.length)
145
- return true;
146
- if (shell.timeoutMs !== undefined)
147
- return true;
148
- if (shell.cwd)
149
- return true;
150
- return false;
174
+ return false;
175
+ return true;
151
176
  }
152
177
  function isSafeGateOnlyShell(task) {
153
178
  if (task.executor !== "shell")
@@ -155,9 +180,32 @@ function isSafeGateOnlyShell(task) {
155
180
  const shell = task.shell;
156
181
  if (!shell)
157
182
  return false;
158
- if (shellHasDisallowedConfig(shell))
183
+ if (!SAFE_SHELL_GATE_KEYS.some((key) => shell[key] !== undefined)) {
184
+ return false;
185
+ }
186
+ // No side-effect-capable config may ride along on a gate-only shell.
187
+ // Deterministic/read-only riders are admitted: projectGovernanceGate (repo
188
+ // governance context path), cwd, and timeoutMs never execute commands, so
189
+ // the governance-standard gate stays rerun-eligible.
190
+ if ((shell.commands?.length ?? 0) > 0)
159
191
  return false;
160
- return SAFE_SHELL_GATE_KEYS.some((key) => shell[key] !== undefined);
192
+ if (shell.preset)
193
+ return false;
194
+ if (shell.frontendPrewriteGate)
195
+ return false;
196
+ if (shell.frontendVerificationBundle)
197
+ return false;
198
+ if (shell.frontendReviewContext)
199
+ return false;
200
+ if (shell.backendTestPipeline)
201
+ return false;
202
+ if (shell.verifyEvidence)
203
+ return false;
204
+ if (shell.nonZeroExitPolicy)
205
+ return false;
206
+ if (shell.envAllowlist?.length)
207
+ return false;
208
+ return true;
161
209
  }
162
210
  function isStaticTask(task) {
163
211
  return task.executor === "static";
@@ -259,7 +307,10 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
259
307
  ...collectReachableDescendants(spec, effectiveFromNodeId),
260
308
  ].sort();
261
309
  }
262
- export function assessResetClosureSafety(spec, resetNodeIds) {
310
+ function isNeverStartedNodeStatus(status) {
311
+ return status === undefined || status === "SKIPPED" || status === "PENDING";
312
+ }
313
+ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
263
314
  const tasks = taskById(spec);
264
315
  const blockingNodes = [];
265
316
  const reasonCodes = [];
@@ -267,7 +318,17 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
267
318
  const task = tasks.get(nodeId);
268
319
  if (!task)
269
320
  continue;
321
+ if (options.parentNodes !== undefined &&
322
+ isNeverStartedNodeStatus(options.parentNodes[nodeId]?.status)) {
323
+ continue;
324
+ }
270
325
  if (isWriterTask(task, spec)) {
326
+ if (nodeId === options.allowedTransportWriterNodeId)
327
+ continue;
328
+ if (nodeId === options.allowedExclusiveShellFromNodeId &&
329
+ task.executor === "shell") {
330
+ continue;
331
+ }
271
332
  blockingNodes.push({
272
333
  nodeId,
273
334
  reasonCode: "restart-subgraph-contains-writer",
@@ -305,7 +366,8 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
305
366
  continue;
306
367
  }
307
368
  if (task.executor === "shell") {
308
- if (!isSafeGateOnlyShell(task)) {
369
+ if (!isSafeGateOnlyShell(task) &&
370
+ !isCanonicalGeneratedVerifyShell(task)) {
309
371
  blockingNodes.push({
310
372
  nodeId,
311
373
  reasonCode: "restart-subgraph-contains-unsafe-shell",
@@ -320,6 +382,100 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
320
382
  reasonCodes: [...new Set(reasonCodes)],
321
383
  };
322
384
  }
385
+ /**
386
+ * Experimental prompt-override admission (fail-closed):
387
+ * - requires the internal switch (see shared/dag-prompt-override.ts);
388
+ * - the target must be a failed (ERROR) node inside the reset closure, so an
389
+ * override can only adjust how an already-failed node retries — never a
390
+ * finished, skipped, or not-yet-started node;
391
+ * - only `subtask_prompt` semantics change: "append" keeps the frozen base
392
+ * and adds a marked addendum block, "replace" swaps the whole prompt;
393
+ * base/final sha256 are recorded for provenance and bound into planHash.
394
+ */
395
+ function assessPromptOverrides(input) {
396
+ const blockingNodes = [];
397
+ const reasonCodes = [];
398
+ const tasks = taskById(input.spec);
399
+ const resetSet = new Set(input.resetNodeIds);
400
+ const seen = new Set();
401
+ const entries = [];
402
+ if (!input.enabled) {
403
+ reasonCodes.push("prompt-override-disabled");
404
+ }
405
+ for (const override of input.overrides) {
406
+ const nodeId = override.nodeId?.trim() ?? "";
407
+ if (seen.has(nodeId)) {
408
+ blockingNodes.push({
409
+ nodeId,
410
+ reasonCode: "prompt-override-duplicate-node",
411
+ });
412
+ reasonCodes.push("prompt-override-duplicate-node");
413
+ continue;
414
+ }
415
+ seen.add(nodeId);
416
+ const task = tasks.get(nodeId);
417
+ if (!task) {
418
+ blockingNodes.push({
419
+ nodeId,
420
+ reasonCode: "prompt-override-node-missing",
421
+ });
422
+ reasonCodes.push("prompt-override-node-missing");
423
+ continue;
424
+ }
425
+ if (!resetSet.has(nodeId)) {
426
+ blockingNodes.push({
427
+ nodeId,
428
+ reasonCode: "prompt-override-outside-reset-closure",
429
+ });
430
+ reasonCodes.push("prompt-override-outside-reset-closure");
431
+ continue;
432
+ }
433
+ if (input.state.nodes[nodeId]?.status !== "ERROR") {
434
+ blockingNodes.push({
435
+ nodeId,
436
+ reasonCode: "prompt-override-node-not-failed",
437
+ });
438
+ reasonCodes.push("prompt-override-node-not-failed");
439
+ continue;
440
+ }
441
+ const mode = override.mode === "append" ? "append" : "replace";
442
+ const prompt = override.prompt ?? "";
443
+ if (!prompt.trim() ||
444
+ prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
445
+ blockingNodes.push({
446
+ nodeId,
447
+ reasonCode: "prompt-override-invalid-prompt",
448
+ });
449
+ reasonCodes.push("prompt-override-invalid-prompt");
450
+ continue;
451
+ }
452
+ const basePromptSha256 = createHash("sha256")
453
+ .update(task.subtask_prompt, "utf8")
454
+ .digest("hex");
455
+ const finalPrompt = composeDagPromptOverridePrompt({
456
+ basePrompt: task.subtask_prompt,
457
+ text: prompt,
458
+ mode,
459
+ });
460
+ const promptSha256 = createHash("sha256")
461
+ .update(finalPrompt, "utf8")
462
+ .digest("hex");
463
+ if (promptSha256 === basePromptSha256) {
464
+ blockingNodes.push({
465
+ nodeId,
466
+ reasonCode: "prompt-override-unchanged",
467
+ });
468
+ reasonCodes.push("prompt-override-unchanged");
469
+ continue;
470
+ }
471
+ entries.push({ nodeId, mode, basePromptSha256, promptSha256 });
472
+ }
473
+ return {
474
+ entries: entries.length > 0 ? entries : undefined,
475
+ blockingNodes,
476
+ reasonCodes: [...new Set(reasonCodes)],
477
+ };
478
+ }
323
479
  export function computePlanHash(planWithoutHash) {
324
480
  const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
325
481
  return createHash("sha256").update(canonical, "utf8").digest("hex");
@@ -448,6 +604,20 @@ export async function evaluateDagRerunPlan(input) {
448
604
  : [];
449
605
  const resetSet = new Set(resetNodeIds);
450
606
  const importedNodeIds = allNodeIds.filter((nodeId) => !resetSet.has(nodeId));
607
+ const tasksByIdForPlan = taskById(input.parentSpec);
608
+ const effectiveTask = effectiveFromNodeId
609
+ ? tasksByIdForPlan.get(effectiveFromNodeId)
610
+ : undefined;
611
+ const effectiveRecord = effectiveFromNodeId
612
+ ? input.parentState.nodes[effectiveFromNodeId]
613
+ : undefined;
614
+ const transportWriterRestart = Boolean(effectiveFromNodeId &&
615
+ effectiveRecord?.status === "ERROR" &&
616
+ isTransportCrashedExclusiveWriter(effectiveTask, effectiveRecord.failureCategory));
617
+ const convergenceWriterRestart = Boolean(transportWriterRestart &&
618
+ input.parentSpec.convergence?.enabled === true &&
619
+ input.parentState.convergence?.enabled === true &&
620
+ !input.parentState.convergence.terminalReason);
451
621
  const controllerIdentityMatched = input.currentControllerFingerprint === undefined ||
452
622
  input.parentControllerFingerprint === undefined ||
453
623
  input.currentControllerFingerprint === input.parentControllerFingerprint;
@@ -482,11 +652,11 @@ export async function evaluateDagRerunPlan(input) {
482
652
  reasonCodes.push("parent-lifecycle-ineligible");
483
653
  blockedReasons.push("parent-lifecycle-ineligible");
484
654
  }
485
- if (input.parentSpec.convergence?.enabled) {
655
+ if (input.parentSpec.convergence?.enabled && !convergenceWriterRestart) {
486
656
  reasonCodes.push("convergence-rerun-unsupported");
487
657
  blockedReasons.push("convergence-rerun-unsupported");
488
658
  }
489
- if (input.parentState.convergence?.enabled) {
659
+ if (input.parentState.convergence?.enabled && !convergenceWriterRestart) {
490
660
  reasonCodes.push("convergence-rerun-unsupported");
491
661
  blockedReasons.push("convergence-rerun-unsupported");
492
662
  }
@@ -530,7 +700,17 @@ export async function evaluateDagRerunPlan(input) {
530
700
  blockedReasons.push("ambiguous-skipped-rewrite");
531
701
  }
532
702
  }
533
- const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds);
703
+ const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, {
704
+ ...(transportWriterRestart && effectiveFromNodeId
705
+ ? { allowedTransportWriterNodeId: effectiveFromNodeId }
706
+ : {}),
707
+ parentNodes: input.parentState.nodes,
708
+ ...(effectiveFromNodeId &&
709
+ effectiveTask?.executor === "shell" &&
710
+ effectiveRecord?.status === "ERROR"
711
+ ? { allowedExclusiveShellFromNodeId: effectiveFromNodeId }
712
+ : {}),
713
+ });
534
714
  blockingNodes.push(...closureSafety.blockingNodes);
535
715
  for (const code of closureSafety.reasonCodes) {
536
716
  reasonCodes.push(code);
@@ -563,6 +743,22 @@ export async function evaluateDagRerunPlan(input) {
563
743
  blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
564
744
  }
565
745
  }
746
+ let promptOverrideEntries;
747
+ if (input.promptOverrides !== undefined && input.promptOverrides.length > 0) {
748
+ const overrideAssessment = assessPromptOverrides({
749
+ spec: input.parentSpec,
750
+ state: input.parentState,
751
+ resetNodeIds,
752
+ overrides: input.promptOverrides,
753
+ enabled: input.promptOverrideEnabled === true,
754
+ });
755
+ blockingNodes.push(...overrideAssessment.blockingNodes);
756
+ for (const code of overrideAssessment.reasonCodes) {
757
+ reasonCodes.push(code);
758
+ blockedReasons.push(code);
759
+ }
760
+ promptOverrideEntries = overrideAssessment.entries;
761
+ }
566
762
  const uniqueReasonCodes = [...new Set(reasonCodes)];
567
763
  const uniqueBlockedReasons = [...new Set(blockedReasons)];
568
764
  const eligible = uniqueBlockedReasons.length === 0 &&
@@ -606,6 +802,7 @@ export async function evaluateDagRerunPlan(input) {
606
802
  parentLifecycle: input.parentLifecycle,
607
803
  workerManaged: input.workerManaged,
608
804
  }),
805
+ ...(promptOverrideEntries ? { promptOverrides: promptOverrideEntries } : {}),
609
806
  };
610
807
  return {
611
808
  ...planWithoutHash,
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { access, copyFile, mkdir, readdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
+ import { composeDagPromptOverridePrompt, isDagRerunPromptOverrideEnabled, } from "../../shared/dag-prompt-override.js";
5
6
  import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
6
7
  import { readControllerIdentityArtifact, captureControllerIdentity } from "./controller-identity.js";
7
8
  import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
@@ -190,6 +191,8 @@ export async function planDagRerun(input) {
190
191
  parentSpec: nonCompletedSpec,
191
192
  parentState: nonCompletedState,
192
193
  parentLifecycle,
194
+ promptOverrides: input.promptOverrides,
195
+ promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
193
196
  workerManaged: nonCompletedWorkerHints.workerManaged,
194
197
  workerAssociation: nonCompletedWorkerHints.workerManaged
195
198
  ? {
@@ -229,6 +232,8 @@ export async function planDagRerun(input) {
229
232
  parentTerminalWorkspace,
230
233
  currentWorkspace,
231
234
  skillSnapshotOk,
235
+ promptOverrides: input.promptOverrides,
236
+ promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
232
237
  workerManaged: workerHints.workerManaged,
233
238
  workerAssociation: workerHints.workerManaged
234
239
  ? {
@@ -384,15 +389,57 @@ function buildPendingNodeRecord(task) {
384
389
  ...(task.outputMode ? { outputMode: task.outputMode } : {}),
385
390
  };
386
391
  }
392
+ /**
393
+ * Stage the continuation spec with operator prompt overrides applied. The
394
+ * parent spec stays frozen; every override must match the plan entry (sha256
395
+ * of the composed final prompt and of the parent base prompt) so the
396
+ * two-phase planHash remains the single source of truth for what changed.
397
+ * "append" entries keep the frozen base and append the operator addendum in a
398
+ * marked block; "replace" entries swap the whole prompt.
399
+ */
400
+ function applyPromptOverridesToSpec(input) {
401
+ const planned = new Map((input.plan.promptOverrides ?? []).map((entry) => [entry.nodeId, entry]));
402
+ const spec = structuredClone(input.spec);
403
+ for (const override of input.overrides) {
404
+ const entry = planned.get(override.nodeId);
405
+ if (!entry) {
406
+ throw new Error(`prompt override is not present in the rerun plan: ${override.nodeId}`);
407
+ }
408
+ const task = spec.tasks.find((candidate) => candidate.id === override.nodeId);
409
+ if (!task) {
410
+ throw new Error(`prompt override node missing from spec: ${override.nodeId}`);
411
+ }
412
+ if (sha256Hex(task.subtask_prompt) !== entry.basePromptSha256) {
413
+ throw new Error(`prompt override base prompt drifted from plan: ${override.nodeId}`);
414
+ }
415
+ const finalPrompt = composeDagPromptOverridePrompt({
416
+ basePrompt: task.subtask_prompt,
417
+ text: override.prompt,
418
+ mode: override.mode === "append" ? "append" : "replace",
419
+ });
420
+ if (sha256Hex(finalPrompt) !== entry.promptSha256) {
421
+ throw new Error(`prompt override text does not match the planned sha256: ${override.nodeId}`);
422
+ }
423
+ task.subtask_prompt = finalPrompt;
424
+ }
425
+ return spec;
426
+ }
387
427
  async function stageContinuationRun(input) {
388
- const { ranks } = topoSortToRanks(input.parentSpec);
389
- const runId = await buildDagRunId(input.parentSpec, { cwd: input.cwd });
428
+ const specForRun = input.promptOverrides !== undefined && input.promptOverrides.length > 0
429
+ ? applyPromptOverridesToSpec({
430
+ spec: input.parentSpec,
431
+ plan: input.plan,
432
+ overrides: input.promptOverrides,
433
+ })
434
+ : input.parentSpec;
435
+ const { ranks } = topoSortToRanks(specForRun);
436
+ const runId = await buildDagRunId(specForRun, { cwd: input.cwd });
390
437
  const runDir = getDagRunDir(input.cwd, "active", runId);
391
438
  await prepareActiveRunDir(runDir);
392
- await writeRunSpec(runDir, input.parentSpec);
439
+ await writeRunSpec(runDir, specForRun);
393
440
  const resetSet = new Set(input.plan.resetNodeIds);
394
441
  const importedSet = new Set(input.plan.importedNodeIds);
395
- const tasksById = new Map(input.parentSpec.tasks.map((task) => [task.id, task]));
442
+ const tasksById = new Map(specForRun.tasks.map((task) => [task.id, task]));
396
443
  const importManifestNodes = [];
397
444
  const nodes = {};
398
445
  for (const nodeId of input.plan.importedNodeIds) {
@@ -433,6 +480,9 @@ async function stageContinuationRun(input) {
433
480
  reason: input.reason,
434
481
  requestId: input.requestId,
435
482
  planHash: input.plan.planHash,
483
+ ...(input.plan.promptOverrides
484
+ ? { promptOverrides: input.plan.promptOverrides }
485
+ : {}),
436
486
  createdAt,
437
487
  };
438
488
  const state = {
@@ -445,6 +495,9 @@ async function stageContinuationRun(input) {
445
495
  ranks,
446
496
  nodes,
447
497
  continuation,
498
+ ...(input.parentState.convergence
499
+ ? { convergence: structuredClone(input.parentState.convergence) }
500
+ : {}),
448
501
  ...(input.parentState.evaluation
449
502
  ? { evaluation: structuredClone(input.parentState.evaluation) }
450
503
  : {}),
@@ -479,7 +532,7 @@ async function stageContinuationRun(input) {
479
532
  await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
480
533
  await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
481
534
  await writeRunState(runDir, state);
482
- return { runId, runDir, state };
535
+ return { runId, runDir, state, spec: specForRun };
483
536
  }
484
537
  export async function executeDagRerun(input) {
485
538
  const existing = await readExistingOperatorRequest(input.cwd, input.requestId);
@@ -490,6 +543,7 @@ export async function executeDagRerun(input) {
490
543
  cwd: input.cwd,
491
544
  parentRunId: input.parentRunId,
492
545
  selectedNodeId: input.selectedNodeId,
546
+ promptOverrides: input.promptOverrides,
493
547
  });
494
548
  if (plan.planHash !== input.planHash) {
495
549
  return {
@@ -527,10 +581,11 @@ export async function executeDagRerun(input) {
527
581
  plan,
528
582
  reason: input.reason,
529
583
  requestId: input.requestId,
584
+ promptOverrides: input.promptOverrides,
530
585
  });
531
586
  const continuationOptions = {
532
587
  cwd: input.cwd,
533
- spec: parentSpec,
588
+ spec: staged.spec,
534
589
  state: staged.state,
535
590
  runDir: staged.runDir,
536
591
  ...(input.maxConcurrent !== undefined
@@ -3,7 +3,8 @@ import path from "node:path";
3
3
  import { generateTaskDagUseCase } from "../../application/dag/generate-task-dag.js";
4
4
  import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
5
5
  import { DAG_RUNS_DIR, isTerminalDagRunStatus, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
6
- import { parseDagSpec } from "./types.js";
6
+ import { deriveDagRerunFeedback } from "./rerun-feedback.js";
7
+ import { parseDagSpec, } from "./types.js";
7
8
  export const RERUN_TASK_LINEAGE_REL_PATH = ".runtime/rerun-task-lineage.json";
8
9
  export function parseDagRerunTaskArgs(args) {
9
10
  if (args.length === 0) {
@@ -218,6 +219,7 @@ async function writeRerunTaskLineage(cwd, newRunId, input) {
218
219
  parentRunId: input.parentRunId,
219
220
  reason: input.reason,
220
221
  requestId: input.requestId,
222
+ feedback: input.feedback,
221
223
  createdAt: new Date().toISOString(),
222
224
  }, { repoRoot: cwd, allowCompletedFactsWrite: true });
223
225
  }
@@ -228,12 +230,30 @@ function blockedResult(input) {
228
230
  reason: input.reason,
229
231
  requestId: input.requestId,
230
232
  mode: "blocked",
233
+ feedback: input.feedback ?? {
234
+ inherited: false,
235
+ reason: "no-unresolved-verify-review-feedback",
236
+ },
231
237
  blockedReason: input.blockedReason,
232
238
  ...(input.note ? { note: input.note } : {}),
233
239
  ...(input.taskId ? { taskId: input.taskId } : {}),
234
240
  ...(input.conflictPaths ? { conflictPaths: input.conflictPaths } : {}),
235
241
  };
236
242
  }
243
+ async function readGeneratedRerunFeedback(outputPath) {
244
+ const raw = JSON.parse(await readFile(outputPath, "utf8"));
245
+ return parseDagSpec(raw).rerunFeedback;
246
+ }
247
+ function summarizeFeedback(feedback, fallbackReason) {
248
+ return feedback
249
+ ? {
250
+ inherited: true,
251
+ sourceNodeId: feedback.sourceNodeId,
252
+ digest: feedback.digest,
253
+ bindingStatus: feedback.binding.status,
254
+ }
255
+ : { inherited: false, reason: fallbackReason };
256
+ }
237
257
  function extractRunSummary(generateResult) {
238
258
  if (generateResult.mode === "generate+basic-validate")
239
259
  return undefined;
@@ -277,7 +297,26 @@ export async function rerunStandaloneTask(input) {
277
297
  note: eligibility.note,
278
298
  });
279
299
  }
300
+ let parentFeedback;
301
+ let missingFeedbackReason = "no-unresolved-verify-review-feedback";
302
+ try {
303
+ parentFeedback = await deriveDagRerunFeedback({
304
+ parentRunId: input.parentRunId,
305
+ taskId: taskResolution.taskId,
306
+ runDir: located.runDir,
307
+ spec,
308
+ state,
309
+ });
310
+ }
311
+ catch {
312
+ // Feedback is additive recovery context. A corrupt/missing historical
313
+ // artifact must be reported honestly but must not block an otherwise
314
+ // eligible full rerun.
315
+ missingFeedbackReason = "feedback-unavailable";
316
+ }
280
317
  const execute = input.execute ?? true;
318
+ let effectiveFeedback;
319
+ let expectedRerunFeedbackDigest;
281
320
  // AC-001 generated-output-conflict preflight: when a real execution is
282
321
  // requested, first generate the DAG draft (execute:false) so we can inspect
283
322
  // the regenerated spec's requireChangedFiles writer write sets against
@@ -296,15 +335,23 @@ export async function rerunStandaloneTask(input) {
296
335
  dryRun: false,
297
336
  profile: input.profile ?? "auto",
298
337
  profileExplicit: input.profileExplicit ?? false,
338
+ rerunFeedback: parentFeedback,
299
339
  });
300
340
  if (preflight.mode === "generate+basic-validate") {
301
341
  let conflictPaths = [];
302
342
  try {
303
343
  const raw = await readFile(preflight.outputPath, "utf8");
304
344
  const preflightSpec = parseDagSpec(JSON.parse(raw));
345
+ effectiveFeedback = preflightSpec.rerunFeedback;
346
+ expectedRerunFeedbackDigest = effectiveFeedback?.digest;
347
+ if (parentFeedback && !effectiveFeedback) {
348
+ throw new Error("rerun feedback missing from generated preflight DAG");
349
+ }
305
350
  conflictPaths = await assessGeneratedOutputConflict(preflightSpec, input.cwd);
306
351
  }
307
- catch {
352
+ catch (error) {
353
+ if (parentFeedback)
354
+ throw error;
308
355
  // If the draft cannot be parsed, fall through to the real execution
309
356
  // path: execution's own validation will surface the structural defect.
310
357
  conflictPaths = [];
@@ -332,7 +379,17 @@ export async function rerunStandaloneTask(input) {
332
379
  dryRun: false,
333
380
  profile: input.profile ?? "auto",
334
381
  profileExplicit: input.profileExplicit ?? false,
382
+ rerunFeedback: parentFeedback,
383
+ expectedRerunFeedbackDigest,
335
384
  });
385
+ if (parentFeedback && !effectiveFeedback) {
386
+ effectiveFeedback = await readGeneratedRerunFeedback(generateResult.outputPath);
387
+ }
388
+ if (expectedRerunFeedbackDigest &&
389
+ effectiveFeedback?.digest !== expectedRerunFeedbackDigest) {
390
+ throw new Error(`rerun feedback drift after generation: expected ${expectedRerunFeedbackDigest}, got ${effectiveFeedback?.digest ?? "missing"}`);
391
+ }
392
+ const feedbackSummary = summarizeFeedback(effectiveFeedback, missingFeedbackReason);
336
393
  const newRunId = extractNewRunId(generateResult);
337
394
  const runSummary = extractRunSummary(generateResult);
338
395
  const completedSuccessfully = !execute ||
@@ -342,6 +399,7 @@ export async function rerunStandaloneTask(input) {
342
399
  parentRunId: input.parentRunId,
343
400
  reason: input.reason,
344
401
  requestId: input.requestId,
402
+ feedback: feedbackSummary,
345
403
  });
346
404
  }
347
405
  const result = {
@@ -350,6 +408,7 @@ export async function rerunStandaloneTask(input) {
350
408
  reason: input.reason,
351
409
  requestId: input.requestId,
352
410
  mode: "generate+validate+execute",
411
+ feedback: feedbackSummary,
353
412
  taskId: taskResolution.taskId,
354
413
  ...(newRunId ? { newRunId } : {}),
355
414
  ...(runSummary && !completedSuccessfully && "status" in runSummary