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,554 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ashlr hook-timings-report — reads ~/.ashlr/hook-timings.jsonl and renders
4
+ * a compact per-hook latency table with p50/p95/max and error/block rates.
5
+ *
6
+ * Exported surface:
7
+ * readHookTimings(path?) → HookTimingRecord[], fault-tolerant
8
+ * computeAggregates(records, windowHours?) → HookAggregate[]
9
+ *
10
+ * CLI: `bun run scripts/hook-timings-report.ts [--hours N]`
11
+ * Exits 0 always.
12
+ */
13
+
14
+ import {
15
+ readHookTimingsDetailed,
16
+ type HookTimingReadQuality,
17
+ type HookTimingRecord,
18
+ } from "./hook-timing-reader.ts";
19
+
20
+ export { readHookTimingsDetailed } from "./hook-timing-reader.ts";
21
+ export type { HookTimingReadQuality, HookTimingRecord } from "./hook-timing-reader.ts";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Types
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export interface HookAggregate {
28
+ hook: string;
29
+ calls: number;
30
+ p50: number;
31
+ p95: number;
32
+ max: number;
33
+ errorPct: number;
34
+ blockPct: number;
35
+ timeoutPct: number;
36
+ }
37
+
38
+ export type TrendClassification = "improved" | "regressed" | "stable" | "new";
39
+
40
+ export interface HookTrend {
41
+ hook: string;
42
+ current: { mean: number; p50: number; p95: number };
43
+ compare: { mean: number; p50: number; p95: number };
44
+ deltaMs: number; // absolute ms: current.p95 - compare.p95
45
+ deltaPct: number; // percent change (negative = improved)
46
+ trend: TrendClassification;
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Reading + parsing
51
+ // ---------------------------------------------------------------------------
52
+
53
+ /**
54
+ * Read and parse hook-timings.jsonl. Skips malformed lines silently.
55
+ * @param path Override the default ~/.ashlr/hook-timings.jsonl path.
56
+ */
57
+ export function readHookTimings(path?: string): HookTimingRecord[] {
58
+ return readHookTimingsDetailed({ path }).records;
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Aggregation
63
+ // ---------------------------------------------------------------------------
64
+
65
+ function percentile(sorted: number[], p: number): number {
66
+ if (sorted.length === 0) return 0;
67
+ if (sorted.length === 1) return sorted[0]!;
68
+ const idx = (p / 100) * (sorted.length - 1);
69
+ const lo = Math.floor(idx);
70
+ const hi = Math.ceil(idx);
71
+ if (lo === hi) return sorted[lo]!;
72
+ const frac = idx - lo;
73
+ return Math.round(sorted[lo]! + frac * (sorted[hi]! - sorted[lo]!));
74
+ }
75
+
76
+ /**
77
+ * Filter records whose ts parses to a time in [from, to) (to = Infinity means open-ended).
78
+ */
79
+ function filterByTime(
80
+ records: HookTimingRecord[],
81
+ from: number,
82
+ to: number = Infinity,
83
+ ): HookTimingRecord[] {
84
+ return records.filter((r) => {
85
+ const t = new Date(r.ts).getTime();
86
+ return !isNaN(t) && t >= from && t < to;
87
+ });
88
+ }
89
+
90
+ /** Group records by hook name. */
91
+ function groupByHook<T extends HookTimingRecord>(recs: T[]): Map<string, T[]> {
92
+ const m = new Map<string, T[]>();
93
+ for (const rec of recs) {
94
+ const arr = m.get(rec.hook) ?? [];
95
+ arr.push(rec);
96
+ m.set(rec.hook, arr);
97
+ }
98
+ return m;
99
+ }
100
+
101
+ /** {mean, p50, p95} summary over an unsorted duration list. */
102
+ function durationStats(durations: number[]): { mean: number; p50: number; p95: number } {
103
+ if (durations.length === 0) return { mean: 0, p50: 0, p95: 0 };
104
+ const sorted = [...durations].sort((a, b) => a - b);
105
+ const mean = Math.round(sorted.reduce((s, v) => s + v, 0) / sorted.length);
106
+ return { mean, p50: percentile(sorted, 50), p95: percentile(sorted, 95) };
107
+ }
108
+
109
+ /**
110
+ * Compute per-hook aggregates, optionally filtered to the last N hours.
111
+ * @param records Raw timing records.
112
+ * @param windowHours Window in hours (default: 24). Pass Infinity for all.
113
+ */
114
+ export function computeAggregates(
115
+ records: HookTimingRecord[],
116
+ windowHours = 24,
117
+ ): HookAggregate[] {
118
+ const cutoff = Date.now() - windowHours * 3_600_000;
119
+ const inWindow = filterByTime(records, cutoff);
120
+
121
+ const aggregates: HookAggregate[] = [];
122
+ for (const [hook, recs] of groupByHook(inWindow)) {
123
+ const durations = recs.map((r) => r.durationMs).sort((a, b) => a - b);
124
+ const errors = recs.filter((r) => r.outcome === "error").length;
125
+ const blocks = recs.filter((r) => r.outcome === "block").length;
126
+ const timeouts = recs.filter((r) => r.outcome === "timeout").length;
127
+ aggregates.push({
128
+ hook,
129
+ calls: recs.length,
130
+ p50: percentile(durations, 50),
131
+ p95: percentile(durations, 95),
132
+ max: durations[durations.length - 1] ?? 0,
133
+ errorPct: (errors / recs.length) * 100,
134
+ blockPct: (blocks / recs.length) * 100,
135
+ timeoutPct: (timeouts / recs.length) * 100,
136
+ });
137
+ }
138
+
139
+ return aggregates.sort((a, b) => b.calls - a.calls);
140
+ }
141
+
142
+ /**
143
+ * Compute trend data comparing the current window vs the previous equal-length
144
+ * compare window that immediately precedes it.
145
+ */
146
+ export function computeTrends(
147
+ records: HookTimingRecord[],
148
+ opts: { windowHours: number; compareHours: number },
149
+ ): HookTrend[] {
150
+ const { windowHours, compareHours } = opts;
151
+ const now = Date.now();
152
+ const currentCutoff = now - windowHours * 3_600_000;
153
+ const compareCutoff = currentCutoff - compareHours * 3_600_000;
154
+
155
+ const toDurations = (recs: HookTimingRecord[]) => recs.map((r) => r.durationMs);
156
+ const currentByHook = new Map(
157
+ [...groupByHook(filterByTime(records, currentCutoff))].map(([h, r]) => [h, toDurations(r)]),
158
+ );
159
+ const compareByHook = new Map(
160
+ [...groupByHook(filterByTime(records, compareCutoff, currentCutoff))].map(
161
+ ([h, r]) => [h, toDurations(r)],
162
+ ),
163
+ );
164
+
165
+ const trends: HookTrend[] = [];
166
+ for (const [hook, curDurations] of currentByHook) {
167
+ const curStats = durationStats(curDurations);
168
+ const cmpDurations = compareByHook.get(hook) ?? [];
169
+ const cmpStats = durationStats(cmpDurations);
170
+
171
+ const deltaMs = curStats.p95 - cmpStats.p95;
172
+ // When the compare window has no data for this hook, we can't compute a
173
+ // percentage. Previously this fell through to "stable" silently — which
174
+ // masked real regressions on hooks that only appeared in the current
175
+ // window. Tag them as "new" so callers (and the dashboard flag output)
176
+ // can surface them distinctly.
177
+ const hasCompareBaseline = cmpDurations.length > 0 && cmpStats.p95 > 0;
178
+ const deltaPct = hasCompareBaseline ? (deltaMs / cmpStats.p95) * 100 : 0;
179
+
180
+ let trend: TrendClassification;
181
+ if (!hasCompareBaseline) trend = "new";
182
+ else if (deltaPct <= -20) trend = "improved";
183
+ else if (deltaPct >= 20) trend = "regressed";
184
+ else trend = "stable";
185
+
186
+ trends.push({ hook, current: curStats, compare: cmpStats, deltaMs, deltaPct, trend });
187
+ }
188
+
189
+ return trends;
190
+ }
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Rendering
194
+ // ---------------------------------------------------------------------------
195
+
196
+ function fmtMs(ms: number): string {
197
+ if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
198
+ return `${ms}ms`;
199
+ }
200
+
201
+ function fmtPct(pct: number, hasOutcome: boolean): string {
202
+ if (!hasOutcome) return "—";
203
+ return `${pct.toFixed(1)}%`;
204
+ }
205
+
206
+ function fmtNum(n: number): string {
207
+ return n.toLocaleString("en-US");
208
+ }
209
+
210
+ function medianOverAll(aggregates: HookAggregate[]): number {
211
+ if (aggregates.length === 0) return 0;
212
+ const total = aggregates.reduce((s, a) => s + a.calls, 0);
213
+ if (total === 0) return 0;
214
+ // weighted median approximation: sort all p50s by call count and pick mid
215
+ const sorted = [...aggregates].sort((a, b) => a.p50 - b.p50);
216
+ let acc = 0;
217
+ const mid = total / 2;
218
+ for (const a of sorted) {
219
+ acc += a.calls;
220
+ if (acc >= mid) return a.p50;
221
+ }
222
+ return sorted[sorted.length - 1]!.p50;
223
+ }
224
+
225
+ function buildFlags(aggregates: HookAggregate[]): string[] {
226
+ const flags: string[] = [];
227
+ for (const a of aggregates) {
228
+ if (a.p95 > 200) {
229
+ flags.push(
230
+ `⚠ ${a.hook} p95 ${fmtMs(a.p95)} exceeds 200ms threshold — consider parallelization`,
231
+ );
232
+ }
233
+ if (a.p95 > 100 && a.p95 <= 200) {
234
+ flags.push(
235
+ `Flagging: ${a.hook} p95 > 100ms — consider parallelization.`,
236
+ );
237
+ }
238
+ if (a.max >= 1000) {
239
+ flags.push(
240
+ `Flag: ${a.hook} max ${fmtMs(a.max)} — investigate slow path.`,
241
+ );
242
+ }
243
+ }
244
+ return flags;
245
+ }
246
+
247
+ export function renderReport(
248
+ aggregates: HookAggregate[],
249
+ windowHours: number,
250
+ totalRecords: number,
251
+ trends?: HookTrend[],
252
+ ): string {
253
+ if (totalRecords === 0) {
254
+ return "ashlr hook timings: no records yet (enable hooks and trigger a few tool calls).";
255
+ }
256
+
257
+ const med = medianOverAll(aggregates);
258
+ const header = `ashlr hook timings · last ${windowHours}h · ${fmtNum(totalRecords)} records · median ${fmtMs(med)}`;
259
+
260
+ // Build trend lookup by hook name
261
+ const trendMap = new Map<string, HookTrend>();
262
+ if (trends) {
263
+ for (const t of trends) trendMap.set(t.hook, t);
264
+ }
265
+
266
+ // Column widths (must fit 80 cols total)
267
+ // With trends: hook(20) calls(7) p50(7) p95(7) trend(3) max(8) error%(8) block%(7) = 67 + spaces
268
+ // Without: hook(22) calls(7) p50(7) p95(7) max(8) error%(8) block%(7) = 66 + spaces
269
+ const hasTrends = trendMap.size > 0;
270
+ const COL_HOOK = hasTrends ? 20 : 22;
271
+ const colHead =
272
+ "hook".padEnd(COL_HOOK) +
273
+ "calls".padStart(7) +
274
+ "p50".padStart(7) +
275
+ "p95".padStart(7) +
276
+ (hasTrends ? " " : "") +
277
+ "max".padStart(8) +
278
+ "error%".padStart(8) +
279
+ "block%".padStart(7);
280
+ const rule = "─".repeat(70);
281
+
282
+ const rows = aggregates.map((a) => {
283
+ const hookCol = a.hook.length > COL_HOOK
284
+ ? a.hook.slice(0, COL_HOOK - 1) + "…"
285
+ : a.hook.padEnd(COL_HOOK);
286
+ const hasErrors = a.calls > 0;
287
+ const hasBlocks = a.calls > 0;
288
+ let trendIndicator = "";
289
+ if (hasTrends) {
290
+ const t = trendMap.get(a.hook);
291
+ if (t?.trend === "improved") trendIndicator = " ↓ ";
292
+ else if (t?.trend === "regressed") trendIndicator = " ↑ ";
293
+ else if (t?.trend === "new") trendIndicator = " ★ "; // no baseline to compare against
294
+ else trendIndicator = " ";
295
+ }
296
+ return (
297
+ hookCol +
298
+ fmtNum(a.calls).padStart(7) +
299
+ fmtMs(a.p50).padStart(7) +
300
+ fmtMs(a.p95).padStart(7) +
301
+ trendIndicator +
302
+ fmtMs(a.max).padStart(8) +
303
+ fmtPct(a.errorPct, hasErrors).padStart(8) +
304
+ fmtPct(a.blockPct, hasBlocks).padStart(7)
305
+ );
306
+ });
307
+
308
+ const flags = buildFlags(aggregates);
309
+
310
+ const parts = [header, "", colHead, rule, ...rows];
311
+ if (flags.length > 0) {
312
+ parts.push("", ...flags);
313
+ }
314
+ return parts.join("\n");
315
+ }
316
+
317
+ /**
318
+ * Compact 4-5 line summary for embedding in `/ashlr-dashboard`.
319
+ * Returns empty string when records is empty.
320
+ */
321
+ export function renderCompact(opts: {
322
+ records: HookTimingRecord[];
323
+ topN: number;
324
+ windowHours?: number;
325
+ }): string {
326
+ const { records, topN, windowHours = 24 } = opts;
327
+ if (records.length === 0) return "";
328
+
329
+ const aggregates = computeAggregates(records, windowHours);
330
+ if (aggregates.length === 0) return "";
331
+
332
+ const totalCalls = aggregates.reduce((s, a) => s + a.calls, 0);
333
+ const med = medianOverAll(aggregates);
334
+
335
+ // Top N slowest by p95
336
+ const byP95 = [...aggregates].sort((a, b) => b.p95 - a.p95).slice(0, topN);
337
+
338
+ const lines: string[] = [];
339
+ lines.push(`Hook performance (last ${windowHours}h): ${fmtNum(totalCalls)} calls · median ${fmtMs(med)}`);
340
+ lines.push(`Top ${Math.min(topN, byP95.length)} slowest by p95:`);
341
+ for (const a of byP95) {
342
+ const hookCol = a.hook.length > 24 ? a.hook.slice(0, 23) + "…" : a.hook.padEnd(24);
343
+ lines.push(` ${hookCol} p95 ${fmtMs(a.p95).padStart(6)} (${fmtNum(a.calls)} calls)`);
344
+ }
345
+ return lines.join("\n");
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // Public top-level builder
350
+ // ---------------------------------------------------------------------------
351
+
352
+ export function buildHookTimingsReport(opts: {
353
+ path?: string;
354
+ hours?: number;
355
+ compare?: boolean;
356
+ flagsOnly?: boolean;
357
+ } = {}): string {
358
+ const hours = opts.hours ?? 24;
359
+ const requestedHours = opts.compare ? hours * 2 : hours;
360
+ const detailed = readHookTimingsDetailed({
361
+ path: opts.path,
362
+ sinceMs: Date.now() - requestedHours * 3_600_000,
363
+ });
364
+ const records = detailed.records;
365
+ const aggregates = computeAggregates(records, hours);
366
+ const totalRecords = aggregates.reduce((s, a) => s + a.calls, 0);
367
+
368
+ if (opts.flagsOnly) {
369
+ if (totalRecords === 0) return detailed.coverage === "partial" ? coverageWarning(detailed.quality) : "";
370
+ const flags = buildFlags(aggregates);
371
+ if (detailed.coverage === "partial") flags.unshift(coverageWarning(detailed.quality));
372
+ return flags.join("\n");
373
+ }
374
+
375
+ const trends = opts.compare
376
+ ? computeTrends(records, { windowHours: hours, compareHours: hours })
377
+ : undefined;
378
+
379
+ const report = renderReport(aggregates, hours, totalRecords, trends);
380
+ return detailed.coverage === "partial"
381
+ ? `${coverageWarning(detailed.quality)}\n${report}`
382
+ : report;
383
+ }
384
+
385
+ function coverageWarning(quality: HookTimingReadQuality): string {
386
+ const reasons: string[] = [];
387
+ if (quality.droppedThrough) reasons.push(`history dropped through ${quality.droppedThrough}`);
388
+ if (quality.sources.some((source) => source.raced)) reasons.push("rotation race");
389
+ if (quality.sources.some((source) => source.unreadable)) reasons.push("unreadable source");
390
+ if (quality.sources.some((source) => source.truncatedPrefix)) reasons.push("source over 16 MiB");
391
+ if (quality.sources.some((source) => source.truncatedTail)) reasons.push("torn tail");
392
+ if (quality.sources.some((source) => source.oversizedRows > 0)) reasons.push("oversized row");
393
+ if (quality.sources.some((source) => source.malformedRows > 0)) reasons.push("malformed row");
394
+ if (quality.metadataMalformed || quality.metadataUnreadable) reasons.push("metadata unavailable");
395
+ else if (quality.metadataPresent && quality.droppedThrough === null) reasons.push("drop time unknown");
396
+ const prefix = "Warning: partial hook-timing coverage";
397
+ if (reasons.length === 0) return `${prefix}.`;
398
+ const message = `${prefix} (${reasons.join(", ")}).`;
399
+ return message.length <= 80 ? message : `${message.slice(0, 77)}...`;
400
+ }
401
+
402
+ // ---------------------------------------------------------------------------
403
+ // Doctor integration — surface hook health in /ashlr-doctor
404
+ // ---------------------------------------------------------------------------
405
+
406
+ export interface DoctorHookFinding {
407
+ status: "ok" | "warn" | "fail";
408
+ label: string;
409
+ detail: string;
410
+ fix?: string;
411
+ }
412
+
413
+ export interface DoctorHookHealthOpts {
414
+ /** Path override for tests. Default: ~/.ashlr/hook-timings.jsonl */
415
+ path?: string;
416
+ /** Window in hours. Default 24. */
417
+ hours?: number;
418
+ /** p95 (ms) above which a hook is yellow-flagged. Default 50. */
419
+ warnP95Ms?: number;
420
+ /** p95 (ms) above which a hook is red-flagged. Default 200. */
421
+ failP95Ms?: number;
422
+ }
423
+
424
+ /**
425
+ * Build the per-hook health lines used by /ashlr-doctor's "hook performance"
426
+ * section. Honors the v1.29 outcome enum (including "timeout") so a hook
427
+ * that's hitting the 2s safety net surfaces here rather than silently dying.
428
+ *
429
+ * Ranking rules:
430
+ * - timeoutPct > 0 → fail (a hook that hangs is the worst outcome)
431
+ * - errorPct > 5 → fail
432
+ * - p95 ≥ failP95Ms → fail
433
+ * - p95 ≥ warnP95Ms → warn
434
+ * - otherwise → ok (but only top-3 ok rows are returned to keep doctor tight)
435
+ */
436
+ export function getDoctorHookHealth(opts: DoctorHookHealthOpts = {}): {
437
+ findings: DoctorHookFinding[];
438
+ totalCalls: number;
439
+ coverage: "complete" | "partial";
440
+ quality: HookTimingReadQuality;
441
+ } {
442
+ const hours = opts.hours ?? 24;
443
+ const warnMs = opts.warnP95Ms ?? 50;
444
+ const failMs = opts.failP95Ms ?? 200;
445
+ const detailed = readHookTimingsDetailed({
446
+ path: opts.path,
447
+ sinceMs: Date.now() - hours * 3_600_000,
448
+ });
449
+ const records = detailed.records;
450
+ const aggregates = computeAggregates(records, hours);
451
+ const totalCalls = aggregates.reduce((s, a) => s + a.calls, 0);
452
+
453
+ if (totalCalls === 0) {
454
+ const findings = detailed.coverage === "partial" ? [{
455
+ status: "warn" as const,
456
+ label: "hook timing coverage",
457
+ detail: coverageWarning(detailed.quality),
458
+ }] : [];
459
+ return { findings, totalCalls: 0, coverage: detailed.coverage, quality: detailed.quality };
460
+ }
461
+
462
+ const findings: DoctorHookFinding[] = [];
463
+ const okRows: DoctorHookFinding[] = [];
464
+
465
+ // Sort by p95 desc so the worst offenders come first.
466
+ const byP95 = [...aggregates].sort((a, b) => b.p95 - a.p95);
467
+
468
+ for (const a of byP95) {
469
+ const pieces = [
470
+ `${a.calls} call${a.calls === 1 ? "" : "s"}`,
471
+ `p50 ${a.p50}ms`,
472
+ `p95 ${a.p95}ms`,
473
+ ];
474
+ if (a.timeoutPct > 0) pieces.push(`timeout ${a.timeoutPct.toFixed(1)}%`);
475
+ if (a.errorPct > 0) pieces.push(`error ${a.errorPct.toFixed(1)}%`);
476
+
477
+ if (a.timeoutPct > 0) {
478
+ findings.push({
479
+ status: "fail",
480
+ label: a.hook,
481
+ detail: pieces.join(" · "),
482
+ fix: `hook hit the 2s safety net — set ASHLR_HOOK_TIMEOUT_MS to a higher value if expected, otherwise investigate ~/.ashlr/hook-errors.jsonl`,
483
+ });
484
+ } else if (a.errorPct > 5) {
485
+ findings.push({
486
+ status: "fail",
487
+ label: a.hook,
488
+ detail: pieces.join(" · "),
489
+ fix: `tail ~/.ashlr/hook-errors.jsonl | grep ${a.hook}`,
490
+ });
491
+ } else if (a.p95 >= failMs) {
492
+ findings.push({
493
+ status: "fail",
494
+ label: a.hook,
495
+ detail: pieces.join(" · "),
496
+ fix: `hook is slow at p95 — consider reducing work in /${a.hook}/ entry script`,
497
+ });
498
+ } else if (a.p95 >= warnMs) {
499
+ findings.push({
500
+ status: "warn",
501
+ label: a.hook,
502
+ detail: pieces.join(" · "),
503
+ });
504
+ } else {
505
+ okRows.push({
506
+ status: "ok",
507
+ label: a.hook,
508
+ detail: pieces.join(" · "),
509
+ });
510
+ }
511
+ }
512
+
513
+ // Keep the top-3 healthy rows for context, prefer most-called.
514
+ const okByCalls = [...okRows].sort((a, b) => {
515
+ const aAgg = aggregates.find((x) => x.hook === a.label);
516
+ const bAgg = aggregates.find((x) => x.hook === b.label);
517
+ return (bAgg?.calls ?? 0) - (aAgg?.calls ?? 0);
518
+ });
519
+ findings.push(...okByCalls.slice(0, 3));
520
+
521
+ if (detailed.coverage === "partial") {
522
+ findings.unshift({
523
+ status: "warn",
524
+ label: "hook timing coverage",
525
+ detail: coverageWarning(detailed.quality),
526
+ });
527
+ }
528
+ return { findings, totalCalls, coverage: detailed.coverage, quality: detailed.quality };
529
+ }
530
+
531
+ // ---------------------------------------------------------------------------
532
+ // CLI entry
533
+ // ---------------------------------------------------------------------------
534
+
535
+ if (import.meta.main) {
536
+ let hours = 24;
537
+ const argv = process.argv.slice(2);
538
+ const hi = argv.indexOf("--hours");
539
+ if (hi !== -1 && argv[hi + 1]) {
540
+ const parsed = parseInt(argv[hi + 1]!, 10);
541
+ if (!isNaN(parsed) && parsed > 0) hours = parsed;
542
+ }
543
+ const compare = argv.includes("--compare");
544
+ const flagsOnly = argv.includes("--flags-only");
545
+
546
+ try {
547
+ const out = buildHookTimingsReport({ hours, compare, flagsOnly });
548
+ if (out) process.stdout.write(out + "\n");
549
+ } catch (err) {
550
+ const msg = err instanceof Error ? err.message : String(err);
551
+ process.stdout.write(`hook-timings-report failed: ${msg}\n`);
552
+ }
553
+ process.exit(0);
554
+ }