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,261 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * stats-cloud-sync.ts — Delta push of lifetime stats to the ashlr Pro backend.
4
+ *
5
+ * Reads local stats (via _stats.ts readStats()), computes the delta since the
6
+ * last successful sync (cursor stored in ~/.ashlr/stats-sync-cursor.json), and
7
+ * POSTs to POST /stats/sync.
8
+ *
9
+ * Privacy:
10
+ * - machineId is a one-way SHA-256 hash of os.hostname() — the raw hostname
11
+ * is never sent. The hash lets the backend deduplicate machine uploads
12
+ * without learning the hostname.
13
+ * - Only pure counts are sent (calls, tokensSaved, byTool, byDay). No paths,
14
+ * no file content, no CWD.
15
+ *
16
+ * Cursor shape: { syncedAt: ISO, calls: number, tokensSaved: number }
17
+ * - `calls` and `tokensSaved` are the lifetime totals at the time of the
18
+ * last successful sync. The delta is (current - cursor). We send the
19
+ * full current lifetime snapshot (not just the delta numbers) because the
20
+ * server uses upsert/idempotent semantics — re-sending the same payload is
21
+ * safe and avoids a separate delta endpoint.
22
+ *
23
+ * Best-effort: never throws, never blocks. On network failure the cursor is left
24
+ * untouched so the next call will retry the full accumulation.
25
+ */
26
+
27
+ import { createHash } from "crypto";
28
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
29
+ import { hostname } from "os";
30
+ import { join } from "path";
31
+
32
+ import { readStats } from "../servers/_stats.ts";
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Paths
36
+ // ---------------------------------------------------------------------------
37
+
38
+ function ashlrDir(): string {
39
+ return join(process.env.HOME ?? process.env.USERPROFILE ?? "~", ".ashlr");
40
+ }
41
+
42
+ export function cursorPath(): string {
43
+ return join(ashlrDir(), "stats-sync-cursor.json");
44
+ }
45
+
46
+ export function proTokenPath(): string {
47
+ return join(ashlrDir(), "pro-token");
48
+ }
49
+
50
+ /**
51
+ * Public-leaderboard opt-in from ~/.ashlr/config.json { "leaderboard": ... }.
52
+ * Returns true ("public"), false ("off"), or undefined (unset → server keeps
53
+ * the user's current setting). Default is therefore OFF.
54
+ */
55
+ export function readLeaderboardOptIn(): boolean | undefined {
56
+ try {
57
+ const cfg = JSON.parse(readFileSync(join(ashlrDir(), "config.json"), "utf-8")) as {
58
+ leaderboard?: unknown;
59
+ };
60
+ if (cfg.leaderboard === "public") return true;
61
+ if (cfg.leaderboard === "off") return false;
62
+ return undefined;
63
+ } catch {
64
+ return undefined;
65
+ }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Pro-token resolution
70
+ // Priority: ASHLR_PRO_TOKEN env var → ~/.ashlr/pro-token file
71
+ // ---------------------------------------------------------------------------
72
+
73
+ export function resolveProToken(): string | null {
74
+ const env = process.env["ASHLR_PRO_TOKEN"];
75
+ if (env && env.trim().length > 0) return env.trim();
76
+ try {
77
+ const p = proTokenPath();
78
+ if (!existsSync(p)) return null;
79
+ const tok = readFileSync(p, "utf-8").trim();
80
+ return tok.length > 0 ? tok : null;
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // machineId — SHA-256 of hostname, first 16 hex chars.
88
+ // Stable per machine; one-way so the server never sees the raw hostname.
89
+ // ---------------------------------------------------------------------------
90
+
91
+ export function machineId(): string {
92
+ return createHash("sha256").update(hostname()).digest("hex").slice(0, 16);
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Cursor
97
+ // ---------------------------------------------------------------------------
98
+
99
+ interface SyncCursor {
100
+ syncedAt: string;
101
+ calls: number;
102
+ tokensSaved: number;
103
+ }
104
+
105
+ export function readCursor(): SyncCursor | null {
106
+ try {
107
+ const raw = readFileSync(cursorPath(), "utf-8");
108
+ const parsed = JSON.parse(raw) as Partial<SyncCursor>;
109
+ if (
110
+ typeof parsed.syncedAt === "string" &&
111
+ typeof parsed.calls === "number" &&
112
+ typeof parsed.tokensSaved === "number"
113
+ ) {
114
+ return parsed as SyncCursor;
115
+ }
116
+ } catch {
117
+ /* missing or corrupt — treat as first sync */
118
+ }
119
+ return null;
120
+ }
121
+
122
+ function writeCursor(cursor: SyncCursor): void {
123
+ try {
124
+ mkdirSync(ashlrDir(), { recursive: true });
125
+ writeFileSync(cursorPath(), JSON.stringify(cursor));
126
+ } catch {
127
+ /* best-effort */
128
+ }
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Core push logic — exported so tests can call it directly
133
+ // ---------------------------------------------------------------------------
134
+
135
+ export interface SyncResult {
136
+ skipped: boolean;
137
+ reason?: string;
138
+ ok?: boolean;
139
+ status?: number;
140
+ error?: string;
141
+ }
142
+
143
+ export async function pushStatsToCloud(opts: {
144
+ apiUrl?: string;
145
+ proToken?: string;
146
+ }): Promise<SyncResult> {
147
+ const token = opts.proToken ?? resolveProToken();
148
+ if (!token) return { skipped: true, reason: "no-pro-token" };
149
+ if (process.env["ASHLR_STATS_UPLOAD"] === "0") {
150
+ return { skipped: true, reason: "kill-switch" };
151
+ }
152
+
153
+ const apiUrl = opts.apiUrl ?? process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
154
+
155
+ let stats: Awaited<ReturnType<typeof readStats>>;
156
+ try {
157
+ stats = await readStats();
158
+ } catch {
159
+ return { skipped: false, error: "read-stats-failed" };
160
+ }
161
+
162
+ const cursor = readCursor();
163
+
164
+ // Skip if nothing has changed since last sync.
165
+ if (
166
+ cursor &&
167
+ cursor.calls === stats.lifetime.calls &&
168
+ cursor.tokensSaved === stats.lifetime.tokensSaved
169
+ ) {
170
+ return { skipped: true, reason: "no-delta" };
171
+ }
172
+
173
+ // Build a pure-counts payload — byTool and byDay collapse to tokensSaved-only
174
+ // maps to match the server's ByToolSchema / ByDaySchema.
175
+ const byTool: Record<string, number> = {};
176
+ for (const [k, v] of Object.entries(stats.lifetime.byTool)) {
177
+ byTool[k] = v.tokensSaved;
178
+ }
179
+ const byDay: Record<string, number> = {};
180
+ for (const [k, v] of Object.entries(stats.lifetime.byDay)) {
181
+ byDay[k] = v.tokensSaved;
182
+ }
183
+
184
+ const leaderboardOptIn = readLeaderboardOptIn();
185
+
186
+ const body = {
187
+ apiToken: token,
188
+ stats: {
189
+ lifetime: {
190
+ calls: stats.lifetime.calls,
191
+ tokensSaved: stats.lifetime.tokensSaved,
192
+ byTool,
193
+ byDay,
194
+ },
195
+ },
196
+ // machineId is informational — the server deduplicates on apiToken+machineId.
197
+ machineId: machineId(),
198
+ // Public-leaderboard opt-in — sent only when explicitly set (else the
199
+ // server leaves the user's current setting unchanged). Default OFF.
200
+ ...(leaderboardOptIn !== undefined ? { leaderboard_opt_in: leaderboardOptIn } : {}),
201
+ };
202
+
203
+ try {
204
+ const res = await fetch(`${apiUrl}/stats/sync`, {
205
+ method: "POST",
206
+ headers: { "Content-Type": "application/json" },
207
+ body: JSON.stringify(body),
208
+ signal: AbortSignal.timeout(10_000),
209
+ });
210
+
211
+ if (res.ok) {
212
+ // Persist cursor only on success. Failures leave cursor untouched → retry.
213
+ writeCursor({
214
+ syncedAt: new Date().toISOString(),
215
+ calls: stats.lifetime.calls,
216
+ tokensSaved: stats.lifetime.tokensSaved,
217
+ });
218
+ return { skipped: false, ok: true, status: res.status };
219
+ }
220
+ return { skipped: false, ok: false, status: res.status, error: `http-${res.status}` };
221
+ } catch (e) {
222
+ return { skipped: false, ok: false, error: e instanceof Error ? e.message : "network-error" };
223
+ }
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Fire-and-forget entry point — called from session hooks
228
+ // ---------------------------------------------------------------------------
229
+
230
+ /**
231
+ * Best-effort cloud sync. Gated on Pro token presence. Never throws, never
232
+ * blocks the caller. Rate-limited: at most once per 5 minutes (tracked via
233
+ * cursor syncedAt). For use at SessionEnd to flush the final stats.
234
+ */
235
+ export function maybeCloudSync(): void {
236
+ const token = resolveProToken();
237
+ if (!token) return;
238
+ if (process.env["ASHLR_STATS_UPLOAD"] === "0") return;
239
+
240
+ // Rate-limit: if cursor is recent (< 5 min), skip.
241
+ const cursor = readCursor();
242
+ if (cursor) {
243
+ const age = Date.now() - new Date(cursor.syncedAt).getTime();
244
+ if (age < 5 * 60 * 1000) return;
245
+ }
246
+
247
+ // Detach — never await this
248
+ void pushStatsToCloud({ proToken: token });
249
+ }
250
+
251
+ if (import.meta.main) {
252
+ const result = await pushStatsToCloud({});
253
+ if (result.skipped) {
254
+ process.stderr.write(`[ashlr:stats-sync] skipped (${result.reason})\n`);
255
+ } else if (result.ok) {
256
+ process.stderr.write(`[ashlr:stats-sync] pushed ok (HTTP ${result.status})\n`);
257
+ } else {
258
+ process.stderr.write(`[ashlr:stats-sync] failed: ${result.error}\n`);
259
+ }
260
+ process.exit(0);
261
+ }
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * telemetry-flush.ts — Read the local telemetry buffer and POST to the
4
+ * maintainer's collection endpoint.
5
+ *
6
+ * Usage:
7
+ * bun run scripts/telemetry-flush.ts # manual flush
8
+ * (also called from session-end hook and hourly background task)
9
+ *
10
+ * Endpoint: ASHLR_TELEMETRY_URL env (default: https://telemetry.ashlr.ai/v1/events)
11
+ *
12
+ * POST shape:
13
+ * { sessionId: <hashed-string>, events: TelemetryRecord[] }
14
+ *
15
+ * Response shape (2xx):
16
+ * { accepted: number }
17
+ *
18
+ * On success: truncate buffer to entries newer than the flush horizon.
19
+ * On error: leave buffer untouched, log to stderr (best-effort), retry next cycle.
20
+ *
21
+ * Privacy: only anonymized, bucketed event shapes are sent. No file paths,
22
+ * no patterns, no content. See docs/telemetry.md for the full contract.
23
+ *
24
+ * Design rules:
25
+ * - Never throws from main(). All errors are swallowed.
26
+ * - Never blocks tool functionality.
27
+ * - 10s network timeout to prevent indefinite hangs.
28
+ */
29
+
30
+ import { existsSync, readFileSync } from "fs";
31
+ import { homedir } from "os";
32
+ import { join } from "path";
33
+ import {
34
+ isTelemetryEnabled,
35
+ readTelemetryBuffer,
36
+ truncateTelemetryBuffer,
37
+ getOrCreateTelemetrySessionId,
38
+ looksLikePath,
39
+ type TelemetryRecord,
40
+ } from "../servers/_telemetry";
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Config
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const DEFAULT_ENDPOINT = "https://telemetry.ashlr.ai/v1/events";
47
+ const FLUSH_TIMEOUT_MS = 10_000;
48
+ const MAX_EVENTS_PER_FLUSH = 500; // avoid huge payloads
49
+
50
+ function home(): string {
51
+ return process.env.HOME ?? homedir();
52
+ }
53
+
54
+ function endpoint(): string {
55
+ return (process.env.ASHLR_TELEMETRY_URL ?? DEFAULT_ENDPOINT).trim();
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Safety guard: strip any record with path-like strings before sending
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function isSafeRecord(r: TelemetryRecord): boolean {
63
+ for (const v of Object.values(r)) {
64
+ if (typeof v === "string" && looksLikePath(v)) return false;
65
+ }
66
+ return true;
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Flush
71
+ // ---------------------------------------------------------------------------
72
+
73
+ interface FlushResult {
74
+ skipped?: string;
75
+ sent?: number;
76
+ error?: string;
77
+ ok: boolean;
78
+ }
79
+
80
+ export async function flush(homeDir: string = home()): Promise<FlushResult> {
81
+ if (!isTelemetryEnabled(homeDir)) {
82
+ return { ok: true, skipped: "telemetry-off" };
83
+ }
84
+
85
+ const records = readTelemetryBuffer(homeDir);
86
+ if (records.length === 0) {
87
+ return { ok: true, skipped: "empty-buffer" };
88
+ }
89
+
90
+ // Batch: take the oldest MAX_EVENTS_PER_FLUSH records.
91
+ const batch = records.slice(0, MAX_EVENTS_PER_FLUSH).filter(isSafeRecord);
92
+ if (batch.length === 0) {
93
+ return { ok: true, skipped: "no-safe-records" };
94
+ }
95
+
96
+ // Flush horizon: the max ts in this batch. After a successful POST we drop
97
+ // everything <= this horizon so re-flush never double-counts.
98
+ const horizonTs = Math.max(...batch.map((r) => r.ts));
99
+
100
+ const sessionId = getOrCreateTelemetrySessionId(homeDir);
101
+
102
+ const body = JSON.stringify({ sessionId, events: batch });
103
+
104
+ // 10-second timeout via AbortController.
105
+ const controller = new AbortController();
106
+ const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
107
+
108
+ try {
109
+ const res = await fetch(endpoint(), {
110
+ method: "POST",
111
+ headers: { "Content-Type": "application/json" },
112
+ body,
113
+ signal: controller.signal,
114
+ });
115
+
116
+ clearTimeout(timeout);
117
+
118
+ if (res.ok) {
119
+ // Success: prune flushed entries from buffer.
120
+ truncateTelemetryBuffer(horizonTs, homeDir);
121
+ return { ok: true, sent: batch.length };
122
+ } else {
123
+ // Non-2xx: leave buffer alone, log, retry next cycle.
124
+ return {
125
+ ok: false,
126
+ error: `HTTP ${res.status}: ${res.statusText}`,
127
+ };
128
+ }
129
+ } catch (err) {
130
+ clearTimeout(timeout);
131
+ const msg = err instanceof Error ? err.message : String(err);
132
+ return { ok: false, error: msg };
133
+ }
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // CLI entry point
138
+ // ---------------------------------------------------------------------------
139
+
140
+ async function main(): Promise<void> {
141
+ const result = await flush();
142
+ if (result.skipped) {
143
+ process.stderr.write(`[ashlr-telemetry] flush skipped: ${result.skipped}\n`);
144
+ return;
145
+ }
146
+ if (result.ok) {
147
+ process.stderr.write(`[ashlr-telemetry] flushed ${result.sent ?? 0} events\n`);
148
+ } else {
149
+ // Silent drop on error — never surface to the user.
150
+ process.stderr.write(`[ashlr-telemetry] flush failed (will retry): ${result.error ?? "unknown"}\n`);
151
+ }
152
+ }
153
+
154
+ if (import.meta.main) {
155
+ await main().catch(() => {
156
+ // Final safety net — main() itself should never throw, but just in case.
157
+ process.exit(0);
158
+ });
159
+ process.exit(0);
160
+ }
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * telemetry-status.ts — Telemetry snapshot for /ashlr-status --telemetry.
4
+ *
5
+ * Reports:
6
+ * - LLM provider that would be selected right now
7
+ * - Embedding cache: total entries + last-100 hit rate
8
+ * - Genome: sections present + last-50 grep fire-rate
9
+ * - Block→ashlr conversion ratio (24h rolling)
10
+ *
11
+ * Contract: always exit 0. No external dependencies beyond the ashlr data
12
+ * files already present on disk. Falls back to "—" for any missing data.
13
+ */
14
+
15
+ import { existsSync, readFileSync, readdirSync } from "fs";
16
+ import { homedir } from "os";
17
+ import { join } from "path";
18
+ import { isTelemetryEnabled, readTelemetryBuffer } from "../servers/_telemetry";
19
+ import { readHookTimingsDetailed } from "./hook-timing-reader.ts";
20
+
21
+ const HOME = process.env.HOME ?? homedir();
22
+ const ASHLR_DIR = join(HOME, ".ashlr");
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // LLM provider detection
26
+ // ---------------------------------------------------------------------------
27
+
28
+ function detectLlmProvider(): string {
29
+ // Anthropic: ANTHROPIC_API_KEY set in env, or ~/.claude/credentials.json
30
+ if (process.env.ANTHROPIC_API_KEY) {
31
+ return "anthropic (ANTHROPIC_API_KEY set)";
32
+ }
33
+ try {
34
+ const credPath = join(HOME, ".claude", "credentials.json");
35
+ if (existsSync(credPath)) {
36
+ const creds = JSON.parse(readFileSync(credPath, "utf-8")) as Record<string, unknown>;
37
+ if (creds.anthropicApiKey || creds.ANTHROPIC_API_KEY) {
38
+ return "anthropic (credentials.json)";
39
+ }
40
+ }
41
+ } catch {
42
+ // ignore
43
+ }
44
+
45
+ // ONNX: check for onnxruntime-node availability
46
+ try {
47
+ require.resolve("onnxruntime-node");
48
+ return "onnx (bundled runtime)";
49
+ } catch {
50
+ // not available
51
+ }
52
+
53
+ // Local: check for Ollama or LM Studio
54
+ const ollamaUrl = process.env.ASHLR_EMBED_URL ?? "http://localhost:11434";
55
+ if (ollamaUrl.includes("11434")) {
56
+ return "local/ollama (if running) → snipCompact fallback";
57
+ }
58
+
59
+ return "snipCompact-only (no LLM provider configured)";
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Embedding cache stats
64
+ // ---------------------------------------------------------------------------
65
+
66
+ function readEmbedCacheStats(): { totalEntries: number; last100HitRate: string } {
67
+ let totalEntries = 0;
68
+ let last100HitRate = "—";
69
+
70
+ // Total entries from embed-calibration.jsonl (each line = one lookup)
71
+ try {
72
+ const calPath = join(ASHLR_DIR, "embed-calibration.jsonl");
73
+ if (existsSync(calPath)) {
74
+ const lines = readFileSync(calPath, "utf-8")
75
+ .split("\n")
76
+ .filter((l) => l.trim());
77
+ totalEntries = lines.length;
78
+
79
+ // Last 100 calls hit rate
80
+ const last100 = lines.slice(-100);
81
+ if (last100.length > 0) {
82
+ let hits = 0;
83
+ for (const line of last100) {
84
+ try {
85
+ const r = JSON.parse(line) as { hit?: boolean };
86
+ if (r.hit === true) hits++;
87
+ } catch {
88
+ // skip
89
+ }
90
+ }
91
+ const rate = Math.round((hits / last100.length) * 100);
92
+ last100HitRate = `${rate}%`;
93
+ }
94
+ }
95
+ } catch {
96
+ // ignore
97
+ }
98
+
99
+ return { totalEntries, last100HitRate };
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Genome stats
104
+ // ---------------------------------------------------------------------------
105
+
106
+ function readGenomeStats(): { sections: string; fireRate: string } {
107
+ // Count sections in .ashlrcode/genome/ relative to cwd
108
+ let sections = "—";
109
+ const genomePath = join(process.cwd(), ".ashlrcode", "genome");
110
+ if (existsSync(genomePath)) {
111
+ try {
112
+ // Count .md files in the genome directory
113
+ const files = readdirSync(genomePath, { withFileTypes: true });
114
+ const mdCount = files.filter((f) => f.isFile() && f.name.endsWith(".md")).length;
115
+ sections = `${mdCount}`;
116
+ } catch {
117
+ sections = "present (count unavailable)";
118
+ }
119
+ } else {
120
+ sections = "0 (no genome — run /ashlr-genome-init)";
121
+ }
122
+
123
+ // Fire rate from last 50 ashlr__grep calls in session-log.jsonl
124
+ let fireRate = "—";
125
+ try {
126
+ const logPath = join(ASHLR_DIR, "session-log.jsonl");
127
+ if (existsSync(logPath)) {
128
+ const lines = readFileSync(logPath, "utf-8")
129
+ .split("\n")
130
+ .filter((l) => l.trim());
131
+
132
+ // Find last 50 ashlr__grep tool_call events + genome_route_taken events
133
+ const grepRelated = lines
134
+ .map((l) => {
135
+ try { return JSON.parse(l) as { event?: string; tool?: string }; }
136
+ catch { return null; }
137
+ })
138
+ .filter((r): r is { event: string; tool?: string } =>
139
+ r !== null && typeof r.event === "string" &&
140
+ (r.tool === "ashlr__grep" || r.event === "genome_route_taken")
141
+ )
142
+ .slice(-100); // look at recent history
143
+
144
+ // Count total grep calls and genome-routed ones in last 50 grep-related events
145
+ const last50 = grepRelated.slice(-50);
146
+ const totalGreps = last50.filter((r) => r.tool === "ashlr__grep").length;
147
+ const genomeRouted = last50.filter((r) => r.event === "genome_route_taken").length;
148
+
149
+ if (totalGreps > 0) {
150
+ const rate = Math.round((genomeRouted / totalGreps) * 100);
151
+ fireRate = `${rate}%`;
152
+ } else if (grepRelated.length > 0) {
153
+ fireRate = "0% (no recent grep calls)";
154
+ }
155
+ }
156
+ } catch {
157
+ // ignore
158
+ }
159
+
160
+ return { sections, fireRate };
161
+ }
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Block→ashlr conversion ratio (24h)
165
+ // ---------------------------------------------------------------------------
166
+
167
+ export function readConversionRatio(
168
+ homeDir: string = HOME,
169
+ nowMs: number = Date.now(),
170
+ ): string {
171
+ const windowMs = 24 * 60 * 60 * 1000;
172
+ const cutoff = nowMs - windowMs;
173
+ let blocks = 0;
174
+ let converted = 0;
175
+
176
+ // Blocks from hook-timings.jsonl (outcome === "block")
177
+ const timingRead = readHookTimingsDetailed({
178
+ path: join(homeDir, ".ashlr", "hook-timings.jsonl"),
179
+ sinceMs: cutoff,
180
+ });
181
+ for (const r of timingRead.rows) {
182
+ const ts = typeof r.ts === "string" ? Date.parse(r.ts) : 0;
183
+ if (Number.isFinite(ts) && ts >= cutoff && r.outcome === "block") blocks++;
184
+ }
185
+
186
+ // Conversions from session-log.jsonl (event === "tool_called_after_block")
187
+ try {
188
+ const logPath = join(homeDir, ".ashlr", "session-log.jsonl");
189
+ if (existsSync(logPath)) {
190
+ const lines = readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim());
191
+ for (const line of lines) {
192
+ try {
193
+ const r = JSON.parse(line) as { ts?: string; event?: string };
194
+ const ts = typeof r.ts === "string" ? Date.parse(r.ts) : 0;
195
+ if (Number.isFinite(ts) && ts >= cutoff && r.event === "tool_called_after_block") {
196
+ converted++;
197
+ }
198
+ } catch {
199
+ // skip
200
+ }
201
+ }
202
+ }
203
+ } catch {
204
+ // ignore
205
+ }
206
+
207
+ if (timingRead.coverage === "partial") {
208
+ return "— unavailable (partial hook-timing coverage; 24h)";
209
+ }
210
+ if (blocks === 0 && converted === 0) return "— (no data yet)";
211
+ const rate = blocks > 0 ? Math.round((converted / blocks) * 100) : 0;
212
+ return `${blocks} blocks / ${converted} converted = ${rate}% (24h)`;
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Main
217
+ // ---------------------------------------------------------------------------
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // Opt-in telemetry status
221
+ // ---------------------------------------------------------------------------
222
+
223
+ function readOptInTelemetryStatus(): string {
224
+ const enabled = isTelemetryEnabled(HOME);
225
+ if (!enabled) {
226
+ return "OFF (default) · to enable: ASHLR_TELEMETRY=on or config.json { \"telemetry\": \"opt-in\" }";
227
+ }
228
+ // Count buffered events.
229
+ let bufferCount = 0;
230
+ try {
231
+ const records = readTelemetryBuffer(HOME);
232
+ bufferCount = records.length;
233
+ } catch {
234
+ /* ignore */
235
+ }
236
+ return `ON · buffer: ${bufferCount} events · to disable: ASHLR_TELEMETRY=off`;
237
+ }
238
+
239
+ async function main(): Promise<void> {
240
+ const provider = detectLlmProvider();
241
+ const { totalEntries, last100HitRate } = readEmbedCacheStats();
242
+ const { sections, fireRate } = readGenomeStats();
243
+ const conversionRatio = readConversionRatio();
244
+ const optInStatus = readOptInTelemetryStatus();
245
+
246
+ const embedStr = totalEntries > 0
247
+ ? `${totalEntries.toLocaleString()} entries · last-100 hit rate ${last100HitRate}`
248
+ : `0 entries · ${last100HitRate}`;
249
+
250
+ const genomeStr = `${sections} sections · last-50 grep fire-rate ${fireRate}`;
251
+
252
+ process.stdout.write([
253
+ "## Telemetry snapshot",
254
+ ` llm-provider: ${provider}`,
255
+ ` embed-cache: ${embedStr}`,
256
+ ` genome: ${genomeStr}`,
257
+ ` block→ashlr: ${conversionRatio}`,
258
+ ` opt-in telemetry: ${optInStatus}`,
259
+ "",
260
+ ].join("\n"));
261
+ }
262
+
263
+ if (import.meta.main) {
264
+ await main();
265
+ process.exit(0);
266
+ }