@mjasnikovs/pi-task 0.38.29 → 0.38.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/dist/config/config.d.ts +70 -70
  2. package/dist/config/config.js +26 -35
  3. package/dist/config/extension-list.d.ts +6 -5
  4. package/dist/config/extension-list.js +3 -2
  5. package/dist/config/reasoning-args.d.ts +9 -7
  6. package/dist/config/reasoning-args.js +12 -10
  7. package/dist/config/reasoning.d.ts +44 -105
  8. package/dist/config/reasoning.js +27 -704
  9. package/dist/config/register.d.ts +34 -48
  10. package/dist/config/register.js +41 -51
  11. package/dist/config/tool-list.d.ts +16 -16
  12. package/dist/config/tool-list.js +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/remote/bridge.d.ts +19 -10
  15. package/dist/remote/bridge.js +3 -2
  16. package/dist/remote/broadcast.js +3 -1
  17. package/dist/remote/events.js +12 -11
  18. package/dist/remote/history.d.ts +1 -1
  19. package/dist/remote/protocol.d.ts +6 -3
  20. package/dist/remote/protocol.js +2 -1
  21. package/dist/remote/push.d.ts +16 -16
  22. package/dist/remote/push.js +27 -27
  23. package/dist/remote/register.d.ts +3 -3
  24. package/dist/remote/register.js +17 -19
  25. package/dist/remote/server.d.ts +9 -8
  26. package/dist/remote/server.js +15 -14
  27. package/dist/remote/session-state.d.ts +5 -4
  28. package/dist/remote/session-state.js +8 -5
  29. package/dist/remote/sw.d.ts +7 -6
  30. package/dist/remote/sw.js +7 -6
  31. package/dist/remote/tailscale.d.ts +4 -2
  32. package/dist/remote/tailscale.js +4 -2
  33. package/dist/remote/ui-highlight.js +6 -5
  34. package/dist/remote/ui-render.js +4 -4
  35. package/dist/remote/ui-script.js +24 -24
  36. package/dist/remote/ui-styles.d.ts +1 -1
  37. package/dist/remote/ui-styles.js +10 -13
  38. package/dist/remote/ui-tools.js +9 -6
  39. package/dist/shared/child-extensions.d.ts +29 -17
  40. package/dist/shared/child-extensions.js +29 -17
  41. package/dist/shared/child-output.d.ts +30 -24
  42. package/dist/shared/child-output.js +25 -17
  43. package/dist/shared/child-process.d.ts +47 -40
  44. package/dist/shared/child-process.js +50 -59
  45. package/dist/shared/command-watchdog.d.ts +85 -16
  46. package/dist/shared/command-watchdog.js +115 -21
  47. package/dist/shared/fs-text.d.ts +16 -10
  48. package/dist/shared/fs-text.js +16 -10
  49. package/dist/shared/git-runner.d.ts +25 -25
  50. package/dist/shared/git-runner.js +25 -25
  51. package/dist/shared/leaked-tool-call.d.ts +17 -11
  52. package/dist/shared/leaked-tool-call.js +23 -15
  53. package/dist/shared/model-endpoint.d.ts +29 -16
  54. package/dist/shared/model-endpoint.js +33 -21
  55. package/dist/shared/pi-invocation.d.ts +7 -4
  56. package/dist/shared/pi-invocation.js +12 -7
  57. package/dist/shared/pkg-version.d.ts +13 -5
  58. package/dist/shared/pkg-version.js +13 -5
  59. package/dist/shared/reasoning-capability.d.ts +35 -24
  60. package/dist/shared/reasoning-capability.js +35 -24
  61. package/dist/shared/stream-watchdog.d.ts +60 -44
  62. package/dist/shared/stream-watchdog.js +62 -45
  63. package/dist/task/accept-debt.d.ts +41 -43
  64. package/dist/task/accept-debt.js +73 -65
  65. package/dist/task/api-synthesis.d.ts +24 -21
  66. package/dist/task/api-synthesis.js +32 -26
  67. package/dist/task/apis-contract.d.ts +32 -64
  68. package/dist/task/apis-contract.js +32 -64
  69. package/dist/task/artifact-closure.d.ts +27 -13
  70. package/dist/task/artifact-closure.js +95 -67
  71. package/dist/task/auto-commit.d.ts +46 -35
  72. package/dist/task/auto-commit.js +51 -38
  73. package/dist/task/auto-io.d.ts +45 -25
  74. package/dist/task/auto-io.js +57 -29
  75. package/dist/task/auto-orchestrator.d.ts +26 -24
  76. package/dist/task/auto-orchestrator.js +192 -165
  77. package/dist/task/auto-prompts.d.ts +36 -24
  78. package/dist/task/auto-prompts.js +40 -26
  79. package/dist/task/autofix-ledger.d.ts +27 -25
  80. package/dist/task/autofix-ledger.js +29 -26
  81. package/dist/task/batch-test-task.d.ts +20 -12
  82. package/dist/task/batch-test-task.js +67 -60
  83. package/dist/task/boot-probe.d.ts +60 -44
  84. package/dist/task/boot-probe.js +91 -72
  85. package/dist/task/cancel-input.d.ts +30 -16
  86. package/dist/task/cancel-input.js +20 -11
  87. package/dist/task/cancel-points.d.ts +27 -20
  88. package/dist/task/cancel-points.js +30 -22
  89. package/dist/task/child-runner.d.ts +124 -55
  90. package/dist/task/child-runner.js +298 -90
  91. package/dist/task/child-status.d.ts +23 -16
  92. package/dist/task/child-status.js +23 -16
  93. package/dist/task/clamp-output.js +12 -5
  94. package/dist/task/command-run.d.ts +31 -28
  95. package/dist/task/command-run.js +44 -35
  96. package/dist/task/command-shrink.d.ts +25 -18
  97. package/dist/task/command-shrink.js +37 -31
  98. package/dist/task/command-watchdog.d.ts +9 -6
  99. package/dist/task/command-watchdog.js +21 -15
  100. package/dist/task/context-attribution.d.ts +34 -26
  101. package/dist/task/context-attribution.js +34 -26
  102. package/dist/task/context-silence.d.ts +39 -29
  103. package/dist/task/context-silence.js +35 -25
  104. package/dist/task/context-usage.d.ts +16 -9
  105. package/dist/task/context-usage.js +16 -9
  106. package/dist/task/contracts.d.ts +8 -4
  107. package/dist/task/contracts.js +25 -17
  108. package/dist/task/coverage-loop.d.ts +22 -18
  109. package/dist/task/coverage-loop.js +35 -30
  110. package/dist/task/critique-probes.d.ts +13 -14
  111. package/dist/task/critique-probes.js +50 -39
  112. package/dist/task/debug-log.d.ts +13 -5
  113. package/dist/task/debug-log.js +32 -20
  114. package/dist/task/decompose-fidelity.d.ts +11 -9
  115. package/dist/task/decompose-fidelity.js +38 -33
  116. package/dist/task/decompose-granularity.d.ts +41 -38
  117. package/dist/task/decompose-granularity.js +41 -38
  118. package/dist/task/deep-render-check.d.ts +22 -14
  119. package/dist/task/deep-render-check.js +40 -31
  120. package/dist/task/dropped-input.d.ts +12 -7
  121. package/dist/task/dropped-input.js +5 -2
  122. package/dist/task/enforce-attribution.d.ts +38 -47
  123. package/dist/task/enforce-attribution.js +46 -52
  124. package/dist/task/enforce-guidelines.d.ts +31 -20
  125. package/dist/task/enforce-guidelines.js +32 -21
  126. package/dist/task/enrichment.d.ts +7 -2
  127. package/dist/task/enrichment.js +26 -14
  128. package/dist/task/env-notes.d.ts +16 -7
  129. package/dist/task/env-notes.js +48 -31
  130. package/dist/task/env-template-closure.d.ts +4 -4
  131. package/dist/task/env-template-closure.js +42 -34
  132. package/dist/task/external-context.d.ts +28 -21
  133. package/dist/task/external-context.js +17 -12
  134. package/dist/task/failure-classifier.d.ts +4 -5
  135. package/dist/task/failure-classifier.js +30 -8
  136. package/dist/task/file-inventory.d.ts +15 -11
  137. package/dist/task/file-inventory.js +25 -22
  138. package/dist/task/final-gate-fix.d.ts +74 -86
  139. package/dist/task/final-gate-fix.js +97 -116
  140. package/dist/task/final-gate-progress.d.ts +29 -46
  141. package/dist/task/final-gate-progress.js +40 -51
  142. package/dist/task/final-gate.d.ts +64 -97
  143. package/dist/task/final-gate.js +192 -199
  144. package/dist/task/fix-child.d.ts +21 -27
  145. package/dist/task/fix-child.js +21 -27
  146. package/dist/task/foreign-path.d.ts +6 -5
  147. package/dist/task/foreign-path.js +0 -0
  148. package/dist/task/frozen-conflict.d.ts +9 -10
  149. package/dist/task/frozen-conflict.js +61 -64
  150. package/dist/task/frozen-path-guard.d.ts +35 -14
  151. package/dist/task/frozen-path-guard.js +56 -39
  152. package/dist/task/gate-child.d.ts +27 -28
  153. package/dist/task/gate-child.js +36 -35
  154. package/dist/task/gate-deps.d.ts +34 -27
  155. package/dist/task/gate-deps.js +169 -159
  156. package/dist/task/gate-tally.d.ts +77 -80
  157. package/dist/task/gate-tally.js +65 -68
  158. package/dist/task/git-state-guard.d.ts +15 -11
  159. package/dist/task/git-state-guard.js +76 -66
  160. package/dist/task/impl-widget.d.ts +25 -16
  161. package/dist/task/impl-widget.js +27 -17
  162. package/dist/task/implementation-guards.d.ts +26 -0
  163. package/dist/task/implementation-guards.js +177 -0
  164. package/dist/task/implementation-thinking.d.ts +33 -31
  165. package/dist/task/implementation-thinking.js +5 -6
  166. package/dist/task/implementation-turn.d.ts +39 -31
  167. package/dist/task/implementation-turn.js +41 -28
  168. package/dist/task/inline-markdown.d.ts +20 -7
  169. package/dist/task/inline-markdown.js +15 -6
  170. package/dist/task/launch-config-gap.js +25 -39
  171. package/dist/task/launch-contract.d.ts +18 -21
  172. package/dist/task/launch-contract.js +28 -30
  173. package/dist/task/launch-manifest.d.ts +6 -2
  174. package/dist/task/launch-manifest.js +35 -34
  175. package/dist/task/ledger.js +16 -14
  176. package/dist/task/lint-fix.d.ts +6 -8
  177. package/dist/task/lint-fix.js +67 -69
  178. package/dist/task/loop-detector.d.ts +27 -8
  179. package/dist/task/loop-detector.js +38 -14
  180. package/dist/task/mid-run-input.d.ts +17 -15
  181. package/dist/task/mid-run-input.js +17 -15
  182. package/dist/task/orchestrator.d.ts +24 -28
  183. package/dist/task/orchestrator.js +89 -66
  184. package/dist/task/orientation.d.ts +18 -23
  185. package/dist/task/orientation.js +24 -31
  186. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  187. package/dist/task/owned-freeze-conflict.js +52 -85
  188. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  189. package/dist/task/owned-freeze-reassign.js +41 -61
  190. package/dist/task/parsers.d.ts +4 -2
  191. package/dist/task/parsers.js +4 -4
  192. package/dist/task/phases.d.ts +41 -48
  193. package/dist/task/phases.js +196 -252
  194. package/dist/task/plan-io.d.ts +6 -7
  195. package/dist/task/plan-io.js +6 -7
  196. package/dist/task/plan-orchestrator.d.ts +10 -8
  197. package/dist/task/plan-orchestrator.js +14 -10
  198. package/dist/task/plan-prompts.d.ts +6 -5
  199. package/dist/task/plan-prompts.js +6 -5
  200. package/dist/task/plan-readonly.d.ts +4 -5
  201. package/dist/task/plan-readonly.js +4 -5
  202. package/dist/task/plan-rounds.d.ts +17 -29
  203. package/dist/task/plan-rounds.js +21 -34
  204. package/dist/task/plan-session.d.ts +58 -72
  205. package/dist/task/plan-session.js +61 -83
  206. package/dist/task/probe-gaming.d.ts +28 -27
  207. package/dist/task/probe-gaming.js +0 -0
  208. package/dist/task/prohibition-probe.d.ts +14 -16
  209. package/dist/task/prompts.d.ts +3 -4
  210. package/dist/task/prompts.js +17 -26
  211. package/dist/task/qa-transcript.d.ts +15 -22
  212. package/dist/task/qa-transcript.js +15 -21
  213. package/dist/task/question-box.d.ts +17 -13
  214. package/dist/task/question-box.js +19 -15
  215. package/dist/task/question-dedup.d.ts +6 -7
  216. package/dist/task/question-dedup.js +13 -14
  217. package/dist/task/question-dialog.d.ts +22 -32
  218. package/dist/task/question-dialog.js +22 -32
  219. package/dist/task/question-source.d.ts +18 -44
  220. package/dist/task/question-source.js +22 -51
  221. package/dist/task/refuted-constraint.d.ts +11 -31
  222. package/dist/task/refuted-constraint.js +27 -51
  223. package/dist/task/regenerable-artifacts.d.ts +12 -31
  224. package/dist/task/regenerable-artifacts.js +12 -31
  225. package/dist/task/render-check.d.ts +11 -22
  226. package/dist/task/render-check.js +33 -46
  227. package/dist/task/repo-health-check.d.ts +10 -14
  228. package/dist/task/repo-health-check.js +17 -23
  229. package/dist/task/requirements.d.ts +38 -71
  230. package/dist/task/requirements.js +78 -126
  231. package/dist/task/research-fanout-budget.d.ts +51 -88
  232. package/dist/task/research-fanout-budget.js +51 -88
  233. package/dist/task/research-worker.d.ts +29 -39
  234. package/dist/task/research-worker.js +37 -61
  235. package/dist/task/resume-gap.d.ts +14 -15
  236. package/dist/task/root-cause-repair.d.ts +9 -9
  237. package/dist/task/root-cause-repair.js +28 -40
  238. package/dist/task/run-bracket.d.ts +10 -13
  239. package/dist/task/run-end.d.ts +12 -22
  240. package/dist/task/run-end.js +8 -16
  241. package/dist/task/run-final-gate.d.ts +19 -21
  242. package/dist/task/run-final-gate.js +62 -80
  243. package/dist/task/runner-globs.d.ts +12 -13
  244. package/dist/task/runner-globs.js +12 -13
  245. package/dist/task/runner-resolve.d.ts +9 -9
  246. package/dist/task/runner-resolve.js +22 -23
  247. package/dist/task/script-escape.d.ts +10 -12
  248. package/dist/task/script-escape.js +13 -14
  249. package/dist/task/serve-entry.d.ts +1 -1
  250. package/dist/task/serve-entry.js +22 -25
  251. package/dist/task/service-blocks.js +4 -2
  252. package/dist/task/shipped-source.d.ts +11 -29
  253. package/dist/task/shipped-source.js +11 -29
  254. package/dist/task/skip-escape.js +10 -14
  255. package/dist/task/spec-urls.d.ts +26 -65
  256. package/dist/task/spec-urls.js +26 -65
  257. package/dist/task/spec-validation.d.ts +17 -20
  258. package/dist/task/spec-validation.js +17 -20
  259. package/dist/task/stall-detector.d.ts +23 -30
  260. package/dist/task/stall-detector.js +23 -30
  261. package/dist/task/stream-watchdog.d.ts +14 -12
  262. package/dist/task/stream-watchdog.js +14 -12
  263. package/dist/task/substitution-probe.d.ts +17 -20
  264. package/dist/task/substitution-probe.js +17 -20
  265. package/dist/task/task-gates.d.ts +36 -41
  266. package/dist/task/task-gates.js +95 -106
  267. package/dist/task/task-io.d.ts +4 -4
  268. package/dist/task/task-io.js +4 -4
  269. package/dist/task/task-parsers.js +4 -3
  270. package/dist/task/task-provenance.d.ts +2 -2
  271. package/dist/task/task-provenance.js +11 -13
  272. package/dist/task/task-types.d.ts +4 -3
  273. package/dist/task/terminal-outcome.d.ts +14 -16
  274. package/dist/task/terminal-outcome.js +12 -14
  275. package/dist/task/test-assembly.d.ts +13 -20
  276. package/dist/task/test-assembly.js +13 -20
  277. package/dist/task/timings.d.ts +5 -3
  278. package/dist/task/timings.js +5 -3
  279. package/dist/task/title-label.d.ts +9 -4
  280. package/dist/task/title-label.js +9 -4
  281. package/dist/task/type-only-answer.d.ts +44 -52
  282. package/dist/task/type-only-answer.js +44 -52
  283. package/dist/task/unfailable-command.d.ts +18 -24
  284. package/dist/task/unfailable-command.js +21 -27
  285. package/dist/task/unknown-routing.d.ts +10 -4
  286. package/dist/task/unknown-routing.js +10 -4
  287. package/dist/task/user-directives.d.ts +5 -8
  288. package/dist/task/user-directives.js +5 -8
  289. package/dist/task/verify-quality.d.ts +18 -22
  290. package/dist/task/verify-quality.js +45 -46
  291. package/dist/task/verify-reconcile.d.ts +15 -10
  292. package/dist/task/verify-reconcile.js +45 -43
  293. package/dist/task/verify-resolution.d.ts +24 -20
  294. package/dist/task/verify-resolution.js +51 -50
  295. package/dist/task/verify-work.d.ts +59 -66
  296. package/dist/task/verify-work.js +101 -138
  297. package/dist/task/widget.d.ts +15 -14
  298. package/dist/task/widget.js +22 -17
  299. package/dist/task/wiring-claims.d.ts +25 -32
  300. package/dist/task/wiring-claims.js +30 -35
  301. package/dist/task/write-guard.d.ts +39 -39
  302. package/dist/task/write-guard.js +48 -51
  303. package/dist/task/yolo.d.ts +34 -30
  304. package/dist/task/yolo.js +42 -37
  305. package/dist/workers/abstention.d.ts +21 -41
  306. package/dist/workers/abstention.js +27 -48
  307. package/dist/workers/brave-search.d.ts +4 -3
  308. package/dist/workers/brave-search.js +5 -2
  309. package/dist/workers/brave-warning.d.ts +7 -4
  310. package/dist/workers/brave-warning.js +19 -7
  311. package/dist/workers/ddg-search.d.ts +6 -6
  312. package/dist/workers/ddg-search.js +18 -12
  313. package/dist/workers/docs-cache.js +5 -2
  314. package/dist/workers/docs-chunk.d.ts +30 -37
  315. package/dist/workers/docs-chunk.js +37 -41
  316. package/dist/workers/docs-core.d.ts +28 -44
  317. package/dist/workers/docs-core.js +25 -44
  318. package/dist/workers/docs-index.js +4 -3
  319. package/dist/workers/docs-lookup.d.ts +15 -22
  320. package/dist/workers/docs-lookup.js +12 -21
  321. package/dist/workers/docs-project.d.ts +15 -9
  322. package/dist/workers/docs-project.js +17 -10
  323. package/dist/workers/docs-resolve.d.ts +19 -20
  324. package/dist/workers/docs-resolve.js +35 -32
  325. package/dist/workers/docs-retrieve.d.ts +5 -6
  326. package/dist/workers/docs-retrieve.js +18 -15
  327. package/dist/workers/exa-search.d.ts +9 -6
  328. package/dist/workers/exa-search.js +23 -12
  329. package/dist/workers/fetch-core.d.ts +13 -16
  330. package/dist/workers/fetch-core.js +23 -23
  331. package/dist/workers/focused-extractor.d.ts +13 -12
  332. package/dist/workers/focused-extractor.js +27 -19
  333. package/dist/workers/html-clean.js +24 -14
  334. package/dist/workers/http-request.d.ts +28 -20
  335. package/dist/workers/http-request.js +22 -17
  336. package/dist/workers/npm-version.d.ts +28 -11
  337. package/dist/workers/npm-version.js +24 -15
  338. package/dist/workers/phantom-imports.d.ts +15 -12
  339. package/dist/workers/phantom-imports.js +30 -24
  340. package/dist/workers/pi-worker-core.d.ts +65 -96
  341. package/dist/workers/pi-worker-core.js +93 -181
  342. package/dist/workers/pi-worker-docs.d.ts +24 -19
  343. package/dist/workers/pi-worker-docs.js +67 -76
  344. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  345. package/dist/workers/pi-worker-fetch.js +27 -19
  346. package/dist/workers/pi-worker-search.js +12 -8
  347. package/dist/workers/pi-worker.d.ts +9 -4
  348. package/dist/workers/pi-worker.js +21 -14
  349. package/dist/workers/reasoning-warning.d.ts +18 -17
  350. package/dist/workers/reasoning-warning.js +22 -20
  351. package/dist/workers/research-cache.js +50 -78
  352. package/dist/workers/search-core.js +7 -5
  353. package/dist/workers/search-types.d.ts +10 -9
  354. package/dist/workers/search-types.js +9 -8
  355. package/dist/workers/session-hint.d.ts +13 -14
  356. package/dist/workers/session-hint.js +8 -9
  357. package/dist/workers/shared.d.ts +21 -25
  358. package/dist/workers/shared.js +0 -0
  359. package/dist/workers/single-read-extension.d.ts +14 -7
  360. package/dist/workers/single-read-extension.js +14 -7
  361. package/dist/workers/single-read-guard.d.ts +27 -30
  362. package/dist/workers/single-read-guard.js +36 -36
  363. package/dist/workers/typeonly-log.d.ts +12 -9
  364. package/dist/workers/typeonly-log.js +29 -33
  365. package/dist/workers/worker-channels.d.ts +15 -23
  366. package/dist/workers/worker-channels.js +15 -23
  367. package/dist/workers/worker-failure.d.ts +38 -46
  368. package/dist/workers/worker-failure.js +31 -39
  369. package/dist/workers/worker-kill.d.ts +25 -26
  370. package/dist/workers/worker-kill.js +16 -19
  371. package/dist/workers/worker-profiles.d.ts +54 -56
  372. package/dist/workers/worker-profiles.js +63 -39
  373. package/package.json +10 -8
