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,503 @@
1
+ /**
2
+ * tree-server-handlers — side-effect module.
3
+ *
4
+ * Importing this file registers the ashlr__tree tool into the shared registry
5
+ * (_tool-base.ts). Used by both the standalone entry point (tree-server.ts)
6
+ * and the router (_router.ts via _router-handlers.ts).
7
+ */
8
+
9
+ import { registerTool, type ToolCallContext, type ToolResult } from "./_tool-base";
10
+ import { existsSync, readdirSync, statSync, lstatSync, readFileSync } from "fs";
11
+ import { join, sep, basename } from "path";
12
+ import { spawnSync } from "child_process";
13
+ import { recordSaving as recordSavingCore } from "./_stats";
14
+ import { clampToCwd } from "./_cwd-clamp";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Constants
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const DEFAULT_EXCLUDES = [
21
+ "node_modules",
22
+ "dist",
23
+ "build",
24
+ ".git",
25
+ ".next",
26
+ ".cache",
27
+ ".turbo",
28
+ "__pycache__",
29
+ ".venv",
30
+ ".DS_Store",
31
+ ];
32
+
33
+ const FILE_SCAN_CAP = 5_000;
34
+ const TIMEOUT_MS = 15_000;
35
+ const DIR_TRUNC_THRESHOLD = 10;
36
+ const LOC_FILE_BYTES_CAP = 2 * 1024 * 1024;
37
+
38
+ interface TreeOptions {
39
+ path?: string;
40
+ depth?: number;
41
+ pattern?: string;
42
+ exclude?: string[];
43
+ sizes?: boolean;
44
+ loc?: boolean;
45
+ maxEntries?: number;
46
+ }
47
+
48
+ interface Node {
49
+ name: string;
50
+ abs: string;
51
+ isDir: boolean;
52
+ size: number;
53
+ loc?: number;
54
+ children?: Node[];
55
+ fileCount?: number;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Helpers
60
+ // ---------------------------------------------------------------------------
61
+
62
+ function formatSize(bytes: number): string {
63
+ if (bytes < 1024) return `${bytes} B`;
64
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(bytes < 10 * 1024 ? 1 : 0)} KB`;
65
+ return `${(bytes / (1024 * 1024)).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
66
+ }
67
+
68
+ function isGitRepo(abs: string): boolean {
69
+ try {
70
+ const res = spawnSync("git", ["-C", abs, "rev-parse", "--is-inside-work-tree"], {
71
+ encoding: "utf-8",
72
+ timeout: 2000,
73
+ });
74
+ return res.status === 0 && (res.stdout || "").trim() === "true";
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ function listGitFiles(abs: string): string[] | null {
81
+ const res = spawnSync("git", ["-C", abs, "ls-files", "--cached", "--others", "--exclude-standard"], {
82
+ encoding: "utf-8",
83
+ timeout: 5000,
84
+ maxBuffer: 50 * 1024 * 1024,
85
+ });
86
+ if (res.status !== 0) return null;
87
+ return (res.stdout || "").split("\n").filter(Boolean);
88
+ }
89
+
90
+ function isProbablyText(buf: Buffer): boolean {
91
+ const n = Math.min(buf.length, 4096);
92
+ for (let i = 0; i < n; i++) {
93
+ if (buf[i] === 0) return false;
94
+ }
95
+ return true;
96
+ }
97
+
98
+ function countLoc(
99
+ absFile: string,
100
+ size: number,
101
+ locState: { locSkippedLarge: number },
102
+ ): number | undefined {
103
+ if (size > LOC_FILE_BYTES_CAP) {
104
+ locState.locSkippedLarge++;
105
+ return undefined;
106
+ }
107
+ try {
108
+ const buf = readFileSync(absFile);
109
+ if (!isProbablyText(buf)) return undefined;
110
+ const text = buf.toString("utf-8");
111
+ if (!text) return 0;
112
+ let n = 1;
113
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) n++;
114
+ if (text.endsWith("\n")) n--;
115
+ return Math.max(0, n);
116
+ } catch {
117
+ return undefined;
118
+ }
119
+ }
120
+
121
+ interface BuildContext {
122
+ root: string;
123
+ excludes: Set<string>;
124
+ pattern?: RegExp;
125
+ maxDepth: number;
126
+ loc: boolean;
127
+ locSkippedLarge: number;
128
+ started: number;
129
+ scanned: { count: number };
130
+ truncatedScan: boolean;
131
+ timedOut: boolean;
132
+ }
133
+
134
+ function walkFs(ctx: BuildContext, abs: string, depth: number): Node | null {
135
+ if (Date.now() - ctx.started > TIMEOUT_MS) {
136
+ ctx.timedOut = true;
137
+ return null;
138
+ }
139
+ let st;
140
+ try {
141
+ st = lstatSync(abs);
142
+ } catch {
143
+ return null;
144
+ }
145
+ if (st.isSymbolicLink()) {
146
+ try {
147
+ const real = statSync(abs);
148
+ const realPath = require("fs").realpathSync(abs);
149
+ if (!realPath.startsWith(ctx.root + sep) && realPath !== ctx.root) return null;
150
+ st = real;
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+ const name = basename(abs);
156
+ if (!st.isDirectory()) {
157
+ if (ctx.scanned.count >= FILE_SCAN_CAP) {
158
+ ctx.truncatedScan = true;
159
+ return null;
160
+ }
161
+ ctx.scanned.count++;
162
+ if (ctx.pattern && !ctx.pattern.test(abs)) return null;
163
+ const node: Node = { name, abs, isDir: false, size: st.size };
164
+ if (ctx.loc) node.loc = countLoc(abs, st.size, ctx);
165
+ return node;
166
+ }
167
+ if (depth > ctx.maxDepth) {
168
+ return { name, abs, isDir: true, size: 0, children: [], fileCount: 0 };
169
+ }
170
+ let entries: string[];
171
+ try {
172
+ entries = readdirSync(abs);
173
+ } catch {
174
+ return null;
175
+ }
176
+ const children: Node[] = [];
177
+ let aggSize = 0;
178
+ let fileCount = 0;
179
+ for (const entry of entries) {
180
+ if (ctx.excludes.has(entry)) continue;
181
+ if (ctx.timedOut || ctx.truncatedScan) break;
182
+ const childAbs = join(abs, entry);
183
+ const child = walkFs(ctx, childAbs, depth + 1);
184
+ if (!child) continue;
185
+ children.push(child);
186
+ aggSize += child.size;
187
+ fileCount += child.isDir ? (child.fileCount ?? 0) : 1;
188
+ }
189
+ return { name, abs, isDir: true, size: aggSize, children, fileCount };
190
+ }
191
+
192
+ function buildFromGitFiles(root: string, files: string[], ctx: BuildContext): Node {
193
+ const rootNode: Node = { name: basename(root), abs: root, isDir: true, size: 0, children: [], fileCount: 0 };
194
+ const dirCache = new Map<string, Node>();
195
+ dirCache.set("", rootNode);
196
+
197
+ const getDir = (relDir: string): Node => {
198
+ if (dirCache.has(relDir)) return dirCache.get(relDir)!;
199
+ const parts = relDir.split("/");
200
+ const parentRel = parts.slice(0, -1).join("/");
201
+ const parent = getDir(parentRel);
202
+ const abs = join(root, relDir);
203
+ const node: Node = { name: parts[parts.length - 1]!, abs, isDir: true, size: 0, children: [], fileCount: 0 };
204
+ parent.children!.push(node);
205
+ dirCache.set(relDir, node);
206
+ return node;
207
+ };
208
+
209
+ for (const rel of files) {
210
+ if (ctx.scanned.count >= FILE_SCAN_CAP) {
211
+ ctx.truncatedScan = true;
212
+ break;
213
+ }
214
+ if (Date.now() - ctx.started > TIMEOUT_MS) {
215
+ ctx.timedOut = true;
216
+ break;
217
+ }
218
+ const parts = rel.split("/");
219
+ if (parts.length - 1 > ctx.maxDepth) continue;
220
+
221
+ const abs = join(root, rel);
222
+ let st;
223
+ try {
224
+ st = lstatSync(abs);
225
+ } catch {
226
+ continue;
227
+ }
228
+ if (st.isSymbolicLink()) {
229
+ try {
230
+ const realPath = require("fs").realpathSync(abs);
231
+ if (!realPath.startsWith(root + sep) && realPath !== root) continue;
232
+ st = statSync(abs);
233
+ } catch {
234
+ continue;
235
+ }
236
+ }
237
+ if (!st.isFile()) continue;
238
+ if (ctx.pattern && !ctx.pattern.test(abs)) continue;
239
+
240
+ ctx.scanned.count++;
241
+ const parentRel = parts.slice(0, -1).join("/");
242
+ const parent = getDir(parentRel);
243
+ const node: Node = { name: parts[parts.length - 1]!, abs, isDir: false, size: st.size };
244
+ if (ctx.loc) node.loc = countLoc(abs, st.size, ctx);
245
+ parent.children!.push(node);
246
+ }
247
+
248
+ const agg = (n: Node): void => {
249
+ if (!n.isDir) return;
250
+ let size = 0;
251
+ let files = 0;
252
+ for (const c of n.children ?? []) {
253
+ agg(c);
254
+ size += c.size;
255
+ files += c.isDir ? (c.fileCount ?? 0) : 1;
256
+ }
257
+ n.size = size;
258
+ n.fileCount = files;
259
+ };
260
+ agg(rootNode);
261
+ return rootNode;
262
+ }
263
+
264
+ function sortChildren(n: Node): void {
265
+ if (!n.isDir || !n.children) return;
266
+ n.children.sort((a, b) => {
267
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
268
+ if (a.isDir) return a.name.localeCompare(b.name);
269
+ if (a.size !== b.size) return b.size - a.size;
270
+ return a.name.localeCompare(b.name);
271
+ });
272
+ for (const c of n.children) sortChildren(c);
273
+ }
274
+
275
+ interface RenderContext {
276
+ budget: { left: number; total: number };
277
+ sizes: boolean;
278
+ loc: boolean;
279
+ truncatedByBudget: boolean;
280
+ }
281
+
282
+ function formatMeta(n: Node, rctx: RenderContext): string {
283
+ const parts: string[] = [];
284
+ if (rctx.sizes) parts.push(formatSize(n.size));
285
+ if (n.isDir && (n.fileCount ?? 0) > 0) parts.push(`${n.fileCount} files`);
286
+ if (!n.isDir && rctx.loc && typeof n.loc === "number") parts.push(`${n.loc} LOC`);
287
+ return parts.length ? " " + parts.join(" \u00b7 ") : "";
288
+ }
289
+
290
+ function renderTree(root: Node, rctx: RenderContext): string {
291
+ const lines: string[] = [];
292
+ const header = `${root.name}/${formatMeta(root, rctx)}`;
293
+ lines.push(header);
294
+
295
+ const walk = (node: Node, prefix: string): void => {
296
+ if (rctx.budget.left <= 0) {
297
+ rctx.truncatedByBudget = true;
298
+ return;
299
+ }
300
+ const kids = node.children ?? [];
301
+ let shown: Node[];
302
+ let elided = 0;
303
+ if (kids.length > DIR_TRUNC_THRESHOLD && kids.length > rctx.budget.left) {
304
+ const first = kids.slice(0, 5);
305
+ const last = kids.slice(-2);
306
+ elided = kids.length - first.length - last.length;
307
+ shown = [...first, ...last];
308
+ } else if (kids.length > DIR_TRUNC_THRESHOLD) {
309
+ if (rctx.budget.left >= kids.length) {
310
+ shown = kids;
311
+ } else {
312
+ const first = kids.slice(0, 5);
313
+ const last = kids.slice(-2);
314
+ elided = kids.length - first.length - last.length;
315
+ shown = [...first, ...last];
316
+ }
317
+ } else {
318
+ shown = kids;
319
+ }
320
+
321
+ const lastShownIdx = shown.length - 1 + (elided > 0 ? 1 : 0);
322
+ const total = shown.length + (elided > 0 ? 1 : 0);
323
+
324
+ for (let i = 0; i < shown.length; i++) {
325
+ if (rctx.budget.left <= 0) {
326
+ rctx.truncatedByBudget = true;
327
+ return;
328
+ }
329
+ const child = shown[i]!;
330
+ const isLast = i === total - 1;
331
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251c\u2500\u2500 ";
332
+ const namePart = child.isDir ? `${child.name}/` : child.name;
333
+ lines.push(`${prefix}${connector}${namePart}${formatMeta(child, rctx)}`);
334
+ rctx.budget.left--;
335
+ if (child.isDir) {
336
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
337
+ walk(child, nextPrefix);
338
+ }
339
+ if (elided > 0 && i === 4) {
340
+ const connector2 = "\u251c\u2500\u2500 ";
341
+ lines.push(`${prefix}${connector2}[... ${elided} more ...]`);
342
+ }
343
+ }
344
+
345
+ void lastShownIdx;
346
+ };
347
+
348
+ walk(root, "");
349
+ return lines.join("\n");
350
+ }
351
+
352
+ function countFilesAndDirs(n: Node): { dirs: number; files: number } {
353
+ let dirs = 0;
354
+ let files = 0;
355
+ const visit = (x: Node): void => {
356
+ if (x.isDir) {
357
+ dirs++;
358
+ for (const c of x.children ?? []) visit(c);
359
+ } else {
360
+ files++;
361
+ }
362
+ };
363
+ visit(n);
364
+ return { dirs: Math.max(0, dirs - 1), files };
365
+ }
366
+
367
+ function baselineBytes(n: Node): number {
368
+ let bytes = 0;
369
+ const visit = (x: Node, prefix: string): void => {
370
+ const rel = prefix ? `${prefix}/${x.name}` : x.name;
371
+ if (x.isDir) {
372
+ for (const c of x.children ?? []) visit(c, rel);
373
+ } else {
374
+ bytes += rel.length + 10;
375
+ }
376
+ };
377
+ visit(n, "");
378
+ return bytes;
379
+ }
380
+
381
+ // ---------------------------------------------------------------------------
382
+ // Core logic (exported for tests)
383
+ // ---------------------------------------------------------------------------
384
+
385
+ export async function ashlrTree(input: TreeOptions): Promise<string> {
386
+ const clamp = clampToCwd(input.path, "ashlr__tree");
387
+ if (!clamp.ok) return clamp.message;
388
+ const rootAbs = clamp.abs;
389
+ if (!existsSync(rootAbs)) {
390
+ throw new Error(`path does not exist: ${rootAbs}`);
391
+ }
392
+ const st = statSync(rootAbs);
393
+ if (!st.isDirectory()) {
394
+ throw new Error(`path is not a directory: ${rootAbs}`);
395
+ }
396
+
397
+ const maxDepth = typeof input.depth === "number" ? input.depth : 4;
398
+ const excludes = new Set(input.exclude ?? DEFAULT_EXCLUDES);
399
+ const sizes = input.sizes ?? true;
400
+ const loc = input.loc ?? false;
401
+ const maxEntries = typeof input.maxEntries === "number" ? input.maxEntries : 500;
402
+ let pattern: RegExp | undefined;
403
+ if (input.pattern) {
404
+ try {
405
+ pattern = new RegExp(input.pattern);
406
+ } catch (e) {
407
+ throw new Error(`invalid pattern regex: ${(e as Error).message}`);
408
+ }
409
+ }
410
+
411
+ const ctx: BuildContext = {
412
+ root: rootAbs,
413
+ excludes,
414
+ pattern,
415
+ maxDepth,
416
+ loc,
417
+ locSkippedLarge: 0,
418
+ started: Date.now(),
419
+ scanned: { count: 0 },
420
+ truncatedScan: false,
421
+ timedOut: false,
422
+ };
423
+
424
+ let rootNode: Node | null = null;
425
+ const gitFiles = isGitRepo(rootAbs) ? listGitFiles(rootAbs) : null;
426
+ if (gitFiles) {
427
+ rootNode = buildFromGitFiles(rootAbs, gitFiles, ctx);
428
+ } else {
429
+ rootNode = walkFs(ctx, rootAbs, 0);
430
+ }
431
+
432
+ if (!rootNode) {
433
+ return `[ashlr__tree] failed to scan ${rootAbs}`;
434
+ }
435
+
436
+ sortChildren(rootNode);
437
+
438
+ const { dirs, files } = countFilesAndDirs(rootNode);
439
+ if (files === 0 && (rootNode.children?.length ?? 0) === 0) {
440
+ return `${rootNode.name}/ [empty]`;
441
+ }
442
+
443
+ const rctx: RenderContext = {
444
+ budget: { left: maxEntries, total: maxEntries },
445
+ sizes,
446
+ loc,
447
+ truncatedByBudget: false,
448
+ };
449
+ const body = renderTree(rootNode, rctx);
450
+
451
+ const summary = `${dirs} dirs \u00b7 ${files} files \u00b7 ${formatSize(rootNode.size)} total`;
452
+ const flags: string[] = [];
453
+ if (ctx.timedOut) flags.push("[... timed out ...]");
454
+ if (ctx.truncatedScan) flags.push("truncated: true (file scan cap hit)");
455
+ if (rctx.truncatedByBudget) flags.push("truncated: true (maxEntries reached)");
456
+ if (ctx.locSkippedLarge > 0) {
457
+ flags.push(`loc skipped: ${ctx.locSkippedLarge} file(s) over ${formatSize(LOC_FILE_BYTES_CAP)}`);
458
+ }
459
+
460
+ const out = [body, "", summary, ...flags].filter(Boolean).join("\n");
461
+
462
+ const baseline = baselineBytes(rootNode);
463
+ await recordSavingCore(baseline, out.length, "ashlr__tree");
464
+ return out;
465
+ }
466
+
467
+ // ---------------------------------------------------------------------------
468
+ // Registration (side-effect on import)
469
+ // ---------------------------------------------------------------------------
470
+
471
+ registerTool({
472
+ name: "ashlr__tree",
473
+ description:
474
+ "Token-efficient project structure view. One call returns a Unicode box-drawing tree with per-directory size/file-count and optional per-file LOC. Honors .gitignore automatically inside git repos, otherwise skips a standard exclusion set (node_modules, dist, build, .git, .next, .cache, .turbo, __pycache__, .venv). Replaces the 3-5 orientation calls (ls -la + find + multiple Reads) Claude Code normally makes when opening a repo.",
475
+ inputSchema: {
476
+ type: "object",
477
+ properties: {
478
+ path: { type: "string", description: "Directory to scan (default: cwd)" },
479
+ depth: { type: "number", description: "Max traversal depth (default: 4)" },
480
+ pattern: { type: "string", description: "Only include files matching this regex (e.g. '\\\\.ts$')" },
481
+ exclude: {
482
+ type: "array",
483
+ items: { type: "string" },
484
+ description:
485
+ "Directory names to skip (default: node_modules, dist, build, .git, .next, .cache, .turbo, __pycache__, .venv, .DS_Store)",
486
+ },
487
+ sizes: { type: "boolean", description: "Include file sizes shown as B/KB/MB (default: true)" },
488
+ loc: {
489
+ type: "boolean",
490
+ description:
491
+ "Include line counts for text files (default: false; skips files over 2 MB)",
492
+ },
493
+ maxEntries: {
494
+ type: "number",
495
+ description: "Hard cap on entries returned (default: 500). When exceeded, per-dir truncation with count.",
496
+ },
497
+ },
498
+ },
499
+ handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
500
+ const text = await ashlrTree(args as unknown as TreeOptions);
501
+ return { content: [{ type: "text", text }] };
502
+ },
503
+ });
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ashlr-tree MCP server — standalone entry point.
4
+ *
5
+ * Imports tree-server-handlers (registers ashlr__tree into the shared
6
+ * registry) then runs as a standalone stdio MCP server. Existing plugin.json
7
+ * entries continue to work unchanged through the v1.12 migration window.
8
+ *
9
+ * For in-process router use, import tree-server-handlers directly.
10
+ */
11
+
12
+ import "./tree-server-handlers";
13
+ import { runStandalone } from "./_tool-base";
14
+
15
+ // Re-export core logic so existing tests that import from this module keep working.
16
+ export { ashlrTree } from "./tree-server-handlers";
17
+
18
+ if (import.meta.main) {
19
+ await runStandalone("ashlr-tree", "0.1.0");
20
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * webfetch-server-handlers — side-effect module.
3
+ *
4
+ * Importing this file registers the ashlr__webfetch tool into the shared
5
+ * registry (_tool-base.ts). Used by both the standalone entry point
6
+ * (webfetch-server.ts) and the router (_router.ts via _router-handlers.ts).
7
+ *
8
+ * Track D's LLM summarizer integration (summarizeIfLarge + recordSavingAccurate)
9
+ * is preserved exactly as it exists in the original webfetch-server.ts.
10
+ */
11
+
12
+ import { registerTool, type ToolCallContext, type ToolResult } from "./_tool-base";
13
+ import { confidenceBadge, confidenceTier, summarizeIfLarge, PROMPTS } from "./_summarize";
14
+ import { recordSavingAccurate } from "./_accounting";
15
+ import { logEvent } from "./_events";
16
+ import { safeFetch, compressHtml, compressJson } from "./_http-helpers";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ interface WebFetchArgs {
23
+ url: string;
24
+ prompt?: string;
25
+ maxBytes?: number;
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Text helpers
30
+ // ---------------------------------------------------------------------------
31
+
32
+ function snipCompact(s: string, maxBytes: number): { text: string; snipped: boolean } {
33
+ if (s.length <= maxBytes) return { text: s, snipped: false };
34
+ const half = Math.floor(maxBytes / 2);
35
+ const head = s.slice(0, half);
36
+ const tail = s.slice(s.length - half);
37
+ const elided = s.length - maxBytes;
38
+ return {
39
+ text: `${head}\n\n[... ${elided} bytes elided — use a larger maxBytes to see more ...]\n\n${tail}`,
40
+ snipped: true,
41
+ };
42
+ }
43
+
44
+ function extractTitle(html: string): string | null {
45
+ const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
46
+ if (!m) return null;
47
+ return m[1]!.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Core logic (exported for tests)
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export async function doWebFetch(args: WebFetchArgs): Promise<string> {
55
+ const { url, prompt, maxBytes = 100_000 } = args;
56
+
57
+ const ctl = new AbortController();
58
+ const t = setTimeout(() => ctl.abort(), 20_000);
59
+ let res: Response;
60
+ try {
61
+ res = await safeFetch(url, {
62
+ headers: { "user-agent": "ashlr-plugin/0.9.2 (+https://plugin.ashlr.ai)" },
63
+ signal: ctl.signal,
64
+ });
65
+ } catch (err) {
66
+ clearTimeout(t);
67
+ throw new Error(`fetch failed: ${(err as Error).message}`);
68
+ }
69
+ clearTimeout(t);
70
+
71
+ const ct = res.headers.get("content-type") ?? "";
72
+ const buf = await res.arrayBuffer();
73
+ const rawText = new TextDecoder().decode(buf);
74
+ const rawBytes = rawText.length;
75
+
76
+ let extracted: string;
77
+ let title: string | null = null;
78
+
79
+ if (ct.includes("json")) {
80
+ extracted = compressJson(rawText);
81
+ } else if (
82
+ ct.includes("html") ||
83
+ ct.includes("xml") ||
84
+ (!ct.includes("text/plain") && rawText.trimStart().startsWith("<"))
85
+ ) {
86
+ title = extractTitle(rawText);
87
+ extracted = compressHtml(rawText);
88
+ } else {
89
+ extracted = rawText;
90
+ }
91
+
92
+ // LLM summarization path (Track D): runs BEFORE the byte cap so
93
+ // summarization can reduce truly large pages before we truncate as last resort.
94
+ //
95
+ // v1.18 (token-compression-wins): web content is denser than code — an
96
+ // article, doc, or blog post just over 4 KB typically already contains a
97
+ // dozen headings, paragraphs, and embedded links. Lowering the summarization
98
+ // threshold from the default 16 KB to 4 KB captures the fat middle of
99
+ // fetched pages without penalizing tiny responses (nav-only pages, quick
100
+ // JSON blobs, 404s) that pass through unchanged.
101
+ const WEBFETCH_SUMMARIZE_THRESHOLD_BYTES = 4 * 1024;
102
+ const summResult = await summarizeIfLarge(extracted, {
103
+ toolName: "ashlr__webfetch",
104
+ systemPrompt: PROMPTS.webfetch,
105
+ thresholdBytes: WEBFETCH_SUMMARIZE_THRESHOLD_BYTES,
106
+ });
107
+ const processedText = summResult.summarized || summResult.wasCached
108
+ ? summResult.text
109
+ : extracted;
110
+
111
+ const { text: capped, snipped } = snipCompact(processedText, maxBytes);
112
+
113
+ const lines: string[] = [];
114
+ if (prompt) lines.push(`[webfetch · prompt: "${prompt}"]`);
115
+ if (title) lines.push(`# ${title}\n`);
116
+ lines.push(capped);
117
+ if (snipped) {
118
+ lines.push(`\n[content truncated at ${maxBytes} bytes — pass a larger maxBytes to see more]`);
119
+ }
120
+
121
+ const compactBytes = lines.join("\n").length;
122
+ await recordSavingAccurate({
123
+ rawBytes,
124
+ compactBytes,
125
+ toolName: "ashlr__webfetch",
126
+ cacheHit: summResult.wasCached,
127
+ });
128
+
129
+ const ratio = rawBytes > 0 ? ((1 - compactBytes / rawBytes) * 100).toFixed(0) : "0";
130
+ lines.push(
131
+ `\n[ashlr__webfetch] URL: ${url} · raw: ${rawBytes}bytes · extracted: ${compactBytes}bytes · ${ratio}% reduction`,
132
+ );
133
+
134
+ const webBadgeOpts = {
135
+ toolName: "ashlr__webfetch",
136
+ rawBytes,
137
+ outputBytes: compactBytes,
138
+ };
139
+ if (confidenceTier(webBadgeOpts) === "low") {
140
+ await logEvent("tool_low_confidence_shipped", { tool: "ashlr__webfetch", reason: "low-confidence" });
141
+ }
142
+ return lines.join("\n") + confidenceBadge(webBadgeOpts);
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Registration (side-effect on import)
147
+ // ---------------------------------------------------------------------------
148
+
149
+ registerTool({
150
+ name: "ashlr__webfetch",
151
+ description:
152
+ "Token-efficient URL fetcher. Aggressively extracts article text from HTML (title + main content, strips nav/scripts/styles), pretty-prints + array-elides JSON, byte-caps plain text. Default cap 100 KB vs native WebFetch which is uncapped. Use instead of WebFetch when you want article content — saves 60-95% tokens on typical pages.",
153
+ inputSchema: {
154
+ type: "object",
155
+ properties: {
156
+ url: { type: "string", description: "URL to fetch (http/https only)" },
157
+ prompt: { type: "string", description: "What you're looking for — included as a hint in the output header" },
158
+ maxBytes: { type: "number", description: "Max bytes of extracted text (default 100000)" },
159
+ },
160
+ required: ["url"],
161
+ },
162
+ handler: async (args: Record<string, unknown>, _ctx: ToolCallContext): Promise<ToolResult> => {
163
+ const text = await doWebFetch(args as unknown as WebFetchArgs);
164
+ return { content: [{ type: "text", text }] };
165
+ },
166
+ });
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ashlr-webfetch MCP server — standalone entry point.
4
+ *
5
+ * Imports webfetch-server-handlers (registers ashlr__webfetch into the shared
6
+ * registry) then runs as a standalone stdio MCP server. Existing plugin.json
7
+ * entries continue to work unchanged through the v1.12 migration window.
8
+ *
9
+ * For in-process router use, import webfetch-server-handlers directly.
10
+ */
11
+
12
+ import "./webfetch-server-handlers";
13
+ import { runStandalone } from "./_tool-base";
14
+
15
+ // Re-export core logic so existing tests that import from this module keep working.
16
+ export { doWebFetch } from "./webfetch-server-handlers";
17
+
18
+ if (import.meta.main) {
19
+ await runStandalone("ashlr-webfetch", "0.1.0");
20
+ }