@mjasnikovs/pi-task 0.38.28 → 0.38.30

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 (370) 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/remote/bridge.d.ts +19 -10
  14. package/dist/remote/bridge.js +3 -2
  15. package/dist/remote/broadcast.js +3 -1
  16. package/dist/remote/events.js +12 -11
  17. package/dist/remote/history.d.ts +1 -1
  18. package/dist/remote/protocol.d.ts +6 -3
  19. package/dist/remote/protocol.js +2 -1
  20. package/dist/remote/push.d.ts +16 -16
  21. package/dist/remote/push.js +27 -27
  22. package/dist/remote/register.d.ts +3 -3
  23. package/dist/remote/register.js +17 -19
  24. package/dist/remote/server.d.ts +9 -8
  25. package/dist/remote/server.js +15 -14
  26. package/dist/remote/session-state.d.ts +5 -4
  27. package/dist/remote/session-state.js +8 -5
  28. package/dist/remote/sw.d.ts +7 -6
  29. package/dist/remote/sw.js +7 -6
  30. package/dist/remote/tailscale.d.ts +4 -2
  31. package/dist/remote/tailscale.js +4 -2
  32. package/dist/remote/ui-highlight.js +6 -5
  33. package/dist/remote/ui-render.js +4 -4
  34. package/dist/remote/ui-script.js +24 -24
  35. package/dist/remote/ui-styles.d.ts +1 -1
  36. package/dist/remote/ui-styles.js +10 -13
  37. package/dist/remote/ui-tools.js +9 -6
  38. package/dist/shared/child-extensions.d.ts +29 -17
  39. package/dist/shared/child-extensions.js +29 -17
  40. package/dist/shared/child-output.d.ts +30 -24
  41. package/dist/shared/child-output.js +25 -17
  42. package/dist/shared/child-process.d.ts +47 -40
  43. package/dist/shared/child-process.js +50 -59
  44. package/dist/shared/command-watchdog.d.ts +22 -16
  45. package/dist/shared/command-watchdog.js +28 -21
  46. package/dist/shared/fs-text.d.ts +16 -10
  47. package/dist/shared/fs-text.js +16 -10
  48. package/dist/shared/git-runner.d.ts +25 -25
  49. package/dist/shared/git-runner.js +25 -25
  50. package/dist/shared/leaked-tool-call.d.ts +17 -11
  51. package/dist/shared/leaked-tool-call.js +23 -15
  52. package/dist/shared/model-endpoint.d.ts +29 -16
  53. package/dist/shared/model-endpoint.js +33 -21
  54. package/dist/shared/pi-invocation.d.ts +7 -4
  55. package/dist/shared/pi-invocation.js +12 -7
  56. package/dist/shared/pkg-version.d.ts +13 -5
  57. package/dist/shared/pkg-version.js +13 -5
  58. package/dist/shared/reasoning-capability.d.ts +35 -24
  59. package/dist/shared/reasoning-capability.js +35 -24
  60. package/dist/shared/stream-watchdog.d.ts +60 -44
  61. package/dist/shared/stream-watchdog.js +62 -45
  62. package/dist/task/accept-debt.d.ts +41 -43
  63. package/dist/task/accept-debt.js +73 -65
  64. package/dist/task/api-synthesis.d.ts +24 -21
  65. package/dist/task/api-synthesis.js +32 -26
  66. package/dist/task/apis-contract.d.ts +32 -64
  67. package/dist/task/apis-contract.js +32 -64
  68. package/dist/task/artifact-closure.d.ts +27 -13
  69. package/dist/task/artifact-closure.js +95 -67
  70. package/dist/task/auto-commit.d.ts +46 -35
  71. package/dist/task/auto-commit.js +51 -38
  72. package/dist/task/auto-io.d.ts +45 -25
  73. package/dist/task/auto-io.js +57 -29
  74. package/dist/task/auto-orchestrator.d.ts +26 -24
  75. package/dist/task/auto-orchestrator.js +178 -162
  76. package/dist/task/auto-prompts.d.ts +36 -24
  77. package/dist/task/auto-prompts.js +40 -26
  78. package/dist/task/autofix-ledger.d.ts +27 -25
  79. package/dist/task/autofix-ledger.js +29 -26
  80. package/dist/task/batch-test-task.d.ts +20 -12
  81. package/dist/task/batch-test-task.js +67 -60
  82. package/dist/task/boot-probe.d.ts +60 -44
  83. package/dist/task/boot-probe.js +91 -72
  84. package/dist/task/cancel-input.d.ts +30 -16
  85. package/dist/task/cancel-input.js +20 -11
  86. package/dist/task/cancel-points.d.ts +27 -20
  87. package/dist/task/cancel-points.js +30 -22
  88. package/dist/task/child-runner.d.ts +46 -51
  89. package/dist/task/child-runner.js +48 -49
  90. package/dist/task/child-status.d.ts +23 -16
  91. package/dist/task/child-status.js +23 -16
  92. package/dist/task/clamp-output.js +12 -5
  93. package/dist/task/command-run.d.ts +31 -28
  94. package/dist/task/command-run.js +44 -35
  95. package/dist/task/command-shrink.d.ts +25 -18
  96. package/dist/task/command-shrink.js +37 -31
  97. package/dist/task/command-watchdog.d.ts +9 -6
  98. package/dist/task/command-watchdog.js +21 -15
  99. package/dist/task/context-attribution.d.ts +34 -26
  100. package/dist/task/context-attribution.js +34 -26
  101. package/dist/task/context-silence.d.ts +39 -29
  102. package/dist/task/context-silence.js +35 -25
  103. package/dist/task/context-usage.d.ts +25 -7
  104. package/dist/task/context-usage.js +21 -6
  105. package/dist/task/contracts.d.ts +8 -4
  106. package/dist/task/contracts.js +25 -17
  107. package/dist/task/coverage-loop.d.ts +22 -18
  108. package/dist/task/coverage-loop.js +35 -30
  109. package/dist/task/critique-probes.d.ts +13 -14
  110. package/dist/task/critique-probes.js +50 -39
  111. package/dist/task/debug-log.d.ts +13 -5
  112. package/dist/task/debug-log.js +32 -20
  113. package/dist/task/decompose-fidelity.d.ts +11 -9
  114. package/dist/task/decompose-fidelity.js +38 -33
  115. package/dist/task/decompose-granularity.d.ts +41 -38
  116. package/dist/task/decompose-granularity.js +41 -38
  117. package/dist/task/deep-render-check.d.ts +22 -14
  118. package/dist/task/deep-render-check.js +40 -31
  119. package/dist/task/dropped-input.d.ts +12 -7
  120. package/dist/task/dropped-input.js +5 -2
  121. package/dist/task/enforce-attribution.d.ts +38 -47
  122. package/dist/task/enforce-attribution.js +46 -52
  123. package/dist/task/enforce-guidelines.d.ts +31 -20
  124. package/dist/task/enforce-guidelines.js +32 -21
  125. package/dist/task/enrichment.d.ts +7 -2
  126. package/dist/task/enrichment.js +26 -14
  127. package/dist/task/env-notes.d.ts +16 -7
  128. package/dist/task/env-notes.js +48 -31
  129. package/dist/task/env-template-closure.d.ts +4 -4
  130. package/dist/task/env-template-closure.js +42 -34
  131. package/dist/task/external-context.d.ts +28 -21
  132. package/dist/task/external-context.js +17 -12
  133. package/dist/task/failure-classifier.d.ts +4 -5
  134. package/dist/task/failure-classifier.js +6 -7
  135. package/dist/task/file-inventory.d.ts +15 -11
  136. package/dist/task/file-inventory.js +25 -22
  137. package/dist/task/final-gate-fix.d.ts +74 -86
  138. package/dist/task/final-gate-fix.js +97 -116
  139. package/dist/task/final-gate-progress.d.ts +29 -46
  140. package/dist/task/final-gate-progress.js +40 -51
  141. package/dist/task/final-gate.d.ts +64 -97
  142. package/dist/task/final-gate.js +192 -199
  143. package/dist/task/fix-child.d.ts +21 -27
  144. package/dist/task/fix-child.js +21 -27
  145. package/dist/task/foreign-path.d.ts +6 -5
  146. package/dist/task/foreign-path.js +0 -0
  147. package/dist/task/frozen-conflict.d.ts +9 -10
  148. package/dist/task/frozen-conflict.js +61 -64
  149. package/dist/task/frozen-path-guard.d.ts +35 -14
  150. package/dist/task/frozen-path-guard.js +56 -39
  151. package/dist/task/gate-child.d.ts +27 -28
  152. package/dist/task/gate-child.js +37 -35
  153. package/dist/task/gate-deps.d.ts +34 -27
  154. package/dist/task/gate-deps.js +169 -159
  155. package/dist/task/gate-tally.d.ts +77 -80
  156. package/dist/task/gate-tally.js +65 -68
  157. package/dist/task/git-state-guard.d.ts +15 -11
  158. package/dist/task/git-state-guard.js +76 -66
  159. package/dist/task/impl-widget.d.ts +25 -16
  160. package/dist/task/impl-widget.js +27 -17
  161. package/dist/task/implementation-thinking.d.ts +33 -31
  162. package/dist/task/implementation-thinking.js +5 -6
  163. package/dist/task/implementation-turn.d.ts +34 -31
  164. package/dist/task/implementation-turn.js +29 -27
  165. package/dist/task/inline-markdown.d.ts +20 -7
  166. package/dist/task/inline-markdown.js +15 -6
  167. package/dist/task/launch-config-gap.js +25 -39
  168. package/dist/task/launch-contract.d.ts +18 -21
  169. package/dist/task/launch-contract.js +28 -30
  170. package/dist/task/launch-manifest.d.ts +6 -2
  171. package/dist/task/launch-manifest.js +35 -34
  172. package/dist/task/ledger.js +16 -14
  173. package/dist/task/lint-fix.d.ts +6 -8
  174. package/dist/task/lint-fix.js +67 -69
  175. package/dist/task/loop-detector.d.ts +9 -8
  176. package/dist/task/loop-detector.js +16 -12
  177. package/dist/task/mid-run-input.d.ts +17 -15
  178. package/dist/task/mid-run-input.js +17 -15
  179. package/dist/task/orchestrator.d.ts +24 -28
  180. package/dist/task/orchestrator.js +62 -64
  181. package/dist/task/orientation.d.ts +18 -23
  182. package/dist/task/orientation.js +24 -31
  183. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  184. package/dist/task/owned-freeze-conflict.js +52 -85
  185. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  186. package/dist/task/owned-freeze-reassign.js +41 -61
  187. package/dist/task/parsers.d.ts +4 -2
  188. package/dist/task/parsers.js +4 -4
  189. package/dist/task/phases.d.ts +41 -48
  190. package/dist/task/phases.js +180 -248
  191. package/dist/task/plan-io.d.ts +6 -7
  192. package/dist/task/plan-io.js +6 -7
  193. package/dist/task/plan-orchestrator.d.ts +10 -8
  194. package/dist/task/plan-orchestrator.js +14 -10
  195. package/dist/task/plan-prompts.d.ts +6 -5
  196. package/dist/task/plan-prompts.js +6 -5
  197. package/dist/task/plan-readonly.d.ts +4 -5
  198. package/dist/task/plan-readonly.js +4 -5
  199. package/dist/task/plan-rounds.d.ts +17 -29
  200. package/dist/task/plan-rounds.js +21 -34
  201. package/dist/task/plan-session.d.ts +58 -72
  202. package/dist/task/plan-session.js +61 -83
  203. package/dist/task/probe-gaming.d.ts +28 -27
  204. package/dist/task/probe-gaming.js +0 -0
  205. package/dist/task/prohibition-probe.d.ts +14 -16
  206. package/dist/task/prompts.d.ts +3 -4
  207. package/dist/task/prompts.js +17 -26
  208. package/dist/task/qa-transcript.d.ts +15 -22
  209. package/dist/task/qa-transcript.js +15 -21
  210. package/dist/task/question-box.d.ts +17 -13
  211. package/dist/task/question-box.js +19 -15
  212. package/dist/task/question-dedup.d.ts +6 -7
  213. package/dist/task/question-dedup.js +13 -14
  214. package/dist/task/question-dialog.d.ts +22 -32
  215. package/dist/task/question-dialog.js +22 -32
  216. package/dist/task/question-source.d.ts +18 -44
  217. package/dist/task/question-source.js +22 -51
  218. package/dist/task/refuted-constraint.d.ts +11 -31
  219. package/dist/task/refuted-constraint.js +27 -51
  220. package/dist/task/regenerable-artifacts.d.ts +12 -31
  221. package/dist/task/regenerable-artifacts.js +12 -31
  222. package/dist/task/render-check.d.ts +11 -22
  223. package/dist/task/render-check.js +33 -46
  224. package/dist/task/repo-health-check.d.ts +10 -14
  225. package/dist/task/repo-health-check.js +17 -23
  226. package/dist/task/requirements.d.ts +38 -71
  227. package/dist/task/requirements.js +78 -126
  228. package/dist/task/research-fanout-budget.d.ts +51 -88
  229. package/dist/task/research-fanout-budget.js +51 -88
  230. package/dist/task/research-worker.d.ts +33 -36
  231. package/dist/task/research-worker.js +39 -61
  232. package/dist/task/resume-gap.d.ts +14 -15
  233. package/dist/task/root-cause-repair.d.ts +9 -9
  234. package/dist/task/root-cause-repair.js +28 -40
  235. package/dist/task/run-bracket.d.ts +10 -13
  236. package/dist/task/run-end.d.ts +12 -22
  237. package/dist/task/run-end.js +8 -16
  238. package/dist/task/run-final-gate.d.ts +19 -21
  239. package/dist/task/run-final-gate.js +62 -80
  240. package/dist/task/runner-globs.d.ts +12 -13
  241. package/dist/task/runner-globs.js +12 -13
  242. package/dist/task/runner-resolve.d.ts +9 -9
  243. package/dist/task/runner-resolve.js +22 -23
  244. package/dist/task/script-escape.d.ts +10 -12
  245. package/dist/task/script-escape.js +13 -14
  246. package/dist/task/serve-entry.d.ts +1 -1
  247. package/dist/task/serve-entry.js +22 -25
  248. package/dist/task/service-blocks.js +4 -2
  249. package/dist/task/shipped-source.d.ts +11 -29
  250. package/dist/task/shipped-source.js +11 -29
  251. package/dist/task/skip-escape.js +10 -14
  252. package/dist/task/spec-urls.d.ts +26 -65
  253. package/dist/task/spec-urls.js +26 -65
  254. package/dist/task/spec-validation.d.ts +17 -20
  255. package/dist/task/spec-validation.js +17 -20
  256. package/dist/task/stall-detector.d.ts +23 -30
  257. package/dist/task/stall-detector.js +23 -30
  258. package/dist/task/stream-watchdog.d.ts +14 -12
  259. package/dist/task/stream-watchdog.js +14 -12
  260. package/dist/task/substitution-probe.d.ts +17 -20
  261. package/dist/task/substitution-probe.js +17 -20
  262. package/dist/task/task-gates.d.ts +36 -41
  263. package/dist/task/task-gates.js +95 -106
  264. package/dist/task/task-io.d.ts +4 -4
  265. package/dist/task/task-io.js +4 -4
  266. package/dist/task/task-parsers.js +4 -3
  267. package/dist/task/task-provenance.d.ts +2 -2
  268. package/dist/task/task-provenance.js +11 -13
  269. package/dist/task/task-types.d.ts +4 -3
  270. package/dist/task/terminal-outcome.d.ts +14 -16
  271. package/dist/task/terminal-outcome.js +12 -14
  272. package/dist/task/test-assembly.d.ts +13 -20
  273. package/dist/task/test-assembly.js +13 -20
  274. package/dist/task/timings.d.ts +5 -3
  275. package/dist/task/timings.js +5 -3
  276. package/dist/task/title-label.d.ts +9 -4
  277. package/dist/task/title-label.js +9 -4
  278. package/dist/task/type-only-answer.d.ts +44 -52
  279. package/dist/task/type-only-answer.js +44 -52
  280. package/dist/task/unfailable-command.d.ts +18 -24
  281. package/dist/task/unfailable-command.js +21 -27
  282. package/dist/task/unknown-routing.d.ts +10 -4
  283. package/dist/task/unknown-routing.js +10 -4
  284. package/dist/task/user-directives.d.ts +5 -8
  285. package/dist/task/user-directives.js +5 -8
  286. package/dist/task/verify-quality.d.ts +18 -22
  287. package/dist/task/verify-quality.js +45 -46
  288. package/dist/task/verify-reconcile.d.ts +15 -10
  289. package/dist/task/verify-reconcile.js +45 -43
  290. package/dist/task/verify-resolution.d.ts +24 -20
  291. package/dist/task/verify-resolution.js +51 -50
  292. package/dist/task/verify-work.d.ts +59 -66
  293. package/dist/task/verify-work.js +101 -138
  294. package/dist/task/widget.d.ts +15 -14
  295. package/dist/task/widget.js +22 -17
  296. package/dist/task/wiring-claims.d.ts +25 -32
  297. package/dist/task/wiring-claims.js +30 -35
  298. package/dist/task/write-guard.d.ts +39 -39
  299. package/dist/task/write-guard.js +48 -51
  300. package/dist/task/yolo.d.ts +34 -30
  301. package/dist/task/yolo.js +42 -37
  302. package/dist/workers/abstention.d.ts +21 -41
  303. package/dist/workers/abstention.js +27 -48
  304. package/dist/workers/brave-search.d.ts +4 -3
  305. package/dist/workers/brave-search.js +5 -2
  306. package/dist/workers/brave-warning.d.ts +7 -4
  307. package/dist/workers/brave-warning.js +19 -7
  308. package/dist/workers/ddg-search.d.ts +6 -6
  309. package/dist/workers/ddg-search.js +18 -12
  310. package/dist/workers/docs-cache.js +5 -2
  311. package/dist/workers/docs-chunk.d.ts +30 -37
  312. package/dist/workers/docs-chunk.js +37 -41
  313. package/dist/workers/docs-core.d.ts +28 -44
  314. package/dist/workers/docs-core.js +25 -44
  315. package/dist/workers/docs-index.js +4 -3
  316. package/dist/workers/docs-lookup.d.ts +15 -22
  317. package/dist/workers/docs-lookup.js +12 -21
  318. package/dist/workers/docs-project.d.ts +15 -9
  319. package/dist/workers/docs-project.js +17 -10
  320. package/dist/workers/docs-resolve.d.ts +19 -20
  321. package/dist/workers/docs-resolve.js +35 -32
  322. package/dist/workers/docs-retrieve.d.ts +5 -6
  323. package/dist/workers/docs-retrieve.js +18 -15
  324. package/dist/workers/exa-search.d.ts +9 -6
  325. package/dist/workers/exa-search.js +23 -12
  326. package/dist/workers/fetch-core.d.ts +13 -16
  327. package/dist/workers/fetch-core.js +23 -23
  328. package/dist/workers/focused-extractor.d.ts +12 -12
  329. package/dist/workers/focused-extractor.js +16 -19
  330. package/dist/workers/html-clean.js +24 -14
  331. package/dist/workers/http-request.d.ts +28 -20
  332. package/dist/workers/http-request.js +22 -17
  333. package/dist/workers/npm-version.d.ts +28 -11
  334. package/dist/workers/npm-version.js +24 -15
  335. package/dist/workers/phantom-imports.d.ts +15 -12
  336. package/dist/workers/phantom-imports.js +30 -24
  337. package/dist/workers/pi-worker-core.d.ts +86 -54
  338. package/dist/workers/pi-worker-core.js +112 -112
  339. package/dist/workers/pi-worker-docs.d.ts +24 -19
  340. package/dist/workers/pi-worker-docs.js +67 -76
  341. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  342. package/dist/workers/pi-worker-fetch.js +27 -19
  343. package/dist/workers/pi-worker-search.js +12 -8
  344. package/dist/workers/pi-worker.d.ts +9 -4
  345. package/dist/workers/pi-worker.js +23 -10
  346. package/dist/workers/reasoning-warning.d.ts +18 -17
  347. package/dist/workers/reasoning-warning.js +22 -20
  348. package/dist/workers/research-cache.js +50 -78
  349. package/dist/workers/search-core.js +7 -5
  350. package/dist/workers/search-types.d.ts +10 -9
  351. package/dist/workers/search-types.js +9 -8
  352. package/dist/workers/session-hint.d.ts +13 -14
  353. package/dist/workers/session-hint.js +8 -9
  354. package/dist/workers/shared.d.ts +21 -25
  355. package/dist/workers/shared.js +0 -0
  356. package/dist/workers/single-read-extension.d.ts +14 -7
  357. package/dist/workers/single-read-extension.js +14 -7
  358. package/dist/workers/single-read-guard.d.ts +25 -28
  359. package/dist/workers/single-read-guard.js +32 -32
  360. package/dist/workers/typeonly-log.d.ts +12 -9
  361. package/dist/workers/typeonly-log.js +29 -33
  362. package/dist/workers/worker-channels.d.ts +15 -23
  363. package/dist/workers/worker-channels.js +15 -23
  364. package/dist/workers/worker-failure.d.ts +38 -46
  365. package/dist/workers/worker-failure.js +31 -39
  366. package/dist/workers/worker-kill.d.ts +25 -26
  367. package/dist/workers/worker-kill.js +16 -19
  368. package/dist/workers/worker-profiles.d.ts +43 -53
  369. package/dist/workers/worker-profiles.js +30 -38
  370. package/package.json +10 -8