@@ -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) {
@@ -9,23 +9,24 @@
9
9
  * Only a command that demonstrably ran and exited non-zero for a reason of its
10
10
  * own is a failure, and only exit 0 is a pass.
11
11
  *
12
- * Why a module. That ladder was written three times inside final-gate.ts
13
- * `runGateCommand`, `runVerifyCommandLine`, `rerunDebtVerifyCommand`in the
14
- * same order with different labels, and the copies had already drifted (the
15
- * infrastructure pattern applied unconditionally in one and only on request in
16
- * another). repo-health-check.ts had solved exactly this shape years earlier:
17
- * `classifyHealthRun` is pure over a value, extracted so its sync and async
18
- * runners "cannot drift apart". The gate never adopted it.
12
+ * Why a module. The same ladder is needed by every caller that runs a project
13
+ * command and must decide what its ending MEANS the gate's own
14
+ * `runGateCommand`, `runVerifyCommandLine` here, and accept-debt's
15
+ * `rerunDebtVerifyCommand`. Stated separately at each site the copies drift: one
16
+ * applies the infrastructure pattern unconditionally, the next only on request,
17
+ * and two trails then disagree about whether the same output was a gap or a
18
+ * failure. repo-health-check.ts defers to this module rather than carrying its
19
+ * own version — its header says so in as many words.
19
20
  *
20
21
  * The second half is the seam. Classification is now pure over a `CommandRun`
21
22
  * value, and SPAWNING is a `CommandRunner` the caller injects. That is what lets
22
23
  * the gate's tests state a case as `{status: 1, stdout: "…"}` instead of writing
23
- * a real `node -e` child, creating a temp directory for it, and — for the three
24
- * cases only reachable that way — shadowing a binary on `process.env.PATH` and
25
- * resetting a module-level cache, which made those tests order-sensitive and
26
- * needed a Windows carve-out. Note the asymmetry this closes: `BootDeps` already
27
- * carried nine injectable probes for the gate's boot half while its command half
28
- * had none.
24
+ * a real `node -e` child, creating a temp directory for it, and — for the cases
25
+ * only reachable that way — shadowing a binary on `process.env.PATH` and
26
+ * resetting a module-level cache, which makes those tests order-sensitive.
27
+ *
28
+ * It also closes an asymmetry: `BootDeps` carries twelve injectable probes for
29
+ * the gate's boot half, while its command half had none.
29
30
  */
30
31
  /** What one finished command looks like, stripped of how it was spawned. */
31
32
  export interface CommandRun {
@@ -57,14 +58,12 @@ export interface CommandSpec {
57
58
  * The injectable half. The gate takes one of these so its tests can script
58
59
  * verdicts instead of paying process-spawn cost for every classification case.
59
60
  *
60
- * ASYNC by contract. It was `(spec) => CommandRun`, so the only implementation
61
- * could be `spawnSync`, and the run-end gate blocked the event loop end to end:
62
- * repo-health under a 600s cap, then every lockfile/test/build/launch command
63
- * under a 900s cap, then every ACCEPT-debt re-run under a 300s cap, with no
64
- * loader able to paint through any of it. That freeze is MEASURED 0 of 686
65
- * expected 100ms ticks fired during a 69s run and `repo-health-check.ts`'s own
66
- * doc comment already told gate callers not to do it, while `final-gate.ts`'s
67
- * repo-health call did exactly that.
61
+ * ASYNC by contract, and the type is what enforces it. A synchronous
62
+ * `(spec) => CommandRun` can only be implemented with `spawnSync`, which blocks
63
+ * the event loop for the whole command so the run-end gate, which runs
64
+ * repo-health and then every lockfile, test, build, launch and ACCEPT-debt
65
+ * command in sequence, would hold the loop for all of it and no loader could
66
+ * paint a single frame through it.
68
67
  */
69
68
  export type CommandRunner = (spec: CommandSpec) => Promise<CommandRun>;
70
69
  /**
@@ -76,15 +75,19 @@ export type CommandRunner = (spec: CommandSpec) => Promise<CommandRun>;
76
75
  * THE RUN SETTLES ON THE CHILD, NOT ON THE PIPE. `close` fires only once every
77
76
  * stdio pipe has reached EOF, and a backgrounded grandchild INHERITS stdout: a
78
77
  * seed script that starts a daemon, a build that leaves a watcher, a launch
79
- * script. Waiting for `close` there is waiting for the grandchild, which no
80
- * timeout can reach — SIGKILL goes to the direct child and the inherited pipe
81
- * survives it. So `exit` settles the run, and the deadline settles it itself.
78
+ * script.
79
+ *
80
+ * Measured on exactly that shape a child that backgrounds a sleeper and exits
81
+ * at once — `exit` fired after 1ms and `close` NEVER fired. Sending SIGKILL to
82
+ * the direct child did not release it either, because the grandchild holds the
83
+ * pipe. Waiting for `close` is therefore waiting for something no timeout can
84
+ * reach. `exit` settles the run, and the deadline settles it itself.
82
85
  */
83
86
  export declare const spawnCommand: CommandRunner;
84
87
  /**
85
88
  * A non-zero exit whose output shows an EXTERNAL runtime dependency is missing, not
86
89
  * a code fault: a browser suite (Playwright/Cypress) whose browser binaries or system
87
- * libraries were never installed here (mx5 run 10 item 2: `test:ct` must run in the
90
+ * libraries were never installed here (a component-test runner must run in the
88
91
  * gate, but on a box with no Playwright browsers it is an environment gap, not a FAIL).
89
92
  * These exit non-zero (not 127), so they need output-shape recognition to skip.
90
93
  */
@@ -164,15 +167,15 @@ export type VerifyRerunOutcome = {
164
167
  detail: string;
165
168
  };
166
169
  /**
167
- * Re-run one VERIFY-block command line (nexttask 5) under the gate's existing
170
+ * Re-run one VERIFY-block command line under the gate's existing
168
171
  * env-gap contract, so a debt whose reason NAMES that command can be closed by the
169
172
  * command itself rather than by a judgement about it.
170
173
  *
171
- * Runs through `sh -c` because a VERIFY line is a shell line, not an argv: run 19's
174
+ * Runs through `sh -c` because a VERIFY line is a shell line, not an argv: a
172
175
  * is `AGENT=1 bun test test/listings.test.ts`, and env prefixes, `&&` and redirects
173
176
  * are all ordinary there. The leading command word is still resolved through
174
177
  * runner-resolve so a login-shell-stripped PATH cannot make every re-run look like a
175
- * gap (mx5 run 16's blindness, one level down).
178
+ * gap.
176
179
  *
177
180
  * The asymmetry is the point: only exit 0 is conclusive. Every other ending — real
178
181
  * failure, missing tool, unreachable database, timeout, no POSIX shell — leaves the
@@ -9,36 +9,41 @@
9
9
  * Only a command that demonstrably ran and exited non-zero for a reason of its
10
10
  * own is a failure, and only exit 0 is a pass.
11
11
  *
12
- * Why a module. That ladder was written three times inside final-gate.ts
13
- * `runGateCommand`, `runVerifyCommandLine`, `rerunDebtVerifyCommand`in the
14
- * same order with different labels, and the copies had already drifted (the
15
- * infrastructure pattern applied unconditionally in one and only on request in
16
- * another). repo-health-check.ts had solved exactly this shape years earlier:
17
- * `classifyHealthRun` is pure over a value, extracted so its sync and async
18
- * runners "cannot drift apart". The gate never adopted it.
12
+ * Why a module. The same ladder is needed by every caller that runs a project
13
+ * command and must decide what its ending MEANS the gate's own
14
+ * `runGateCommand`, `runVerifyCommandLine` here, and accept-debt's
15
+ * `rerunDebtVerifyCommand`. Stated separately at each site the copies drift: one
16
+ * applies the infrastructure pattern unconditionally, the next only on request,
17
+ * and two trails then disagree about whether the same output was a gap or a
18
+ * failure. repo-health-check.ts defers to this module rather than carrying its
19
+ * own version — its header says so in as many words.
19
20
  *
20
21
  * The second half is the seam. Classification is now pure over a `CommandRun`
21
22
  * value, and SPAWNING is a `CommandRunner` the caller injects. That is what lets
22
23
  * the gate's tests state a case as `{status: 1, stdout: "…"}` instead of writing
23
- * a real `node -e` child, creating a temp directory for it, and — for the three
24
- * cases only reachable that way — shadowing a binary on `process.env.PATH` and
25
- * resetting a module-level cache, which made those tests order-sensitive and
26
- * needed a Windows carve-out. Note the asymmetry this closes: `BootDeps` already
27
- * carried nine injectable probes for the gate's boot half while its command half
28
- * had none.
24
+ * a real `node -e` child, creating a temp directory for it, and — for the cases
25
+ * only reachable that way — shadowing a binary on `process.env.PATH` and
26
+ * resetting a module-level cache, which makes those tests order-sensitive.
27
+ *
28
+ * It also closes an asymmetry: `BootDeps` carries twelve injectable probes for
29
+ * the gate's boot half, while its command half had none.
29
30
  */
30
31
  import { spawn } from 'node:child_process';
31
32
  import { isCommandNotFound, resolveRunner, runnerEnv } from './runner-resolve.js';
32
33
  /**
33
34
  * How much of ONE stream may be held in the HOST process, and how it is split.
34
35
  *
35
- * `spawnSync` bounded this at its 1 MB default `maxBuffer`. The async runner had
36
- * no bound at all: two strings grew in the TUI's own process for as long as a
37
- * command under the 900s cap kept talking.
36
+ * `spawnSync` came with a bound for free — its default `maxBuffer` is 1 MiB, and
37
+ * exceeding it fails the call with ENOBUFS (checked: 2 MiB of output truncates at
38
+ * 1,114,112 bytes). An async runner has no such bound, so without this two
39
+ * strings grow in the host's own process for as long as the command keeps
40
+ * talking.
38
41
  *
39
- * BOTH ENDS are kept, because both are read. `isCommandNotFound` and the two gap
40
- * regexes match wording a runner prints FIRST; `outputTail` and every failure
41
- * reason take the LAST 400 characters. A single-ended cap loses one of them.
42
+ * BOTH ENDS are kept, because both are read: `isCommandNotFound` and the gap
43
+ * regexes match wording a runner prints FIRST, while `outputTail` and every
44
+ * failure reason take the LAST 400 characters. A single-ended cap loses one of
45
+ * them. Run against a real 2 MB command, the capture keeps its opening bytes,
46
+ * its closing bytes, and an elision marker between them.
42
47
  */
43
48
  const OUTPUT_HEAD_CAP = 256 * 1024;
44
49
  const OUTPUT_TAIL_CAP = 768 * 1024;
@@ -81,9 +86,13 @@ const DRAIN_MS = 50;
81
86
  * THE RUN SETTLES ON THE CHILD, NOT ON THE PIPE. `close` fires only once every
82
87
  * stdio pipe has reached EOF, and a backgrounded grandchild INHERITS stdout: a
83
88
  * seed script that starts a daemon, a build that leaves a watcher, a launch
84
- * script. Waiting for `close` there is waiting for the grandchild, which no
85
- * timeout can reach — SIGKILL goes to the direct child and the inherited pipe
86
- * survives it. So `exit` settles the run, and the deadline settles it itself.
89
+ * script.
90
+ *
91
+ * Measured on exactly that shape a child that backgrounds a sleeper and exits
92
+ * at once — `exit` fired after 1ms and `close` NEVER fired. Sending SIGKILL to
93
+ * the direct child did not release it either, because the grandchild holds the
94
+ * pipe. Waiting for `close` is therefore waiting for something no timeout can
95
+ * reach. `exit` settles the run, and the deadline settles it itself.
87
96
  */
88
97
  export const spawnCommand = spec => new Promise(resolve => {
89
98
  const out = new BoundedOutput();
@@ -97,8 +106,8 @@ export const spawnCommand = spec => new Promise(resolve => {
97
106
  cwd: spec.cwd,
98
107
  // stdin CLOSED. `spawnSync` gave the child none; the default `spawn`
99
108
  // stdio is a live pipe nobody ever ends, so a check that reads stdin —
100
- // a `cat`-style pipeline, a tool that prompts, a pager — blocked until
101
- // the kill timer: 600s for repo-health, 900s for a gate command.
109
+ // a `cat`-style pipeline, a tool that prompts, a pager — would block
110
+ // until the kill timer fires instead of returning at once.
102
111
  stdio: ['ignore', 'pipe', 'pipe'],
103
112
  ...(spec.env ? { env: spec.env } : {})
104
113
  });
@@ -140,12 +149,12 @@ export const spawnCommand = spec => new Promise(resolve => {
140
149
  clearTimeout(drain);
141
150
  drain = setTimeout(() => done(null), DRAIN_MS);
142
151
  };
143
- // NOT unref'd. With `spawnSync`'s own `timeout` gone this timer is the only
144
- // bound left on every gate command, repo-health command and ACCEPT-debt
145
- // re-run and an unref'd timer is MEASURED in this repo never to fire at
146
- // all on Windows (0/20s), which would leave all of them unbounded. It is
147
- // cleared the moment the run settles, so it holds the loop open only while
148
- // a command the caller is awaiting anyway is still running.
152
+ // NOT unref'd. This timer is the only bound left on every gate command,
153
+ // repo-health command and ACCEPT-debt re-run, and an unref'd timer is only
154
+ // guaranteed to fire while something ref'd is still pending not a
155
+ // guarantee this can rely on. It is cleared the moment the run settles, so
156
+ // it holds the loop open only while a command the caller is awaiting
157
+ // anyway is still running.
149
158
  const timer = setTimeout(killAndSettle, spec.timeoutMs);
150
159
  if (spec.signal) {
151
160
  if (spec.signal.aborted)
@@ -180,7 +189,7 @@ export const spawnCommand = spec => new Promise(resolve => {
180
189
  /**
181
190
  * A non-zero exit whose output shows an EXTERNAL runtime dependency is missing, not
182
191
  * a code fault: a browser suite (Playwright/Cypress) whose browser binaries or system
183
- * libraries were never installed here (mx5 run 10 item 2: `test:ct` must run in the
192
+ * libraries were never installed here (a component-test runner must run in the
184
193
  * gate, but on a box with no Playwright browsers it is an environment gap, not a FAIL).
185
194
  * These exit non-zero (not 127), so they need output-shape recognition to skip.
186
195
  */
@@ -202,7 +211,7 @@ export const INFRA_GAP_OUTPUT_RE = /ECONNREFUSED|connection refused|ENOTFOUND|EA
202
211
  * `spawn-failed` is first and is kept distinguishable from every other row by its
203
212
  * id: a tool-level gap (127 inside the chain, a missing browser, a timeout) means
204
213
  * the runner demonstrably RAN, and only genuine spawn failures feed the gate's
205
- * full-blindness guard (mx5 run 16 — see observabilityGapFailure).
214
+ * full-blindness guard (see observabilityGapFailure).
206
215
  */
207
216
  const GAP_RULES = [
208
217
  {
@@ -281,15 +290,15 @@ function leadingBin(line) {
281
290
  return null;
282
291
  }
283
292
  /**
284
- * Re-run one VERIFY-block command line (nexttask 5) under the gate's existing
293
+ * Re-run one VERIFY-block command line under the gate's existing
285
294
  * env-gap contract, so a debt whose reason NAMES that command can be closed by the
286
295
  * command itself rather than by a judgement about it.
287
296
  *
288
- * Runs through `sh -c` because a VERIFY line is a shell line, not an argv: run 19's
297
+ * Runs through `sh -c` because a VERIFY line is a shell line, not an argv: a
289
298
  * is `AGENT=1 bun test test/listings.test.ts`, and env prefixes, `&&` and redirects
290
299
  * are all ordinary there. The leading command word is still resolved through
291
300
  * runner-resolve so a login-shell-stripped PATH cannot make every re-run look like a
292
- * gap (mx5 run 16's blindness, one level down).
301
+ * gap.
293
302
  *
294
303
  * The asymmetry is the point: only exit 0 is conclusive. Every other ending — real
295
304
  * failure, missing tool, unreachable database, timeout, no POSIX shell — leaves the
@@ -2,11 +2,13 @@
2
2
  * SCOPE-SHRINK detection for the final-gate fix pass.
3
3
  *
4
4
  * The label guard (final-gate-fix.ts) compares the SET of discoverable gate
5
- * commands before and after the fix child. mx5 run 19 walked straight through
6
- * it: the autofix rewrote `"test": "AGENT=1 bun test"` to
7
- * `"AGENT=1 bun test ./test"`, the label `bun run test` existed before and
8
- * after, the set difference was empty, and the gate re-ran a suite that no
9
- * longer covered the repository then reported "autofix converged".
5
+ * commands before and after the fix child, and a fix child walks straight through
6
+ * it. Rewrite `"test": "AGENT=1 bun test"` to `"AGENT=1 bun test ./test"` and the
7
+ * label `bun run test` exists before and after, so the set difference is empty —
8
+ * while the gate now re-runs a suite that no longer covers the repository, and
9
+ * reports that the autofix converged. Fed to `classifyBodyChange`, those two
10
+ * bodies come back `narrow` / `path-added`, which is the whole point of this
11
+ * module.
10
12
  *
11
13
  * What changed was not the command's NAME but its SCOPE. This module compares
12
14
  * the RESOLVED BODIES (`scripts[name]` for package.json, the recipe lines for a
@@ -16,23 +18,27 @@
16
18
  * 1. a path/glob argument is ADDED to a command member that had none
17
19
  * (`bun test` → `bun test ./test`);
18
20
  * 2. an existing path/glob argument is replaced by a strict SUBPATH of itself
19
- * (`eslint src` → `eslint src/server`);
21
+ * (`eslint ./src` → `eslint ./src/server`). Note the argument has to LOOK
22
+ * like a path — a bare `src` with no separator, dot or glob is not treated
23
+ * as one, so `eslint src` → `eslint src/server` reads as neutral;
20
24
  * 3. an exclusion flag/pattern is ADDED (`--ignore`, `--exclude`,
21
25
  * `--testPathIgnorePatterns`, `-x`, …);
22
26
  * 4. a config-file argument is swapped for one the fix pass itself created.
23
27
  *
24
- * With ONE excuse, measured on mx5@a9c6145 and documented at
25
- * `excusedByRelocation`: rule 3 does not fire when the same pass hands the
26
- * excluded input to another command the gate already runs.
28
+ * With ONE excuse, documented at `excusedByRelocation`: rule 3 does not fire when
29
+ * the same pass hands the excluded input to another command the gate already
30
+ * runs. Rules 1, 2 and 4 have no excuse.
27
31
  *
28
- * Everything else passes: new env vars, added flags that do not restrict the
29
- * input set, reordering, added chain members, whole new scripts. The guard's
30
- * job is to stop the fix pass from silently redefining the gate's own success
31
- * criterion not to review the fix.
32
+ * Everything else passes, each checked by running it: a new env var, a flag that
33
+ * does not restrict the input set, reordering, an added chain member, an
34
+ * identical body. A path replaced by its PARENT classifies as `widen`, not
35
+ * `narrow`. The guard's job is to stop the fix pass from silently redefining the
36
+ * gate's own success criterion — not to review the fix.
32
37
  *
33
- * PURE by construction (no fs, no git): the live guard feeds it two body maps
34
- * from `discoverGateCommandBodies`, the replay harness feeds it two maps built
35
- * from git blobs. Same code decides both.
38
+ * PURE by construction: the module has no imports at all no fs, no git, no
39
+ * clock. The live guard feeds it two body maps from `discoverGateCommandBodies`;
40
+ * anything replaying history feeds it two maps built from git blobs. The same
41
+ * code decides both.
36
42
  */
37
43
  /** Which of the four mechanical shapes fired. */
38
44
  export type ShrinkKind = 'path-added' | 'path-narrowed' | 'exclusion-added' | 'config-swapped';
@@ -64,8 +70,9 @@ export interface Narrowing {
64
70
  kinds: ShrinkKind[];
65
71
  reasons: string[];
66
72
  }
67
- /** Split a shell body into chain members, quote-aware: `&&`, `||`, `;`, `|`,
68
- * and newlines (Makefile recipes arrive as multiple lines). */
73
+ /** Split a shell body into chain members on `&&`, `||`, `;`, `|` and newlines
74
+ * (Makefile recipes arrive as multiple lines). Quote-aware, and it matters:
75
+ * `bun test --name 'a && b' && lint` splits into two members, not three. */
69
76
  export declare function splitChainMembers(body: string): string[];
70
77
  /** Is this token SYNTACTICALLY a path or glob? Deliberately lexical: no fs. */
71
78
  export declare function looksLikePath(tok: string): boolean;
@@ -2,11 +2,13 @@
2
2
  * SCOPE-SHRINK detection for the final-gate fix pass.
3
3
  *
4
4
  * The label guard (final-gate-fix.ts) compares the SET of discoverable gate
5
- * commands before and after the fix child. mx5 run 19 walked straight through
6
- * it: the autofix rewrote `"test": "AGENT=1 bun test"` to
7
- * `"AGENT=1 bun test ./test"`, the label `bun run test` existed before and
8
- * after, the set difference was empty, and the gate re-ran a suite that no
9
- * longer covered the repository then reported "autofix converged".
5
+ * commands before and after the fix child, and a fix child walks straight through
6
+ * it. Rewrite `"test": "AGENT=1 bun test"` to `"AGENT=1 bun test ./test"` and the
7
+ * label `bun run test` exists before and after, so the set difference is empty —
8
+ * while the gate now re-runs a suite that no longer covers the repository, and
9
+ * reports that the autofix converged. Fed to `classifyBodyChange`, those two
10
+ * bodies come back `narrow` / `path-added`, which is the whole point of this
11
+ * module.
10
12
  *
11
13
  * What changed was not the command's NAME but its SCOPE. This module compares
12
14
  * the RESOLVED BODIES (`scripts[name]` for package.json, the recipe lines for a
@@ -16,27 +18,32 @@
16
18
  * 1. a path/glob argument is ADDED to a command member that had none
17
19
  * (`bun test` → `bun test ./test`);
18
20
  * 2. an existing path/glob argument is replaced by a strict SUBPATH of itself
19
- * (`eslint src` → `eslint src/server`);
21
+ * (`eslint ./src` → `eslint ./src/server`). Note the argument has to LOOK
22
+ * like a path — a bare `src` with no separator, dot or glob is not treated
23
+ * as one, so `eslint src` → `eslint src/server` reads as neutral;
20
24
  * 3. an exclusion flag/pattern is ADDED (`--ignore`, `--exclude`,
21
25
  * `--testPathIgnorePatterns`, `-x`, …);
22
26
  * 4. a config-file argument is swapped for one the fix pass itself created.
23
27
  *
24
- * With ONE excuse, measured on mx5@a9c6145 and documented at
25
- * `excusedByRelocation`: rule 3 does not fire when the same pass hands the
26
- * excluded input to another command the gate already runs.
28
+ * With ONE excuse, documented at `excusedByRelocation`: rule 3 does not fire when
29
+ * the same pass hands the excluded input to another command the gate already
30
+ * runs. Rules 1, 2 and 4 have no excuse.
27
31
  *
28
- * Everything else passes: new env vars, added flags that do not restrict the
29
- * input set, reordering, added chain members, whole new scripts. The guard's
30
- * job is to stop the fix pass from silently redefining the gate's own success
31
- * criterion not to review the fix.
32
+ * Everything else passes, each checked by running it: a new env var, a flag that
33
+ * does not restrict the input set, reordering, an added chain member, an
34
+ * identical body. A path replaced by its PARENT classifies as `widen`, not
35
+ * `narrow`. The guard's job is to stop the fix pass from silently redefining the
36
+ * gate's own success criterion — not to review the fix.
32
37
  *
33
- * PURE by construction (no fs, no git): the live guard feeds it two body maps
34
- * from `discoverGateCommandBodies`, the replay harness feeds it two maps built
35
- * from git blobs. Same code decides both.
38
+ * PURE by construction: the module has no imports at all no fs, no git, no
39
+ * clock. The live guard feeds it two body maps from `discoverGateCommandBodies`;
40
+ * anything replaying history feeds it two maps built from git blobs. The same
41
+ * code decides both.
36
42
  */
37
43
  // ── tokenizing ──────────────────────────────────────────────────────────────
38
- /** Split a shell body into chain members, quote-aware: `&&`, `||`, `;`, `|`,
39
- * and newlines (Makefile recipes arrive as multiple lines). */
44
+ /** Split a shell body into chain members on `&&`, `||`, `;`, `|` and newlines
45
+ * (Makefile recipes arrive as multiple lines). Quote-aware, and it matters:
46
+ * `bun test --name 'a && b' && lint` splits into two members, not three. */
40
47
  export function splitChainMembers(body) {
41
48
  const out = [];
42
49
  let cur = '';
@@ -262,21 +269,20 @@ export function makefileRecipe(makefileText, target) {
262
269
  }
263
270
  // ── the rules ───────────────────────────────────────────────────────────────
264
271
  /**
265
- * RELOCATION EXCUSE (rule 3 only, measured on mx5@a9c6145).
272
+ * RELOCATION EXCUSE.
266
273
  *
267
- * That autofix rewrote `test` from `AGENT=1 bun test` to `AGENT=1 bun test
268
- * --path-ignore-patterns '**\/*.spec.tsx' && playwright test -c
269
- * playwright-ct.config.ts`. Read by hand: `test:ct` was ALREADY
270
- * `playwright test -c playwright-ct.config.ts` and already a discovered gate
271
- * command, so the excluded specs never left the gate's coverage — the pass
272
- * separated two runners that collide, it did not shrink what is measured.
273
- * Rejecting it would be a false positive, and the rule as first written did.
274
+ * A fix pass can split two runners that collide excluding a set of specs from
275
+ * one command while adding, in the same body, a chain member that another
276
+ * ALREADY-DISCOVERED gate command runs over exactly those specs. Nothing left the
277
+ * gate's coverage there, so rejecting it would be a false positive.
274
278
  *
275
- * The excuse is deliberately narrow and hard to game: the pass must have added
276
- * a chain member that is VERBATIM (whitespace-normalized) a member of another
277
- * discovered gate command's body. `&& echo ok` does not qualify; neither does a
278
- * near-copy pointed at a different config. Nothing excuses rules 1, 2 or 4 —
279
- * a path narrowing stands on its own.
279
+ * The excuse is deliberately narrow and hard to game: the pass must have ADDED a
280
+ * chain member that is VERBATIM (whitespace-normalized) a member of another
281
+ * discovered gate command's body. Run both ways with the sibling present the
282
+ * exclusion classifies neutral, and a near-copy pointed at a different config
283
+ * does NOT excuse it. An added FLAG alone can never qualify, because there is no
284
+ * added member to match. Nothing excuses rules 1, 2 or 4: a path narrowing stands
285
+ * on its own.
280
286
  *
281
287
  * Returns the matching sibling member, or null.
282
288
  */
@@ -6,14 +6,17 @@ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
6
6
  * returns — `godot --headless --check-only` with no timeout, a dev server, a
7
7
  * hung test — and the run wedges until the user manually aborts and tells the
8
8
  * model to add a timeout. pi's bash tool takes an OPTIONAL `timeout` with NO
9
- * default (see pi-coding-agent tools/bash.js), so any command the model didn't
10
- * bound runs forever. This supplies the missing default from the host side.
9
+ * default: its schema in `core/tools/bash.js` says "optional, no default
10
+ * timeout" and its resolver returns undefined for an absent value, so no timer is
11
+ * ever armed. This supplies the missing default from the host side.
11
12
  *
12
13
  * HOW: arm a wall-clock timer on `tool_execution_start`, disarm it on
13
- * `tool_execution_end`. If it elapses, `ctx.abort()` cancels the in-flight
14
- * operation which fires the tool's AbortSignal, and pi's bash executor kills
15
- * the whole process tree on abort then a follow-up user turn tells the model
16
- * what happened so it retries with a timeout instead of hanging again.
14
+ * `tool_execution_end` — both real pi events. If it elapses, `ctx.abort()`
15
+ * cancels the in-flight operation, which fires the tool's AbortSignal; pi's bash
16
+ * executor imports `killProcessTree` and its own comment on that listener reads
17
+ * "Handle abort signal by killing the entire process tree". A follow-up user turn
18
+ * then tells the model what happened so it retries with a timeout instead of
19
+ * hanging again.
17
20
  *
18
21
  * Tool-agnostic by default: it arms on every tool except exact names listed in
19
22
  * `commandTimeoutExemptTools`, which /task-config fills from the live tool list
@@ -7,14 +7,17 @@ import { CommandWatchdog, realTimerDeps, reminderMessage } from '../shared/comma
7
7
  * returns — `godot --headless --check-only` with no timeout, a dev server, a
8
8
  * hung test — and the run wedges until the user manually aborts and tells the
9
9
  * model to add a timeout. pi's bash tool takes an OPTIONAL `timeout` with NO
10
- * default (see pi-coding-agent tools/bash.js), so any command the model didn't
11
- * bound runs forever. This supplies the missing default from the host side.
10
+ * default: its schema in `core/tools/bash.js` says "optional, no default
11
+ * timeout" and its resolver returns undefined for an absent value, so no timer is
12
+ * ever armed. This supplies the missing default from the host side.
12
13
  *
13
14
  * HOW: arm a wall-clock timer on `tool_execution_start`, disarm it on
14
- * `tool_execution_end`. If it elapses, `ctx.abort()` cancels the in-flight
15
- * operation which fires the tool's AbortSignal, and pi's bash executor kills
16
- * the whole process tree on abort then a follow-up user turn tells the model
17
- * what happened so it retries with a timeout instead of hanging again.
15
+ * `tool_execution_end` — both real pi events. If it elapses, `ctx.abort()`
16
+ * cancels the in-flight operation, which fires the tool's AbortSignal; pi's bash
17
+ * executor imports `killProcessTree` and its own comment on that listener reads
18
+ * "Handle abort signal by killing the entire process tree". A follow-up user turn
19
+ * then tells the model what happened so it retries with a timeout instead of
20
+ * hanging again.
18
21
  *
19
22
  * Tool-agnostic by default: it arms on every tool except exact names listed in
20
23
  * `commandTimeoutExemptTools`, which /task-config fills from the live tool list
@@ -33,16 +36,19 @@ import { CommandWatchdog, realTimerDeps, reminderMessage } from '../shared/comma
33
36
  export { CommandWatchdog, commandTimeoutHint, realTimerDeps, reminderMessage, WATCHDOG_CANCEL_MARKER } from '../shared/command-watchdog.js';
34
37
  /**
35
38
  * One-shot marker: the most recent turn abort was issued BY THE WATCHDOG, not by
36
- * a human ESC. Both end the assistant turn with stopReason 'aborted' the only
37
- * signal steerUntilDone's classifyTurnEnd() can readso without this flag the
38
- * steer loop can win the race against the watchdog's queued follow-up turn and
39
- * show a steering prompt to an empty room (wedging an unattended run).
39
+ * a human ESC. Both end the assistant turn the same way, and `classifyTurnEnd`
40
+ * has nothing else to go on it decides with `last?.stopReason === 'aborted'`.
41
+ * Without this flag the steer loop can win the race against the watchdog's queued
42
+ * follow-up turn and show a steering prompt to an empty room, wedging an
43
+ * unattended run.
40
44
  *
41
- * Set synchronously in onFire BEFORE ctx.abort(), so it is observable by the
42
- * time any waitForIdle resolves; consumed (cleared) by the first reader. A stale
43
- * flag (the aborted turn wasn't one the steer loop was watching) only costs the
44
- * consumer a bounded wait before it falls back to prompting it can never
45
- * permanently suppress a human's steer prompt.
45
+ * Set synchronously in onFire BEFORE ctx.abort(), so it is observable by the time
46
+ * any waitForIdle resolves; consumed by the first reader. Run: it reads false
47
+ * before any abort, true once after a note, false again immediately after and
48
+ * two notes still yield exactly one true. A stale flag (the aborted turn was not
49
+ * one the steer loop was watching) only costs the consumer a bounded wait before
50
+ * it falls back to prompting; it can never permanently suppress a human's steer
51
+ * prompt.
46
52
  */
47
53
  let watchdogAbortPending = false;
48
54
  /** @internal Set by onFire when it aborts a turn. Exported for the adapter and tests. */