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,210 @@
1
+ /**
2
+ * Crash-dump channel — post-mortem records for handler crashes.
3
+ *
4
+ * When a handler in `_tool-base.ts` runStandalone catches a thrown exception,
5
+ * it already emits a `tool_crashed` observability event. This module adds an
6
+ * on-disk dump so maintainers can inspect the full args/stack later without
7
+ * re-running the crashing call.
8
+ *
9
+ * Layout: ~/.ashlr/crashes/<YYYY-MM-DD>.jsonl (append-only JSONL, one crash
10
+ * per line). Rotated to the last 7 days on each write.
11
+ *
12
+ * Contract:
13
+ * - Never throws — this is observability, not a critical path.
14
+ * - Inputs are redacted for common secret patterns before serialization.
15
+ * - args are truncated to 1 KB, stack to 4 KB, to bound disk growth.
16
+ */
17
+
18
+ import { appendFile, mkdir, readdir, unlink } from "fs/promises";
19
+ import { homedir } from "os";
20
+ import { dirname, join } from "path";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Config
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const MAX_ARGS_BYTES = 1024;
27
+ const MAX_STACK_BYTES = 4096;
28
+ const KEEP_DAYS = 7;
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Path helpers — resolved lazily so tests overriding $HOME work.
32
+ // ---------------------------------------------------------------------------
33
+
34
+ function home(): string {
35
+ return process.env.HOME ?? homedir();
36
+ }
37
+
38
+ export function crashesDir(): string {
39
+ return join(home(), ".ashlr", "crashes");
40
+ }
41
+
42
+ function crashFilePath(date = new Date()): string {
43
+ const key = date.toISOString().slice(0, 10); // YYYY-MM-DD
44
+ return join(crashesDir(), `${key}.jsonl`);
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Redaction
49
+ // ---------------------------------------------------------------------------
50
+
51
+ /**
52
+ * Redact common secret patterns from a string. Conservative: matches shapes
53
+ * more than specific tokens so a leaked key in an error message doesn't land
54
+ * unredacted on disk.
55
+ *
56
+ * - Authorization: Bearer <token>
57
+ * - sk-… / ghp_… / gho_… / ghu_… / ghs_… / npm_… style keys
58
+ * - 40-char hex (legacy git tokens)
59
+ * - base64-ish runs ≥ 32 chars with typical key entropy markers
60
+ * - key=value or "key": "value" for names like token, apiKey, secret, password
61
+ */
62
+ export function redactSecrets(input: string): string {
63
+ if (!input) return input;
64
+ let s = input;
65
+
66
+ // Authorization header values (Bearer / Basic)
67
+ s = s.replace(/(Authorization:\s*(?:Bearer|Basic)\s+)[^\s"'`]+/gi, "$1<redacted>");
68
+
69
+ // Provider-prefixed keys
70
+ s = s.replace(/\b(sk-[A-Za-z0-9_\-]{16,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|ghu_[A-Za-z0-9]{20,}|ghs_[A-Za-z0-9]{20,}|npm_[A-Za-z0-9]{20,})\b/g, "<redacted>");
71
+
72
+ // "token": "...", "apiKey": "...", etc. (JSON-ish)
73
+ s = s.replace(
74
+ /("(?:token|api[_-]?key|authorization|password|secret|cookie|session)"\s*:\s*")([^"\\]{4,})(")/gi,
75
+ "$1<redacted>$3",
76
+ );
77
+
78
+ // token=... / api_key=... / password=... (querystring / env-style)
79
+ s = s.replace(
80
+ /\b(token|api[_-]?key|authorization|password|secret)=([^\s&"']+)/gi,
81
+ "$1=<redacted>",
82
+ );
83
+
84
+ // Long hex (40+ chars) — legacy tokens
85
+ s = s.replace(/\b[a-f0-9]{40,}\b/gi, "<redacted-hex>");
86
+
87
+ return s;
88
+ }
89
+
90
+ function redactDeep(value: unknown, depth = 0): unknown {
91
+ if (depth > 6) return "<max-depth>";
92
+ if (value == null) return value;
93
+ if (typeof value === "string") return redactSecrets(value);
94
+ if (typeof value === "number" || typeof value === "boolean") return value;
95
+ if (Array.isArray(value)) return value.map((v) => redactDeep(v, depth + 1));
96
+ if (typeof value === "object") {
97
+ const out: Record<string, unknown> = {};
98
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
99
+ // Redact the *value* when the key itself looks sensitive, regardless of content.
100
+ if (/\b(token|api[_-]?key|authorization|password|secret|cookie)\b/i.test(k)) {
101
+ out[k] = "<redacted>";
102
+ } else {
103
+ out[k] = redactDeep(v, depth + 1);
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ return String(value);
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Public API
113
+ // ---------------------------------------------------------------------------
114
+
115
+ export interface CrashDumpInput {
116
+ tool: string;
117
+ args: unknown;
118
+ error: unknown;
119
+ }
120
+
121
+ export interface CrashDumpRecord {
122
+ ts: string;
123
+ tool: string;
124
+ message: string;
125
+ stack?: string;
126
+ args: string;
127
+ node?: string;
128
+ bun?: string;
129
+ }
130
+
131
+ function truncate(s: string, max: number): string {
132
+ if (s.length <= max) return s;
133
+ return s.slice(0, max) + `… [truncated ${s.length - max} chars]`;
134
+ }
135
+
136
+ function runtimeVersions(): { node?: string; bun?: string } {
137
+ const out: { node?: string; bun?: string } = {};
138
+ if (typeof process.versions?.node === "string") out.node = process.versions.node;
139
+ // Bun exposes Bun.version at runtime; guard for non-Bun envs.
140
+ const b = (globalThis as { Bun?: { version?: string } }).Bun;
141
+ if (b && typeof b.version === "string") out.bun = b.version;
142
+ return out;
143
+ }
144
+
145
+ /**
146
+ * Append a crash record to today's dump file and rotate old files.
147
+ * Never throws.
148
+ */
149
+ export async function writeCrashDump(input: CrashDumpInput): Promise<void> {
150
+ try {
151
+ const err = input.error;
152
+ const message = err instanceof Error ? err.message : String(err);
153
+ const rawStack = err instanceof Error && typeof err.stack === "string" ? err.stack : undefined;
154
+
155
+ // Serialize args to JSON first so the 1KB budget is predictable; fall back
156
+ // to a string coercion if the structure contains cycles.
157
+ let argsStr: string;
158
+ try {
159
+ argsStr = JSON.stringify(redactDeep(input.args));
160
+ } catch {
161
+ argsStr = redactSecrets(String(input.args));
162
+ }
163
+
164
+ const record: CrashDumpRecord = {
165
+ ts: new Date().toISOString(),
166
+ tool: input.tool,
167
+ message: redactSecrets(message),
168
+ stack: rawStack ? truncate(redactSecrets(rawStack), MAX_STACK_BYTES) : undefined,
169
+ args: truncate(argsStr, MAX_ARGS_BYTES),
170
+ ...runtimeVersions(),
171
+ };
172
+
173
+ const path = crashFilePath();
174
+ await mkdir(dirname(path), { recursive: true });
175
+ await appendFile(path, JSON.stringify(record) + "\n", "utf-8");
176
+
177
+ // Rotate older files past KEEP_DAYS. Best-effort — retention failures
178
+ // don't need to surface to the caller.
179
+ await rotateOldFiles().catch(() => undefined);
180
+ } catch {
181
+ // Never propagate — post-mortem must not crash the crashed process.
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Delete dump files older than KEEP_DAYS. Retains today + the prior 6 days.
187
+ */
188
+ async function rotateOldFiles(): Promise<void> {
189
+ const dir = crashesDir();
190
+ let names: string[];
191
+ try {
192
+ names = await readdir(dir);
193
+ } catch {
194
+ return;
195
+ }
196
+ const today = new Date();
197
+ // Build the set of accepted date keys for the retention window.
198
+ const keep = new Set<string>();
199
+ for (let i = 0; i < KEEP_DAYS; i++) {
200
+ const d = new Date(today.getTime() - i * 86_400_000);
201
+ keep.add(d.toISOString().slice(0, 10));
202
+ }
203
+ for (const name of names) {
204
+ if (!name.endsWith(".jsonl")) continue;
205
+ const dateKey = name.slice(0, 10);
206
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateKey)) continue;
207
+ if (keep.has(dateKey)) continue;
208
+ await unlink(join(dir, name)).catch(() => undefined);
209
+ }
210
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Shared cwd clamp for filesystem-touching MCP tools.
3
+ *
4
+ * The MCP tools in this directory run inside the user's Claude Code session
5
+ * with whatever filesystem access the host shell has. The calling model can
6
+ * be prompt-injected by third-party content (file contents, tool output,
7
+ * web pages it's summarizing) — so a tool that accepts an arbitrary `path`
8
+ * or `cwd` argument and then walks the filesystem is a direct disclosure
9
+ * channel: `glob pattern="**" cwd="/etc"` or `tree path="/"` both exfiltrate
10
+ * the host's filesystem layout.
11
+ *
12
+ * This helper clamps a caller-supplied path to an allow-list of roots:
13
+ * 1. `process.cwd()` (the MCP server's launch dir — usually the plugin cache)
14
+ * 2. `$CLAUDE_PROJECT_DIR` if set (Claude Code forwards this in hooks; we
15
+ * forward it through `scripts/mcp-entrypoint.ts` so MCP tools can touch
16
+ * the user's actual workspace, not just the plugin cache)
17
+ * 3. Any path in `$ASHLR_ALLOW_PROJECT_PATHS` (colon-separated on Unix,
18
+ * semicolon on Windows) — explicit user opt-in for plugin developers
19
+ * and users with multi-root workspaces
20
+ * 4. Fallback: `~/.ashlr/last-project.json::projectDir` — used ONLY when
21
+ * (2) and (3) are both unset AND process.cwd() looks like the plugin
22
+ * install dir. This handles the case where Claude Code does NOT forward
23
+ * CLAUDE_PROJECT_DIR to MCP subprocesses (verified behavior as of
24
+ * v1.19.0). The session-start hook writes the file with the real project
25
+ * dir, and the MCP server reads it as a last-resort hint.
26
+ *
27
+ * Only (1) is automatic; (2), (3), and (4) are trust anchors that only appear
28
+ * when the user or the host application has signed off: (2) + (3) via explicit
29
+ * env vars; (4) via a file that the session-start hook writes on behalf of
30
+ * Claude Code itself. A prompt-injected attacker can't inject new env vars or
31
+ * write into `~/.ashlr/` without already owning the host. The stale-file
32
+ * check + canonical() + existence-check below guard against attacker-crafted
33
+ * or old hint files opening new filesystem access.
34
+ *
35
+ * `path.relative()` is the right primitive because `startsWith(cwd + sep)`
36
+ * breaks on Windows drive roots (`C:\` already ends with a separator, and
37
+ * a cross-drive path resolves to an absolute path, not one starting with
38
+ * `..`).
39
+ */
40
+
41
+ import { readFileSync, realpathSync, statSync } from "fs";
42
+ import { homedir } from "os";
43
+ import { isAbsolute, join, relative, resolve, sep } from "path";
44
+
45
+ export type ClampResult =
46
+ | { ok: true; abs: string }
47
+ | { ok: false; message: string };
48
+
49
+ /**
50
+ * Resolve symlinks to a canonical form when possible, fall back to a plain
51
+ * resolve for paths that don't exist yet. macOS returns `process.cwd()` in
52
+ * canonical form (e.g., `/private/var/folders/…`) while user-supplied tmp
53
+ * paths come back as their symlinked shortcut (`/var/folders/…`). Without
54
+ * this canonicalization, the clamp check below would refuse legitimate
55
+ * paths inside cwd on macOS.
56
+ */
57
+ /** Last error code seen by canonical()'s realpathSync — distinguishes ENOENT
58
+ * (path doesn't exist, normal walk-up) from EACCES/ELOOP/etc. (symlink/jail
59
+ * boundary on Docker, chroot, sandboxed mounts). Read by the deny-path branch
60
+ * so its message can be specific when the failure isn't just a missing file. */
61
+ let _lastRealpathErrCode: string | null = null;
62
+
63
+ function canonical(p: string): string {
64
+ try {
65
+ _lastRealpathErrCode = null;
66
+ return realpathSync(p);
67
+ } catch (err: unknown) {
68
+ const errCode = (err as { code?: string } | null)?.code ?? null;
69
+ _lastRealpathErrCode = errCode;
70
+ if (errCode === "ENOENT") {
71
+ // expected — path missing, fall through to walk-up
72
+ } else {
73
+ // non-ENOENT (EACCES, ELOOP, EPERM, etc.) — likely a symlink/jail boundary
74
+ // on Docker, chroot, or restricted mount. Diagnostic message will say so.
75
+ }
76
+ // Path doesn't exist yet — walk up to the nearest existing ancestor,
77
+ // canonicalize that, and reattach the non-existent suffix. Without this
78
+ // walk-up, a path like "/var/tmp/abc/nope" (where /var → /private/var
79
+ // and "nope" doesn't exist) would compare unresolved against a
80
+ // canonicalized cwd and be wrongly refused on macOS.
81
+ //
82
+ // Cap the walk at MAX_WALK_UP segments so a prompt-injected caller can't
83
+ // pass a pathologically long path to force O(n) synchronous realpathSync
84
+ // failures. Real filesystems never nest anywhere near this deep; anything
85
+ // beyond it is either malicious or already outside any plausible cwd.
86
+ const parts = p.split(sep);
87
+ const MAX_WALK_UP = 32;
88
+ const start = parts.length - 1;
89
+ const stop = Math.max(1, start - MAX_WALK_UP);
90
+ for (let i = start; i >= stop; i--) {
91
+ let prefix = parts.slice(0, i).join(sep) || sep;
92
+ // Windows drive-letter-only prefixes ("D:", "C:") are *drive-relative*,
93
+ // not absolute: `realpathSync("D:")` resolves to the per-drive current
94
+ // working directory, not the drive root. Without this guard, a
95
+ // non-existent outside path like "D:\\etc" walks up to "D:", which
96
+ // canonicalizes to cwd, and then the suffix "etc" gets joined back —
97
+ // producing `<cwd>\\etc` and wrongly clamping an outside path *inside*
98
+ // cwd. Normalise to the drive root ("D:\\") so realpathSync returns
99
+ // the root itself instead of the per-drive CWD.
100
+ if (process.platform === "win32" && /^[A-Za-z]:$/.test(prefix)) {
101
+ prefix = prefix + sep;
102
+ }
103
+ try {
104
+ return join(realpathSync(prefix), ...parts.slice(i));
105
+ } catch {
106
+ // Shorter prefix; keep climbing.
107
+ }
108
+ }
109
+ return p;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Detect whether `process.cwd()` looks like an ashlr plugin install dir,
115
+ * i.e. a Claude Code plugin cache path. Used to gate the `last-project.json`
116
+ * fallback — we only read the file when the MCP server was spawned without
117
+ * a project-dir hint via env.
118
+ *
119
+ * Two signals:
120
+ * - Path contains `.claude/plugins/cache/` (normalized for Windows).
121
+ * - `$CLAUDE_PLUGIN_ROOT` is set and equals (canonically) process.cwd().
122
+ */
123
+ export function cwdLooksLikePluginRoot(cwd: string): boolean {
124
+ const normalized = cwd.replace(/\\/g, "/");
125
+ if (normalized.includes("/.claude/plugins/cache/")) return true;
126
+ const pluginRoot = process.env["CLAUDE_PLUGIN_ROOT"];
127
+ if (pluginRoot) {
128
+ try {
129
+ if (canonical(resolve(pluginRoot)) === cwd) return true;
130
+ } catch {
131
+ // ignore — plugin root may not exist in tests
132
+ }
133
+ }
134
+ return false;
135
+ }
136
+
137
+ /**
138
+ * Staleness check for `last-project.json`. Returns true iff the hint was
139
+ * written in the last 24h. This guards against reviving access to an old
140
+ * project after the user has moved on — a stale hint is effectively an
141
+ * ambient-authority grant we want to decay.
142
+ */
143
+ const LAST_PROJECT_TTL_MS = 24 * 60 * 60 * 1000;
144
+ function hintIsFresh(updatedAt: string | undefined): boolean {
145
+ if (!updatedAt) return false;
146
+ const t = Date.parse(updatedAt);
147
+ if (!Number.isFinite(t)) return false;
148
+ return Date.now() - t <= LAST_PROJECT_TTL_MS;
149
+ }
150
+
151
+ /**
152
+ * Resolve the effective $HOME for the hint file. Respects
153
+ * `ASHLR_HOME_OVERRIDE` as a test-only escape hatch (Bun caches `homedir()`
154
+ * at startup and does not re-read `HOME` on mutation, so tests can't rely
155
+ * on `process.env.HOME =` the way Node does). In production this env var
156
+ * is unset and we fall back to `homedir()`.
157
+ */
158
+ function resolveHintHome(): string {
159
+ const override = process.env["ASHLR_HOME_OVERRIDE"];
160
+ if (override && override.trim()) return override.trim();
161
+ return homedir();
162
+ }
163
+
164
+ /**
165
+ * Read the `~/.ashlr/last-project.json` hint file (if present + fresh) and
166
+ * return its canonicalised `projectDir`. Returns null when:
167
+ * - file missing / unreadable / invalid JSON
168
+ * - `updatedAt` older than 24h
169
+ * - `projectDir` missing, not a string, or does not resolve to an existing
170
+ * directory (stale entries from a deleted workspace are ignored)
171
+ *
172
+ * The canonical() + existsSync-via-statSync gauntlet ensures an attacker who
173
+ * can write into `~/.ashlr/last-project.json` can't grant access to
174
+ * non-existent paths — but note: if they can write `~/.ashlr/`, they already
175
+ * own the host. The check is defense-in-depth, not a security boundary.
176
+ *
177
+ * Exported with the `_...ForTest` prefix to signal it is for the companion
178
+ * cwd-clamp tests only; `allowedRoots()` below is the production caller.
179
+ */
180
+ export function _readLastProjectHintForTest(home: string = resolveHintHome()): string | null {
181
+ try {
182
+ const p = join(home, ".ashlr", "last-project.json");
183
+ const raw = readFileSync(p, "utf-8");
184
+ const parsed = JSON.parse(raw) as {
185
+ projectDir?: unknown;
186
+ updatedAt?: unknown;
187
+ };
188
+ if (typeof parsed.projectDir !== "string" || !parsed.projectDir) return null;
189
+ if (typeof parsed.updatedAt !== "string" || !hintIsFresh(parsed.updatedAt)) {
190
+ return null;
191
+ }
192
+ const abs = canonical(resolve(parsed.projectDir));
193
+ // Directory must currently exist — a hint pointing at a deleted
194
+ // workspace must not extend the allow-list.
195
+ const st = statSync(abs);
196
+ if (!st.isDirectory()) return null;
197
+ return abs;
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Collect the canonicalized allow-list of root paths the clamp accepts.
205
+ * Always includes `process.cwd()`; optionally extends with
206
+ * `CLAUDE_PROJECT_DIR` (from Claude Code) and `ASHLR_ALLOW_PROJECT_PATHS`
207
+ * (user opt-in). When neither is set *and* the MCP server was spawned from
208
+ * the plugin install dir, falls back to reading a project-dir hint written
209
+ * by the session-start hook. Deduped so refusal messages stay readable.
210
+ */
211
+ function allowedRoots(): string[] {
212
+ const cwdAbs = canonical(process.cwd());
213
+ const roots: string[] = [cwdAbs];
214
+
215
+ // User-owned config dirs (~/.claude, ~/.ashlr): always allowed. The agent
216
+ // routinely needs to touch ~/.claude/plans, ~/.claude/CLAUDE.md, and
217
+ // ~/.ashlr/{settings,config}.json during normal work — refusing those paths
218
+ // sends every such call back to the built-in Edit/Read fallback and forfeits
219
+ // the per-call savings (12+ refusals/day measured in v1.20.1). Both dirs are
220
+ // gated by host-level filesystem permissions; a prompt-injected attacker
221
+ // who could already read/write the user's project can already reach them
222
+ // via shell, so adding them to the allow-list does not widen the threat
223
+ // surface meaningfully.
224
+ for (const sub of [".claude", ".ashlr"]) {
225
+ try {
226
+ const abs = canonical(resolve(homedir(), sub));
227
+ if (!roots.includes(abs)) roots.push(abs);
228
+ } catch {
229
+ // Home dir missing or sub-dir doesn't exist — skip silently.
230
+ }
231
+ }
232
+
233
+ const claudeProjectDir = process.env["CLAUDE_PROJECT_DIR"];
234
+ if (claudeProjectDir) {
235
+ try {
236
+ const abs = canonical(resolve(claudeProjectDir));
237
+ if (!roots.includes(abs)) roots.push(abs);
238
+ } catch {
239
+ // Env var pointed at a path we can't stat — ignore silently. The
240
+ // clamp falls back to the other roots. Logging would spam stderr on
241
+ // every tool call in a workspace with a misspelled env.
242
+ }
243
+ }
244
+
245
+ const envPaths = process.env["ASHLR_ALLOW_PROJECT_PATHS"];
246
+ if (envPaths) {
247
+ // Platform-appropriate PATH separator. Users setting this in a
248
+ // cross-platform shell are most likely to use ":" on Unix and ";" on
249
+ // Windows — match Node's `path.delimiter` convention.
250
+ const delim = process.platform === "win32" ? ";" : ":";
251
+ for (const raw of envPaths.split(delim)) {
252
+ const trimmed = raw.trim();
253
+ if (!trimmed) continue;
254
+ try {
255
+ const abs = canonical(resolve(trimmed));
256
+ if (!roots.includes(abs)) roots.push(abs);
257
+ } catch {
258
+ // Invalid path entry — skip and keep going.
259
+ }
260
+ }
261
+ }
262
+
263
+ // File-based fallback (v1.19.1 hotfix): when *neither* env var is set AND
264
+ // process.cwd() looks like the plugin install dir, read the last-project
265
+ // hint written by the session-start hook. Claude Code does NOT forward
266
+ // CLAUDE_PROJECT_DIR to MCP subprocesses, so without this fallback the
267
+ // MCP server can only see files under the plugin cache.
268
+ //
269
+ // Env wins: if either env var is set, we trust that signal and skip the
270
+ // file entirely (preserves pre-v1.19.1 semantics + keeps tests
271
+ // deterministic when callers explicitly scope the allow-list via env).
272
+ if (!claudeProjectDir && !envPaths && cwdLooksLikePluginRoot(cwdAbs)) {
273
+ const hint = _readLastProjectHintForTest();
274
+ if (hint && !roots.includes(hint)) roots.push(hint);
275
+ }
276
+
277
+ return roots;
278
+ }
279
+
280
+ /**
281
+ * Pick the user's primary project root from the allow-list.
282
+ *
283
+ * Skips paths that look like a plugin install (cache dir or $CLAUDE_PLUGIN_ROOT)
284
+ * and the HOME config dirs (~/.claude, ~/.ashlr). Falls back to `process.cwd()`
285
+ * when no project-shaped root is available — the cwd-clamp will then refuse
286
+ * the call, which is the correct error surface.
287
+ *
288
+ * Used by tools that take an optional `cwd` argument (ashlr__grep, ashlr__tree)
289
+ * so omitting `cwd` searches the user's project rather than the plugin install
290
+ * directory the MCP server happens to be living in.
291
+ */
292
+ export function primaryProjectRoot(): string {
293
+ const home = canonical(homedir());
294
+ const homeConfigDirs = new Set([
295
+ canonical(join(home, ".claude")),
296
+ canonical(join(home, ".ashlr")),
297
+ ]);
298
+ for (const root of allowedRoots()) {
299
+ if (cwdLooksLikePluginRoot(root)) continue;
300
+ if (homeConfigDirs.has(root)) continue;
301
+ return root;
302
+ }
303
+ return canonical(process.cwd());
304
+ }
305
+
306
+ /**
307
+ * Resolve `userPath` against the allow-listed roots and verify it stays
308
+ * inside at least one of them.
309
+ *
310
+ * Returns `{ ok: true, abs }` on success — caller uses `abs` as the
311
+ * filesystem target. Returns `{ ok: false, message }` on refusal — caller
312
+ * should surface `message` verbatim to the model (matches the ls-server
313
+ * convention so agents learn one refusal shape).
314
+ */
315
+ export function clampToCwd(
316
+ userPath: string | undefined,
317
+ toolName: string,
318
+ ): ClampResult {
319
+ const roots = allowedRoots();
320
+ const rootAbs = canonical(resolve(userPath ?? "."));
321
+
322
+ for (const root of roots) {
323
+ const rel = relative(root, rootAbs);
324
+ const insideRoot = rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
325
+ if (insideRoot) return { ok: true, abs: rootAbs };
326
+ }
327
+
328
+ const primary = roots[0];
329
+ const extra = roots.length > 1 ? `; also allowed: ${roots.slice(1).join(", ")}` : "";
330
+ // v1.21 Track E: when realpathSync failed with a non-ENOENT code, the
331
+ // path may not actually be "outside cwd" — it might be inside a symlink/jail
332
+ // boundary that realpath couldn't traverse. Surface that distinction so the
333
+ // user knows whether to pass a different path or check Docker mount perms.
334
+ const jailDiag = _lastRealpathErrCode && _lastRealpathErrCode !== "ENOENT"
335
+ ? ` — realpath failed [${_lastRealpathErrCode}], likely symlink/jail boundary`
336
+ : "";
337
+ const absInput = rootAbs;
338
+ return {
339
+ ok: false,
340
+ message: `${toolName}: refused path outside working directory: ${absInput}${jailDiag}\n(cwd is ${primary}${extra})`,
341
+ };
342
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * _edit-log — shared edit session log used by edit-server + flush-server.
3
+ *
4
+ * All edits write immediately to disk; this log is a "what did I just write?"
5
+ * reporting aid — ashlr__flush drains it to return a compact batch summary.
6
+ *
7
+ * Module-level singleton (process-wide). Lives for a single MCP server session.
8
+ */
9
+
10
+ export interface EditLogEntry {
11
+ relPath: string;
12
+ hunksApplied: number;
13
+ }
14
+
15
+ const editLog: EditLogEntry[] = [];
16
+
17
+ /** Append one edit record to the log. */
18
+ export function appendEdit(entry: EditLogEntry): void {
19
+ editLog.push(entry);
20
+ }
21
+
22
+ /**
23
+ * Drain and return all pending edits. The log is empty after this call.
24
+ * Returns an empty array if nothing was logged since the last drain.
25
+ */
26
+ export function drainEdits(): EditLogEntry[] {
27
+ return editLog.splice(0, editLog.length);
28
+ }