@mjasnikovs/pi-task 0.38.29 → 0.38.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/dist/config/config.d.ts +70 -70
  2. package/dist/config/config.js +26 -35
  3. package/dist/config/extension-list.d.ts +6 -5
  4. package/dist/config/extension-list.js +3 -2
  5. package/dist/config/reasoning-args.d.ts +9 -7
  6. package/dist/config/reasoning-args.js +12 -10
  7. package/dist/config/reasoning.d.ts +44 -105
  8. package/dist/config/reasoning.js +27 -704
  9. package/dist/config/register.d.ts +34 -48
  10. package/dist/config/register.js +41 -51
  11. package/dist/config/tool-list.d.ts +16 -16
  12. package/dist/config/tool-list.js +1 -1
  13. package/dist/remote/bridge.d.ts +19 -10
  14. package/dist/remote/bridge.js +3 -2
  15. package/dist/remote/broadcast.js +3 -1
  16. package/dist/remote/events.js +12 -11
  17. package/dist/remote/history.d.ts +1 -1
  18. package/dist/remote/protocol.d.ts +6 -3
  19. package/dist/remote/protocol.js +2 -1
  20. package/dist/remote/push.d.ts +16 -16
  21. package/dist/remote/push.js +27 -27
  22. package/dist/remote/register.d.ts +3 -3
  23. package/dist/remote/register.js +17 -19
  24. package/dist/remote/server.d.ts +9 -8
  25. package/dist/remote/server.js +15 -14
  26. package/dist/remote/session-state.d.ts +5 -4
  27. package/dist/remote/session-state.js +8 -5
  28. package/dist/remote/sw.d.ts +7 -6
  29. package/dist/remote/sw.js +7 -6
  30. package/dist/remote/tailscale.d.ts +4 -2
  31. package/dist/remote/tailscale.js +4 -2
  32. package/dist/remote/ui-highlight.js +6 -5
  33. package/dist/remote/ui-render.js +4 -4
  34. package/dist/remote/ui-script.js +24 -24
  35. package/dist/remote/ui-styles.d.ts +1 -1
  36. package/dist/remote/ui-styles.js +10 -13
  37. package/dist/remote/ui-tools.js +9 -6
  38. package/dist/shared/child-extensions.d.ts +29 -17
  39. package/dist/shared/child-extensions.js +29 -17
  40. package/dist/shared/child-output.d.ts +30 -24
  41. package/dist/shared/child-output.js +25 -17
  42. package/dist/shared/child-process.d.ts +47 -40
  43. package/dist/shared/child-process.js +50 -59
  44. package/dist/shared/command-watchdog.d.ts +22 -16
  45. package/dist/shared/command-watchdog.js +28 -21
  46. package/dist/shared/fs-text.d.ts +16 -10
  47. package/dist/shared/fs-text.js +16 -10
  48. package/dist/shared/git-runner.d.ts +25 -25
  49. package/dist/shared/git-runner.js +25 -25
  50. package/dist/shared/leaked-tool-call.d.ts +17 -11
  51. package/dist/shared/leaked-tool-call.js +23 -15
  52. package/dist/shared/model-endpoint.d.ts +29 -16
  53. package/dist/shared/model-endpoint.js +33 -21
  54. package/dist/shared/pi-invocation.d.ts +7 -4
  55. package/dist/shared/pi-invocation.js +12 -7
  56. package/dist/shared/pkg-version.d.ts +13 -5
  57. package/dist/shared/pkg-version.js +13 -5
  58. package/dist/shared/reasoning-capability.d.ts +35 -24
  59. package/dist/shared/reasoning-capability.js +35 -24
  60. package/dist/shared/stream-watchdog.d.ts +60 -44
  61. package/dist/shared/stream-watchdog.js +62 -45
  62. package/dist/task/accept-debt.d.ts +41 -43
  63. package/dist/task/accept-debt.js +73 -65
  64. package/dist/task/api-synthesis.d.ts +24 -21
  65. package/dist/task/api-synthesis.js +32 -26
  66. package/dist/task/apis-contract.d.ts +32 -64
  67. package/dist/task/apis-contract.js +32 -64
  68. package/dist/task/artifact-closure.d.ts +27 -13
  69. package/dist/task/artifact-closure.js +95 -67
  70. package/dist/task/auto-commit.d.ts +46 -35
  71. package/dist/task/auto-commit.js +51 -38
  72. package/dist/task/auto-io.d.ts +45 -25
  73. package/dist/task/auto-io.js +57 -29
  74. package/dist/task/auto-orchestrator.d.ts +26 -24
  75. package/dist/task/auto-orchestrator.js +178 -162
  76. package/dist/task/auto-prompts.d.ts +36 -24
  77. package/dist/task/auto-prompts.js +40 -26
  78. package/dist/task/autofix-ledger.d.ts +27 -25
  79. package/dist/task/autofix-ledger.js +29 -26
  80. package/dist/task/batch-test-task.d.ts +20 -12
  81. package/dist/task/batch-test-task.js +67 -60
  82. package/dist/task/boot-probe.d.ts +60 -44
  83. package/dist/task/boot-probe.js +91 -72
  84. package/dist/task/cancel-input.d.ts +30 -16
  85. package/dist/task/cancel-input.js +20 -11
  86. package/dist/task/cancel-points.d.ts +27 -20
  87. package/dist/task/cancel-points.js +30 -22
  88. package/dist/task/child-runner.d.ts +46 -51
  89. package/dist/task/child-runner.js +48 -49
  90. package/dist/task/child-status.d.ts +23 -16
  91. package/dist/task/child-status.js +23 -16
  92. package/dist/task/clamp-output.js +12 -5
  93. package/dist/task/command-run.d.ts +31 -28
  94. package/dist/task/command-run.js +44 -35
  95. package/dist/task/command-shrink.d.ts +25 -18
  96. package/dist/task/command-shrink.js +37 -31
  97. package/dist/task/command-watchdog.d.ts +9 -6
  98. package/dist/task/command-watchdog.js +21 -15
  99. package/dist/task/context-attribution.d.ts +34 -26
  100. package/dist/task/context-attribution.js +34 -26
  101. package/dist/task/context-silence.d.ts +39 -29
  102. package/dist/task/context-silence.js +35 -25
  103. package/dist/task/context-usage.d.ts +16 -9
  104. package/dist/task/context-usage.js +16 -9
  105. package/dist/task/contracts.d.ts +8 -4
  106. package/dist/task/contracts.js +25 -17
  107. package/dist/task/coverage-loop.d.ts +22 -18
  108. package/dist/task/coverage-loop.js +35 -30
  109. package/dist/task/critique-probes.d.ts +13 -14
  110. package/dist/task/critique-probes.js +50 -39
  111. package/dist/task/debug-log.d.ts +13 -5
  112. package/dist/task/debug-log.js +32 -20
  113. package/dist/task/decompose-fidelity.d.ts +11 -9
  114. package/dist/task/decompose-fidelity.js +38 -33
  115. package/dist/task/decompose-granularity.d.ts +41 -38
  116. package/dist/task/decompose-granularity.js +41 -38
  117. package/dist/task/deep-render-check.d.ts +22 -14
  118. package/dist/task/deep-render-check.js +40 -31
  119. package/dist/task/dropped-input.d.ts +12 -7
  120. package/dist/task/dropped-input.js +5 -2
  121. package/dist/task/enforce-attribution.d.ts +38 -47
  122. package/dist/task/enforce-attribution.js +46 -52
  123. package/dist/task/enforce-guidelines.d.ts +31 -20
  124. package/dist/task/enforce-guidelines.js +32 -21
  125. package/dist/task/enrichment.d.ts +7 -2
  126. package/dist/task/enrichment.js +26 -14
  127. package/dist/task/env-notes.d.ts +16 -7
  128. package/dist/task/env-notes.js +48 -31
  129. package/dist/task/env-template-closure.d.ts +4 -4
  130. package/dist/task/env-template-closure.js +42 -34
  131. package/dist/task/external-context.d.ts +28 -21
  132. package/dist/task/external-context.js +17 -12
  133. package/dist/task/failure-classifier.d.ts +4 -5
  134. package/dist/task/failure-classifier.js +6 -7
  135. package/dist/task/file-inventory.d.ts +15 -11
  136. package/dist/task/file-inventory.js +25 -22
  137. package/dist/task/final-gate-fix.d.ts +74 -86
  138. package/dist/task/final-gate-fix.js +97 -116
  139. package/dist/task/final-gate-progress.d.ts +29 -46
  140. package/dist/task/final-gate-progress.js +40 -51
  141. package/dist/task/final-gate.d.ts +64 -97
  142. package/dist/task/final-gate.js +192 -199
  143. package/dist/task/fix-child.d.ts +21 -27
  144. package/dist/task/fix-child.js +21 -27
  145. package/dist/task/foreign-path.d.ts +6 -5
  146. package/dist/task/foreign-path.js +0 -0
  147. package/dist/task/frozen-conflict.d.ts +9 -10
  148. package/dist/task/frozen-conflict.js +61 -64
  149. package/dist/task/frozen-path-guard.d.ts +35 -14
  150. package/dist/task/frozen-path-guard.js +56 -39
  151. package/dist/task/gate-child.d.ts +27 -28
  152. package/dist/task/gate-child.js +36 -35
  153. package/dist/task/gate-deps.d.ts +34 -27
  154. package/dist/task/gate-deps.js +169 -159
  155. package/dist/task/gate-tally.d.ts +77 -80
  156. package/dist/task/gate-tally.js +65 -68
  157. package/dist/task/git-state-guard.d.ts +15 -11
  158. package/dist/task/git-state-guard.js +76 -66
  159. package/dist/task/impl-widget.d.ts +25 -16
  160. package/dist/task/impl-widget.js +27 -17
  161. package/dist/task/implementation-thinking.d.ts +33 -31
  162. package/dist/task/implementation-thinking.js +5 -6
  163. package/dist/task/implementation-turn.d.ts +34 -31
  164. package/dist/task/implementation-turn.js +29 -27
  165. package/dist/task/inline-markdown.d.ts +20 -7
  166. package/dist/task/inline-markdown.js +15 -6
  167. package/dist/task/launch-config-gap.js +25 -39
  168. package/dist/task/launch-contract.d.ts +18 -21
  169. package/dist/task/launch-contract.js +28 -30
  170. package/dist/task/launch-manifest.d.ts +6 -2
  171. package/dist/task/launch-manifest.js +35 -34
  172. package/dist/task/ledger.js +16 -14
  173. package/dist/task/lint-fix.d.ts +6 -8
  174. package/dist/task/lint-fix.js +67 -69
  175. package/dist/task/loop-detector.d.ts +9 -8
  176. package/dist/task/loop-detector.js +16 -12
  177. package/dist/task/mid-run-input.d.ts +17 -15
  178. package/dist/task/mid-run-input.js +17 -15
  179. package/dist/task/orchestrator.d.ts +24 -28
  180. package/dist/task/orchestrator.js +62 -64
  181. package/dist/task/orientation.d.ts +18 -23
  182. package/dist/task/orientation.js +24 -31
  183. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  184. package/dist/task/owned-freeze-conflict.js +52 -85
  185. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  186. package/dist/task/owned-freeze-reassign.js +41 -61
  187. package/dist/task/parsers.d.ts +4 -2
  188. package/dist/task/parsers.js +4 -4
  189. package/dist/task/phases.d.ts +41 -48
  190. package/dist/task/phases.js +179 -248
  191. package/dist/task/plan-io.d.ts +6 -7
  192. package/dist/task/plan-io.js +6 -7
  193. package/dist/task/plan-orchestrator.d.ts +10 -8
  194. package/dist/task/plan-orchestrator.js +14 -10
  195. package/dist/task/plan-prompts.d.ts +6 -5
  196. package/dist/task/plan-prompts.js +6 -5
  197. package/dist/task/plan-readonly.d.ts +4 -5
  198. package/dist/task/plan-readonly.js +4 -5
  199. package/dist/task/plan-rounds.d.ts +17 -29
  200. package/dist/task/plan-rounds.js +21 -34
  201. package/dist/task/plan-session.d.ts +58 -72
  202. package/dist/task/plan-session.js +61 -83
  203. package/dist/task/probe-gaming.d.ts +28 -27
  204. package/dist/task/probe-gaming.js +0 -0
  205. package/dist/task/prohibition-probe.d.ts +14 -16
  206. package/dist/task/prompts.d.ts +3 -4
  207. package/dist/task/prompts.js +17 -26
  208. package/dist/task/qa-transcript.d.ts +15 -22
  209. package/dist/task/qa-transcript.js +15 -21
  210. package/dist/task/question-box.d.ts +17 -13
  211. package/dist/task/question-box.js +19 -15
  212. package/dist/task/question-dedup.d.ts +6 -7
  213. package/dist/task/question-dedup.js +13 -14
  214. package/dist/task/question-dialog.d.ts +22 -32
  215. package/dist/task/question-dialog.js +22 -32
  216. package/dist/task/question-source.d.ts +18 -44
  217. package/dist/task/question-source.js +22 -51
  218. package/dist/task/refuted-constraint.d.ts +11 -31
  219. package/dist/task/refuted-constraint.js +27 -51
  220. package/dist/task/regenerable-artifacts.d.ts +12 -31
  221. package/dist/task/regenerable-artifacts.js +12 -31
  222. package/dist/task/render-check.d.ts +11 -22
  223. package/dist/task/render-check.js +33 -46
  224. package/dist/task/repo-health-check.d.ts +10 -14
  225. package/dist/task/repo-health-check.js +17 -23
  226. package/dist/task/requirements.d.ts +38 -71
  227. package/dist/task/requirements.js +78 -126
  228. package/dist/task/research-fanout-budget.d.ts +51 -88
  229. package/dist/task/research-fanout-budget.js +51 -88
  230. package/dist/task/research-worker.d.ts +29 -39
  231. package/dist/task/research-worker.js +37 -61
  232. package/dist/task/resume-gap.d.ts +14 -15
  233. package/dist/task/root-cause-repair.d.ts +9 -9
  234. package/dist/task/root-cause-repair.js +28 -40
  235. package/dist/task/run-bracket.d.ts +10 -13
  236. package/dist/task/run-end.d.ts +12 -22
  237. package/dist/task/run-end.js +8 -16
  238. package/dist/task/run-final-gate.d.ts +19 -21
  239. package/dist/task/run-final-gate.js +62 -80
  240. package/dist/task/runner-globs.d.ts +12 -13
  241. package/dist/task/runner-globs.js +12 -13
  242. package/dist/task/runner-resolve.d.ts +9 -9
  243. package/dist/task/runner-resolve.js +22 -23
  244. package/dist/task/script-escape.d.ts +10 -12
  245. package/dist/task/script-escape.js +13 -14
  246. package/dist/task/serve-entry.d.ts +1 -1
  247. package/dist/task/serve-entry.js +22 -25
  248. package/dist/task/service-blocks.js +4 -2
  249. package/dist/task/shipped-source.d.ts +11 -29
  250. package/dist/task/shipped-source.js +11 -29
  251. package/dist/task/skip-escape.js +10 -14
  252. package/dist/task/spec-urls.d.ts +26 -65
  253. package/dist/task/spec-urls.js +26 -65
  254. package/dist/task/spec-validation.d.ts +17 -20
  255. package/dist/task/spec-validation.js +17 -20
  256. package/dist/task/stall-detector.d.ts +23 -30
  257. package/dist/task/stall-detector.js +23 -30
  258. package/dist/task/stream-watchdog.d.ts +14 -12
  259. package/dist/task/stream-watchdog.js +14 -12
  260. package/dist/task/substitution-probe.d.ts +17 -20
  261. package/dist/task/substitution-probe.js +17 -20
  262. package/dist/task/task-gates.d.ts +36 -41
  263. package/dist/task/task-gates.js +95 -106
  264. package/dist/task/task-io.d.ts +4 -4
  265. package/dist/task/task-io.js +4 -4
  266. package/dist/task/task-parsers.js +4 -3
  267. package/dist/task/task-provenance.d.ts +2 -2
  268. package/dist/task/task-provenance.js +11 -13
  269. package/dist/task/task-types.d.ts +4 -3
  270. package/dist/task/terminal-outcome.d.ts +14 -16
  271. package/dist/task/terminal-outcome.js +12 -14
  272. package/dist/task/test-assembly.d.ts +13 -20
  273. package/dist/task/test-assembly.js +13 -20
  274. package/dist/task/timings.d.ts +5 -3
  275. package/dist/task/timings.js +5 -3
  276. package/dist/task/title-label.d.ts +9 -4
  277. package/dist/task/title-label.js +9 -4
  278. package/dist/task/type-only-answer.d.ts +44 -52
  279. package/dist/task/type-only-answer.js +44 -52
  280. package/dist/task/unfailable-command.d.ts +18 -24
  281. package/dist/task/unfailable-command.js +21 -27
  282. package/dist/task/unknown-routing.d.ts +10 -4
  283. package/dist/task/unknown-routing.js +10 -4
  284. package/dist/task/user-directives.d.ts +5 -8
  285. package/dist/task/user-directives.js +5 -8
  286. package/dist/task/verify-quality.d.ts +18 -22
  287. package/dist/task/verify-quality.js +45 -46
  288. package/dist/task/verify-reconcile.d.ts +15 -10
  289. package/dist/task/verify-reconcile.js +45 -43
  290. package/dist/task/verify-resolution.d.ts +24 -20
  291. package/dist/task/verify-resolution.js +51 -50
  292. package/dist/task/verify-work.d.ts +59 -66
  293. package/dist/task/verify-work.js +101 -138
  294. package/dist/task/widget.d.ts +15 -14
  295. package/dist/task/widget.js +22 -17
  296. package/dist/task/wiring-claims.d.ts +25 -32
  297. package/dist/task/wiring-claims.js +30 -35
  298. package/dist/task/write-guard.d.ts +39 -39
  299. package/dist/task/write-guard.js +48 -51
  300. package/dist/task/yolo.d.ts +34 -30
  301. package/dist/task/yolo.js +42 -37
  302. package/dist/workers/abstention.d.ts +21 -41
  303. package/dist/workers/abstention.js +27 -48
  304. package/dist/workers/brave-search.d.ts +4 -3
  305. package/dist/workers/brave-search.js +5 -2
  306. package/dist/workers/brave-warning.d.ts +7 -4
  307. package/dist/workers/brave-warning.js +19 -7
  308. package/dist/workers/ddg-search.d.ts +6 -6
  309. package/dist/workers/ddg-search.js +18 -12
  310. package/dist/workers/docs-cache.js +5 -2
  311. package/dist/workers/docs-chunk.d.ts +30 -37
  312. package/dist/workers/docs-chunk.js +37 -41
  313. package/dist/workers/docs-core.d.ts +28 -44
  314. package/dist/workers/docs-core.js +25 -44
  315. package/dist/workers/docs-index.js +4 -3
  316. package/dist/workers/docs-lookup.d.ts +15 -22
  317. package/dist/workers/docs-lookup.js +12 -21
  318. package/dist/workers/docs-project.d.ts +15 -9
  319. package/dist/workers/docs-project.js +17 -10
  320. package/dist/workers/docs-resolve.d.ts +19 -20
  321. package/dist/workers/docs-resolve.js +35 -32
  322. package/dist/workers/docs-retrieve.d.ts +5 -6
  323. package/dist/workers/docs-retrieve.js +18 -15
  324. package/dist/workers/exa-search.d.ts +9 -6
  325. package/dist/workers/exa-search.js +23 -12
  326. package/dist/workers/fetch-core.d.ts +13 -16
  327. package/dist/workers/fetch-core.js +23 -23
  328. package/dist/workers/focused-extractor.d.ts +12 -12
  329. package/dist/workers/focused-extractor.js +16 -19
  330. package/dist/workers/html-clean.js +24 -14
  331. package/dist/workers/http-request.d.ts +28 -20
  332. package/dist/workers/http-request.js +22 -17
  333. package/dist/workers/npm-version.d.ts +28 -11
  334. package/dist/workers/npm-version.js +24 -15
  335. package/dist/workers/phantom-imports.d.ts +15 -12
  336. package/dist/workers/phantom-imports.js +30 -24
  337. package/dist/workers/pi-worker-core.d.ts +69 -71
  338. package/dist/workers/pi-worker-core.js +100 -109
  339. package/dist/workers/pi-worker-docs.d.ts +24 -19
  340. package/dist/workers/pi-worker-docs.js +67 -76
  341. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  342. package/dist/workers/pi-worker-fetch.js +27 -19
  343. package/dist/workers/pi-worker-search.js +12 -8
  344. package/dist/workers/pi-worker.d.ts +9 -4
  345. package/dist/workers/pi-worker.js +21 -14
  346. package/dist/workers/reasoning-warning.d.ts +18 -17
  347. package/dist/workers/reasoning-warning.js +22 -20
  348. package/dist/workers/research-cache.js +50 -78
  349. package/dist/workers/search-core.js +7 -5
  350. package/dist/workers/search-types.d.ts +10 -9
  351. package/dist/workers/search-types.js +9 -8
  352. package/dist/workers/session-hint.d.ts +13 -14
  353. package/dist/workers/session-hint.js +8 -9
  354. package/dist/workers/shared.d.ts +21 -25
  355. package/dist/workers/shared.js +0 -0
  356. package/dist/workers/single-read-extension.d.ts +14 -7
  357. package/dist/workers/single-read-extension.js +14 -7
  358. package/dist/workers/single-read-guard.d.ts +25 -28
  359. package/dist/workers/single-read-guard.js +32 -32
  360. package/dist/workers/typeonly-log.d.ts +12 -9
  361. package/dist/workers/typeonly-log.js +29 -33
  362. package/dist/workers/worker-channels.d.ts +15 -23
  363. package/dist/workers/worker-channels.js +15 -23
  364. package/dist/workers/worker-failure.d.ts +38 -46
  365. package/dist/workers/worker-failure.js +31 -39
  366. package/dist/workers/worker-kill.d.ts +25 -26
  367. package/dist/workers/worker-kill.js +16 -19
  368. package/dist/workers/worker-profiles.d.ts +43 -53
  369. package/dist/workers/worker-profiles.js +30 -38
  370. package/package.json +10 -8
