ashlr-plugin 1.36.4

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 (499) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +39 -0
  3. package/.claude-plugin/plugin.json +36 -0
  4. package/.codex/agents/README.md +9 -0
  5. package/.codex/agents/ashlr-explorer.md +36 -0
  6. package/.codex/agents/ashlr-worker.md +38 -0
  7. package/.codex-plugin/plugin.json +45 -0
  8. package/.mcp.json +14 -0
  9. package/LICENSE +21 -0
  10. package/README.md +311 -0
  11. package/agents/ashlr-code.md +69 -0
  12. package/agents/ashlr-explore.md +44 -0
  13. package/agents/ashlr-plan.md +54 -0
  14. package/bench/fixtures/notebook-large.ipynb +660 -0
  15. package/bench/fixtures/notebook-small.ipynb +39 -0
  16. package/bench/fixtures/tasklist-closed.json +62 -0
  17. package/bench/fixtures/tasklist-large-mixed.json +27 -0
  18. package/bench/fixtures/tasklist-sprint-open.json +79 -0
  19. package/bench/fixtures/websearch-bun-runtime.json +32 -0
  20. package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
  21. package/bench/fixtures/websearch-typescript-generics.json +38 -0
  22. package/bench/refs/node-sdk/.refrev +1 -0
  23. package/bench/refs/node-sdk/README.md +17 -0
  24. package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
  25. package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
  26. package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
  27. package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
  28. package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
  29. package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
  30. package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
  31. package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
  32. package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
  33. package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
  34. package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
  35. package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
  36. package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
  37. package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
  38. package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
  39. package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
  40. package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
  41. package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
  42. package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
  43. package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
  44. package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
  45. package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
  46. package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
  47. package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
  48. package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
  49. package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
  50. package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
  51. package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
  52. package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
  53. package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
  54. package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
  55. package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
  56. package/bench/refs/python-lib/.refrev +1 -0
  57. package/bench/refs/python-lib/README.md +18 -0
  58. package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
  59. package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
  60. package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
  61. package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
  62. package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
  63. package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
  64. package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
  65. package/bench/refs/python-lib/pandas/core/api.py +138 -0
  66. package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
  67. package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
  68. package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
  69. package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
  70. package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
  71. package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
  72. package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
  73. package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
  74. package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
  75. package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
  76. package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
  77. package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
  78. package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
  79. package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
  80. package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
  81. package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
  82. package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
  83. package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
  84. package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
  85. package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
  86. package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
  87. package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
  88. package/bench/refs/rust-project/.refrev +1 -0
  89. package/bench/refs/rust-project/README.md +17 -0
  90. package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
  91. package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
  92. package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
  93. package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
  94. package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
  95. package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
  96. package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
  97. package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
  98. package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
  99. package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
  100. package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
  101. package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
  102. package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
  103. package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
  104. package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
  105. package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
  106. package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
  107. package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
  108. package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
  109. package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
  110. package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
  111. package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
  112. package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
  113. package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
  114. package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
  115. package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
  116. package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
  117. package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
  118. package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
  119. package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
  120. package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
  121. package/commands/_spawn-patterns.json +32 -0
  122. package/commands/ashlr-allow.md +21 -0
  123. package/commands/ashlr-benchmark.md +53 -0
  124. package/commands/ashlr-brief.md +97 -0
  125. package/commands/ashlr-budget.md +76 -0
  126. package/commands/ashlr-compact.md +54 -0
  127. package/commands/ashlr-dashboard.md +37 -0
  128. package/commands/ashlr-doctor.md +56 -0
  129. package/commands/ashlr-eco-mode.md +92 -0
  130. package/commands/ashlr-genome-init.md +24 -0
  131. package/commands/ashlr-genome-keygen.md +36 -0
  132. package/commands/ashlr-genome-loop.md +118 -0
  133. package/commands/ashlr-genome-push.md +36 -0
  134. package/commands/ashlr-genome-rewrap.md +48 -0
  135. package/commands/ashlr-genome-team-init.md +67 -0
  136. package/commands/ashlr-handoff.md +23 -0
  137. package/commands/ashlr-help.md +86 -0
  138. package/commands/ashlr-hook-timings.md +30 -0
  139. package/commands/ashlr-leaderboard.md +50 -0
  140. package/commands/ashlr-ollama-setup.md +39 -0
  141. package/commands/ashlr-orchestrate-status.md +72 -0
  142. package/commands/ashlr-orchestrate.md +83 -0
  143. package/commands/ashlr-parallelize.md +63 -0
  144. package/commands/ashlr-report-crash.md +33 -0
  145. package/commands/ashlr-resume.md +72 -0
  146. package/commands/ashlr-savings.md +24 -0
  147. package/commands/ashlr-settings.md +50 -0
  148. package/commands/ashlr-spawn.md +61 -0
  149. package/commands/ashlr-start.md +60 -0
  150. package/commands/ashlr-status.md +64 -0
  151. package/commands/ashlr-team-invite.md +47 -0
  152. package/commands/ashlr-tier.md +122 -0
  153. package/commands/ashlr-tour.md +60 -0
  154. package/commands/ashlr-update.md +155 -0
  155. package/commands/ashlr-upgrade.md +96 -0
  156. package/commands/goal.md +58 -0
  157. package/commands/loop.md +40 -0
  158. package/docs/CNAME +1 -0
  159. package/docs/PRO_TIER.md +104 -0
  160. package/docs/README.md +21 -0
  161. package/docs/architecture.md +415 -0
  162. package/docs/ashlr-pipe.md +161 -0
  163. package/docs/assets/logo.svg +19 -0
  164. package/docs/assets/og.png +0 -0
  165. package/docs/assets/og.svg +41 -0
  166. package/docs/benchmarks-v2.json +355 -0
  167. package/docs/benchmarks.json +102 -0
  168. package/docs/benchmarks.md +211 -0
  169. package/docs/billing.md +185 -0
  170. package/docs/cloud-genome.md +155 -0
  171. package/docs/competitive-wozcode.md +92 -0
  172. package/docs/deploy.md +325 -0
  173. package/docs/email-templates.md +113 -0
  174. package/docs/embed-tuning.md +103 -0
  175. package/docs/genome.md +120 -0
  176. package/docs/github-oauth-onboarding.md +127 -0
  177. package/docs/hero-video-script.md +129 -0
  178. package/docs/index.html +2262 -0
  179. package/docs/install-prompt.md +78 -0
  180. package/docs/install-windows.md +132 -0
  181. package/docs/install.ps1 +171 -0
  182. package/docs/install.sh +146 -0
  183. package/docs/launch/README.md +114 -0
  184. package/docs/launch/hn.md +49 -0
  185. package/docs/launch/reddit.md +45 -0
  186. package/docs/launch/x.md +65 -0
  187. package/docs/launch-post.md +112 -0
  188. package/docs/legal.md +78 -0
  189. package/docs/llm-providers.md +99 -0
  190. package/docs/multi-host-mcp.md +217 -0
  191. package/docs/multi-turn-architecture.md +108 -0
  192. package/docs/operations/deployment.md +108 -0
  193. package/docs/operations/error-monitoring.md +70 -0
  194. package/docs/operations/weekly-digest.md +70 -0
  195. package/docs/operations.md +494 -0
  196. package/docs/platform-support.md +133 -0
  197. package/docs/policy-packs.md +188 -0
  198. package/docs/press-kit/fact-sheet.md +78 -0
  199. package/docs/press-kit/founder-bio.md +3 -0
  200. package/docs/press-kit/logo.svg +33 -0
  201. package/docs/press-kit/screenshots.md +146 -0
  202. package/docs/pricing.md +251 -0
  203. package/docs/pro-backend-architecture.md +523 -0
  204. package/docs/producthunt.md +52 -0
  205. package/docs/router-migration.md +110 -0
  206. package/docs/session-log-schema.md +68 -0
  207. package/docs/social.md +95 -0
  208. package/docs/team-genome.md +528 -0
  209. package/docs/telemetry.md +194 -0
  210. package/docs/test-isolation.md +100 -0
  211. package/docs/upgrade.md +159 -0
  212. package/hooks/_hook-errors.ts +87 -0
  213. package/hooks/_hook-perf-emit.ts +113 -0
  214. package/hooks/_hook-timing-ledger.ts +1133 -0
  215. package/hooks/_nudge-throttle.ts +127 -0
  216. package/hooks/_recent-blocks.ts +127 -0
  217. package/hooks/audit-upload.ts +86 -0
  218. package/hooks/codex-hooks.json +171 -0
  219. package/hooks/commit-attribution.ts +179 -0
  220. package/hooks/edit-batching-nudge.ts +154 -0
  221. package/hooks/genome-scribe-hook.ts +219 -0
  222. package/hooks/hooks.json +294 -0
  223. package/hooks/orient-nudge-hook.ts +242 -0
  224. package/hooks/policy-enforce.ts +214 -0
  225. package/hooks/post-tool-use-embedding.ts +93 -0
  226. package/hooks/post-tool-use-genome.sh +37 -0
  227. package/hooks/post-tool-use-genome.ts +69 -0
  228. package/hooks/posttooluse-correlate.ts +122 -0
  229. package/hooks/posttooluse-dedupe-output.ts +241 -0
  230. package/hooks/posttooluse-genome-refresh.ts +223 -0
  231. package/hooks/posttooluse-native-nudge.ts +205 -0
  232. package/hooks/posttooluse-stale-result.ts +277 -0
  233. package/hooks/pretooluse-bash.ts +63 -0
  234. package/hooks/pretooluse-budget-guard.ts +318 -0
  235. package/hooks/pretooluse-common.ts +985 -0
  236. package/hooks/pretooluse-eco-router.ts +196 -0
  237. package/hooks/pretooluse-edit.sh +93 -0
  238. package/hooks/pretooluse-edit.ts +197 -0
  239. package/hooks/pretooluse-glob.ts +70 -0
  240. package/hooks/pretooluse-grep.sh +85 -0
  241. package/hooks/pretooluse-grep.ts +96 -0
  242. package/hooks/pretooluse-notebookedit.ts +121 -0
  243. package/hooks/pretooluse-read.sh +118 -0
  244. package/hooks/pretooluse-read.ts +118 -0
  245. package/hooks/pretooluse-task.ts +130 -0
  246. package/hooks/pretooluse-webfetch.ts +66 -0
  247. package/hooks/pretooluse-websearch.ts +85 -0
  248. package/hooks/pretooluse-write.ts +128 -0
  249. package/hooks/pulse-emit.ts +197 -0
  250. package/hooks/session-end-consolidate.sh +35 -0
  251. package/hooks/session-end-consolidate.ts +188 -0
  252. package/hooks/session-end-stats-gc.ts +80 -0
  253. package/hooks/session-log-append.sh +94 -0
  254. package/hooks/session-log-append.ts +116 -0
  255. package/hooks/session-start.ts +993 -0
  256. package/hooks/sessionend-hook-health-nudge.ts +451 -0
  257. package/hooks/sessionend-savings-nudge.ts +318 -0
  258. package/hooks/sessionend-status-line-nudge.ts +296 -0
  259. package/hooks/sessionstart-brief.ts +140 -0
  260. package/hooks/sessionstart-cost-refactor.ts +95 -0
  261. package/hooks/sessionstart-efficient.ts +110 -0
  262. package/hooks/sessionstart-genome-author.ts +93 -0
  263. package/hooks/sessionstart-lean-tools.ts +98 -0
  264. package/hooks/sessionstart-search.ts +97 -0
  265. package/hooks/stop-accounting.ts +152 -0
  266. package/hooks/subagent-stop-rollup.ts +162 -0
  267. package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
  268. package/package.json +56 -0
  269. package/ports/README.md +108 -0
  270. package/ports/cursor/mcp.json +11 -0
  271. package/ports/goose/recipe.yaml +26 -0
  272. package/scripts/ashlr-mcp.ts +40 -0
  273. package/scripts/auto-update.ts +177 -0
  274. package/scripts/baseline-scan.ts +831 -0
  275. package/scripts/bench-orchestrate.ts +377 -0
  276. package/scripts/benchmark-refs.ts +292 -0
  277. package/scripts/bootstrap.mjs +100 -0
  278. package/scripts/brief-eval.ts +234 -0
  279. package/scripts/bump-version.ts +82 -0
  280. package/scripts/bun-resolve.d.mts +11 -0
  281. package/scripts/bun-resolve.mjs +59 -0
  282. package/scripts/calibrate-grep.ts +467 -0
  283. package/scripts/capture-grep-workload.ts +260 -0
  284. package/scripts/check-tier.ts +61 -0
  285. package/scripts/check-version-sync.ts +97 -0
  286. package/scripts/cli-orchestrate-status.ts +386 -0
  287. package/scripts/cli-orchestrate.ts +303 -0
  288. package/scripts/cli.ts +478 -0
  289. package/scripts/cloud-smoke-test.ts +313 -0
  290. package/scripts/coach-report.ts +373 -0
  291. package/scripts/context-db-migrate.ts +137 -0
  292. package/scripts/context-status.ts +43 -0
  293. package/scripts/demo-run.ts +255 -0
  294. package/scripts/deploy-smoke.sh +140 -0
  295. package/scripts/doctor.ts +992 -0
  296. package/scripts/embed-file-worker.ts +104 -0
  297. package/scripts/embed-tune.ts +308 -0
  298. package/scripts/errors-report.ts +428 -0
  299. package/scripts/find-test-leak.ts +69 -0
  300. package/scripts/generate-badge.ts +357 -0
  301. package/scripts/genome-auto-consolidate.ts +533 -0
  302. package/scripts/genome-auto-propose.ts +578 -0
  303. package/scripts/genome-cloud-pull.ts +363 -0
  304. package/scripts/genome-cloud-push.ts +382 -0
  305. package/scripts/genome-cloud-sync.ts +587 -0
  306. package/scripts/genome-commit-watcher.ts +295 -0
  307. package/scripts/genome-init.ts +986 -0
  308. package/scripts/genome-key.ts +233 -0
  309. package/scripts/genome-keygen.ts +168 -0
  310. package/scripts/genome-link.ts +59 -0
  311. package/scripts/genome-refresh-worker.ts +364 -0
  312. package/scripts/genome-rewrap.ts +90 -0
  313. package/scripts/genome-synthesizer.ts +679 -0
  314. package/scripts/genome-team-init.ts +312 -0
  315. package/scripts/handoff-pack.ts +438 -0
  316. package/scripts/hook-bootstrap.mjs +75 -0
  317. package/scripts/hook-timing-reader.ts +371 -0
  318. package/scripts/hook-timings-report.ts +554 -0
  319. package/scripts/install-genome-hooks.ts +196 -0
  320. package/scripts/install-onnx-model.ts +154 -0
  321. package/scripts/install-permissions.ts +276 -0
  322. package/scripts/install-status-line.ts +150 -0
  323. package/scripts/mcp-entrypoint.sh +78 -0
  324. package/scripts/mcp-entrypoint.ts +130 -0
  325. package/scripts/mdx-angle-bracket-lint.ts +152 -0
  326. package/scripts/measure-cold-start.ts +126 -0
  327. package/scripts/migrate-stats-to-sqlite.ts +86 -0
  328. package/scripts/ollama-setup.ts +498 -0
  329. package/scripts/onboarding-wizard.ts +1691 -0
  330. package/scripts/orchestrate-emit-telemetry.ts +177 -0
  331. package/scripts/orchestrate-expand.ts +517 -0
  332. package/scripts/orchestrate-run.ts +744 -0
  333. package/scripts/print-legend.ts +50 -0
  334. package/scripts/product-metadata.ts +38 -0
  335. package/scripts/prune-old-memories.ts +208 -0
  336. package/scripts/publish.sh +88 -0
  337. package/scripts/read-calibration.ts +122 -0
  338. package/scripts/report-crash.ts +226 -0
  339. package/scripts/run-benchmark.ts +750 -0
  340. package/scripts/run-root-tests.ts +28 -0
  341. package/scripts/savings-dashboard.ts +1258 -0
  342. package/scripts/savings-report-extras.ts +447 -0
  343. package/scripts/savings-status-line.ts +873 -0
  344. package/scripts/session-event-emit.ts +266 -0
  345. package/scripts/session-greet.ts +686 -0
  346. package/scripts/session-log-report.ts +551 -0
  347. package/scripts/session-resume.ts +614 -0
  348. package/scripts/set-hook-mode.ts +83 -0
  349. package/scripts/smoke-cross-platform.ts +117 -0
  350. package/scripts/smoke-realtime.ts +257 -0
  351. package/scripts/smoke-tools.ts +21 -0
  352. package/scripts/stats-cloud-pull.ts +213 -0
  353. package/scripts/stats-cloud-sync.ts +261 -0
  354. package/scripts/telemetry-flush.ts +160 -0
  355. package/scripts/telemetry-status.ts +266 -0
  356. package/scripts/timing-budget.ts +35 -0
  357. package/scripts/ui-animation.ts +120 -0
  358. package/scripts/ui.ts +431 -0
  359. package/scripts/upgrade-flow.ts +775 -0
  360. package/scripts/weekly-digest.ts +394 -0
  361. package/servers/_accounting.ts +78 -0
  362. package/servers/_ask-router.ts +84 -0
  363. package/servers/_ast-chunker.ts +434 -0
  364. package/servers/_ast-helpers.ts +183 -0
  365. package/servers/_ast-languages.ts +153 -0
  366. package/servers/_ast-refactor/_shared.ts +221 -0
  367. package/servers/_ast-refactor/cross-file-rename.ts +560 -0
  368. package/servers/_ast-refactor/extract-function.ts +357 -0
  369. package/servers/_ast-refactor/file-local-rename.ts +172 -0
  370. package/servers/_ast-refactor.ts +41 -0
  371. package/servers/_ast-skeleton.ts +107 -0
  372. package/servers/_bash-summarizers-registry.ts +1096 -0
  373. package/servers/_crash-dump.ts +210 -0
  374. package/servers/_cwd-clamp.ts +342 -0
  375. package/servers/_edit-log.ts +28 -0
  376. package/servers/_edit-match.ts +236 -0
  377. package/servers/_edit-validate.ts +196 -0
  378. package/servers/_embed-calibration.ts +123 -0
  379. package/servers/_embedding-cache.ts +401 -0
  380. package/servers/_embedding-model.ts +329 -0
  381. package/servers/_events.ts +113 -0
  382. package/servers/_genome-cache.ts +154 -0
  383. package/servers/_genome-commits.ts +172 -0
  384. package/servers/_genome-crypto-v2.ts +242 -0
  385. package/servers/_genome-crypto.ts +259 -0
  386. package/servers/_genome-discoveries.ts +140 -0
  387. package/servers/_genome-embed-populator.ts +216 -0
  388. package/servers/_genome-freshness.ts +134 -0
  389. package/servers/_genome-issues.ts +171 -0
  390. package/servers/_genome-live.ts +263 -0
  391. package/servers/_genome-prs.ts +188 -0
  392. package/servers/_genome-search.ts +479 -0
  393. package/servers/_genome-sync.ts +297 -0
  394. package/servers/_history-tracker.ts +279 -0
  395. package/servers/_http-helpers.ts +151 -0
  396. package/servers/_identity-hash.ts +151 -0
  397. package/servers/_llm-providers/anthropic.ts +132 -0
  398. package/servers/_llm-providers/cloud.ts +195 -0
  399. package/servers/_llm-providers/index.ts +350 -0
  400. package/servers/_llm-providers/local.ts +127 -0
  401. package/servers/_llm-providers/onnx.ts +393 -0
  402. package/servers/_llm-providers/types.ts +27 -0
  403. package/servers/_manifest-v2.ts +447 -0
  404. package/servers/_mcp-host.ts +93 -0
  405. package/servers/_nudge-events.ts +451 -0
  406. package/servers/_orchestrate-executor.ts +316 -0
  407. package/servers/_prefetch.ts +435 -0
  408. package/servers/_pricing.ts +256 -0
  409. package/servers/_pro.ts +381 -0
  410. package/servers/_read-cache.ts +44 -0
  411. package/servers/_router-handlers.ts +46 -0
  412. package/servers/_router.ts +55 -0
  413. package/servers/_run-with-timeout.ts +173 -0
  414. package/servers/_savings-render.ts +214 -0
  415. package/servers/_stats-sqlite.ts +640 -0
  416. package/servers/_stats.ts +1183 -0
  417. package/servers/_status-line-cells.ts +469 -0
  418. package/servers/_streaks.ts +162 -0
  419. package/servers/_summarize.ts +208 -0
  420. package/servers/_task-graph-render.ts +217 -0
  421. package/servers/_task-graph.ts +545 -0
  422. package/servers/_telemetry.ts +719 -0
  423. package/servers/_test-parsers.ts +530 -0
  424. package/servers/_test-watch.ts +626 -0
  425. package/servers/_text-helpers.ts +37 -0
  426. package/servers/_token-measure.ts +160 -0
  427. package/servers/_tool-base.ts +248 -0
  428. package/servers/_weekly-digest.ts +391 -0
  429. package/servers/ask-server-handlers.ts +49 -0
  430. package/servers/ask-server.ts +174 -0
  431. package/servers/bash-server-handlers.ts +138 -0
  432. package/servers/bash-server.ts +1136 -0
  433. package/servers/bench.ts +186 -0
  434. package/servers/diff-semantic-server-handlers.ts +39 -0
  435. package/servers/diff-semantic-server.ts +566 -0
  436. package/servers/diff-server-handlers.ts +317 -0
  437. package/servers/diff-server.ts +20 -0
  438. package/servers/edit-server-handlers.ts +31 -0
  439. package/servers/edit-server.ts +209 -0
  440. package/servers/edit-structural-server-handlers.ts +322 -0
  441. package/servers/efficiency-server-handlers.ts +161 -0
  442. package/servers/efficiency-server.ts +1135 -0
  443. package/servers/flush-server-handlers.ts +21 -0
  444. package/servers/flush-server.ts +19 -0
  445. package/servers/genome-server-handlers.ts +118 -0
  446. package/servers/genome-server.ts +397 -0
  447. package/servers/github-server-handlers.ts +187 -0
  448. package/servers/github-server.ts +856 -0
  449. package/servers/glob-server-handlers.ts +233 -0
  450. package/servers/glob-server.ts +20 -0
  451. package/servers/grep-server-handlers.ts +61 -0
  452. package/servers/grep-server.ts +564 -0
  453. package/servers/http-server-handlers.ts +36 -0
  454. package/servers/http-server.ts +149 -0
  455. package/servers/logs-server-handlers.ts +42 -0
  456. package/servers/logs-server.ts +359 -0
  457. package/servers/ls-server-handlers.ts +166 -0
  458. package/servers/ls-server.ts +20 -0
  459. package/servers/multi-edit-server-handlers.ts +51 -0
  460. package/servers/multi-edit-server.ts +422 -0
  461. package/servers/notebook-edit-server-handlers.ts +34 -0
  462. package/servers/notebook-edit-server.ts +181 -0
  463. package/servers/orient-server-handlers.ts +52 -0
  464. package/servers/orient-server.ts +436 -0
  465. package/servers/pipe-server-handlers.ts +68 -0
  466. package/servers/pipe-server.ts +300 -0
  467. package/servers/read-server-handlers.ts +42 -0
  468. package/servers/read-server.ts +206 -0
  469. package/servers/rename-file-server-handlers.ts +67 -0
  470. package/servers/rename-file-server.ts +699 -0
  471. package/servers/savings-server-handlers.ts +27 -0
  472. package/servers/savings-server.ts +54 -0
  473. package/servers/search-replace-regex-server-handlers.ts +96 -0
  474. package/servers/search-replace-regex-server.ts +751 -0
  475. package/servers/sql-server-handlers.ts +58 -0
  476. package/servers/sql-server.ts +505 -0
  477. package/servers/task-server-handlers.ts +358 -0
  478. package/servers/task-server.ts +24 -0
  479. package/servers/test-server-handlers.ts +351 -0
  480. package/servers/test-server.ts +20 -0
  481. package/servers/tree-server-handlers.ts +503 -0
  482. package/servers/tree-server.ts +20 -0
  483. package/servers/webfetch-server-handlers.ts +166 -0
  484. package/servers/webfetch-server.ts +20 -0
  485. package/servers/websearch-server-handlers.ts +215 -0
  486. package/servers/websearch-server.ts +19 -0
  487. package/servers/write-server-handlers.ts +27 -0
  488. package/servers/write-server.ts +97 -0
  489. package/skills/ashlr-brief/SKILL.md +80 -0
  490. package/skills/ashlr-codex-doctor/SKILL.md +21 -0
  491. package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
  492. package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
  493. package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
  494. package/skills/ashlr-codex-savings/SKILL.md +17 -0
  495. package/skills/ashlr-cost-refactor/SKILL.md +50 -0
  496. package/skills/ashlr-efficient/SKILL.md +64 -0
  497. package/skills/ashlr-genome-author/SKILL.md +43 -0
  498. package/skills/ashlr-lean-tools/SKILL.md +43 -0
  499. package/skills/ashlr-search/SKILL.md +46 -0
