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,329 @@
1
+ /**
2
+ * _embedding-model.ts — Pluggable embedder for the ashlr embedding cache.
3
+ *
4
+ * Day-1 strategy: BM25-style sparse pseudo-embedding via hash projection.
5
+ * - Tokenize input text (whitespace + punctuation split, lowercase).
6
+ * - Compute per-token IDF weights from a per-project corpus stored at
7
+ * ~/.ashlr/embed-corpus.json.
8
+ * - Project token TF-IDF scores into a fixed 256-dim vector via a
9
+ * deterministic hash (FNV-1a → index mod 256, sign from bit 0).
10
+ * - Normalize to unit length before returning.
11
+ *
12
+ * This gives useful cosine similarity WITHOUT any ML runtime:
13
+ * - Same input → same vector (deterministic).
14
+ * - Similar inputs (shared tokens) → high cosine.
15
+ * - Dissimilar inputs → low cosine.
16
+ *
17
+ * Remote embedding (optional):
18
+ * Set ASHLR_EMBED_URL=http://localhost:11434/api/embeddings to call an
19
+ * Ollama/LM Studio endpoint. Falls back to BM25 on any failure.
20
+ *
21
+ * Env vars:
22
+ * ASHLR_EMBED_URL — HTTP POST endpoint that returns { embedding: number[] }
23
+ * ASHLR_EMBED_MODEL — model name to pass in the request body (default: nomic-embed-text)
24
+ * ASHLR_CONTEXT_DB_DISABLE — if "1", embed() still works (used outside the cache path)
25
+ */
26
+
27
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
28
+ import { homedir } from "os";
29
+ import { join } from "path";
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Constants
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /** Fixed output dimension for the BM25 hash-projection embedder. */
36
+ export const EMBED_DIM = 256;
37
+
38
+ /** Max tokens considered per document (cap to keep hashing O(1) on huge files). */
39
+ const MAX_TOKENS = 2048;
40
+
41
+ /** IDF corpus path. */
42
+ const corpusPath = (): string =>
43
+ join(homedir(), ".ashlr", "embed-corpus.json");
44
+
45
+ /** Minimum document frequency count before IDF kicks in (avoids div-by-0 on tiny corpus). */
46
+ const MIN_DF = 1;
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Corpus (IDF weights)
50
+ // ---------------------------------------------------------------------------
51
+
52
+ interface Corpus {
53
+ /** document count (each upsertCorpus call = 1 doc) */
54
+ docCount: number;
55
+ /** token → document frequency */
56
+ df: Record<string, number>;
57
+ }
58
+
59
+ let _corpus: Corpus | null = null;
60
+ let _pendingDocs = 0;
61
+ const _pendingDf: Map<string, number> = new Map();
62
+
63
+ function loadCorpus(): Corpus {
64
+ if (_corpus) return _corpus;
65
+ _corpus = readCorpusFromDisk();
66
+ return _corpus;
67
+ }
68
+
69
+ function readCorpusFromDisk(): Corpus {
70
+ const path = corpusPath();
71
+ if (existsSync(path)) {
72
+ try {
73
+ return JSON.parse(readFileSync(path, "utf-8")) as Corpus;
74
+ } catch {
75
+ // corrupt; reinitialize
76
+ }
77
+ }
78
+ return { docCount: 0, df: {} };
79
+ }
80
+
81
+ function atomicWriteCorpus(c: Corpus): void {
82
+ const path = corpusPath();
83
+ const dir = join(homedir(), ".ashlr");
84
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
85
+ const tmp = `${path}.${process.pid}.tmp`;
86
+ writeFileSync(tmp, JSON.stringify(c), "utf-8");
87
+ renameSync(tmp, path);
88
+ }
89
+
90
+ /**
91
+ * Acquire a cross-process advisory lock on `~/.ashlr/embed-corpus.json.lock`.
92
+ * Returns true if acquired, false if contended after ~1s of retries.
93
+ * Stale locks (>5s old) are reclaimed automatically.
94
+ *
95
+ * Async-with-sleep is load-bearing: short-lived worker processes (e.g. the
96
+ * embed-file-worker fired by the post-tool-use hook) exit immediately after
97
+ * their main task, so a purely synchronous no-sleep retry loop would burn
98
+ * attempts too fast to let a contending writer finish and release.
99
+ */
100
+ async function tryAcquireCorpusLock(): Promise<boolean> {
101
+ const lockPath = `${corpusPath()}.lock`;
102
+ for (let attempt = 0; attempt < 10; attempt++) {
103
+ try {
104
+ const fd = openSync(lockPath, "wx");
105
+ closeSync(fd);
106
+ return true;
107
+ } catch {
108
+ // EEXIST — check for stale lock
109
+ try {
110
+ const st = statSync(lockPath);
111
+ if (Date.now() - st.mtimeMs > 5_000) {
112
+ try { unlinkSync(lockPath); } catch { /* ignore */ }
113
+ }
114
+ } catch { /* stat race — lock may have been released, loop */ }
115
+ }
116
+ if (attempt < 9) await new Promise((r) => setTimeout(r, 100));
117
+ }
118
+ return false;
119
+ }
120
+
121
+ function releaseCorpusLock(): void {
122
+ try { unlinkSync(`${corpusPath()}.lock`); } catch { /* best-effort */ }
123
+ }
124
+
125
+ /**
126
+ * Flush pending in-process deltas to disk with a read-modify-write that
127
+ * merges against the current on-disk version. Cross-process races are
128
+ * bounded to single-delta loss worst-case (not full history), because each
129
+ * writer reads fresh and applies only its own pending delta.
130
+ */
131
+ async function flushPendingDeltasToDisk(): Promise<void> {
132
+ if (_pendingDocs === 0 && _pendingDf.size === 0) return;
133
+ if (!(await tryAcquireCorpusLock())) return;
134
+ try {
135
+ const onDisk = readCorpusFromDisk();
136
+ onDisk.docCount += _pendingDocs;
137
+ for (const [t, d] of _pendingDf) onDisk.df[t] = (onDisk.df[t] ?? 0) + d;
138
+ atomicWriteCorpus(onDisk);
139
+ // In-memory copy reflects our best-known truth
140
+ _corpus = onDisk;
141
+ _pendingDocs = 0;
142
+ _pendingDf.clear();
143
+ } catch {
144
+ // best-effort — don't crash the caller
145
+ } finally {
146
+ releaseCorpusLock();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Explicit, awaitable flush. Short-lived processes (e.g. embed-file-worker)
152
+ * should await this before exit to avoid silently dropping their pending
153
+ * IDF deltas when the setImmediate callback gets pre-empted by process.exit.
154
+ */
155
+ export async function flushCorpusNow(): Promise<void> {
156
+ await flushPendingDeltasToDisk();
157
+ }
158
+
159
+ /**
160
+ * Update the IDF corpus with tokens from a new document.
161
+ * Call this when indexing new content so IDF weights improve over time.
162
+ * Fire-and-forget safe (synchronous, but cheap).
163
+ *
164
+ * Concurrency: in-memory updates are immediate; disk persistence is deferred
165
+ * via setImmediate and uses a delta pattern + advisory lock so concurrent
166
+ * processes don't silently drop each other's history.
167
+ */
168
+ export function upsertCorpus(text: string): void {
169
+ const c = loadCorpus();
170
+ const tokens = tokenize(text);
171
+ const seen = new Set(tokens);
172
+ c.docCount += 1;
173
+ _pendingDocs += 1;
174
+ for (const t of seen) {
175
+ c.df[t] = (c.df[t] ?? 0) + 1;
176
+ _pendingDf.set(t, (_pendingDf.get(t) ?? 0) + 1);
177
+ }
178
+ _corpus = c;
179
+ setImmediate(() => {
180
+ flushPendingDeltasToDisk().catch(() => { /* best-effort */ });
181
+ });
182
+ }
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // Tokenization
186
+ // ---------------------------------------------------------------------------
187
+
188
+ const TOKEN_RE = /[a-z0-9_$]+/g;
189
+
190
+ /** Tokenize text: lowercase, extract alphanumeric+underscore runs. */
191
+ export function tokenize(text: string): string[] {
192
+ return (text.toLowerCase().match(TOKEN_RE) ?? []).slice(0, MAX_TOKENS);
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // FNV-1a hash projection
197
+ // ---------------------------------------------------------------------------
198
+
199
+ /**
200
+ * Deterministic hash of a string → integer in [0, EMBED_DIM).
201
+ * Uses FNV-1a 32-bit: fast, no deps, uniform-ish distribution.
202
+ */
203
+ function fnv1a(s: string): number {
204
+ let h = 0x811c9dc5;
205
+ for (let i = 0; i < s.length; i++) {
206
+ h ^= s.charCodeAt(i);
207
+ h = Math.imul(h, 0x01000193);
208
+ }
209
+ return h >>> 0; // unsigned
210
+ }
211
+
212
+ /** Map a token to a bucket index [0, EMBED_DIM) and a sign (+1 or -1). */
213
+ function tokenToProjection(token: string): { idx: number; sign: number } {
214
+ const h = fnv1a(token);
215
+ const idx = h % EMBED_DIM;
216
+ const sign = (h & 1) === 0 ? 1 : -1;
217
+ return { idx, sign };
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // BM25-style pseudo-embedding (local, no ML runtime)
222
+ // ---------------------------------------------------------------------------
223
+
224
+ /**
225
+ * Build a 256-dim BM25 pseudo-embedding for the given text.
226
+ * Uses TF × IDF weights projected via FNV-1a hash.
227
+ * Returns a normalized Float32Array.
228
+ */
229
+ export function bm25Embed(text: string): Float32Array {
230
+ const corpus = loadCorpus();
231
+ const tokens = tokenize(text);
232
+ const N = Math.max(corpus.docCount, 1);
233
+
234
+ // TF: raw count
235
+ const tf: Record<string, number> = {};
236
+ for (const t of tokens) {
237
+ tf[t] = (tf[t] ?? 0) + 1;
238
+ }
239
+
240
+ const vec = new Float32Array(EMBED_DIM);
241
+
242
+ for (const [token, count] of Object.entries(tf)) {
243
+ const df = Math.max(corpus.df[token] ?? 0, MIN_DF);
244
+ // IDF: log((N + 1) / (df + 1)) — smoothed BM25-style
245
+ const idf = Math.log((N + 1) / (df + 1)) + 1;
246
+ const weight = count * idf;
247
+ const { idx, sign } = tokenToProjection(token);
248
+ vec[idx] += sign * weight;
249
+ }
250
+
251
+ return normalizeInPlace(vec);
252
+ }
253
+
254
+ // ---------------------------------------------------------------------------
255
+ // Remote embedder (Ollama / LM Studio)
256
+ // ---------------------------------------------------------------------------
257
+
258
+ /**
259
+ * Call a remote embedding endpoint.
260
+ * Returns null on any failure so the caller can fall back to BM25.
261
+ */
262
+ async function remoteEmbed(text: string): Promise<Float32Array | null> {
263
+ const url = process.env.ASHLR_EMBED_URL;
264
+ if (!url) return null;
265
+
266
+ const model = process.env.ASHLR_EMBED_MODEL ?? "nomic-embed-text";
267
+
268
+ try {
269
+ const res = await fetch(url, {
270
+ method: "POST",
271
+ headers: { "Content-Type": "application/json" },
272
+ body: JSON.stringify({ model, prompt: text }),
273
+ // 5-second timeout — don't block the grep path
274
+ signal: AbortSignal.timeout(5_000),
275
+ });
276
+
277
+ if (!res.ok) return null;
278
+
279
+ const json = (await res.json()) as { embedding?: number[] };
280
+ if (!Array.isArray(json.embedding)) return null;
281
+
282
+ const vec = new Float32Array(json.embedding);
283
+ return normalizeInPlace(vec);
284
+ } catch {
285
+ return null;
286
+ }
287
+ }
288
+
289
+ // ---------------------------------------------------------------------------
290
+ // Normalization helper
291
+ // ---------------------------------------------------------------------------
292
+
293
+ export function normalizeInPlace(v: Float32Array): Float32Array {
294
+ let sum = 0;
295
+ for (let i = 0; i < v.length; i++) sum += v[i]! * v[i]!;
296
+ const mag = Math.sqrt(sum);
297
+ if (mag === 0) return v;
298
+ for (let i = 0; i < v.length; i++) v[i]! /= mag;
299
+ return v;
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Public API
304
+ // ---------------------------------------------------------------------------
305
+
306
+ /**
307
+ * Compute an embedding for the given text.
308
+ *
309
+ * Strategy:
310
+ * 1. If ASHLR_EMBED_URL is set, try the remote endpoint.
311
+ * 2. On failure (or no URL), fall back to BM25 pseudo-embedding.
312
+ *
313
+ * The returned Float32Array is already normalized to unit length.
314
+ */
315
+ export async function embed(text: string): Promise<Float32Array> {
316
+ if (process.env.ASHLR_EMBED_URL) {
317
+ const remote = await remoteEmbed(text);
318
+ if (remote) return remote;
319
+ // Fall through to BM25 on failure
320
+ }
321
+ return bm25Embed(text);
322
+ }
323
+
324
+ /**
325
+ * The dimension of the vector returned by `embed()`.
326
+ * When using a remote model this may differ — callers should use
327
+ * the actual vector length; this constant applies only to BM25.
328
+ */
329
+ export { EMBED_DIM as BM25_DIM };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Observability event emitter for ashlr MCP tools.
3
+ *
4
+ * Appends JSONL records to ~/.ashlr/session-log.jsonl (the same file the
5
+ * PostToolUse hook writes to). Purely additive — does NOT change any tool's
6
+ * return shape and never throws.
7
+ *
8
+ * Kill switch: set ASHLR_SESSION_LOG=0 to disable all emission.
9
+ */
10
+
11
+ import { appendFile, mkdir } from "fs/promises";
12
+ import { homedir } from "os";
13
+ import { dirname, join } from "path";
14
+ import { currentSessionId } from "./_stats";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export type EventKind =
21
+ | "tool_fallback"
22
+ | "tool_escalate"
23
+ | "tool_error"
24
+ | "tool_noop"
25
+ | "tool_call"
26
+ | "accounting_cache_hit"
27
+ | "tool_crashed"
28
+ // v1.22 Track A — savings-math trust pass
29
+ | "tool_low_confidence_shipped"
30
+ | "tool_skip_micro_edit"
31
+ // v1.22 Track D — LLM provider telemetry
32
+ | "llm_summarize_provider_used"
33
+ // v1.22 Track G — adoption + visibility telemetry
34
+ | "tool_called_after_block"
35
+ | "genome_route_taken"
36
+ | "embed_cache_hit"
37
+ | "embed_cache_miss"
38
+ // v1.23 Track HH — genome stale detection
39
+ | "genome_stale_detected"
40
+ // v1.24 Track E — warm-start corpus tier telemetry
41
+ | "embed_warm_index"
42
+ // v1.27 Track 5 — genome-search inverted index
43
+ | "genome_search_hit"
44
+ | "genome_search_miss"
45
+ // v1.30 Track 3 — wizard completion telemetry
46
+ | "wizard_step";
47
+
48
+ export interface EventPayload {
49
+ tool: string;
50
+ reason?: string;
51
+ extra?: Record<string, unknown>;
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Path helpers (resolve at call-time so tests overriding $HOME work)
56
+ // ---------------------------------------------------------------------------
57
+
58
+ function home(): string {
59
+ return process.env.HOME ?? homedir();
60
+ }
61
+
62
+ function logPath(): string {
63
+ return join(home(), ".ashlr", "session-log.jsonl");
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Public API
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * Append a single observability event to the session log.
72
+ *
73
+ * Schema mirrors the PostToolUse hook:
74
+ * ts, agent, event, tool, cwd, session, + any extras from payload.extra
75
+ *
76
+ * Best-effort: never throws.
77
+ */
78
+ export async function logEvent(
79
+ event: EventKind,
80
+ payload: EventPayload,
81
+ ): Promise<void> {
82
+ if (process.env.ASHLR_SESSION_LOG === "0") return;
83
+
84
+ try {
85
+ const record: Record<string, unknown> = {
86
+ ts: new Date().toISOString(),
87
+ agent: "ashlr-mcp",
88
+ event,
89
+ tool: payload.tool,
90
+ cwd: process.cwd(),
91
+ session: currentSessionId(),
92
+ };
93
+
94
+ if (payload.reason !== undefined) {
95
+ record.reason = payload.reason;
96
+ }
97
+
98
+ if (payload.extra) {
99
+ for (const [k, v] of Object.entries(payload.extra)) {
100
+ record[k] = v;
101
+ }
102
+ }
103
+
104
+ const line = JSON.stringify(record) + "\n";
105
+ const path = logPath();
106
+
107
+ // Ensure the directory exists (best-effort; may already exist).
108
+ await mkdir(dirname(path), { recursive: true }).catch(() => undefined);
109
+ await appendFile(path, line, "utf-8");
110
+ } catch {
111
+ // Never propagate — this is observability, not a critical path.
112
+ }
113
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * In-memory LRU cache for genome retrieval results.
3
+ *
4
+ * Wraps retrieveSectionsV2 from @ashlr/core-efficiency with a 64-entry LRU
5
+ * keyed by (genomeRoot, pattern). Entries are invalidated when the genome
6
+ * manifest mtime changes — detected cheaply on each cache hit.
7
+ *
8
+ * Never throws: on any miss or error, falls through to direct retrieval.
9
+ */
10
+
11
+ import { existsSync, readFileSync, statSync } from "fs";
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ import { runWithTimeout } from "./_run-with-timeout";
15
+ import { createHash } from "crypto";
16
+ import { retrieveSectionsV2 as _retrieveSectionsV2 } from "@ashlr/core-efficiency";
17
+ import { canonicalizeRepoUrl } from "../scripts/genome-cloud-pull";
18
+
19
+ const CAPACITY = 64;
20
+
21
+ type Retriever = typeof _retrieveSectionsV2;
22
+
23
+ interface CacheEntry {
24
+ result: Awaited<ReturnType<Retriever>>;
25
+ manifestMtime: number;
26
+ /** LRU recency counter — higher = more recent. */
27
+ lru: number;
28
+ }
29
+
30
+ const cache = new Map<string, CacheEntry>();
31
+ let lruClock = 0;
32
+
33
+ function manifestMtime(genomeRoot: string): number {
34
+ try {
35
+ return statSync(join(genomeRoot, ".ashlrcode", "genome", "manifest.json")).mtimeMs;
36
+ } catch {
37
+ return 0;
38
+ }
39
+ }
40
+
41
+ function cacheKey(genomeRoot: string, pattern: string): string {
42
+ return `${genomeRoot}\x00${pattern}`;
43
+ }
44
+
45
+ function evictLRU(): void {
46
+ if (cache.size < CAPACITY) return;
47
+ let oldestKey = "";
48
+ let oldestLru = Infinity;
49
+ for (const [k, v] of cache) {
50
+ if (v.lru < oldestLru) { oldestLru = v.lru; oldestKey = k; }
51
+ }
52
+ if (oldestKey) cache.delete(oldestKey);
53
+ }
54
+
55
+ /**
56
+ * Retrieve genome sections, returning a cached result when the manifest mtime
57
+ * has not changed since the last retrieval for this (genomeRoot, pattern).
58
+ *
59
+ * The optional `retriever` parameter exists solely for testing: callers can
60
+ * inject a stub without needing `mock.module`, which would leak module state
61
+ * across the full test suite. Production callers omit it and get the real
62
+ * `retrieveSectionsV2` from @ashlr/core-efficiency.
63
+ */
64
+ export async function retrieveCached(
65
+ genomeRoot: string,
66
+ pattern: string,
67
+ limit: number,
68
+ retriever: Retriever = _retrieveSectionsV2,
69
+ ): Promise<Awaited<ReturnType<Retriever>>> {
70
+ const key = cacheKey(genomeRoot, pattern);
71
+ const mtime = manifestMtime(genomeRoot);
72
+ const entry = cache.get(key);
73
+
74
+ if (entry && entry.manifestMtime === mtime) {
75
+ entry.lru = ++lruClock;
76
+ return entry.result;
77
+ }
78
+
79
+ // Miss or stale — retrieve directly. Never throw.
80
+ try {
81
+ const result = await retriever(genomeRoot, pattern, limit);
82
+ evictLRU();
83
+ cache.set(key, { result, manifestMtime: mtime, lru: ++lruClock });
84
+ return result;
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+
90
+ /** Exposed for tests — clears all cache state. */
91
+ export function _clearCache(): void {
92
+ cache.clear();
93
+ lruClock = 0;
94
+ }
95
+
96
+ /** Exposed for tests — current cache size. */
97
+ export function _cacheSize(): number {
98
+ return cache.size;
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Cloud genome fallback
103
+ // ---------------------------------------------------------------------------
104
+
105
+ /**
106
+ * Locate the cloud-pulled genome directory for the given working directory.
107
+ *
108
+ * Semantics:
109
+ * - Local `.ashlrcode/genome/` wins if present (callers should check that
110
+ * first). This function is only called when no local genome was found.
111
+ * - Reads the git remote of `cwd`, canonicalizes it, computes the project
112
+ * hash, and checks whether `~/.ashlr/genomes/<hash>/` exists with a valid
113
+ * `.ashlr-cloud-genome` marker file.
114
+ * - Returns the cloud genome directory path, or `null` if unavailable.
115
+ * - Never throws.
116
+ *
117
+ * @param cwd - Working directory to resolve the git remote from.
118
+ * @param home - Override for testing (default: os.homedir()).
119
+ */
120
+ export async function findCloudGenome(cwd: string, home?: string): Promise<string | null> {
121
+ try {
122
+ const h = home ?? homedir();
123
+
124
+ // Resolve git remote for cwd
125
+ const res = await runWithTimeout({
126
+ command: "git",
127
+ args: ["remote", "get-url", "origin"],
128
+ cwd,
129
+ timeoutMs: 5_000,
130
+ });
131
+ if (res.exitCode !== 0 || !res.stdout) return null;
132
+ const rawRemote = res.stdout.trim();
133
+ if (!rawRemote) return null;
134
+
135
+ const canonUrl = canonicalizeRepoUrl(rawRemote);
136
+ const hash = createHash("sha256").update(canonUrl).digest("hex").slice(0, 8);
137
+ const genomeDir = join(h, ".ashlr", "genomes", hash);
138
+ const markerPath = join(genomeDir, ".ashlr-cloud-genome");
139
+
140
+ if (!existsSync(markerPath)) return null;
141
+
142
+ // Validate the marker is parseable JSON with required fields
143
+ try {
144
+ const marker = JSON.parse(readFileSync(markerPath, "utf-8")) as Record<string, unknown>;
145
+ if (!marker["genomeId"]) return null;
146
+ } catch {
147
+ return null;
148
+ }
149
+
150
+ return genomeDir;
151
+ } catch {
152
+ return null;
153
+ }
154
+ }