@@ -2,26 +2,23 @@
2
2
  * The **Focused extractor** — the one place a no-tools child pi is run to answer ONE question
3
3
  * over content already in hand, cite a verbatim `<excerpt>`, and have that citation checked.
4
4
  *
5
- * It had four copies: `fetchFocused` (fetch-core), `docsFocused` (docs-core), and both paths
6
- * of `pi-worker-docs` (project source, npm package) the last two 17-of-20 lines identical
7
- * and 150 lines apart inside one function. Everything the four did around the child was
8
- * byte-identical: the `--no-tools` argv, `getPiInvocation`, `runChild`, `parseChildOutput`,
9
- * and no retry. What genuinely differed was DATA, and the copies hid it:
5
+ * Its two callers are `fetchFocused` (fetch-core) and `docsLookup` (docs-lookup), and
6
+ * everything they do around the child is the same: the `--no-tools` argv,
7
+ * `getPiInvocation`, `runChild`, `parseChildOutput`, and no retry. What genuinely differs is
8
+ * DATA, and each difference has a name here:
10
9
  *
11
- * 1. the prompt body — so {@link FocusedRequest.prompt} is a string the caller assembles;
10
+ * 1. the prompt body — {@link FocusedRequest.prompt} is a string the caller assembles;
12
11
  * 2. WHAT the excerpt is verified against — see {@link FocusedRequest.verifyAgainst};
