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,85 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * pretooluse-websearch.ts — PreToolUse hook for the WebSearch tool.
4
+ *
5
+ * In redirect mode (default) this hook BLOCKS the native WebSearch call and
6
+ * routes the agent to ashlr__websearch, which deduplicates results by domain,
7
+ * truncates snippets to ~500 chars, and synthesizes a 1-paragraph summary.
8
+ *
9
+ * Set `ASHLR_HOOK_MODE=nudge` to downgrade to a soft suggestion.
10
+ * Set `ASHLR_HOOK_MODE=off` (or `~/.ashlr/settings.json { toolRedirect: false }`)
11
+ * for total pass-through.
12
+ */
13
+
14
+ import {
15
+ buildNudgeContext,
16
+ buildPassThrough,
17
+ buildRedirectBlock,
18
+ flushHookTimings,
19
+ getHookModeFor,
20
+ parsePayload,
21
+ readStdin,
22
+ recordHookTiming,
23
+ } from "./pretooluse-common";
24
+ import { installHookTimeout } from "./pretooluse-common";
25
+
26
+ if (import.meta.main) installHookTimeout("pretooluse-websearch");
27
+
28
+ const hookStartedAt = Date.now();
29
+
30
+ async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
31
+ recordHookTiming({ hook: "pretooluse-websearch", tool, durationMs: Date.now() - hookStartedAt, outcome });
32
+ await flushHookTimings();
33
+ process.exit(code);
34
+ }
35
+
36
+ const raw = await readStdin();
37
+ const payload = parsePayload(raw);
38
+ if (!payload) await exit(0, "ok");
39
+
40
+ const tool = payload!.tool_name || undefined;
41
+ if (payload!.tool_name !== "WebSearch") await exit(0, "ok", tool);
42
+ if (payload!.bypass) await exit(0, "bypass", tool);
43
+
44
+ const mode = getHookModeFor("websearch");
45
+ if (mode === "off") {
46
+ process.stdout.write(JSON.stringify(buildPassThrough()));
47
+ await exit(0, "ok", tool);
48
+ }
49
+
50
+ // Extract query from the raw payload for the redirect message.
51
+ let query = "<query>";
52
+ try {
53
+ const parsed = JSON.parse(raw) as { tool_input?: { query?: string } };
54
+ if (typeof parsed?.tool_input?.query === "string") query = parsed.tool_input.query;
55
+ } catch {
56
+ /* ignore */
57
+ }
58
+
59
+ if (mode === "nudge") {
60
+ process.stdout.write(
61
+ JSON.stringify({
62
+ hookSpecificOutput: {
63
+ hookEventName: "PreToolUse",
64
+ additionalContext:
65
+ `[ashlr] Prefer the MCP tool \`ashlr__websearch\` over the built-in WebSearch. ` +
66
+ `It deduplicates results by domain, truncates snippets to ~500 chars, and synthesizes ` +
67
+ `a 1-paragraph summary when more than 3 results are returned — saving 40-80% tokens. ` +
68
+ `Call it with { "query": ${JSON.stringify(query)} }.`,
69
+ },
70
+ }),
71
+ );
72
+ await exit(0, "ok", tool);
73
+ }
74
+
75
+ // Default: redirect mode — block and route to ashlr__websearch.
76
+ const safeQuery = query.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
77
+ const reason =
78
+ `[ashlr] Blocking the built-in WebSearch. Call ` +
79
+ `mcp__plugin_ashlr_ashlr__ashlr__websearch instead — it deduplicates results ` +
80
+ `by domain, truncates snippets to ~500 chars, and synthesizes a 1-paragraph ` +
81
+ `summary for result sets > 3 entries. ` +
82
+ `Equivalent call: { "query": "${safeQuery}" }. ` +
83
+ `Set ASHLR_HOOK_MODE=nudge to downgrade this redirect to a soft suggestion.`;
84
+ process.stdout.write(JSON.stringify(buildRedirectBlock(reason)));
85
+ await exit(0, "block", tool);
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * pretooluse-write.ts — PreToolUse hook for the Write tool (new-file creation).
4
+ *
5
+ * v1.22 (Track C): covers the Write tool for cases where ashlr__write provides
6
+ * compression benefit. The key distinction:
7
+ *
8
+ * - Write on a NON-EXISTENT path (new-file creation): pass through.
9
+ * ashlr__write on a new file still avoids echoing content, but since Claude
10
+ * Code's native Write for a new file doesn't echo content back either, the
11
+ * savings differential is minimal — don't block the agent.
12
+ *
13
+ * - Write on an EXISTING file (full rewrite): redirect to ashlr__write.
14
+ * Native Write echoes the full new content back; ashlr__write delegates to
15
+ * ashlr__edit (diff format) — ~80% token savings on large files.
16
+ *
17
+ * Note: The existing pretooluse-edit.ts hook also fires for Write (via its
18
+ * "Edit|Write|MultiEdit" matcher) and handles large existing files. This hook
19
+ * specifically targets Write with a Write-focused redirect message pointing at
20
+ * ashlr__write rather than ashlr__edit. The two hooks can coexist — each
21
+ * provides a redirect; Claude Code will use the first deny it sees.
22
+ *
23
+ * Size threshold: same 5KB as pretooluse-edit (smaller files: savings don't
24
+ * materialize, pass through to avoid friction).
25
+ */
26
+
27
+ import {
28
+ buildPassThrough,
29
+ buildRedirectBlock,
30
+ fileSize,
31
+ flushHookTimings,
32
+ getHookModeFor,
33
+ isInsideCwd,
34
+ isInsidePluginRoot,
35
+ parsePayload,
36
+ pluginRootFrom,
37
+ readStdin,
38
+ recordHookTiming,
39
+ } from "./pretooluse-common";
40
+ import { installHookTimeout } from "./pretooluse-common";
41
+
42
+ if (import.meta.main) installHookTimeout("pretooluse-write");
43
+
44
+ const THRESHOLD = 5120;
45
+ const hookStartedAt = Date.now();
46
+
47
+ async function exit(code: number, outcome: "ok" | "bypass" | "block" | "error", tool?: string): Promise<never> {
48
+ recordHookTiming({ hook: "pretooluse-write", tool, durationMs: Date.now() - hookStartedAt, outcome });
49
+ await flushHookTimings();
50
+ process.exit(code);
51
+ }
52
+
53
+ const raw = await readStdin();
54
+ const payload = parsePayload(raw);
55
+ if (!payload) await exit(0, "ok");
56
+
57
+ const tool = payload!.tool_name || undefined;
58
+ if (payload!.tool_name !== "Write") await exit(0, "ok", tool);
59
+ if (!payload!.file_path) await exit(0, "ok", tool);
60
+ if (payload!.bypass) await exit(0, "bypass", tool);
61
+
62
+ const pluginRoot = pluginRootFrom(import.meta.url);
63
+ if (isInsidePluginRoot(payload!.file_path, pluginRoot)) await exit(0, "ok", tool);
64
+
65
+ const mode = getHookModeFor("write");
66
+ if (mode === "off") {
67
+ process.stdout.write(JSON.stringify(buildPassThrough()));
68
+ await exit(0, "ok", tool);
69
+ }
70
+
71
+ // Nudge mode: soft suggestion for existing files only. New-file Write never nudged.
72
+ if (mode === "nudge") {
73
+ const size = fileSize(payload!.file_path);
74
+ if (size === null) {
75
+ // New file — pass through silently.
76
+ process.stdout.write(JSON.stringify(buildPassThrough()));
77
+ await exit(0, "ok", tool);
78
+ }
79
+ process.stdout.write(
80
+ JSON.stringify({
81
+ hookSpecificOutput: {
82
+ hookEventName: "PreToolUse",
83
+ additionalContext:
84
+ `[ashlr] You're rewriting an existing file with the built-in Write tool. ` +
85
+ `Prefer \`ashlr__write\` — it delegates to ashlr__edit (diff format) for ` +
86
+ `existing files, returning only a compact diff summary instead of the full new content. ` +
87
+ `Call it with { "filePath": "${payload!.file_path}", "content": "..." }.`,
88
+ },
89
+ }),
90
+ );
91
+ await exit(0, "ok", tool);
92
+ }
93
+
94
+ // Redirect mode: only redirect existing files above the size threshold.
95
+ const size = fileSize(payload!.file_path);
96
+ if (size === null) {
97
+ // New file — not compressible, pass through.
98
+ process.stdout.write(JSON.stringify(buildPassThrough()));
99
+ await exit(0, "ok", tool);
100
+ }
101
+ if (size! <= THRESHOLD) await exit(0, "ok", tool);
102
+
103
+ // Outside cwd: nudge only, don't block.
104
+ if (!isInsideCwd(payload!.file_path)) {
105
+ process.stdout.write(
106
+ JSON.stringify({
107
+ hookSpecificOutput: {
108
+ hookEventName: "PreToolUse",
109
+ additionalContext:
110
+ `[ashlr] You're rewriting an existing file with the built-in Write tool. ` +
111
+ `Prefer \`ashlr__write\` for compact diff output. ` +
112
+ `Call it with { "filePath": "${payload!.file_path}", "content": "..." }.`,
113
+ },
114
+ }),
115
+ );
116
+ await exit(0, "ok", tool);
117
+ }
118
+
119
+ const mcp = "mcp__plugin_ashlr_ashlr__ashlr__write";
120
+ const reason =
121
+ `[ashlr] To bypass: set ASHLR_HOOK_MODE=nudge in ~/.ashlr/config.json. ` +
122
+ `Current rule: blocking built-in Write on ${payload!.file_path} (${size} bytes) — ` +
123
+ `call ${mcp} instead, which delegates to ashlr__edit (diff format) for existing ` +
124
+ `files and returns only a compact diff summary, avoiding the full content echo ` +
125
+ `(~80% token savings on files this size). ` +
126
+ `Equivalent call: { "filePath": "${payload!.file_path}", "content": "..." }.`;
127
+ process.stdout.write(JSON.stringify(buildRedirectBlock(reason)));
128
+ await exit(0, "block", tool);
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * pulse-emit.ts — opt-in Pulse emitter.
4
+ *
5
+ * When `ASHLR_PULSE_OTLP_ENDPOINT` is set, fires one OTLP/HTTP-JSON span
6
+ * per tool call to a Pulse server (the repo next door, `ashlrai/ashlr-pulse`).
7
+ * When it's unset, the hook is a 1-ms exit — the plugin's zero-telemetry-by-
8
+ * default posture is preserved. There is no implicit "cloud" path; users who
9
+ * want shared visibility point at their own pulse server.
10
+ *
11
+ * Privacy
12
+ * - No prompts, completions, cwd paths, or tool input contents are sent.
13
+ * - The cwd is hashed (`claude.project.hash`); repo name + branch are sent
14
+ * as plain strings because those are already on the user's remote.
15
+ * - Token counts are NULL at the plugin layer — provider token usage lives
16
+ * in the LLM response, which the plugin doesn't see. Pulse correctly
17
+ * maps these to NULL columns.
18
+ *
19
+ * Kill switches / env
20
+ * - `ASHLR_PULSE_OTLP_ENDPOINT` — set to `http://host/api/otlp/v1/traces`
21
+ * - `ASHLR_PULSE_PAT` — Bearer PAT minted on the Pulse server.
22
+ * When set, takes precedence over the
23
+ * x-ashlr-user dev fallback. Required for
24
+ * production servers (NODE_ENV=production
25
+ * rejects the dev header).
26
+ * - `ASHLR_PULSE_USER` — overrides the `x-ashlr-user` header (dev only)
27
+ * - `ASHLR_PULSE_TIMEOUT_MS` — per-POST timeout (default 1500)
28
+ *
29
+ * Hook contract: PostToolUse receives tool_name / tool_input / tool_result on
30
+ * stdin as JSON. We never exit non-zero; the hook is fail-open.
31
+ */
32
+
33
+ import { createHash } from "crypto";
34
+ import { spawnSync } from "child_process";
35
+
36
+ const ENDPOINT = process.env.ASHLR_PULSE_OTLP_ENDPOINT;
37
+ if (!ENDPOINT) process.exit(0);
38
+
39
+ const TIMEOUT_MS = Number(process.env.ASHLR_PULSE_TIMEOUT_MS ?? "1500");
40
+ const USER = process.env.ASHLR_PULSE_USER ?? process.env.USER ?? "dev-local";
41
+ const PAT = process.env.ASHLR_PULSE_PAT;
42
+
43
+ function size(v: unknown): number {
44
+ if (v == null) return 0;
45
+ if (typeof v === "string") return Buffer.byteLength(v, "utf-8");
46
+ try { return Buffer.byteLength(JSON.stringify(v), "utf-8"); } catch { return 0; }
47
+ }
48
+
49
+ function gitInfo(cwd: string): { repo: string | null; branch: string | null } {
50
+ try {
51
+ const branch = spawnSync("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"], {
52
+ encoding: "utf-8",
53
+ timeout: 500,
54
+ });
55
+ const remote = spawnSync("git", ["-C", cwd, "config", "--get", "remote.origin.url"], {
56
+ encoding: "utf-8",
57
+ timeout: 500,
58
+ });
59
+ const br = branch.status === 0 ? branch.stdout.trim() : null;
60
+ let repo: string | null = null;
61
+ if (remote.status === 0) {
62
+ const url = remote.stdout.trim();
63
+ // Extract owner/name from either https://host/owner/name(.git) or git@host:owner/name(.git)
64
+ const m = url.match(/[:\/]([^:\/]+\/[^:\/]+?)(?:\.git)?$/);
65
+ if (m) repo = m[1] ?? null;
66
+ }
67
+ return { repo, branch: br };
68
+ } catch {
69
+ return { repo: null, branch: null };
70
+ }
71
+ }
72
+
73
+ function sessionId(): string {
74
+ const explicit = process.env.CLAUDE_SESSION_ID;
75
+ if (explicit && explicit.trim().length > 0) return explicit.trim();
76
+ const seed = `${process.cwd()}:${process.ppid ?? "?"}`;
77
+ let h = 0;
78
+ for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0;
79
+ return `h${(h >>> 0).toString(16)}`;
80
+ }
81
+
82
+ function nowUnixNano(): string {
83
+ // hrtime.bigint() is process-local; map via wall clock + high-res offset so
84
+ // Pulse can sort across processes. OTLP accepts the int-as-string form.
85
+ return String(BigInt(Date.now()) * 1_000_000n);
86
+ }
87
+
88
+ interface HookInput {
89
+ tool_name?: string;
90
+ tool_input?: unknown;
91
+ tool_result?: unknown;
92
+ tool_response?: unknown;
93
+ }
94
+
95
+ function buildPayload(input: HookInput): unknown {
96
+ const tool = typeof input.tool_name === "string" ? input.tool_name : "unknown";
97
+ const inBytes = size(input.tool_input);
98
+ const outBytes = size(input.tool_result ?? input.tool_response);
99
+ const cwd = process.cwd();
100
+ const projectHash = createHash("sha256").update(cwd).digest("hex");
101
+ const { repo, branch } = gitInfo(cwd);
102
+ const sid = sessionId();
103
+ const ts = nowUnixNano();
104
+
105
+ const attributes: Array<{ key: string; value: Record<string, unknown> }> = [
106
+ // GenAI marker — Pulse's spanToActivityEvent requires at least one of
107
+ // gen_ai.system or a claude.* attribute.
108
+ { key: "gen_ai.system", value: { stringValue: "anthropic" } },
109
+ { key: "claude.session.id", value: { stringValue: sid } },
110
+ { key: "claude.tool.calls_count", value: { intValue: "1" } },
111
+ { key: "claude.tool.calls_types", value: { stringValue: tool } },
112
+ { key: "claude.project.hash", value: { stringValue: projectHash } },
113
+ { key: "claude.tool.input_bytes", value: { intValue: String(inBytes) } },
114
+ { key: "claude.tool.output_bytes", value: { intValue: String(outBytes) } },
115
+ ];
116
+ if (repo) attributes.push({ key: "claude.repo.name", value: { stringValue: repo } });
117
+ if (branch) attributes.push({ key: "claude.git.branch", value: { stringValue: branch } });
118
+
119
+ return {
120
+ resourceSpans: [
121
+ {
122
+ resource: {
123
+ attributes: [
124
+ { key: "service.name", value: { stringValue: "ashlr-plugin" } },
125
+ { key: "service.instance", value: { stringValue: sid } },
126
+ ],
127
+ },
128
+ scopeSpans: [
129
+ {
130
+ scope: { name: "ashlr-plugin", version: "1" },
131
+ spans: [
132
+ {
133
+ name: `tool.${tool}`,
134
+ startTimeUnixNano: ts,
135
+ endTimeUnixNano: ts,
136
+ attributes,
137
+ },
138
+ ],
139
+ },
140
+ ],
141
+ },
142
+ ],
143
+ };
144
+ }
145
+
146
+ async function postOtlp(payload: unknown): Promise<void> {
147
+ const ctl = new AbortController();
148
+ const t = setTimeout(() => ctl.abort(), TIMEOUT_MS);
149
+ try {
150
+ // PAT (Bearer) takes precedence — required for production Pulse
151
+ // servers. Falls back to the x-ashlr-user dev header for local-only
152
+ // setups. Only one auth header is sent so the server's auth path
153
+ // stays unambiguous.
154
+ const headers: Record<string, string> = { "content-type": "application/json" };
155
+ if (PAT) headers["authorization"] = `Bearer ${PAT}`;
156
+ else headers["x-ashlr-user"] = USER;
157
+
158
+ await fetch(ENDPOINT as string, {
159
+ method: "POST",
160
+ headers,
161
+ body: JSON.stringify(payload),
162
+ signal: ctl.signal,
163
+ });
164
+ } catch (e) {
165
+ // best-effort — never surface to the agent, but leave a stderr trace so
166
+ // production failures can be grepped.
167
+ process.stderr.write("[ashlr-pulse-emit] OTLP POST failed: " + (e instanceof Error ? e.message : String(e)) + "\n");
168
+ } finally {
169
+ clearTimeout(t);
170
+ }
171
+ }
172
+
173
+ // Drain stdin (hook input JSON), build payload, POST, exit 0.
174
+ const chunks: Buffer[] = [];
175
+ process.stdin.on("data", (c: Buffer) => chunks.push(c));
176
+ process.stdin.on("end", async () => {
177
+ let input: HookInput = {};
178
+ try {
179
+ const raw = Buffer.concat(chunks).toString("utf-8").trim();
180
+ if (raw) input = JSON.parse(raw) as HookInput;
181
+ } catch (e) {
182
+ // best-effort — leave a stderr trace.
183
+ process.stderr.write("[ashlr-pulse-emit] stdin parse failed: " + (e instanceof Error ? e.message : String(e)) + "\n");
184
+ }
185
+
186
+ try {
187
+ const payload = buildPayload(input);
188
+ await postOtlp(payload);
189
+ } catch (e) {
190
+ // never block, but leave a stderr trace.
191
+ process.stderr.write("[ashlr-pulse-emit] payload build/post failed: " + (e instanceof Error ? e.message : String(e)) + "\n");
192
+ }
193
+ process.exit(0);
194
+ });
195
+
196
+ // Expose for tests.
197
+ export { buildPayload };
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # ashlr-plugin SessionEnd: consolidate pending genome proposals.
3
+ #
4
+ # Invoked by Claude Code on session shutdown. Hands off to
5
+ # scripts/genome-auto-consolidate.ts which reads proposals.jsonl in the
6
+ # current project's genome, merges them into their target sections, and
7
+ # truncates the queue.
8
+ #
9
+ # Best-effort: a failed consolidation should never disturb the user's
10
+ # session exit. All output goes to stderr; the hook exits 0 always.
11
+
12
+ set +e
13
+
14
+ if [ "${ASHLR_GENOME_AUTO:-1}" = "0" ]; then
15
+ exit 0
16
+ fi
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$SCRIPT_DIR")}"
20
+ CONSOLIDATE_TS="$PLUGIN_ROOT/scripts/genome-auto-consolidate.ts"
21
+
22
+ if ! command -v bun >/dev/null 2>&1; then
23
+ exit 0
24
+ fi
25
+ if [ ! -f "$CONSOLIDATE_TS" ]; then
26
+ exit 0
27
+ fi
28
+
29
+ TARGET_DIR="${PROJECT_ROOT:-$PWD}"
30
+
31
+ # Run in background so session teardown isn't delayed by filesystem latency.
32
+ # The script is idempotent and bounded, so fire-and-forget is safe.
33
+ bun run "$CONSOLIDATE_TS" --dir "$TARGET_DIR" 1>&2 2>/dev/null &
34
+
35
+ exit 0
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * session-end-consolidate.ts — Cross-platform replacement for session-end-consolidate.sh.
4
+ *
5
+ * Invoked by Claude Code on session shutdown. Two responsibilities:
6
+ * 1. Consolidate — hand off to scripts/genome-auto-consolidate.ts which
7
+ * reads proposals.jsonl in the current project's genome, merges them
8
+ * into target sections, and truncates the queue.
9
+ * 2. Push (v1.17 T2.5, opt-in) — after consolidation, fire
10
+ * scripts/genome-cloud-push.ts so teammates pulling on SessionStart see
11
+ * this session's merged state. No-op when the repo has no
12
+ * .ashlrcode/genome/.cloud-id.
13
+ *
14
+ * Best-effort: a failed consolidation or push must never disturb the user's
15
+ * session exit. All output goes to stderr. Exits 0 always.
16
+ */
17
+
18
+ import { resolve, dirname, join } from "path";
19
+ import { fileURLToPath } from "url";
20
+ import { existsSync } from "fs";
21
+
22
+ if (process.env.ASHLR_GENOME_AUTO === "0") process.exit(0);
23
+
24
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
25
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT ?? resolve(scriptDir, "..");
26
+ const consolidateTs = join(pluginRoot, "scripts", "genome-auto-consolidate.ts");
27
+ const pushTs = join(pluginRoot, "scripts", "genome-cloud-push.ts");
28
+ const telemetryFlushTs = join(pluginRoot, "scripts", "telemetry-flush.ts");
29
+ const sessionEventEmitTs = join(pluginRoot, "scripts", "session-event-emit.ts");
30
+ const refreshTs = join(pluginRoot, "scripts", "genome-refresh-worker.ts");
31
+ const hookPerfEmitTs = join(pluginRoot, "hooks", "_hook-perf-emit.ts");
32
+ const hookHealthNudgeTs = join(pluginRoot, "hooks", "sessionend-hook-health-nudge.ts");
33
+ const statusLineNudgeTs = join(pluginRoot, "hooks", "sessionend-status-line-nudge.ts");
34
+
35
+ if (!existsSync(consolidateTs)) process.exit(0);
36
+
37
+ const targetDir = process.env.PROJECT_ROOT ?? process.cwd();
38
+
39
+ // Consolidation bound to 10s — generous for local fs; prevents a pathological
40
+ // consolidate from stalling session shutdown. Push is opt-in and bounded
41
+ // separately by its own network timeout.
42
+ const CONSOLIDATE_BUDGET_MS = 10_000;
43
+ const POST_HOOK_BUDGET_MS = 15_000;
44
+
45
+ const deadline = Date.now() + POST_HOOK_BUDGET_MS;
46
+
47
+ async function awaitWithBudget(
48
+ proc: { exited: Promise<number> },
49
+ budgetMs: number,
50
+ ): Promise<"ok" | "timeout"> {
51
+ let to: ReturnType<typeof setTimeout> | null = null;
52
+ const timeout = new Promise<"timeout">((resolveP) => {
53
+ to = setTimeout(() => resolveP("timeout"), budgetMs);
54
+ });
55
+ const done = proc.exited.then(() => "ok" as const);
56
+ const result = await Promise.race([done, timeout]);
57
+ if (to) clearTimeout(to);
58
+ return result;
59
+ }
60
+
61
+ async function main(): Promise<void> {
62
+ // 1. Consolidate. Await so the push path below sees the merged state.
63
+ try {
64
+ const proc = Bun.spawn(["bun", "run", consolidateTs, "--dir", targetDir], {
65
+ stdin: "ignore",
66
+ stdout: "ignore",
67
+ stderr: "pipe",
68
+ });
69
+ await awaitWithBudget(proc, CONSOLIDATE_BUDGET_MS);
70
+ } catch {
71
+ /* best-effort */
72
+ }
73
+
74
+ // 2. Push — only if the push script exists (older plugin versions don't
75
+ // ship it) and we still have budget. Fire-and-forget; network latency
76
+ // doesn't block shutdown.
77
+ if (existsSync(pushTs) && Date.now() < deadline) {
78
+ try {
79
+ Bun.spawn(["bun", "run", pushTs, "--quiet", "--cwd", targetDir], {
80
+ stdin: "ignore",
81
+ stdout: "ignore",
82
+ stderr: "pipe",
83
+ });
84
+ } catch {
85
+ /* best-effort */
86
+ }
87
+ }
88
+
89
+ // 3. Genome incremental refresh — process pending edits accumulated during
90
+ // the session. Fire-and-forget with a 10s budget; a slow refresh never
91
+ // blocks session exit. Uses --quiet so no output pollutes the hook channel.
92
+ if (existsSync(refreshTs) && Date.now() < deadline) {
93
+ try {
94
+ Bun.spawn(["bun", "run", refreshTs, "--quiet"], {
95
+ stdin: "ignore",
96
+ stdout: "ignore",
97
+ stderr: "pipe",
98
+ });
99
+ } catch {
100
+ /* best-effort — refresh never blocks shutdown */
101
+ }
102
+ }
103
+
104
+ // 4. Hook-perf telemetry rollup (opt-in). Reads hook-timings.jsonl, computes
105
+ // p50/p99 per hook, and writes hook_perf events to the telemetry buffer.
106
+ // Fire-and-forget; runs before the flush so events are captured in this batch.
107
+ if (existsSync(hookPerfEmitTs) && Date.now() < deadline) {
108
+ try {
109
+ Bun.spawn(["bun", "run", hookPerfEmitTs], {
110
+ stdin: "ignore",
111
+ stdout: "ignore",
112
+ stderr: "pipe",
113
+ });
114
+ } catch {
115
+ /* best-effort — hook perf telemetry never blocks shutdown */
116
+ }
117
+ }
118
+
119
+ // 5. Hook health nudges (errors + regression). Bounded by a 1.5s budget
120
+ // and runs awaitably so its stdout reaches the SessionEnd channel before
121
+ // shutdown. Tail-bounded reads keep this well under the 2s hook safety net.
122
+ if (existsSync(hookHealthNudgeTs) && Date.now() < deadline) {
123
+ try {
124
+ const proc = Bun.spawn(["bun", "run", hookHealthNudgeTs], {
125
+ stdin: "ignore",
126
+ stdout: "inherit",
127
+ stderr: "pipe",
128
+ });
129
+ await awaitWithBudget(proc, 1_500);
130
+ } catch {
131
+ /* best-effort — hook health nudges never block shutdown */
132
+ }
133
+ }
134
+
135
+ // 6. Status-line discovery nudge — one-shot per user. Bounded by a
136
+ // 1s budget and runs awaitably so stdout reaches the SessionEnd channel
137
+ // before shutdown. Tiny JSON reads only; never touches jsonls.
138
+ if (existsSync(statusLineNudgeTs) && Date.now() < deadline) {
139
+ try {
140
+ const proc = Bun.spawn(["bun", "run", statusLineNudgeTs], {
141
+ stdin: "ignore",
142
+ stdout: "inherit",
143
+ stderr: "pipe",
144
+ });
145
+ await awaitWithBudget(proc, 1_000);
146
+ } catch {
147
+ /* best-effort — status-line discovery nudge never blocks shutdown */
148
+ }
149
+ }
150
+
151
+ // 7. Telemetry flush (opt-in, no-op when telemetry is off). Fire-and-forget;
152
+ // network errors are silently dropped by the flush script itself. We spawn
153
+ // rather than import so a crash in the flush script never affects shutdown.
154
+ if (existsSync(telemetryFlushTs) && Date.now() < deadline) {
155
+ try {
156
+ Bun.spawn(["bun", "run", telemetryFlushTs], {
157
+ stdin: "ignore",
158
+ stdout: "ignore",
159
+ stderr: "pipe",
160
+ });
161
+ } catch {
162
+ /* best-effort — telemetry never blocks shutdown */
163
+ }
164
+ }
165
+
166
+ // 8. Q4 session-event emit. Last step in the chain.
167
+ // Posts ONE structured event per session (tool counts, savings totals,
168
+ // discovery refs, branch SHA) to /v1/session-events. Honors the same
169
+ // opt-in telemetry consent gate as the telemetry buffer flush above.
170
+ //
171
+ // Awaited against a 500ms budget so SessionEnd never exceeds the 2s
172
+ // hook safety net (v1.29) even when the network is slow — the script
173
+ // also enforces its own AbortSignal.timeout(500) internally.
174
+ if (existsSync(sessionEventEmitTs) && Date.now() < deadline) {
175
+ try {
176
+ const proc = Bun.spawn(["bun", "run", sessionEventEmitTs, "--dir", targetDir], {
177
+ stdin: "ignore",
178
+ stdout: "ignore",
179
+ stderr: "pipe",
180
+ });
181
+ await awaitWithBudget(proc, 500);
182
+ } catch {
183
+ /* best-effort — session-event emit never blocks shutdown */
184
+ }
185
+ }
186
+ }
187
+
188
+ main().finally(() => process.exit(0));