@@ -3,24 +3,30 @@
3
3
  *
4
4
  * A cancel is a *request*, never a kill. It is honoured only where the on-disk
5
5
  * state is durable and /task-auto-resume provably continues from it. Killing a
6
- * child mid-phase would also stop the run (the abort signal already reaches
7
- * every child's process group) but throws away that phase's work and can leave
8
- * a half-written tree so the flag is polled at boundaries instead.
6
+ * child mid-phase would also stop the run the abort signal reaches every
7
+ * child's process group but it throws away that phase's work and can leave a
8
+ * half-written tree, so the flag is polled at boundaries instead.
9
9
  *
10
- * The checkpoint set, and why each one is safe:
10
+ * The checkpoint set is exactly these four, one call site each:
11
11
  *
12
- * loop-top the previous task is checked off + committed and the next one
13
- * has not started. The original (and until now the ONLY) point.
14
- * pre-task after the pre-task checkpoint commit, before the runner is
15
- * constructed: tree committed, no inner id stamped yet, so the
16
- * entry is simply still unchecked and a resume restarts it.
17
- * phase:<name> inside the runner's phase loop, after setTaskSection +
18
- * postCommitPhase have persisted the phase output and
19
- * front-matter `phase` names the NEXT phase. This is exactly
20
- * the state /task-resume already resumes from (PHASE_INDEX
21
- * skip-forward), so a cancel here costs nothing.
22
- * pre-final-gate every task is checked off and committed; the whole-repo gate
23
- * has not started. A resume re-enters the same branch.
12
+ * loop-top (auto-orchestrator) the previous task is checked off and
13
+ * committed and the next one has not started.
14
+ * pre-task (auto-orchestrator) after the pre-task checkpoint commit,
15
+ * before the runner is constructed: tree committed, no inner id
16
+ * stamped yet, so the entry is simply still unchecked and a
17
+ * resume restarts it.
18
+ * phase:<name> (orchestrator, inside the phase loop) after setTaskSection and
19
+ * postCommitPhase have persisted this phase's output. Front-matter
20
+ * `phase` still names the phase that JUST FINISHED, not the next
21
+ * one: `advance()` writes it at the TOP of each iteration and
22
+ * nothing moves it afterwards postCommitPhase writes only
23
+ * `title` and `label`. Since the resume skip rule is
24
+ * `idx < resumeIdx`, a resume therefore RE-ENTERS this phase and
25
+ * runs it again. Nothing is lost, because its section is already
26
+ * on disk; the cost is repeating one phase, not zero.
27
+ * pre-final-gate (run-final-gate) every task is checked off and committed; the
28
+ * whole-repo gate has not started. A resume re-enters the same
29
+ * branch.
24
30
  *
25
31
  * DELIBERATELY NOT checkpoints — stopping here is not safe:
26
32
  * - mid implementation turn: uncommitted, half-applied edits. The user's ESC
@@ -31,7 +37,8 @@
31
37
  */
32
38
  let requested = false;
33
39
  /** Checkpoints actually reached since the last reset, in order. Instrumentation
34
- * for the A/B harness and the unit tests never read by the loop itself. */
40
+ * only: `checkpointsCrossed` and `resetCheckpointTrail` have no caller anywhere
41
+ * in src/ — every consumer is a test. */
35
42
  const crossed = [];
36
43
  export function requestCancel() {
37
44
  requested = true;
@@ -51,7 +58,7 @@ export function isCancelRequested() {
51
58
  export function resetCancel() {
52
59
  requested = false;
53
60
  }
54
- /** Drop the recorded checkpoint trail (A/B + tests). */
61
+ /** Drop the recorded checkpoint trail. */
55
62
  export function resetCheckpointTrail() {
56
63
  crossed.length = 0;
57
64
  }
@@ -64,14 +71,15 @@ export function resetCheckpointTrail() {
64
71
  */
65
72
  export function cancelCheckpoint(where) {
66
73
  crossed.push(where);
67
- // A/B ONLY (scripts/cancel-latency-ab.ts): reproduce the pre-checkpoint
68
- // behaviour the flag was observed at the loop top and nowhere else so the
69
- // two arms differ in exactly the thing under test and share every other path.
74
+ // CANCEL_AB_ARM=baseline collapses the checkpoint set back to loop-top alone,
75
+ // so the two arms differ in exactly one thing. Nothing in src/ ever sets it;
76
+ // the only writer in the tree is cancel-points.test.ts, which uses it to pin
77
+ // that the extra checkpoints — and only they — are what the flag gates.
70
78
  if (process.env.CANCEL_AB_ARM === 'baseline' && where !== 'loop-top')
71
79
  return false;
72
80
  return requested;
73
81
  }
74
- /** Checkpoints crossed since the last reset (A/B + tests). */
82
+ /** Checkpoints crossed since the last reset. */
75
83
  export function checkpointsCrossed() {
76
84
  return crossed;
77
85
  }
@@ -18,22 +18,19 @@ export declare const MAX_LOOP_RESTARTS = 2;
18
18
  /**
19
19
  * Optional wall-clock bound on ONE spawn of a phase child. DEFAULT: OFF.
20
20
  *
21
- * It used to default to 600_000, sized against measured HEALTHY planning
22
- * children on one local 27B backend (decompose 89s, whole plan phase 321s) on
23
- * the reasoning that ten minutes was a 3-6x margin over honest work.
21
+ * WHY OFF, AND NOT A NUMBER. A wall clock on a model child measures the
22
+ * MODEL'S SPEED, not its health. The same planning child that answers well in
23
+ * seconds on one backend takes many minutes on another, or on the same backend
24
+ * with thinking turned on — so any cap generous enough to be safe is too loose
25
+ * to catch anything, and any cap tight enough to catch a runaway kills healthy
26
+ * work. Assume a model that emits one token per second and the number has no
27
+ * defensible value at all.
24
28
  *
25
- * That premise was measured and is false. Replaying ONE captured auto-decompose
26
- * request against the same backend with reasoning ON, n=10, everything else
27
- * byte-identical (2026-08-17): all ten answered correctly with 26-42 titles, and
28
- * every one took 610-927s. The cap would have killed 10 out of 10 GOOD runs and
29
- * then failed the phase with PhaseTimeoutError. The number was not measuring the
30
- * pathology, it was measuring one model's speed on one day.
31
- *
32
- * The runaway it was there to catch — a decompose child that ran 16m23s at
33
- * 117,370 of a 120,064-token window, forward-paging past the loop detector — is
34
- * now caught by StallDetector (stall-detector.ts), which bounds NON-PROGRESS and
35
- * CONTEXT CHURN instead of elapsed seconds. Both of those are properties of the
36
- * pathology, so neither has to be re-tuned for a slower model or a bigger repo.
29
+ * The runaway it was there to catch a child forward-paging through its whole
30
+ * context window, past the loop detector, never going to return is caught by
31
+ * StallDetector (stall-detector.ts) instead. That bounds NON-PROGRESS and
32
+ * CONTEXT CHURN, both properties of the pathology itself, so neither has to be
33
+ * re-tuned for a slower model or a bigger repo.
37
34
  *
38
35
  * The value and the plumbing stay for a caller that genuinely wants a hard stop
39
36
  * (tests inject a short one), but nothing sets it in production. Pass
@@ -84,13 +81,13 @@ export declare const USER_CANCELLED = "__user_cancelled__";
84
81
  /**
85
82
  * One child-pi invocation, as a value.
86
83
  *
87
- * WHY A RECORD. This was thirteen ordered positionals, and the thirteenth
88
- * carried its own DEBT note saying so. Two production callers reached it, and
89
- * they had already drifted: the degrade attempt wrote three bare `undefined`s to
90
- * reach the later slots and passed the RAW signal, so it escaped the wall clock
91
- * its own strike siblings run under the same defect class this file already
92
- * recorded for `runAutoInstall` and for the deleted `runPhaseWithLoopGuard`.
93
- * Adjacent optionals of the same type can no longer swap without a type error.
84
+ * WHY A RECORD RATHER THAN POSITIONALS. With this many optional parameters of
85
+ * the same type, a caller reaching a late one must write bare `undefined`s to get
86
+ * there, and one that miscounts silently lands the wrong value in the wrong slot.
87
+ * The failure that shape produces here is a child spawned with the RAW signal
88
+ * instead of the wall-clocked one, escaping a guard its siblings run under, with
89
+ * nothing to catch it. Named fields make adjacent optionals of the same type
90
+ * impossible to swap without a type error.
94
91
  */
95
92
  export interface ChildRun {
96
93
  cwd: string;
@@ -111,9 +108,11 @@ export interface ChildRun {
111
108
  */
112
109
  onToolResult?: (text: string, isError: boolean) => void;
113
110
  /**
114
- * The child's context window in tokens. Nothing in pi's event stream reports
115
- * one (issue #16), so the parent hands its own down children carry no `-m`
116
- * and resolve the same default model. 0 / omitted = unknown, as before.
111
+ * The child's context window in tokens. Nothing in pi's `--mode json` stream
112
+ * reports one a real capture carries token counts and a model id, but no key
113
+ * naming a window — so the parent hands its own down. Children carry no `-m`
114
+ * (CHILD_BASE_ARGS) and resolve the same default model, which is what makes
115
+ * the parent's window the honest value. 0 or omitted = unknown.
117
116
  */
118
117
  contextWindow?: number;
119
118
  /**
@@ -132,12 +131,12 @@ export interface PhaseDeps {
132
131
  /**
133
132
  * The parent session's context window in tokens, handed down to every child.
134
133
  *
135
- * pi's `--mode json` stream reports token counts but no window (issue #16),
136
- * so without this the gauge shows a bare number and — worse — the
137
- * StallDetector's CONTEXT CHURN rule, which is gated on a positive window,
138
- * can never fire. Children are spawned without `-m` (CHILD_BASE_ARGS) and so
139
- * run the parent's own default model; its window is the honest value.
140
- * Absent = unknown, and both consumers degrade exactly as they did before.
134
+ * pi's `--mode json` stream reports token counts but no window, so without
135
+ * this the gauge shows a bare number and — worse — the StallDetector's CONTEXT
136
+ * CHURN rule can never fire: it opens with `if (this.contextWindow <= 0)
137
+ * return false`. Children are spawned without `-m` (CHILD_BASE_ARGS) and so
138
+ * run the parent's own default model, which is what makes its window the
139
+ * honest value. Absent = unknown, and both consumers degrade.
141
140
  */
142
141
  contextWindow?: number;
143
142
  /**
@@ -185,11 +184,10 @@ export interface PhaseDeps {
185
184
  * the substitute answers directly and NONE of those guards run.
186
185
  *
187
186
  * The child's NAME is the first parameter because the name is what a caller
188
- * branches on and what a test wants to assert. It used to be discarded before
189
- * reaching the only injectable boundary (`spawn`), so a phase test had to
190
- * reconstruct it by matching prompt PROSE against prompts.ts — which made
191
- * prompt copy load-bearing test infrastructure in a codebase whose practice is
192
- * rewording prompts and A/B-ing them.
187
+ * branches on and what a test wants to assert. Discarded before it reaches
188
+ * the only injectable boundary (`spawn`), a phase test has to reconstruct it
189
+ * by matching prompt PROSE against prompts.ts — which makes prompt copy
190
+ * load-bearing test infrastructure in a codebase that rewords prompts.
193
191
  *
194
192
  * `spawn` stays: the ladder's OWN tests must drive a real process to exercise
195
193
  * the rungs. This seam is for callers to whom the child is a premise.
@@ -232,13 +230,10 @@ export interface PhaseDeps {
232
230
  * The subset of `PhaseDeps` a CALLER may supply — every injectable seam, derived
233
231
  * by naming what the runner owns instead of by listing what it does not.
234
232
  *
235
- * `TaskRunnerOptions` used to declare `spawnFn`, `runChild`, `runWorker` and a
236
- * seven-name `Pick` called `lookups` separately; `RunSingleTaskOptions` re-picked
237
- * all four, `runSingleTask` re-forwarded each by name, and the constructor spread
238
- * them back together. Four coordinated edits, none of which failed to compile if
239
- * you skipped one — the exact indictment this codebase already recorded against
240
- * `ConfigItem`. Four seams (`timeoutMs`, `sleepFor`, `childExtensions`,
241
- * `logDebug`) had in fact been left behind, so a runner-driven test of the
233
+ * Declaring the seams separately on `TaskRunnerOptions`, re-picking them on
234
+ * `RunSingleTaskOptions`, re-forwarding each by name and spreading them back
235
+ * together is four coordinated edits, none of which fails to compile if you skip
236
+ * one. Seams get left behind that way, and a runner-driven test of the
242
237
  * connection-error rung really slept and the debug trail could not be asserted at
243
238
  * all. Derived by `Omit`, a NEW seam field joins this with no second edit.
244
239
  */
@@ -254,16 +249,16 @@ export type PhaseSeams = Omit<PhaseDeps, 'cwd' | 'taskId' | 'signal' | 'onChildO
254
249
  *
255
250
  * THREE RUNAWAY GUARDS ride the same budget, because this is the runner every
256
251
  * /task-auto planning child goes through (clarify, decompose, coverage,
257
- * contract-extract) and until mx5-n 2026-08-14 it had none:
252
+ * contract-extract), and an unguarded planning child can burn a whole run:
258
253
  * • a LoopDetector, so an identical repeated tool call is killed and
259
254
  * re-prompted instead of being allowed to fill the context window;
260
255
  * • a StallDetector, the backstop for the varied-args thrash the loop
261
- * detector's short window cannot see — the shape that actually cost us a
262
- * 16-minute decompose child that was never going to return. It bounds
256
+ * detector's short window cannot see — a child that keeps calling tools with
257
+ * different arguments, learns nothing, and is never going to return. It bounds
263
258
  * consecutive no-new-ground calls and total context churn, NOT elapsed time;
264
- * • PHASE_CHILD_TIMEOUT_MS, a hard wall clock, OFF by default because the
265
- * measured healthy range (610-927s for a reasoning-on decompose) overlaps
266
- * any value that would catch the pathology. See its comment.
259
+ * • PHASE_CHILD_TIMEOUT_MS, a hard wall clock, OFF by default: a healthy
260
+ * reasoning-on planning child and a runaway one occupy the same range of
261
+ * elapsed times, so no threshold separates them. See its comment.
267
262
  * All three are checked BEFORE the triage ladder: we killed the child, so its
268
263
  * exit status describes our SIGTERM and says nothing about its verdict.
269
264
  */
@@ -292,8 +287,8 @@ export declare function prependHint(hint: string | null, prompt: string): string
292
287
  /**
293
288
  * The two things a phase child can disagree about. Everything else — the loop
294
289
  * and stall detectors, the wall clock, the loop trail, the triage ladder and its
295
- * budget — is the one loop's, because the two wrappers that used to differ
296
- * disagreed on nothing else that was ever observable.
290
+ * budget — is the one loop's. These two are the only differences observable from
291
+ * outside it.
297
292
  */
298
293
  export interface PhaseChildOptions {
299
294
  /**
@@ -28,22 +28,19 @@ export const MAX_LOOP_RESTARTS = 2; // 3 strikes total (initial attempt + 2 rest
28
28
  /**
29
29
  * Optional wall-clock bound on ONE spawn of a phase child. DEFAULT: OFF.
30
30
  *
31
- * It used to default to 600_000, sized against measured HEALTHY planning
32
- * children on one local 27B backend (decompose 89s, whole plan phase 321s) on
33
- * the reasoning that ten minutes was a 3-6x margin over honest work.
31
+ * WHY OFF, AND NOT A NUMBER. A wall clock on a model child measures the
32
+ * MODEL'S SPEED, not its health. The same planning child that answers well in
33
+ * seconds on one backend takes many minutes on another, or on the same backend
34
+ * with thinking turned on — so any cap generous enough to be safe is too loose
35
+ * to catch anything, and any cap tight enough to catch a runaway kills healthy
36
+ * work. Assume a model that emits one token per second and the number has no
37
+ * defensible value at all.
34
38
  *
35
- * That premise was measured and is false. Replaying ONE captured auto-decompose
36
- * request against the same backend with reasoning ON, n=10, everything else
37
- * byte-identical (2026-08-17): all ten answered correctly with 26-42 titles, and
38
- * every one took 610-927s. The cap would have killed 10 out of 10 GOOD runs and
39
- * then failed the phase with PhaseTimeoutError. The number was not measuring the
40
- * pathology, it was measuring one model's speed on one day.
41
- *
42
- * The runaway it was there to catch — a decompose child that ran 16m23s at
43
- * 117,370 of a 120,064-token window, forward-paging past the loop detector — is
44
- * now caught by StallDetector (stall-detector.ts), which bounds NON-PROGRESS and
45
- * CONTEXT CHURN instead of elapsed seconds. Both of those are properties of the
46
- * pathology, so neither has to be re-tuned for a slower model or a bigger repo.
39
+ * The runaway it was there to catch a child forward-paging through its whole
40
+ * context window, past the loop detector, never going to return is caught by
41
+ * StallDetector (stall-detector.ts) instead. That bounds NON-PROGRESS and
42
+ * CONTEXT CHURN, both properties of the pathology itself, so neither has to be
43
+ * re-tuned for a slower model or a bigger repo.
47
44
  *
48
45
  * The value and the plumbing stay for a caller that genuinely wants a hard stop
49
46
  * (tests inject a short one), but nothing sets it in production. Pass
@@ -145,8 +142,8 @@ thinking = []) {
145
142
  // `--mode json` puts the child into the structured event stream the
146
143
  // unified runner parses in `mode: 'json-events'`. Without it the child
147
144
  // emits plain text, every line fails JSON.parse, finalText stays empty,
148
- // and every phase fails with "X child produced no output". Was silently
149
- // dropped in the 4e34f96 split-refactor; do not remove again.
145
+ // and every phase fails with "X child produced no output". A refactor has
146
+ // dropped it once already; do not remove it again.
150
147
  //
151
148
  // An empty `tools` string means "no tools at all" — emit `--no-tools`
152
149
  // instead of `--tools ''` (which pi would reject). Used by pure-judgment
@@ -154,8 +151,9 @@ thinking = []) {
154
151
  // hand them, never spend time reading the repo.
155
152
  //
156
153
  // The prompt is NOT an argv element: it goes to the child over stdin (see
157
- // runChild below / getPiInvocation), so a large inlined-design prompt can't
158
- // overflow the OS command-line limit (Windows `spawn ENAMETOOLONG`).
154
+ // runChild below / getPiInvocation), so a large inlined-design prompt cannot
155
+ // exceed the OS argv ceiling which fails the spawn outright rather than
156
+ // truncating (`E2BIG` on this platform).
159
157
  //
160
158
  // `extensions` are internal `-e` loads for in-run guards (the caller supplies
161
159
  // the path). A no-tools child cannot make a tool call, so it never carries
@@ -173,7 +171,7 @@ export async function runChild({ cwd, tools, prompt, signal, onLine, onContextUs
173
171
  const result = await runChildUnified(spawnFn ?? spawn, invocation, cwd, signal, {
174
172
  mode: 'json-events',
175
173
  // A hung model stream reports nothing at all, so without this the
176
- // phase child waits forever (mx5 run 14: ~2.9h of dead air). The kill
174
+ // phase child waits forever. The kill
177
175
  // is reported below as a connection-class cause, which routes it into
178
176
  // the retry/backoff path this file already has for a LOUD disconnect.
179
177
  streamInactivityMs: getConfig().streamInactivityMs,
@@ -226,9 +224,8 @@ export async function runChild({ cwd, tools, prompt, signal, onLine, onContextUs
226
224
  * verdict, so a fix to any rung lands in every caller at once.
227
225
  *
228
226
  * `attempt` is the caller's 0-based counter, `budget` the matching restart
229
- * allowance (MAX_LEAK_RETRIES, which is also MAX_LOOP_RESTARTS the loop and
230
- * leak budgets were separate constants at the same value and are shared by one
231
- * loop now) — so a phase runs `budget + 1` attempts before a rung gives up.
227
+ * allowance MAX_LEAK_RETRIES and MAX_LOOP_RESTARTS are both 2, and one loop
228
+ * spends the pair so a phase runs `budget + 1` attempts before a rung gives up.
232
229
  *
233
230
  * `verb` names the restart in the debug log ("retry" by default, "restart" for
234
231
  * refine and grill-gen). It is the only externally visible thing that differed
@@ -240,7 +237,11 @@ export async function runChild({ cwd, tools, prompt, signal, onLine, onContextUs
240
237
  */
241
238
  async function triageChildResult(deps, name, r, attempt, budget, verb) {
242
239
  if (r.exitCode !== 0) {
243
- throw new Error(`${name} child failed: ${r.stderr || '(no stderr)'}`);
240
+ // The exit code is the only signal when stderr is empty, and pi exits
241
+ // silently on several paths (143 = SIGTERM/loop-kill, 137 = SIGKILL/OOM).
242
+ // Dropping it left "(no stderr)" as the whole diagnosis.
243
+ const why = r.stderr || `no stderr, exit ${r.exitCode}`;
244
+ throw new Error(`${name} child failed: ${why}`);
244
245
  }
245
246
  if (r.modelError) {
246
247
  // The model/provider failed (pi exited 0 with a stopReason "error"
@@ -286,16 +287,16 @@ async function triageChildResult(deps, name, r, attempt, budget, verb) {
286
287
  *
287
288
  * THREE RUNAWAY GUARDS ride the same budget, because this is the runner every
288
289
  * /task-auto planning child goes through (clarify, decompose, coverage,
289
- * contract-extract) and until mx5-n 2026-08-14 it had none:
290
+ * contract-extract), and an unguarded planning child can burn a whole run:
290
291
  * • a LoopDetector, so an identical repeated tool call is killed and
291
292
  * re-prompted instead of being allowed to fill the context window;
292
293
  * • a StallDetector, the backstop for the varied-args thrash the loop
293
- * detector's short window cannot see — the shape that actually cost us a
294
- * 16-minute decompose child that was never going to return. It bounds
294
+ * detector's short window cannot see — a child that keeps calling tools with
295
+ * different arguments, learns nothing, and is never going to return. It bounds
295
296
  * consecutive no-new-ground calls and total context churn, NOT elapsed time;
296
- * • PHASE_CHILD_TIMEOUT_MS, a hard wall clock, OFF by default because the
297
- * measured healthy range (610-927s for a reasoning-on decompose) overlaps
298
- * any value that would catch the pathology. See its comment.
297
+ * • PHASE_CHILD_TIMEOUT_MS, a hard wall clock, OFF by default: a healthy
298
+ * reasoning-on planning child and a runaway one occupy the same range of
299
+ * elapsed times, so no threshold separates them. See its comment.
299
300
  * All three are checked BEFORE the triage ladder: we killed the child, so its
300
301
  * exit status describes our SIGTERM and says nothing about its verdict.
301
302
  */
@@ -351,10 +352,10 @@ export async function runPhaseChild(deps, name, tools, prompt, opts = {}) {
351
352
  throw new Error(USER_CANCELLED);
352
353
  const detector = new LoopDetector(LOOP_WINDOW, LOOP_THRESHOLD);
353
354
  const stall = new StallDetector();
354
- // Arm the churn rule BEFORE the first tool call. The window used to reach
355
- // the detector only through a context snapshot, and pi's stream never
356
- // carries one, so it was always 0 and rule 2 never fired (issue #16). The
357
- // parent knows the value at spawn time — say it then, not later.
355
+ // Arm the churn rule BEFORE the first tool call. pi's stream carries no
356
+ // context WINDOW, so a detector that waited to be told one would sit at 0,
357
+ // and the churn rule returns false on a non-positive window. The parent
358
+ // knows the value at spawn time — say it then, not later.
358
359
  stall.noteContext(deps.contextWindow ?? 0);
359
360
  const clock = phaseTimeout(deps.signal, budgetMs);
360
361
  let r;
@@ -365,9 +366,10 @@ export async function runPhaseChild(deps, name, tools, prompt, opts = {}) {
365
366
  signal: clock.signal,
366
367
  thinking,
367
368
  onContextUsage: snapshot => {
368
- // Real window or nothing: noteContext ignores 0, and until
369
- // deps.contextWindow existed 0 was all it ever saw, which
370
- // left the churn rule permanently disarmed (issue #16).
369
+ // Real window or nothing: `noteContext` ignores 0, which is
370
+ // why the parent's value must be supplied at spawn — a
371
+ // stream that only ever reports 0 leaves the churn rule
372
+ // permanently disarmed.
371
373
  stall.noteContext(snapshot.contextWindow);
372
374
  deps.onContextUsage?.(snapshot);
373
375
  },
@@ -450,8 +452,8 @@ export function prependHint(hint, prompt) {
450
452
  * Append one line to the task file's `loop events` section.
451
453
  *
452
454
  * Best-effort by contract: it runs for EVERY phase child now that there is one
453
- * loop, and the six sites that used to reach the un-trailed wrapper do not all
454
- * own a task file on disk (a scripted harness, a bare unit deps bag). A trail
455
+ * loop, and not every caller owns a task file on disk (a scripted harness, a
456
+ * bare unit deps bag). A trail
455
457
  * that cannot be written must cost the phase nothing — the loop kill itself is
456
458
  * already reported through the debug log and the thrown LoopExhaustedError.
457
459
  */
@@ -480,11 +482,9 @@ async function appendLoopEvent(cwd, taskId, phase, hit, strike, outcome) {
480
482
  */
481
483
  async function runDegradedFinalAttempt(deps, name, prompt, hit, loopHistory) {
482
484
  deps.logDebug?.(`${name}: loop budget exhausted — degrading to a no-tools final attempt`);
483
- // BEHAVIOUR DELTA. This attempt now runs under the same wall clock as the
484
- // strikes that led here. It used to pass `deps.signal` raw one of the
485
- // three drifts that came of reaching past bare `undefined`s to the later
486
- // positionals — so the one attempt made after a loop budget was spent was
487
- // also the one attempt that could hang forever.
485
+ // This attempt runs under the same wall clock as the strikes that led here.
486
+ // Passing `deps.signal` raw instead would make the one attempt taken after a
487
+ // loop budget is spent the one attempt that can hang forever.
488
488
  const clock = phaseTimeout(deps.signal, deps.timeoutMs ?? PHASE_CHILD_TIMEOUT_MS);
489
489
  let r;
490
490
  try {
@@ -502,11 +502,10 @@ async function runDegradedFinalAttempt(deps, name, prompt, hit, loopHistory) {
502
502
  clock.cleanup();
503
503
  }
504
504
  if (r.exitCode !== 0 || r.modelError || r.text.trim().length === 0) {
505
- // A wall-clock kill is NOT a loop. The clock above is new here, and
506
- // without this check a degrade that outran its budget was reported as
507
- // "loop budget exhausted", carrying a loop history that did not cause it
508
- // the same mislabel class the worker-kill roster exists to prevent, on
509
- // the very path that clock was added to guard.
505
+ // A wall-clock kill is NOT a loop. Without this check a child that outran
506
+ // its budget is reported as "loop budget exhausted", carrying a loop
507
+ // history that did not cause it the same mislabel class the worker-kill
508
+ // roster exists to prevent, on the very path the clock guards.
510
509
  if (clock.timedOut()) {
511
510
  throw new PhaseTimeoutError(name, deps.timeoutMs ?? PHASE_CHILD_TIMEOUT_MS, 1);
512
511
  }
@@ -2,21 +2,22 @@
2
2
  * ChildStatus — the live status of the child pi currently running under a
3
3
  * status loader: its latest output line and its context usage.
4
4
  *
5
- * Four sites used to keep this state by hand — `let lastLine; let contextUsage;`
5
+ * Without it each spawn site keeps this state by hand — `let lastLine; let contextUsage;`
6
6
  * plus two callbacks (`onChildOutput` writes the line, `onContextUsage` folds a
7
7
  * snapshot through `resolveContextUsage` with the parent window), a reset before
8
- * every child, and a loader whose every tick read both — in `/task-auto`'s
9
- * planning `runChild`, `/task-plan`'s `child`, `buildGateDeps` (an accessor box
10
- * handed to `makeGateChild`), and the single-task `TaskRunner`. The first three
11
- * are one ritual and are now this class; the fourth stays where it is (see
12
- * `orchestrator.ts`: its state is the whole-run `WidgetState`, shared by
13
- * reference with `PhaseContext` and written by the phases themselves).
8
+ * every child, and a loader whose every tick reads both.
9
+ *
10
+ * Three sites share that ritual and are now this class: `/task-auto`'s planning
11
+ * `runChild`, `buildGateDeps`, and `/task-plan` one `new ChildStatus` each, and
12
+ * no others. The single-task `TaskRunner` deliberately stays where it is: its
13
+ * state is the whole-run `WidgetState`, shared by reference with `PhaseContext`
14
+ * and written by the phases themselves (see `orchestrator.ts`).
14
15
  *
15
16
  * `track` is the loader ritual: reset, raise the loader reading this status on
16
17
  * every tick, run, always stop. The status OUTLIVES a track — `buildGateDeps`
17
18
  * shares one across every gate child, and the verify gate raises its own
18
- * gate-wide loader over a child that renders none (`frame: null`), so both must
19
- * see the same object.
19
+ * gate-wide loader over a child that renders none (`frame: null`, reached when
20
+ * `deps.loader === false` in gate-child), so both must see the same object.
20
21
  */
21
22
  import type { ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
22
23
  import type { ContextSnapshot } from '../shared/child-process.js';
@@ -55,12 +56,17 @@ export declare class ChildStatus {
55
56
  };
56
57
  /**
57
58
  * Run `run` under the loader: reset, raise a loader whose every tick is
58
- * `frame()` plus the live line and gauge, and stop it in a `finally` — a
59
- * throwing child must not leave the widget up. `frame` wins on a clash, which
60
- * is how the verify gate shows its deterministic-stage label until the child
61
- * has a line of its own. `frame: null` renders NO loader (the caller already
62
- * has one reading this status) but still resets, so the previous child's
63
- * trailer is cleared either way.
59
+ * `frame()` spread OVER the live line and gauge, and stop it in a `finally`.
60
+ * All four behaviours were run against a fake loader:
61
+ * - the reset lands first, so the previous child's line never reaches the
62
+ * new loader's first tick;
63
+ * - `frame` wins on a clash, because it is spread last which is how the
64
+ * verify gate shows its deterministic-stage label until the child has a
65
+ * line of its own;
66
+ * - `frame: null` raises NO loader at all (the caller already has one
67
+ * reading this status) but still resets;
68
+ * - a child that THROWS still stops the loader, so a failure never leaves
69
+ * the widget up.
64
70
  */
65
71
  track<T>(ctx: ExtensionCommandContext, frame: (() => AutoLoaderState) | null, run: () => Promise<T>): Promise<T>;
66
72
  }
@@ -98,6 +104,7 @@ export declare function runPlanningChild(opts: {
98
104
  }): Promise<string>;
99
105
  /**
100
106
  * Wire a `ChildStatus` as a phase child's stream callbacks — plus the window the
101
- * child must be TOLD, since pi's event stream never reports one (issue #16).
107
+ * child must be TOLD, since pi's `--mode json` stream reports token counts but no
108
+ * context window.
102
109
  */
103
110
  export declare function statusCallbacks(status: ChildStatus): Pick<PhaseDeps, 'onChildOutput' | 'onContextUsage' | 'contextWindow'>;
@@ -2,21 +2,22 @@
2
2
  * ChildStatus — the live status of the child pi currently running under a
3
3
  * status loader: its latest output line and its context usage.
4
4
  *
5
- * Four sites used to keep this state by hand — `let lastLine; let contextUsage;`
5
+ * Without it each spawn site keeps this state by hand — `let lastLine; let contextUsage;`
6
6
  * plus two callbacks (`onChildOutput` writes the line, `onContextUsage` folds a
7
7
  * snapshot through `resolveContextUsage` with the parent window), a reset before
8
- * every child, and a loader whose every tick read both — in `/task-auto`'s
9
- * planning `runChild`, `/task-plan`'s `child`, `buildGateDeps` (an accessor box
10
- * handed to `makeGateChild`), and the single-task `TaskRunner`. The first three
11
- * are one ritual and are now this class; the fourth stays where it is (see
12
- * `orchestrator.ts`: its state is the whole-run `WidgetState`, shared by
13
- * reference with `PhaseContext` and written by the phases themselves).
8
+ * every child, and a loader whose every tick reads both.
9
+ *
10
+ * Three sites share that ritual and are now this class: `/task-auto`'s planning
11
+ * `runChild`, `buildGateDeps`, and `/task-plan` one `new ChildStatus` each, and
12
+ * no others. The single-task `TaskRunner` deliberately stays where it is: its
13
+ * state is the whole-run `WidgetState`, shared by reference with `PhaseContext`
14
+ * and written by the phases themselves (see `orchestrator.ts`).
14
15
  *
15
16
  * `track` is the loader ritual: reset, raise the loader reading this status on
16
17
  * every tick, run, always stop. The status OUTLIVES a track — `buildGateDeps`
17
18
  * shares one across every gate child, and the verify gate raises its own
18
- * gate-wide loader over a child that renders none (`frame: null`), so both must
19
- * see the same object.
19
+ * gate-wide loader over a child that renders none (`frame: null`, reached when
20
+ * `deps.loader === false` in gate-child), so both must see the same object.
20
21
  */
21
22
  import { runPhaseChild } from './child-runner.js';
22
23
  import { resolveContextUsage } from './context-usage.js';
@@ -59,12 +60,17 @@ export class ChildStatus {
59
60
  }
60
61
  /**
61
62
  * Run `run` under the loader: reset, raise a loader whose every tick is
62
- * `frame()` plus the live line and gauge, and stop it in a `finally` — a
63
- * throwing child must not leave the widget up. `frame` wins on a clash, which
64
- * is how the verify gate shows its deterministic-stage label until the child
65
- * has a line of its own. `frame: null` renders NO loader (the caller already
66
- * has one reading this status) but still resets, so the previous child's
67
- * trailer is cleared either way.
63
+ * `frame()` spread OVER the live line and gauge, and stop it in a `finally`.
64
+ * All four behaviours were run against a fake loader:
65
+ * - the reset lands first, so the previous child's line never reaches the
66
+ * new loader's first tick;
67
+ * - `frame` wins on a clash, because it is spread last which is how the
68
+ * verify gate shows its deterministic-stage label until the child has a
69
+ * line of its own;
70
+ * - `frame: null` raises NO loader at all (the caller already has one
71
+ * reading this status) but still resets;
72
+ * - a child that THROWS still stops the loader, so a failure never leaves
73
+ * the widget up.
68
74
  */
69
75
  async track(ctx, frame, run) {
70
76
  this.reset();
@@ -99,7 +105,8 @@ export async function runPlanningChild(opts) {
99
105
  }
100
106
  /**
101
107
  * Wire a `ChildStatus` as a phase child's stream callbacks — plus the window the
102
- * child must be TOLD, since pi's event stream never reports one (issue #16).
108
+ * child must be TOLD, since pi's `--mode json` stream reports token counts but no
109
+ * context window.
103
110
  */
104
111
  export function statusCallbacks(status) {
105
112
  return {
@@ -1,11 +1,18 @@
1
1
  /**
2
2
  * clamp-output — the ONE trail-side ceiling for captured tool output.
3
3
  *
4
- * `appendGateRecord` flattens newlines to spaces, so the gate trail stays one line
5
- * per entry; this caps the volume, because a wedged tool can emit megabytes.
6
- * Extracted from task-gates.ts so every probe that carries evidence into a failure
7
- * detail clamps the SAME way: a second clamp with a second ceiling is how two
8
- * trails start disagreeing about what was captured.
4
+ * `appendGateRecord` already collapses every run of whitespace-around-newline
5
+ * into a single space, so each trail entry is one line. What it does NOT do is
6
+ * bound the LENGTH, which is this. A wedged tool has no upper limit on what it
7
+ * prints, and the trail is a task file a human reads.
8
+ *
9
+ * Both probes that carry tool output into a failure detail — the render check and
10
+ * the gate's own command records — call THIS function, so the two trails cannot
11
+ * start disagreeing about what was captured. A second clamp with a second ceiling
12
+ * is exactly how that happens.
13
+ *
14
+ * At the boundary: 1200 characters pass through untouched, 1201 or more come back
15
+ * clamped to 1200 plus a single ellipsis.
9
16
  */
10
17
  const TRAIL_OUTPUT_MAX_CHARS = 1200;
11
18
  export function clampOutput(output) {