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,750 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * scripts/run-benchmark.ts
4
+ *
5
+ * Reproducible benchmark runner for the ashlr-plugin token-efficiency layer.
6
+ *
7
+ * Usage:
8
+ * bun run scripts/run-benchmark.ts
9
+ * bun run scripts/run-benchmark.ts --repo /path/to/repo
10
+ * bun run scripts/run-benchmark.ts --out docs/benchmarks-v2.json
11
+ * bun run scripts/run-benchmark.ts --dry-run
12
+ *
13
+ * Measures actual byte/token reduction for ashlr__read, ashlr__grep, and
14
+ * ashlr__edit by calling the handler functions directly — no MCP layer.
15
+ */
16
+
17
+ import { readFileSync, writeFileSync, existsSync } from "fs";
18
+ import { resolve, relative } from "path";
19
+ import { spawnSync } from "child_process";
20
+
21
+ import {
22
+ estimateTokensFromString,
23
+ snipCompact,
24
+ type Message,
25
+ } from "@ashlr/core-efficiency";
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // CLI args
29
+ // ---------------------------------------------------------------------------
30
+
31
+ interface Args {
32
+ repo: string;
33
+ out: string;
34
+ dryRun: boolean;
35
+ validateTokenizer: boolean;
36
+ compare: boolean;
37
+ }
38
+
39
+ function parseArgs(argv: string[]): Args {
40
+ const args: Args = {
41
+ repo: process.cwd(),
42
+ out: resolve(process.cwd(), "docs/benchmarks-v2.json"),
43
+ dryRun: false,
44
+ validateTokenizer: false,
45
+ compare: false,
46
+ };
47
+ for (let i = 0; i < argv.length; i++) {
48
+ if (argv[i] === "--repo" && argv[i + 1]) args.repo = resolve(argv[++i]!);
49
+ else if (argv[i] === "--out" && argv[i + 1]) args.out = resolve(argv[++i]!);
50
+ else if (argv[i] === "--dry-run") args.dryRun = true;
51
+ else if (argv[i] === "--validate-tokenizer") args.validateTokenizer = true;
52
+ else if (argv[i] === "--compare") args.compare = true;
53
+ }
54
+ return args;
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Seeded PRNG (mulberry32) — deterministic from repo commit SHA
59
+ // ---------------------------------------------------------------------------
60
+
61
+ function mulberry32(seed: number): () => number {
62
+ let s = seed >>> 0;
63
+ return function () {
64
+ s += 0x6d2b79f5;
65
+ let z = s;
66
+ z = Math.imul(z ^ (z >>> 15), z | 1);
67
+ z ^= z + Math.imul(z ^ (z >>> 7), z | 61);
68
+ return ((z ^ (z >>> 14)) >>> 0) / 0x100000000;
69
+ };
70
+ }
71
+
72
+ function seedFromCommit(sha: string): number {
73
+ // fold 40-char hex SHA into a 32-bit int
74
+ let n = 0;
75
+ for (let i = 0; i < sha.length; i++) {
76
+ n = (Math.imul(n, 31) + sha.charCodeAt(i)) >>> 0;
77
+ }
78
+ return n;
79
+ }
80
+
81
+ function seededSample<T>(arr: T[], n: number, rand: () => number): T[] {
82
+ const copy = arr.slice();
83
+ const result: T[] = [];
84
+ for (let i = 0; i < n && copy.length > 0; i++) {
85
+ const idx = Math.floor(rand() * copy.length);
86
+ result.push(copy.splice(idx, 1)[0]!);
87
+ }
88
+ return result;
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Bootstrap confidence interval (deterministic seeded PRNG)
93
+ // ---------------------------------------------------------------------------
94
+
95
+ /**
96
+ * Compute 95% bootstrap CI (2.5/97.5 percentile of resampled means).
97
+ * Uses mulberry32 seeded from the data — no Math.random, fully deterministic.
98
+ */
99
+ export function bootstrapCI(ratios: number[], n = 1000): { lo: number; hi: number } {
100
+ if (ratios.length === 0) return { lo: 1, hi: 1 };
101
+ if (ratios.length === 1) return { lo: ratios[0]!, hi: ratios[0]! };
102
+
103
+ // Seed from data: fold all values into uint32
104
+ let seedVal = 0;
105
+ for (const r of ratios) {
106
+ seedVal = (Math.imul(seedVal, 31) + Math.round(r * 1e9)) >>> 0;
107
+ }
108
+ const rand = mulberry32(seedVal === 0 ? 0xdeadbeef : seedVal);
109
+
110
+ const means: number[] = new Array(n);
111
+ const len = ratios.length;
112
+ for (let i = 0; i < n; i++) {
113
+ let sum = 0;
114
+ for (let j = 0; j < len; j++) {
115
+ sum += ratios[Math.floor(rand() * len)]!;
116
+ }
117
+ means[i] = sum / len;
118
+ }
119
+ means.sort((a, b) => a - b);
120
+ return {
121
+ lo: means[Math.floor(n * 0.025)]!,
122
+ hi: means[Math.floor(n * 0.975)]!,
123
+ };
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Repo metadata
128
+ // ---------------------------------------------------------------------------
129
+
130
+ interface RepoMeta {
131
+ url: string;
132
+ commit: string;
133
+ files: number;
134
+ loc: number;
135
+ }
136
+
137
+ function gitOutput(args: string[], cwd: string): string {
138
+ const res = spawnSync("git", args, { encoding: "utf-8", cwd });
139
+ return (res.stdout ?? "").trim();
140
+ }
141
+
142
+ function getRepoMeta(repoPath: string): RepoMeta {
143
+ const commit = gitOutput(["rev-parse", "HEAD"], repoPath) || "unknown";
144
+ const url = gitOutput(["remote", "get-url", "origin"], repoPath) || "local";
145
+ const lsFiles = spawnSync("git", ["ls-files"], { encoding: "utf-8", cwd: repoPath });
146
+ const allFiles = (lsFiles.stdout ?? "")
147
+ .split("\n")
148
+ .map((f) => f.trim())
149
+ .filter(Boolean);
150
+
151
+ // Count LOC via wc -l on git-tracked files (best-effort)
152
+ let loc = 0;
153
+ try {
154
+ const wcRes = spawnSync("git", ["ls-files", "-z"], {
155
+ encoding: "buffer",
156
+ cwd: repoPath,
157
+ timeout: 10_000,
158
+ });
159
+ if (wcRes.stdout) {
160
+ const files = wcRes.stdout
161
+ .toString("utf-8")
162
+ .split("\0")
163
+ .filter(Boolean)
164
+ .map((f) => resolve(repoPath, f));
165
+ for (const f of files) {
166
+ try {
167
+ const content = readFileSync(f, "utf-8");
168
+ loc += content.split("\n").length;
169
+ } catch {
170
+ // skip unreadable files
171
+ }
172
+ }
173
+ }
174
+ } catch {
175
+ loc = allFiles.length * 30; // rough fallback
176
+ }
177
+
178
+ return { url, commit, files: allFiles.length, loc };
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // File enumeration + size-bucket sampling
183
+ // ---------------------------------------------------------------------------
184
+
185
+ type SizeBucket = "2-5KB" | "5-15KB" | "15-50KB" | "50+KB";
186
+
187
+ interface FileSample {
188
+ path: string;
189
+ bucket: SizeBucket;
190
+ bytes: number;
191
+ }
192
+
193
+ function sizeKB(bytes: number): SizeBucket {
194
+ const kb = bytes / 1024;
195
+ if (kb < 5) return "2-5KB";
196
+ if (kb < 15) return "5-15KB";
197
+ if (kb < 50) return "15-50KB";
198
+ return "50+KB";
199
+ }
200
+
201
+ function enumerateFiles(repoPath: string): FileSample[] {
202
+ const lsFiles = spawnSync("git", ["ls-files"], {
203
+ encoding: "utf-8",
204
+ cwd: repoPath,
205
+ timeout: 30_000,
206
+ });
207
+ if (lsFiles.status !== 0) {
208
+ throw new Error(
209
+ `git ls-files failed in ${repoPath}: ${lsFiles.stderr ?? "unknown error"}`,
210
+ );
211
+ }
212
+ const files = (lsFiles.stdout ?? "")
213
+ .split("\n")
214
+ .map((f) => f.trim())
215
+ .filter(Boolean);
216
+
217
+ const result: FileSample[] = [];
218
+ for (const f of files) {
219
+ const abs = resolve(repoPath, f);
220
+ try {
221
+ const content = readFileSync(abs);
222
+ const bytes = content.length;
223
+ if (bytes < 2048) continue; // snipCompact only fires on > 2KB
224
+ result.push({ path: abs, bucket: sizeKB(bytes), bytes });
225
+ } catch {
226
+ // skip locked/binary files
227
+ }
228
+ }
229
+ return result;
230
+ }
231
+
232
+ function selectSample(files: FileSample[], rand: () => number): FileSample[] {
233
+ const buckets: Record<SizeBucket, FileSample[]> = {
234
+ "2-5KB": [],
235
+ "5-15KB": [],
236
+ "15-50KB": [],
237
+ "50+KB": [],
238
+ };
239
+ for (const f of files) buckets[f.bucket].push(f);
240
+
241
+ // Pick up to 4 from each bucket, targeting ~16 total
242
+ const picked: FileSample[] = [];
243
+ for (const bucket of ["2-5KB", "5-15KB", "15-50KB", "50+KB"] as SizeBucket[]) {
244
+ const n = Math.min(4, buckets[bucket].length);
245
+ picked.push(...seededSample(buckets[bucket], n, rand));
246
+ }
247
+ return picked;
248
+ }
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // ashlr__read measurement
252
+ // ---------------------------------------------------------------------------
253
+
254
+ interface ReadSample {
255
+ path: string;
256
+ bucket: SizeBucket;
257
+ rawBytes: number;
258
+ rawTokens: number;
259
+ ashlrBytes: number;
260
+ ashlrTokens: number;
261
+ ratio: number; // 0–1, lower = more savings; 0.3 means 70% reduction
262
+ }
263
+
264
+ function measureRead(file: FileSample): ReadSample | null {
265
+ try {
266
+ const content = readFileSync(file.path, "utf-8");
267
+ const rawBytes = Buffer.byteLength(content, "utf-8");
268
+ const rawTokens = estimateTokensFromString(content);
269
+
270
+ // Replicate ashlrRead logic: wrap as tool_result, apply snipCompact
271
+ const msgs: Message[] = [
272
+ {
273
+ role: "user",
274
+ content: [{ type: "tool_result", tool_use_id: "bench-read", content }],
275
+ },
276
+ ];
277
+ const compact = snipCompact(msgs);
278
+ const block = (compact[0]!.content as { type: string; content: string }[])[0]!;
279
+ const out = (block as { content: string }).content;
280
+
281
+ const ashlrBytes = Buffer.byteLength(out, "utf-8");
282
+ const ashlrTokens = estimateTokensFromString(out);
283
+ const ratio = rawTokens > 0 ? ashlrTokens / rawTokens : 1;
284
+
285
+ return {
286
+ path: file.path,
287
+ bucket: file.bucket,
288
+ rawBytes,
289
+ rawTokens,
290
+ ashlrBytes,
291
+ ashlrTokens,
292
+ ratio,
293
+ };
294
+ } catch {
295
+ return null;
296
+ }
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // ashlr__grep measurement
301
+ // ---------------------------------------------------------------------------
302
+
303
+ const GREP_PATTERNS = ["function ", "import ", "TODO", "class ", "interface "];
304
+
305
+ interface GrepSample {
306
+ pattern: string;
307
+ rawBytes: number;
308
+ rawTokens: number;
309
+ ashlrBytes: number;
310
+ ashlrTokens: number;
311
+ ratio: number;
312
+ method: "rg-truncated";
313
+ }
314
+
315
+ function resolveRg(): string {
316
+ // Try Bun.which first (respects PATH without shell aliases)
317
+ const bunWhich =
318
+ typeof (globalThis as { Bun?: { which(s: string): string | null } }).Bun !== "undefined"
319
+ ? (globalThis as { Bun: { which(s: string): string | null } }).Bun.which("rg")
320
+ : null;
321
+ if (bunWhich) return bunWhich;
322
+
323
+ // Common binary locations (ordered by likelihood); include codex-vendored rg
324
+ const candidates = [
325
+ "/opt/homebrew/bin/rg",
326
+ "/usr/local/bin/rg",
327
+ "/usr/bin/rg",
328
+ // codex vendor path — actual executable, not the dotslash wrapper
329
+ "/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path/rg",
330
+ "/opt/homebrew/lib/node_modules/@openai/codex/bin/rg",
331
+ ];
332
+ for (const p of candidates) {
333
+ if (existsSync(p)) return p;
334
+ }
335
+
336
+ // Cross-platform fallback: `where` on Windows, `which` on POSIX.
337
+ const whichCmd = process.platform === "win32" ? "where" : "which";
338
+ const typeRes = spawnSync(whichCmd, ["rg"], { encoding: "utf-8" });
339
+ const typePath = (typeRes.stdout ?? "").split(/[\r\n]/)[0]?.trim() ?? "";
340
+ if (typePath && existsSync(typePath)) return typePath;
341
+
342
+ return "rg";
343
+ }
344
+
345
+ function measureGrep(pattern: string, repoPath: string): GrepSample | null {
346
+ try {
347
+ const rgBin = resolveRg();
348
+ const res = spawnSync(rgBin, ["--json", "-n", "--", pattern, repoPath], {
349
+ encoding: "utf-8",
350
+ timeout: 15_000,
351
+ maxBuffer: 32 * 1024 * 1024,
352
+ });
353
+ const raw = res.stdout ?? "";
354
+ if (!raw.trim()) return null;
355
+
356
+ const rawBytes = Buffer.byteLength(raw, "utf-8");
357
+ const rawTokens = estimateTokensFromString(raw);
358
+
359
+ // Replicate rg-fallback path in ashlrGrep: truncate to 4000 chars head+tail
360
+ const truncated =
361
+ raw.length > 4000
362
+ ? raw.slice(0, 2000) + "\n\n[... truncated ...]\n\n" + raw.slice(-1000)
363
+ : raw;
364
+
365
+ const ashlrBytes = Buffer.byteLength(truncated, "utf-8");
366
+ const ashlrTokens = estimateTokensFromString(truncated);
367
+ const ratio = rawTokens > 0 ? ashlrTokens / rawTokens : 1;
368
+
369
+ return {
370
+ pattern,
371
+ rawBytes,
372
+ rawTokens,
373
+ ashlrBytes,
374
+ ashlrTokens,
375
+ ratio,
376
+ method: "rg-truncated",
377
+ };
378
+ } catch {
379
+ return null;
380
+ }
381
+ }
382
+
383
+ // ---------------------------------------------------------------------------
384
+ // ashlr__edit measurement
385
+ // ---------------------------------------------------------------------------
386
+
387
+ type EditSize = "small" | "medium" | "large";
388
+
389
+ interface EditSample {
390
+ size: EditSize;
391
+ searchChars: number;
392
+ replaceChars: number;
393
+ naiveBytes: number; // search + replace as raw text
394
+ naiveTokens: number;
395
+ ashlrBytes: number; // diff-summary representation
396
+ ashlrTokens: number;
397
+ ratio: number;
398
+ }
399
+
400
+ function summarizeEditForBench(
401
+ search: string,
402
+ replace: string,
403
+ fileSize: number,
404
+ ): string {
405
+ // Replicate the summarizeEdit output from efficiency-server.ts
406
+ const first = (s: string) => s.split("\n")[0]?.slice(0, 72) ?? "";
407
+ return [
408
+ `[ashlr__edit] file.ts · 1 of 1 hunks applied`,
409
+ ` - removed (${estimateTokensFromString(search)} tok): ${first(search)}${search.length > 72 ? "…" : ""}`,
410
+ ` + added (${estimateTokensFromString(replace)} tok): ${first(replace)}${replace.length > 72 ? "…" : ""}`,
411
+ ].join("\n");
412
+ }
413
+
414
+ function measureEdit(size: EditSize): EditSample {
415
+ // Synthetic edits of known sizes
416
+ const scenarios: Record<EditSize, { search: string; replace: string; fileSize: number }> = {
417
+ small: {
418
+ search: "const foo = 1;\n",
419
+ replace: "const foo = 2;\n",
420
+ fileSize: 3_000,
421
+ },
422
+ medium: {
423
+ search: [
424
+ "function processItems(items: string[]): string[] {",
425
+ " return items.filter(Boolean).map(s => s.trim());",
426
+ "}",
427
+ "",
428
+ "function formatOutput(result: string[]): string {",
429
+ " return result.join(', ');",
430
+ "}",
431
+ ].join("\n"),
432
+ replace: [
433
+ "function processItems(items: string[]): string[] {",
434
+ " return items",
435
+ " .filter(Boolean)",
436
+ " .map(s => s.trim())",
437
+ " .sort();",
438
+ "}",
439
+ "",
440
+ "function formatOutput(result: string[]): string {",
441
+ " return result.join(', ');",
442
+ "}",
443
+ ].join("\n"),
444
+ fileSize: 12_000,
445
+ },
446
+ large: {
447
+ search: Array.from({ length: 60 }, (_, i) =>
448
+ i % 3 === 0
449
+ ? ` // step ${i}: process batch`
450
+ : i % 3 === 1
451
+ ? ` const result${i} = items.slice(${i * 10}, ${i * 10 + 10}).map(transform);`
452
+ : ` output.push(...result${i - 1});`,
453
+ ).join("\n"),
454
+ replace: Array.from({ length: 60 }, (_, i) =>
455
+ i % 3 === 0
456
+ ? ` // step ${i}: process batch (optimized)`
457
+ : i % 3 === 1
458
+ ? ` const result${i} = batchTransform(items, ${i * 10}, ${i * 10 + 10});`
459
+ : ` output.push(...result${i - 1});`,
460
+ ).join("\n"),
461
+ fileSize: 40_000,
462
+ },
463
+ };
464
+
465
+ const { search, replace, fileSize } = scenarios[size];
466
+
467
+ // Naive: ship full before+after (2× file approximated as search+replace + surrounding context)
468
+ const naiveText = `BEFORE:\n${search}\n\nAFTER:\n${replace}`;
469
+ const naiveBytes = Buffer.byteLength(naiveText, "utf-8");
470
+ const naiveTokens = estimateTokensFromString(naiveText);
471
+
472
+ // ashlr__edit: send only the diff summary
473
+ const summary = summarizeEditForBench(search, replace, fileSize);
474
+ const ashlrBytes = Buffer.byteLength(summary, "utf-8");
475
+ const ashlrTokens = estimateTokensFromString(summary);
476
+ const ratio = naiveTokens > 0 ? ashlrTokens / naiveTokens : 1;
477
+
478
+ return {
479
+ size,
480
+ searchChars: search.length,
481
+ replaceChars: replace.length,
482
+ naiveBytes,
483
+ naiveTokens,
484
+ ashlrBytes,
485
+ ashlrTokens,
486
+ ratio,
487
+ };
488
+ }
489
+
490
+ // ---------------------------------------------------------------------------
491
+ // Aggregation
492
+ // ---------------------------------------------------------------------------
493
+
494
+ interface ToolAggregate {
495
+ mean: number;
496
+ p50: number;
497
+ p90: number;
498
+ confidenceInterval: { lo: number; hi: number };
499
+ }
500
+
501
+ function aggregate(ratios: number[]): ToolAggregate {
502
+ if (ratios.length === 0) return { mean: 1, p50: 1, p90: 1, confidenceInterval: { lo: 1, hi: 1 } };
503
+ const sorted = ratios.slice().sort((a, b) => a - b);
504
+ const mean = ratios.reduce((s, r) => s + r, 0) / ratios.length;
505
+ const p50 = sorted[Math.floor(sorted.length * 0.5)]!;
506
+ const p90 = sorted[Math.floor(sorted.length * 0.9)]!;
507
+ const confidenceInterval = bootstrapCI(ratios);
508
+ return { mean, p50, p90, confidenceInterval };
509
+ }
510
+
511
+ // ---------------------------------------------------------------------------
512
+ // Output schema
513
+ // ---------------------------------------------------------------------------
514
+
515
+ interface BenchmarkOutput {
516
+ version: 2;
517
+ measuredAt: string;
518
+ repo: RepoMeta;
519
+ samples: {
520
+ "ashlr__read": ReadSample[];
521
+ "ashlr__grep": GrepSample[];
522
+ "ashlr__edit": EditSample[];
523
+ };
524
+ aggregate: {
525
+ "ashlr__read": ToolAggregate;
526
+ "ashlr__grep": ToolAggregate;
527
+ "ashlr__edit": ToolAggregate;
528
+ overall: { mean: number };
529
+ };
530
+ methodology: string;
531
+ heuristicError?: number | null;
532
+ }
533
+
534
+ const METHODOLOGY = `
535
+ Measurement methodology (version 2):
536
+
537
+ **ashlr__read**: For each sampled source file, we measure raw file bytes and
538
+ token count (chars/4 heuristic). We then apply the same snipCompact
539
+ transformation used at runtime — wrapping the content in a tool_result message
540
+ and calling snipCompact() — and measure the resulting byte/token count. The
541
+ ratio is ashlrTokens / rawTokens. Files below 2 KB are excluded because
542
+ snipCompact only fires on tool results > 2 000 chars; savings are zero by
543
+ design for small files.
544
+
545
+ Files are selected deterministically: the repo HEAD commit SHA is folded into a
546
+ 32-bit seed (mulberry32 PRNG), then up to 4 files are sampled from each of four
547
+ size buckets (2–5 KB, 5–15 KB, 15–50 KB, 50+ KB). Re-running on the same
548
+ commit always picks the same files.
549
+
550
+ **ashlr__grep**: Five common patterns (function, import, TODO, class, interface)
551
+ are run via rg --json against the repo root. Raw output bytes are measured
552
+ directly. The ashlr__grep fallback path (no genome) truncates output to 4 000
553
+ chars (head 2 000 + tail 1 000). The ratio is truncated/raw.
554
+
555
+ Note: when a .ashlrcode/genome/ index is present, real-world grep savings are
556
+ substantially higher. This benchmark measures only the conservative
557
+ no-genome baseline.
558
+
559
+ **ashlr__edit**: Three synthetic edits (small ~15 chars, medium ~300 chars,
560
+ large ~3 000 chars) compare the naive "ship before+after as text" approach
561
+ against ashlr__edit's diff-summary format (one header line + removed/added
562
+ first-lines). The ratio is summary tokens / naive tokens.
563
+
564
+ **Aggregation**: per-tool mean/p50/p90 are computed over each tool's ratio
565
+ values (lower ratio = more savings). The \`overall.mean\` is pooled across
566
+ every individual sample regardless of tool — so tools with more samples (read
567
+ has 15, grep has 5, edit has 3) weight the overall figure proportionally.
568
+ That makes the headline number reflect the workload mix, not a uniform
569
+ per-tool average. The unweighted mean of per-tool means is intentionally NOT
570
+ published because it gives equal weight to a 3-sample tool and a 15-sample
571
+ tool, which over-weights the synthetic edit overhead.
572
+
573
+ Token counts use the chars/4 heuristic, the same estimator the plugin uses at
574
+ runtime for savings accounting.
575
+ `.trim();
576
+
577
+ // ---------------------------------------------------------------------------
578
+ // Main
579
+ // ---------------------------------------------------------------------------
580
+
581
+ export async function runBenchmark(opts: {
582
+ repo: string;
583
+ out: string;
584
+ dryRun: boolean;
585
+ validateTokenizer?: boolean;
586
+ compare?: boolean;
587
+ }): Promise<BenchmarkOutput> {
588
+ const { repo, out, dryRun, validateTokenizer = false, compare = false } = opts;
589
+
590
+ if (!existsSync(repo)) {
591
+ throw new Error(`Repo path does not exist: ${repo}`);
592
+ }
593
+
594
+ console.log(`[run-benchmark] repo: ${repo}`);
595
+ const meta = getRepoMeta(repo);
596
+ console.log(`[run-benchmark] commit: ${meta.commit} files: ${meta.files} loc: ${meta.loc}`);
597
+
598
+ // Seed PRNG from commit SHA
599
+ const rand = mulberry32(seedFromCommit(meta.commit));
600
+
601
+ // --- ashlr__read ---
602
+ console.log("[run-benchmark] enumerating files...");
603
+ const allFiles = enumerateFiles(repo);
604
+ if (allFiles.length === 0) {
605
+ console.warn("[run-benchmark] WARNING: no files >= 2KB found — read samples will be empty");
606
+ }
607
+ const sampled = selectSample(allFiles, rand);
608
+ console.log(`[run-benchmark] sampled ${sampled.length} files for read benchmark`);
609
+
610
+ const readSamples: ReadSample[] = [];
611
+ for (const file of sampled) {
612
+ const s = measureRead(file);
613
+ if (s) {
614
+ readSamples.push(s);
615
+ const pct = ((1 - s.ratio) * 100).toFixed(1);
616
+ console.log(
617
+ `[run-benchmark] read ${relative(repo, file.path).padEnd(50)} ${file.bucket.padEnd(8)} ${pct}% saved`,
618
+ );
619
+ } else {
620
+ console.warn(`[run-benchmark] read SKIP ${file.path}`);
621
+ }
622
+ }
623
+
624
+ // --- ashlr__grep ---
625
+ console.log("[run-benchmark] measuring grep...");
626
+ const grepSamples: GrepSample[] = [];
627
+ for (const pattern of GREP_PATTERNS) {
628
+ const s = measureGrep(pattern, repo);
629
+ if (s) {
630
+ grepSamples.push(s);
631
+ const pct = ((1 - s.ratio) * 100).toFixed(1);
632
+ const label = `"${pattern.trim()}"`.padEnd(12);
633
+ console.log(
634
+ `[run-benchmark] grep ${label} ${pct}% saved (${s.rawBytes} raw → ${s.ashlrBytes} ashlr bytes)`,
635
+ );
636
+ } else {
637
+ console.warn(`[run-benchmark] grep no matches for "${pattern}" — skipped`);
638
+ }
639
+ }
640
+
641
+ // --- ashlr__edit ---
642
+ console.log("[run-benchmark] measuring edit...");
643
+ const formatSampleSavings = (pct: number) => pct >= 0
644
+ ? `${pct.toFixed(1)}% saved`
645
+ : `${Math.abs(pct).toFixed(1)}% overhead`;
646
+ const editSamples: EditSample[] = [];
647
+ for (const size of ["small", "medium", "large"] as EditSize[]) {
648
+ const s = measureEdit(size);
649
+ editSamples.push(s);
650
+ const pct = (1 - s.ratio) * 100;
651
+ console.log(`[run-benchmark] edit ${size.padEnd(8)} ${formatSampleSavings(pct)}`);
652
+ }
653
+
654
+ // --- Aggregates ---
655
+ const readAgg = aggregate(readSamples.map((s) => s.ratio));
656
+ const grepAgg = aggregate(grepSamples.map((s) => s.ratio));
657
+ const editAgg = aggregate(editSamples.map((s) => s.ratio));
658
+ const allRatios = [
659
+ ...readSamples.map((s) => s.ratio),
660
+ ...grepSamples.map((s) => s.ratio),
661
+ ...editSamples.map((s) => s.ratio),
662
+ ];
663
+ const overallMean = allRatios.length > 0
664
+ ? allRatios.reduce((s, r) => s + r, 0) / allRatios.length
665
+ : 1;
666
+
667
+ const output: BenchmarkOutput = {
668
+ version: 2,
669
+ measuredAt: new Date().toISOString(),
670
+ repo: meta,
671
+ samples: {
672
+ "ashlr__read": readSamples,
673
+ "ashlr__grep": grepSamples,
674
+ "ashlr__edit": editSamples,
675
+ },
676
+ aggregate: {
677
+ "ashlr__read": readAgg,
678
+ "ashlr__grep": grepAgg,
679
+ "ashlr__edit": editAgg,
680
+ overall: { mean: overallMean },
681
+ },
682
+ methodology: METHODOLOGY,
683
+ };
684
+
685
+ const formatSavings = (ratio: number) => {
686
+ const pct = (1 - ratio) * 100;
687
+ return pct >= 0 ? `−${pct.toFixed(1)}% token savings` : `+${Math.abs(pct).toFixed(1)}% token overhead`;
688
+ };
689
+
690
+ console.log(`\n[run-benchmark] RESULT: overall mean ${formatSavings(overallMean)}`);
691
+ console.log(`[run-benchmark] read mean ${formatSavings(readAgg.mean)}`);
692
+ console.log(`[run-benchmark] grep mean ${formatSavings(grepAgg.mean)}`);
693
+ console.log(`[run-benchmark] edit mean ${formatSavings(editAgg.mean)}`);
694
+ console.log(`[run-benchmark] read CI [${((1-readAgg.confidenceInterval.hi)*100).toFixed(1)}%–${((1-readAgg.confidenceInterval.lo)*100).toFixed(1)}%]`);
695
+
696
+ // --compare: print A/B table
697
+ if (compare && readSamples.length > 0) {
698
+ console.log("\n[run-benchmark] --compare: native vs ashlr (read samples)");
699
+ console.log(" file native-tok ashlr-tok ratio");
700
+ for (const s of readSamples) {
701
+ const name = s.path.split("/").slice(-2).join("/").padEnd(41);
702
+ console.log(` ${name} ${String(s.rawTokens).padStart(10)} ${String(s.ashlrTokens).padStart(9)} ${(s.ratio * 100).toFixed(1)}%`);
703
+ }
704
+ }
705
+
706
+ // --validate-tokenizer: sample 5% through measureTokens
707
+ let heuristicError: number | null = null;
708
+ if (validateTokenizer && process.env.ANTHROPIC_API_KEY) {
709
+ console.log("[run-benchmark] --validate-tokenizer: sampling API token counts...");
710
+ const { measureTokens } = await import("../servers/_token-measure.ts");
711
+ const sampleN = Math.max(1, Math.ceil(readSamples.length * 0.05));
712
+ const toValidate = seededSample(readSamples, sampleN, rand);
713
+ let totalEstimate = 0;
714
+ let totalMeasured = 0;
715
+ for (const s of toValidate) {
716
+ try {
717
+ const content = readFileSync(s.path, "utf-8");
718
+ const measured = await measureTokens(content);
719
+ if (measured !== null) { totalEstimate += s.rawTokens; totalMeasured += measured; }
720
+ } catch { /* skip unreadable */ }
721
+ }
722
+ if (totalMeasured > 0) {
723
+ heuristicError = Math.abs(totalEstimate - totalMeasured) / totalMeasured * 100;
724
+ console.log(`[run-benchmark] heuristicError: ${heuristicError.toFixed(2)}%`);
725
+ }
726
+ } else if (validateTokenizer) {
727
+ console.log("[run-benchmark] --validate-tokenizer: ANTHROPIC_API_KEY not set, skipping");
728
+ }
729
+
730
+ if (heuristicError !== null) output.heuristicError = heuristicError;
731
+
732
+ if (!dryRun) {
733
+ writeFileSync(out, JSON.stringify(output, null, 2), "utf-8");
734
+ console.log(`[run-benchmark] wrote ${out}`);
735
+ } else {
736
+ console.log("[run-benchmark] --dry-run: skipping file write");
737
+ }
738
+
739
+ return output;
740
+ }
741
+
742
+ if (import.meta.main) {
743
+ const args = parseArgs(process.argv.slice(2));
744
+ try {
745
+ await runBenchmark(args);
746
+ } catch (err) {
747
+ console.error("[run-benchmark] ERROR:", err instanceof Error ? err.message : String(err));
748
+ process.exit(1);
749
+ }
750
+ }