@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
@@ -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. */
@@ -1,25 +1,25 @@
1
1
  /**
2
- * Deterministic detector for the F-1 laundering shape: a CONTEXT bullet that asserts
2
+ * Deterministic detector for the laundering shape: a CONTEXT bullet that asserts
3
3
  * external API USAGE SEMANTICS under an attribution cue that no EXTERNAL CONTEXT block
4
4
  * can actually support.
5
5
  *
6
- * THE SHAPE, from mx5 run 15 (TASK_0027.md, verbatim):
6
+ * THE SHAPE:
7
7
  *
8
8
  * - The `hono` dependency is pinned at `^4.12.31` in package.json, and the external
9
9
  * context confirms `hc<AppType>` pattern with base URL `/api` for same-origin
10
10
  * relative paths works correctly (per Hono RPC docs LIVE data).
11
11
  *
12
- * worker:context has tools `read,grep` only (phases.ts:623) it cannot consult any
13
- * documentation — so the base-URL claim was necessarily from model memory. It shipped
14
- * into TASK_0027's CONSTRAINTS and ACCEPTANCE, the implementation obeyed it exactly
15
- * (`hc<AppType>('/api')` plus `api.api.auth.login.$post()`), and every request went to
16
- * `/api/api/...` ⇒ 404 ⇒ the product's entire API surface was dead.
12
+ * The `worker:context` row in phases.ts gives that worker `tools: 'read,grep'` and
13
+ * nothing else no fetch, no docs channel — so a base-URL claim like that one is
14
+ * necessarily from model memory. It then ships into a task's CONSTRAINTS and
15
+ * ACCEPTANCE, the implementation obeys it exactly, and every request goes to the
16
+ * wrong path.
17
17
  *
18
18
  * WHY THE OBVIOUS TEST DOES NOT WORK. "Flag a bullet whose package has no EXTERNAL
19
19
  * CONTEXT block" misses this case: EXTERNAL CONTEXT *did* carry a `### npm: hono` block.
20
20
  * That block contains version numbers and nothing else, so it cannot support a claim
21
21
  * about what a base URL MEANS — yet it lends the sentence an air of having been checked.
22
- * That is the whole mechanism (F-1e): one citable fact, the pinned version, fused in a
22
+ * That is the whole mechanism: one citable fact, the pinned version, fused in a
23
23
  * single sentence with an uncitable one under a shared attribution. The true half
24
24
  * launders the false half.
25
25
  *
@@ -30,27 +30,35 @@
30
30
  * ### url: fetched page content -> CAN source a semantics claim
31
31
  * ### service: 3 search-result SNIPPETS -> cannot source a semantics claim
32
32
  *
33
- * The `service` exclusion is not a judgement call — it is the LIVE-DATA RULE's own scope.
34
- * That rule makes a service block authoritative for "current API surface, deprecation
35
- * status, and replacement systems", i.e. versions/status/names. A service block is a
36
- * title + URL + one-line description per result (service-blocks.ts:10); it cannot carry
37
- * what a parameter MEANS. This matters concretely: TASK_0027's enrichment produced
38
- * exactly one service block, `### service: Hono RPC client` (extractEnrichTargets on the
39
- * verbatim refined task yields services=[Hono RPC client], urls=[], packages=[any,api,hc]).
40
- * Were `service` treated as source-capable for semantics, the subject string "Hono RPC
41
- * client" would match the package `hono` and the fatal bullet would pass unflagged — the
42
- * detector would be unable to catch the very defect it exists for.
33
+ * The `service` exclusion is not a judgement call — it is the LIVE-DATA RULE's own
34
+ * scope. That rule (prompts.ts) makes a service block authoritative for "current
35
+ * API surface, deprecation status, and replacement systems", i.e. versions, status
36
+ * and names. And a service block is only ever a title, a URL and a one-line
37
+ * description per result see `formatServiceBlock` so it cannot carry what a
38
+ * parameter MEANS.
39
+ *
40
+ * This matters concretely, and was checked: with a single
41
+ * `### service: Hono RPC client` block as the only external context, the fatal
42
+ * bullet IS flagged. Were `service` treated as source-capable, that subject would
43
+ * match the package `hono` and the bullet would pass — leaving the detector unable
44
+ * to catch the one defect it exists for.
43
45
  *
44
46
  * A bullet is FLAGGED iff all three hold:
45
- * 1. it carries an attribution cue ("per ... LIVE data", "the external context
46
- * confirms", "docs confirm", "per the official docs", ...);
47
- * 2. it asserts API usage semantics — how something is called, what a parameter means,
48
- * what a default is, what behaviour results — as opposed to a version or a status;
47
+ * 1. it carries an attribution cue ("per... LIVE data", "the external context
48
+ * confirms", "docs confirm", "per the official docs",...);
49
+ * 2. it asserts API usage semantics — how something is called, what a parameter
50
+ * means, what a default is, what behaviour results — as opposed to a version
51
+ * or a status;
49
52
  * 3. no `### url:` or `### docs:` block exists for any package the bullet names.
50
53
  *
51
- * Pure and side-effect free; unit-tested in context-attribution.test.ts against the real
52
- * run-15 bullets, including the three legitimate attributed bullets (TASK_0007, _0012,
53
- * _0031) that must NOT be flagged.
54
+ * All six branches were run against the laundering bullet above. It is FLAGGED with
55
+ * an npm-only block, with a service-only block, and with no external context at
56
+ * all; it is NOT flagged once a `### docs:` or `### url:` block for that package
57
+ * exists. And the same context leaves alone a version claim made under an
58
+ * attribution cue, a semantics claim made with NO cue, and a plain "is pinned at"
59
+ * statement of fact.
60
+ *
61
+ * Pure and side-effect free; also unit-tested in context-attribution.test.ts.
54
62
  */
55
63
  /** A block that actually appears in an EXTERNAL CONTEXT header. */
56
64
  export interface ContextBlock {
@@ -81,7 +89,7 @@ export interface BulletSpan {
81
89
  /**
82
90
  * Split a CONTEXT section into bullets WITH their line ranges. Continuation lines are
83
91
  * folded into the bullet above so a hard-wrapped claim is judged as one sentence — which
84
- * is exactly how the fatal run-15 bullet was written.
92
+ * is exactly how the fatal bullet was written.
85
93
  */
86
94
  export declare function splitBulletSpans(context: string): BulletSpan[];
87
95
  /** Split a CONTEXT section into its bullets, joining hard-wrapped continuation lines. */