@mjasnikovs/pi-task 0.38.28 → 0.38.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/dist/config/config.d.ts +70 -70
  2. package/dist/config/config.js +26 -35
  3. package/dist/config/extension-list.d.ts +6 -5
  4. package/dist/config/extension-list.js +3 -2
  5. package/dist/config/reasoning-args.d.ts +9 -7
  6. package/dist/config/reasoning-args.js +12 -10
  7. package/dist/config/reasoning.d.ts +44 -105
  8. package/dist/config/reasoning.js +27 -704
  9. package/dist/config/register.d.ts +34 -48
  10. package/dist/config/register.js +41 -51
  11. package/dist/config/tool-list.d.ts +16 -16
  12. package/dist/config/tool-list.js +1 -1
  13. package/dist/remote/bridge.d.ts +19 -10
  14. package/dist/remote/bridge.js +3 -2
  15. package/dist/remote/broadcast.js +3 -1
  16. package/dist/remote/events.js +12 -11
  17. package/dist/remote/history.d.ts +1 -1
  18. package/dist/remote/protocol.d.ts +6 -3
  19. package/dist/remote/protocol.js +2 -1
  20. package/dist/remote/push.d.ts +16 -16
  21. package/dist/remote/push.js +27 -27
  22. package/dist/remote/register.d.ts +3 -3
  23. package/dist/remote/register.js +17 -19
  24. package/dist/remote/server.d.ts +9 -8
  25. package/dist/remote/server.js +15 -14
  26. package/dist/remote/session-state.d.ts +5 -4
  27. package/dist/remote/session-state.js +8 -5
  28. package/dist/remote/sw.d.ts +7 -6
  29. package/dist/remote/sw.js +7 -6
  30. package/dist/remote/tailscale.d.ts +4 -2
  31. package/dist/remote/tailscale.js +4 -2
  32. package/dist/remote/ui-highlight.js +6 -5
  33. package/dist/remote/ui-render.js +4 -4
  34. package/dist/remote/ui-script.js +24 -24
  35. package/dist/remote/ui-styles.d.ts +1 -1
  36. package/dist/remote/ui-styles.js +10 -13
  37. package/dist/remote/ui-tools.js +9 -6
  38. package/dist/shared/child-extensions.d.ts +29 -17
  39. package/dist/shared/child-extensions.js +29 -17
  40. package/dist/shared/child-output.d.ts +30 -24
  41. package/dist/shared/child-output.js +25 -17
  42. package/dist/shared/child-process.d.ts +47 -40
  43. package/dist/shared/child-process.js +50 -59
  44. package/dist/shared/command-watchdog.d.ts +22 -16
  45. package/dist/shared/command-watchdog.js +28 -21
  46. package/dist/shared/fs-text.d.ts +16 -10
  47. package/dist/shared/fs-text.js +16 -10
  48. package/dist/shared/git-runner.d.ts +25 -25
  49. package/dist/shared/git-runner.js +25 -25
  50. package/dist/shared/leaked-tool-call.d.ts +17 -11
  51. package/dist/shared/leaked-tool-call.js +23 -15
  52. package/dist/shared/model-endpoint.d.ts +29 -16
  53. package/dist/shared/model-endpoint.js +33 -21
  54. package/dist/shared/pi-invocation.d.ts +7 -4
  55. package/dist/shared/pi-invocation.js +12 -7
  56. package/dist/shared/pkg-version.d.ts +13 -5
  57. package/dist/shared/pkg-version.js +13 -5
  58. package/dist/shared/reasoning-capability.d.ts +35 -24
  59. package/dist/shared/reasoning-capability.js +35 -24
  60. package/dist/shared/stream-watchdog.d.ts +60 -44
  61. package/dist/shared/stream-watchdog.js +62 -45
  62. package/dist/task/accept-debt.d.ts +41 -43
  63. package/dist/task/accept-debt.js +73 -65
  64. package/dist/task/api-synthesis.d.ts +24 -21
  65. package/dist/task/api-synthesis.js +32 -26
  66. package/dist/task/apis-contract.d.ts +32 -64
  67. package/dist/task/apis-contract.js +32 -64
  68. package/dist/task/artifact-closure.d.ts +27 -13
  69. package/dist/task/artifact-closure.js +95 -67
  70. package/dist/task/auto-commit.d.ts +46 -35
  71. package/dist/task/auto-commit.js +51 -38
  72. package/dist/task/auto-io.d.ts +45 -25
  73. package/dist/task/auto-io.js +57 -29
  74. package/dist/task/auto-orchestrator.d.ts +26 -24
  75. package/dist/task/auto-orchestrator.js +178 -162
  76. package/dist/task/auto-prompts.d.ts +36 -24
  77. package/dist/task/auto-prompts.js +40 -26
  78. package/dist/task/autofix-ledger.d.ts +27 -25
  79. package/dist/task/autofix-ledger.js +29 -26
  80. package/dist/task/batch-test-task.d.ts +20 -12
  81. package/dist/task/batch-test-task.js +67 -60
  82. package/dist/task/boot-probe.d.ts +60 -44
  83. package/dist/task/boot-probe.js +91 -72
  84. package/dist/task/cancel-input.d.ts +30 -16
  85. package/dist/task/cancel-input.js +20 -11
  86. package/dist/task/cancel-points.d.ts +27 -20
  87. package/dist/task/cancel-points.js +30 -22
  88. package/dist/task/child-runner.d.ts +46 -51
  89. package/dist/task/child-runner.js +48 -49
  90. package/dist/task/child-status.d.ts +23 -16
  91. package/dist/task/child-status.js +23 -16
  92. package/dist/task/clamp-output.js +12 -5
  93. package/dist/task/command-run.d.ts +31 -28
  94. package/dist/task/command-run.js +44 -35
  95. package/dist/task/command-shrink.d.ts +25 -18
  96. package/dist/task/command-shrink.js +37 -31
  97. package/dist/task/command-watchdog.d.ts +9 -6
  98. package/dist/task/command-watchdog.js +21 -15
  99. package/dist/task/context-attribution.d.ts +34 -26
  100. package/dist/task/context-attribution.js +34 -26
  101. package/dist/task/context-silence.d.ts +39 -29
  102. package/dist/task/context-silence.js +35 -25
  103. package/dist/task/context-usage.d.ts +25 -7
  104. package/dist/task/context-usage.js +21 -6
  105. package/dist/task/contracts.d.ts +8 -4
  106. package/dist/task/contracts.js +25 -17
  107. package/dist/task/coverage-loop.d.ts +22 -18
  108. package/dist/task/coverage-loop.js +35 -30
  109. package/dist/task/critique-probes.d.ts +13 -14
  110. package/dist/task/critique-probes.js +50 -39
  111. package/dist/task/debug-log.d.ts +13 -5
  112. package/dist/task/debug-log.js +32 -20
  113. package/dist/task/decompose-fidelity.d.ts +11 -9
  114. package/dist/task/decompose-fidelity.js +38 -33
  115. package/dist/task/decompose-granularity.d.ts +41 -38
  116. package/dist/task/decompose-granularity.js +41 -38
  117. package/dist/task/deep-render-check.d.ts +22 -14
  118. package/dist/task/deep-render-check.js +40 -31
  119. package/dist/task/dropped-input.d.ts +12 -7
  120. package/dist/task/dropped-input.js +5 -2
  121. package/dist/task/enforce-attribution.d.ts +38 -47
  122. package/dist/task/enforce-attribution.js +46 -52
  123. package/dist/task/enforce-guidelines.d.ts +31 -20
  124. package/dist/task/enforce-guidelines.js +32 -21
  125. package/dist/task/enrichment.d.ts +7 -2
  126. package/dist/task/enrichment.js +26 -14
  127. package/dist/task/env-notes.d.ts +16 -7
  128. package/dist/task/env-notes.js +48 -31
  129. package/dist/task/env-template-closure.d.ts +4 -4
  130. package/dist/task/env-template-closure.js +42 -34
  131. package/dist/task/external-context.d.ts +28 -21
  132. package/dist/task/external-context.js +17 -12
  133. package/dist/task/failure-classifier.d.ts +4 -5
  134. package/dist/task/failure-classifier.js +6 -7
  135. package/dist/task/file-inventory.d.ts +15 -11
  136. package/dist/task/file-inventory.js +25 -22
  137. package/dist/task/final-gate-fix.d.ts +74 -86
  138. package/dist/task/final-gate-fix.js +97 -116
  139. package/dist/task/final-gate-progress.d.ts +29 -46
  140. package/dist/task/final-gate-progress.js +40 -51
  141. package/dist/task/final-gate.d.ts +64 -97
  142. package/dist/task/final-gate.js +192 -199
  143. package/dist/task/fix-child.d.ts +21 -27
  144. package/dist/task/fix-child.js +21 -27
  145. package/dist/task/foreign-path.d.ts +6 -5
  146. package/dist/task/foreign-path.js +0 -0
  147. package/dist/task/frozen-conflict.d.ts +9 -10
  148. package/dist/task/frozen-conflict.js +61 -64
  149. package/dist/task/frozen-path-guard.d.ts +35 -14
  150. package/dist/task/frozen-path-guard.js +56 -39
  151. package/dist/task/gate-child.d.ts +27 -28
  152. package/dist/task/gate-child.js +37 -35
  153. package/dist/task/gate-deps.d.ts +34 -27
  154. package/dist/task/gate-deps.js +169 -159
  155. package/dist/task/gate-tally.d.ts +77 -80
  156. package/dist/task/gate-tally.js +65 -68
  157. package/dist/task/git-state-guard.d.ts +15 -11
  158. package/dist/task/git-state-guard.js +76 -66
  159. package/dist/task/impl-widget.d.ts +25 -16
  160. package/dist/task/impl-widget.js +27 -17
  161. package/dist/task/implementation-thinking.d.ts +33 -31
  162. package/dist/task/implementation-thinking.js +5 -6
  163. package/dist/task/implementation-turn.d.ts +34 -31
  164. package/dist/task/implementation-turn.js +29 -27
  165. package/dist/task/inline-markdown.d.ts +20 -7
  166. package/dist/task/inline-markdown.js +15 -6
  167. package/dist/task/launch-config-gap.js +25 -39
  168. package/dist/task/launch-contract.d.ts +18 -21
  169. package/dist/task/launch-contract.js +28 -30
  170. package/dist/task/launch-manifest.d.ts +6 -2
  171. package/dist/task/launch-manifest.js +35 -34
  172. package/dist/task/ledger.js +16 -14
  173. package/dist/task/lint-fix.d.ts +6 -8
  174. package/dist/task/lint-fix.js +67 -69
  175. package/dist/task/loop-detector.d.ts +9 -8
  176. package/dist/task/loop-detector.js +16 -12
  177. package/dist/task/mid-run-input.d.ts +17 -15
  178. package/dist/task/mid-run-input.js +17 -15
  179. package/dist/task/orchestrator.d.ts +24 -28
  180. package/dist/task/orchestrator.js +62 -64
  181. package/dist/task/orientation.d.ts +18 -23
  182. package/dist/task/orientation.js +24 -31
  183. package/dist/task/owned-freeze-conflict.d.ts +21 -20
  184. package/dist/task/owned-freeze-conflict.js +52 -85
  185. package/dist/task/owned-freeze-reassign.d.ts +40 -60
  186. package/dist/task/owned-freeze-reassign.js +41 -61
  187. package/dist/task/parsers.d.ts +4 -2
  188. package/dist/task/parsers.js +4 -4
  189. package/dist/task/phases.d.ts +41 -48
  190. package/dist/task/phases.js +180 -248
  191. package/dist/task/plan-io.d.ts +6 -7
  192. package/dist/task/plan-io.js +6 -7
  193. package/dist/task/plan-orchestrator.d.ts +10 -8
  194. package/dist/task/plan-orchestrator.js +14 -10
  195. package/dist/task/plan-prompts.d.ts +6 -5
  196. package/dist/task/plan-prompts.js +6 -5
  197. package/dist/task/plan-readonly.d.ts +4 -5
  198. package/dist/task/plan-readonly.js +4 -5
  199. package/dist/task/plan-rounds.d.ts +17 -29
  200. package/dist/task/plan-rounds.js +21 -34
  201. package/dist/task/plan-session.d.ts +58 -72
  202. package/dist/task/plan-session.js +61 -83
  203. package/dist/task/probe-gaming.d.ts +28 -27
  204. package/dist/task/probe-gaming.js +0 -0
  205. package/dist/task/prohibition-probe.d.ts +14 -16
  206. package/dist/task/prompts.d.ts +3 -4
  207. package/dist/task/prompts.js +17 -26
  208. package/dist/task/qa-transcript.d.ts +15 -22
  209. package/dist/task/qa-transcript.js +15 -21
  210. package/dist/task/question-box.d.ts +17 -13
  211. package/dist/task/question-box.js +19 -15
  212. package/dist/task/question-dedup.d.ts +6 -7
  213. package/dist/task/question-dedup.js +13 -14
  214. package/dist/task/question-dialog.d.ts +22 -32
  215. package/dist/task/question-dialog.js +22 -32
  216. package/dist/task/question-source.d.ts +18 -44
  217. package/dist/task/question-source.js +22 -51
  218. package/dist/task/refuted-constraint.d.ts +11 -31
  219. package/dist/task/refuted-constraint.js +27 -51
  220. package/dist/task/regenerable-artifacts.d.ts +12 -31
  221. package/dist/task/regenerable-artifacts.js +12 -31
  222. package/dist/task/render-check.d.ts +11 -22
  223. package/dist/task/render-check.js +33 -46
  224. package/dist/task/repo-health-check.d.ts +10 -14
  225. package/dist/task/repo-health-check.js +17 -23
  226. package/dist/task/requirements.d.ts +38 -71
  227. package/dist/task/requirements.js +78 -126
  228. package/dist/task/research-fanout-budget.d.ts +51 -88
  229. package/dist/task/research-fanout-budget.js +51 -88
  230. package/dist/task/research-worker.d.ts +33 -36
  231. package/dist/task/research-worker.js +39 -61
  232. package/dist/task/resume-gap.d.ts +14 -15
  233. package/dist/task/root-cause-repair.d.ts +9 -9
  234. package/dist/task/root-cause-repair.js +28 -40
  235. package/dist/task/run-bracket.d.ts +10 -13
  236. package/dist/task/run-end.d.ts +12 -22
  237. package/dist/task/run-end.js +8 -16
  238. package/dist/task/run-final-gate.d.ts +19 -21
  239. package/dist/task/run-final-gate.js +62 -80
  240. package/dist/task/runner-globs.d.ts +12 -13
  241. package/dist/task/runner-globs.js +12 -13
  242. package/dist/task/runner-resolve.d.ts +9 -9
  243. package/dist/task/runner-resolve.js +22 -23
  244. package/dist/task/script-escape.d.ts +10 -12
  245. package/dist/task/script-escape.js +13 -14
  246. package/dist/task/serve-entry.d.ts +1 -1
  247. package/dist/task/serve-entry.js +22 -25
  248. package/dist/task/service-blocks.js +4 -2
  249. package/dist/task/shipped-source.d.ts +11 -29
  250. package/dist/task/shipped-source.js +11 -29
  251. package/dist/task/skip-escape.js +10 -14
  252. package/dist/task/spec-urls.d.ts +26 -65
  253. package/dist/task/spec-urls.js +26 -65
  254. package/dist/task/spec-validation.d.ts +17 -20
  255. package/dist/task/spec-validation.js +17 -20
  256. package/dist/task/stall-detector.d.ts +23 -30
  257. package/dist/task/stall-detector.js +23 -30
  258. package/dist/task/stream-watchdog.d.ts +14 -12
  259. package/dist/task/stream-watchdog.js +14 -12
  260. package/dist/task/substitution-probe.d.ts +17 -20
  261. package/dist/task/substitution-probe.js +17 -20
  262. package/dist/task/task-gates.d.ts +36 -41
  263. package/dist/task/task-gates.js +95 -106
  264. package/dist/task/task-io.d.ts +4 -4
  265. package/dist/task/task-io.js +4 -4
  266. package/dist/task/task-parsers.js +4 -3
  267. package/dist/task/task-provenance.d.ts +2 -2
  268. package/dist/task/task-provenance.js +11 -13
  269. package/dist/task/task-types.d.ts +4 -3
  270. package/dist/task/terminal-outcome.d.ts +14 -16
  271. package/dist/task/terminal-outcome.js +12 -14
  272. package/dist/task/test-assembly.d.ts +13 -20
  273. package/dist/task/test-assembly.js +13 -20
  274. package/dist/task/timings.d.ts +5 -3
  275. package/dist/task/timings.js +5 -3
  276. package/dist/task/title-label.d.ts +9 -4
  277. package/dist/task/title-label.js +9 -4
  278. package/dist/task/type-only-answer.d.ts +44 -52
  279. package/dist/task/type-only-answer.js +44 -52
  280. package/dist/task/unfailable-command.d.ts +18 -24
  281. package/dist/task/unfailable-command.js +21 -27
  282. package/dist/task/unknown-routing.d.ts +10 -4
  283. package/dist/task/unknown-routing.js +10 -4
  284. package/dist/task/user-directives.d.ts +5 -8
  285. package/dist/task/user-directives.js +5 -8
  286. package/dist/task/verify-quality.d.ts +18 -22
  287. package/dist/task/verify-quality.js +45 -46
  288. package/dist/task/verify-reconcile.d.ts +15 -10
  289. package/dist/task/verify-reconcile.js +45 -43
  290. package/dist/task/verify-resolution.d.ts +24 -20
  291. package/dist/task/verify-resolution.js +51 -50
  292. package/dist/task/verify-work.d.ts +59 -66
  293. package/dist/task/verify-work.js +101 -138
  294. package/dist/task/widget.d.ts +15 -14
  295. package/dist/task/widget.js +22 -17
  296. package/dist/task/wiring-claims.d.ts +25 -32
  297. package/dist/task/wiring-claims.js +30 -35
  298. package/dist/task/write-guard.d.ts +39 -39
  299. package/dist/task/write-guard.js +48 -51
  300. package/dist/task/yolo.d.ts +34 -30
  301. package/dist/task/yolo.js +42 -37
  302. package/dist/workers/abstention.d.ts +21 -41
  303. package/dist/workers/abstention.js +27 -48
  304. package/dist/workers/brave-search.d.ts +4 -3
  305. package/dist/workers/brave-search.js +5 -2
  306. package/dist/workers/brave-warning.d.ts +7 -4
  307. package/dist/workers/brave-warning.js +19 -7
  308. package/dist/workers/ddg-search.d.ts +6 -6
  309. package/dist/workers/ddg-search.js +18 -12
  310. package/dist/workers/docs-cache.js +5 -2
  311. package/dist/workers/docs-chunk.d.ts +30 -37
  312. package/dist/workers/docs-chunk.js +37 -41
  313. package/dist/workers/docs-core.d.ts +28 -44
  314. package/dist/workers/docs-core.js +25 -44
  315. package/dist/workers/docs-index.js +4 -3
  316. package/dist/workers/docs-lookup.d.ts +15 -22
  317. package/dist/workers/docs-lookup.js +12 -21
  318. package/dist/workers/docs-project.d.ts +15 -9
  319. package/dist/workers/docs-project.js +17 -10
  320. package/dist/workers/docs-resolve.d.ts +19 -20
  321. package/dist/workers/docs-resolve.js +35 -32
  322. package/dist/workers/docs-retrieve.d.ts +5 -6
  323. package/dist/workers/docs-retrieve.js +18 -15
  324. package/dist/workers/exa-search.d.ts +9 -6
  325. package/dist/workers/exa-search.js +23 -12
  326. package/dist/workers/fetch-core.d.ts +13 -16
  327. package/dist/workers/fetch-core.js +23 -23
  328. package/dist/workers/focused-extractor.d.ts +12 -12
  329. package/dist/workers/focused-extractor.js +16 -19
  330. package/dist/workers/html-clean.js +24 -14
  331. package/dist/workers/http-request.d.ts +28 -20
  332. package/dist/workers/http-request.js +22 -17
  333. package/dist/workers/npm-version.d.ts +28 -11
  334. package/dist/workers/npm-version.js +24 -15
  335. package/dist/workers/phantom-imports.d.ts +15 -12
  336. package/dist/workers/phantom-imports.js +30 -24
  337. package/dist/workers/pi-worker-core.d.ts +86 -54
  338. package/dist/workers/pi-worker-core.js +112 -112
  339. package/dist/workers/pi-worker-docs.d.ts +24 -19
  340. package/dist/workers/pi-worker-docs.js +67 -76
  341. package/dist/workers/pi-worker-fetch.d.ts +7 -3
  342. package/dist/workers/pi-worker-fetch.js +27 -19
  343. package/dist/workers/pi-worker-search.js +12 -8
  344. package/dist/workers/pi-worker.d.ts +9 -4
  345. package/dist/workers/pi-worker.js +23 -10
  346. package/dist/workers/reasoning-warning.d.ts +18 -17
  347. package/dist/workers/reasoning-warning.js +22 -20
  348. package/dist/workers/research-cache.js +50 -78
  349. package/dist/workers/search-core.js +7 -5
  350. package/dist/workers/search-types.d.ts +10 -9
  351. package/dist/workers/search-types.js +9 -8
  352. package/dist/workers/session-hint.d.ts +13 -14
  353. package/dist/workers/session-hint.js +8 -9
  354. package/dist/workers/shared.d.ts +21 -25
  355. package/dist/workers/shared.js +0 -0
  356. package/dist/workers/single-read-extension.d.ts +14 -7
  357. package/dist/workers/single-read-extension.js +14 -7
  358. package/dist/workers/single-read-guard.d.ts +25 -28
  359. package/dist/workers/single-read-guard.js +32 -32
  360. package/dist/workers/typeonly-log.d.ts +12 -9
  361. package/dist/workers/typeonly-log.js +29 -33
  362. package/dist/workers/worker-channels.d.ts +15 -23
  363. package/dist/workers/worker-channels.js +15 -23
  364. package/dist/workers/worker-failure.d.ts +38 -46
  365. package/dist/workers/worker-failure.js +31 -39
  366. package/dist/workers/worker-kill.d.ts +25 -26
  367. package/dist/workers/worker-kill.js +16 -19
  368. package/dist/workers/worker-profiles.d.ts +43 -53
  369. package/dist/workers/worker-profiles.js +30 -38
  370. package/package.json +10 -8
