@mjasnikovs/pi-task 0.38.29 → 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 +16 -9
  104. package/dist/task/context-usage.js +16 -9
  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 +36 -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 +179 -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 +29 -39
  231. package/dist/task/research-worker.js +37 -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 +69 -71
  338. package/dist/workers/pi-worker-core.js +100 -109
  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 +21 -14
  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
@@ -2,22 +2,21 @@
2
2
  * boot-probe — does the assembled product actually START, and does the page it
3
3
  * serves actually render?
4
4
  *
5
- * Lifted out of final-gate.ts, where it was 42% of a 2185-line file and the largest
6
- * of seven unrelated concerns. Nothing inside `src/` imported any of it except the
7
- * one call site in `runFinalIntegrationGate`; the de-facto module boundary already
8
- * existed in the CONSUMERS — seven validation harnesses under `scripts/` import
9
- * exactly this surface and nothing else from the gate.
5
+ * Its one consumer is final-gate.ts, which imports twelve names from here, calls
6
+ * `discoverBootCommand` at three sites and `runBootSection` at one, and re-exports
7
+ * six of them plus `BootSectionVerdict` for its own callers.
10
8
  *
11
- * The public surface is those six names: `discoverBootCommand`, `detectsServedApp`,
12
- * `runBootCheck`, `bootSkipVerdict`, `nonLaunchScriptReason` and `BootDeps`. The
13
- * listener parsers stay exported because their tests are worth keeping, but they are
14
- * now a sibling module's surface rather than noise in the gate's. Everything else —
15
- * orphan-port recovery, pgid probing, the HTTP evidence probe, port reservation —
16
- * is private, which it could not be while it shared a file with the gate.
9
+ * The entry points are `discoverBootCommand`, `detectsServedApp`, `runBootCheck`,
10
+ * `runBootSection` and `bootSkipVerdict`. Much else is exported too — the three
11
+ * listener parsers, the port helpers (`pickFreePort`, `isPortFree`,
12
+ * `preferredDeclaredPort`), `canEnumerateListeners`, `defaultFindPortHolder`,
13
+ * `recoverOrphanPort`, `rejectedLaunchScript` because the gate or their own
14
+ * tests reach for them. Only the pgid helpers, the HTTP evidence probe, the reap
15
+ * and the spawn/teardown defaults are private.
17
16
  *
18
- * The boot check is deliberately NOT a CLOSURE_SCANS row: it is an async, stateful,
19
- * port-binding exercise, and every row would need its own escape hatch. This is a
20
- * file move, not a re-shaping.
17
+ * The boot check is deliberately NOT a CLOSURE_SCANS row (final-gate.ts): it is an
18
+ * async, stateful, port-binding exercise, and every row would need its own escape
19
+ * hatch.
21
20
  */
22
21
  import { spawn, spawnSync } from 'node:child_process';
23
22
  import { existsSync, readFileSync } from 'node:fs';
@@ -98,9 +97,9 @@ function isWatcherOnlyMultiplexer(member, scripts) {
98
97
  }
99
98
  /**
100
99
  * Why this script is NOT a launch of the shipped app, or null when it plausibly
101
- * is one (mx5 run 18, validated).
100
+ * is one.
102
101
  *
103
- * Run 18's boot command resolved to `bun run dev`, whose body is
102
+ * A boot command can resolve to `bun run dev`, whose body is
104
103
  * `docker compose -f docker-compose.dev.yml up -d && until docker compose … pg_isready
105
104
  * … && concurrently "bun run dev:css" "bun run dev:js" "bun run --watch
106
105
  * src/server/index.ts"`. The gate sandbox has no docker, so the chain died at 127 and
@@ -112,15 +111,22 @@ function isWatcherOnlyMultiplexer(member, scripts) {
112
111
  * producing an unfalsifiable skip.
113
112
  *
114
113
  * CONSERVATIVE AND LEXICAL BY CONSTRUCTION. Only two shapes are rejected, both
115
- * decidable from the script text alone:
114
+ * decidable from the script text alone, and both run against every case named
115
+ * below:
116
116
  * 1. the chain OPENS with container orchestration (docker/podman/nerdctl … up|start|run);
117
117
  * 2. the whole body is a multiplexer (concurrently/npm-run-all/run-p/run-s/turbo)
118
118
  * whose every child is an ASSET watcher in watch mode (tailwind/tsc/esbuild/…),
119
119
  * i.e. nothing in it can ever listen.
120
- * Anything else — `vite`, `next dev`, `node dist/index.js`, `nodemon`, `bun --watch
121
- * src/index.ts`, and any multiplexer with one non-asset child — is accepted
122
- * unchanged. Deciding whether a watcher actually SERVES is not attempted here; that
123
- * is exactly what the static serve-entry check is for.
120
+ * Anything else — `vite`, `next dev`, `node dist/index.js`, `nodemon`, `bun run
121
+ * --watch src/index.ts`, and any multiplexer with one non-asset child — is accepted
122
+ * unchanged. All of those were run and accepted; so was a bare
123
+ * `tailwindcss --watch`, which is only rejected INSIDE a multiplexer. A chain
124
+ * opening `docker compose … up` is rejected even when a real launch follows it,
125
+ * while `bun run docker-compose.dev.yml` is accepted, since the verb must be a
126
+ * bare token.
127
+ *
128
+ * Deciding whether a watcher actually SERVES is not attempted here; that is exactly
129
+ * what the static serve-entry check is for.
124
130
  */
