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,699 @@
1
+ /**
2
+ * ashlr-rename-file — v1.19 module-path refactor.
3
+ *
4
+ * Complements v1.18's `ashlr__edit_structural rename-cross-file` (which
5
+ * renames IDENTIFIERS across files). This tool renames MODULE PATHS: you
6
+ * move/rename a source file on disk and every import specifier in the
7
+ * project that resolved to it is rewritten in-place.
8
+ *
9
+ * Scope rules:
10
+ * - `from` / `to` are both cwd-clamped (clampToCwd). Any path that escapes
11
+ * cwd → refusal.
12
+ * - `from` must exist as a regular file; `to` must not exist.
13
+ * - `to`'s parent directory must already exist (we don't auto-create dirs
14
+ * — the caller is explicit about where the file lands).
15
+ * - Binary files (by extension) are rejected so we don't ever mutate
16
+ * non-source assets.
17
+ *
18
+ * Importer detection (ripgrep-based, not a naive whole-tree scan):
19
+ * - We shell out to `rg -l` to enumerate files containing the *basename*
20
+ * of `from` (without extension) in an import/require/export-from
21
+ * statement, bounded to .ts/.tsx/.js/.jsx files and subject to the
22
+ * `roots` filter if provided.
23
+ * - For each candidate, we parse every `import ... from "spec"`,
24
+ * `require("spec")`, and `export ... from "spec"` string literal and
25
+ * resolve it relative to the importer. If the resolved target equals
26
+ * `from`, the specifier is rewritten to the appropriate relative form
27
+ * of `to`.
28
+ * - Extension elision (`./foo` → `./foo.ts`) and index resolution
29
+ * (`./foo` → `./foo/index.ts`) are both honored. The rewritten
30
+ * specifier preserves the caller's original extension style (if the
31
+ * import omitted the extension, the new specifier does too).
32
+ * - Bare package specifiers (`react`, `@scope/pkg`) are never touched.
33
+ *
34
+ * Apply semantics:
35
+ * - Dry run lists every planned edit; nothing is written and the file is
36
+ * not moved.
37
+ * - On apply: every text edit is written first, then `fs.rename(from, to)`.
38
+ * If any edit fails to write, the remaining writes are skipped and the
39
+ * already-written files are best-effort rolled back from our in-memory
40
+ * pre-image. We only rename `from → to` if every text edit succeeded.
41
+ * - No force mode. No symlink following. Intentionally tight scope for v1.
42
+ */
43
+
44
+ import { access, readFile, rename, stat, writeFile } from "fs/promises";
45
+ import { accessSync, realpathSync, statSync } from "fs";
46
+ import { spawnSync } from "child_process";
47
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "path";
48
+ import { clampToCwd } from "./_cwd-clamp";
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Types
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export interface RenameFileArgs {
55
+ from: string;
56
+ to: string;
57
+ dryRun?: boolean;
58
+ maxFiles?: number;
59
+ roots?: string[];
60
+ }
61
+
62
+ export interface ImporterEdit {
63
+ /** Absolute path of the importer file. */
64
+ path: string;
65
+ /** Byte offsets into the importer's source (inside the string literal). */
66
+ start: number;
67
+ end: number;
68
+ /** The original specifier text (without surrounding quotes). */
69
+ oldSpecifier: string;
70
+ /** The rewritten specifier text (without surrounding quotes). */
71
+ newSpecifier: string;
72
+ }
73
+
74
+ export interface RenameFilePlan {
75
+ from: string;
76
+ to: string;
77
+ /** All edits grouped by importer. Each inner array is non-empty. */
78
+ edits: Array<{ path: string; edits: ImporterEdit[]; source: string }>;
79
+ /** Candidate files rg surfaced but that had no matching specifier. */
80
+ skipped: number;
81
+ /** Non-fatal warnings (e.g., maxFiles truncation). */
82
+ warnings: string[];
83
+ }
84
+
85
+ export type RenameFileResult =
86
+ | { ok: true; plan: RenameFilePlan; written: number; renamed: boolean }
87
+ | { ok: false; reason: string };
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Constants
91
+ // ---------------------------------------------------------------------------
92
+
93
+ const DEFAULT_MAX_FILES = 200;
94
+
95
+ /** Source file extensions we'll follow. */
96
+ const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
97
+
98
+ /**
99
+ * Binary extensions we flat-out refuse — catching user accidents like
100
+ * `ashlr__rename_file from=assets/hero.png to=assets/banner.png`. This is
101
+ * NOT a security check (the caller could still move these with `mv`); it's
102
+ * a contract: this tool understands import-path semantics, which binaries
103
+ * don't have. Keep the list conservative — if an unknown extension slips
104
+ * through, the downstream rg scan will simply return zero importers.
105
+ */
106
+ const BINARY_EXTS = new Set([
107
+ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".ico", ".bmp",
108
+ ".pdf", ".wasm", ".woff", ".woff2", ".ttf", ".otf", ".eot",
109
+ ".mp3", ".mp4", ".mov", ".avi", ".webm", ".ogg", ".wav", ".flac",
110
+ ".zip", ".tar", ".gz", ".bz2", ".7z", ".rar",
111
+ ".so", ".dylib", ".dll", ".exe", ".class",
112
+ ".db", ".sqlite", ".sqlite3",
113
+ ]);
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // rg resolver (duplicated from efficiency-server to avoid circular import)
117
+ // ---------------------------------------------------------------------------
118
+
119
+ function resolveRg(): string {
120
+ const candidates = [
121
+ "/opt/homebrew/bin/rg",
122
+ "/usr/local/bin/rg",
123
+ "/usr/bin/rg",
124
+ // codex vendor bundle (present on some macOS dev machines)
125
+ "/opt/homebrew/lib/node_modules/@openai/codex/node_modules/@openai/codex-darwin-arm64/vendor/aarch64-apple-darwin/path/rg",
126
+ "/opt/homebrew/lib/node_modules/@openai/codex/bin/rg",
127
+ ];
128
+ const bun = (globalThis as { Bun?: { which(bin: string): string | null } }).Bun;
129
+ const fromBun = bun ? bun.which("rg") : null;
130
+ if (fromBun) return fromBun;
131
+ for (const p of candidates) {
132
+ try {
133
+ accessSync(p);
134
+ return p;
135
+ } catch {
136
+ // keep trying
137
+ }
138
+ }
139
+ return "rg";
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Specifier resolution
144
+ // ---------------------------------------------------------------------------
145
+
146
+ /**
147
+ * Resolve an import specifier against the importer's directory to an
148
+ * absolute filesystem path. Handles extension elision and `index.<ext>`
149
+ * lookup. Returns `null` for bare specifiers (packages) and unresolvable
150
+ * paths — callers MUST check for null before comparing.
151
+ */
152
+ function resolveSpecifier(importerDir: string, spec: string): string | null {
153
+ // Bare specifier: doesn't start with `.` or `/`. Examples: `react`,
154
+ // `@scope/pkg`, `node:fs`. Never a candidate for rewrite.
155
+ if (!spec.startsWith(".") && !spec.startsWith("/") && !isAbsolute(spec)) {
156
+ return null;
157
+ }
158
+
159
+ const base = isAbsolute(spec) ? spec : resolve(importerDir, spec);
160
+
161
+ // 1. Exact path as written — but ONLY if it's a regular file. If `base`
162
+ // is a directory (e.g., `./foo` where `foo/` is a dir with index.ts),
163
+ // Node resolves it via the index lookup below, not as the dir itself.
164
+ if (regularFileExistsSync(base)) return base;
165
+
166
+ // 2. Extension elision: spec lacks an extension, try each source ext.
167
+ if (!SOURCE_EXTS.has(extname(base))) {
168
+ for (const ext of SOURCE_EXTS) {
169
+ if (regularFileExistsSync(base + ext)) return base + ext;
170
+ }
171
+ // 3. Directory + index.<ext>
172
+ for (const ext of SOURCE_EXTS) {
173
+ const idx = join(base, "index" + ext);
174
+ if (regularFileExistsSync(idx)) return idx;
175
+ }
176
+ }
177
+
178
+ return null;
179
+ }
180
+
181
+ function regularFileExistsSync(p: string): boolean {
182
+ try {
183
+ const s = statSync(p);
184
+ return s.isFile();
185
+ } catch {
186
+ return false;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Given the absolute path of the importer, the absolute path of the
192
+ * new target (`to`), and the original specifier text (so we can preserve
193
+ * extension style + index style), produce the new specifier string.
194
+ *
195
+ * - Always use POSIX separators (import specifiers are URL-ish).
196
+ * - Prefix with `./` if the relative path is in the same dir (otherwise
197
+ * Node/TS treats it as a bare package).
198
+ * - If the original specifier omitted the extension, the new one does too.
199
+ * - If the original specifier resolved via `index.<ext>`, strip
200
+ * `/index` from the new one so it stays symmetric.
201
+ */
202
+ function computeNewSpecifier(
203
+ importerDir: string,
204
+ toAbs: string,
205
+ oldSpec: string,
206
+ resolvedFromAbs: string,
207
+ ): string {
208
+ let rel = relative(importerDir, toAbs);
209
+ // Normalize to POSIX separators — import strings are always forward slash.
210
+ if (sep !== "/") rel = rel.split(sep).join("/");
211
+
212
+ // Did the original specifier carry an extension we recognize?
213
+ const oldExt = posixExtname(oldSpec);
214
+ const hadExt = SOURCE_EXTS.has(oldExt);
215
+
216
+ // Did the original resolve via `index.<ext>` (and did the spec NOT
217
+ // spell out `/index` itself)?
218
+ const toFile = basenameNoSepStripped(resolvedFromAbs);
219
+ const resolvedViaIndex =
220
+ /^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(toFile) &&
221
+ !oldSpec.endsWith("/index") &&
222
+ !/\/index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(oldSpec);
223
+
224
+ // If the new `to` is also an `index.<ext>` and the caller imported via
225
+ // directory-index, strip the `/index.<ext>` tail so `"./bar"` continues
226
+ // to resolve via index. If the caller spelled out `/index`, leave it be.
227
+ if (resolvedViaIndex) {
228
+ const tail = /\/index\.(ts|tsx|js|jsx|mjs|cjs)$/;
229
+ if (tail.test(rel)) rel = rel.replace(tail, "");
230
+ } else if (!hadExt) {
231
+ // Extension elision: the original spec omitted the extension. Strip
232
+ // the extension from the new one so we stay consistent.
233
+ const ext = posixExtname(rel);
234
+ if (SOURCE_EXTS.has(ext)) {
235
+ rel = rel.slice(0, -ext.length);
236
+ }
237
+ }
238
+
239
+ // Ensure relative prefix. `relative()` may produce `bar.ts` (same dir) —
240
+ // which as an import spec is a bare package, not a relative import.
241
+ if (!rel.startsWith(".") && !rel.startsWith("/")) {
242
+ rel = "./" + rel;
243
+ }
244
+
245
+ return rel;
246
+ }
247
+
248
+ function posixExtname(p: string): string {
249
+ const idx = p.lastIndexOf(".");
250
+ const slashIdx = p.lastIndexOf("/");
251
+ if (idx < 0 || idx < slashIdx) return "";
252
+ return p.slice(idx);
253
+ }
254
+
255
+ function basenameNoSepStripped(p: string): string {
256
+ const parts = p.split(/[\\/]/);
257
+ return parts[parts.length - 1] ?? "";
258
+ }
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // Import-specifier extraction
262
+ // ---------------------------------------------------------------------------
263
+
264
+ /**
265
+ * Regex matching an import/require/export-from statement's string literal
266
+ * and capturing the specifier text plus quote character. Covers:
267
+ *
268
+ * import x from "foo"
269
+ * import { a, b } from 'foo'
270
+ * import "foo"
271
+ * import * as x from "foo"
272
+ * export { x } from "foo"
273
+ * export * from "foo"
274
+ * const x = require("foo")
275
+ * const x = require('foo')
276
+ * await import("foo")
277
+ *
278
+ * Group 1: specifier text. Quote character is captured so we can match the
279
+ * closing quote without cross-pollinating on mismatched quotes.
280
+ *
281
+ * Deliberately narrow: we don't try to handle template literals or
282
+ * computed dynamic imports. v1 targets the 99% case; anything exotic is
283
+ * left untouched and the caller sees a "no matching specifier" skip.
284
+ */
285
+ const IMPORT_RE =
286
+ // eslint-disable-next-line no-useless-escape
287
+ /(?:^|[^\w$])(?:import|export)(?:[\s\S]*?)\s+from\s+(['"])([^'"\n]+?)\1|(?:^|[^\w$])import\s+(['"])([^'"\n]+?)\3|(?:^|[^\w$])(?:require|import)\s*\(\s*(['"])([^'"\n]+?)\5\s*\)/g;
288
+
289
+ interface RawMatch {
290
+ /** Byte offset of the opening quote (inclusive). */
291
+ quoteStart: number;
292
+ /** Specifier text (without quotes). */
293
+ specifier: string;
294
+ /** Length of specifier text. */
295
+ specLen: number;
296
+ }
297
+
298
+ function extractImportMatches(source: string): RawMatch[] {
299
+ const out: RawMatch[] = [];
300
+ IMPORT_RE.lastIndex = 0;
301
+ let m: RegExpExecArray | null;
302
+ while ((m = IMPORT_RE.exec(source))) {
303
+ // Which alternation fired? Check groups 2 / 4 / 6.
304
+ let quote: string;
305
+ let spec: string;
306
+ if (m[2] !== undefined) {
307
+ quote = m[1]!;
308
+ spec = m[2]!;
309
+ } else if (m[4] !== undefined) {
310
+ quote = m[3]!;
311
+ spec = m[4]!;
312
+ } else if (m[6] !== undefined) {
313
+ quote = m[5]!;
314
+ spec = m[6]!;
315
+ } else {
316
+ continue;
317
+ }
318
+ // Find the opening quote position inside the full match. m.index points
319
+ // at the leading separator/newline — scan forward for the quote.
320
+ const quoteIdx = source.indexOf(quote + spec + quote, m.index);
321
+ if (quoteIdx < 0) continue;
322
+ out.push({
323
+ quoteStart: quoteIdx,
324
+ specifier: spec,
325
+ specLen: spec.length,
326
+ });
327
+ }
328
+ return out;
329
+ }
330
+
331
+ // ---------------------------------------------------------------------------
332
+ // Candidate discovery via ripgrep
333
+ // ---------------------------------------------------------------------------
334
+
335
+ /**
336
+ * Return the list of absolute file paths that could plausibly import
337
+ * `fromAbs` — files that mention the basename (with or without extension)
338
+ * inside an import/require/export-from string literal. Falls back to a
339
+ * broad list of any TS/JS files containing the basename if rg is unavailable.
340
+ */
341
+ function discoverCandidates(
342
+ fromAbs: string,
343
+ searchRoots: string[],
344
+ cap: number,
345
+ ): { files: string[]; truncated: boolean } {
346
+ const base = basenameNoSepStripped(fromAbs);
347
+ const baseNoExt = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
348
+ // For directory-index files (`foo/index.ts`), the importing specifier is
349
+ // typically the PARENT dir (`./foo`), not `index`. We add the parent dir
350
+ // name as a second prefilter needle so those importers surface.
351
+ const parentName =
352
+ /^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base)
353
+ ? basenameNoSepStripped(dirname(fromAbs))
354
+ : null;
355
+
356
+ const rg = resolveRg();
357
+ const files = new Set<string>();
358
+ let truncated = false;
359
+
360
+ const runRg = (needle: string): void => {
361
+ if (!needle) return;
362
+ const args = [
363
+ "--files-with-matches",
364
+ "--fixed-strings",
365
+ "--glob", "*.ts",
366
+ "--glob", "*.tsx",
367
+ "--glob", "*.js",
368
+ "--glob", "*.jsx",
369
+ "--glob", "*.mjs",
370
+ "--glob", "*.cjs",
371
+ needle,
372
+ ...searchRoots,
373
+ ];
374
+ const res = spawnSync(rg, args, { encoding: "utf-8", timeout: 15_000 });
375
+ if (res.error || (res.status !== 0 && res.status !== 1)) return;
376
+ for (const line of (res.stdout ?? "").split("\n")) {
377
+ const p = line.trim();
378
+ if (!p) continue;
379
+ if (files.size >= cap) {
380
+ truncated = true;
381
+ return;
382
+ }
383
+ // Canonicalize so symlinked tmpdirs on macOS (/var → /private/var)
384
+ // produce paths comparable to the clamp's canonicalized `from`.
385
+ let canonical: string;
386
+ try {
387
+ canonical = realpathSync(p);
388
+ } catch {
389
+ canonical = p;
390
+ }
391
+ files.add(canonical);
392
+ }
393
+ };
394
+
395
+ runRg(baseNoExt);
396
+ if (base !== baseNoExt) runRg(base);
397
+ if (parentName && parentName !== baseNoExt) runRg(parentName);
398
+
399
+ return { files: Array.from(files), truncated };
400
+ }
401
+
402
+ // ---------------------------------------------------------------------------
403
+ // Core planner
404
+ // ---------------------------------------------------------------------------
405
+
406
+ export async function planRenameFile(args: RenameFileArgs): Promise<RenameFileResult> {
407
+ const { from, to, maxFiles = DEFAULT_MAX_FILES, roots } = args;
408
+
409
+ if (!from || typeof from !== "string") {
410
+ return { ok: false, reason: "'from' is required" };
411
+ }
412
+ if (!to || typeof to !== "string") {
413
+ return { ok: false, reason: "'to' is required" };
414
+ }
415
+
416
+ const fromClamp = clampToCwd(from, "ashlr__rename_file");
417
+ if (!fromClamp.ok) return { ok: false, reason: fromClamp.message };
418
+ const toClamp = clampToCwd(to, "ashlr__rename_file");
419
+ if (!toClamp.ok) return { ok: false, reason: toClamp.message };
420
+
421
+ const fromAbs = fromClamp.abs;
422
+ const toAbs = toClamp.abs;
423
+
424
+ // Same-path is a no-op refusal so the caller can't accidentally truncate
425
+ // the file via self-rename.
426
+ if (fromAbs === toAbs) {
427
+ return { ok: false, reason: `'from' and 'to' resolve to the same path: ${fromAbs}` };
428
+ }
429
+
430
+ // `from` must be a regular file.
431
+ let fromStat;
432
+ try {
433
+ fromStat = await stat(fromAbs);
434
+ } catch {
435
+ return { ok: false, reason: `'from' does not exist: ${from}` };
436
+ }
437
+ if (!fromStat.isFile()) {
438
+ return { ok: false, reason: `'from' is not a regular file: ${from}` };
439
+ }
440
+
441
+ // Binary-file refusal.
442
+ const fromExt = extname(fromAbs).toLowerCase();
443
+ if (BINARY_EXTS.has(fromExt)) {
444
+ return { ok: false, reason: `binary files are not supported (extension ${fromExt}): ${from}` };
445
+ }
446
+ const toExt = extname(toAbs).toLowerCase();
447
+ if (BINARY_EXTS.has(toExt)) {
448
+ return { ok: false, reason: `binary files are not supported (extension ${toExt}): ${to}` };
449
+ }
450
+
451
+ // `to` must NOT exist.
452
+ try {
453
+ await access(toAbs);
454
+ return { ok: false, reason: `'to' already exists: ${to}` };
455
+ } catch {
456
+ // desired: does not exist
457
+ }
458
+
459
+ // `to`'s parent directory MUST exist.
460
+ const toDir = dirname(toAbs);
461
+ try {
462
+ const s = await stat(toDir);
463
+ if (!s.isDirectory()) {
464
+ return { ok: false, reason: `destination parent is not a directory: ${toDir}` };
465
+ }
466
+ } catch {
467
+ return { ok: false, reason: `destination directory does not exist (create it first): ${toDir}` };
468
+ }
469
+
470
+ // Determine the search roots. If the caller passed `roots`, clamp each.
471
+ // Otherwise default to cwd.
472
+ const searchRoots: string[] = [];
473
+ if (Array.isArray(roots) && roots.length > 0) {
474
+ for (const r of roots) {
475
+ const c = clampToCwd(r, "ashlr__rename_file");
476
+ if (!c.ok) return { ok: false, reason: c.message };
477
+ searchRoots.push(c.abs);
478
+ }
479
+ } else {
480
+ searchRoots.push(process.cwd());
481
+ }
482
+
483
+ // Discover candidate importers.
484
+ const { files: candidates, truncated } = discoverCandidates(fromAbs, searchRoots, maxFiles);
485
+
486
+ const warnings: string[] = [];
487
+ if (truncated) {
488
+ warnings.push(
489
+ `ripgrep returned more than ${maxFiles} candidates — only the first ${maxFiles} were scanned. ` +
490
+ `Rerun with a larger maxFiles or narrower roots.`,
491
+ );
492
+ }
493
+
494
+ // For each candidate, parse import specifiers and resolve to an abs path.
495
+ const perFileEdits: Array<{ path: string; edits: ImporterEdit[]; source: string }> = [];
496
+ let skipped = 0;
497
+
498
+ for (const importer of candidates) {
499
+ // Skip the renamed file itself — relevant edits don't apply to it
500
+ // (its own path is what we're changing, not a reference to another file).
501
+ if (importer === fromAbs) {
502
+ skipped++;
503
+ continue;
504
+ }
505
+ let source: string;
506
+ try {
507
+ source = await readFile(importer, "utf-8");
508
+ } catch {
509
+ skipped++;
510
+ continue;
511
+ }
512
+
513
+ const matches = extractImportMatches(source);
514
+ if (matches.length === 0) {
515
+ skipped++;
516
+ continue;
517
+ }
518
+
519
+ const importerDir = dirname(importer);
520
+ const edits: ImporterEdit[] = [];
521
+
522
+ for (const m of matches) {
523
+ const resolved = resolveSpecifier(importerDir, m.specifier);
524
+ if (!resolved || resolved !== fromAbs) continue;
525
+
526
+ const newSpec = computeNewSpecifier(importerDir, toAbs, m.specifier, resolved);
527
+ if (newSpec === m.specifier) continue; // already correct, no-op
528
+
529
+ edits.push({
530
+ path: importer,
531
+ start: m.quoteStart + 1, // inside opening quote
532
+ end: m.quoteStart + 1 + m.specLen, // inside closing quote
533
+ oldSpecifier: m.specifier,
534
+ newSpecifier: newSpec,
535
+ });
536
+ }
537
+
538
+ if (edits.length === 0) {
539
+ skipped++;
540
+ continue;
541
+ }
542
+ perFileEdits.push({ path: importer, edits, source });
543
+ }
544
+
545
+ return {
546
+ ok: true,
547
+ plan: {
548
+ from: fromAbs,
549
+ to: toAbs,
550
+ edits: perFileEdits,
551
+ skipped,
552
+ warnings,
553
+ },
554
+ written: 0,
555
+ renamed: false,
556
+ };
557
+ }
558
+
559
+ // ---------------------------------------------------------------------------
560
+ // Apply (write edits, then rename)
561
+ // ---------------------------------------------------------------------------
562
+
563
+ /**
564
+ * Apply a plan from `planRenameFile`. Writes each importer's updated text,
565
+ * then renames `from → to`. On any write failure, best-effort rolls back
566
+ * every already-written importer using the cached pre-image. The file
567
+ * itself is only renamed if every text edit succeeded.
568
+ *
569
+ * Concurrency note: we sequence writes (not parallel) because a failure
570
+ * rollback is simpler when we know exactly which files made it to disk.
571
+ */
572
+ export async function applyRenameFilePlan(
573
+ plan: RenameFilePlan,
574
+ ): Promise<{ written: number; renamed: boolean; rollbackWarnings: string[] }> {
575
+ const writtenPaths: Array<{ path: string; original: string }> = [];
576
+ const rollbackWarnings: string[] = [];
577
+
578
+ for (const fe of plan.edits) {
579
+ const updated = applyEditsToSource(fe.source, fe.edits);
580
+ try {
581
+ await writeFile(fe.path, updated, "utf-8");
582
+ writtenPaths.push({ path: fe.path, original: fe.source });
583
+ } catch (err) {
584
+ // Rollback everything we wrote.
585
+ for (const prev of writtenPaths) {
586
+ try {
587
+ await writeFile(prev.path, prev.original, "utf-8");
588
+ } catch (rbErr) {
589
+ rollbackWarnings.push(
590
+ `rollback failed for ${prev.path}: ${(rbErr as Error).message}`,
591
+ );
592
+ }
593
+ }
594
+ const msg = err instanceof Error ? err.message : String(err);
595
+ throw new Error(`failed to write ${fe.path}: ${msg}`);
596
+ }
597
+ }
598
+
599
+ // All text edits written — now rename the file itself.
600
+ try {
601
+ await rename(plan.from, plan.to);
602
+ } catch (err) {
603
+ // File rename failed — roll back importer edits so the codebase stays
604
+ // consistent (references still point at the un-moved file).
605
+ for (const prev of writtenPaths) {
606
+ try {
607
+ await writeFile(prev.path, prev.original, "utf-8");
608
+ } catch (rbErr) {
609
+ rollbackWarnings.push(
610
+ `rollback failed for ${prev.path}: ${(rbErr as Error).message}`,
611
+ );
612
+ }
613
+ }
614
+ const msg = err instanceof Error ? err.message : String(err);
615
+ throw new Error(`failed to rename ${plan.from} → ${plan.to}: ${msg}`);
616
+ }
617
+
618
+ return { written: writtenPaths.length, renamed: true, rollbackWarnings };
619
+ }
620
+
621
+ /**
622
+ * Apply a set of ImporterEdits (all against the same source) and return
623
+ * the updated text. Edits are applied right-to-left so earlier offsets
624
+ * remain valid throughout the rewrite. Overlap detection is a sanity net —
625
+ * our extractor never emits overlapping ranges, but a future extension
626
+ * might, and silently miscounting bytes would corrupt the file.
627
+ */
628
+ function applyEditsToSource(source: string, edits: ImporterEdit[]): string {
629
+ const sorted = [...edits].sort((a, b) => b.start - a.start);
630
+ for (let i = 0; i < sorted.length - 1; i++) {
631
+ if (sorted[i]!.start < sorted[i + 1]!.end) {
632
+ throw new Error("rename-file: overlapping edits detected");
633
+ }
634
+ }
635
+ let out = source;
636
+ for (const e of sorted) {
637
+ out = out.slice(0, e.start) + e.newSpecifier + out.slice(e.end);
638
+ }
639
+ return out;
640
+ }
641
+
642
+ // ---------------------------------------------------------------------------
643
+ // Top-level callable (used by the MCP handler)
644
+ // ---------------------------------------------------------------------------
645
+
646
+ export async function ashlrRenameFile(args: RenameFileArgs): Promise<string> {
647
+ const planRes = await planRenameFile(args);
648
+ if (!planRes.ok) {
649
+ throw new Error(planRes.reason);
650
+ }
651
+ const { plan } = planRes;
652
+ const dryRun = args.dryRun === true;
653
+
654
+ const importerCount = plan.edits.length;
655
+ const totalEdits = plan.edits.reduce((s, f) => s + f.edits.length, 0);
656
+
657
+ const lines: string[] = [];
658
+ lines.push(
659
+ `[ashlr__rename_file] ${dryRun ? "(dry run) " : ""}${plan.from} → ${plan.to}`,
660
+ );
661
+ lines.push(
662
+ ` ${importerCount} importer${importerCount === 1 ? "" : "s"}, ${totalEdits} specifier${totalEdits === 1 ? "" : "s"} updated, ${plan.skipped} candidate${plan.skipped === 1 ? "" : "s"} skipped`,
663
+ );
664
+ if (plan.warnings.length > 0) {
665
+ lines.push(" warnings:");
666
+ for (const w of plan.warnings) lines.push(` ${w}`);
667
+ }
668
+
669
+ // List every planned edit when the set is small enough to read at a glance.
670
+ if (importerCount <= 20) {
671
+ for (const fe of plan.edits) {
672
+ lines.push(` ${fe.path}`);
673
+ for (const e of fe.edits) {
674
+ lines.push(` "${e.oldSpecifier}" → "${e.newSpecifier}"`);
675
+ }
676
+ }
677
+ }
678
+
679
+ if (dryRun) {
680
+ lines.push(" (dry run — no files written, file not renamed)");
681
+ return lines.join("\n");
682
+ }
683
+
684
+ if (importerCount === 0) {
685
+ // Still rename the file itself — the caller asked us to move it; the
686
+ // absence of importers just means nothing else needs updating.
687
+ await rename(plan.from, plan.to);
688
+ lines.push(" renamed");
689
+ return lines.join("\n");
690
+ }
691
+
692
+ const applied = await applyRenameFilePlan(plan);
693
+ lines.push(` ${applied.written} file${applied.written === 1 ? "" : "s"} written, renamed`);
694
+ if (applied.rollbackWarnings.length > 0) {
695
+ lines.push(" rollback warnings:");
696
+ for (const w of applied.rollbackWarnings) lines.push(` ${w}`);
697
+ }
698
+ return lines.join("\n");
699
+ }