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,391 @@
1
+ /**
2
+ * _weekly-digest.ts — Once-per-week Monday banner with relatable comparisons.
3
+ *
4
+ * Fires on the first Monday session of the week. Tracks in:
5
+ * ~/.ashlr/weekly-digest.json: { lastShownWeek: "YYYY-WW" }
6
+ *
7
+ * Opt-out via:
8
+ * ASHLR_DISABLE_DIGEST=1 (env var)
9
+ * ~/.ashlr/config.json :: { "digest": "off" }
10
+ *
11
+ * Banner format (plain/no-color):
12
+ * Last week: 12.4M tokens saved · ~$37.20
13
+ * That's like 9 cups of coffee, or 2 Spotify months.
14
+ * Streak: 5d · top tool: ashlr__grep (43% of savings)
15
+ */
16
+
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
18
+ import { homedir } from "os";
19
+ import { join } from "path";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Types
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export interface DigestState {
26
+ lastShownWeek: string; // "YYYY-WW" — ISO week key
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Comparison bank
31
+ // ---------------------------------------------------------------------------
32
+
33
+ interface Comparison {
34
+ /** Minimum dollar value this item represents. */
35
+ minDollars: number;
36
+ /** Display label when quantity > 1 (e.g. "cups of coffee"). */
37
+ plural: string;
38
+ /** Display label when quantity === 1 (e.g. "cup of coffee"). */
39
+ singular: string;
40
+ /** Dollar value of one unit. */
41
+ unitCost: number;
42
+ }
43
+
44
+ // Ordered ascending by unitCost. The selector picks the largest item that
45
+ // yields quantity >= 1 and rounds cleanly, then multiplies.
46
+ export const COMPARISONS: readonly Comparison[] = [
47
+ { minDollars: 0, plural: "pencils", singular: "pencil", unitCost: 1 },
48
+ { minDollars: 0, plural: "song downloads", singular: "song download", unitCost: 1.29 },
49
+ { minDollars: 0, plural: "minutes of GitHub Actions", singular: "minute of GitHub Actions", unitCost: 0.008 },
50
+ { minDollars: 3, plural: "cups of coffee", singular: "cup of coffee", unitCost: 4 },
51
+ { minDollars: 3, plural: "sandwiches", singular: "sandwich", unitCost: 5 },
52
+ { minDollars: 8, plural: "Spotify months", singular: "Spotify month", unitCost: 10 },
53
+ { minDollars: 8, plural: "paperbacks", singular: "paperback", unitCost: 12 },
54
+ { minDollars: 40, plural: "dinners out", singular: "dinner out", unitCost: 50 },
55
+ { minDollars: 80, plural: "moderate side-gig hours", singular: "moderate side-gig hour", unitCost: 100 },
56
+ ];
57
+
58
+ /**
59
+ * Build a relatable comparison string for the given dollar amount.
60
+ *
61
+ * Algorithm:
62
+ * 1. Filter items whose minDollars <= dollars and unitCost <= dollars.
63
+ * 2. Among those, prefer items where qty = floor(dollars/unitCost) is in
64
+ * the "sweet spot" range [2, 15] — these make the most relatable strings
65
+ * ("9 cups of coffee" beats "3 paperbacks" or "0.5 dinners").
66
+ * 3. Within the sweet-spot candidates, pick the one with the highest unit
67
+ * cost (largest denomination that still gives a readable count).
68
+ * 4. Fall back to the largest qualifying item when no sweet-spot candidate
69
+ * exists (e.g. very large dollar amounts).
70
+ *
71
+ * Returns "" when dollars < 0.01 (nothing to compare).
72
+ */
73
+ export function buildComparison(dollars: number): string {
74
+ if (!Number.isFinite(dollars) || dollars < 0.01) return "";
75
+
76
+ // Collect all eligible items (minDollars <= dollars, unitCost <= dollars).
77
+ const eligible = COMPARISONS.filter(
78
+ (item) => dollars >= item.minDollars && dollars >= item.unitCost,
79
+ );
80
+
81
+ if (eligible.length === 0) {
82
+ // Degenerate: use smallest item regardless.
83
+ const item = COMPARISONS[0]!;
84
+ const qty = Math.floor(dollars / item.unitCost);
85
+ if (qty <= 0) return "";
86
+ return `${qty} ${qty === 1 ? item.singular : item.plural}`;
87
+ }
88
+
89
+ // Target quantity: the "ideal" count that makes a comparison feel vivid.
90
+ // Quantities near 5-10 are most relatable ("9 cups of coffee" > "3 paperbacks").
91
+ // Algorithm: among eligible items, score each by how close its quantity is to
92
+ // TARGET_QTY (lower distance = better). Break ties by higher unitCost so we
93
+ // prefer recognizable real-world items over cheap ones.
94
+ const TARGET_QTY = 8;
95
+
96
+ // Compute qty for each eligible item.
97
+ const withQty = eligible.map((item) => ({
98
+ item,
99
+ qty: Math.floor(dollars / item.unitCost),
100
+ })).filter(({ qty }) => qty >= 1); // at least 1
101
+
102
+ let best: Comparison;
103
+ if (withQty.length === 0) {
104
+ best = eligible[0]!;
105
+ } else {
106
+ best = withQty.reduce((a, b) => {
107
+ const distA = Math.abs(a.qty - TARGET_QTY);
108
+ const distB = Math.abs(b.qty - TARGET_QTY);
109
+ if (distA !== distB) return distA < distB ? a : b;
110
+ // Tie-break: higher unit cost wins (more recognizable denomination).
111
+ return b.item.unitCost > a.item.unitCost ? b : a;
112
+ }).item;
113
+ }
114
+
115
+ const qty = Math.floor(dollars / best.unitCost);
116
+ if (qty <= 0) return "";
117
+ const label = qty === 1 ? best.singular : best.plural;
118
+ return `${qty} ${label}`;
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // ISO week helper
123
+ // ---------------------------------------------------------------------------
124
+
125
+ /**
126
+ * Returns "YYYY-WW" for the ISO week containing `date`.
127
+ * Week 1 = the week containing the first Thursday of the year.
128
+ */
129
+ export function isoWeekKey(date: Date = new Date()): string {
130
+ // Copy and move to Thursday of the same week to determine the year.
131
+ const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
132
+ // Day of week: 0=Sun, 1=Mon … 6=Sat. ISO: Mon=1..Sun=7.
133
+ const dow = d.getUTCDay() || 7; // convert Sun(0) → 7
134
+ d.setUTCDate(d.getUTCDate() + 4 - dow); // move to Thursday
135
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
136
+ const weekNum = Math.ceil(((d.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7);
137
+ return `${d.getUTCFullYear()}-${String(weekNum).padStart(2, "0")}`;
138
+ }
139
+
140
+ /**
141
+ * Returns the ISO week key for the week prior to `date`.
142
+ */
143
+ export function priorWeekKey(date: Date = new Date()): string {
144
+ const d = new Date(date);
145
+ d.setUTCDate(d.getUTCDate() - 7);
146
+ return isoWeekKey(d);
147
+ }
148
+
149
+ /**
150
+ * Returns the YYYY-MM-DD keys for the 7 days of the ISO week identified by
151
+ * `weekKey` (Mon..Sun).
152
+ */
153
+ export function weekDayKeys(weekKey: string): string[] {
154
+ const [yearStr, weekStr] = weekKey.split("-");
155
+ const year = parseInt(yearStr!, 10);
156
+ const week = parseInt(weekStr!, 10);
157
+ // Jan 4 is always in week 1.
158
+ const jan4 = new Date(Date.UTC(year, 0, 4));
159
+ const dow4 = jan4.getUTCDay() || 7;
160
+ // Monday of week 1:
161
+ const monday1 = new Date(jan4);
162
+ monday1.setUTCDate(monday1.getUTCDate() - (dow4 - 1));
163
+ // Monday of our target week:
164
+ const targetMonday = new Date(monday1);
165
+ targetMonday.setUTCDate(targetMonday.getUTCDate() + (week - 1) * 7);
166
+ const keys: string[] = [];
167
+ for (let i = 0; i < 7; i++) {
168
+ const d = new Date(targetMonday);
169
+ d.setUTCDate(d.getUTCDate() + i);
170
+ keys.push(d.toISOString().slice(0, 10));
171
+ }
172
+ return keys;
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Paths
177
+ // ---------------------------------------------------------------------------
178
+
179
+ export function digestStatePath(home: string = homedir()): string {
180
+ return join(home, ".ashlr", "weekly-digest.json");
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // IO helpers
185
+ // ---------------------------------------------------------------------------
186
+
187
+ export function readDigestState(home: string = homedir()): DigestState {
188
+ try {
189
+ const p = digestStatePath(home);
190
+ if (!existsSync(p)) return { lastShownWeek: "" };
191
+ const raw = JSON.parse(readFileSync(p, "utf-8")) as Partial<DigestState>;
192
+ return {
193
+ lastShownWeek: typeof raw.lastShownWeek === "string" ? raw.lastShownWeek : "",
194
+ };
195
+ } catch {
196
+ return { lastShownWeek: "" };
197
+ }
198
+ }
199
+
200
+ export function writeDigestState(data: DigestState, home: string = homedir()): void {
201
+ try {
202
+ mkdirSync(join(home, ".ashlr"), { recursive: true });
203
+ writeFileSync(digestStatePath(home), JSON.stringify(data, null, 2));
204
+ } catch {
205
+ /* best-effort */
206
+ }
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Opt-out helpers
211
+ // ---------------------------------------------------------------------------
212
+
213
+ export function isDigestDisabled(home: string = homedir()): boolean {
214
+ // Env var opt-out.
215
+ if (process.env.ASHLR_DISABLE_DIGEST === "1") return true;
216
+ // Config file opt-out.
217
+ try {
218
+ const cfgPath = join(home, ".ashlr", "config.json");
219
+ if (!existsSync(cfgPath)) return false;
220
+ const raw = JSON.parse(readFileSync(cfgPath, "utf-8")) as Record<string, unknown>;
221
+ return raw.digest === "off";
222
+ } catch {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Stats types (minimal — we only need byDay + byTool)
229
+ // ---------------------------------------------------------------------------
230
+
231
+ export interface DigestStats {
232
+ lifetime?: {
233
+ byDay?: Record<string, { tokensSaved?: number; calls?: number }>;
234
+ byTool?: Record<string, { tokensSaved?: number; calls?: number }>;
235
+ };
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // Banner building
240
+ // ---------------------------------------------------------------------------
241
+
242
+ export interface DigestBannerOpts {
243
+ home?: string;
244
+ now?: Date;
245
+ stats?: DigestStats;
246
+ /** Dollars saved (injected by caller; computed from costFor). */
247
+ dollarsSaved?: number;
248
+ /** Current streak length (from _streaks.ts). */
249
+ currentStreak?: number;
250
+ /**
251
+ * Test hook: when true, marks the digest as shown in state but skips the
252
+ * actual write so tests stay isolated.
253
+ */
254
+ suppressStateWrite?: boolean;
255
+ }
256
+
257
+ export interface DigestBannerResult {
258
+ /** The rendered banner text, or null if the digest should not fire. */
259
+ banner: string | null;
260
+ /** Whether this is the first time we're showing this week's digest. */
261
+ fired: boolean;
262
+ }
263
+
264
+ export interface DigestBannerContent {
265
+ tokens: number;
266
+ dollars: number;
267
+ comparisonA: string;
268
+ comparisonB: string;
269
+ currentStreak: number;
270
+ topTool: string;
271
+ topToolPct: number;
272
+ }
273
+
274
+ /**
275
+ * Build the banner content fields without any IO side-effects.
276
+ * Pure function — used by tests and by buildDigestBanner.
277
+ */
278
+ export function computeBannerContent(
279
+ opts: Required<Pick<DigestBannerOpts, "now" | "stats" | "dollarsSaved" | "currentStreak">>,
280
+ ): DigestBannerContent {
281
+ const { now, stats, dollarsSaved, currentStreak } = opts;
282
+ const prior = priorWeekKey(now);
283
+ const dayKeys = weekDayKeys(prior);
284
+ const byDay = stats.lifetime?.byDay ?? {};
285
+ const tokens = dayKeys.reduce((sum, k) => sum + (byDay[k]?.tokensSaved ?? 0), 0);
286
+ const dollars = dollarsSaved ?? 0;
287
+
288
+ // Two independent comparisons for variety.
289
+ const compA = buildComparison(dollars);
290
+ // For the second comparison pick the next-size-up item if possible.
291
+ let compB = "";
292
+ const aIdx = COMPARISONS.findIndex((c) => compA.includes(c.plural) || compA.includes(c.singular));
293
+ if (aIdx > 0) {
294
+ const alt = COMPARISONS[aIdx - 1];
295
+ if (alt && dollars >= alt.unitCost) {
296
+ const qty = Math.floor(dollars / alt.unitCost);
297
+ compB = qty > 0 ? `${qty} ${qty === 1 ? alt.singular : alt.plural}` : "";
298
+ }
299
+ }
300
+
301
+ // Top tool by tokens saved last week.
302
+ const byTool = stats.lifetime?.byTool ?? {};
303
+ let topTool = "";
304
+ let topTok = 0;
305
+ for (const [name, t] of Object.entries(byTool)) {
306
+ const tok = t?.tokensSaved ?? 0;
307
+ if (tok > topTok) { topTok = tok; topTool = name; }
308
+ }
309
+ const topToolPct = tokens > 0 && topTok > 0 ? Math.round((topTok / tokens) * 100) : 0;
310
+
311
+ return { tokens, dollars, comparisonA: compA, comparisonB: compB, currentStreak, topTool, topToolPct };
312
+ }
313
+
314
+ /**
315
+ * Render the digest banner as a plain-text string (no ANSI — the session-greet
316
+ * layer will apply its own color if appropriate).
317
+ */
318
+ export function renderDigestBanner(content: DigestBannerContent): string {
319
+ const { tokens, dollars, comparisonA, comparisonB, currentStreak, topTool, topToolPct } = content;
320
+
321
+ function fmtTokens(n: number): string {
322
+ if (n < 1000) return String(n);
323
+ if (n < 1_000_000) return (n / 1000).toFixed(1) + "K";
324
+ return (n / 1_000_000).toFixed(1) + "M";
325
+ }
326
+
327
+ const dollarStr = dollars < 0.01 ? "~$0.00" : dollars < 100
328
+ ? `~$${dollars.toFixed(2)}`
329
+ : `~$${Math.round(dollars)}`;
330
+
331
+ const lines: string[] = [];
332
+ lines.push(`Last week: ${fmtTokens(tokens)} tokens saved · ${dollarStr}`);
333
+
334
+ const comparisons: string[] = [];
335
+ if (comparisonA) comparisons.push(comparisonA);
336
+ if (comparisonB && comparisonB !== comparisonA) comparisons.push(comparisonB);
337
+ if (comparisons.length > 0) {
338
+ lines.push(`That's like ${comparisons.join(", or ")}.`);
339
+ }
340
+
341
+ const meta: string[] = [];
342
+ if (currentStreak >= 3) meta.push(`${currentStreak}d streak`);
343
+ if (topTool && topToolPct > 0) meta.push(`top tool: ${topTool} (${topToolPct}% of savings)`);
344
+ if (meta.length > 0) {
345
+ lines.push(meta.join(" · "));
346
+ }
347
+
348
+ return lines.join("\n");
349
+ }
350
+
351
+ /**
352
+ * Main entry point: check if a weekly digest should fire, build + return the
353
+ * banner text, and mark the week as shown.
354
+ *
355
+ * Returns null when:
356
+ * - digest is disabled (env/config opt-out)
357
+ * - already shown this week
358
+ * - total tokens == 0 (nothing happened last week)
359
+ */
360
+ export function buildDigestBanner(opts: DigestBannerOpts = {}): DigestBannerResult {
361
+ const home = opts.home ?? homedir();
362
+ const now = opts.now ?? new Date();
363
+
364
+ if (isDigestDisabled(home)) return { banner: null, fired: false };
365
+
366
+ const thisWeek = isoWeekKey(now);
367
+ const state = readDigestState(home);
368
+ if (state.lastShownWeek === thisWeek) return { banner: null, fired: false };
369
+
370
+ const stats = opts.stats ?? ({} as DigestStats);
371
+ const dollars = opts.dollarsSaved ?? 0;
372
+
373
+ const content = computeBannerContent({
374
+ now,
375
+ stats,
376
+ dollarsSaved: dollars,
377
+ currentStreak: opts.currentStreak ?? 0,
378
+ });
379
+
380
+ // Suppress empty banners.
381
+ if (content.tokens <= 0) return { banner: null, fired: false };
382
+
383
+ const banner = renderDigestBanner(content);
384
+
385
+ // Mark as shown.
386
+ if (!opts.suppressStateWrite) {
387
+ writeDigestState({ lastShownWeek: thisWeek }, home);
388
+ }
389
+
390
+ return { banner, fired: true };
391
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * ask-server-handlers — registers ashlr__ask on the shared registry.
3
+ *
4
+ * Routing decisions are delegated to _ask-router.ts (pure, no tool imports).
5
+ * Dispatch today still calls the underlying tool functions directly; once
6
+ * every downstream server (efficiency, orient, tree, glob) lives on the
7
+ * registry, the dispatch can switch to `getTool(...).handler(...)` and drop
8
+ * those imports entirely. Until then, this module imports ask-server's
9
+ * existing `askHandler` which does the direct calls.
10
+ */
11
+
12
+ import { registerTool, toErrorResult, type ToolCallContext, type ToolResult } from "./_tool-base";
13
+ import { askHandler } from "./ask-server";
14
+
15
+ registerTool({
16
+ name: "ashlr__ask",
17
+ description:
18
+ "Single-tool entry point for ashlr. Accepts a natural-language question and " +
19
+ "auto-routes to the correct underlying tool (ashlr__read, ashlr__grep, " +
20
+ "ashlr__orient, ashlr__tree, or ashlr__glob) using deterministic rules — no " +
21
+ "LLM in the routing step. Output always starts with a one-line trace showing " +
22
+ "which tool fired and why.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ question: {
27
+ type: "string",
28
+ description: "Natural-language question about the codebase",
29
+ },
30
+ cwd: {
31
+ type: "string",
32
+ description: "Working directory context (default: process.cwd())",
33
+ },
34
+ },
35
+ required: ["question"],
36
+ },
37
+ handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
38
+ const a = (args ?? {}) as { question?: string; cwd?: string };
39
+ try {
40
+ const text = await askHandler({
41
+ question: typeof a.question === "string" ? a.question : "",
42
+ cwd: typeof a.cwd === "string" ? a.cwd : undefined,
43
+ });
44
+ return { content: [{ type: "text", text }] };
45
+ } catch (err) {
46
+ return toErrorResult("ashlr__ask error", err);
47
+ }
48
+ },
49
+ });
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ashlr-ask MCP server.
4
+ *
5
+ * Exposes a single tool (ashlr__ask) that accepts a natural-language question
6
+ * and routes it to the correct underlying ashlr tool via deterministic rules —
7
+ * no LLM involved in routing.
8
+ *
9
+ * Routing table (first match wins):
10
+ * 1. glob token (e.g. **\/*.ts) → ashlr__glob
11
+ * 2. read/show-me/file + path token → ashlr__read
12
+ * 3. grep/find/search/where-is/which → ashlr__grep
13
+ * 4. structural (how does/explain/why) → ashlr__orient
14
+ * 5. list/tree/structure/directory → ashlr__tree
15
+ * fallback → ashlr__orient
16
+ */
17
+
18
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
19
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
+ import {
21
+ CallToolRequestSchema,
22
+ ListToolsRequestSchema,
23
+ } from "@modelcontextprotocol/sdk/types.js";
24
+
25
+ // Register the four tools askHandler dispatches to. Import only the specific
26
+ // handler modules — not _router-handlers — to avoid the circular dep chain:
27
+ // _router-handlers → ask-server-handlers → ask-server → _router-handlers.
28
+ // v1.21 Track B: efficiency-server decomposed; import 5 per-tool handlers.
29
+ import "./read-server-handlers";
30
+ import "./grep-server-handlers";
31
+ import "./edit-server-handlers";
32
+ import "./flush-server-handlers";
33
+ import "./savings-server-handlers";
34
+ import "./orient-server-handlers";
35
+ import "./tree-server-handlers";
36
+ import "./glob-server-handlers";
37
+ import { logEvent } from "./_events";
38
+ import { getTool } from "./_tool-base";
39
+ import { extractKeywords } from "./_text-helpers";
40
+ import { routeQuestion, type RouteDecision, type RoutedTool } from "./_ask-router";
41
+
42
+ // Re-export for callers that historically imported these from ask-server.
43
+ export { routeQuestion, extractKeywords };
44
+ export type { RouteDecision, RoutedTool };
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Dispatch
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export async function askHandler(input: { question: string; cwd?: string }): Promise<string> {
51
+ const { question, cwd } = input;
52
+ const decision = routeQuestion(question);
53
+
54
+ // Log the routing decision for /ashlr-usage analytics.
55
+ await logEvent("tool_call", {
56
+ tool: "ashlr__ask",
57
+ reason: `routed-to=${decision.tool}`,
58
+ extra: { routeReason: decision.reason, extracted: decision.extracted ?? null },
59
+ });
60
+
61
+ const trace = `[ashlr__ask] routed to ${decision.tool} (${decision.reason})`;
62
+
63
+ const ctx = {
64
+ sessionId: process.env.CLAUDE_SESSION_ID || process.env.ASHLR_SESSION_ID || undefined,
65
+ env: process.env,
66
+ };
67
+
68
+ let result: string;
69
+ switch (decision.tool) {
70
+ case "ashlr__read": {
71
+ const toolH = getTool("ashlr__read");
72
+ if (!toolH) throw new Error("ashlr__read not registered");
73
+ const r = await toolH.handler({ path: decision.extracted ?? question }, ctx);
74
+ result = r.content.map((c) => c.text).join("\n");
75
+ break;
76
+ }
77
+ case "ashlr__grep": {
78
+ const toolH = getTool("ashlr__grep");
79
+ if (!toolH) throw new Error("ashlr__grep not registered");
80
+ const r = await toolH.handler({ pattern: decision.extracted ?? question, cwd }, ctx);
81
+ result = r.content.map((c) => c.text).join("\n");
82
+ break;
83
+ }
84
+ case "ashlr__orient": {
85
+ const toolH = getTool("ashlr__orient");
86
+ if (!toolH) throw new Error("ashlr__orient not registered");
87
+ const r = await toolH.handler({ query: question, dir: cwd }, ctx);
88
+ result = r.content.map((c) => c.text).join("\n");
89
+ break;
90
+ }
91
+ case "ashlr__tree": {
92
+ const toolH = getTool("ashlr__tree");
93
+ if (!toolH) throw new Error("ashlr__tree not registered");
94
+ const r = await toolH.handler({ path: cwd }, ctx);
95
+ result = r.content.map((c) => c.text).join("\n");
96
+ break;
97
+ }
98
+ case "ashlr__glob": {
99
+ const toolH = getTool("ashlr__glob");
100
+ if (!toolH) throw new Error("ashlr__glob not registered");
101
+ const r = await toolH.handler({ pattern: decision.extracted ?? question, cwd }, ctx);
102
+ result = r.content.map((c) => c.text).join("\n");
103
+ break;
104
+ }
105
+ default: {
106
+ const toolH = getTool("ashlr__orient");
107
+ if (!toolH) throw new Error("ashlr__orient not registered");
108
+ const r = await toolH.handler({ query: question, dir: cwd }, ctx);
109
+ result = r.content.map((c) => c.text).join("\n");
110
+ }
111
+ }
112
+
113
+ return `${trace}\n${result}`;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // MCP server wiring
118
+ // ---------------------------------------------------------------------------
119
+
120
+ const server = new Server(
121
+ { name: "ashlr-ask", version: "0.1.0" },
122
+ { capabilities: { tools: {} } },
123
+ );
124
+
125
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
126
+ tools: [
127
+ {
128
+ name: "ashlr__ask",
129
+ description:
130
+ "Single-tool entry point for ashlr. Accepts a natural-language question and " +
131
+ "auto-routes to the correct underlying tool (ashlr__read, ashlr__grep, " +
132
+ "ashlr__orient, ashlr__tree, or ashlr__glob) using deterministic rules — no " +
133
+ "LLM in the routing step. Output always starts with a one-line trace showing " +
134
+ "which tool fired and why.",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ question: {
139
+ type: "string",
140
+ description: "Natural-language question about the codebase",
141
+ },
142
+ cwd: {
143
+ type: "string",
144
+ description: "Working directory context (default: process.cwd())",
145
+ },
146
+ },
147
+ required: ["question"],
148
+ },
149
+ },
150
+ ],
151
+ }));
152
+
153
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
154
+ const { name, arguments: args } = req.params;
155
+ try {
156
+ if (name !== "ashlr__ask") {
157
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
158
+ }
159
+ const a = (args ?? {}) as { question?: string; cwd?: string };
160
+ const text = await askHandler({
161
+ question: typeof a.question === "string" ? a.question : "",
162
+ cwd: typeof a.cwd === "string" ? a.cwd : undefined,
163
+ });
164
+ return { content: [{ type: "text", text }] };
165
+ } catch (err) {
166
+ const message = err instanceof Error ? err.message : String(err);
167
+ return { content: [{ type: "text", text: `ashlr__ask error: ${message}` }], isError: true };
168
+ }
169
+ });
170
+
171
+ if (import.meta.main) {
172
+ const transport = new StdioServerTransport();
173
+ await server.connect(transport);
174
+ }