@@ -0,0 +1,350 @@
1
+ /**
2
+ * LLM provider dispatcher and public summarizeIfLarge facade.
3
+ *
4
+ * Provider hierarchy (ASHLR_LLM_PROVIDER=auto, which is the default):
5
+ * anthropic (ANTHROPIC_API_KEY or ~/.claude/.credentials.json)
6
+ * → cloud (~/.ashlr/pro-token, calls hosted ashlr Haiku proxy)
7
+ * → onnx (onnxruntime-node + bundled model — currently stubbed)
8
+ * → local (LM Studio / Ollama at ASHLR_LLM_URL)
9
+ * → none (falls back to snipCompact truncation)
10
+ *
11
+ * Anthropic-direct keeps priority over cloud because it's cheaper for users
12
+ * who already have their own key. Cloud serves Pro-but-no-key users.
13
+ *
14
+ * Override with:
15
+ * ASHLR_LLM_PROVIDER=anthropic — use Anthropic only (no fallback)
16
+ * ASHLR_LLM_PROVIDER=cloud — use cloud proxy only
17
+ * ASHLR_LLM_PROVIDER=onnx — use ONNX only
18
+ * ASHLR_LLM_PROVIDER=local — use local LLM only (preserves old behavior)
19
+ * ASHLR_LLM_PROVIDER=off — disable LLM summarization entirely
20
+ *
21
+ * Cost telemetry: every summarize() call records inTokens/outTokens/cost
22
+ * via logEvent("llm_summarize_provider_used") and recordSaving for
23
+ * LLM cost accounting.
24
+ */
25
+
26
+ import { createHash } from "crypto";
27
+ import { existsSync } from "fs";
28
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
29
+ import { homedir } from "os";
30
+ import { dirname, join } from "path";
31
+
32
+ import { logEvent } from "../_events.ts";
33
+ import { bumpSummarization } from "../_stats.ts";
34
+ import { costForLLM } from "../_pricing.ts";
35
+ import { anthropicProvider, _resetAnthropicAvailabilityCache } from "./anthropic.ts";
36
+ import { onnxProvider } from "./onnx.ts";
37
+ import { localProvider, _resetLocalAvailabilityCache } from "./local.ts";
38
+ import { cloudProvider, _resetCloudAvailabilityCache } from "./cloud.ts";
39
+ import type { LlmProvider, ProviderName } from "./types.ts";
40
+
41
+ export type { LlmProvider, ProviderName };
42
+ export { _resetAnthropicAvailabilityCache, _resetLocalAvailabilityCache, _resetCloudAvailabilityCache };
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // None provider — signals "no LLM available"; caller falls back to snipCompact
46
+ // ---------------------------------------------------------------------------
47
+
48
+ const noneProvider: LlmProvider = {
49
+ name: "none",
50
+ async isAvailable() { return false; },
51
+ async summarize() {
52
+ throw new Error("No LLM provider available; caller should fall back to snipCompact");
53
+ },
54
+ };
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Provider selection
58
+ // ---------------------------------------------------------------------------
59
+
60
+ /**
61
+ * Select the best available provider given the ASHLR_LLM_PROVIDER env var.
62
+ * Returns noneProvider when nothing is available — never throws.
63
+ */
64
+ export async function selectProvider(
65
+ override?: string,
66
+ ): Promise<LlmProvider> {
67
+ const setting = (override ?? process.env.ASHLR_LLM_PROVIDER ?? "auto")
68
+ .toLowerCase()
69
+ .trim() as ProviderName;
70
+
71
+ if (setting === "off") return noneProvider;
72
+
73
+ if (setting === "anthropic") return anthropicProvider;
74
+ if (setting === "cloud") return cloudProvider;
75
+ if (setting === "onnx") return onnxProvider;
76
+ if (setting === "local") return localProvider;
77
+
78
+ // "auto" — try each in order, return first available.
79
+ // EXCEPTION: if the user explicitly set ASHLR_LLM_URL, respect that as a
80
+ // strong "use local" signal and prefer local over Anthropic. Without that
81
+ // env var, Anthropic-direct is preferred (best quality, cheaper for user).
82
+ // Cloud fills the gap for Pro users who have a pro-token but no Anthropic key.
83
+ if (process.env.ASHLR_LLM_URL) {
84
+ if (await localProvider.isAvailable()) return localProvider;
85
+ }
86
+ if (await anthropicProvider.isAvailable()) return anthropicProvider;
87
+ if (await cloudProvider.isAvailable()) return cloudProvider;
88
+ if (await onnxProvider.isAvailable()) return onnxProvider;
89
+ if (await localProvider.isAvailable()) return localProvider;
90
+
91
+ return noneProvider;
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Cache (shared with _summarize.ts facade)
96
+ // ---------------------------------------------------------------------------
97
+
98
+ const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
99
+ const PROMPT_VERSION = 1;
100
+
101
+ // One-shot guard so `[ashlr] WARN: local LLM unreachable` only prints once
102
+ // per process, not on every summarize call when the misconfiguration persists.
103
+ let _localUnreachableWarned = false;
104
+
105
+ /**
106
+ * Footer appended to the snipCompact fallback when no LLM provider is
107
+ * available. Names the concrete env vars / setup commands so users have a
108
+ * one-line path to enabling the smart-summary route — the previous "LLM
109
+ * unreachable" text was true but unactionable.
110
+ */
111
+ const LLM_FALLBACK_FOOTER =
112
+ "[ashlr · no LLM provider · using snipCompact fallback]\n" +
113
+ " enable smart summaries by configuring one of:\n" +
114
+ " ANTHROPIC_API_KEY=… (cloud, best quality)\n" +
115
+ " /ashlr-ollama-setup (local, free, no key)\n" +
116
+ " ASHLR_LLM_URL=http://…/v1 (any OpenAI-compatible endpoint)";
117
+
118
+ function home(): string { return process.env.HOME ?? homedir(); }
119
+ function cacheDir(): string { return join(home(), ".ashlr", "summary-cache"); }
120
+
121
+ function sha256(s: string): string {
122
+ return createHash("sha256").update(s).digest("hex");
123
+ }
124
+
125
+ async function readCache(path: string): Promise<string | null> {
126
+ if (!existsSync(path)) return null;
127
+ try {
128
+ const s = await stat(path);
129
+ if (Date.now() - s.mtimeMs > CACHE_TTL_MS) return null;
130
+ return await readFile(path, "utf-8");
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ async function writeCache(path: string, content: string): Promise<void> {
137
+ await mkdir(dirname(path), { recursive: true });
138
+ await writeFile(path, content, "utf-8");
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Fallback truncation (preserved from original _summarize.ts)
143
+ // ---------------------------------------------------------------------------
144
+
145
+ function snipFallback(raw: string): string {
146
+ if (raw.length <= 2000) return raw;
147
+ return raw.slice(0, 800) + "\n\n[... " + (raw.length - 1600) + " bytes elided ...]\n\n" + raw.slice(-800);
148
+ }
149
+
150
+ function bypassHint(rawBytes: number, summaryBytes: number): string {
151
+ const ratio = rawBytes > 0 ? (rawBytes / summaryBytes).toFixed(1) : "?";
152
+ return `[ashlr summary · ${rawBytes.toLocaleString()} → ${summaryBytes.toLocaleString()} bytes · ${ratio}× reduction · pass bypassSummary:true to see full output]`;
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // SummarizeOpts + SummarizeResult (mirrored from _summarize.ts public API)
157
+ // ---------------------------------------------------------------------------
158
+
159
+ export interface SummarizeOpts {
160
+ toolName: string;
161
+ systemPrompt: string;
162
+ thresholdBytes?: number;
163
+ bypass?: boolean;
164
+ timeoutMs?: number;
165
+ /** Legacy: override the LLM endpoint URL (OpenAI-compat). Passed through to
166
+ * local provider only for backward compatibility with existing tests. */
167
+ endpointOverride?: string;
168
+ /** Override the provider name (for testing). Bypasses env var. */
169
+ providerOverride?: string;
170
+ }
171
+
172
+ export interface SummarizeResult {
173
+ text: string;
174
+ summarized: boolean;
175
+ wasCached: boolean;
176
+ fellBack: boolean;
177
+ outputBytes: number;
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Public entry point
182
+ // ---------------------------------------------------------------------------
183
+
184
+ const DEFAULT_THRESHOLD_BYTES = 16_384;
185
+
186
+ /**
187
+ * Summarize `rawText` if it exceeds the threshold. Falls back to snipCompact
188
+ * when no LLM provider is available or when the provider throws.
189
+ *
190
+ * This is the implementation that `servers/_summarize.ts` re-exports, keeping
191
+ * the public API stable for all callers.
192
+ */
193
+ export async function summarizeIfLarge(
194
+ rawText: string,
195
+ opts: SummarizeOpts,
196
+ ): Promise<SummarizeResult> {
197
+ const threshold = opts.thresholdBytes ?? DEFAULT_THRESHOLD_BYTES;
198
+ const rawBytes = Buffer.byteLength(rawText, "utf-8");
199
+
200
+ if (rawBytes <= threshold) {
201
+ await logEvent("tool_noop", { tool: opts.toolName, reason: "below-threshold" });
202
+ return { text: rawText, summarized: false, wasCached: false, fellBack: false, outputBytes: rawBytes };
203
+ }
204
+
205
+ if (opts.bypass) {
206
+ await logEvent("tool_noop", { tool: opts.toolName, reason: "bypassed" });
207
+ return {
208
+ text: rawText + "\n\n[ashlr · summarization bypassed (bypassSummary:true)]",
209
+ summarized: false,
210
+ wasCached: false,
211
+ fellBack: false,
212
+ outputBytes: rawBytes,
213
+ };
214
+ }
215
+
216
+ // Cache check
217
+ const cacheKey = sha256(opts.toolName + "::" + PROMPT_VERSION + "::" + rawText);
218
+ const cachePath = join(cacheDir(), `${cacheKey}.txt`);
219
+ const cached = await readCache(cachePath);
220
+ if (cached) {
221
+ await bumpStat("cacheHits");
222
+ const out = cached + "\n" + bypassHint(rawBytes, Buffer.byteLength(cached, "utf-8"));
223
+ return { text: out, summarized: true, wasCached: true, fellBack: false, outputBytes: Buffer.byteLength(out, "utf-8") };
224
+ }
225
+
226
+ await bumpStat("calls");
227
+
228
+ // If endpointOverride is provided, use the legacy local-provider path directly
229
+ // (preserves backward compat with existing tests that pass endpointOverride).
230
+ if (opts.endpointOverride) {
231
+ const summary = await callLegacyEndpoint(rawText, opts);
232
+ if (summary == null) {
233
+ await logEvent("tool_fallback", { tool: opts.toolName, reason: "llm-unreachable" });
234
+ const fallback = snipFallback(rawText) + "\n\n" + LLM_FALLBACK_FOOTER;
235
+ return { text: fallback, summarized: false, wasCached: false, fellBack: true, outputBytes: Buffer.byteLength(fallback, "utf-8") };
236
+ }
237
+ await writeCache(cachePath, summary).catch(() => undefined);
238
+ const out = summary + "\n" + bypassHint(rawBytes, Buffer.byteLength(summary, "utf-8"));
239
+ return { text: out, summarized: true, wasCached: false, fellBack: false, outputBytes: Buffer.byteLength(out, "utf-8") };
240
+ }
241
+
242
+ // New provider dispatch path
243
+ const provider = await selectProvider(opts.providerOverride);
244
+ const t0 = Date.now();
245
+ let summary: string | null = null;
246
+ let inTokens = 0;
247
+ let outTokens = 0;
248
+ let latencyMs = 0;
249
+ let fellBackToSnipCompact = false;
250
+
251
+ try {
252
+ const result = await provider.summarize(rawText, opts.systemPrompt);
253
+ summary = result.output;
254
+ inTokens = result.inTokens;
255
+ outTokens = result.outTokens;
256
+ latencyMs = result.latencyMs;
257
+ } catch {
258
+ latencyMs = Date.now() - t0;
259
+ fellBackToSnipCompact = true;
260
+ }
261
+
262
+ // Emit telemetry event
263
+ const llmCostUsd = costForLLM(provider.name, inTokens, outTokens);
264
+ await logEvent("tool_call", {
265
+ tool: opts.toolName,
266
+ extra: {
267
+ event: "llm_summarize_provider_used",
268
+ provider: provider.name,
269
+ latency_ms: latencyMs,
270
+ in_tokens: inTokens,
271
+ out_tokens: outTokens,
272
+ fellBackToSnipCompact,
273
+ llmCostUsd,
274
+ },
275
+ });
276
+
277
+ if (summary == null) {
278
+ await logEvent("tool_fallback", { tool: opts.toolName, reason: "llm-unreachable" });
279
+ // Per v1.22 review: when the SELECTED provider is local AND it failed,
280
+ // surface a one-line stderr warning that names the URL the user
281
+ // configured. Without this, a typo in ASHLR_LLM_URL silently degrades
282
+ // every summarization to truncation with the only signal buried inside
283
+ // tool output. Only logged once per process to avoid spam.
284
+ if (provider.name === "local" && !_localUnreachableWarned) {
285
+ _localUnreachableWarned = true;
286
+ const url = process.env.ASHLR_LLM_URL ?? "http://localhost:1234/v1";
287
+ process.stderr.write(
288
+ `[ashlr] WARN: local LLM at ${url} unreachable; falling back to truncation. ` +
289
+ `Verify the endpoint, or unset ASHLR_LLM_URL to let auto-selection prefer Anthropic.\n`,
290
+ );
291
+ }
292
+ const fallback = snipFallback(rawText) + "\n\n" + LLM_FALLBACK_FOOTER;
293
+ return { text: fallback, summarized: false, wasCached: false, fellBack: true, outputBytes: Buffer.byteLength(fallback, "utf-8") };
294
+ }
295
+
296
+ await writeCache(cachePath, summary).catch(() => undefined);
297
+ const out = summary + "\n" + bypassHint(rawBytes, Buffer.byteLength(summary, "utf-8"));
298
+ return { text: out, summarized: true, wasCached: false, fellBack: false, outputBytes: Buffer.byteLength(out, "utf-8") };
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Legacy endpoint path (backward compat for endpointOverride callers/tests)
303
+ // ---------------------------------------------------------------------------
304
+
305
+ async function callLegacyEndpoint(rawText: string, opts: SummarizeOpts): Promise<string | null> {
306
+ const url = opts.endpointOverride!;
307
+ const apiKey =
308
+ process.env.ASHLR_LLM_KEY ??
309
+ (process.env.ASHLR_PRO_TOKEN && !process.env.ASHLR_LLM_URL
310
+ ? process.env.ASHLR_PRO_TOKEN
311
+ : "local-llm");
312
+ const model = process.env.ASHLR_LLM_MODEL ?? "qwen/qwen3-coder-30b@8bit";
313
+ const timeoutMs = opts.timeoutMs ?? 5_000;
314
+
315
+ const ctl = new AbortController();
316
+ const t = setTimeout(() => ctl.abort(), timeoutMs);
317
+ try {
318
+ const res = await fetch(`${url}/chat/completions`, {
319
+ method: "POST",
320
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
321
+ signal: ctl.signal,
322
+ body: JSON.stringify({
323
+ model,
324
+ stream: false,
325
+ max_tokens: 800,
326
+ temperature: 0.1,
327
+ messages: [
328
+ { role: "system", content: opts.systemPrompt },
329
+ { role: "user", content: rawText },
330
+ ],
331
+ }),
332
+ });
333
+ clearTimeout(t);
334
+ if (!res.ok) return null;
335
+ const j = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
336
+ const text = j.choices?.[0]?.message?.content?.trim();
337
+ return text && text.length > 0 ? text : null;
338
+ } catch {
339
+ clearTimeout(t);
340
+ return null;
341
+ }
342
+ }
343
+
344
+ // ---------------------------------------------------------------------------
345
+ // Internal stat bump helper
346
+ // ---------------------------------------------------------------------------
347
+
348
+ async function bumpStat(field: "calls" | "cacheHits"): Promise<void> {
349
+ try { await bumpSummarization(field); } catch { /* best-effort */ }
350
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Local LLM provider — LM Studio / Ollama via OpenAI-compat API.
3
+ *
4
+ * Preserves the existing _summarize.ts local-LLM logic verbatim:
5
+ * - ASHLR_LLM_URL (default: http://localhost:1234/v1)
6
+ * - ASHLR_LLM_KEY (default: "local-llm")
7
+ * - ASHLR_LLM_MODEL (default: "qwen/qwen3-coder-30b@8bit")
8
+ *
9
+ * Availability: pings GET /models at the configured URL; result is cached for
10
+ * 60 seconds so repeated isAvailable() calls don't spam the local server.
11
+ */
12
+
13
+ import type { LlmProvider, LlmSummarizeResult } from "./types.ts";
14
+
15
+ function localEndpoint(): string {
16
+ // Mirror the llmEndpoint() logic from the old _summarize.ts — pro-token
17
+ // auto-routing uses the ashlr cloud; explicit ASHLR_LLM_URL wins; default
18
+ // is LM Studio at localhost:1234.
19
+ if (process.env.ASHLR_PRO_TOKEN && !process.env.ASHLR_LLM_URL) {
20
+ const base = process.env.ASHLR_API_URL ?? "https://api.ashlr.ai";
21
+ return `${base}/llm`;
22
+ }
23
+ return process.env.ASHLR_LLM_URL ?? "http://localhost:1234/v1";
24
+ }
25
+
26
+ // Availability cache: { available, expiresAt }
27
+ let _availCache: { available: boolean; expiresAt: number } | null = null;
28
+ const AVAIL_CACHE_MS = 60_000;
29
+
30
+ export const localProvider: LlmProvider = {
31
+ name: "local",
32
+
33
+ async isAvailable(): Promise<boolean> {
34
+ // Explicit user config wins — if ASHLR_LLM_URL is set, trust the user
35
+ // knows their endpoint works. This both (a) respects intent and
36
+ // (b) avoids requiring test stubs to mock the probe endpoint
37
+ // (`/models`) just to be eligible for the chat path. Same rule for
38
+ // ASHLR_PRO_TOKEN (cloud-routed).
39
+ if (process.env.ASHLR_LLM_URL || process.env.ASHLR_PRO_TOKEN) return true;
40
+
41
+ const now = Date.now();
42
+ if (_availCache && now < _availCache.expiresAt) return _availCache.available;
43
+
44
+ const url = localEndpoint();
45
+ const ctl = new AbortController();
46
+ const t = setTimeout(() => ctl.abort(), 2_000);
47
+
48
+ try {
49
+ const res = await fetch(`${url}/models`, {
50
+ signal: ctl.signal,
51
+ headers: { Authorization: `Bearer ${process.env.ASHLR_LLM_KEY ?? "local-llm"}` },
52
+ });
53
+ clearTimeout(t);
54
+ const available = res.ok;
55
+ _availCache = { available, expiresAt: now + AVAIL_CACHE_MS };
56
+ return available;
57
+ } catch {
58
+ clearTimeout(t);
59
+ _availCache = { available: false, expiresAt: now + AVAIL_CACHE_MS };
60
+ return false;
61
+ }
62
+ },
63
+
64
+ async summarize(
65
+ text: string,
66
+ prompt: string,
67
+ opts?: { maxTokens?: number },
68
+ ): Promise<LlmSummarizeResult> {
69
+ const url = localEndpoint();
70
+ const apiKey = process.env.ASHLR_LLM_KEY ?? (
71
+ process.env.ASHLR_PRO_TOKEN && !process.env.ASHLR_LLM_URL
72
+ ? process.env.ASHLR_PRO_TOKEN
73
+ : "local-llm"
74
+ );
75
+ const model = process.env.ASHLR_LLM_MODEL ?? "qwen/qwen3-coder-30b@8bit";
76
+ const maxTokens = opts?.maxTokens ?? 800;
77
+
78
+ const ctl = new AbortController();
79
+ const timer = setTimeout(() => ctl.abort(), 5_000);
80
+
81
+ const t0 = Date.now();
82
+ let res: Response;
83
+ try {
84
+ res = await fetch(`${url}/chat/completions`, {
85
+ method: "POST",
86
+ signal: ctl.signal,
87
+ headers: {
88
+ "Content-Type": "application/json",
89
+ Authorization: `Bearer ${apiKey}`,
90
+ },
91
+ body: JSON.stringify({
92
+ model,
93
+ stream: false,
94
+ max_tokens: maxTokens,
95
+ temperature: 0.1,
96
+ messages: [
97
+ { role: "system", content: prompt },
98
+ { role: "user", content: text },
99
+ ],
100
+ }),
101
+ });
102
+ } finally {
103
+ clearTimeout(timer);
104
+ }
105
+
106
+ if (!res.ok) throw new Error(`Local LLM error ${res.status}`);
107
+
108
+ const latencyMs = Date.now() - t0;
109
+ const j = (await res.json()) as {
110
+ choices?: Array<{ message?: { content?: string } }>;
111
+ usage?: { prompt_tokens?: number; completion_tokens?: number };
112
+ };
113
+
114
+ const output = j.choices?.[0]?.message?.content?.trim() ?? "";
115
+ if (!output) throw new Error("Local LLM: empty response");
116
+
117
+ const inTokens = j.usage?.prompt_tokens ?? 0;
118
+ const outTokens = j.usage?.completion_tokens ?? 0;
119
+
120
+ return { output, inTokens, outTokens, latencyMs };
121
+ },
122
+ };
123
+
124
+ /** Test hook: reset the availability cache so tests can toggle endpoints. */
125
+ export function _resetLocalAvailabilityCache(): void {
126
+ _availCache = null;
127
+ }