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,196 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * install-genome-hooks — wire the genome commit-watcher into `.git/hooks/post-commit`.
4
+ *
5
+ * Opt-in. Called from `scripts/genome-init.ts` after a successful init (or
6
+ * manually). Idempotent — running it twice is a no-op.
7
+ *
8
+ * Strategy:
9
+ * 1. Locate the git directory (honors `core.hooksPath`).
10
+ * 2. If no post-commit hook exists, write a fresh one that invokes
11
+ * `bun run scripts/genome-commit-watcher.ts` against the repo root.
12
+ * 3. If a post-commit hook already exists AND already calls the watcher,
13
+ * do nothing.
14
+ * 4. Otherwise, APPEND a chained block that runs the watcher. We never
15
+ * overwrite a user's existing hook content.
16
+ *
17
+ * Performance contract: the appended shim runs the watcher in <1s typical.
18
+ * We `&` the watcher to background it so even a degenerate case (cold bun
19
+ * startup) doesn't block the commit. Errors are swallowed via `|| true`.
20
+ *
21
+ * Usage:
22
+ * bun run scripts/install-genome-hooks.ts [--cwd <dir>] [--force]
23
+ */
24
+
25
+ import { execFileSync } from "child_process";
26
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
27
+ import { dirname, join, relative, resolve } from "path";
28
+
29
+ export const HOOK_MARKER_BEGIN = "# >>> ashlr genome-commit-watcher >>>";
30
+ export const HOOK_MARKER_END = "# <<< ashlr genome-commit-watcher <<<";
31
+
32
+ export interface InstallerArgs {
33
+ cwd: string;
34
+ force: boolean;
35
+ }
36
+
37
+ export function parseInstallerArgs(argv: string[]): InstallerArgs {
38
+ let cwd = process.cwd();
39
+ let force = false;
40
+ for (let i = 0; i < argv.length; i++) {
41
+ const a = argv[i];
42
+ if (a === "--cwd" && argv[i + 1]) cwd = resolve(argv[++i]!);
43
+ else if (a === "--force") force = true;
44
+ }
45
+ return { cwd, force };
46
+ }
47
+
48
+ /**
49
+ * Discover the hooks dir for the repo rooted at `cwd`.
50
+ *
51
+ * Honors core.hooksPath (e.g. when a project uses lefthook / husky). Returns
52
+ * null when `cwd` is not inside a git repo.
53
+ *
54
+ * Exported so the installer can be tested against a fixture repo.
55
+ */
56
+ export function findHooksDir(cwd: string): string | null {
57
+ try {
58
+ const gitCommonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
59
+ cwd,
60
+ encoding: "utf-8",
61
+ stdio: ["ignore", "pipe", "pipe"],
62
+ }).trim();
63
+ if (!gitCommonDir) return null;
64
+ const gitDir = resolve(cwd, gitCommonDir);
65
+
66
+ // Respect core.hooksPath when set.
67
+ let configured = "";
68
+ try {
69
+ configured = execFileSync("git", ["config", "--get", "core.hooksPath"], {
70
+ cwd,
71
+ encoding: "utf-8",
72
+ stdio: ["ignore", "pipe", "pipe"],
73
+ }).trim();
74
+ } catch {
75
+ configured = "";
76
+ }
77
+ if (configured) {
78
+ return resolve(cwd, configured);
79
+ }
80
+ return join(gitDir, "hooks");
81
+ } catch {
82
+ return null;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Render the shell block we inject into post-commit.
88
+ *
89
+ * The shim resolves the watcher path relative to the repo root (passed as
90
+ * argument) so it works regardless of where the user runs git from.
91
+ */
92
+ export function renderHookBlock(repoRoot: string, watcherPath: string): string {
93
+ const rel = relative(repoRoot, watcherPath) || "scripts/genome-commit-watcher.ts";
94
+ return [
95
+ HOOK_MARKER_BEGIN,
96
+ "# Auto-installed by scripts/install-genome-hooks.ts — see that file for details.",
97
+ "# Backgrounded + best-effort so it never blocks or fails a commit.",
98
+ `if command -v bun >/dev/null 2>&1; then`,
99
+ ` ( cd "$(git rev-parse --show-toplevel)" && bun run "${rel}" >/dev/null 2>&1 & ) || true`,
100
+ `fi`,
101
+ HOOK_MARKER_END,
102
+ ].join("\n");
103
+ }
104
+
105
+ export interface InstallResult {
106
+ installed: boolean;
107
+ /** Reason for skipping when installed === false. */
108
+ reason?: "no-git" | "already-installed";
109
+ hookPath?: string;
110
+ }
111
+
112
+ /**
113
+ * Install (or re-install) the post-commit hook for the repo at `cwd`.
114
+ *
115
+ * Idempotent: if the marker block is already present, returns
116
+ * `{ installed: false, reason: "already-installed" }`.
117
+ *
118
+ * When --force is passed we replace any existing marker block with a fresh one.
119
+ */
120
+ export function installPostCommitHook(args: InstallerArgs): InstallResult {
121
+ const hooksDir = findHooksDir(args.cwd);
122
+ if (!hooksDir) return { installed: false, reason: "no-git" };
123
+
124
+ if (!existsSync(hooksDir)) {
125
+ mkdirSync(hooksDir, { recursive: true });
126
+ }
127
+
128
+ const hookPath = join(hooksDir, "post-commit");
129
+ const watcherAbs = resolve(args.cwd, "scripts", "genome-commit-watcher.ts");
130
+ const block = renderHookBlock(args.cwd, watcherAbs);
131
+
132
+ let existing = "";
133
+ if (existsSync(hookPath)) {
134
+ existing = readFileSync(hookPath, "utf-8");
135
+ }
136
+
137
+ const hasMarker = existing.includes(HOOK_MARKER_BEGIN);
138
+
139
+ if (hasMarker && !args.force) {
140
+ return { installed: false, reason: "already-installed", hookPath };
141
+ }
142
+
143
+ let next: string;
144
+ if (!existing.trim()) {
145
+ // Fresh hook — write a complete shebang script.
146
+ next = `#!/usr/bin/env bash\n# Managed in part by ashlr-plugin. Custom logic below the marker is preserved.\n\n${block}\n`;
147
+ } else if (hasMarker) {
148
+ // Replace the marker block (force path).
149
+ const re = new RegExp(
150
+ `${escapeRegex(HOOK_MARKER_BEGIN)}[\\s\\S]*?${escapeRegex(HOOK_MARKER_END)}\\n?`,
151
+ "g",
152
+ );
153
+ next = existing.replace(re, `${block}\n`);
154
+ } else {
155
+ // Chain — append at the end, preserving any existing logic above.
156
+ const sep = existing.endsWith("\n") ? "" : "\n";
157
+ next = `${existing}${sep}\n${block}\n`;
158
+ }
159
+
160
+ writeFileSync(hookPath, next, "utf-8");
161
+ try {
162
+ chmodSync(hookPath, 0o755);
163
+ } catch {
164
+ // chmod may fail on Windows — the file is still written.
165
+ }
166
+ return { installed: true, hookPath };
167
+ }
168
+
169
+ function escapeRegex(s: string): string {
170
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // CLI
175
+ // ---------------------------------------------------------------------------
176
+
177
+ export function main(argv: string[] = process.argv.slice(2)): number {
178
+ const args = parseInstallerArgs(argv);
179
+ const res = installPostCommitHook(args);
180
+ if (res.installed) {
181
+ // eslint-disable-next-line no-console
182
+ console.log(`installed post-commit hook → ${res.hookPath}`);
183
+ return 0;
184
+ }
185
+ if (res.reason === "no-git") {
186
+ console.error("not a git repo — skipped");
187
+ return 1;
188
+ }
189
+ // eslint-disable-next-line no-console
190
+ console.log(`post-commit hook already installed at ${res.hookPath}`);
191
+ return 0;
192
+ }
193
+
194
+ if ((import.meta as { main?: boolean }).main) {
195
+ process.exit(main());
196
+ }
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Install the ONNX summarization model for ashlr's offline provider.
4
+ *
5
+ * Target model: Xenova/distilbart-cnn-6-6 (seq2seq text summarization, ~300MB)
6
+ * Source: https://huggingface.co/Xenova/distilbart-cnn-6-6
7
+ * Model dir: ~/.ashlr/models/distilbart/
8
+ *
9
+ * After running this script:
10
+ * - The ONNX provider activates automatically when ANTHROPIC_API_KEY is absent.
11
+ * - Or force it: ASHLR_LLM_PROVIDER=onnx
12
+ *
13
+ * Note: onnxruntime-node must be installed first (optional dependency):
14
+ * bun add onnxruntime-node # or: npm install onnxruntime-node
15
+ *
16
+ * Disk space: ~300MB total (encoder ~200MB + decoder ~100MB + tokenizer files).
17
+ * This is why the model is NOT bundled by default — run this script only if you
18
+ * want offline summarization without an Anthropic API key.
19
+ */
20
+
21
+ import { mkdir, writeFile } from "fs/promises";
22
+ import { existsSync, statSync } from "fs";
23
+ import { homedir } from "os";
24
+ import { join } from "path";
25
+
26
+ function home(): string {
27
+ return process.env.HOME ?? homedir();
28
+ }
29
+
30
+ const MODEL_DIR = join(home(), ".ashlr", "models", "distilbart");
31
+
32
+ // Hugging Face ONNX-exported files from the Xenova/transformers.js project.
33
+ const HF_BASE = "https://huggingface.co/Xenova/distilbart-cnn-6-6/resolve/main";
34
+ const MODEL_FILES = [
35
+ { name: "config.json", path: "config.json", sizeMB: "<1" },
36
+ { name: "tokenizer.json", path: "tokenizer.json", sizeMB: "1" },
37
+ { name: "tokenizer_config.json", path: "tokenizer_config.json", sizeMB: "<1" },
38
+ { name: "onnx/encoder_model.onnx", path: "onnx/encoder_model.onnx", sizeMB: "195" },
39
+ { name: "onnx/decoder_model_merged.onnx", path: "onnx/decoder_model_merged.onnx", sizeMB: "107" },
40
+ ];
41
+
42
+ function fmtBytes(bytes: number): string {
43
+ if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`;
44
+ if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB`;
45
+ return `${bytes} B`;
46
+ }
47
+
48
+ async function downloadFile(url: string, dest: string): Promise<void> {
49
+ const dir = join(dest, "..");
50
+ await mkdir(dir, { recursive: true });
51
+ const ctl = new AbortController();
52
+ const timeout = setTimeout(() => ctl.abort(), 10 * 60 * 1000); // 10min per file
53
+ try {
54
+ const res = await fetch(url, { signal: ctl.signal });
55
+ if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText} for ${url}`);
56
+ const buf = await res.arrayBuffer();
57
+ await writeFile(dest, new Uint8Array(buf));
58
+ } finally {
59
+ clearTimeout(timeout);
60
+ }
61
+ }
62
+
63
+ async function main(): Promise<void> {
64
+ console.log("ashlr ONNX model installer");
65
+ console.log("Model: Xenova/distilbart-cnn-6-6 (seq2seq summarization)");
66
+ console.log(`Model directory: ${MODEL_DIR}`);
67
+ console.log("");
68
+
69
+ // Check onnxruntime-node
70
+ let onnxInstalled = false;
71
+ try {
72
+ require("onnxruntime-node");
73
+ console.log("✓ onnxruntime-node: installed");
74
+ onnxInstalled = true;
75
+ } catch {
76
+ console.log("⚠ onnxruntime-node: NOT installed");
77
+ console.log("");
78
+ console.log("The ONNX provider requires onnxruntime-node. Install it with:");
79
+ console.log(" bun add onnxruntime-node");
80
+ console.log(" # or: npm install onnxruntime-node");
81
+ console.log("");
82
+ console.log("Continuing to download the model files — you can install onnxruntime-node after.");
83
+ console.log("");
84
+ }
85
+
86
+ await mkdir(join(MODEL_DIR, "onnx"), { recursive: true });
87
+
88
+ console.log(`Downloading ${MODEL_FILES.length} files from Hugging Face (~300MB total)...`);
89
+ console.log("This will take several minutes on a typical connection.");
90
+ console.log("");
91
+
92
+ let downloaded = 0;
93
+ let skipped = 0;
94
+ let failed = 0;
95
+
96
+ for (const file of MODEL_FILES) {
97
+ const dest = join(MODEL_DIR, file.name);
98
+ if (existsSync(dest)) {
99
+ const size = statSync(dest).size;
100
+ console.log(` SKIP ${file.name} (${fmtBytes(size)}, already exists)`);
101
+ skipped++;
102
+ continue;
103
+ }
104
+ const url = `${HF_BASE}/${file.path}`;
105
+ process.stdout.write(` DL ${file.name} (~${file.sizeMB} MB) ... `);
106
+ try {
107
+ await downloadFile(url, dest);
108
+ const size = statSync(dest).size;
109
+ console.log(`done (${fmtBytes(size)})`);
110
+ downloaded++;
111
+ } catch (e) {
112
+ console.log(`FAILED: ${e instanceof Error ? e.message : String(e)}`);
113
+ failed++;
114
+ }
115
+ }
116
+
117
+ console.log("");
118
+
119
+ if (failed > 0) {
120
+ console.log(`✗ ${failed} file(s) failed to download. Check your connection and retry.`);
121
+ process.exit(1);
122
+ }
123
+
124
+ // Total size
125
+ let totalBytes = 0;
126
+ for (const file of MODEL_FILES) {
127
+ const dest = join(MODEL_DIR, file.name);
128
+ if (existsSync(dest)) totalBytes += statSync(dest).size;
129
+ }
130
+
131
+ console.log(`✓ Model installed: ${fmtBytes(totalBytes)} in ${MODEL_DIR}`);
132
+ if (downloaded > 0) console.log(` ${downloaded} file(s) downloaded, ${skipped} skipped`);
133
+ console.log("");
134
+
135
+ if (!onnxInstalled) {
136
+ console.log("Next step: install onnxruntime-node to activate the ONNX provider:");
137
+ console.log(" bun add onnxruntime-node");
138
+ console.log(" # or: npm install onnxruntime-node");
139
+ console.log("");
140
+ } else {
141
+ console.log("ONNX provider is ready. It activates automatically when:");
142
+ console.log(" - ANTHROPIC_API_KEY is not set");
143
+ console.log(" - ASHLR_LLM_URL is not set");
144
+ console.log("");
145
+ console.log("Or force it: ASHLR_LLM_PROVIDER=onnx");
146
+ }
147
+
148
+ console.log("Verify the install: bun run /ashlr-doctor");
149
+ }
150
+
151
+ main().catch((e) => {
152
+ console.error("Fatal:", e instanceof Error ? e.message : String(e));
153
+ process.exit(1);
154
+ });
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * install-permissions.ts — add ashlr MCP tool wildcards to ~/.claude/settings.json
4
+ * so Claude Code stops prompting on every mcp__ashlr-*__ call in bypassPermissions mode.
5
+ *
6
+ * Usage:
7
+ * bun run scripts/install-permissions.ts # add entries
8
+ * bun run scripts/install-permissions.ts --dry-run # preview without writing
9
+ * bun run scripts/install-permissions.ts --remove # undo (strip ashlr entries)
10
+ * bun run scripts/install-permissions.ts --settings /path/to/settings.json
11
+ *
12
+ * Exits 0 on success; 1 only on unrecoverable I/O errors.
13
+ */
14
+
15
+ import { existsSync } from "fs";
16
+ import { mkdir, readFile, realpath, rename, writeFile } from "fs/promises";
17
+ import { homedir } from "os";
18
+ import { dirname, join } from "path";
19
+ import { randomBytes } from "crypto";
20
+ import { fileURLToPath } from "url";
21
+
22
+ // ---------- types ----------
23
+
24
+ export interface PermissionsResult {
25
+ added: string[];
26
+ alreadyPresent: string[];
27
+ removed: string[];
28
+ dryRun: boolean;
29
+ settingsPath: string;
30
+ }
31
+
32
+ // ---------- plugin.json helpers ----------
33
+
34
+ function findPluginRoot(startDir: string): string | null {
35
+ const envRoot = process.env.CLAUDE_PLUGIN_ROOT;
36
+ if (envRoot && existsSync(join(envRoot, ".claude-plugin/plugin.json"))) return envRoot;
37
+ let dir = startDir;
38
+ for (let i = 0; i < 10; i++) {
39
+ if (existsSync(join(dir, ".claude-plugin/plugin.json"))) return dir;
40
+ const parent = dirname(dir);
41
+ if (parent === dir) break;
42
+ dir = parent;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export async function readMcpServerNames(pluginRoot: string): Promise<string[]> {
48
+ try {
49
+ const raw = await readFile(join(pluginRoot, ".claude-plugin/plugin.json"), "utf8");
50
+ const parsed = JSON.parse(raw) as { mcpServers?: Record<string, unknown> };
51
+ if (parsed.mcpServers && typeof parsed.mcpServers === "object") {
52
+ const keys = Object.keys(parsed.mcpServers);
53
+ // v1.13 router consolidation: a single "ashlr" entry fronts every tool.
54
+ // Existing user settings.json blocks (mcp__ashlr-efficiency__*, etc.)
55
+ // still need the per-server wildcards to match, so fall through to the
56
+ // hardcoded list below when the manifest is collapsed.
57
+ if (!(keys.length === 1 && keys[0] === "ashlr")) {
58
+ return keys;
59
+ }
60
+ }
61
+ } catch {
62
+ // fall through to default list
63
+ }
64
+ // fallback: hard-coded list in case plugin.json is unreadable
65
+ return [
66
+ "ashlr-efficiency",
67
+ "ashlr-sql",
68
+ "ashlr-bash",
69
+ "ashlr-tree",
70
+ "ashlr-http",
71
+ "ashlr-diff",
72
+ "ashlr-logs",
73
+ "ashlr-genome",
74
+ "ashlr-orient",
75
+ "ashlr-github",
76
+ "ashlr-glob",
77
+ "ashlr-webfetch",
78
+ ];
79
+ }
80
+
81
+ /** Build the full list of allow entries we manage: per-server wildcards + catch-all. */
82
+ export function buildAshlrEntries(serverNames: string[]): string[] {
83
+ const entries: string[] = serverNames.map((name) => `mcp__${name}__*`);
84
+ // Legacy catch-all for pre-router `mcp__ashlr-<server>__*` names.
85
+ entries.push("mcp__ashlr-*");
86
+ // v1.13+ router canonical names are `mcp__plugin_ashlr_ashlr__ashlr__<tool>`.
87
+ // Without this entry /ashlr-allow silently fails to stop Claude Code from
88
+ // prompting on every tool call. Covers every future router-exposed tool.
89
+ entries.push("mcp__plugin_ashlr_*");
90
+ return entries;
91
+ }
92
+
93
+ /** Returns true if `entry` is one we own (matches ashlr pattern). */
94
+ export function isAshlrEntry(entry: string): boolean {
95
+ // Both catch-all literals (`mcp__ashlr-*`, `mcp__plugin_ashlr_*`) are covered
96
+ // by these prefix regexes — no need for explicit string equality checks.
97
+ return /^mcp__ashlr(-|__)/.test(entry) || /^mcp__plugin_ashlr_/.test(entry);
98
+ }
99
+
100
+ // ---------- settings.json I/O ----------
101
+
102
+ export async function readSettings(settingsPath: string): Promise<Record<string, unknown>> {
103
+ try {
104
+ // Resolve symlinks so rename() lands at the real path
105
+ const resolved = await realpath(settingsPath).catch(() => settingsPath);
106
+ const raw = await readFile(resolved, "utf8");
107
+ const parsed = JSON.parse(raw);
108
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
109
+ return parsed as Record<string, unknown>;
110
+ }
111
+ } catch {
112
+ // missing file or malformed JSON → start fresh
113
+ }
114
+ return {};
115
+ }
116
+
117
+ async function writeSettingsAtomic(settingsPath: string, data: Record<string, unknown>): Promise<void> {
118
+ await mkdir(dirname(settingsPath), { recursive: true });
119
+ const tmp = settingsPath + ".tmp." + process.pid + "." + randomBytes(3).toString("hex");
120
+ await writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
121
+ try {
122
+ // If the file is a symlink resolve to target for the rename
123
+ const target = await realpath(settingsPath).catch(() => settingsPath);
124
+ await rename(tmp, target);
125
+ } catch (err) {
126
+ // best-effort: temp file cleanup on a failed rename — if unlink itself fails (e.g. file already gone) the original rename error is still what the caller needs to see, so swallow this.
127
+ await import("fs/promises").then((m) => m.unlink(tmp)).catch(() => {});
128
+ throw err;
129
+ }
130
+ }
131
+
132
+ // ---------- core logic ----------
133
+
134
+ export interface InstallOptions {
135
+ dryRun?: boolean;
136
+ remove?: boolean;
137
+ settingsPath?: string;
138
+ pluginRoot?: string;
139
+ /**
140
+ * When true, suppress any stdin prompts and proceed with the write
141
+ * unconditionally. Used by `hooks/session-start.ts` first-run
142
+ * auto-consent flow so the hook never blocks waiting for input.
143
+ */
144
+ nonInteractive?: boolean;
145
+ }
146
+
147
+ export async function installPermissions(opts: InstallOptions = {}): Promise<PermissionsResult> {
148
+ const here = dirname(fileURLToPath(import.meta.url));
149
+ const resolvedRoot = opts.pluginRoot ?? findPluginRoot(here) ?? here;
150
+ const settingsPath = opts.settingsPath ?? join(homedir(), ".claude/settings.json");
151
+
152
+ const serverNames = await readMcpServerNames(resolvedRoot);
153
+ const ashlrEntries = buildAshlrEntries(serverNames);
154
+
155
+ const settings = await readSettings(settingsPath);
156
+
157
+ // Ensure permissions.allow exists
158
+ if (!settings.permissions || typeof settings.permissions !== "object" || Array.isArray(settings.permissions)) {
159
+ settings.permissions = {};
160
+ }
161
+ const perms = settings.permissions as Record<string, unknown>;
162
+ if (!Array.isArray(perms.allow)) {
163
+ perms.allow = [];
164
+ }
165
+ const allow = perms.allow as string[];
166
+
167
+ const result: PermissionsResult = {
168
+ added: [],
169
+ alreadyPresent: [],
170
+ removed: [],
171
+ dryRun: opts.dryRun ?? false,
172
+ settingsPath,
173
+ };
174
+
175
+ if (opts.remove) {
176
+ // Strip all ashlr entries
177
+ const before = allow.length;
178
+ const kept = allow.filter((e) => !isAshlrEntry(e));
179
+ result.removed = allow.filter((e) => isAshlrEntry(e));
180
+ if (!opts.dryRun && result.removed.length > 0) {
181
+ perms.allow = kept;
182
+ await writeSettingsAtomic(settingsPath, settings);
183
+ }
184
+ return result;
185
+ }
186
+
187
+ // Add mode: idempotent. Per-server wildcards (e.g. `mcp__ashlr-foo__*`) are
188
+ // subsumed by the catch-alls `mcp__ashlr-*` / `mcp__plugin_ashlr_*` — when
189
+ // a catch-all is already present, skip the per-server entries it covers
190
+ // instead of re-adding them on every invocation.
191
+ const hasAshlrCatchAll = allow.includes("mcp__ashlr-*");
192
+ const hasPluginCatchAll = allow.includes("mcp__plugin_ashlr_*");
193
+ for (const entry of ashlrEntries) {
194
+ const isCatchAll = entry === "mcp__ashlr-*" || entry === "mcp__plugin_ashlr_*";
195
+ const subsumed =
196
+ !isCatchAll &&
197
+ ((hasAshlrCatchAll && entry.startsWith("mcp__ashlr-")) ||
198
+ (hasPluginCatchAll && entry.startsWith("mcp__plugin_ashlr_")));
199
+ if (allow.includes(entry) || subsumed) {
200
+ result.alreadyPresent.push(entry);
201
+ } else {
202
+ result.added.push(entry);
203
+ }
204
+ }
205
+
206
+ if (!opts.dryRun && result.added.length > 0) {
207
+ for (const entry of result.added) {
208
+ allow.push(entry);
209
+ }
210
+ await writeSettingsAtomic(settingsPath, settings);
211
+ }
212
+
213
+ return result;
214
+ }
215
+
216
+ // ---------- main ----------
217
+
218
+ function formatResult(r: PermissionsResult): string {
219
+ const lines: string[] = [];
220
+ const prefix = r.dryRun ? "[dry-run] " : "";
221
+
222
+ if (r.removed.length > 0) {
223
+ lines.push(`${prefix}Removed ${r.removed.length} ashlr permission entr${r.removed.length === 1 ? "y" : "ies"}:`);
224
+ for (const e of r.removed) lines.push(` - ${e}`);
225
+ } else if (r.added.length === 0 && r.alreadyPresent.length === 0 && !r.removed.length) {
226
+ // remove mode, nothing to do
227
+ lines.push(`${prefix}No ashlr permission entries found to remove.`);
228
+ }
229
+
230
+ if (r.added.length > 0) {
231
+ lines.push(`${prefix}Added ${r.added.length} entr${r.added.length === 1 ? "y" : "ies"} to ${r.settingsPath}:`);
232
+ for (const e of r.added) lines.push(` + ${e}`);
233
+ }
234
+
235
+ if (r.alreadyPresent.length > 0) {
236
+ lines.push(`Already present (${r.alreadyPresent.length}): ${r.alreadyPresent.join(", ")}`);
237
+ }
238
+
239
+ if (r.added.length === 0 && r.alreadyPresent.length > 0 && r.removed.length === 0) {
240
+ lines.push("All ashlr MCP tool entries already in allowlist. Nothing to do.");
241
+ }
242
+
243
+ if (!r.dryRun && r.added.length > 0) {
244
+ lines.push("");
245
+ lines.push("Restart Claude Code (or run /reload-plugins) to pick up the new permissions.");
246
+ }
247
+
248
+ return lines.join("\n");
249
+ }
250
+
251
+ async function main(): Promise<number> {
252
+ const args = process.argv.slice(2);
253
+ const dryRun = args.includes("--dry-run");
254
+ const remove = args.includes("--remove");
255
+
256
+ let settingsPath: string | undefined;
257
+ const settingsIdx = args.indexOf("--settings");
258
+ if (settingsIdx !== -1 && args[settingsIdx + 1]) {
259
+ settingsPath = args[settingsIdx + 1];
260
+ }
261
+
262
+ try {
263
+ const result = await installPermissions({ dryRun, remove, settingsPath });
264
+ process.stdout.write(formatResult(result) + "\n");
265
+ return 0;
266
+ } catch (err) {
267
+ process.stderr.write(
268
+ `ashlr install-permissions: I/O error — ${err instanceof Error ? err.message : String(err)}\n`,
269
+ );
270
+ return 1;
271
+ }
272
+ }
273
+
274
+ if (import.meta.main) {
275
+ main().then((code) => process.exit(code));
276
+ }