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,313 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * cloud-smoke-test.ts — Automated smoke runner for the ashlr cloud backend.
4
+ *
5
+ * Reads:
6
+ * ASHLR_API_URL (default: https://api.ashlr.ai)
7
+ * ASHLR_PRO_TOKEN (optional — Pro-gated checks skip when absent)
8
+ *
9
+ * Runs 5 checks matching server/docs/cloud-smoke-tests.md:
10
+ * 1. /healthz + /readyz reachable
11
+ * 2. POST /v1/events round-trip
12
+ * 3. (Pro) GET /user/me Bearer validation
13
+ * 4. (Pro) POST /llm/summarize round-trip
14
+ * 5. (Pro) GET /stats/aggregate shape
15
+ *
16
+ * Each result: { name, status: "pass"|"fail"|"skip", ms, detail? }
17
+ * Exit 0 if all pass/skip. Exit 1 if any fail.
18
+ *
19
+ * CI usage:
20
+ * ASHLR_API_URL=https://staging.ashlr.ai bun run scripts/cloud-smoke-test.ts
21
+ */
22
+
23
+ export interface CheckResult {
24
+ name: string;
25
+ status: "pass" | "fail" | "skip";
26
+ ms: number;
27
+ detail?: string;
28
+ }
29
+
30
+ export interface SmokeConfig {
31
+ apiUrl: string;
32
+ proToken?: string;
33
+ /** Override fetch for testing */
34
+ fetchImpl?: typeof fetch;
35
+ /** Suppress console output (used in tests) */
36
+ silent?: boolean;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Internal helpers
41
+ // ---------------------------------------------------------------------------
42
+
43
+ async function timed<T>(fn: () => Promise<T>): Promise<{ result: T; ms: number }> {
44
+ const start = Date.now();
45
+ const result = await fn();
46
+ return { result, ms: Date.now() - start };
47
+ }
48
+
49
+ function skip(name: string): CheckResult {
50
+ return { name, status: "skip", ms: 0 };
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Check 1 — /healthz + /readyz
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export async function checkHealth(cfg: SmokeConfig): Promise<CheckResult> {
58
+ const name = "healthz+readyz";
59
+ const f = cfg.fetchImpl ?? fetch;
60
+
61
+ const { ms } = await timed(async () => {
62
+ for (const path of ["/healthz", "/readyz"]) {
63
+ const res = await f(`${cfg.apiUrl}${path}`, { signal: AbortSignal.timeout(5000) });
64
+ if (!res.ok) throw new Error(`${path} returned ${res.status}`);
65
+ // Liveness/readiness contract is HTTP 200, not a specific body shape.
66
+ // The healthz endpoint returns { status, version, uptimeSeconds }; the
67
+ // readyz endpoint returns { db, checks: {...} }. Accept anything that
68
+ // parses as JSON.
69
+ await res.json();
70
+ }
71
+ }).catch((err: unknown) => {
72
+ return { ms: 0, error: String(err) };
73
+ });
74
+
75
+ // Re-run cleanly to capture ms properly
76
+ let ms2 = 0;
77
+ let detail: string | undefined;
78
+ try {
79
+ const t = await timed(async () => {
80
+ for (const path of ["/healthz", "/readyz"]) {
81
+ const res = await f(`${cfg.apiUrl}${path}`, { signal: AbortSignal.timeout(5000) });
82
+ if (!res.ok) throw new Error(`${path} returned ${res.status}`);
83
+ await res.json();
84
+ }
85
+ });
86
+ ms2 = t.ms;
87
+ } catch (err: unknown) {
88
+ detail = String(err);
89
+ return { name, status: "fail", ms: ms2, detail };
90
+ }
91
+ void ms;
92
+ return { name, status: "pass", ms: ms2 };
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Check 2 — POST /v1/events round-trip
97
+ // ---------------------------------------------------------------------------
98
+
99
+ export async function checkTelemetry(cfg: SmokeConfig): Promise<CheckResult> {
100
+ const name = "POST /v1/events";
101
+ const f = cfg.fetchImpl ?? fetch;
102
+
103
+ let ms = 0;
104
+ try {
105
+ const t = await timed(async () => {
106
+ const res = await f(`${cfg.apiUrl}/v1/events`, {
107
+ method: "POST",
108
+ headers: { "content-type": "application/json" },
109
+ body: JSON.stringify({
110
+ sessionId: "smoke0000000000ff",
111
+ events: [
112
+ {
113
+ ts: Math.floor(Date.now() / 1000),
114
+ kind: "version",
115
+ sessionId: "smoke0000000000ff",
116
+ pluginVersion: "1.27.0",
117
+ bunVersion: "1.0.0",
118
+ platform: "darwin",
119
+ arch: "arm64",
120
+ },
121
+ ],
122
+ }),
123
+ signal: AbortSignal.timeout(5000),
124
+ });
125
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
126
+ const body = await res.json() as Record<string, unknown>;
127
+ if (typeof body["accepted"] !== "number") {
128
+ throw new Error(`expected numeric 'accepted', got: ${JSON.stringify(body)}`);
129
+ }
130
+ });
131
+ ms = t.ms;
132
+ } catch (err: unknown) {
133
+ return { name, status: "fail", ms, detail: String(err) };
134
+ }
135
+ return { name, status: "pass", ms };
136
+ }
137
+
138
+ // ---------------------------------------------------------------------------
139
+ // Check 3 — GET /user/me (Pro)
140
+ // ---------------------------------------------------------------------------
141
+
142
+ export async function checkUserMe(cfg: SmokeConfig): Promise<CheckResult> {
143
+ const name = "GET /user/me";
144
+ if (!cfg.proToken) return skip(name);
145
+ const f = cfg.fetchImpl ?? fetch;
146
+
147
+ let ms = 0;
148
+ try {
149
+ const t = await timed(async () => {
150
+ const res = await f(`${cfg.apiUrl}/user/me`, {
151
+ headers: { Authorization: `Bearer ${cfg.proToken}` },
152
+ signal: AbortSignal.timeout(5000),
153
+ });
154
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
155
+ const body = await res.json() as Record<string, unknown>;
156
+ if (!body["userId"] || !body["tier"]) {
157
+ throw new Error(`missing userId/tier in response: ${JSON.stringify(body)}`);
158
+ }
159
+ });
160
+ ms = t.ms;
161
+ } catch (err: unknown) {
162
+ return { name, status: "fail", ms, detail: String(err) };
163
+ }
164
+ return { name, status: "pass", ms };
165
+ }
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Check 4 — POST /llm/summarize (Pro)
169
+ // ---------------------------------------------------------------------------
170
+
171
+ export async function checkLlmSummarize(cfg: SmokeConfig): Promise<CheckResult> {
172
+ const name = "POST /llm/summarize";
173
+ if (!cfg.proToken) return skip(name);
174
+ const f = cfg.fetchImpl ?? fetch;
175
+
176
+ let ms = 0;
177
+ try {
178
+ const t = await timed(async () => {
179
+ const res = await f(`${cfg.apiUrl}/llm/summarize`, {
180
+ method: "POST",
181
+ headers: {
182
+ Authorization: `Bearer ${cfg.proToken}`,
183
+ "content-type": "application/json",
184
+ },
185
+ body: JSON.stringify({
186
+ text: "The quick brown fox jumps over the lazy dog. ".repeat(10),
187
+ systemPrompt: "Summarize in one sentence.",
188
+ toolName: "ashlr__read",
189
+ maxTokens: 80,
190
+ }),
191
+ signal: AbortSignal.timeout(30000),
192
+ });
193
+ if (res.status === 429) {
194
+ // Rate-limited or capped — not a failure; check code field
195
+ const body = await res.json() as Record<string, unknown>;
196
+ const code = body["code"];
197
+ if (!["rate_limit", "daily_cap", "cost_cap"].includes(String(code))) {
198
+ throw new Error(`unexpected 429 code: ${code}`);
199
+ }
200
+ // Treat expected cap codes as pass (system working correctly)
201
+ return;
202
+ }
203
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
204
+ const body = await res.json() as Record<string, unknown>;
205
+ if (!body["summary"] || !body["modelUsed"]) {
206
+ throw new Error(`missing summary/modelUsed: ${JSON.stringify(body)}`);
207
+ }
208
+ });
209
+ ms = t.ms;
210
+ } catch (err: unknown) {
211
+ return { name, status: "fail", ms, detail: String(err) };
212
+ }
213
+ return { name, status: "pass", ms };
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Check 5 — GET /stats/aggregate (Pro)
218
+ // ---------------------------------------------------------------------------
219
+
220
+ export async function checkStatsAggregate(cfg: SmokeConfig): Promise<CheckResult> {
221
+ const name = "GET /stats/aggregate";
222
+ if (!cfg.proToken) return skip(name);
223
+ const f = cfg.fetchImpl ?? fetch;
224
+
225
+ let ms = 0;
226
+ try {
227
+ const t = await timed(async () => {
228
+ const res = await f(`${cfg.apiUrl}/stats/aggregate`, {
229
+ headers: { Authorization: `Bearer ${cfg.proToken}` },
230
+ signal: AbortSignal.timeout(5000),
231
+ });
232
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
233
+ const body = await res.json() as Record<string, unknown>;
234
+ if (typeof body["machine_count"] !== "number") {
235
+ throw new Error(`missing numeric machine_count: ${JSON.stringify(body)}`);
236
+ }
237
+ });
238
+ ms = t.ms;
239
+ } catch (err: unknown) {
240
+ return { name, status: "fail", ms, detail: String(err) };
241
+ }
242
+ return { name, status: "pass", ms };
243
+ }
244
+
245
+ // ---------------------------------------------------------------------------
246
+ // Run all checks
247
+ // ---------------------------------------------------------------------------
248
+
249
+ export async function runAll(cfg: SmokeConfig): Promise<CheckResult[]> {
250
+ // Health must pass before we bother with the rest
251
+ const health = await checkHealth(cfg);
252
+ const results: CheckResult[] = [health];
253
+
254
+ if (health.status === "fail") {
255
+ // Skip remaining checks — server is unreachable
256
+ for (const name of [
257
+ "POST /v1/events",
258
+ "GET /user/me",
259
+ "POST /llm/summarize",
260
+ "GET /stats/aggregate",
261
+ ]) {
262
+ results.push({ name, status: "skip", ms: 0, detail: "skipped: healthz failed" });
263
+ }
264
+ return results;
265
+ }
266
+
267
+ results.push(
268
+ await checkTelemetry(cfg),
269
+ await checkUserMe(cfg),
270
+ await checkLlmSummarize(cfg),
271
+ await checkStatsAggregate(cfg),
272
+ );
273
+ return results;
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Formatting
278
+ // ---------------------------------------------------------------------------
279
+
280
+ export function formatResults(results: CheckResult[]): string {
281
+ const lines: string[] = ["", "ashlr cloud smoke test", "─".repeat(40)];
282
+ for (const r of results) {
283
+ const icon = r.status === "pass" ? "✓" : r.status === "skip" ? "–" : "✗";
284
+ const ms = r.status !== "skip" ? ` ${r.ms}ms` : "";
285
+ const detail = r.detail ? ` (${r.detail})` : "";
286
+ lines.push(` ${icon} ${r.name}${ms}${detail}`);
287
+ }
288
+ lines.push("─".repeat(40));
289
+
290
+ const passed = results.filter((r) => r.status === "pass").length;
291
+ const failed = results.filter((r) => r.status === "fail").length;
292
+ const skipped = results.filter((r) => r.status === "skip").length;
293
+ lines.push(` ${passed} passed · ${failed} failed · ${skipped} skipped`);
294
+ lines.push("");
295
+ return lines.join("\n");
296
+ }
297
+
298
+ // ---------------------------------------------------------------------------
299
+ // Main
300
+ // ---------------------------------------------------------------------------
301
+
302
+ if (import.meta.main) {
303
+ const apiUrl = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
304
+ const proToken = process.env["ASHLR_PRO_TOKEN"];
305
+
306
+ const cfg: SmokeConfig = { apiUrl, proToken };
307
+ const results = await runAll(cfg);
308
+
309
+ process.stdout.write(formatResults(results));
310
+
311
+ const anyFail = results.some((r) => r.status === "fail");
312
+ process.exit(anyFail ? 1 : 0);
313
+ }
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ashlr coach report — reads ~/.ashlr/session-log.jsonl (+ rotated .1)
4
+ * and emits proactive nudges about tokens that could have been saved.
5
+ *
6
+ * Exported surface:
7
+ * buildCoachReport(opts?) -> formatted string, never throws.
8
+ *
9
+ * CLI: `bun run scripts/coach-report.ts [--days N]`
10
+ * Exits 0 always.
11
+ */
12
+
13
+ import { existsSync, readFileSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { basename, join, relative } from "path";
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Schema (matches session-log-report.ts LogRecord shape)
19
+ // ---------------------------------------------------------------------------
20
+
21
+ interface LogRecord {
22
+ ts: string;
23
+ agent: string;
24
+ event: string;
25
+ tool: string;
26
+ cwd: string;
27
+ session: string;
28
+ input_size: number;
29
+ output_size: number;
30
+ // session_end / TodoWrite extras
31
+ calls?: number;
32
+ tokens_saved?: number;
33
+ started_at?: string;
34
+ reason?: string;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Options
39
+ // ---------------------------------------------------------------------------
40
+
41
+ export interface BuildCoachReportOpts {
42
+ /** Override home dir — used by tests. */
43
+ home?: string;
44
+ /** How many days back to scan (default: 7). */
45
+ days?: number;
46
+ /** Injected now in ms — tests pin this. */
47
+ now?: number;
48
+ /** Max lines per file (default: unlimited). */
49
+ limitLines?: number;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // JSONL reader (same pattern as session-log-report.ts)
54
+ // ---------------------------------------------------------------------------
55
+
56
+ function readLines(path: string, limit?: number): string[] {
57
+ try {
58
+ if (!existsSync(path)) return [];
59
+ const raw = readFileSync(path, "utf-8");
60
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
61
+ return limit ? lines.slice(0, limit) : lines;
62
+ } catch {
63
+ return [];
64
+ }
65
+ }
66
+
67
+ function parseRecords(lines: string[]): LogRecord[] {
68
+ const out: LogRecord[] = [];
69
+ for (const line of lines) {
70
+ try {
71
+ const r = JSON.parse(line) as Partial<LogRecord>;
72
+ if (typeof r.ts !== "string" || typeof r.event !== "string") continue;
73
+ out.push({
74
+ ts: r.ts,
75
+ agent: r.agent ?? "unknown",
76
+ event: r.event,
77
+ tool: r.tool ?? "unknown",
78
+ cwd: r.cwd ?? "",
79
+ session: r.session ?? "",
80
+ input_size: typeof r.input_size === "number" ? r.input_size : 0,
81
+ output_size: typeof r.output_size === "number" ? r.output_size : 0,
82
+ calls: r.calls,
83
+ tokens_saved: r.tokens_saved,
84
+ started_at: r.started_at,
85
+ reason: r.reason,
86
+ });
87
+ } catch {
88
+ // skip malformed lines silently
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Formatting helpers
96
+ // ---------------------------------------------------------------------------
97
+
98
+ const WIDTH = 78;
99
+
100
+ function rule(): string {
101
+ return "-".repeat(WIDTH);
102
+ }
103
+
104
+ // Bullets are emitted as single lines; terminal/renderer handles soft wrap.
105
+ // This keeps toContain checks in tests unambiguous across word boundaries.
106
+ function wrap(text: string): string {
107
+ return text;
108
+ }
109
+
110
+ function fmtKtok(tokens: number): string {
111
+ if (tokens < 1000) return `${tokens}tok`;
112
+ return `${(tokens / 1000).toFixed(1)}Ktok`;
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Rule checks
117
+ // ---------------------------------------------------------------------------
118
+
119
+ interface Bullet {
120
+ text: string;
121
+ }
122
+
123
+ /**
124
+ * Rule 1: Native Read on large files.
125
+ * tool === "Read" AND input_size > 2048.
126
+ * Potential savings = sum(input_size) * 0.6 / 4 (bytes -> tokens @ 4 bytes/tok,
127
+ * 60% reduction from ashlr__read's snipCompact).
128
+ */
129
+ function checkNativeReadLarge(records: LogRecord[]): Bullet | null {
130
+ const hits = records.filter(
131
+ (r) => r.event === "tool_call" && r.tool === "Read" && r.input_size > 2048,
132
+ );
133
+ if (hits.length === 0) return null;
134
+ const totalBytes = hits.reduce((s, r) => s + r.input_size, 0);
135
+ const wastedTokens = Math.round((totalBytes * 0.6) / 4);
136
+ const n = hits.length;
137
+ return {
138
+ text: wrap(
139
+ `Used native Read on ${n} large file${n === 1 ? "" : "s"} — ` +
140
+ `~${fmtKtok(wastedTokens)} wasted. ` +
141
+ `Try /ashlr-allow or set ASHLR_ENFORCE=1.`,
142
+ ),
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Rule 2: Native Grep usage.
148
+ * tool === "Grep".
149
+ */
150
+ function checkNativeGrep(records: LogRecord[]): Bullet | null {
151
+ const hits = records.filter(
152
+ (r) => r.event === "tool_call" && r.tool === "Grep",
153
+ );
154
+ if (hits.length === 0) return null;
155
+ const n = hits.length;
156
+ return {
157
+ text: wrap(
158
+ `Native Grep fired ${n} time${n === 1 ? "" : "s"} — pays full rg output each call. ` +
159
+ `ashlr__grep averages 5x smaller.`,
160
+ ),
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Rule 3: Long Bash outputs uncompressed.
166
+ * tool === "Bash" AND output_size > 16384.
167
+ */
168
+ function checkLargeBashOutput(records: LogRecord[]): Bullet | null {
169
+ const hits = records.filter(
170
+ (r) => r.event === "tool_call" && r.tool === "Bash" && r.output_size > 16_384,
171
+ );
172
+ if (hits.length === 0) return null;
173
+ const m = hits.length;
174
+ // Approximate token class: largest output / 4
175
+ const maxOut = Math.max(...hits.map((r) => r.output_size));
176
+ const tokClass = fmtKtok(Math.round(maxOut / 4));
177
+ return {
178
+ text: wrap(
179
+ `Native Bash returned ${tokClass}-class output ${m} time${m === 1 ? "" : "s"} — ` +
180
+ `ashlr__bash auto-compresses.`,
181
+ ),
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Rule 4: No genome but heavy ashlr__grep usage.
187
+ * Find the project (cwd) with the most ashlr__grep calls.
188
+ * If that project has no .ashlrcode/genome/manifest.json, fire the bullet.
189
+ */
190
+ function checkNoGenome(records: LogRecord[]): Bullet | null {
191
+ const grepRecords = records.filter(
192
+ (r) => r.event === "tool_call" && r.tool === "ashlr__grep" && r.cwd,
193
+ );
194
+ if (grepRecords.length === 0) return null;
195
+
196
+ // Tally per-project
197
+ const byCwd = new Map<string, number>();
198
+ for (const r of grepRecords) {
199
+ byCwd.set(r.cwd, (byCwd.get(r.cwd) ?? 0) + 1);
200
+ }
201
+
202
+ // Top project by ashlr__grep calls
203
+ let topCwd = "";
204
+ let topCount = 0;
205
+ for (const [cwd, count] of byCwd) {
206
+ if (count > topCount) {
207
+ topCount = count;
208
+ topCwd = cwd;
209
+ }
210
+ }
211
+
212
+ if (topCount < 3) return null; // not "heavy" usage
213
+
214
+ const manifestPath = join(topCwd, ".ashlrcode", "genome", "manifest.json");
215
+ if (existsSync(manifestPath)) return null;
216
+
217
+ // Project display name: last path component (platform-agnostic)
218
+ const projectName = basename(topCwd) || topCwd;
219
+
220
+ return {
221
+ text: wrap(
222
+ `Project ${projectName} has ${topCount} ashlr__grep calls but no genome — ` +
223
+ `run /ashlr-genome-init for ~4x better retrieval.`,
224
+ ),
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Rule 5: Repeated reads of the same file within one session.
230
+ * Within same session field, find paths (from input parsed as file path)
231
+ * that appear >= 3 times across Read/ashlr__read tool calls.
232
+ * We use the record's cwd + a stable key derived from input_size grouping as
233
+ * a proxy — but more precisely we look for same (session, tool, cwd) groups
234
+ * where input_size repeats. However, the log doesn't store the path argument
235
+ * directly. We detect repetition by grouping on (session, tool, cwd) with
236
+ * identical input_size as a proxy for "same file".
237
+ *
238
+ * Actually the spec says "count records where a path appears >= 3 times" but
239
+ * the path isn't in the log schema. We instead track (session, cwd, tool,
240
+ * input_size) as a file fingerprint — same size + same cwd + same session
241
+ * implies the same file was re-read.
242
+ */
243
+ function checkRepeatedReads(records: LogRecord[]): Bullet | null {
244
+ const readTools = new Set(["Read", "ashlr__read"]);
245
+ const readRecords = records.filter(
246
+ (r) => r.event === "tool_call" && readTools.has(r.tool) && r.input_size > 0,
247
+ );
248
+ if (readRecords.length === 0) return null;
249
+
250
+ // Group by (session, cwd, input_size) as file fingerprint
251
+ const counts = new Map<string, number>();
252
+ for (const r of readRecords) {
253
+ const key = `${r.session}|${r.cwd}|${r.input_size}`;
254
+ counts.set(key, (counts.get(key) ?? 0) + 1);
255
+ }
256
+
257
+ // Find worst offender
258
+ let maxCount = 0;
259
+ let maxKey = "";
260
+ for (const [key, count] of counts) {
261
+ if (count > maxCount) {
262
+ maxCount = count;
263
+ maxKey = key;
264
+ }
265
+ }
266
+
267
+ if (maxCount < 3) return null;
268
+
269
+ const [, cwd, sizeStr] = maxKey.split("|");
270
+ const sizeBytes = parseInt(sizeStr ?? "0", 10);
271
+ // Use cwd basename as proxy for file location descriptor
272
+ const projectName = basename(cwd ?? "") || (cwd ?? "unknown");
273
+ const sizeDesc = sizeBytes > 1024
274
+ ? `~${(sizeBytes / 1024).toFixed(1)}K`
275
+ : `~${sizeBytes}B`;
276
+
277
+ return {
278
+ text: wrap(
279
+ `A ${sizeDesc} file in ${projectName} was re-read ${maxCount} times in one session — ` +
280
+ `the 2nd+ reads were cache hits (free) but ashlr__read's in-process cache ` +
281
+ `only fires for exact mtime matches.`,
282
+ ),
283
+ };
284
+ }
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // Public API
288
+ // ---------------------------------------------------------------------------
289
+
290
+ export function buildCoachReport(opts: BuildCoachReportOpts = {}): string {
291
+ const homeDir = opts.home ?? homedir();
292
+ const now = opts.now ?? Date.now();
293
+ const days = opts.days ?? 7;
294
+ const windowMs = days * 24 * 60 * 60 * 1000;
295
+ const windowStart = now - windowMs;
296
+
297
+ const logPath = join(homeDir, ".ashlr", "session-log.jsonl");
298
+ const rotatedPath = join(homeDir, ".ashlr", "session-log.jsonl.1");
299
+
300
+ const lines1 = readLines(rotatedPath, opts.limitLines);
301
+ const lines2 = readLines(logPath, opts.limitLines);
302
+ const allLines = [...lines1, ...lines2];
303
+
304
+ const allRecords = parseRecords(allLines);
305
+
306
+ // Filter to window
307
+ const records = allRecords.filter((r) => {
308
+ const ts = Date.parse(r.ts);
309
+ return Number.isFinite(ts) && ts >= windowStart;
310
+ });
311
+
312
+ const scanned = allRecords.length;
313
+
314
+ const header =
315
+ `ashlr coach · last ${days} day${days === 1 ? "" : "s"} · ` +
316
+ `session-log records scanned: ${scanned}`;
317
+
318
+ const bullets: Bullet[] = [];
319
+
320
+ const r1 = checkNativeReadLarge(records);
321
+ if (r1) bullets.push(r1);
322
+
323
+ const r2 = checkNativeGrep(records);
324
+ if (r2) bullets.push(r2);
325
+
326
+ const r3 = checkLargeBashOutput(records);
327
+ if (r3) bullets.push(r3);
328
+
329
+ const r4 = checkNoGenome(records);
330
+ if (r4) bullets.push(r4);
331
+
332
+ const r5 = checkRepeatedReads(records);
333
+ if (r5) bullets.push(r5);
334
+
335
+ const footer =
336
+ "Run /ashlr-usage for full tool breakdown · /ashlr-savings for totals.";
337
+
338
+ const parts: string[] = [header, rule()];
339
+
340
+ if (bullets.length === 0) {
341
+ parts.push("No obvious improvements — you're using the plugin well.");
342
+ } else {
343
+ for (const b of bullets.slice(0, 5)) {
344
+ parts.push(`- ${b.text}`);
345
+ }
346
+ }
347
+
348
+ parts.push(rule());
349
+ parts.push(footer);
350
+
351
+ return parts.join("\n");
352
+ }
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // CLI entry
356
+ // ---------------------------------------------------------------------------
357
+
358
+ if (import.meta.main) {
359
+ try {
360
+ // Parse --days N flag
361
+ const args = process.argv.slice(2);
362
+ const daysIdx = args.indexOf("--days");
363
+ const days = daysIdx !== -1 && args[daysIdx + 1]
364
+ ? parseInt(args[daysIdx + 1]!, 10)
365
+ : 7;
366
+
367
+ process.stdout.write(buildCoachReport({ days: isNaN(days) ? 7 : days }) + "\n");
368
+ } catch (err) {
369
+ const msg = err instanceof Error ? err.message : String(err);
370
+ process.stdout.write(`coach-report failed: ${msg}\n`);
371
+ }
372
+ process.exit(0);
373
+ }