13
12
  * 3. what the answer MEANS (fetch's coverage-miss sentinel) — left to the caller, because
14
13
  * classifying an answer is the caller's domain, not the child runner's.
15
14
  *
16
- * Two things the copies got wrong, fixed here once:
17
- * - **Failure.** `docsFocused` never called `formatChildFailure`, so a child that exited
18
- * non-zero had its raw stdout parsed as if it were an answer (`parseChildOutput` returns
19
- * the whole trimmed stdout when there is no `<answer>` tag) and handed to the caller.
20
- * The result type below makes that unrepresentable: there is no `answer` on a failure.
21
- * - **Evidence.** Only fetch kept the rich {@link ExcerptVerification}; the other three
22
- * reduced it to a bare boolean, which is exactly the diagnostic gap typeonly-log.ts
23
- * names — an `excerptVerified === false` that cannot afterwards be attributed to
24
- * fabrication vs a normaliser gap. Every caller now gets the struct.
15
+ * Two invariants the result type enforces rather than documents:
16
+ * - **Failure.** A child that aborted or exited non-zero has no `answer` field at all. It
17
+ * cannot be read as one, which matters because `parseChildOutput` returns the whole
18
+ * trimmed stdout when there is no `<answer>` tag a crashed child's error dump.
19
+ * - **Evidence.** Every caller gets the full {@link ExcerptVerification}, not a bare
20
+ * boolean, so a false verdict can afterwards be attributed to fabrication rather than a
21
+ * normaliser gap.
25
22
  */
26
23
  import { spawn as defaultSpawn } from 'node:child_process';
27
24
  import { getPiInvocation } from '../shared/pi-invocation.js';
@@ -30,9 +27,9 @@ import { childBaseArgs } from '../shared/child-extensions.js';
30
27
  import { parseChildOutput, verifyExcerpt } from '../shared/child-output.js';
31
28
  import { formatChildFailure } from './shared.js';
32
29
  /**
33
- * The argv every focused extraction child runs with: the shared child base (whitelisted
34
- * extensions + `--print --no-session …`) plus `--no-tools`. This was three byte-identical
35
- * one-liners in fetch-core, docs-core and pi-worker-docs.
30
+ * The argv every focused extraction child runs with: the shared child base — any whitelisted
31
+ * extensions, then `--print --no-skills --no-extensions --no-prompt-templates
32
+ * --no-context-files --no-session` — then the caller's thinking fragment, then `--no-tools`.
36
33
  *
37
34
  * `--no-tools` is the contract, not a default: the child is given all the content it may use
38
35
  * inside its prompt, so a tool call could only reach for something unsourced.
@@ -47,7 +44,7 @@ export const focusedChildArgs = (thinking = []) => [
47
44
  * `<answer>`/`<excerpt>` and verify the excerpt against `verifyAgainst`.
48
45
  *
49
46
  * Never retries — a re-ask of a deterministic extraction over unchanged content is a second
50
- * bill for the same answer, and all four call sites already had it that way.
47
+ * bill for the same answer. Exactly one child is spawned per call.
51
48
  */
52
49
  export async function runFocusedExtraction(req) {
53
50
  const spawn = req.spawn ?? defaultSpawn;
@@ -20,7 +20,8 @@ export function cleanHtml(html, baseUrl) {
20
20
  finalUrl: baseUrl
21
21
  };
22
22
  }
23
- // Fallback: turndown the body
23
+ // Readability found no article — turndown the raw body instead. An empty body
24
+ // yields empty markdown, and the title falls back to the URL's hostname.
24
25
  const body = doc.body;
25
26
  const bodyHtml = body ? body.innerHTML : '';
26
27
  const markdown = turndown.turndown(bodyHtml).trim();
@@ -32,14 +33,17 @@ export function cleanHtml(html, baseUrl) {
32
33
  }
33
34
  const DEFAULT_TIMEOUT_MS = 15_000;
34
35
  const DEFAULT_MAX_BYTES = 2 * 1024 * 1024; // 2 MB
35
- // Read at runtime so the User-Agent never drifts out of sync with releases.
36
+ // Read at runtime `readPkgVersion` re-reads package.json on every call so the
37
+ // User-Agent never drifts out of sync with releases and no build step bakes it in.
36
38
  const PKG_VERSION = readPkgVersion();
37
39
  const USER_AGENT = `pi-worker/${PKG_VERSION} (+https://npmjs.com/package/@mjasnikovs/pi-worker)`;
38
- // Decide how to handle a response based on its content-type. HTML is run through
39
- // the readability/turndown pipeline; text-ish formats (markdown, plain text,
40
- // JSON, XML/feeds) are already clean and pass through verbatim; binary formats
41
- // (PDF, images, octet-stream, ) are rejected. A missing content-type is treated
42
- // as text many plain-text endpoints (llms.txt, robots.txt) omit the header.
40
+ // Decide how to handle a response based on its content-type, case-insensitively
41
+ // and ignoring any `; charset=…` tail. HTML and XHTML run through the
42
+ // readability/turndown pipeline; text-ish formats (any `text/*`, JSON and `+json`,
43
+ // XML and `+xml`, javascript) are already clean and pass through verbatim; anything
44
+ // elsePDF, images, octet-stream is rejected as `not-html`. A missing
45
+ // content-type is treated as text: many plain-text endpoints (llms.txt,
46
+ // robots.txt) omit the header.
43
47
  function classifyContentType(contentType) {
44
48
  const mime = contentType.split(';')[0].trim().toLowerCase();
45
49
  if (mime === '')
@@ -58,14 +62,17 @@ function classifyContentType(contentType) {
58
62
  }
59
63
  // Extract the charset from a content-type header, if present and supported by
60
64
  // TextDecoder; otherwise fall back to UTF-8 so non-UTF-8 pages aren't mangled.
65
+ // Quotes around the label are stripped, and an unsupported label decodes exactly
66
+ // as UTF-8 would rather than throwing.
61
67
  function decoderFor(contentType) {
62
68
  const match = /charset=([^;]+)/i.exec(contentType);
63
69
  const charset = match?.[1]?.trim().replace(/^["']|["']$/g, '');
64
70
  if (charset) {
65
71
  try {
66
- // The runtime accepts any charset label string; the type is narrowed
67
- // to a known-encoding union by Bun/Node's lib (DOM's looser signature
68
- // is no longer pulled in transitively). Cast to the actual param type.
72
+ // The runtime accepts any charset label string, but the type here is the
73
+ // narrow `Encoding` union passing a plain `string` is TS2345,
74
+ // "not assignable to parameter of type 'Encoding | undefined'". Cast to
75
+ // the actual param type rather than widening the guard.
69
76
  return new TextDecoder(charset, {
70
77
  fatal: false
71
78
  });
@@ -113,16 +120,19 @@ export async function fetchAndClean(url, opts = {}) {
113
120
  let bytesRead = 0;
114
121
  try {
115
122
  while (true) {
116
- // response.body's stream type doesn't resolve here, so the chunk
117
- // surfaces as `any`; pin it to the Uint8Array the reader yields.
123
+ // `reader.read()` resolves to `any` under this tsconfig assigning
124
+ // its `value` to a `number` raises no error so the destructure is
125
+ // pinned to the Uint8Array the reader actually yields.
118
126
  const { value, done } = (await reader.read());
119
127
  if (done)
120
128
  break;
121
129
  if (value) {
122
130
  bytesRead += value.byteLength;
123
131
  if (bytesRead > maxBytes) {
124
- // OUR abort, told apart from the user's and the clock's
125
- // by the seam all three abort the same signal.
132
+ // OUR abort. All three cancels fire the same signal, so the
133
+ // seam is what tells them apart: `ctl.abort()` here,
134
+ // `ctl.userAborted()` for the caller's, `ctl.timedOut()` for
135
+ // the clock. Only the local `sizeExceeded` flag says it was us.
126
136
  sizeExceeded = true;
127
137
  ctl.abort();
128
138
  break;
@@ -1,32 +1,36 @@
1
1
  /**
2
2
  * http-request — the one bounded HTTP request in this codebase.
3
3
  *
4
- * Five modules used to hand-roll the same ~12 lines: an internal
5
- * `AbortController`, a `setTimeout` that aborts it, a `userAborted` flag set
6
- * from the caller's signal, and a `finally` that clears the timer and removes
7
- * the listener. Five copies of a rule is five chances to drift, and it HAD
8
- * drifted `npm-version.ts` never grew the `userAborted` flag, so a user cancel
9
- * came back as `null`, indistinguishable from a registry that is down.
4
+ * Its five callers brave-search, ddg-search, exa-search, html-clean and
5
+ * npm-version would otherwise each hand-roll the same dozen lines: an internal
6
+ * `AbortController`, a `setTimeout` that aborts it, a `userAborted` flag set from
7
+ * the caller's signal, and a `finally` that clears the timer and removes the
8
+ * listener. Five copies of a rule is five chances to drift, and the flag is the
9
+ * piece that goes missing: without it a user cancel is indistinguishable from a
10
+ * host that is down.
10
11
  *
11
12
  * What is shared is the BOUNDING, not the interpretation. Each caller still owns
12
13
  * its own status-code policy (DDG treats 429/403 as rate-limiting, Brave splits
13
- * auth from rate-limit, npm treats every non-OK as "no answer") and its own error
14
- * type, because those genuinely differ. What they cannot differ on is whether the
15
- * request was cancelled by the user, killed by the clock, or refused by the
16
- * network so that verdict is made once, here.
14
+ * auth from rate-limit, npm-version treats every non-OK as "no answer") and its
15
+ * own error type, because those genuinely differ. A status code never reaches
16
+ * {@link HttpRequestError} at all the handler is simply called with the
17
+ * response, whatever its status.
17
18
  *
18
19
  * The handler runs INSIDE the timeout. `fetch` resolves as soon as the headers
19
20
  * arrive, so a seam that returned the `Response` and cleared its own timer would
20
- * leave the body read unbounded — a hung stream would hang forever. Passing a
21
- * handler keeps the clock over the whole operation, which is what every copy of
22
- * the ritual already did.
21
+ * leave the body read unbounded — a hung stream would hang forever. Here the
22
+ * clock aborts a handler that is still reading, and `ctl.timedOut()` is true
23
+ * inside it when that happens.
23
24
  */
24
25
  /** Injectable fetch — the narrow signature keeps test fakes free of Bun's extras. */
25
26
  export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
26
27
  /**
27
- * Why the request never produced a response. Deliberately only two kinds: a
28
- * status code is not a failure of the REQUEST, and what a given status means is
29
- * the caller's policy.
28
+ * Why the request never produced a response. Only two kinds, because a status code
29
+ * is not a failure of the REQUEST and what a status means is the caller's policy.
30
+ *
31
+ * `aborted` means the CALLER cancelled — nothing else mints it. A timeout during
32
+ * `fetch` arrives here as `network`, since only `userAborted` is consulted on that
33
+ * path; a handler that needs to tell the clock apart reads `ctl.timedOut()`.
30
34
  */
31
35
  export declare class HttpRequestError extends Error {
32
36
  readonly kind: 'aborted' | 'network';
@@ -44,7 +48,9 @@ export interface HttpRequestOpts {
44
48
  redirect?: 'follow' | 'error' | 'manual';
45
49
  /** Wall clock over the request AND the handler. Required — no silent default. */
46
50
  timeoutMs: number;
47
- /** The caller's cancel. Its firing is what makes `userAborted()` true. */
51
+ /** The caller's cancel. Its firing is what makes `userAborted()` true — and a
52
+ * signal that is ALREADY aborted sets the flag before `fetch` is called, so
53
+ * the immediate rejection is still classified as `aborted`. */
48
54
  signal?: AbortSignal;
49
55
  fetchImpl?: FetchLike;
50
56
  }
@@ -54,8 +60,9 @@ export interface HttpRequestControl {
54
60
  readonly signal: AbortSignal;
55
61
  /**
56
62
  * Abort the in-flight request from inside the handler — a size cap hit, an
57
- * early stop. Distinct from both a user cancel and the timeout, so a handler
58
- * that calls this can tell its own abort apart from the other two.
63
+ * early stop. It sets NEITHER flag, so after calling it `userAborted()` and
64
+ * `timedOut()` are both false while `signal.aborted` is true. A handler that
65
+ * calls this can therefore tell its own abort from the other two.
59
66
  */
60
67
  abort(): void;
61
68
  /** The CALLER cancelled. Not the timeout, not `abort()`. */
@@ -70,5 +77,6 @@ export interface HttpRequestControl {
70
77
  * `handle` throws propagates untouched — that is the caller's own policy talking.
71
78
  */
72
79
  export declare function httpRequest<T>(url: string, opts: HttpRequestOpts, handle: (response: Response, ctl: HttpRequestControl) => Promise<T>): Promise<T>;
73
- /** A readable one-liner for an unknown thrown value. Four byte-identical copies. */
80
+ /** A readable one-liner for an unknown thrown value: an Error's `message`,
81
+ * otherwise `String(err)` — so `42` and `null` render as text, not as a crash. */
74
82
  export declare function describeError(err: unknown): string;
@@ -1,30 +1,34 @@
1
1
  /**
2
2
  * http-request — the one bounded HTTP request in this codebase.
3
3
  *
4
- * Five modules used to hand-roll the same ~12 lines: an internal
5
- * `AbortController`, a `setTimeout` that aborts it, a `userAborted` flag set
6
- * from the caller's signal, and a `finally` that clears the timer and removes
7
- * the listener. Five copies of a rule is five chances to drift, and it HAD
8
- * drifted `npm-version.ts` never grew the `userAborted` flag, so a user cancel
9
- * came back as `null`, indistinguishable from a registry that is down.
4
+ * Its five callers brave-search, ddg-search, exa-search, html-clean and
5
+ * npm-version would otherwise each hand-roll the same dozen lines: an internal
6
+ * `AbortController`, a `setTimeout` that aborts it, a `userAborted` flag set from
7
+ * the caller's signal, and a `finally` that clears the timer and removes the
8
+ * listener. Five copies of a rule is five chances to drift, and the flag is the
9
+ * piece that goes missing: without it a user cancel is indistinguishable from a
10
+ * host that is down.
10
11
  *
11
12
  * What is shared is the BOUNDING, not the interpretation. Each caller still owns
12
13
  * its own status-code policy (DDG treats 429/403 as rate-limiting, Brave splits
13
- * auth from rate-limit, npm treats every non-OK as "no answer") and its own error
14
- * type, because those genuinely differ. What they cannot differ on is whether the
15
- * request was cancelled by the user, killed by the clock, or refused by the
16
- * network so that verdict is made once, here.
14
+ * auth from rate-limit, npm-version treats every non-OK as "no answer") and its
15
+ * own error type, because those genuinely differ. A status code never reaches
16
+ * {@link HttpRequestError} at all the handler is simply called with the
17
+ * response, whatever its status.
17
18
  *
18
19
  * The handler runs INSIDE the timeout. `fetch` resolves as soon as the headers
19
20
  * arrive, so a seam that returned the `Response` and cleared its own timer would
20
- * leave the body read unbounded — a hung stream would hang forever. Passing a
21
- * handler keeps the clock over the whole operation, which is what every copy of
22
- * the ritual already did.
21
+ * leave the body read unbounded — a hung stream would hang forever. Here the
22
+ * clock aborts a handler that is still reading, and `ctl.timedOut()` is true
23
+ * inside it when that happens.
23
24
  */
24
25
  /**
25
- * Why the request never produced a response. Deliberately only two kinds: a
26
- * status code is not a failure of the REQUEST, and what a given status means is
27
- * the caller's policy.
26
+ * Why the request never produced a response. Only two kinds, because a status code
27
+ * is not a failure of the REQUEST and what a status means is the caller's policy.
28
+ *
29
+ * `aborted` means the CALLER cancelled — nothing else mints it. A timeout during
30
+ * `fetch` arrives here as `network`, since only `userAborted` is consulted on that
31
+ * path; a handler that needs to tell the clock apart reads `ctl.timedOut()`.
28
32
  */
29
33
  export class HttpRequestError extends Error {
30
34
  kind;
@@ -95,7 +99,8 @@ export async function httpRequest(url, opts, handle) {
95
99
  opts.signal.removeEventListener('abort', onUserAbort);
96
100
  }
97
101
  }
98
- /** A readable one-liner for an unknown thrown value. Four byte-identical copies. */
102
+ /** A readable one-liner for an unknown thrown value: an Error's `message`,
103
+ * otherwise `String(err)` — so `42` and `null` render as text, not as a crash. */
99
104
  export function describeError(err) {
100
105
  if (err instanceof Error)
101
106
  return err.message;
@@ -1,18 +1,25 @@
1
1
  /**
2
2
  * Live npm registry version lookup.
3
3
  *
4
- * Solves a real failure mode: the auto-answer/research workers have no live
5
- * source for "what's the latest published version of X", so they answer from
6
- * training data, which goes stale. TASK_0005 hit this the worker said
7
- * "18.3.1, the latest stable React" when React 19.x had shipped months earlier.
4
+ * Without it the auto-answer and research workers have no live source for "what
5
+ * is the latest published version of X", so they answer from training data, which
6
+ * is stale by construction it can only ever name a version that existed when the
7
+ * model was trained.
8
8
  *
9
- * This module fetches the npm registry's metadata endpoint and returns just
10
- * enough to anchor the worker in current reality: the dist-tag 'latest', a
11
- * short list of recent versions, and the publish date of latest.
9
+ * This fetches the registry's metadata endpoint and returns just enough to anchor
10
+ * a worker in the present: the dist-tag `latest`, a short list of versions, and
11
+ * the publish date of `latest`.
12
12
  */
13
13
  export interface NpmVersionInfo {
14
14
  pkg: string;
15
+ /** The `latest` dist-tag — the stable release npm would install. */
15
16
  latest: string;
17
+ /**
18
+ * The LAST `RECENT_VERSIONS_LIMIT` keys of the registry's `versions` map,
19
+ * reversed. That is publication order, not semver order, and it is not
20
+ * filtered: for a package that publishes prereleases these are mostly canary
21
+ * and experimental builds, not the recent stable releases.
22
+ */
16
23
  recent: string[];
17
24
  publishedAt?: string;
18
25
  }
@@ -23,10 +30,20 @@ export interface NpmVersionOpts {
23
30
  }
24
31
  /**
25
32
  * Fetch the latest version + recent version list for an npm package.
26
- * Returns `null` (never throws) on any failure — registry down, package not
27
- * found, network error, malformed response. The caller treats null as "no
28
- * fresh data available" and continues without it.
33
+ *
34
+ * Returns `null` on every ordinary failure an invalid package name, a registry
35
+ * that is down, a 404, a malformed body, a missing `dist-tags.latest`. The caller
36
+ * treats null as "no fresh data available" and continues without it.
37
+ *
38
+ * It is NOT null-on-everything: a user cancel is re-thrown (see below), and so is
39
+ * anything that is not an {@link HttpRequestError}.
40
+ *
41
+ * An invalid name is rejected before any request is made. `UPPER` is valid — the
42
+ * name regex is case-insensitive — while an empty name, a leading `.` or `_`, a
43
+ * space, more than 214 characters, or an unscoped path like `a/b/c` are not.
29
44
  */
30
45
  export declare function npmVersionLookup(pkg: string, opts?: NpmVersionOpts): Promise<NpmVersionInfo | null>;
31
- /** Format an NpmVersionInfo as a short Markdown block for EXTERNAL CONTEXT. */
46
+ /** Format an NpmVersionInfo as a short Markdown block for EXTERNAL CONTEXT: a
47
+ * `### npm: <pkg>` heading, a `latest:` line that gains ` (published YYYY-MM-DD)`
48
+ * when the date is known, and a `recent:` line only when the list is non-empty. */
32
49
  export declare function formatNpmVersionSection(info: NpmVersionInfo): string;
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Live npm registry version lookup.
3
3
  *
4
- * Solves a real failure mode: the auto-answer/research workers have no live
5
- * source for "what's the latest published version of X", so they answer from
6
- * training data, which goes stale. TASK_0005 hit this the worker said
7
- * "18.3.1, the latest stable React" when React 19.x had shipped months earlier.
4
+ * Without it the auto-answer and research workers have no live source for "what
5
+ * is the latest published version of X", so they answer from training data, which
6
+ * is stale by construction it can only ever name a version that existed when the
7
+ * model was trained.
8
8
  *
9
- * This module fetches the npm registry's metadata endpoint and returns just
10
- * enough to anchor the worker in current reality: the dist-tag 'latest', a
11
- * short list of recent versions, and the publish date of latest.
9
+ * This fetches the registry's metadata endpoint and returns just enough to anchor
10
+ * a worker in the present: the dist-tag `latest`, a short list of versions, and
11
+ * the publish date of `latest`.
12
12
  */
13
13
  import { httpRequest, HttpRequestError } from './http-request.js';
14
14
  const REGISTRY_BASE = 'https://registry.npmjs.org';
@@ -16,9 +16,17 @@ const DEFAULT_TIMEOUT_MS = 3000;
16
16
  const RECENT_VERSIONS_LIMIT = 10;
17
17
  /**
18
18
  * Fetch the latest version + recent version list for an npm package.
19
- * Returns `null` (never throws) on any failure — registry down, package not
20
- * found, network error, malformed response. The caller treats null as "no
21
- * fresh data available" and continues without it.
19
+ *
20
+ * Returns `null` on every ordinary failure an invalid package name, a registry
21
+ * that is down, a 404, a malformed body, a missing `dist-tags.latest`. The caller
22
+ * treats null as "no fresh data available" and continues without it.
23
+ *
24
+ * It is NOT null-on-everything: a user cancel is re-thrown (see below), and so is
25
+ * anything that is not an {@link HttpRequestError}.
26
+ *
27
+ * An invalid name is rejected before any request is made. `UPPER` is valid — the
28
+ * name regex is case-insensitive — while an empty name, a leading `.` or `_`, a
29
+ * space, more than 214 characters, or an unscoped path like `a/b/c` are not.
22
30
  */
23
31
  export async function npmVersionLookup(pkg, opts = {}) {
24
32
  if (!isValidPackageName(pkg))
@@ -54,10 +62,9 @@ export async function npmVersionLookup(pkg, opts = {}) {
54
62
  });
55
63
  }
56
64
  catch (err) {
57
- // A user cancel is re-thrown, not swallowed. This module was the copy of the
58
- // request ritual that never grew a `userAborted` flag, so a cancelled lookup
59
- // returned `null` indistinguishable from a registry that is down, and the
60
- // caller went on assembling a block for a run the user had already stopped.
65
+ // A user cancel is RE-THROWN, not swallowed. Returning null here would make a
66
+ // cancelled lookup indistinguishable from a registry that is down, and the
67
+ // caller would go on assembling a version block for a run the user stopped.
61
68
  if (err instanceof HttpRequestError && err.kind === 'aborted')
62
69
  throw err;
63
70
  if (err instanceof HttpRequestError)
@@ -65,7 +72,9 @@ export async function npmVersionLookup(pkg, opts = {}) {
65
72
  throw err;
66
73
  }
67
74
  }
68
- /** Format an NpmVersionInfo as a short Markdown block for EXTERNAL CONTEXT. */
75
+ /** Format an NpmVersionInfo as a short Markdown block for EXTERNAL CONTEXT: a
76
+ * `### npm: <pkg>` heading, a `latest:` line that gains ` (published YYYY-MM-DD)`
77
+ * when the date is known, and a `recent:` line only when the list is non-empty. */
69
78
  export function formatNpmVersionSection(info) {
70
79
  const lines = [`### npm: ${info.pkg}`, `latest: ${info.latest}`];
71
80
  if (info.publishedAt) {
@@ -37,7 +37,9 @@ export declare function findPhantomImports(text: string, cwd: string, loadText?:
37
37
  /** Render flagged phantoms as an authoritative research section, or '' if none. */
38
38
  export declare function formatApiCorrections(phantoms: PhantomImport[]): string;
39
39
  /**
40
- * A top-of-handoff override banner (Layer B). The deterministic check has proven
40
+ * A top-of-handoff override banner (Layer B orchestrator.ts names the two layers:
41
+ * A strips phantoms from the refined spec, B overrides at delivery). The check has
42
+ * proven —
41
43
  * against the installed type definitions — that the spec, or a design doc it
42
44
  * references, names an API that does NOT exist. The implementer is told the spec/doc
43
45
  * is authoritative, so a residual affirmative that survived into the composed spec, or
@@ -57,17 +59,18 @@ export declare function formatApiOverrideBanner(phantoms: PhantomImport[]): stri
57
59
  export declare function findDeliveryPhantoms(spec: string, cwd: string): Promise<PhantomImport[]>;
58
60
  /**
59
61
  * Subtractively rewrite every flagged phantom specifier in `text` to the canonical
60
- * import the installed types prove, so NO affirmative occurrence of the non-existent
61
- * specifier survives downstream. This is the half an appended correction can't do:
62
- * REFINE preserves identifiers verbatim, so `bun:sql` otherwise rides into the
63
- * composed GOAL (proven: compose re-leaks it 4/4) and on to the implementer. Strike
64
- * it at the source and compose has nothing to contradict.
62
+ * import the installed types prove, so NO affirmative occurrence of the specifier
63
+ * survives downstream. This is the half an APPENDED correction cannot do: later
64
+ * phases copy identifiers verbatim, so a `bun:sql` left in the text keeps being
65
+ * re-stated no matter what a correction says about it.
65
66
  *
66
- * Deterministic, no LLM. Idempotent on healthy input: once a specifier is replaced
67
- * the literal is gone, so a second pass is a no-op (the only residue is a `declare
68
- * module` comment that quotes the spec inside a slash-star, which the bare-word rule
69
- * skips via its lookbehind). Handles the four forms a phantom takes: an import/require
70
- * `from "<spec>"`, a fabricated `declare module "<spec>"`, a backticked prose mention
71
- * `` `<spec>` ``, and a bare word `<spec>`.
67
+ * Deterministic, no LLM. Handles the four forms a phantom takes: an import/require
68
+ * `from "<spec>"`, a fabricated `declare module "<spec>"`, a backticked prose
69
+ * mention `` `<spec>` ``, and a bare word. A path-like `./bun:sql/x` is left alone
70
+ * by the bare-word rule's lookbehind.
71
+ *
72
+ * Idempotent: once a specifier is replaced the literal is gone. The `declare
73
+ * module` replacement deliberately does not echo the spec, so a second pass over
74
+ * the output returns it byte for byte.
72
75
  */
73
76
  export declare function rewritePhantomSpecifiers(text: string, phantoms: PhantomImport[]): string;
@@ -3,13 +3,16 @@
3
3
  * task/spec but NOT actually declared by the runtime's installed types.
4
4
  *
5
5
  * A real `<runtime>:<sub>` builtin appears as `declare module "<runtime>:<sub>"`
6
- * in the runtime's type files (bun-types declares bun:sqlite/bun:test/bun:ffi/…;
7
- * @types/node declares node:fs/…). A specifier with no such declaration is a
8
- * hallucination — the symbol, if it exists at all, lives on the base module. This
9
- * is the exact `bun:sql` failure class: a design doc invented `bun:sql`, every
10
- * phase echoed it, and the implementer fabricated a `declare module "bun:sql"`
11
- * shim to make it compile. Verifying the specifier against the installed types
12
- * catches it deterministically, with no false positives and no LLM call.
6
+ * in the runtime's type files: bun-types declares exactly bun:bundle, bun:ffi,
7
+ * bun:jsc, bun:sqlite and bun:test, and @types/node declares node:assert,
8
+ * node:buffer, node:child_process and the rest. A specifier with no such
9
+ * declaration is a hallucination — the symbol, if it exists at all, lives on the
10
+ * base module. `bun:sql` is the canonical case: bun-types declares no such module,
11
+ * but `declare module "bun"` does declare `const sql: SQL`, so the correct import
12
+ * is from `"bun"` and a `declare module "bun:sql"` shim only hides the mistake.
13
+ *
14
+ * The check is deterministic and needs no LLM call. It can only be as good as the
15
+ * installed types: a runtime whose types are not on disk is skipped entirely.
13
16
  */
14
17
  import * as fs from 'node:fs';
15
18
  import * as path from 'node:path';
@@ -22,9 +25,9 @@ const DECLARE_MODULE_RE = /declare module "([^"]+)"/g;
22
25
  export function extractRuntimeSpecifiers(text) {
23
26
  const seen = new Set();
24
27
  for (const m of text.matchAll(SPEC_RE)) {
25
- // Normalise: drop a trailing punctuation the word boundary may include is
26
- // already excluded by the class; lowercase the runtime only (subpaths are
27
- // case-sensitive). splitRuntimeNamespace lowercases the runtime itself.
28
+ // Kept exactly as written, including case: `BUN:SQL` and `bun:sql` are two
29
+ // entries here. `splitRuntimeNamespace` lowercases the runtime downstream,
30
+ // and the subpath is case-sensitive, so nothing is folded at this layer.
28
31
  if (!seen.has(m[0]))
29
32
  seen.add(m[0]);
30
33
  }
@@ -44,8 +47,8 @@ export function classifyRuntimeImport(spec, runtime, sub, typeText) {
44
47
  if (!real) {
45
48
  const leaf = sub.split('/').pop() ?? sub;
46
49
  // Does the base runtime module declare a symbol named like the submodule
47
- // leaf (e.g. `bun:sql` `const sql` / `class SQL` in `declare module "bun"`)?
48
- // Case-insensitive so `sql` matches the `SQL` class too.
50
+ // leaf? Case-insensitive, which is what makes `bun:sql` find the real
51
+ // `const sql: SQL` and report `SQL` as the symbol to import.
49
52
  const re = new RegExp(`\\b(?:const|class|function|let|var|namespace|interface|type)\\s+(${escapeRe(leaf)})\\b`, 'i');
50
53
  const m = re.exec(typeText);
51
54
  baseSymbol = m ? m[1] : null;
@@ -174,7 +177,9 @@ export function formatApiCorrections(phantoms) {
174
177
  return `API CORRECTIONS\n${lines.join('\n')}`;
175
178
  }
176
179
  /**
177
- * A top-of-handoff override banner (Layer B). The deterministic check has proven
180
+ * A top-of-handoff override banner (Layer B orchestrator.ts names the two layers:
181
+ * A strips phantoms from the refined spec, B overrides at delivery). The check has
182
+ * proven —
178
183
  * against the installed type definitions — that the spec, or a design doc it
179
184
  * references, names an API that does NOT exist. The implementer is told the spec/doc
180
185
  * is authoritative, so a residual affirmative that survived into the composed spec, or
@@ -225,18 +230,19 @@ export async function findDeliveryPhantoms(spec, cwd) {
225
230
  }
226
231
  /**
227
232
  * Subtractively rewrite every flagged phantom specifier in `text` to the canonical
228
- * import the installed types prove, so NO affirmative occurrence of the non-existent
229
- * specifier survives downstream. This is the half an appended correction can't do:
230
- * REFINE preserves identifiers verbatim, so `bun:sql` otherwise rides into the
231
- * composed GOAL (proven: compose re-leaks it 4/4) and on to the implementer. Strike
232
- * it at the source and compose has nothing to contradict.
233
+ * import the installed types prove, so NO affirmative occurrence of the specifier
234
+ * survives downstream. This is the half an APPENDED correction cannot do: later
235
+ * phases copy identifiers verbatim, so a `bun:sql` left in the text keeps being
236
+ * re-stated no matter what a correction says about it.
237
+ *
238
+ * Deterministic, no LLM. Handles the four forms a phantom takes: an import/require
239
+ * `from "<spec>"`, a fabricated `declare module "<spec>"`, a backticked prose
240
+ * mention `` `<spec>` ``, and a bare word. A path-like `./bun:sql/x` is left alone
241
+ * by the bare-word rule's lookbehind.
233
242
  *
234
- * Deterministic, no LLM. Idempotent on healthy input: once a specifier is replaced
235
- * the literal is gone, so a second pass is a no-op (the only residue is a `declare
236
- * module` comment that quotes the spec inside a slash-star, which the bare-word rule
237
- * skips via its lookbehind). Handles the four forms a phantom takes: an import/require
238
- * `from "<spec>"`, a fabricated `declare module "<spec>"`, a backticked prose mention
239
- * `` `<spec>` ``, and a bare word `<spec>`.
243
+ * Idempotent: once a specifier is replaced the literal is gone. The `declare
244
+ * module` replacement deliberately does not echo the spec, so a second pass over
245
+ * the output returns it byte for byte.
240
246
  */
241
247
  export function rewritePhantomSpecifiers(text, phantoms) {
242
248
  let out = text;