125
131
  export function nonLaunchScriptReason(body, scripts = {}) {
126
132
  const members = chainMembers(body);
@@ -139,7 +145,7 @@ export function nonLaunchScriptReason(body, scripts = {}) {
139
145
  * else `dev`; Makefile `run`). null means the project has nothing to boot —
140
146
  * the boot check degrades to nothing-to-run.
141
147
  *
142
- * A script that is not a LAUNCH at all (nonLaunchScriptReason — mx5 run 18's
148
+ * A script that is not a LAUNCH at all (nonLaunchScriptReason — an
143
149
  * `docker compose up` orchestrator) is rejected here and falls through to the
144
150
  * next candidate, then to null. Discovering nothing is strictly better than
145
151
  * discovering something unfalsifiable: an env-gap skip of an orchestration script
@@ -161,7 +167,7 @@ export function discoverBootCommand(cwd) {
161
167
  }
162
168
  /**
163
169
  * The launch script that EXISTS but was rejected as not-a-launch, if any. Without
164
- * this the rejection would trade run 18's unfalsifiable skip for pure silence: no
170
+ * this the rejection would trade an unfalsifiable skip for pure silence: no
165
171
  * boot command means bootSkipVerdict has no label to name, and a project whose test
166
172
  * suite ran still reports `observed > 0`, so unobservedVerdict stays quiet too. A
167
173
  * served app whose only declared launch script cannot start it was not observed to
@@ -245,11 +251,14 @@ export function parseSsListeners(stdout) {
245
251
  return out;
246
252
  }
247
253
  /**
248
- * `netstat -tlnp` rows → {pid, port} (mx5 run 14, validated: the agent-sandbox
249
- * image ships NEITHER ss NOR lsof only ps and netstat so the served-app boot
250
- * check could never observe a listener and failed unfalsifiably). The pid rides
251
- * in the trailing "PID/Program name" column ("1234/bun"); rows the kernel will
252
- * not attribute to us print "-" there and are skipped.
254
+ * `netstat -tlnp` rows → {pid, port}. Three parsers exist because no single
255
+ * enumerator is present everywhere a box may ship netstat and no ss, or ss and
256
+ * no netstat and without SOME enumerator the served-app check can never observe
257
+ * a listener and fails unfalsifiably.
258
+ *
259
+ * The pid rides in the trailing "PID/Program name" column ("1234/bun"); rows the
260
+ * kernel will not attribute to us print "-" there and are skipped. Both run as
261
+ * described.
253
262
  */
254
263
  export function parseNetstatListeners(stdout) {
255
264
  const out = [];
@@ -309,7 +318,7 @@ function listeningSockets() {
309
318
  return [];
310
319
  }
311
320
  /**
312
- * Can ANY socket-enumeration tool run here at all? (mx5 run 14: the sandbox had
321
+ * Can ANY socket-enumeration tool run here at all? (a sandbox may have
313
322
  * none, so `groupHasListener` returned false forever and the boot check emitted
314
323
  * "never opened a listening socket" no matter what the app did — an unfalsifiable
315
324
  * FAIL that failed a run whose app demonstrably served.) This is a CAPABILITY
@@ -318,8 +327,10 @@ function listeningSockets() {
318
327
  * must degrade to the survival rule exactly like win32 — never a false FAIL on a
319
328
  * platform we cannot probe.
320
329
  *
321
- * "Ran" = spawned without ENOENT and either exited 0 or printed something (lsof
322
- * exits 1 on an empty match set; a netstat that rejects `-p` prints nothing).
330
+ * "Ran" = spawned without ENOENT and either exited 0 or printed something. Both
331
+ * halves of that disjunction are load-bearing: `lsof` really does exit 1 on an
332
+ * empty match set (checked), and a netstat that rejects `-p` prints nothing, so
333
+ * neither an exit code nor output alone would answer the question.
323
334
  * Memoised: the answer is a property of the box, not of the run.
324
335
  */
325
336
  let listenerToolCapability = null;
@@ -341,10 +352,10 @@ export function canEnumerateListeners() {
341
352
  }
342
353
  /**
343
354
  * A free TCP port on the loopback interface, or null if one cannot be reserved.
344
- * The boot check hands this to the child as PORT so that a successful HTTP
345
- * request to it is OWNERSHIP evidence: nobody else knows the number (mx5 runs
346
- * 8/10/11 orphaned servers from earlier checks answered curl on the
347
- * conventional :3000 and passed checks the app had not earned).
355
+ * The boot check hands this to the child as PORT so that a successful HTTP request
356
+ * to it is OWNERSHIP evidence: nobody else knows the number, whereas an orphaned
357
+ * server left by an earlier check still answers on a conventional port and would
358
+ * pass a check the app had not earned.
348
359
  */
349
360
  export function pickFreePort() {
350
361
  return new Promise(resolve => {
@@ -362,7 +373,8 @@ export function pickFreePort() {
362
373
  }
363
374
  });
364
375
  }
365
- /** Can we bind 127.0.0.1:`port` right now? (Free ⇒ the boot child can have it.) */
376
+ /** Can we bind 127.0.0.1:`port` right now? Free ⇒ the boot child can have it.
377
+ * Run: true for a just-reserved port, false while anything holds it. */
366
378
  export function isPortFree(port) {
367
379
  return new Promise(resolve => {
368
380
  try {
@@ -388,8 +400,9 @@ export async function preferredDeclaredPort(cwd) {
388
400
  return (await isPortFree(port)) ? port : null;
389
401
  }
390
402
  /**
391
- * Does anything answer HTTP on 127.0.0.1:`port`? Any response at all (404, 500
392
- * a status is a listener) counts; only a connection error or timeout is a no.
403
+ * Does anything answer HTTP on 127.0.0.1:`port`? Any response at all counts a
404
+ * status IS a listener, and a live server answering 404 on an unknown path is the
405
+ * ordinary case — so only a connection error or a timeout is a no.
393
406
  * Runs in a throwaway child of our own runtime so it needs no curl on PATH and
394
407
  * stays synchronous inside the boot poll.
395
408
  */
@@ -493,9 +506,10 @@ function defaultSpawnBoot(bin, args, o) {
493
506
  /**
494
507
  * The real group teardown, best-effort. A group already gone is not an error.
495
508
  *
496
- * Windows has no process groups / negative-pid kill: `taskkill /T` tears down the
497
- * whole tree (the detached child plus any grandchildren it spawned) and `/F`
498
- * forces it, so the SIGTERM→SIGKILL escalation collapses to one idempotent call.
509
+ * On POSIX the negative pid signals the whole group, which is what makes a
510
+ * `detached` spawn reapable together with anything it backgrounded. Windows has
511
+ * neither process groups nor a negative-pid kill, so that branch shells out to
512
+ * `taskkill /T /F` as a single forced tree teardown instead of escalating.
499
513
  */
500
514
  function defaultKillGroup(pid, sig) {
501
515
  try {
@@ -511,8 +525,11 @@ function defaultKillGroup(pid, sig) {
511
525
  }
512
526
  }
513
527
  /**
514
- * Exercise the start command ONCE. For a CLI project (`expectServer` false) the
515
- * command's own fate within the grace window decides:
528
+ * Exercise the start command ONCE. All four outcomes below were run against real
529
+ * child processes in throwaway projects.
530
+ *
531
+ * For a CLI project (`expectServer` false) the command's own fate within the grace
532
+ * window decides:
516
533
  *
517
534
  * - non-zero exit (or signal death) before the window closes → FAIL, output tail;
518
535
  * - exit 0 before the window closes → PASS (a CLI-style "run" that finished);
@@ -522,22 +539,21 @@ function defaultKillGroup(pid, sig) {
522
539
  * For a SERVED app (`expectServer` true — the spec/plan promised an HTTP server) mere
523
540
  * survival is not enough: a watcher (`dev` = tailwind/bundler --watch) stays alive
524
541
  * forever without ever listening, and a type-only entrypoint exits 0 in <1s having
525
- * served nothing (mx5 run 10 — both were blessed by the survival rule). The boot then
542
+ * served nothing. The boot then
526
543
  * PASSes only once a LISTENing socket owned by our process group is observed; if the
527
544
  * command exits, or the grace window closes, with no listener ever seen → FAIL naming
528
545
  * that a listening server was expected.
529
546
  *
530
- * OBSERVABILITY is a precondition of that FAIL (mx5 run 14, validated). The listener
531
- * requirement needs pgid-attributed socket enumeration; win32 has none, and neither
532
- * does a Linux image shipping no ss/netstat/lsof run 14's sandbox was exactly that,
533
- * so the check emitted "never opened a listening socket" against an app that
534
- * demonstrably served, three autofix passes could not falsify it, and the run was
535
- * recorded failed. Two defences, in order:
547
+ * OBSERVABILITY is a precondition of that FAIL. The listener requirement needs
548
+ * pgid-attributed socket enumeration; win32 has none, and neither does a Linux
549
+ * image shipping no ss, netstat or lsof. Without a defence the check would emit
550
+ * "never opened a listening socket" against an app that demonstrably serves, and
551
+ * no amount of fixing could falsify it. Two defences, in order:
536
552
  *
537
553
  * - the child is spawned with a freshly reserved, otherwise-unused PORT, and an
538
554
  * HTTP answer on THAT port proves a listener regardless of tooling. The private
539
555
  * port is what makes the HTTP probe trustworthy: an orphaned server from an
540
- * earlier check answers on :3000, but nobody else knows this number.
556
+ * earlier check answers on:3000, but nobody else knows this number.
541
557
  * - if nothing can enumerate listeners AND the assigned port never answered, the
542
558
  * served-app requirement is unobservable here, so `expectServer` collapses to
543
559
  * the survival rule and the PASS is stamped UNOBSERVED. An app that ignores PORT
@@ -545,8 +561,10 @@ function defaultKillGroup(pid, sig) {
545
561
  * not an app defect, and it may not be reported as one.
546
562
  *
547
563
  * A child that EXITS non-zero still FAILs in every environment: "the process died"
548
- * needs no socket probe, so run 14's original true positive (a `--hot` runtime
549
- * pinning a crashed app) stays reportable wherever the tooling exists.
564
+ * needs no socket probe. Confirmed a start script exiting 3 comes back as
565
+ * `exited 3` with the output tail attached, with no listener question asked. That
566
+ * is what keeps a crashed app reportable even where a hot-reloading runtime would
567
+ * otherwise hold the process open.
550
568
  *
551
569
  * Env-gap contract as everywhere: spawn error (ENOENT) or a command-not-found
552
570
  * inside the chain (exit 127, or the runner's own wording where the platform
@@ -563,11 +581,11 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
563
581
  // a client whose base URL was baked in at build time calls that origin and no
564
582
  // other, so serving it anywhere else makes the whole authenticated half
565
583
  // unobservable. Anything else — no declaration, a port already held — falls back
566
- // to the freshly reserved private port that run 14's ownership evidence needs.
584
+ // to the freshly reserved private port that the ownership evidence needs.
567
585
  const noPreference = () => Promise.resolve(null);
568
586
  const preferred = expectServer ? await (opts.deps?.preferredPort ?? noPreference)() : null;
569
587
  const assignedPort = preferred ?? (expectServer ? await (opts.deps?.pickPort ?? pickFreePort)() : null);
570
- // Runner resolution (mx5 run 16): same contract as runGateCommand — resolve
588
+ // Runner resolution: same contract as runGateCommand — resolve
571
589
  // the runner and carry its directory on PATH so the boot script's own chain
572
590
  // can re-invoke it.
573
591
  const runner = resolveRunner(bin);
@@ -610,7 +628,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
610
628
  };
611
629
  const reapGroup = opts.deps?.killGroup ?? defaultKillGroup;
612
630
  const killGroup = (sig) => {
613
- // Truthiness, deliberately: `process.kill(-0, sig)` signals the
631
+ // Truthiness, deliberately: `process.kill(0, sig)` signals the
614
632
  // CALLER's own process group, so a pid of 0 turns a best-effort
615
633
  // teardown into self-termination. Node's spawn never yields 0, but
616
634
  // `spawnBoot` is a seam now and a fake or future child could.
@@ -630,7 +648,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
630
648
  };
631
649
  // Served apps only: poll for a listening socket owned by our process group.
632
650
  // As soon as one appears the boot has demonstrably served → run the render
633
- // check against the LIVE listener (mx5 runs 8/11: a listener that serves a
651
+ // check against the LIVE listener (a listener that serves a
634
652
  // permanently blank page passed every curl-shaped check), then PASS/FAIL.
635
653
  // The probe is spawnSync, so the interval cannot re-enter mid-check.
636
654
  // The deep probe is asynchronous (it drives a browser session), so the
@@ -671,7 +689,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
671
689
  : undefined);
672
690
  }
673
691
  // The page renders. Now sign in and prove the AUTHENTICATED half
674
- // is alive (mx5 run 17): the server accepted the login and the
692
+ // is alive: the server accepted the login and the
675
693
  // client never used it. Async, so the interval is held off by
676
694
  // `probing` until this settles.
677
695
  probing = true;
@@ -705,7 +723,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
705
723
  // Blind here (no enumeration tool, and the assigned port never
706
724
  // answered) ⇒ we cannot tell "never listened" from "ignores PORT".
707
725
  // Survival rule, stamped UNOBSERVED — an observer limitation is not
708
- // an app defect (mx5 run 14).
726
+ // an app defect.
709
727
  if (!canEnumerate)
710
728
  return passAndKill(UNOBSERVED_LISTENER_NOTE);
711
729
  settle({
@@ -745,7 +763,7 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
745
763
  const tail = outputTail(out, err);
746
764
  // A bind collision is an environment condition, not an app defect — hand
747
765
  // it back distinctly so the gate can reap our own orphan and retry rather
748
- // than reporting the app "crashed" (mx5 run 9 item 3).
766
+ // than reporting the app "crashed".
749
767
  if (isAddressInUse(`${out}\n${err}`)) {
750
768
  settle({
751
769
  outcome: 'orphan-port',
@@ -760,9 +778,9 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
760
778
  }
761
779
  /**
762
780
  * The SAME third verdict, at the door unobservedVerdict cannot reach: the boot
763
- * check specifically (mx5 run 18, validated).
781
+ * check specifically.
764
782
  *
765
- * Run 18 shipped an app with no HTTP server behind a converged final gate. Its
783
+ * A run can ship an app with no HTTP server behind a converged final gate. Its
766
784
  * `src/server/index.ts` ends at `export {app}` — no `Bun.serve`, no
767
785
  * `export default app`, no `start` script — so `bun run src/server/index.ts` exits
768
786
  * 0 immediately and the product cannot be started at all. The gate's boot command
@@ -771,26 +789,27 @@ export async function runBootCheck(cwd, [bin, args], graceMs = 10_000, opts = {}
771
789
  * contribute nothing to `dynObserved`, and `bun run test`, `test:ct`, `build`,
772
790
  * `lint`, `seed` and `migrate` all ran — so `dynObserved > 0`, the full-skip
773
791
  * blindness guard (observabilityGapFailure) stayed correctly quiet, and the trail
774
- * read `final-gate: autofix converged — statics + … passed` with 24/24 tasks green.
792
+ * report then reads `final-gate: autofix converged — statics + … passed`, with
793
+ * every task green.
775
794
  *
776
795
  * The defect is that "the app was never observed to boot" and "the app booted
777
- * fine" produced BYTE-IDENTICAL gate output. That is the class scripts/ab-verdict.ts
796
+ * fine" produced BYTE-IDENTICAL gate output. That is the class the verdict check
778
797
  * exists to kill one layer up: absence of evidence rendered in the shape of
779
798
  * evidence. So a discovered-but-skipped boot now names itself, and — unlike every
780
799
  * other skip — it CANNOT be cancelled by observations from other commands.
781
- * Component tests are the trap here, not the alibi: run 18 had 51 green Playwright
800
+ * Component tests are the trap here, not the alibi: a suite of green Playwright
782
801
  * CT tests, and CT mounts components in a browser without ever assembling or
783
802
  * starting the server.
784
803
  *
785
804
  * DECIDED, do not silently re-open:
786
805
  * - NOT a FAIL. A boot skip on a docker-less box is a genuine environment gap, and
787
- * failing it re-creates run 16's unfalsifiable-FAIL mistake pointing the other
806
+ * failing it re-creates the unfalsifiable-FAIL mistake pointing the other
788
807
  * way. UNOBSERVED blocks nothing while being loud and durable (the caller records
789
808
  * it as final-gate debt the next run re-surfaces), and it keeps "boot never ran"
790
809
  * out of the autofix child's seed — a child cannot fix a missing docker, so the
791
810
  * highest-probability response would be to FABRICATE a bootable command, the
792
811
  * class that refuted the `## verified tooling` harvest.
793
- * - BOTH skip flavours count. Run 18's skip carried `spawnFailed: false` (127 inside
812
+ * - BOTH skip flavours count. A skip can carry `spawnFailed: false` (127 inside
794
813
  * the script chain, not an ENOENT on the runner), so keying off spawnFailed would
795
814
  * have missed the actual defect.
796
815
  * - SERVED APPS ONLY. `expectServer === false` (a CLI/library project) is fenced off
@@ -850,13 +869,13 @@ export async function runBootSection(cwd, opts = {}) {
850
869
  return { observed: false, warnings };
851
870
  }
852
871
  const label = `${boot[0]} ${boot[1].join(' ')}`;
853
- // Render check (mx5 runs 8/11): for a served app, load the live page in a
872
+ // Render check: for a served app, load the live page in a
854
873
  // headless browser and judge the RENDERED DOM — curl can't run JS, so a
855
874
  // blank-mount app passed every prior "renders" check. runRenderCheck
856
875
  // env-gap-SKIPs when no browser exists, so a box without one never gets a
857
876
  // false FAIL.
858
877
  //
859
- // Authenticated deep-render check (mx5 run 17): the page above renders, so now
878
+ // Authenticated deep-render check: the page above renders, so now
860
879
  // sign in with the account the project's own dotenv declares (the same
861
880
  // ADMIN_PHONE/ADMIN_PASSWORD the launch contract's seed step consumes) and
862
881
  // require the session to actually work. WEB-ONLY by construction — it hangs off
@@ -887,18 +906,18 @@ export async function runBootSection(cwd, opts = {}) {
887
906
  if (unobserved !== null)
888
907
  verdict.unobservedNote = unobserved;
889
908
  if (b.outcome === 'fail') {
890
- // OBSERVED (nexttask 19A). Every path that produces `fail` here is a probe
909
+ // OBSERVED. Every path that produces `fail` here is a probe
891
910
  // that looked: the render judge saw an empty body, the deep session saw the
892
911
  // authenticated half dead, the enumerator saw no listener, or the launch
893
912
  // command itself exited non-zero. The one condition that means "we could not
894
- // look" — no ss/netstat/lsof, mx5 run 14 — returns PASS stamped UNOBSERVED
913
+ // look" — no ss/netstat/lsof — returns PASS stamped UNOBSERVED
895
914
  // and never reaches here.
896
915
  verdict.failure = { detail: `boot check: \`${label}\` ${b.detail}`, rank: 0, observed: true };
897
916
  }
898
917
  else if (b.outcome === 'orphan-port') {
899
918
  // Could not clear the port. Distinct HARNESS diagnosis, never a bare app
900
919
  // FAIL: name the port and (when known) the process squatting on it. The
901
- // holder lookup reads the SAME deps the boot ran under — it used to be a
920
+ // holder lookup reads the SAME deps the boot ran under. A
902
921
  // second reach into `BootDeps` from the gate, one layer away from the run.
903
922
  const holder = b.port !== null ? (deps.findPortHolder ?? defaultFindPortHolder)(b.port) : null;
904
923
  const who = holder ? ` — held by an orphaned process (pid ${holder.pid}: ${holder.command})`
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * THE PROBLEM. While a /task-auto run is in flight, the host's interactive main
5
5
  * loop is parked at `await session.prompt("/task-auto …")` and never loops back
6
- * to read input. pi's editor submit handler (interactive-mode.js) has two paths:
6
+ * to read input. pi's editor submit handler — `modes/interactive/interactive-mode.js`
7
+ * — has two paths, and both are verbatim in the installed package:
7
8
  *
8
- * - streaming → session.prompt(text, {streamingBehavior:"steer"}), and
9
- * agent-session.prompt() runs extension commands immediately.
10
- * - not streaming → pendingUserInputs.push(text) — a queue only the parked
11
- * main loop drains.
9
+ * - streaming → `session.prompt(text, {streamingBehavior: "steer"})`. pi's own
10
+ * comment on that branch says it "handles extension commands
11
+ * (execute immediately)".
12
+ * - not streaming → `pendingUserInputs.push(text)`, reached only when no
13
+ * `onInputCallback` is registered — i.e. exactly when the main
14
+ * loop is parked. `getUserInput()` is the sole drain.
12
15
  *
13
16
  * The host session is NOT streaming for most of a run: the spec phases and every
14
17
  * gate are child `pi` processes, not host turns. So a /task-auto-cancel typed
@@ -16,12 +19,17 @@
16
19
  * already finished — by which point `autoRunning` is false and it answers "No
17
20
  * /task-auto loop is running." The command was, for most of a run, inert.
18
21
  *
19
- * THE FIX. `ctx.ui.onTerminalInput` (→ TUI.addInputListener) is a raw stdin
20
- * listener that the TUI dispatches BEFORE the focused component sees the bytes,
21
- * independently of the parked main loop. We watch for the submit key, read what
22
- * the editor is holding, and if it is the cancel command we raise the request
23
- * ourselves and `consume` the keystroke so the line is never queued for a
24
- * post-run replay of the confusing "no loop is running" message.
22
+ * THE FIX. `ctx.ui.onTerminalInput` (→ `TUI.addInputListener`) is a raw stdin
23
+ * listener, and the ordering it depends on is real: `handleTerminalInput` runs
24
+ * every registered input listener FIRST, returning immediately on the first one
25
+ * that answers `{consume: true}`, before any focused-component dispatch below it.
26
+ * That happens independently of the parked main loop.
27
+ *
28
+ * So we watch for the submit key, read what the editor is holding, and if it is
29
+ * the cancel command we raise the request ourselves and `consume` the keystroke —
30
+ * otherwise the line sits in the queue and replays after the run as the confusing
31
+ * "No /task-auto loop is running." auto-orchestrator emits when nothing is in
32
+ * flight.
25
33
  *
26
34
  * WHAT ELSE IT NOW CARRIES. The same interception makes the terminal behave like
27
35
  * the browser for everything else typed mid-run, instead of feeding pi's queue:
@@ -32,7 +40,9 @@
32
40
  * task turn, rather than starting a competing turn or being replayed after
33
41
  * the run.
34
42
  *
35
- * Three things are deliberately NOT intercepted, and each would be a regression:
43
+ * Three things are deliberately NOT intercepted, and each would be a regression
44
+ * (the key test: ESC against a full editor is not a submission, and neither is any
45
+ * ordinary character):
36
46
  * - a keystroke that is not a submit, so typing and history are untouched;
37
47
  * - anything typed while a prompt/dialog is open (the raw listener sees keys
38
48
  * BEFORE the focused component, so swallowing here would eat the user's
@@ -65,10 +75,14 @@ export declare function installCancelListener(ctx: ExtensionCommandContext, onCa
65
75
  * Begin intercepting mid-run terminal input. Refcounted, because runs nest:
66
76
  * /task-auto arms for its loop and every task inside it arms again, so a plain
67
77
  * "replace" would let the first inner run's end silently un-arm the rest of the
68
- * loop. The first `onCancel` wins /task-auto passes one so a typed
69
- * /task-auto-cancel can post its acknowledgement through the live ctx; a plain
70
- * /task passes none and lets the generic bridge dispatch handle that command
71
- * like any other.
78
+ * loop. Run: three nested arms produce ONE install and take three disarms to
79
+ * release.
80
+ *
81
+ * The first `onCancel` wins — /task-auto passes one so a typed /task-auto-cancel
82
+ * can post its acknowledgement through the live ctx; a plain /task passes none
83
+ * and lets the generic bridge dispatch handle that command like any other. An arm
84
+ * that adds a callback where there was none upgrades in place, reinstalling
85
+ * against the same refcount rather than starting a second one.
72
86
  */
73
87
  export declare function armCancelListener(ctx: ExtensionCommandContext, onCancel?: (live: ExtensionCommandContext) => void): void;
74
88
  /**
@@ -1,11 +1,14 @@
1
1
  import { holdInput, isRunActive } from './mid-run-input.js';
2
2
  import { getBridge } from '../remote/bridge.js';
3
3
  import { getState } from '../remote/session-state.js';
4
- /** The command this listener delivers. Accepts a leading slash only matching
5
- * bare "task-auto-cancel" would fire on prose about the command. */
4
+ /** The command this listener delivers. Anchored at both ends and requiring the
5
+ * leading slash, so bare "task-auto-cancel", "/task-auto-cancel now" and
6
+ * "please run /task-auto-cancel" all fail to match — prose about the command must
7
+ * never fire it. Surrounding whitespace is tolerated. */
6
8
  const CANCEL_RE = /^\/task-auto-cancel\s*$/;
7
9
  /** Enter, in the encodings a terminal actually sends. A bare "\n" is what many
8
- * terminals emit in raw mode; "\r" is the usual CR. */
10
+ * terminals emit in raw mode; "\r" is the usual CR; "\r\n" covers the pair. All
11
+ * three submit; nothing else does, ESC included. */
9
12
  function isSubmitKey(data) {
10
13
  return data === '\r' || data === '\n' || data === '\r\n';
11
14
  }
@@ -132,10 +135,12 @@ export function installCancelListener(ctx, onCancel) {
132
135
  }
133
136
  // ─── Armed listener (survives session replacement) ────────────────────────────
134
137
  /**
135
- * The listener does NOT survive `ctx.newSession()`. pi's InteractiveMode
136
- * registers `setBeforeSessionInvalidate(() => this.resetExtensionUI())`, and
137
- * `resetExtensionUI` calls `clearExtensionTerminalInputListeners()` — so every
138
- * per-task session replacement silently drops it. That replacement happens at
138
+ * The listener does NOT survive `ctx.newSession()`, and the chain is all three
139
+ * links, each present in the installed package: `InteractiveMode` registers
140
+ * `setBeforeSessionInvalidate(() => this.resetExtensionUI())`, `resetExtensionUI`
141
+ * calls `clearExtensionTerminalInputListeners()`, and that clears the very set
142
+ * this listener lives in — so every per-task session replacement silently drops
143
+ * it. That replacement happens at
139
144
  * the START of each task, which is exactly the window this listener exists to
140
145
  * cover, so the run must re-arm against the fresh ctx as soon as one appears
141
146
  * (runSingleTask, where the new ctx is also handed to the remote bridge).
@@ -148,10 +153,14 @@ let armed = null;
148
153
  * Begin intercepting mid-run terminal input. Refcounted, because runs nest:
149
154
  * /task-auto arms for its loop and every task inside it arms again, so a plain
150
155
  * "replace" would let the first inner run's end silently un-arm the rest of the
151
- * loop. The first `onCancel` wins /task-auto passes one so a typed
152
- * /task-auto-cancel can post its acknowledgement through the live ctx; a plain
153
- * /task passes none and lets the generic bridge dispatch handle that command
154
- * like any other.
156
+ * loop. Run: three nested arms produce ONE install and take three disarms to
157
+ * release.
158
+ *
159
+ * The first `onCancel` wins — /task-auto passes one so a typed /task-auto-cancel
160
+ * can post its acknowledgement through the live ctx; a plain /task passes none
161
+ * and lets the generic bridge dispatch handle that command like any other. An arm
162
+ * that adds a callback where there was none upgrades in place, reinstalling
163
+ * against the same refcount rather than starting a second one.
155
164
  */
156
165
  export function armCancelListener(ctx, onCancel) {
157
166
  if (armed) {
@@ -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
@@ -29,8 +35,9 @@
29
35
  * work is written but unverified and uncommitted; the gates are what make it
30
36
  * durable. Cancel is observed on the far side, at loop-top.
31
37
  */
32
- /** Every place the cancel flag is polled. Kept as a closed union so the A/B
33
- * harness and the tests enumerate the same set the loop does. */
38
+ /** Every place the cancel flag is polled. A closed union so the tests enumerate
39
+ * the same set the loop does — and they do: cancel-points.test.ts asserts on the
40
+ * recorded trail rather than on the loop's own bookkeeping. */
34
41
  export type CancelCheckpoint = 'loop-top' | 'pre-task' | 'pre-final-gate' | `phase:${string}`;
35
42
  export declare function requestCancel(): void;
36
43
  export declare function isCancelRequested(): boolean;
@@ -44,7 +51,7 @@ export declare function isCancelRequested(): boolean;
44
51
  * of a trial to get a clean slate.
45
52
  */
46
53
  export declare function resetCancel(): void;
47
- /** Drop the recorded checkpoint trail (A/B + tests). */
54
+ /** Drop the recorded checkpoint trail. */
48
55
  export declare function resetCheckpointTrail(): void;
49
56
  /**
50
57
  * Poll the cancel flag at a safe checkpoint. Records the visit either way, so a
@@ -54,5 +61,5 @@ export declare function resetCheckpointTrail(): void;
54
61
  * @returns true when the caller must stop here.
55
62
  */
56
63
  export declare function cancelCheckpoint(where: CancelCheckpoint): boolean;
57
- /** Checkpoints crossed since the last reset (A/B + tests). */
64
+ /** Checkpoints crossed since the last reset. */
58
65
  export declare function checkpointsCrossed(): readonly CancelCheckpoint[];