package/dist/task/yolo.js CHANGED
@@ -8,13 +8,14 @@
8
8
  *
9
9
  * WHY PER-SITE, NOT ONE HOOK (the trap this module exists to avoid): the single
10
10
  * interactive choke point is SessionUI.ask() (remote/bridge.ts) — auto-picking
11
- * "index 0" inside it would be one tiny patch, and it would be wrong. The
12
- * verify-FAIL picker is reached ONLY AFTER MAX_AUTO_AUTOFIX unattended autofix
13
- * attempts have already failed, yet it STILL tints AUTOFIX as recommended: a
14
- * central hook would pick AUTOFIX forever and defeat the exact cap that exists to
15
- * break a non-converging loop. So every site decides for itself, BEFORE ask() is
16
- * called — which also means the prompt notification (the lone pushNotify in
17
- * ask()) is suppressed structurally, with zero suppression code.
11
+ * "index 0" inside it would be one tiny patch, and it would be wrong. One way the
12
+ * verify-FAIL picker is reached is after MAX_AUTO_AUTOFIX unattended autofix
13
+ * attempts have all failed, and it STILL tints AUTOFIX as recommended, because the
14
+ * research still recommends it. A central hook would pick AUTOFIX forever and
15
+ * defeat the exact cap that exists to break a non-converging loop. So every site
16
+ * decides for itself, BEFORE ask() is called — which also means the prompt
17
+ * notification (bridge.ts holds exactly one pushNotify, inside ask()) is
18
+ * suppressed structurally, with zero suppression code.
18
19
  *
