@mjasnikovs/pi-task 0.38.29 → 0.38.31

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 (373) hide show
  1. package/dist/config/config.d.ts +70 -70
  2. package/dist/config/config.js +26 -35
  3. package/dist/config/extension-list.d.ts +6 -5
  4. package/dist/config/extension-list.js +3 -2
  5. package/dist/config/reasoning-args.d.ts +9 -7
  6. package/dist/config/reasoning-args.js +12 -10
  7. package/dist/config/reasoning.d.ts +44 -105
  8. package/dist/config/reasoning.js +27 -704
  9. package/dist/config/register.d.ts +34 -48
  10. package/dist/config/register.js +41 -51
  11. package/dist/config/tool-list.d.ts +16 -16
  12. package/dist/config/tool-list.js +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/remote/bridge.d.ts +19 -10
  15. package/dist/remote/bridge.js +3 -2
  16. package/dist/remote/broadcast.js +3 -1
  17. package/dist/remote/events.js +12 -11
  18. package/dist/remote/history.d.ts +1 -1
  19. package/dist/remote/protocol.d.ts +6 -3
  20. package/dist/remote/protocol.js +2 -1
  21. package/dist/remote/push.d.ts +16 -16
  22. package/dist/remote/push.js +27 -27
  23. package/dist/remote/register.d.ts +3 -3
  24. package/dist/remote/register.js +17 -19
  25. package/dist/remote/server.d.ts +9 -8
  26. package/dist/remote/server.js +15 -14
  27. package/dist/remote/session-state.d.ts +5 -4
  28. package/dist/remote/session-state.js +8 -5
  29. package/dist/remote/sw.d.ts +7 -6
  30. package/dist/remote/sw.js +7 -6
  31. package/dist/remote/tailscale.d.ts +4 -2
  32. package/dist/remote/tailscale.js +4 -2
  33. package/dist/remote/ui-highlight.js +6 -5
  34. package/dist/remote/ui-render.js +4 -4
  35. package/dist/remote/ui-script.js +24 -24
  36. package/dist/remote/ui-styles.d.ts +1 -1
  37. package/dist/remote/ui-styles.js +10 -13
  38. package/dist/remote/ui-tools.js +9 -6
  39. package/dist/shared/child-extensions.d.ts +29 -17
  40. package/dist/shared/child-extensions.js +29 -17
  41. package/dist/shared/child-output.d.ts +30 -24
  42. package/dist/shared/child-output.js +25 -17
  43. package/dist/shared/child-process.d.ts +47 -40
  44. package/dist/shared/child-process.js +50 -59
  45. package/dist/shared/command-watchdog.d.ts +85 -16
  46. package/dist/shared/command-watchdog.js +115 -21
  47. package/dist/shared/fs-text.d.ts +16 -10
  48. package/dist/shared/fs-text.js +16 -10
  49. package/dist/shared/git-runner.d.ts +25 -25
  50. package/dist/shared/git-runner.js +25 -25
  51. package/dist/shared/leaked-tool-call.d.ts +17 -11
  52. package/dist/shared/leaked-tool-call.js +23 -15
  53. package/dist/shared/model-endpoint.d.ts +29 -16
  54. package/dist/shared/model-endpoint.js +33 -21
  55. package/dist/shared/pi-invocation.d.ts +7 -4
  56. package/dist/shared/pi-invocation.js +12 -7
  57. package/dist/shared/pkg-version.d.ts +13 -5
  58. package/dist/shared/pkg-version.js +13 -5
  59. package/dist/shared/reasoning-capability.d.ts +35 -24
  60. package/dist/shared/reasoning-capability.js +35 -24
  61. package/dist/shared/stream-watchdog.d.ts +60 -44
  62. package/dist/shared/stream-watchdog.js +62 -45
  63. package/dist/task/accept-debt.d.ts +41 -43
  64. package/dist/task/accept-debt.js +73 -65
  65. package/dist/task/api-synthesis.d.ts +24 -21
  66. package/dist/task/api-synthesis.js +32 -26
  67. package/dist/task/apis-contract.d.ts +32 -64
  68. package/dist/task/apis-contract.js +32 -64
  69. package/dist/task/artifact-closure.d.ts +27 -13
  70. package/dist/task/artifact-closure.js +95 -67
  71. package/dist/task/auto-commit.d.ts +46 -35
  72. package/dist/task/auto-commit.js +51 -38
  73. package/dist/task/auto-io.d.ts +45 -25
  74. package/dist/task/auto-io.js +57 -29
  75. package/dist/task/auto-orchestrator.d.ts +26 -24
  76. package/dist/task/auto-orchestrator.js +192 -165
  77. package/dist/task/auto-prompts.d.ts +36 -24
  78. package/dist/task/auto-prompts.js +40 -26
  79. package/dist/task/autofix-ledger.d.ts +27 -25
  80. package/dist/task/autofix-ledger.js +29 -26
  81. package/dist/task/batch-test-task.d.ts +20 -12
  82. package/dist/task/batch-test-task.js +67 -60
  83. package/dist/task/boot-probe.d.ts +60 -44
  84. package/dist/task/boot-probe.js +91 -72
  85. package/dist/task/cancel-input.d.ts +30 -16
  86. package/dist/task/cancel-input.js +20 -11
  87. package/dist/task/cancel-points.d.ts +27 -20
  88. package/dist/task/cancel-points.js +30 -22
  89. package/dist/task/child-runner.d.ts +124 -55
  90. package/dist/task/child-runner.js +298 -90
  91. package/dist/task/child-status.d.ts +23 -16
  92. package/dist/task/child-status.js +23 -16
  93. package/dist/task/clamp-output.js +12 -5
  94. package/dist/task/command-run.d.ts +31 -28
  95. package/dist/task/command-run.js +44 -35
  96. package/dist/task/command-shrink.d.ts +25 -18
  97. package/dist/task/command-shrink.js +37 -31
  98. package/dist/task/command-watchdog.d.ts +9 -6
  99. package/dist/task/command-watchdog.js +21 -15
  100. package/dist/task/context-attribution.d.ts +34 -26
  101. package/dist/task/context-attribution.js +34 -26
  102. package/dist/task/context-silence.d.ts +39 -29
  103. package/dist/task/context-silence.js +35 -25
  104. package/dist/task/context-usage.d.ts +16 -9
  105. package/dist/task/context-usage.js +16 -9
  106. package/dist/task/contracts.d.ts +8 -4
  107. package/dist/task/contracts.js +25 -17
  108. package/dist/task/coverage-loop.d.ts +22 -18
  109. package/dist/task/coverage-loop.js +35 -30
  110. package/dist/task/critique-probes.d.ts +13 -14
  111. package/dist/task/critique-probes.js +50 -39
  112. package/dist/task/debug-log.d.ts +13 -5
  113. package/dist/task/debug-log.js +32 -20
  114. package/dist/task/decompose-fidelity.d.ts +11 -9
  115. package/dist/task/decompose-fidelity.js +38 -33
  116. package/dist/task/decompose-granularity.d.ts +41 -38
  117. package/dist/task/decompose-granularity.js +41 -38
  118. package/dist/task/deep-render-check.d.ts +22 -14
  119. package/dist/task/deep-render-check.js +40 -31
  120. package/dist/task/dropped-input.d.ts +12 -7
  121. package/dist/task/dropped-input.js +5 -2
  122. package/dist/task/enforce-attribution.d.ts +38 -47
  123. package/dist/task/enforce-attribution.js +46 -52
  124. package/dist/task/enforce-guidelines.d.ts +31 -20
  125. package/dist/task/enforce-guidelines.js +32 -21
  126. package/dist/task/enrichment.d.ts +7 -2
  127. package/dist/task/enrichment.js +26 -14
  128. package/dist/task/env-notes.d.ts +16 -7
  129. package/dist/task/env-notes.js +48 -31
  130. package/dist/task/env-template-closure.d.ts +4 -4
  131. package/dist/task/env-template-closure.js +42 -34
  132. package/dist/task/external-context.d.ts +28 -21
  133. package/dist/task/external-context.js +17 -12
  134. package/dist/task/failure-classifier.d.ts +4 -5
  135. package/dist/task/failure-classifier.js +30 -8
  136. package/dist/task/file-inventory.d.ts +15 -11
  137. package/dist/task/file-inventory.js +25 -22
  138. package/dist/task/final-gate-fix.d.ts +74 -86
  139. package/dist/task/final-gate-fix.js +97 -116
  140. package/dist/task/final-gate-progress.d.ts +29 -46
  141. package/dist/task/final-gate-progress.js +40 -51
  142. package/dist/task/final-gate.d.ts +64 -97
  143. package/dist/task/final-gate.js +192 -199
  144. package/dist/task/fix-child.d.ts +21 -27
  145. package/dist/task/fix-child.js +21 -27
  146. package/dist/task/foreign-path.d.ts +6 -5
  147. package/dist/task/foreign-path.js +0 -0
  148. package/dist/task/frozen-conflict.d.ts +9 -10
  149. package/dist/task/frozen-conflict.js +61 -64
  150. package/dist/task/frozen-path-guard.d.ts +35 -14
  151. package/dist/task/frozen-path-guard.js +56 -39
  152. package/dist/task/gate-child.d.ts +27 -28
  153. package/dist/task/gate-child.js +36 -35
  154. package/dist/task/gate-deps.d.ts +34 -27
  155. package/dist/task/gate-deps.js +169 -159
  156. package/dist/task/gate-tally.d.ts +77 -80
  157. package/dist/task/gate-tally.js +65 -68
  158. package/dist/task/git-state-guard.d.ts +15 -11
  159. package/dist/task/git-state-guard.js +76 -66
  160. package/dist/task/impl-widget.d.ts +25 -16
  161. package/dist/task/impl-widget.js +27 -17
  162. package/dist/task/implementation-guards.d.ts +26 -0
  163. package/dist/task/implementation-guards.js +177 -0
  164. package/dist/task/implementation-thinking.d.ts +33 -31
  165. package/dist/task/implementation-thinking.js +5 -6
  166. package/dist/task/implementation-turn.d.ts +39 -31
  167. package/dist/task/implementation-turn.js +41 -28
  168. package/dist/task/inline-markdown.d.ts +20 -7
  169. package/dist/task/inline-markdown.js +15 -6
  170. package/dist/task/launch-config-gap.js +25 -39
  171. package/dist/task/launch-contract.d.ts +18 -21
  172. package/dist/task/launch-contract.js +28 -30
  173. package/dist/task/launch-manifest.d.ts +6 -2
  174. package/dist/task/launch-manifest.js +35 -34
  175. package/dist/task/ledger.js +16 -14
  176. package/dist/task/lint-fix.d.ts +6 -8
  177. package/dist/task/lint-fix.js +67 -69
  178. package/dist/task/loop-detector.d.ts +27 -8
  179. package/dist/task/loop-detector.js +38 -14
  180. package/dist/task/mid-run-input.d.ts +17 -15
  181. package/dist/task/mid-run-input.js +17 -15
  182. package/dist/task/orchestrator.d.ts +24 -28
  183. package/dist/task/orchestrator.js +89 -66
  184. package/dist/task/orientation.d.ts +18 -23
  185. package/dist/task/orientation.js +24 -31
  186. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  187. package/dist/task/owned-freeze-conflict.js +52 -85
  188. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  189. package/dist/task/owned-freeze-reassign.js +41 -61
  190. package/dist/task/parsers.d.ts +4 -2
  191. package/dist/task/parsers.js +4 -4
  192. package/dist/task/phases.d.ts +41 -48
  193. package/dist/task/phases.js +196 -252
  194. package/dist/task/plan-io.d.ts +6 -7
  195. package/dist/task/plan-io.js +6 -7
  196. package/dist/task/plan-orchestrator.d.ts +10 -8
  197. package/dist/task/plan-orchestrator.js +14 -10
  198. package/dist/task/plan-prompts.d.ts +6 -5
  199. package/dist/task/plan-prompts.js +6 -5
  200. package/dist/task/plan-readonly.d.ts +4 -5
  201. package/dist/task/plan-readonly.js +4 -5
  202. package/dist/task/plan-rounds.d.ts +17 -29
  203. package/dist/task/plan-rounds.js +21 -34
  204. package/dist/task/plan-session.d.ts +58 -72
  205. package/dist/task/plan-session.js +61 -83
  206. package/dist/task/probe-gaming.d.ts +28 -27
  207. package/dist/task/probe-gaming.js +0 -0
  208. package/dist/task/prohibition-probe.d.ts +14 -16
  209. package/dist/task/prompts.d.ts +3 -4
  210. package/dist/task/prompts.js +17 -26
  211. package/dist/task/qa-transcript.d.ts +15 -22
  212. package/dist/task/qa-transcript.js +15 -21
  213. package/dist/task/question-box.d.ts +17 -13
  214. package/dist/task/question-box.js +19 -15
  215. package/dist/task/question-dedup.d.ts +6 -7
  216. package/dist/task/question-dedup.js +13 -14
  217. package/dist/task/question-dialog.d.ts +22 -32
  218. package/dist/task/question-dialog.js +22 -32
  219. package/dist/task/question-source.d.ts +18 -44
  220. package/dist/task/question-source.js +22 -51
  221. package/dist/task/refuted-constraint.d.ts +11 -31
  222. package/dist/task/refuted-constraint.js +27 -51
  223. package/dist/task/regenerable-artifacts.d.ts +12 -31
  224. package/dist/task/regenerable-artifacts.js +12 -31
  225. package/dist/task/render-check.d.ts +11 -22
  226. package/dist/task/render-check.js +33 -46
  227. package/dist/task/repo-health-check.d.ts +10 -14
  228. package/dist/task/repo-health-check.js +17 -23
  229. package/dist/task/requirements.d.ts +38 -71
  230. package/dist/task/requirements.js +78 -126
  231. package/dist/task/research-fanout-budget.d.ts +51 -88
  232. package/dist/task/research-fanout-budget.js +51 -88
  233. package/dist/task/research-worker.d.ts +29 -39
  234. package/dist/task/research-worker.js +37 -61
  235. package/dist/task/resume-gap.d.ts +14 -15
  236. package/dist/task/root-cause-repair.d.ts +9 -9
  237. package/dist/task/root-cause-repair.js +28 -40
  238. package/dist/task/run-bracket.d.ts +10 -13
  239. package/dist/task/run-end.d.ts +12 -22
  240. package/dist/task/run-end.js +8 -16
  241. package/dist/task/run-final-gate.d.ts +19 -21
  242. package/dist/task/run-final-gate.js +62 -80
  243. package/dist/task/runner-globs.d.ts +12 -13
  244. package/dist/task/runner-globs.js +12 -13
  245. package/dist/task/runner-resolve.d.ts +9 -9
  246. package/dist/task/runner-resolve.js +22 -23
  247. package/dist/task/script-escape.d.ts +10 -12
  248. package/dist/task/script-escape.js +13 -14
  249. package/dist/task/serve-entry.d.ts +1 -1
  250. package/dist/task/serve-entry.js +22 -25
  251. package/dist/task/service-blocks.js +4 -2
  252. package/dist/task/shipped-source.d.ts +11 -29
  253. package/dist/task/shipped-source.js +11 -29
  254. package/dist/task/skip-escape.js +10 -14
  255. package/dist/task/spec-urls.d.ts +26 -65
  256. package/dist/task/spec-urls.js +26 -65
  257. package/dist/task/spec-validation.d.ts +17 -20
  258. package/dist/task/spec-validation.js +17 -20
  259. package/dist/task/stall-detector.d.ts +23 -30
  260. package/dist/task/stall-detector.js +23 -30
  261. package/dist/task/stream-watchdog.d.ts +14 -12
  262. package/dist/task/stream-watchdog.js +14 -12
  263. package/dist/task/substitution-probe.d.ts +17 -20
  264. package/dist/task/substitution-probe.js +17 -20
  265. package/dist/task/task-gates.d.ts +36 -41
  266. package/dist/task/task-gates.js +95 -106
  267. package/dist/task/task-io.d.ts +4 -4
  268. package/dist/task/task-io.js +4 -4
  269. package/dist/task/task-parsers.js +4 -3
  270. package/dist/task/task-provenance.d.ts +2 -2
  271. package/dist/task/task-provenance.js +11 -13
  272. package/dist/task/task-types.d.ts +4 -3
  273. package/dist/task/terminal-outcome.d.ts +14 -16
  274. package/dist/task/terminal-outcome.js +12 -14
  275. package/dist/task/test-assembly.d.ts +13 -20
  276. package/dist/task/test-assembly.js +13 -20
  277. package/dist/task/timings.d.ts +5 -3
  278. package/dist/task/timings.js +5 -3
  279. package/dist/task/title-label.d.ts +9 -4
  280. package/dist/task/title-label.js +9 -4
  281. package/dist/task/type-only-answer.d.ts +44 -52
  282. package/dist/task/type-only-answer.js +44 -52
  283. package/dist/task/unfailable-command.d.ts +18 -24
  284. package/dist/task/unfailable-command.js +21 -27
  285. package/dist/task/unknown-routing.d.ts +10 -4
  286. package/dist/task/unknown-routing.js +10 -4
  287. package/dist/task/user-directives.d.ts +5 -8
  288. package/dist/task/user-directives.js +5 -8
  289. package/dist/task/verify-quality.d.ts +18 -22
  290. package/dist/task/verify-quality.js +45 -46
  291. package/dist/task/verify-reconcile.d.ts +15 -10
  292. package/dist/task/verify-reconcile.js +45 -43
  293. package/dist/task/verify-resolution.d.ts +24 -20
  294. package/dist/task/verify-resolution.js +51 -50
  295. package/dist/task/verify-work.d.ts +59 -66
  296. package/dist/task/verify-work.js +101 -138
  297. package/dist/task/widget.d.ts +15 -14
  298. package/dist/task/widget.js +22 -17
  299. package/dist/task/wiring-claims.d.ts +25 -32
  300. package/dist/task/wiring-claims.js +30 -35
  301. package/dist/task/write-guard.d.ts +39 -39
  302. package/dist/task/write-guard.js +48 -51
  303. package/dist/task/yolo.d.ts +34 -30
  304. package/dist/task/yolo.js +42 -37
  305. package/dist/workers/abstention.d.ts +21 -41
  306. package/dist/workers/abstention.js +27 -48
  307. package/dist/workers/brave-search.d.ts +4 -3
  308. package/dist/workers/brave-search.js +5 -2
  309. package/dist/workers/brave-warning.d.ts +7 -4
  310. package/dist/workers/brave-warning.js +19 -7
  311. package/dist/workers/ddg-search.d.ts +6 -6
  312. package/dist/workers/ddg-search.js +18 -12
  313. package/dist/workers/docs-cache.js +5 -2
  314. package/dist/workers/docs-chunk.d.ts +30 -37
  315. package/dist/workers/docs-chunk.js +37 -41
  316. package/dist/workers/docs-core.d.ts +28 -44
  317. package/dist/workers/docs-core.js +25 -44
  318. package/dist/workers/docs-index.js +4 -3
  319. package/dist/workers/docs-lookup.d.ts +15 -22
  320. package/dist/workers/docs-lookup.js +12 -21
  321. package/dist/workers/docs-project.d.ts +15 -9
  322. package/dist/workers/docs-project.js +17 -10
  323. package/dist/workers/docs-resolve.d.ts +19 -20
  324. package/dist/workers/docs-resolve.js +35 -32
  325. package/dist/workers/docs-retrieve.d.ts +5 -6
  326. package/dist/workers/docs-retrieve.js +18 -15
  327. package/dist/workers/exa-search.d.ts +9 -6
  328. package/dist/workers/exa-search.js +23 -12
  329. package/dist/workers/fetch-core.d.ts +13 -16
  330. package/dist/workers/fetch-core.js +23 -23
  331. package/dist/workers/focused-extractor.d.ts +13 -12
  332. package/dist/workers/focused-extractor.js +27 -19
  333. package/dist/workers/html-clean.js +24 -14
  334. package/dist/workers/http-request.d.ts +28 -20
  335. package/dist/workers/http-request.js +22 -17
  336. package/dist/workers/npm-version.d.ts +28 -11
  337. package/dist/workers/npm-version.js +24 -15
  338. package/dist/workers/phantom-imports.d.ts +15 -12
  339. package/dist/workers/phantom-imports.js +30 -24
  340. package/dist/workers/pi-worker-core.d.ts +65 -96
  341. package/dist/workers/pi-worker-core.js +93 -181
  342. package/dist/workers/pi-worker-docs.d.ts +24 -19
  343. package/dist/workers/pi-worker-docs.js +67 -76
  344. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  345. package/dist/workers/pi-worker-fetch.js +27 -19
  346. package/dist/workers/pi-worker-search.js +12 -8
  347. package/dist/workers/pi-worker.d.ts +9 -4
  348. package/dist/workers/pi-worker.js +21 -14
  349. package/dist/workers/reasoning-warning.d.ts +18 -17
  350. package/dist/workers/reasoning-warning.js +22 -20
  351. package/dist/workers/research-cache.js +50 -78
  352. package/dist/workers/search-core.js +7 -5
  353. package/dist/workers/search-types.d.ts +10 -9
  354. package/dist/workers/search-types.js +9 -8
  355. package/dist/workers/session-hint.d.ts +13 -14
  356. package/dist/workers/session-hint.js +8 -9
  357. package/dist/workers/shared.d.ts +21 -25
  358. package/dist/workers/shared.js +0 -0
  359. package/dist/workers/single-read-extension.d.ts +14 -7
  360. package/dist/workers/single-read-extension.js +14 -7
  361. package/dist/workers/single-read-guard.d.ts +27 -30
  362. package/dist/workers/single-read-guard.js +36 -36
  363. package/dist/workers/typeonly-log.d.ts +12 -9
  364. package/dist/workers/typeonly-log.js +29 -33
  365. package/dist/workers/worker-channels.d.ts +15 -23
  366. package/dist/workers/worker-channels.js +15 -23
  367. package/dist/workers/worker-failure.d.ts +38 -46
  368. package/dist/workers/worker-failure.js +31 -39
  369. package/dist/workers/worker-kill.d.ts +25 -26
  370. package/dist/workers/worker-kill.js +16 -19
  371. package/dist/workers/worker-profiles.d.ts +54 -56
  372. package/dist/workers/worker-profiles.js +63 -39
  373. package/package.json +10 -8
