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,451 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * sessionend-hook-health-nudge.ts — Two SessionEnd nudges that surface
4
+ * hook health to users who would otherwise never look at it:
5
+ *
6
+ * 1. Hook-error visibility (nudge #2) — if any hook wrote to
7
+ * ~/.ashlr/hook-errors.jsonl during this session, fire one nudge
8
+ * with a count + first 2 error summaries + link to
9
+ * `/ashlr-doctor --errors`. Silent failures hide real bugs;
10
+ * this makes them visible without spamming.
11
+ *
12
+ * 2. Hook regression detector (nudge #7) — if any hook in
13
+ * ~/.ashlr/hook-timings.jsonl breached a budget this session
14
+ * (p95 > 200ms OR max > 1s), fire one nudge with the offending
15
+ * hook names + link to `/ashlr-hook-timings --flags-only`.
16
+ *
17
+ * Both nudges:
18
+ * - Scope to "current session" via stats.json `sessions[*].startedAt`
19
+ * (no session-id in the jsonl rows; we approximate by `ts >= sessionStart`).
20
+ * - Throttle via ~/.ashlr/hook-health-nudge-state.json so the same
21
+ * category cannot re-fire within 24h across session boundaries.
22
+ * - Tail-read each jsonl (last 500 lines) so a very large file never
23
+ * blows past the SessionEnd 2s safety net (v1.29).
24
+ *
25
+ * Best-effort: any I/O error → null (no nudge). Never throws. Never blocks.
26
+ * Output: prints nudge text(s) to stdout, separated by blank lines. Exits 0.
27
+ */
28
+
29
+ import {
30
+ closeSync,
31
+ existsSync,
32
+ fstatSync,
33
+ mkdirSync,
34
+ openSync,
35
+ readFileSync,
36
+ readSync,
37
+ renameSync,
38
+ writeFileSync,
39
+ } from "fs";
40
+ import { homedir } from "os";
41
+ import { join } from "path";
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Thresholds + windows
45
+ // ---------------------------------------------------------------------------
46
+
47
+ /** p95 budget — anything above this triggers a regression nudge. */
48
+ export const REGRESSION_P95_MS = 200;
49
+ /** Max budget — a single sample over this triggers a regression nudge. */
50
+ export const REGRESSION_MAX_MS = 1000;
51
+ /** Minimum sample count for p95 to be meaningful. */
52
+ export const MIN_SAMPLES_FOR_P95 = 5;
53
+ /** Per-category throttle window (24h). */
54
+ export const THROTTLE_WINDOW_MS = 24 * 60 * 60_000;
55
+ /** Tail-read line cap — both jsonls. Keeps total scan budget well under 50ms. */
56
+ export const TAIL_LINES_CAP = 500;
57
+ /** Hard byte ceiling per tail read, independent of file size. */
58
+ export const TAIL_BYTES_CAP = 256 * 1024;
59
+ const TAIL_CHUNK_BYTES = 16 * 1024;
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // State
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export interface HealthNudgeState {
66
+ /** Epoch ms of the last hook-error nudge (any). */
67
+ last_error_nudge_at: number;
68
+ /** Epoch ms of the last hook-regression nudge (any). */
69
+ last_regression_nudge_at: number;
70
+ }
71
+
72
+ const defaultState = (): HealthNudgeState => ({
73
+ last_error_nudge_at: 0,
74
+ last_regression_nudge_at: 0,
75
+ });
76
+
77
+ export function healthNudgeStatePath(homeDir: string = homedir()): string {
78
+ return join(homeDir, ".ashlr", "hook-health-nudge-state.json");
79
+ }
80
+
81
+ export function readHealthState(homeDir: string = homedir()): HealthNudgeState {
82
+ try {
83
+ const p = healthNudgeStatePath(homeDir);
84
+ if (!existsSync(p)) return defaultState();
85
+ const raw = JSON.parse(readFileSync(p, "utf-8")) as Partial<HealthNudgeState>;
86
+ return {
87
+ last_error_nudge_at:
88
+ typeof raw.last_error_nudge_at === "number" ? raw.last_error_nudge_at : 0,
89
+ last_regression_nudge_at:
90
+ typeof raw.last_regression_nudge_at === "number" ? raw.last_regression_nudge_at : 0,
91
+ };
92
+ } catch {
93
+ return defaultState();
94
+ }
95
+ }
96
+
97
+ export function writeHealthState(
98
+ state: HealthNudgeState,
99
+ homeDir: string = homedir(),
100
+ ): void {
101
+ try {
102
+ const dir = join(homeDir, ".ashlr");
103
+ mkdirSync(dir, { recursive: true });
104
+ const finalPath = healthNudgeStatePath(homeDir);
105
+ const tmpPath = `${finalPath}.${process.pid}.tmp`;
106
+ writeFileSync(tmpPath, JSON.stringify(state, null, 2));
107
+ renameSync(tmpPath, finalPath);
108
+ } catch {
109
+ /* best-effort */
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Tail reader — bounded scan to respect 2s safety net
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /**
118
+ * Read the last `lineCap` lines of a JSONL file, parse each as JSON,
119
+ * drop malformed lines. Returns [] if the file does not exist or fails to
120
+ * read. Bounded to `lineCap` to protect against pathological files.
121
+ */
122
+ export function tailJsonl<T = Record<string, unknown>>(
123
+ path: string,
124
+ lineCap: number = TAIL_LINES_CAP,
125
+ byteCap: number = TAIL_BYTES_CAP,
126
+ ): T[] {
127
+ let fd: number | undefined;
128
+ try {
129
+ if (!existsSync(path)) return [];
130
+ fd = openSync(path, "r");
131
+ const size = fstatSync(fd).size;
132
+ if (size === 0 || lineCap <= 0 || byteCap <= 0) return [];
133
+ let position = size;
134
+ let bytes = 0;
135
+ let newlineCount = 0;
136
+ const chunks: Buffer[] = [];
137
+ while (position > 0 && bytes < byteCap && newlineCount <= lineCap) {
138
+ const length = Math.min(TAIL_CHUNK_BYTES, position, byteCap - bytes);
139
+ position -= length;
140
+ const chunk = Buffer.allocUnsafe(length);
141
+ const read = readSync(fd, chunk, 0, length, position);
142
+ if (read !== length) return [];
143
+ chunks.unshift(chunk);
144
+ bytes += read;
145
+ for (const byte of chunk) if (byte === 10) newlineCount++;
146
+ }
147
+ let raw = Buffer.concat(chunks).toString("utf8");
148
+ // Starting mid-file means the first bytes are a partial row.
149
+ if (position > 0) {
150
+ const firstNewline = raw.indexOf("\n");
151
+ raw = firstNewline === -1 ? "" : raw.slice(firstNewline + 1);
152
+ }
153
+ const allLines = raw.split("\n");
154
+ const slice = allLines.filter((line) => line.trim()).slice(-lineCap);
155
+ const out: T[] = [];
156
+ for (const line of slice) {
157
+ const t = line.trim();
158
+ if (!t) continue;
159
+ try {
160
+ const parsed: unknown = JSON.parse(t);
161
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
162
+ out.push(parsed as T);
163
+ }
164
+ } catch {
165
+ /* skip malformed */
166
+ }
167
+ }
168
+ return out;
169
+ } catch {
170
+ return [];
171
+ } finally {
172
+ if (fd !== undefined) {
173
+ try { closeSync(fd); } catch { /* best effort */ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Session-start resolver — reuses stats.json (same as sessionend-savings-nudge)
180
+ // ---------------------------------------------------------------------------
181
+
182
+ interface StatsBucket {
183
+ startedAt?: string;
184
+ tokensSaved?: number;
185
+ }
186
+ interface StatsFile {
187
+ sessions?: Record<string, StatsBucket>;
188
+ }
189
+
190
+ /**
191
+ * Pick the most active session's startedAt as "current session". Falls back
192
+ * to `now - 1h` when nothing is available — that bound keeps nudges
193
+ * locally scoped while still finding records on a fresh install.
194
+ */
195
+ export function resolveSessionStartMs(
196
+ homeDir: string = homedir(),
197
+ nowMs: number = Date.now(),
198
+ ): number {
199
+ try {
200
+ const p = join(homeDir, ".ashlr", "stats.json");
201
+ if (!existsSync(p)) return nowMs - 60 * 60_000;
202
+ const stats = JSON.parse(readFileSync(p, "utf-8")) as StatsFile;
203
+ let bestStart = "";
204
+ let bestTokens = -1;
205
+ for (const bucket of Object.values(stats.sessions ?? {})) {
206
+ const tok = bucket.tokensSaved ?? 0;
207
+ if (tok > bestTokens && typeof bucket.startedAt === "string") {
208
+ bestTokens = tok;
209
+ bestStart = bucket.startedAt;
210
+ }
211
+ }
212
+ if (!bestStart) return nowMs - 60 * 60_000;
213
+ const parsed = Date.parse(bestStart);
214
+ return Number.isFinite(parsed) ? parsed : nowMs - 60 * 60_000;
215
+ } catch {
216
+ return nowMs - 60 * 60_000;
217
+ }
218
+ }
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // Nudge #2 — hook-error visibility
222
+ // ---------------------------------------------------------------------------
223
+
224
+ export interface HookErrorRecord {
225
+ ts: string;
226
+ hook: string;
227
+ context: string;
228
+ error: string;
229
+ }
230
+
231
+ export interface ErrorNudgeResult {
232
+ nudgeText: string | null;
233
+ count: number;
234
+ errors: HookErrorRecord[];
235
+ }
236
+
237
+ /**
238
+ * Pure: build the nudge text. Exported for tests.
239
+ */
240
+ export function buildErrorNudge(count: number, errs: HookErrorRecord[]): string {
241
+ const head =
242
+ count === 1
243
+ ? `1 hook error this session.`
244
+ : `${count} hook errors this session.`;
245
+ const lines: string[] = [head];
246
+ for (const e of errs.slice(0, 2)) {
247
+ // Trim very long error bodies so we don't dump a stack trace into the
248
+ // SessionEnd channel. 120 chars is the same cap used by /ashlr-doctor.
249
+ const trimmed = e.error.length > 120 ? e.error.slice(0, 117) + "..." : e.error;
250
+ lines.push(` • ${e.hook}: ${trimmed}`);
251
+ }
252
+ if (count > 2) lines.push(` + ${count - 2} more`);
253
+ lines.push("Run /ashlr-doctor --errors to inspect them.");
254
+ return lines.join("\n");
255
+ }
256
+
257
+ /**
258
+ * Compute the error nudge given current-session error records.
259
+ */
260
+ export function computeErrorNudge(
261
+ records: HookErrorRecord[],
262
+ state: HealthNudgeState,
263
+ nowMs: number,
264
+ ): ErrorNudgeResult {
265
+ if (records.length === 0) return { nudgeText: null, count: 0, errors: [] };
266
+ // Throttle: same nudge category cannot re-fire within 24h.
267
+ if (nowMs - state.last_error_nudge_at < THROTTLE_WINDOW_MS) {
268
+ return { nudgeText: null, count: records.length, errors: records };
269
+ }
270
+ return {
271
+ nudgeText: buildErrorNudge(records.length, records),
272
+ count: records.length,
273
+ errors: records,
274
+ };
275
+ }
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // Nudge #7 — hook regression detector
279
+ // ---------------------------------------------------------------------------
280
+
281
+ export interface HookTimingRow {
282
+ ts: string;
283
+ hook: string;
284
+ tool?: string | null;
285
+ durationMs: number;
286
+ outcome: string;
287
+ }
288
+
289
+ export interface RegressionFinding {
290
+ hook: string;
291
+ p95: number;
292
+ max: number;
293
+ samples: number;
294
+ }
295
+
296
+ export interface RegressionNudgeResult {
297
+ nudgeText: string | null;
298
+ findings: RegressionFinding[];
299
+ }
300
+
301
+ function percentile(sorted: number[], p: number): number {
302
+ if (sorted.length === 0) return 0;
303
+ if (sorted.length === 1) return sorted[0]!;
304
+ const idx = (p / 100) * (sorted.length - 1);
305
+ const lo = Math.floor(idx);
306
+ const hi = Math.ceil(idx);
307
+ if (lo === hi) return sorted[lo]!;
308
+ const frac = idx - lo;
309
+ return Math.round(sorted[lo]! + frac * (sorted[hi]! - sorted[lo]!));
310
+ }
311
+
312
+ export function buildRegressionNudge(findings: RegressionFinding[]): string {
313
+ const names = findings.map((f) => f.hook).slice(0, 3).join(", ");
314
+ const extra = findings.length > 3 ? ` (+${findings.length - 3} more)` : "";
315
+ const detail = findings
316
+ .slice(0, 3)
317
+ .map((f) => {
318
+ const parts: string[] = [];
319
+ if (f.p95 > REGRESSION_P95_MS) parts.push(`p95 ${f.p95}ms`);
320
+ if (f.max >= REGRESSION_MAX_MS) {
321
+ const maxStr = f.max >= 1000 ? `${(f.max / 1000).toFixed(1)}s` : `${f.max}ms`;
322
+ parts.push(`max ${maxStr}`);
323
+ }
324
+ return ` • ${f.hook} — ${parts.join(" · ")}`;
325
+ })
326
+ .join("\n");
327
+ return [
328
+ `Hook performance regressions this session: ${names}${extra}.`,
329
+ detail,
330
+ "Run /ashlr-hook-timings --flags-only for details.",
331
+ ].join("\n");
332
+ }
333
+
334
+ /**
335
+ * Compute regression findings + nudge. A hook is flagged when its
336
+ * session-window p95 > 200ms (with ≥5 samples) OR its max ≥ 1000ms.
337
+ */
338
+ export function computeRegressionNudge(
339
+ records: HookTimingRow[],
340
+ state: HealthNudgeState,
341
+ nowMs: number,
342
+ ): RegressionNudgeResult {
343
+ if (records.length === 0) return { nudgeText: null, findings: [] };
344
+
345
+ // Group by hook.
346
+ const byHook = new Map<string, number[]>();
347
+ for (const r of records) {
348
+ const arr = byHook.get(r.hook) ?? [];
349
+ arr.push(r.durationMs);
350
+ byHook.set(r.hook, arr);
351
+ }
352
+
353
+ const findings: RegressionFinding[] = [];
354
+ for (const [hook, durations] of byHook) {
355
+ const sorted = [...durations].sort((a, b) => a - b);
356
+ const max = sorted[sorted.length - 1] ?? 0;
357
+ const p95 = sorted.length >= MIN_SAMPLES_FOR_P95 ? percentile(sorted, 95) : 0;
358
+ const p95Breach = p95 > REGRESSION_P95_MS;
359
+ const maxBreach = max >= REGRESSION_MAX_MS;
360
+ if (p95Breach || maxBreach) {
361
+ findings.push({ hook, p95, max, samples: sorted.length });
362
+ }
363
+ }
364
+
365
+ if (findings.length === 0) return { nudgeText: null, findings: [] };
366
+
367
+ // Sort worst-first by max then p95.
368
+ findings.sort((a, b) => b.max - a.max || b.p95 - a.p95);
369
+
370
+ if (nowMs - state.last_regression_nudge_at < THROTTLE_WINDOW_MS) {
371
+ return { nudgeText: null, findings };
372
+ }
373
+
374
+ return { nudgeText: buildRegressionNudge(findings), findings };
375
+ }
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // Orchestrator — read state, scan files, emit, persist
379
+ // ---------------------------------------------------------------------------
380
+
381
+ export interface FireResult {
382
+ errorNudge: string | null;
383
+ regressionNudge: string | null;
384
+ }
385
+
386
+ export function checkAndFireHealthNudges(
387
+ homeDir: string = homedir(),
388
+ nowMs: number = Date.now(),
389
+ ): FireResult {
390
+ try {
391
+ const sessionStartMs = resolveSessionStartMs(homeDir, nowMs);
392
+ const state = readHealthState(homeDir);
393
+
394
+ // ----- Nudge #2: hook-errors -----
395
+ const errPath = join(homeDir, ".ashlr", "hook-errors.jsonl");
396
+ const allErrs = tailJsonl<HookErrorRecord>(errPath);
397
+ const sessionErrs = allErrs.filter((e) => {
398
+ const t = Date.parse(e.ts);
399
+ return Number.isFinite(t) && t >= sessionStartMs;
400
+ });
401
+ const errorResult = computeErrorNudge(sessionErrs, state, nowMs);
402
+
403
+ // ----- Nudge #7: hook regression -----
404
+ let regressionResult: RegressionNudgeResult = { nudgeText: null, findings: [] };
405
+ try {
406
+ const timingsPath = join(homeDir, ".ashlr", "hook-timings.jsonl");
407
+ const activeTimings = tailJsonl<HookTimingRow>(timingsPath, TAIL_LINES_CAP);
408
+ const retainedTimings = activeTimings.length < TAIL_LINES_CAP
409
+ ? tailJsonl<HookTimingRow>(`${timingsPath}.1`, TAIL_LINES_CAP - activeTimings.length)
410
+ : [];
411
+ const allTimings = [...retainedTimings, ...activeTimings];
412
+ const sessionTimings = allTimings.filter((t) => {
413
+ const ts = Date.parse(t.ts);
414
+ return Number.isFinite(ts) && ts >= sessionStartMs;
415
+ });
416
+ regressionResult = computeRegressionNudge(sessionTimings, state, nowMs);
417
+ } catch {
418
+ // Timing corruption must not suppress an independently computed error nudge.
419
+ }
420
+
421
+ // Persist throttle state only when we actually emitted.
422
+ const next: HealthNudgeState = { ...state };
423
+ if (errorResult.nudgeText) next.last_error_nudge_at = nowMs;
424
+ if (regressionResult.nudgeText) next.last_regression_nudge_at = nowMs;
425
+ if (errorResult.nudgeText || regressionResult.nudgeText) {
426
+ writeHealthState(next, homeDir);
427
+ }
428
+
429
+ return {
430
+ errorNudge: errorResult.nudgeText,
431
+ regressionNudge: regressionResult.nudgeText,
432
+ };
433
+ } catch {
434
+ return { errorNudge: null, regressionNudge: null };
435
+ }
436
+ }
437
+
438
+ // ---------------------------------------------------------------------------
439
+ // CLI entry — invoked by session-end-consolidate.ts
440
+ // ---------------------------------------------------------------------------
441
+
442
+ if (import.meta.main) {
443
+ const { errorNudge, regressionNudge } = checkAndFireHealthNudges();
444
+ const parts: string[] = [];
445
+ if (errorNudge) parts.push(errorNudge);
446
+ if (regressionNudge) parts.push(regressionNudge);
447
+ if (parts.length > 0) {
448
+ process.stdout.write("\n" + parts.join("\n\n") + "\n");
449
+ }
450
+ process.exit(0);
451
+ }