19
20
  * Guard direction (repo constraint): an auto-pick may cost time, never work.
20
21
  * Anything this module cannot stand behind — no recommendation to take, an answer
@@ -27,10 +28,11 @@
27
28
  */
28
29
  import { getConfig } from '../config/config.js';
29
30
  /**
30
- * Visible provenance marker on EVERY artifact an auto-pick writes (gate trails,
31
- * task-file Q&A, the debt ledger's reason text). A later audit reading only the
32
- * artifacts must never mistake an auto-pick for a human decision — that is the
33
- * same confusion the accept-debt origins were introduced to prevent.
31
+ * Visible provenance marker on the artifacts an auto-pick writes: gate trail lines
32
+ * and the task file's Q&A record (qa-transcript.ts `QA_PROVENANCE`). A later audit
33
+ * reading only the artifacts must never mistake an auto-pick for a human decision.
34
+ * The debt ledger does not use this stamp — it carries the `'yolo-accepted'` ORIGIN
35
+ * instead, which is stronger: a typed field rather than text.
34
36
  */
35
37
  export const YOLO_STAMP = '(YOLO)';
36
38
  /** Is unattended auto-pick on for this run? The ONLY config read in this module. */
@@ -38,16 +40,17 @@ export function isYoloMode() {
38
40
  return getConfig().yoloMode;
39
41
  }