@@ -23,8 +23,10 @@ import { REFINE_PROMPT, RESEARCH_FILES_PROMPT, RESEARCH_APIS_PROMPT, RESEARCH_CO
23
23
  import { appendGateRecord, readSection, removeTaskSection, setTaskSection, updateTaskFrontMatter } from './task-io.js';
24
24
  import { applyRefutations } from './refuted-constraint.js';
25
25
  import { spawnSync } from 'node:child_process';
26
+ import {} from './task-types.js';
26
27
  import { renderInlineMarkdown, stripInlineMarkdown } from './inline-markdown.js';
27
28
  import { isDuplicateQuestion, MAX_DUP_STRIKES, DUP_REPROMPT_HINT } from './question-dedup.js';
29
+ import {} from './widget.js';
28
30
  import { parseGrillQuestions, parseAutoAnswer, autoAnswerHasTag, parseVerifyToolingOutput, deriveTitle } from './parsers.js';
29
31
  import { compressTitle } from './title-label.js';
30
32
  import { parseVerifyBlock, validateSpecShape, validateRefineShape, stripSpecPreamble, isCritiqueClean } from './spec-validation.js';
@@ -36,7 +38,7 @@ import { readContracts, buildContractsBlock, buildContractsVerifyBlock } from '.
36
38
  import { readRequirements, buildRequirementsBlock, buildOwnedRequirementsBlock, readOwnedRequirements, writeOwnedRequirements, ownedForTitle, appendOwnedConstraints } from './requirements.js';
37
39
  import { detachUnsatisfiableRequirements, claimPendingRequirements, unclaimedPendingRequirements, formatReassignActions } from './owned-freeze-reassign.js';
38
40
  import { trackedSourceOracle } from './owned-freeze-conflict.js';
39
- import { thinkingForChild, runPhaseChild, runWithEmphasisRetry, prependHint, USER_CANCELLED } from './child-runner.js';
41
+ import { thinkingForChild, runPhaseChild, runWithEmphasisRetry, prependHint, USER_CANCELLED, CommandTimeoutError, isFatalChildCause } from './child-runner.js';
40
42
  import { runResearchWorker, researchWorkerCacheHeading } from './research-worker.js';
41
43
  import { SessionUI } from '../remote/bridge.js';
42
44
  import { isYoloMode, yoloPickAutoAnswer } from './yolo.js';
@@ -85,14 +87,12 @@ export function replaceToolingWithVerified(research, verifiedCommands) {
85
87
  }
86
88
  // ─── Phase functions ─────────────────────────────────────────────────────────
87
89
  // Authoritative directive that travels with the refine orientation block. Refine
88
- // runs BEFORE research, so on a "Scaffold project with package.json…" title it has
89
- // no signal the file already exists and authors greenfield strip-constraints
90
- // ("bun-plugin-tailwind the only dependency", "exactly N scripts") that compose
91
- // then obeys over the research facts the implementer executes a wholesale
92
- // rewrite that drops every existing dependency and script (mx5 TASK_0001 emptied
93
- // package.json; the REAL pipeline reproduced it 3/3 even with research surfacing
94
- // the deps). Handing refine the manifest/config CONTENT up front, with this
95
- // reframe, fixes it at the origin (A/B: 1/5 → 5/5 preserve, 5/5 end-to-end).
90
+ // is PHASE_ORDER[0] and research is [1], so on a "scaffold the project" title
91
+ // refine has no signal that the manifest already exists. It can then author
92
+ // greenfield strip-constraints ("X is the only dependency", "exactly N scripts")
93
+ // which compose obeys and the implementer rewrites the manifest from scratch,
94
+ // dropping every dependency and script already in it. Handing refine the
95
+ // manifest/config CONTENT up front, with this reframe, fixes it at the origin.
96
96
  const REFINE_PRESERVE_DIRECTIVE = 'EXISTING FILES ON DISK — AUTHORITATIVE (overrides any "scaffold / create / set '
97
97
  + 'up / initialize / from scratch / only / exactly / minimal" wording in the task '
98
98
  + 'below): the files shown in the PROJECT ORIENTATION block ALREADY EXIST. When '
@@ -146,14 +146,13 @@ export async function phaseContractsBlock(deps) {
146
146
  }
147
147
  /**
148
148
  * The carried-context blocks a GENERATIVE phase (refine, compose) receives: the
149
- * cross-slice contracts plus the carried cross-cutting requirements (mx5 run 11,
150
- * goals A/C — `.pi-tasks/requirements.md`, written at plan time). The verbatim
151
- * requirement quotes travel INTO every task's spec generation, so a mandated
152
- * methodology ("a test lands in the same change as each new route") reaches the
153
- * task's GOAL/CONSTRAINTS and its VERIFY a pointer back to the spec doc
154
- * recovered the dropped §10 in only 1 of ~6 applicable run-11 tasks; content
155
- * travels, pointers don't. Both blocks are '' outside their runs, so a bare
156
- * /task is byte-identical to before.
149
+ * cross-slice contracts plus the carried cross-cutting requirements
150
+ * (`.pi-tasks/requirements.md`, written at plan time). The verbatim requirement
151
+ * quotes travel INTO every task's spec generation, so a mandated methodology
152
+ * ("a test lands in the same change as each new route") reaches the task's
153
+ * GOAL/CONSTRAINTS and its VERIFY. Content travels; a pointer back to the spec
154
+ * doc does not. Both blocks are '' outside a /task-auto run, so a bare /task
155
+ * sees no change.
157
156
  */
158
157
  export async function phaseCarriedBlocks(deps) {
159
158
  const contracts = await phaseContractsBlock(deps);
@@ -162,13 +161,13 @@ export async function phaseCarriedBlocks(deps) {
162
161
  return [contracts, requirements, owned].filter(b => b.length > 0).join('\n');
163
162
  }
164
163
  /**
165
- * The owned (task-mapped) requirements for THIS task (mx5 run 16): matched by
164
+ * The owned (task-mapped) requirements for THIS task: matched by
166
165
  * the plan title the coverage map keyed them to, which is the task's stored
167
166
  * `raw prompt` section verbatim. Empty outside /task-auto runs, for spliced
168
- * repair tasks, and when the plan recorded no mapping — all of which degrade to
169
- * the pre-run-16 behavior. This is the BELT (prompt block, into refine +
170
- * compose); appendOwnedConstraints on the final spec is the BRACES the belt
171
- * alone folded the clause in only 2/8 live reps per fixture.
167
+ * repair tasks, and when the plan recorded no mapping — all of which make this a
168
+ * no-op. This is the BELT (a prompt block, into refine and compose);
169
+ * `appendOwnedConstraints` on the final spec is the BRACES. The belt is a request
170
+ * to the model and can be ignored; the braces are a host-side append and cannot.
172
171
  */
173
172
  async function ownedForThisTask(deps) {
174
173
  try {
@@ -196,17 +195,18 @@ function repoSourceOracle(cwd) {
196
195
  });
197
196
  }
198
197
  /**
199
- * DETACH (nexttask 2) — an owned requirement this task cannot satisfy, because a
198
+ * DETACH — an owned requirement this task cannot satisfy, because a
200
199
  * category freeze in the very spec that carries it covers the only file that
201
200
  * could, stops being this task's obligation and is released to whichever later
202
201
  * task writes that file.
203
202
  *
204
203
  * Runs at the LAST spec-producing step, where the pair first exists: the stamped
205
204
  * bullet is written one statement earlier by `appendOwnedConstraints`, and a
206
- * critique-time probe measured 0/40 because the stamp did not exist yet. It
207
- * never edits prose and never asks a model — the run-18 rewrite lever resolved
208
- * 11 of 20 pairs by DELETING the authoritative clause. The quote stays in the
209
- * ledger throughout; only its owner changes.
205
+ * critique-time probe would find nothing because the stamp does not exist yet.
206
+ * It never edits prose and never asks a model — a model asked to remove a
207
+ * contradiction can satisfy the request by DELETING the authoritative clause,
208
+ * which is not satisfaction of the requirement. The quote stays in the ledger
209
+ * throughout; only its owner changes.
210
210
  */
211
211
  export async function resolveOwnedFreezeForThisTask(deps, spec) {
212
212
  const ledger = await readOwnedRequirements(deps.cwd).catch(() => []);
@@ -230,16 +230,16 @@ export async function resolveOwnedFreezeForThisTask(deps, spec) {
230
230
  return res.spec;
231
231
  }
232
232
  /**
233
- * CLAIM (nexttask 2) — the other half. A requirement detached by an earlier task
233
+ * CLAIM — the other half. A requirement detached by an earlier task
234
234
  * becomes THIS task's own when its refined prompt says it writes the frozen
235
235
  * file. Run before compose builds its carried blocks, so the claimed obligation
236
236
  * rides the same belt every owned requirement does and the braces stamp it onto
237
237
  * this spec.
238
238
  *
239
- * The claimant is the only party that knows: at detach time the later tasks are
240
- * bare plan titles, and none of mx5 run 19's 26 titles contains the path. Over
241
- * the same run's 26 REFINED prompts, `writeIntent` picks out exactly the two
242
- * tasks that write the server file.
239
+ * The claimant is the only party that knows. At detach time the later tasks are
240
+ * bare plan titles, and a plan title names a slice of behaviour rather than a
241
+ * file. By its own compose a task has a REFINED prompt, which does say which
242
+ * files it will write and that is what `writeIntent` reads.
243
243
  */
244
244
  export async function claimOwnedFreezeForThisTask(deps, refined) {
245
245
  const ledger = await readOwnedRequirements(deps.cwd).catch(() => []);
@@ -259,24 +259,23 @@ export const phaseRefine = async (deps, raw, planContext) => {
259
259
  const contracts = await phaseCarriedBlocks(deps);
260
260
  // Imperative tool directives the user wrote into the RAW prompt ("via web
261
261
  // search", "fetch <url>"). Refine paraphrases the task and a weak model drops
262
- // these some of the time (mx5 run 9: "via web search" vanished, the whole run
263
- // made 0 search calls). Hand them to refine as a MUST-PRESERVE block (belt) and
262
+ // these some of the time, and a directive that vanishes at refine is gone from
263
+ // every later phase. Hand them to refine as a MUST-PRESERVE block (belt) and
264
264
  // re-check the output below (lever). Empty on an ordinary prompt → refine unchanged.
265
265
  const directives = extractUserDirectives(raw);
266
266
  const directivesBlock = preserveDirectivesBlock(directives);
267
267
  const refined = await runPhaseChild(deps, 'refine', 'read', REFINE_PROMPT(raw, planContext, existingFiles, contracts, directivesBlock),
268
268
  // refine's deliverable is a 4-section text rewrite that never strictly
269
- // needs a successful read on a test-writing task against a large
270
- // existing codebase the model over-explores (re-reads source hunting for
271
- // the impl) and burns the loop budget. Degrade to a no-tools final
272
- // attempt instead of hard-failing the whole run. See TASK_0016 (mx5):
273
- // refine looped 3×/resume forever; the deliverable was always producible
274
- // from the title + design doc alone.
269
+ // needs a successful read: it is producible from the title and the design
270
+ // doc alone. Against a large existing codebase the model can over-explore
271
+ // instead re-reading source hunting for the implementation and burn the
272
+ // loop budget. Degrade to a no-tools final attempt rather than hard-failing
273
+ // the whole run.
275
274
  { degradeOnExhaustion: true, verb: 'restart' });
276
275
  // Shape check, REPORTED not enforced. Refine's four sections are what
277
276
  // extractCapsSection, scopedToolingGoal, deriveTitle and extractEnrichTargets
278
- // each look for, and all four fail SILENTLY when one is missing so the loss
279
- // was previously invisible. Deliberately not a retry or a throw: refine's
277
+ // each look for, and all four fail SILENTLY when one is missing, so without
278
+ // this line the loss leaves no trace. Deliberately not a retry or a throw: refine's
280
279
  // output is usable prose even when a heading is gone (the four consumers
281
280
  // degrade, they do not break), and a run must not die over a heading. This
282
281
  // puts the miss in the debug log where an audit can find it.
@@ -302,7 +301,16 @@ export async function phaseVerifyTooling(deps, research) {
302
301
  try {
303
302
  verifyOutput = await runPhaseChild(deps, 'verify-tooling', 'read,bash', VERIFY_TOOLING_PROMPT(toolingList));
304
303
  }
305
- catch {
304
+ catch (e) {
305
+ if (isFatalChildCause(e))
306
+ throw e;
307
+ // The fallback ships the tooling list UNVERIFIED, which is the right
308
+ // degrade for a child that merely failed. A hung command is different: it
309
+ // cost the ceiling on every strike and says the SPEC named something
310
+ // unbounded, so it is the one cause worth a trail line rather than silence.
311
+ if (e instanceof CommandTimeoutError) {
312
+ deps.logDebug?.(`verify-tooling: ${e.message} — shipping the list unverified`);
313
+ }
306
314
  return replaceToolingWithVerified(research, commands);
307
315
  }
308
316
  const parsed = parseVerifyToolingOutput(verifyOutput);
@@ -316,10 +324,10 @@ export async function phaseVerifyTooling(deps, research) {
316
324
  * The worker channels the APIS research worker is given.
317
325
  *
318
326
  * `pi-worker-search` + `pi-worker-fetch` ride along only when the configured
319
- * engine is usable (a keyless engine always is; brave needs its key). A tool
320
- * without a key just errors, and a weak model burns calls on it — while search
321
- * being ABSENT was structural in the other direction: three consecutive audited
322
- * runs made 0 search calls because the child literally did not have the tool.
327
+ * engine is usable. `searchConfigured` decides: exa and ddg are keyless and
328
+ * always usable, brave is usable only with its key. A keyless tool that errors
329
+ * on every call is something a weak model burns calls on; a tool the child was
330
+ * never handed is a channel it cannot use at all, however the prompt is worded.
323
331
  *
324
332
  * Both halves of "given a channel" — the tools string and the `-e` path — come
325
333
  * from the same rows, so they cannot disagree.
@@ -335,10 +343,10 @@ export function apisWorkerChannels() {
335
343
  * ddg) always are; only brave needs its API key.
336
344
  */
337
345
  export function searchConfigured(getEnv = k => process.env[k], provider = getConfig().searchProvider) {
338
- // Asks the SAME row `search()` asks. This used to re-state brave's env pair
339
- // under a comment saying it "mirrors search-core's lookup" — two statements of
340
- // one fact, and the one that decides whether the APIS worker is even handed the
341
- // search tool.
346
+ // Goes through `searchProviderKey`, the same lookup `search()` itself uses.
347
+ // Re-stating brave's env pair here would make two statements of one fact, and
348
+ // this is the one that decides whether the APIS worker is handed the search
349
+ // tool at all.
342
350
  return searchProviderKey(provider, getEnv) !== null;
343
351
  }
344
352
  /** Extra prompt block for the APIS worker when search is available — trigger-framed
@@ -353,9 +361,9 @@ export const RESEARCH_SEARCH_HINT = '\n\nLIVE WEB — use pi-worker-search for e
353
361
  * In-process guards loaded into the TOOLING worker only: block a re-read of any
354
362
  * file already read, and block any byte-identical grep/find/ls repeat, feeding
355
363
  * the model "you already have this, answer now" instead of letting it re-run.
356
- * TOOLING reads each file once and never needs an identical search twice in any
357
- * healthy recorded run, so neither rule has a legitimate false positive here.
358
- * See single-read-guard.ts.
364
+ * TOOLING's job is to name verification commands, so it has no reason to read a
365
+ * file twice or repeat a search byte for byte which is what makes both rules
366
+ * safe to arm HERE and nowhere else. See single-read-guard.ts.
359
367
  */
360
368
  export const SINGLE_READ_EXTENSION_PATH = fileURLToPath(new URL('../workers/single-read-extension.js', import.meta.url));
361
369
  /**
@@ -363,15 +371,13 @@ export const SINGLE_READ_EXTENSION_PATH = fileURLToPath(new URL('../workers/sing
363
371
  * cares about — never the per-file edit list. Big refined prompts embed a long
364
372
  * bulleted "fix these files" checklist *inside* the GOAL block; handing that to
365
373
  * a weak local model drags it into reading/grepping source it doesn't need and
366
- * it loops (TASK_0017: read(sql-adapter.ts) ×5 → loop-kill → fails the phase).
374
+ * it loops.
367
375
  * So scope TOOLING's view to the GOAL prose, truncated at the first bullet.
368
376
  *
369
377
  * Fallbacks, in order: no bare `GOAL` header (free-form refined) → the whole
370
378
  * refined unchanged; a GOAL block with no bullets → the full GOAL block. The
371
379
  * GOAL boundary is the next bare ALL-CAPS header (CONSTRAINTS, KNOWN-UNKNOWNS,
372
- * …) or end of text. Verified against every recorded refined prompt: the
373
- * bullet-heavy failure case drops 3319→329 chars with zero source-file
374
- * mentions; the rest are unchanged or only lightly trimmed.
380
+ * …) or end of text.
375
381
  */
376
382
  export function scopedToolingGoal(refined) {
377
383
  const m = /^GOAL[ \t]*\n([\s\S]*?)(?=\n[A-Z][A-Z][A-Z -]*[ \t]*\n|$(?![\s\S]))/m.exec(refined);
@@ -400,8 +406,8 @@ async function manifestDependencyNames(cwd) {
400
406
  /**
401
407
  * Prepended to worker:apis's prompt on the ONE retry the zero-retrieval gate triggers. It
402
408
  * names the exact failure (a section written with no retrieval) so the correction is concrete,
403
- * and bounds the retrieval to the symbols about to be listed — a broad "read everything" here
404
- * would trade the memory-written section for the 37-read near-runaway at phases.ts's read tail.
409
+ * and bounds the retrieval to the symbols about to be listed — a broad "read everything"
410
+ * here would trade a memory-written section for a read runaway.
405
411
  */
406
412
  const APIS_ZERO_RETRIEVAL_PREAMBLE = 'STOP. Your previous attempt at this task wrote a complete APIS section without calling a '
407
413
  + 'single retrieval tool — so every signature, type, and command in it was recalled from '
@@ -412,12 +418,11 @@ const APIS_ZERO_RETRIEVAL_PREAMBLE = 'STOP. Your previous attempt at this task w
412
418
  + 'list — no more, no less; do not read the whole tree.';
413
419
  /**
414
420
  * Prepended to worker:context's prompt on the ONE retry the silent-retry gate triggers. The
415
- * previous attempt produced ZERO bullets STEP 0 (context-silence.ts) showed every such rep
416
- * across 48 live reps was a genuine loss (a loop-degrade or a hallucinated non-bullet
417
- * fragment), never a legitimate empty answer, because the same tree reliably yields 11–21
418
- * bullets. So this names that failure and steers away from the two shapes that caused it:
419
- * the repeated-grep thrash that trips the loop-killer, and emitting anything that is not a
420
- * bullet. It does NOT loosen the sourced-bullet invariant — it explicitly repeats that
421
+ * previous attempt produced ZERO bullets, which `classifyContextSilence`
422
+ * (context-silence.ts) has already judged a genuine loss rather than an honest empty
423
+ * answer. So this names that failure and steers away from the two shapes that cause it:
424
+ * the repeated-grep thrash that trips the loop-killer, and emitting anything that is not
425
+ * a bullet. It does NOT loosen the sourced-bullet invariant it explicitly repeats that
421
426
  * external-API semantics stay open questions unless quoted.
422
427
  */
423
428
  const CONTEXT_SILENT_RETRY_PREAMBLE = 'STOP. Your previous attempt at this task produced ZERO usable bullets — either it thrashed '
@@ -448,15 +453,12 @@ export async function phaseResearch(deps, refined) {
448
453
  // repo; purely additive (nothing is blocked) so it can only remove a
449
454
  // redundant read, never hide a file.
450
455
  //
451
- // Applied to FILES and APIS only — NOT CONTEXT/TOOLING. Verified with a live
452
- // A/B on the local model (real pi, real repo): FILES and APIS are bimodal
453
- // they sometimes answer from the inventory but sometimes spiral into heavy
454
- // reads (APIS hit 37 reads / 221s, a near-runaway), and pre-supplying the core
455
- // collapses that to 0 reads / ~3.5s with the model honoring "do not re-read"
456
- // (0 core re-reads in every ON run). CONTEXT works from inventory+grep and
457
- // reads ~0 files regardless, so the block was pure prefill and made it slower
458
- // in 5/5 reps; TOOLING is already scoped + single-read-guarded and saw no
459
- // benefit. So orientation only goes where reads actually happen.
456
+ // Applied to FILES and APIS only — NOT CONTEXT/TOOLING, and the split is by
457
+ // whether the worker reads at all. FILES and APIS explore by reading, so
458
+ // pre-supplying the core replaces reads they would otherwise make. CONTEXT
459
+ // works from the inventory and grep, and TOOLING is scoped to the GOAL prose
460
+ // and single-read-guarded, so for those two the block is pure prefill with no
461
+ // read to displace. Orientation only goes where reads actually happen.
460
462
  const orientationPaths = getConfig().orientation && inventoryRaw.length > 0 ?
461
463
  inventoryRaw.split('\n').filter(l => l.trim().length > 0)
462
464
  : [];
@@ -476,25 +478,20 @@ export async function phaseResearch(deps, refined) {
476
478
  // Judged against the EXTERNAL CONTEXT this run actually gathered — the same string
477
479
  // the worker is handed below — and the manifest's dependency names.
478
480
  const manifestPackages = await manifestDependencyNames(deps.cwd);
479
- // PROMPT 4's spec-cited-URL lever is NOT WIRED HERE. It is built and unit-tested in
480
- // ./spec-urls.ts and its live A/B FAILED: baseline 2/20 vs treatment 3/20, Fisher
481
- // one-tailed p = 0.50, over two fixtures, with delivery into this very prompt proven
482
- // separately (scripts/spec-url-prompt-delivery-check.ts). Shipping it anyway would put
483
- // ~1000 characters of prefill into every APIS prompt for no measured benefit, which is
484
- // the pattern nexxtasks exists to prevent. To re-run the experiment, restore the block
485
- // this comment replaces see the git history of this file and the PROMPT 4 entry in
486
- // nexxtasks.txt RESULTS.
487
- // nexttask 5B fan-out bounds. All four read their env ONCE per research
488
- // phase, so every worker in a run sees the same policy and a harness cannot
489
- // half-apply an arm. CAP, SCALE and carry-forward are null/false in the
490
- // shipped configuration; the progress deadline shipped ON (nexttask 9).
481
+ // The spec-cited-URL lever from ./spec-urls.ts is built and unit-tested but is
482
+ // NOT WIRED HERE: pointing the worker at a page it should have read did not
483
+ // change what it produced, so the block would be prefill on every APIS prompt
484
+ // for nothing.
485
+ // Research fan-out bounds, read from the environment ONCE per research phase,
486
+ // so every worker in a run sees the same policy and a harness cannot half-apply
487
+ // one. In the shipped configuration the budget knobs are unset and the progress
488
+ // deadline is on.
491
489
  const leverEnv = snapshotLeverEnv();
492
490
  const fanoutBudget = projectDocsBudget(leverEnv);
493
491
  const progressCeilingMs = workerProgressCeilingMs(leverEnv);
494
- // Which deadline policy was in force is a fact about how every number below
495
- // was produced. Run 18's 120 discarded minutes were only recoverable because
496
- // 5A started writing down what the workers actually did; a run whose logs do
497
- // not say which policy it ran under cannot be compared with one that does.
492
+ // Which deadline policy was in force decides what every worker timing below
493
+ // means, and the two policies are not comparable. Logging it is what makes a
494
+ // trail readable after the fact.
498
495
  deps.logDebug?.(progressCeilingMs === null ?
499
496
  'phase:research: worker deadline = fixed elapsed cap (progress deadline DISABLED)'
500
497
  : `phase:research: worker deadline = no-progress, ceiling ${progressCeilingMs}ms`);
@@ -512,10 +509,9 @@ export async function phaseResearch(deps, refined) {
512
509
  // wall-clock-relative (queueing shows up in waitMs).
513
510
  //
514
511
  // Restarted attempts get their OWN row. Without one the widget contradicts
515
- // itself: mx5 run 18 printed `workers 722.2s` over a longest member reading
516
- // `worker:apis work 239.3s`, because wait/work describe the final attempt
517
- // while the phase clock counts all three. The discarded time is the whole
518
- // gap, so naming it is what closes the widget.
512
+ // itself: waitMs and workMs describe only the FINAL attempt while the phase
513
+ // clock counts every attempt, so the phase total exceeds its longest member by
514
+ // the time the discarded attempts took. Naming that gap is what reconciles them.
519
515
  const recordWorker = (label, p) => p.then(r => {
520
516
  deps.recordSubStep?.(`${label} wait`, r.waitMs);
521
517
  deps.recordSubStep?.(`${label} work`, r.workMs);
@@ -524,33 +520,15 @@ export async function phaseResearch(deps, refined) {
524
520
  }
525
521
  return r;
526
522
  });
527
- // Run the four workers ONE AT A TIME. Settled by an A/B on the local
528
- // llama.cpp backend (single GPU, same task/model) and the answer FLIPS
529
- // with thinking:
530
- // - thinking ON → parallel wins: long decodes batch well, 4 concurrent
531
- // finish in ~max(worker), not the sum.
532
- // - thinking OFF sequential wins: with short decodes the batching upside
533
- // is gone, but 4 concurrent streams still split the one GPU and slow
534
- // each other ~4x (context worker measured 27s solo vs 128s under load),
535
- // so summed-but-fast (~100s) beats max-of-slowed (~130s).
536
- //
537
- // KNOWN-OPEN, AND THIS IS THE HONEST STATE OF IT. That A/B was run when
538
- // every worker carried Qwen3's `/no_think` prompt suffix, so "thinking OFF"
539
- // was assumed and sequential followed. The suffix has since been measured
540
- // INERT — with server thinking on and `/no_think` still in the prompt,
541
- // Qwen3.8 emitted a median 17k-char trace anyway (n=25) — and it has been
542
- // removed in favour of the `research` reasoning group (config/reasoning.ts).
543
- // So the arm this default was chosen under may never have been the arm that
544
- // ran, and the group's level is now a user-visible setting rather than a
523
+ // Run the four workers ONE AT A TIME by default. Which order wins depends on
524
+ // the backend: concurrent streams share one local GPU and slow each other
525
+ // down, so the sum of four fast workers can beat the max of four slowed ones,
526
+ // while a backend that genuinely serves parallel streams has no such tradeoff.
527
+ // `parallelResearchWorkers` is the opt-in for those backends. The worker's
528
+ // reasoning level (config/reasoning.ts `research` group) changes decode length
529
+ // and so changes the answer too, which is why this is a config knob and not a
545
530
  // constant.
546
531
  //
547
- // The default stays SEQUENTIAL because that is the measured-safe arm on a
548
- // single-GPU box and because flipping a shipped default on an invalidated
549
- // premise would be replacing one unmeasured claim with another. The
550
- // parallel x reasoning-level interaction is unmeasured; re-run the A/B
551
- // before changing this, and treat `parallelResearchWorkers` as the opt-in
552
- // for backends that genuinely serve parallel streams.
553
- //
554
532
  // Result order (files, apis, context, tooling) is preserved for assembly.
555
533
  // Resolved once: `searchConfigured()` reads the environment, and the tools
556
534
  // string and the `-e` paths must be derived from the SAME answer.
@@ -568,46 +546,38 @@ export async function phaseResearch(deps, refined) {
568
546
  // Read-heavy: gets the orientation core (see note above). Search/fetch
569
547
  // ride along only when a Brave key exists — see SEARCH_EXTENSION_PATH.
570
548
  // FILES' finished map rides along when available (serial default), so
571
- // the worker doesn't re-derive where-things-live via docs-"."
572
- // queries the FILES worker just answered (run-7 F7: up to 10
573
- // duplicate `.`-decodes per task through the serial bottleneck).
549
+ // the worker doesn't re-derive where-things-live through project-docs
550
+ // queries the FILES worker just answered.
574
551
  prompt: prior => orientation.block
575
552
  + promptHeader
576
553
  + RESEARCH_APIS_PROMPT(refined, prior.find(s => s.name === 'FILES')?.text || undefined)
577
554
  + (searchConfigured() ? RESEARCH_SEARCH_HINT : '')
578
- // 5B CAP arm empty unless PI_TASK_PROJECT_DOCS_BUDGET is
579
- // set. The tool-side half lives in pi-worker-docs.ts; a
580
- // budget enforced without being announced would just read
581
- // to the worker as a broken tool.
555
+ // Empty unless PI_TASK_PROJECT_DOCS_BUDGET is set. The tool-side
556
+ // half lives in pi-worker-docs.ts; a budget enforced without being
557
+ // announced would just read to the worker as a broken tool.
582
558
  + (fanoutBudget === null ? '' : projectDocsBudgetNotice(fanoutBudget)),
583
559
  // The tools string and the `-e` paths are ONE fact — which worker
584
- // channels this research worker is given — and used to be two literals
560
+ // channels this research worker is given — and would otherwise be two literals
585
561
  // kept in step by eye. `channelSet` derives both from the same rows.
586
562
  tools: `read,grep,find,ls,${apisChannels.tools}`,
587
563
  fanoutBounded: true,
588
564
  extensions: apisChannels.extensions,
589
- // ZERO-RETRIEVAL GATE (mx5 run-15 F-1, distinct from the STAGE 1-3 stopping-point
590
- // thread). In a MINORITY of reps worker:apis emits a complete, plausible APIS section
591
- // having made ZERO retrieval tool calls the whole thing recalled from memory.
592
- // The output contract at RESEARCH_APIS_PROMPT already INSTRUCTS tool use and the
593
- // worker skips it anyway (STAGE 2 proved a prompt line does not move grounding), so
594
- // this is a deterministic gate, not another instruction: groundingRetrievalCount === 0
595
- // on a non-empty section is ungrounded BY CONSTRUCTION no semantic judgement, the
596
- // exact checkable handle STAGE 3's prose-clause gate lacked. The forced-retrieval
597
- // retry recovers a grounded section rather than silencing the worker (entry count
598
- // preserved). It bounds itself ("look up the symbols you will list — no more") away
599
- // from the near-runaway 37-read tail.
565
+ // ZERO-RETRIEVAL GATE. worker:apis can emit a complete, plausible APIS
566
+ // section having made ZERO retrieval tool calls the whole thing recalled
567
+ // from memory. RESEARCH_APIS_PROMPT already instructs tool use, so another
568
+ // instruction is not the answer; this is a deterministic gate instead.
569
+ // `groundingRetrievalCount === 0` on a NON-EMPTY section is ungrounded by
570
+ // construction, with no semantic judgement needed. The forced-retrieval
571
+ // retry recovers a grounded section rather than silencing the worker (the
572
+ // original is kept if the retry still retrieves nothing, so the entry count
573
+ // cannot collapse), and it bounds itself "look up the symbols you will
574
+ // list no more" away from a read runaway.
600
575
  //
601
- // EFFICACY NOT DEMONSTRATED read before trusting this to matter. The live A/B
602
- // (scripts/live-apis-zero-retrieval-ab.ts) ABSTAINED, underpowered: the failure is
603
- // RARE and intermittent (base rate 0/40 one session, pooled ~5%), so the primary
604
- // reduction (baseline 1/40 zero-retrieval ships -> treatment 0/40) did NOT reach
605
- // significance (Fisher p = 0.50 — needs ~5 baseline ships, ~85 reps at ~6%). What IS
606
- // established: the gate is correct BY CONSTRUCTION; on the 3 firings observed it
607
- // recovered a grounded section every time; and it did NO harm (ungrounded-symbol rate
608
- // went DOWN not up, no entry collapse, no runaway, cost +4-6%). It is wired as a
609
- // harmless safety net, NOT a proven-effective lever — do not cite it as a measured win
610
- // (nexxtasks.txt "ZERO-RETRIEVAL GATE ... ABSTAIN"). A powered A/B is STILL OPEN.
576
+ // A SAFETY NET, NOT A DEMONSTRATED WIN. The failure it catches is
577
+ // intermittent, and nothing here establishes how often it fires or what it
578
+ // is worth. What the code does establish is that it cannot make things
579
+ // worse: it only ever replaces a zero-retrieval section with a retrieving
580
+ // one.
611
581
  zeroRetrievalRetry: APIS_ZERO_RETRIEVAL_PREAMBLE
612
582
  },
613
583
  {
@@ -619,30 +589,26 @@ export async function phaseResearch(deps, refined) {
619
589
  // spawning long enumeration loops whose output then inflates
620
590
  // prefill on every subsequent round.
621
591
  //
622
- // RECORDED DECISION (mx5 run-15 F-1, PROMPT 1 item 4): this worker stays
623
- // ISOLATED it is NOT given the APIS worker's output. It keeps `read,grep`
624
- // and is forbidden, by prompt and by the post-check below, from asserting
625
- // external-API behaviour it cannot see. Handing it the APIS section would
626
- // widen what it may assert without making any of it checkable here, and
627
- // APIS' own answers are the ones F-2 shows are type-only and unverified.
592
+ // ISOLATED on purpose: this worker is NOT given the APIS worker's output.
593
+ // It keeps `read,grep` and is forbidden by prompt and by the post-check
594
+ // below from asserting external-API behaviour it cannot see. Handing it
595
+ // the APIS section would widen what it may assert without making any of it
596
+ // checkable here.
628
597
  tools: 'read,grep',
629
- // SILENT-RETRY GATE. STEP 0 measured this worker silent (zero bullets) in ~10%
630
- // of live reps (5/48, Wilson95 [4.5%, 22.2%]) and classified EVERY silent rep as
631
- // a genuine lossa loop-degrade banner (60%) or a hallucinated non-bullet
632
- // fragment (40%) never a legitimate empty answer, because the identical fixture
633
- // reliably yields 11–21 bullets. A silent section is therefore a dropped section;
634
- // retry once, keep the retry only if it emits bullets. See context-silence.ts.
598
+ // SILENT-RETRY GATE. This worker can come back with zero bullets. The two
599
+ // causes `classifyContextSilence` recognises a loop-degrade banner, and a
600
+ // hallucinated non-bullet fragmentare both dropped sections, not honest
601
+ // empty answers, so retry once and keep the retry only if it emits bullets.
602
+ // See context-silence.ts.
635
603
  retryIfSilent: CONTEXT_SILENT_RETRY_PREAMBLE,
636
- // BRACES for the LIVE-DATA RULE. In run 15 this worker wrote, verbatim, "The
637
- // `hono` dependency is pinned at `^4.12.31` in package.json, and the external
638
- // context confirms `hc<AppType>` pattern with base URL `/api` ... works
639
- // correctly (per Hono RPC docs LIVE data)". It has read+grep only, so the
640
- // base-URL half came from memory; fused with the true version half under one
641
- // attribution it read as sourced, became a hard requirement in TASK_0027's
642
- // CONSTRAINTS and ACCEPTANCE, and every request went to /api/api/... 404.
643
- // A flagged bullet is demoted to an OPEN QUESTION here before the section is
644
- // persisted — so it cannot reach compose as fact. Demotion, not deletion: the
645
- // bullet count is preserved, because a silenced worker is a different
604
+ // BRACES for the LIVE-DATA RULE (the belt is the prompt). This worker has
605
+ // `read,grep` only, so it cannot have read a doc yet it can write one
606
+ // bullet that fuses a fact it DID read from the manifest with an external-API
607
+ // claim it did not, under a single "per the docs" attribution. Fused that
608
+ // way the whole bullet reads as sourced and can become a hard CONSTRAINT.
609
+ // A flagged bullet is demoted to an OPEN QUESTION here — before the section
610
+ // is persisted so it cannot reach compose as fact. Demotion, not deletion:
611
+ // the bullet count is preserved, because a silenced worker is a different
646
612
  // regression.
647
613
  postProcess: text => {
648
614
  const r = demoteUnsourcedAttributions(text, externalContext, manifestPackages);
@@ -684,10 +650,10 @@ export async function phaseResearch(deps, refined) {
684
650
  taskId: deps.taskId,
685
651
  signal: deps.signal,
686
652
  spawn: deps.spawn,
687
- // ONE CELL PER WORKER since 2026-08-28. The four workers used to
688
- // share the `research` cell on the grounds that they are the same
689
- // job over four questions; the run logs disagree. The cells DO NOT
690
- // ship identical the evidence is on each of them in reasoning.ts.
653
+ // ONE CELL PER WORKER. The four are the same job over four
654
+ // questions, so one shared `research` cell is the tempting shape
655
+ // but the four cells do not ship identical, so sharing one would
656
+ // silently change three of them. See config/reasoning.ts.
691
657
  thinkingFor: thinkingForChild,
692
658
  logDebug: deps.logDebug,
693
659
  onChildOutput: deps.onChildOutput,
@@ -699,7 +665,7 @@ export async function phaseResearch(deps, refined) {
699
665
  }, prior);
700
666
  const sections = [];
701
667
  if (!getConfig().parallelResearchWorkers) {
702
- // Default: ONE AT A TIME (see the A/B note above the specs) — a fatal
668
+ // Default: ONE AT A TIME (see the note above the worker specs) — a fatal
703
669
  // failure throws before later workers run, and each worker can see the
704
670
  // finished sections before it (APIS builds on the FILES map).
705
671
  for (const spec of workerSpecs) {
@@ -711,8 +677,8 @@ export async function phaseResearch(deps, refined) {
711
677
  // worker runs to its own outcome first, so one fatal failure cannot
712
678
  // orphan the others' output — their sections persist for the resume
713
679
  // before the failure is thrown. Assembly order stays the spec order
714
- // regardless of completion order. No prior sections exist here, so
715
- // prompt builders get none (APIS runs map-less, as before this option).
680
+ // regardless of completion order. No prior sections exist here, so prompt
681
+ // builders get none APIS runs without the FILES map it gets when serial.
716
682
  const settled = await Promise.allSettled(workerSpecs.map(spec => drive(spec, [])));
717
683
  for (const s of settled) {
718
684
  if (s.status === 'rejected')
@@ -784,7 +750,7 @@ export async function phaseAutoAnswer(deps, refined, research, question) {
784
750
  text = await runPhaseChild(deps, 'grill-auto', 'read', prependHint(GRILL_AUTO_FORMAT_HINT, basePrompt));
785
751
  }
786
752
  let parsed = parseAutoAnswer(text);
787
- // Anti-synthesis guard (mx5 run 13, Bug A): the auto-answer invented
753
+ // Anti-synthesis guard: the auto-answer invented
788
754
  // `Bun.mkdirSync` while research's APIS section carried the correct list,
789
755
  // and the invention was promoted into requirements + VERIFY. Deterministic
790
756
  // verbatim-substring check: an API-shaped identifier in the answer that is
@@ -804,7 +770,9 @@ export async function phaseAutoAnswer(deps, refined, research, question) {
804
770
  if (autoAnswerHasTag(text2))
805
771
  reasked = parseAutoAnswer(text2);
806
772
  }
807
- catch {
773
+ catch (e) {
774
+ if (isFatalChildCause(e))
775
+ throw e;
808
776
  reasked = null;
809
777
  }
810
778
  if (reasked === null
@@ -859,8 +827,8 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
859
827
  const ui = new SessionUI(ctx);
860
828
  // ONE record, two renderings (task/qa-transcript.ts): `forRecord()` is what
861
829
  // compose and critique are handed, `forGenerator()` is what the next grill-gen
862
- // call sees. The provenance rule below used to be a comment 12 lines under a
863
- // push that broke it.
830
+ // call sees. The two differ only in provenance suffixes, which is why they can
831
+ // drift apart unnoticed.
864
832
  const transcript = new QaTranscript(GRILL_QA_POLICY);
865
833
  const askedQuestions = []; // plain text of each question, for the dup backstop
866
834
  // Deterministic backstop against a model that ignores "never re-ask": a
@@ -953,8 +921,6 @@ export async function phaseGrill(deps, ctx, widgetState, refined, research) {
953
921
  * the same place. The task file's `## refined prompt` is deliberately left as
954
922
  * refine wrote it; the drop is recorded on the `## gates` trail with both source
955
923
  * lines quoted, so the decision stays auditable after the fact.
956
- *
957
- * STEP 0 `scripts/refuted-constraint-baserate.ts`; A/B-1 `…-ab.ts` (PASS).
958
924
  */
959
925
  export async function dropRefutedConstraints(deps, refined, research) {
960
926
  const refuted = applyRefutations(refined, research);
@@ -1019,17 +985,15 @@ export async function phaseCritique(deps, spec, refined, qa, planContext, resear
1019
985
  * An additional deterministic defect block, forced into the rewrite exactly
1020
986
  * like the probes below and overriding a CLEAN triage the same way.
1021
987
  *
1022
- * This is the A/B seam for a probe that is not wired yet: the discipline
1023
- * here is "wire only on PASS" (memory/prompt4-spec-urls-failed.md), so a
1024
- * candidate probe has to be measurable through the SHIPPED critique path
1025
- * rather than through a hand-copied replica of it, or the two arms differ by
1026
- * more than the probe. Undefined in production.
988
+ * The seam exists so a candidate probe can be exercised through the SHIPPED
989
+ * critique path rather than through a hand-copied replica of it — otherwise a
990
+ * comparison differs by more than the probe. Undefined in production.
1027
991
  */
1028
992
  extraDefects) {
1029
993
  // Fast triage before the expensive full rewrite. The rewrite regenerates
1030
- // the entire spec from scratch and is the costliest tail of the pipeline
1031
- // (observed up to ~240s). Most compose drafts are already good, so we first
1032
- // ask a cheap, short-output triage pass whether a rewrite is even needed.
994
+ // the entire spec from scratch, which makes it the most expensive step in the
995
+ // pipeline. Most compose drafts are already good, so we first ask a cheap,
996
+ // short-output triage pass whether a rewrite is even needed.
1033
997
  //
1034
998
  // We only short-circuit when the draft already has a runnable VERIFY block
1035
999
  // (parseVerifyBlock !== null): the final handoff gate rejects specs without
@@ -1041,12 +1005,12 @@ extraDefects) {
1041
1005
  // model does not self-discover reliably, each forced into the rewrite and each
1042
1006
  // overriding a CLEAN triage. They live as rows in CRITIQUE_PROBES
1043
1007
  // (critique-probes.ts) so the override and the merge below are DERIVED from
1044
- // the table rather than retyped — a probe used to be listed by hand in three
1008
+ // the table rather than retyped — a probe would otherwise be listed by hand in three
1045
1009
  // places, and forgetting the override term shipped the very defect it was
1046
1010
  // added to catch.
1047
1011
  //
1048
1012
  // The contract registry is read here rather than inside the table because the
1049
- // critique PROMPT needs it too (run-8 F3): threading the design's pinned
1013
+ // critique PROMPT needs it too: threading the design's pinned
1050
1014
  // interface facts into the rewrite lets it RECONCILE a synthesized wiring
1051
1015
  // specific against the facts it must reproduce — the generation-side
1052
1016
  // complement of the verify-side boundary check.
@@ -1066,12 +1030,14 @@ extraDefects) {
1066
1030
  let verdict;
1067
1031
  try {
1068
1032
  // No tools: triage judges only the spec/refined/qa text it is given.
1069
- // Granting `read` here let it wander the repo to "verify" findings,
1070
- // which made the supposedly-cheap pass cost as much as a rewrite
1071
- // (observed ~133s). The judgement needs no file access.
1033
+ // Granting `read` lets it wander the repo to "verify" findings, which
1034
+ // costs as much as the rewrite this pass exists to avoid. The judgement
1035
+ // needs no file access.
1072
1036
  verdict = await runPhaseChild(deps, 'critique-triage', '', CRITIQUE_TRIAGE_PROMPT(spec, refined, qa, contractsBlock));
1073
1037
  }
1074
- catch {
1038
+ catch (e) {
1039
+ if (isFatalChildCause(e))
1040
+ throw e;
1075
1041
  verdict = null;
1076
1042
  }
1077
1043
  deps.recordSubStep?.('triage', Date.now() - tTriage);
@@ -1142,10 +1108,9 @@ export async function critiqueWithFallback(d, p) {
1142
1108
  // so returning that same draft would persist a VERIFY-less spec the
1143
1109
  // handoff gate rejects and resume can't heal. Compose now enforces a
1144
1110
  // parseable VERIFY, so this should hold; keep the guard so a regression
1145
- // fails the run cleanly instead of shipping a broken spec.
1146
- // (verify_grep_theater: both rewrite attempts kept a grep-only VERIFY;
1147
- // the draft carries the same defect but is the validated-shape fallback
1148
- // — deliver it rather than fail the run. The guard costs time, never work.)
1111
+ // fails the run cleanly instead of shipping a broken spec. A draft that
1112
+ // carries the SAME defect the rewrite failed to fix is still delivered —
1113
+ // it is the validated-shape fallback, and failing the run costs more.
1149
1114
  if (parseVerifyBlock(p.spec) === null)
1150
1115
  throw err;
1151
1116
  p.ctx.ui.notify(msg === 'verify_grep_theater' ?
@@ -1159,8 +1124,8 @@ export async function critiqueWithFallback(d, p) {
1159
1124
  * REFINE — restate the raw prompt as a bounded 4-section spec, then subtractively
1160
1125
  * strike any phantom runtime specifier (`bun:sql`) it carried up verbatim from the
1161
1126
  * spec doc, BEFORE it flows to research/grill/compose. An appended correction alone
1162
- * loses: the affirmative survives into the composed GOAL and on to the implementer
1163
- * (proven: compose re-leaks it 4/4). Rewriting the source so compose has nothing to
1127
+ * loses: the affirmative survives into the composed GOAL and on to the
1128
+ * implementer, because compose re-leaks it. Rewriting the source so compose has nothing to
1164
1129
  * contradict is the fix. Silent + no-op when nothing is wrong or the runtime's types
1165
1130
  * aren't installed.
1166
1131
  */
@@ -1213,16 +1178,16 @@ export async function composePhase(d, p) {
1213
1178
  * CRITIQUE — the last spec-producing step, and the two host-side corrections that
1214
1179
  * must run after it in THIS ORDER.
1215
1180
  *
1216
- * BRACES (mx5 run 16): append any owned design obligation the spec still omits as a
1217
- * CONSTRAINTS bullet. The belt block upstream is obeyed ~25% (measured); a host-side
1181
+ * BRACES: append any owned design obligation the spec still omits as a
1182
+ * CONSTRAINTS bullet. The belt block upstream is obeyed only sometimes; a host-side
1218
1183
  * append is obeyed by construction. Idempotent — quotes the spec already carries
1219
1184
  * (belt-obeying reps) are skipped.
1220
1185
  *
1221
1186
  * Then DETACH: an owned obligation whose only file this spec also FREEZES is
1222
1187
  * unsatisfiable here, so it moves to the pending task that writes that file rather
1223
1188
  * than shipping a requirement no one can meet. It MUST run after the append, because
1224
- * the append is what writes the stamp the detach reads a critique-time probe
1225
- * measured 0/40 because the stamp did not exist yet.
1189
+ * the append is what writes the stamp the detach reads, so a critique-time probe
1190
+ * would find nothing.
1226
1191
  */
1227
1192
  export async function critiquePhase(d, p) {
1228
1193
  const spec = await critiqueWithFallback(d, p);
@@ -1239,10 +1204,9 @@ export async function critiquePhase(d, p) {
1239
1204
  * The pipeline, as a table with no bodies.
1240
1205
  *
1241
1206
  * Every row's `run` is a named exported function, so the COMPOSITION inside a
1242
- * step which is where this codebase's recorded phase defects have lived, not in
1243
- * the parts is drivable directly instead of only through a whole TaskRunner run.
1244
- * The parts stay exported and separately covered; what changed is that the ORDER
1245
- * they run in is now asserted by driving the row rather than retyped in a test.
1207
+ * step is drivable directly instead of only through a whole TaskRunner run. That
1208
+ * matters because a step's parts can each be correct while the order they run in
1209
+ * is wrong see `runPhaseRow`, which is the surface that pins the order.
1246
1210
  */
1247
1211
  export const PHASES = [
1248
1212
  {
@@ -1279,50 +1243,30 @@ export async function runPhaseRow(row, deps, pc) {
1279
1243
  export async function replayPhaseCarry(row, deps, pc) {
1280
1244
  await row.carry?.(deps, pc);
1281
1245
  }
1282
- // INTEGRATION-DEPTH APPEND (2026-07-27): the lever proposed for this exact site
1283
- // deterministically append a known-runnable integration command to the VERIFY block
1284
- // whenever a task's ACCEPTANCE claims runtime behaviour — is REFUTED at STEP 0 and was
1285
- // NOT built. Nothing below is wired; this is the record.
1246
+ // INTEGRATION-DEPTH APPEND considered for this site, NOT built.
1286
1247
  //
1287
- // The defect is real and reproduces on four unrelated stacks. Measured with
1288
- // scripts/verify-integration-depth-step0.ts (published metric regex in that file;
1289
- // re-runnable, no model time): VERIFY blocks that boot or hit the real integrated
1290
- // product — mx5 7/41, IAR1 3/10, godot-engine 2/20, runner 0/2, total 12/73 (16.4%).
1291
- // That total OVERSTATES the truth: godot's two hits only match because the block greps
1292
- // a URL out of CLAUDE.md, and IAR1's TASK_0006 curls a GitHub tarball to check a hash,
1293
- // so genuine product integration is ~9/73 (~12%). The addressable class — runtime claim
1294
- // in ACCEPTANCE, a boot command available with provenance, static-only VERIFY anyway —
1295
- // is 29/73 (39.7%), clearing the task's 25% kill condition, but 27 of those 29 are mx5.
1248
+ // The idea: whenever a task's ACCEPTANCE claims runtime behaviour, deterministically
1249
+ // append a known-runnable integration command to its VERIFY block. Most VERIFY blocks
1250
+ // never boot or touch the integrated product, so the gap is real.
1296
1251
  //
1297
- // What kills the lever is its input, not its premise. It needs a command that is
1298
- // (1) provenance-bearing, (2) proven runnable, (3) terminating, and (4) integration-
1299
- // shaped. Measured on scratch clones with scripts/integration-command-provenance.ts,
1300
- // that intersection is EMPTY on every stack available here:
1301
- // mx5 `bun run dev` is the only command matching the metric and it does not
1302
- // terminate (probe killed it at 60s) appending it hangs the verify child
1303
- // until the 15-minute command watchdog and then FAILs. Every command that DOES
1304
- // terminate (`bun run build` exit 0) fails the metric.
1305
- // godot `godot --headless --quit` (0.8s, exit 0) and the real GUT runner (1.7s,
1306
- // exit 0) are both appendable — and neither matches the metric, which is
1307
- // web-shaped (curl/localhost/PORT=/run dev/http://). Nothing appendable can
1308
- // move the registered metric on a non-web stack, so the two-stack A/B the
1309
- // task requires is unsatisfiable by construction, not by sample size.
1310
- // IAR1 no candidate at all: cmake is absent from this box (the N5 finding).
1311
- // R4 forbids swapping the metric after seeing results, so a broadened "boots the real
1312
- // product" metric is a separate pre-registered experiment, not a rescue of this one.
1252
+ // What kills it is the input, not the premise. The append needs a command that is all
1253
+ // four of: provenance-bearing, proven runnable, TERMINATING, and integration-shaped.
1254
+ // That intersection tends to be empty. A dev server matches the shape and never
1255
+ // terminates appending it hangs the verify child until the command watchdog and then
1256
+ // FAILs. Everything that does terminate (a build, a headless one-shot) is not
1257
+ // integration-shaped. And on a non-web stack nothing appendable is web-shaped at all.
1313
1258
  //
1314
- // The deeper finding: on the stack holding 93% of the addressable mass, integration is
1315
- // not a COMMAND. mx5's 7 integrated blocks are whole task-specific procedures — pick a
1316
- // free port (3911/41234/42421/3001), export DATABASE_URL, boot, seed, log in, assert N
1317
- // endpoints, tear down. A host-side append cannot synthesize that from provenance
1318
- // (launch-contract.md records script NAMES only — no port, no health URL), and
1319
- // synthesizing it from the task's own `## verified tooling` is the N5 fabrication road.
1320
- // That machinery already exists in ONE place that owns ports, seeding and teardown: the
1321
- // final gate's render check. The mx5-class defect belongs there (nexttask TASK 6), not
1322
- // in per-task VERIFY blocks.
1259
+ // The deeper point: on a web stack, integration is not a COMMAND. An integrated VERIFY
1260
+ // block is a whole task-specific procedure — pick a free port, export the database URL,
1261
+ // boot, seed, log in, assert some endpoints, tear down. A host-side append cannot
1262
+ // synthesize that from provenance, because a launch contract records script NAMES and
1263
+ // nothing else: no port, no health URL. Synthesizing it from the task's own
1264
+ // `## verified tooling` section is fabrication.
1323
1265
  //
1324
- // Durable assets kept: both rigs above and their unit tests. Do NOT wire an append here
1325
- // without a command source that satisfies all four properties at once.
1266
+ // That machinery already exists in ONE place that owns ports, seeding and teardown: the
1267
+ // final gate's render check. This class of defect belongs there, not in per-task VERIFY
1268
+ // blocks. Do NOT wire an append here without a command source that satisfies all four
1269
+ // properties at once.
1326
1270
  /** Dispatch a row's declared post-commit effect. Rows with none do nothing. */
1327
1271
  export async function postCommitPhase(phase, deps, pc, out) {
1328
1272
  await phase.postCommit?.(deps, pc, out);