40
42
  /**
41
- * The clarify/grill policy: take the RECOMMENDED option — which is positional,
42
- * index 0 of the card list (question-box.ts tints it green), i.e. `suggested`,
43
- * falling back to the B-side `alt` when a fork offers only that.
43
+ * The clarify/grill policy: take the RECOMMENDED option — `suggested`, falling
44
+ * back to the B-side `alt` when a fork offers only that. Recommended is positional
45
+ * for a human too: bridge.ts marks card index 0, and question-box.ts tints the
46
+ * marked card green.
44
47
  *
45
48
  * `unsafe` is the step-aside channel: a caller that KNOWS the recommendation must
46
- * not be auto-accepted passes why, and the question is skipped instead. Its one
47
- * producer today is the anti-synthesis demotion — an answer proven to name a
48
- * hallucinated API identifier. Auto-accepting that would re-promote exactly the
49
- * invention the demotion was built to stop (it reached requirements AND the VERIFY
50
- * block in mx5 run 13), so a machine may never take it; a human still can.
49
+ * not be auto-accepted passes why, and the question is skipped instead. The only
50
+ * thing that supplies it is `yoloPickAutoAnswer` below, on the anti-synthesis
51
+ * demotion — an answer proven to name an unverified API identifier. Auto-accepting
52
+ * that would re-promote exactly the invention the demotion exists to stop, so a
53
+ * machine may never take it; a human still can.
51
54
  */
52
55
  export function yoloPickAnswer(enabled, opts) {
53
56
  if (!enabled)
@@ -62,12 +65,12 @@ export function yoloPickAnswer(enabled, opts) {
62
65
  return { kind: 'answer', answer: pick };
63
66
  }
64
67
  /**
65
- * The same policy expressed over an {@link AutoAnswer}, for the grill site. Only
66
- * the ANTI-SYNTHESIS unknown is unsafe: the other two producers (an integration
67
- * unknown research could not ground, a child that threw) carry an ordinary
68
- * best-effort recommendation, which is precisely what a human would be shown as
69
- * the green card. The variants are told apart by the union's `reason` tag never
70
- * by pattern-matching the answer text.
68
+ * The same policy expressed over an {@link AutoAnswer}, for the grill site. Of the
69
+ * four `reason` tags an unknown can carry `api-synthesis`, `integration`,
70
+ * `threw`, `model-unknown` only ANTI-SYNTHESIS is unsafe. The other three carry
71
+ * an ordinary best-effort recommendation, which is precisely what a human would be
72
+ * shown as the green card. The variants are told apart by that tag, never by
73
+ * pattern-matching the answer text.
71
74
  */
72
75
  export function yoloPickAutoAnswer(enabled, auto) {
73
76
  if (!enabled)
@@ -85,25 +88,27 @@ export function yoloPickAutoAnswer(enabled, auto) {
85
88
  /**
86
89
  * The verify-FAIL picker policy: ACCEPT (and write a debt), never AUTOFIX.
87
90
  *
88
- * Not a preference — a bound. Every branch that REACHES this picker has already
89
- * spent its unattended budget: the loop auto-runs AUTOFIX while the research
90
- * recommends it, up to MAX_AUTO_AUTOFIX consecutive failures, and only then hands
91
- * over. Answering AUTOFIX here would restart that budget from a site whose whole
92
- * purpose is that the budget ran out. So YOLO takes the terminal option and
93
- * records the defect ('yolo-accepted' an origin a human never produces).
91
+ * Not a preference — a bound. task-gates.ts consults this only after its own
92
+ * unattended paths are exhausted: `autoFixNow` has run AUTOFIX up to
93
+ * MAX_AUTO_AUTOFIX times while the research recommended it, and the YOLO rescue has
94
+ * spent its one attempt on an ACCEPT recommendation with an untouched budget.
95
+ * Answering AUTOFIX here would restart that budget from the very site that proves
96
+ * it ran out. So YOLO takes the terminal option and records the defect under the
97
+ * `'yolo-accepted'` debt origin, which a human decision never produces.
94
98
  */
95
99
  export function yoloVerifyResolution(enabled) {
96
100
  return enabled ? { action: 'accept' } : null;
97
101
  }
98
102
  /**
99
- * The final-integration-gate policy: keep autofixing WHILE the picker still
100
- * offers that card (the loop withdraws it after MAX_FINAL_GATE_AUTOFIX attempts),
101
- * then leave the run FAILED.
103
+ * The final-integration-gate policy: keep autofixing WHILE the picker still offers
104
+ * that card run-final-gate.ts withdraws it from `options` after
105
+ * MAX_FINAL_GATE_AUTOFIX attempts, and passes that as `canAutofix` — then leave the
106
+ * run FAILED.
102
107
  *
103
108
  * 'leave', not 'accept': an unattended run that cannot fix the whole-repo gate has
104
- * not produced a working project, and the honest terminal state is a failed run a
105
- * resume can re-enter mx5 run 13 ended "FAIL accepted by user" on an app that
106
- * 404'd at `/`, and that acceptance is what made the failure look like a success.
109
+ * not produced a working project. The honest terminal state is a failed run a
110
+ * resume can re-enter. Accepting instead would make the failure read as a success
111
+ * in every artifact that survives the run.
107
112
  */
108
113
  export function yoloFinalGateChoice(enabled, canAutofix) {
109
114
  if (!enabled)
@@ -3,31 +3,19 @@
3
3
  * content it was given cannot answer the question, and the one place both halves
4
4
  * of that contract live.
5
5
  *
6
- * Why one module. The sentinel has two halves that MUST agree: the sentence a
6
+ * WHY ONE MODULE. The sentinel has two halves that MUST agree: the sentence a
7
7
  * prompt instructs the child to emit, and the predicate that later recognises it.
8
- * They used to be independent. Three prompts wrote three phrasings two of them
9
- * as bare string literals buried in a template and four regexes matched
10
- * different subsets of the three:
11
- *
12
- * pi-worker-docs /unclear from this package/i (package only)
13
- * typeonly-log /unclear from this (package|project)/i (no page)
14
- * type-only-answer /unclear from this (package|page)/i (no project)
15
- * pi-worker-fetch no check at all
16
- *
17
- * That already cost one bug, recorded in typeonly-log: matching only the first
18
- * silently scored every PROJECT abstention as a valid answer. The fix went into
19
- * that one regex; the other three never learned.
20
- *
21
- * And it was still live on the fetch channel. pi-worker-docs documents F-2(e) at
22
- * length — an "unclear" non-answer exits 0, so it was memoised into the research
23
- * cache and re-served to every sibling task (52 of run 15's cached entries were
24
- * "unclear" with hitCache true), one dead end paid for many times, with
25
- * escalation unable to re-fire because the miss never recurred. `pi-worker-fetch`
26
- * cached on `childExitCode === 0` alone, so "unclear from this page" reproduced
27
- * exactly that failure on pages instead of packages.
28
- *
29
- * With emitter and matcher reading one table, a fourth corpus is one row and
30
- * cannot be half-wired.
8
+ * Held apart, each corpus grows its own phrasing and its own regex, and a regex
9
+ * that names only some of the corpora scores the rest as real answers — silently,
10
+ * because a non-answer is well-formed output. Here `buildExtractionPrompt` and
11
+ * `ABSTENTION_RE` are both built from `NOUNS`, so a new corpus is one row in that
12
+ * table and cannot be half-wired.
13
+ *
14
+ * The consumers split two ways. It must not be SCORED as an answer
15
+ * (typeonly-log.ts, task/type-only-answer.ts) and it must not be CACHED as one
16
+ * (`docsCacheable` in pi-worker-docs.ts, `fetchCacheable` in pi-worker-fetch.ts) —
17
+ * a non-answer exits 0 like any other, so a cache keyed on exit code alone
18
+ * memoises the dead end and re-serves it to every later sibling.
31
19
  */
32
20
  /** The content a focused extractor was pointed at. One row per corpus. */
33
21
  export type AbstentionKind = 'package' | 'project' | 'page';
@@ -37,28 +25,20 @@ export type AbstentionKind = 'package' | 'project' | 'page';
37
25
  * describing what the prompts actually ask for.
38
26
  */
39
27
  export declare function abstentionSentence(kind: AbstentionKind): string;
40
- /**
41
- * True when the child declined to answer rather than answering.
42
- *
43
- * Callers use this for two different decisions and both matter: it must not be
44
- * SCORED as an answer (typeonly-log, type-only-answer), and it must not be
45
- * CACHED as one (pi-worker-docs, pi-worker-fetch) — a memoised non-answer is
46
- * re-served to every later sibling and permanently suppresses escalation.
47
- */
28
+ /** True when the child declined to answer rather than answering. See the header
29
+ * for the two decisions this drives. */
48
30
  export declare function isAbstention(text: string): boolean;
49
31
  /**
50
- * The extraction prompt every corpus shares.
32
+ * The extraction prompt every corpus shares — docs-core.ts and docs-project.ts
33
+ * both build theirs here, differing only in the nouns this signature takes.
51
34
  *
52
- * Rules 1–5 were word-for-word identical in the npm and project prompt builders,
53
- * differing only in the four nouns this signature takes and one of those, the
54
- * abstention sentence in rule 4, was a bare literal that the matchers above then
55
- * had to guess at. Building the prompt from the same table that recognises its
56
- * output is the whole point: rule 4 tells the child to write EXACTLY the sentence
57
- * `isAbstention` looks for, by construction.
35
+ * Rule 4 interpolates `abstentionSentence`, so the sentence the child is told to
36
+ * write is by construction the sentence `isAbstention` looks for. Typing the words
37
+ * instead is what lets the two drift.
58
38
  *
59
39
  * `subject` is the prose noun ("npm package", "local project's source code");
60
- * `tag` names both the identity element and the content element, which must match
61
- * because rules 1, 2 and 4 all refer to `<{tag}-content>`.
40
+ * `tag` names both the identity element and the content element, and the two must
41
+ * match because rules 1, 2 and 4 all refer to `<{tag}-content>`.
62
42
  */
63
43
  export declare function buildExtractionPrompt(opts: {
64
44
  kind: AbstentionKind;
@@ -3,31 +3,19 @@
3
3
  * content it was given cannot answer the question, and the one place both halves
4
4
  * of that contract live.
5
5
  *
6
- * Why one module. The sentinel has two halves that MUST agree: the sentence a
6
+ * WHY ONE MODULE. The sentinel has two halves that MUST agree: the sentence a
7
7
  * prompt instructs the child to emit, and the predicate that later recognises it.
8
- * They used to be independent. Three prompts wrote three phrasings two of them
9
- * as bare string literals buried in a template and four regexes matched
10
- * different subsets of the three:
8
+ * Held apart, each corpus grows its own phrasing and its own regex, and a regex
9
+ * that names only some of the corpora scores the rest as real answers — silently,
10
+ * because a non-answer is well-formed output. Here `buildExtractionPrompt` and
11
+ * `ABSTENTION_RE` are both built from `NOUNS`, so a new corpus is one row in that
12
+ * table and cannot be half-wired.
11
13
  *
12
- * pi-worker-docs /unclear from this package/i (package only)
13
- * typeonly-log /unclear from this (package|project)/i (no page)
14
- * type-only-answer /unclear from this (package|page)/i (no project)
15
- * pi-worker-fetch no check at all
16
- *
17
- * That already cost one bug, recorded in typeonly-log: matching only the first
18
- * silently scored every PROJECT abstention as a valid answer. The fix went into
19
- * that one regex; the other three never learned.
20
- *
21
- * And it was still live on the fetch channel. pi-worker-docs documents F-2(e) at
22
- * length — an "unclear" non-answer exits 0, so it was memoised into the research
23
- * cache and re-served to every sibling task (52 of run 15's cached entries were
24
- * "unclear" with hitCache true), one dead end paid for many times, with
25
- * escalation unable to re-fire because the miss never recurred. `pi-worker-fetch`
26
- * cached on `childExitCode === 0` alone, so "unclear from this page" reproduced
27
- * exactly that failure on pages instead of packages.
28
- *
29
- * With emitter and matcher reading one table, a fourth corpus is one row and
30
- * cannot be half-wired.
14
+ * The consumers split two ways. It must not be SCORED as an answer
15
+ * (typeonly-log.ts, task/type-only-answer.ts) and it must not be CACHED as one
16
+ * (`docsCacheable` in pi-worker-docs.ts, `fetchCacheable` in pi-worker-fetch.ts)
17
+ * a non-answer exits 0 like any other, so a cache keyed on exit code alone
18
+ * memoises the dead end and re-serves it to every later sibling.
31
19
  */
32
20
  /**
33
21
  * The noun each corpus calls itself IN THE PROMPT. The sentinel is built from
@@ -51,40 +39,31 @@ export function abstentionSentence(kind) {
51
39
  * Matches any corpus's abstention. Built from the same table the prompts read, so
52
40
  * adding a corpus cannot leave a matcher behind.
53
41
  *
54
- * Deliberately a SUBSTRING match, unlike fetch-core's separate
55
- * `not covered by this page` sentinel, which is anchored. The two differ because
56
- * the instructions differ: the "not covered" rule asks for a partial answer that
57
- * NAMES what is missing, so a sourced answer can legitimately contain the phrase
58
- * and an anchored match is required to avoid filing it as a coverage miss. The
59
- * abstention rule asks for the sentinel INSTEAD of an answer, and models wrap it
60
- * ("Unclear from this package the README does not mention it."), so anchoring
61
- * here would miss the real non-answers this exists to catch.
42
+ * A SUBSTRING match, unlike fetch-core's separate `not covered by this page`
43
+ * sentinel, which is anchored. The instructions differ, so the matchers must. The
44
+ * "not covered" rule asks for a partial answer that NAMES what is missing, so a
45
+ * sourced answer legitimately contains the phrase and only an anchored match keeps
46
+ * it from being filed as a coverage miss. Rule 4 below asks for this sentinel
47
+ * INSTEAD of an answer, so nothing sourced can contain it, and a substring match
48
+ * still catches a child that wraps the sentence in an explanation.
62
49
  */
63
50
  const ABSTENTION_RE = new RegExp(`unclear\\s+from\\s+this\\s+(${Object.values(NOUNS).join('|')})\\b`, 'i');
64
- /**
65
- * True when the child declined to answer rather than answering.
66
- *
67
- * Callers use this for two different decisions and both matter: it must not be
68
- * SCORED as an answer (typeonly-log, type-only-answer), and it must not be
69
- * CACHED as one (pi-worker-docs, pi-worker-fetch) — a memoised non-answer is
70
- * re-served to every later sibling and permanently suppresses escalation.
71
- */
51
+ /** True when the child declined to answer rather than answering. See the header
52
+ * for the two decisions this drives. */
72
53
  export function isAbstention(text) {
73
54
  return ABSTENTION_RE.test(text);
74
55
  }
75
56
  /**
76
- * The extraction prompt every corpus shares.
57
+ * The extraction prompt every corpus shares — docs-core.ts and docs-project.ts
58
+ * both build theirs here, differing only in the nouns this signature takes.
77
59
  *
78
- * Rules 1–5 were word-for-word identical in the npm and project prompt builders,
79
- * differing only in the four nouns this signature takes and one of those, the
80
- * abstention sentence in rule 4, was a bare literal that the matchers above then
81
- * had to guess at. Building the prompt from the same table that recognises its
82
- * output is the whole point: rule 4 tells the child to write EXACTLY the sentence
83
- * `isAbstention` looks for, by construction.
60
+ * Rule 4 interpolates `abstentionSentence`, so the sentence the child is told to
61
+ * write is by construction the sentence `isAbstention` looks for. Typing the words
62
+ * instead is what lets the two drift.
84
63
  *
85
64
  * `subject` is the prose noun ("npm package", "local project's source code");
86
- * `tag` names both the identity element and the content element, which must match
87
- * because rules 1, 2 and 4 all refer to `<{tag}-content>`.
65
+ * `tag` names both the identity element and the content element, and the two must
66
+ * match because rules 1, 2 and 4 all refer to `<{tag}-content>`.
88
67
  */
89
68
  export function buildExtractionPrompt(opts) {
90
69
  const { tag } = opts;
@@ -7,9 +7,10 @@ export interface BraveSearchOpts {
7
7
  timeoutMs?: number;
8
8
  signal?: AbortSignal;
9
9
  /**
10
- * Injectable fetch. Brave was the ONE provider without this: exa and ddg both
11
- * took a `fetchImpl`, so brave's status ladder the widest of the three was
12
- * the only one no test could drive at the request level.
10
+ * Injectable fetch. All three search providers accept one. Brave gets the most
11
+ * out of it: its status ladder is the widest three branches (auth,
12
+ * rate-limit, http) against DuckDuckGo's two and Exa's one and
13
+ * http-request.test.ts drives 401/403/429/503 straight through this seam.
13
14
  */
14
15
  fetchImpl?: FetchLike;
15
16
  }
@@ -27,8 +27,11 @@ export async function braveSearch(query, opts) {
27
27
  'x-subscription-token': opts.apiKey
28
28
  }
29
29
  }, async (response) => {
30
- // Brave's own status policy: a rejected key and a rate limit are
31
- // different problems for the user, and neither is a plain HTTP fault.
30
+ // A rejected key and a rate limit are different problems for the
31
+ // user, so each throws its own MESSAGE rather than the generic
32
+ // `Brave Search HTTP <n>`. That message is what reaches the user:
33
+ // search-core turns any BraveSearchError into
34
+ // `{kind: 'error', message}` and passes the text through verbatim.
32
35
  if (response.status === 401 || response.status === 403) {
33
36
  throw new BraveSearchError(`Brave Search rejected the key (HTTP ${response.status}). Check BRAVE_SEARCH_API_KEY.`, 'auth', response.status);
34
37
  }
@@ -1,8 +1,11 @@
1
1
  /**
2
- * One-line startup hint shown when Brave is the selected search provider but
3
- * no key is configured (Brave is the only provider that needs one — exa and
4
- * ddg are keyless, so no hint renders for them). It never blocks work and
5
- * clears itself on the first interaction (any keystroke).
2
+ * One-line startup hint shown when Brave is the selected search provider but no
3
+ * key is configured. Brave is the only provider that needs one:
4
+ * `SEARCH_PROVIDER_KEY_ENV` in search-types.ts gives exa and ddg an empty var
5
+ * list, so no hint can render for them.
6
+ *
7
+ * It never blocks work. registerSessionHint renders it only in TUI mode and
8
+ * clears it on the first raw keystroke.
6
9
  */
7
10
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
8
11
  export declare function registerBraveKeyWarning(pi: ExtensionAPI): void;
@@ -1,8 +1,11 @@
1
1
  /**
2
- * One-line startup hint shown when Brave is the selected search provider but
3
- * no key is configured (Brave is the only provider that needs one — exa and
4
- * ddg are keyless, so no hint renders for them). It never blocks work and
5
- * clears itself on the first interaction (any keystroke).
2
+ * One-line startup hint shown when Brave is the selected search provider but no
3
+ * key is configured. Brave is the only provider that needs one:
4
+ * `SEARCH_PROVIDER_KEY_ENV` in search-types.ts gives exa and ddg an empty var
5
+ * list, so no hint can render for them.
6
+ *
7
+ * It never blocks work. registerSessionHint renders it only in TUI mode and
8
+ * clears it on the first raw keystroke.
6
9
  */
7
10
  import { getConfig } from '../config/config.js';
8
11
  import { registerSessionHint } from './session-hint.js';
@@ -10,12 +13,21 @@ const WIDGET_KEY = 'pi-task-brave-warning';
10
13
  const WARNING = '⚠ pi-task: search provider is Brave but BRAVE_SEARCH_API_KEY is not set — web search '
11
14
  + 'is disabled. Get a free key at https://api.search.brave.com/app/keys or switch '
12
15
  + 'provider in /task-config';
13
- /** Mirrors the lookup in search-core so the hint matches what the worker reads. */
16
+ /**
17
+ * The same two env vars, in the same order, that `SEARCH_PROVIDER_KEY_ENV` lists
18
+ * for brave — a second copy, because this runs at session_start with no provider
19
+ * in hand.
20
+ *
21
+ * The two are not byte-equivalent. `??` skips only null/undefined, while
22
+ * `searchProviderKey` skips any falsy value, so `BRAVE_SEARCH_API_KEY=""` with
23
+ * `BRAVE_API_KEY` set makes search work while this still warns. Erring toward
24
+ * showing the hint is the harmless direction.
25
+ */
14
26
  function hasBraveKey() {
15
27
  return Boolean(process.env.BRAVE_SEARCH_API_KEY ?? process.env.BRAVE_API_KEY);
16
28
  }
17
29
  export function registerBraveKeyWarning(pi) {
18
- // Only the brave provider can be misconfigured; say nothing whenever another
19
- // provider is selected or a key is already present.
30
+ // Only the brave provider can be misconfigured, so returning null — say
31
+ // nothing is the answer for every other provider and for a key already set.
20
32
  registerSessionHint(pi, WIDGET_KEY, () => getConfig().searchProvider !== 'brave' || hasBraveKey() ? null : { text: WARNING });
21
33
  }
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Web search via DuckDuckGo's HTML endpoint no API key required.
2
+ * Web search by scraping DuckDuckGo's HTML endpoint. No API key: search-types.ts
3
+ * gives `ddg` an empty env-var list.
3
4
  *
4
- * DDG has no official web-results API; html.duckduckgo.com/html serves plain
5
- * HTML result pages that parse cleanly with linkedom. Result links are wrapped
6
- * in a `duckduckgo.com/l/?uddg=<encoded>` redirect that we unwrap so callers
7
- * get the destination URL. Ad rows redirect through duckduckgo.com itself and
8
- * are dropped.
5
+ * `html.duckduckgo.com/html/?q=…` serves an ordinary result page that linkedom
6
+ * parses. Its result links are wrapped in a `//duckduckgo.com/l/?uddg=<encoded>`
7
+ * redirect, which `unwrapDdgRedirect` below decodes so callers get the
8
+ * destination URL rather than the tracker.
9
9
  */
10
10
  import { type FetchLike } from './http-request.js';
11
11
  import type { SearchResult } from './search-types.js';
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Web search via DuckDuckGo's HTML endpoint no API key required.
2
+ * Web search by scraping DuckDuckGo's HTML endpoint. No API key: search-types.ts
3
+ * gives `ddg` an empty env-var list.
3
4
  *
4
- * DDG has no official web-results API; html.duckduckgo.com/html serves plain
5
- * HTML result pages that parse cleanly with linkedom. Result links are wrapped
6
- * in a `duckduckgo.com/l/?uddg=<encoded>` redirect that we unwrap so callers
7
- * get the destination URL. Ad rows redirect through duckduckgo.com itself and
8
- * are dropped.
5
+ * `html.duckduckgo.com/html/?q=…` serves an ordinary result page that linkedom
6
+ * parses. Its result links are wrapped in a `//duckduckgo.com/l/?uddg=<encoded>`
7
+ * redirect, which `unwrapDdgRedirect` below decodes so callers get the
8
+ * destination URL rather than the tracker.
9
9
  */
10
10
  import { parseHTML } from 'linkedom';
11
11
  import { httpRequest, HttpRequestError } from './http-request.js';
@@ -13,7 +13,9 @@ const DDG_ENDPOINT = 'https://html.duckduckgo.com/html/';
13
13
  const DEFAULT_COUNT = 10;
14
14
  const MAX_COUNT = 20;
15
15
  const DEFAULT_TIMEOUT_MS = 15_000;
16
- // DDG serves a bot-challenge page to clients without a browser-ish UA.
16
+ // Required. Without a browser-ish UA the same request answers HTTP 202 with a
17
+ // different page — no `result__a` rows at all, and the words "anomaly" and
18
+ // "challenge" in the body — so the parser would return an empty list.
17
19
  const USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0';
18
20
  export class DdgSearchError extends Error {
19
21
  kind;
@@ -39,7 +41,8 @@ export async function ddgSearch(query, opts = {}) {
39
41
  accept: 'text/html'
40
42
  }
41
43
  }, async (response) => {
42
- // DDG's own status policy: 429/403 is throttling, not a plain HTTP fault.
44
+ // 429 and 403 both mean throttling here, so they share one `kind` and
45
+ // one message rather than falling into the generic HTTP branch.
43
46
  if (response.status === 429 || response.status === 403) {
44
47
  throw new DdgSearchError(`DuckDuckGo is rate-limiting this client (HTTP ${response.status}). Try again in a moment.`, 'rate-limit', response.status);
45
48
  }
@@ -67,7 +70,9 @@ export function parseDdgHtml(html) {
67
70
  continue;
68
71
  const href = anchor.getAttribute('href');
69
72
  const targetUrl = href === null ? null : unwrapDdgRedirect(href);
70
- // A row whose link never leaves duckduckgo.com is an ad/module, not a hit.
73
+ // A row whose link never leaves duckduckgo.com is an ad or a module, not a
74
+ // hit. This also drops an `l/?` href carrying no `uddg`, and one whose
75
+ // `uddg` does not decode to a URL.
71
76
  if (targetUrl === null)
72
77
  continue;
73
78
  const title = collapse(anchor.textContent ?? '');
@@ -80,9 +85,10 @@ export function parseDdgHtml(html) {
80
85
  return results;
81
86
  }
82
87
  /**
83
- * Result hrefs look like `//duckduckgo.com/l/?uddg=<encoded-destination>&rut=…`;
84
- * return the decoded destination, a non-DDG href unchanged, or null for links
85
- * that stay on duckduckgo.com (ad click-trackers).
88
+ * Result hrefs look like `//duckduckgo.com/l/?uddg=<encoded-destination>&rut=…`.
89
+ * Returns the decoded destination, a non-DDG href unchanged, or null for a link
90
+ * that stays on duckduckgo.com no `uddg`, an undecodable one, or an unparseable
91
+ * href.
86
92
  */
87
93
  function unwrapDdgRedirect(href) {
88
94
  let parsed;
@@ -35,13 +35,16 @@ CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
35
35
  END;
36
36
  `;
37
37
  const req = createRequire(import.meta.url);
38
+ // Branch on the RUNTIME, not on a try/catch: each builtin exists in exactly one
39
+ // of them. Requiring `bun:sqlite` under node throws MODULE_NOT_FOUND, and
40
+ // `node:sqlite` under bun throws ERR_UNKNOWN_BUILTIN_MODULE. `createRequire`
41
+ // keeps both specifiers out of the static import graph, so neither bundler nor
42
+ // type-checker has to resolve the one that is absent.
38
43
  function openDb(dbPath) {
39
44
  if (typeof globalThis.Bun !== 'undefined') {
40
- // Bun runtime: use bun:sqlite
41
45
  const { Database } = req('bun:sqlite');
42
46
  return new Database(dbPath);
43
47
  }
44
- // Node.js runtime: use node:sqlite
45
48
  const { DatabaseSync } = req('node:sqlite');
46
49
  return new DatabaseSync(dbPath);
47
50
  }
@@ -1,29 +1,24 @@
1
1
  /**
2
- * docs-chunk — cutting source text into retrievable chunks, for every corpus the
3
- * docs Worker tool indexes.
2
+ * docs-chunk — cutting source text into retrievable chunks, for both corpora the
3
+ * docs Worker tool indexes: an npm package's `.d.ts` + README (docs-index.ts),
4
+ * and the local project's own `.ts`/`.tsx` (docs-project.ts).
4
5
  *
5
- * There are two corpora an npm package's `.d.ts` + README, and the local
6
- * project's own `.ts`/`.tsx` and they were chunked by two copies of this code.
7
- * Not similar code: the same chunk-split regex, the same size cap, byte-identical
8
- * `chunkDts`/`chunkTs` bodies, and byte-identical `splitAtMatches`/`sliceBytes`,
9
- * in `docs-index.ts` and `docs-project.ts`. One copy had tests; the other
10
- * (`docs-project.ts`, 319 lines) had no test file at all and was only ever
11
- * reached incidentally through the worker's own suite.
6
+ * The chunk boundary is load-bearing for retrieval: a chunk that splits a
7
+ * declaration in half matches on neither half's terms, so one boundary rule for
8
+ * both corpora is the point of this module.
12
9
  *
13
- * The chunk boundary is load-bearing for retrieval quality a chunk that splits
14
- * a declaration in half retrieves as neither so having it in two places was two
15
- * places for it to drift.
16
- *
17
- * What is NOT unified: the two INDEX bodies. They key on genuinely different
18
- * provenance (a package is name+version with a content hash and keeps its old
19
- * versions; the project is a cwd key with a max-mtime version and drops its old
20
- * ones on every re-index), and collapsing them would change one of those
21
- * behaviours rather than describe them.
10
+ * What is NOT shared: the two INDEX bodies. They key on genuinely different
11
+ * provenance. A package is `(name, version)` with a content hash, and re-indexing
12
+ * runs `DELETE FROM chunks WHERE name = ? AND version = ?`, so older versions
13
+ * survive. The project is a cwd-hash name with a max-mtime version, and
14
+ * re-indexing runs `DELETE FROM chunks WHERE name = ?`, dropping every older
15
+ * version. Collapsing them would change one of those behaviours, not describe it.
22
16
  */
23
17
  /**
24
- * Chunk ceiling. Chosen against the retrieval budget: chunks are assembled into
25
- * a fixed character budget for the extraction child, so a chunk larger than this
26
- * would crowd out every other result.
18
+ * Chunk ceiling, in UTF-8 bytes. Sized against the retrieval budget: retrieved
19
+ * chunks are assembled into `RETRIEVE_CONTENT_BUDGET` (24,000 characters) before
20
+ * going to the extraction child, so this caps any one chunk at about a third of
21
+ * what the child will ever see.
27
22
  */
28
23
  export declare const MAX_CHUNK_BYTES: number;
29
24
  /**
@@ -44,30 +39,28 @@ export declare function splitAtMatches(text: string, re: RegExp): string[];
44
39
  * Cut a string into pieces of at most `maxBytes` UTF-8 bytes, never splitting a
45
40
  * character.
46
41
  *
47
- * The cut point is walked BACK to a UTF-8 lead byte first. Both copies of this
48
- * function used to cut at exactly `maxBytes` and rely on
49
- * `Buffer.toString('utf8')` to tidy up, which it does not: decoding a buffer that
50
- * ends mid-character yields a U+FFFD replacement character. That replacement is
51
- * 3 bytes wide, so measuring the advance by the decoded slice's byte length then
52
- * skipped PAST the straddling character. A `€`-dense chunk came out as
53
- * `€€€�€€€�…` — corrupted, and one character shorter per slice.
42
+ * The cut point is walked BACK to a UTF-8 lead byte first. Cutting at exactly
43
+ * `maxBytes` and letting `Buffer.toString('utf8')` tidy up does not work: decoding
44
+ * a buffer that ends mid-character yields U+FFFD. That replacement is 3 bytes
45
+ * wide, so the decoded slice measures LONGER than the cut — a 100-byte cut of
46
+ * `€`-dense text decodes to 102 bytes and advancing by the decoded length then
47
+ * skips past the straddling character entirely.
54
48
  *
55
49
  * It matters beyond looking wrong: a chunk is quoted back as an `<excerpt>` and
56
- * checked verbatim against the source, and an excerpt carrying a replacement
57
- * character can never be found, so the answer is flagged as a possible
58
- * hallucination. Only reachable on non-ASCII text past the 8 KB chunk ceiling,
59
- * which is why two copies of it survived untested.
50
+ * checked verbatim against the source (`excerptVerified`), and an excerpt carrying
51
+ * a replacement character can never be found, so the answer is flagged as a
52
+ * possible hallucination. Only reachable on non-ASCII text past the chunk ceiling.
60
53
  */
61
54
  export declare function sliceBytes(s: string, maxBytes: number): string[];
62
55
  /**
63
56
  * Chunk a declaration file (`.d.ts`, `.ts`, `.tsx`), one chunk per declaration,
64
57
  * each labelled with the file it came from.
65
58
  *
66
- * `relPath` is a MODEL-FACING label and is used exactly as given — the npm path
67
- * normalises it to POSIX first so an index is identical across platforms, while
68
- * the project path keeps the native separator. It is never re-joined to the
69
- * filesystem, so neither choice is wrong; passing it through keeps that decision
70
- * with the caller that has a reason for it.
59
+ * `relPath` is a MODEL-FACING label and is used exactly as given. docs-index.ts
60
+ * normalises it to POSIX (`.replace(/\\/g, '/')`) so a package index is identical
61
+ * across platforms; docs-project.ts passes `path.relative` through with the native
62
+ * separator. It is never re-joined to the filesystem, so neither is wrong this
63
+ * leaves the choice with the caller that has a reason for it.
71
64
  */
72
65
  export declare function chunkDeclarations(content: string, relPath: string): string[];
73
66
  /** Chunk a README, one chunk per top-level section, each labelled by heading. */