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,578 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * ashlr genome auto-propose — PostToolUse observer that writes structured
4
+ * proposals to `.ashlrcode/genome/proposals.jsonl` when a tool result looks
5
+ * architecturally interesting.
6
+ *
7
+ * Pipeline:
8
+ * 1. Read PostToolUse JSON from stdin.
9
+ * 2. Skip trivial tools; whitelist known-interesting ones.
10
+ * 3. Regex-match architecture/decision signals on the result text.
11
+ * 4. Dedup by SHA-256(first 500 chars) against a persisted Set capped at 10K.
12
+ * 5. Walk up from cwd (or $PROJECT_ROOT) to find `.ashlrcode/genome`.
13
+ * 6. Append a JSONL proposal with the current generation number.
14
+ *
15
+ * Design rules:
16
+ * - Never throw. Any error → silent exit 0 (never block the agent).
17
+ * - Respect ASHLR_GENOME_AUTO=0 and ~/.claude/settings.json + ~/.ashlr/config.json.
18
+ * - At most one proposal per invocation.
19
+ */
20
+
21
+ import { createHash } from "crypto";
22
+ import {
23
+ appendFileSync,
24
+ existsSync,
25
+ mkdirSync,
26
+ readFileSync,
27
+ writeFileSync,
28
+ } from "fs";
29
+ import { homedir } from "os";
30
+ import { dirname, join, resolve } from "path";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // 5A Pre-filter constants
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** Size threshold above which the stdout-dump heuristic activates (50 KB). */
37
+ const STDOUT_DUMP_SIZE_THRESHOLD = 50 * 1024;
38
+
39
+ /** Path segments that indicate generated / vendored / tooling dirs. */
40
+ const FILTERED_PATH_SEGMENTS = [
41
+ "node_modules/",
42
+ "dist/",
43
+ "build/",
44
+ ".next/",
45
+ ".cache/",
46
+ "coverage/",
47
+ ".git/",
48
+ ".ashlrcode/",
49
+ ];
50
+
51
+ /** How many recent proposals to check for content dedup (tail window). */
52
+ const RECENT_DEDUP_WINDOW = 5;
53
+
54
+ /** How many bytes to read from the tail of proposals.jsonl for recent dedup. */
55
+ const RECENT_DEDUP_TAIL_BYTES = 8 * 1024; // 8 KB
56
+
57
+ // Simple counter — incremented each time a proposal is filtered by 5A rules.
58
+ // Exposed via export for observability in tests; not persisted.
59
+ export let stats = { proposalsFiltered: 0 };
60
+
61
+ /** Reset stats — test helper only. */
62
+ export function _resetStats(): void {
63
+ stats = { proposalsFiltered: 0 };
64
+ }
65
+
66
+ const SIGNAL_RE =
67
+ /architecture|decision|ADR|convention|pattern|trade.?off|invariant|contract/i;
68
+
69
+ const WHITELIST = new Set<string>([
70
+ "Read",
71
+ "Grep",
72
+ "Edit",
73
+ "Write",
74
+ "Bash",
75
+ "ashlr__read",
76
+ "ashlr__grep",
77
+ "ashlr__edit",
78
+ ]);
79
+
80
+ const TRIVIAL = new Set<string>(["TodoWrite", "ashlr__savings"]);
81
+
82
+ // Minimum text length to even consider proposing. Bumped from 200 → 400 in
83
+ // v1.13 after the 367-proposals-in-a-day audit showed ~200-char fragments
84
+ // were mostly captured code snippets with no architectural payload. 400
85
+ // roughly corresponds to a paragraph of prose + one code block.
86
+ const MIN_CONTENT_LEN = 400;
87
+ const MAX_SEEN = 10_000;
88
+ const SEEN_PATH = join(homedir(), ".ashlr", "genome-proposals-seen.json");
89
+
90
+ // Minimum manifest section count before the overlap gate kicks in. Below this,
91
+ // a genome is too fresh to have built a meaningful vocabulary and the gate
92
+ // would reject everything.
93
+ const MANIFEST_GATE_MIN_SECTIONS = 3;
94
+
95
+ // Tokens shorter than this aren't useful for overlap detection ("the", "and").
96
+ const MIN_OVERLAP_TOKEN_LEN = 5;
97
+
98
+ // English stopwords to drop from the manifest vocabulary so overlap matches
99
+ // are semantically meaningful. Small list; exact-match lowercase.
100
+ const OVERLAP_STOPWORDS = new Set<string>([
101
+ "about", "after", "again", "against", "because", "before", "being",
102
+ "between", "during", "from", "into", "onto", "other", "over", "some",
103
+ "such", "than", "that", "their", "them", "these", "they", "this",
104
+ "those", "through", "under", "with", "within", "without", "which",
105
+ "while", "would", "could", "should", "shall", "might", "there",
106
+ "here", "when", "where", "what", "how", "why", "your", "yours",
107
+ ]);
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Types
111
+ // ---------------------------------------------------------------------------
112
+
113
+ interface PostToolUsePayload {
114
+ tool_name?: string;
115
+ tool_input?: unknown;
116
+ tool_result?: unknown;
117
+ tool_response?: unknown;
118
+ }
119
+
120
+ interface AshlrConfig {
121
+ genomeAuto?: boolean;
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Helpers
126
+ // ---------------------------------------------------------------------------
127
+
128
+ function isAutoEnabled(): boolean {
129
+ if (process.env.ASHLR_GENOME_AUTO === "0") return false;
130
+ const cfgPath = join(homedir(), ".ashlr", "config.json");
131
+ if (existsSync(cfgPath)) {
132
+ try {
133
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf-8")) as AshlrConfig;
134
+ if (cfg.genomeAuto === false) return false;
135
+ } catch {
136
+ /* ignore */
137
+ }
138
+ }
139
+ return true;
140
+ }
141
+
142
+ function isInterestingTool(name: string | undefined): boolean {
143
+ if (!name) return false;
144
+ if (TRIVIAL.has(name)) return false;
145
+ if (WHITELIST.has(name)) return true;
146
+ // Match mcp__...ashlr__read/grep/edit MCP-prefixed variants.
147
+ if (/ashlr__(read|grep|edit)$/.test(name)) return true;
148
+ return false;
149
+ }
150
+
151
+ function extractText(payload: PostToolUsePayload): string {
152
+ const candidates: unknown[] = [payload.tool_result, payload.tool_response];
153
+ const parts: string[] = [];
154
+ for (const c of candidates) {
155
+ if (!c) continue;
156
+ if (typeof c === "string") {
157
+ parts.push(c);
158
+ } else if (typeof c === "object") {
159
+ try {
160
+ parts.push(JSON.stringify(c));
161
+ } catch {
162
+ /* ignore */
163
+ }
164
+ }
165
+ }
166
+ return parts.join("\n");
167
+ }
168
+
169
+ function findGenomeDir(start: string): string | null {
170
+ let cur = resolve(start);
171
+ // Walk up until filesystem root.
172
+ while (true) {
173
+ const candidate = join(cur, ".ashlrcode", "genome");
174
+ if (existsSync(candidate)) return candidate;
175
+ const parent = dirname(cur);
176
+ if (parent === cur) return null;
177
+ cur = parent;
178
+ }
179
+ }
180
+
181
+ function currentGeneration(genomeDir: string): number {
182
+ const manifestFile = join(genomeDir, "manifest.json");
183
+ if (!existsSync(manifestFile)) return 1;
184
+ try {
185
+ const raw = JSON.parse(readFileSync(manifestFile, "utf-8")) as {
186
+ generation?: { number?: number };
187
+ };
188
+ return raw.generation?.number ?? 1;
189
+ } catch {
190
+ return 1;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Extract a vocabulary of meaningful tokens from a genome manifest —
196
+ * section titles, tags, and summaries. Used as the second-gate filter so
197
+ * auto-proposals only fire when the text overlaps something the project
198
+ * already tracks.
199
+ *
200
+ * Returns a set of lowercased tokens ≥ {@link MIN_OVERLAP_TOKEN_LEN} chars.
201
+ * Returns an empty set if the manifest can't be read or has too few
202
+ * sections to build a useful vocabulary — the caller should then skip the
203
+ * gate so fresh genomes aren't locked out.
204
+ */
205
+ export function buildManifestVocabulary(genomeDir: string): Set<string> {
206
+ const manifestFile = join(genomeDir, "manifest.json");
207
+ if (!existsSync(manifestFile)) return new Set();
208
+ let manifest: {
209
+ sections?: Array<{ title?: string; tags?: string[]; summary?: string }>;
210
+ };
211
+ try {
212
+ manifest = JSON.parse(readFileSync(manifestFile, "utf-8"));
213
+ } catch {
214
+ return new Set();
215
+ }
216
+ const sections = Array.isArray(manifest.sections) ? manifest.sections : [];
217
+ if (sections.length < MANIFEST_GATE_MIN_SECTIONS) return new Set();
218
+
219
+ const vocab = new Set<string>();
220
+ const ingest = (raw: string | undefined): void => {
221
+ if (!raw) return;
222
+ for (const token of raw.toLowerCase().split(/[^a-z0-9_\-]+/)) {
223
+ if (token.length < MIN_OVERLAP_TOKEN_LEN) continue;
224
+ if (OVERLAP_STOPWORDS.has(token)) continue;
225
+ vocab.add(token);
226
+ }
227
+ };
228
+ for (const section of sections) {
229
+ ingest(section.title);
230
+ ingest(section.summary);
231
+ if (Array.isArray(section.tags)) {
232
+ for (const tag of section.tags) ingest(tag);
233
+ }
234
+ }
235
+ return vocab;
236
+ }
237
+
238
+ /** `true` if `text` mentions at least one token in `vocabulary`. */
239
+ export function textOverlapsVocabulary(text: string, vocabulary: Set<string>): boolean {
240
+ if (vocabulary.size === 0) return true; // gate disabled (fresh genome)
241
+ const lower = text.toLowerCase();
242
+ for (const token of vocabulary) {
243
+ if (lower.includes(token)) return true;
244
+ }
245
+ return false;
246
+ }
247
+
248
+ /** Env / config override: `ASHLR_GENOME_REQUIRE_OVERLAP=0` skips the gate. */
249
+ function overlapGateEnabled(): boolean {
250
+ return process.env.ASHLR_GENOME_REQUIRE_OVERLAP !== "0";
251
+ }
252
+
253
+ interface SeenCache {
254
+ /** FIFO list of hashes; oldest at index 0, newest at end. */
255
+ hashes: string[];
256
+ }
257
+
258
+ function loadSeen(): SeenCache {
259
+ if (!existsSync(SEEN_PATH)) return { hashes: [] };
260
+ try {
261
+ const raw = JSON.parse(readFileSync(SEEN_PATH, "utf-8")) as unknown;
262
+ if (raw && typeof raw === "object" && Array.isArray((raw as SeenCache).hashes)) {
263
+ return { hashes: (raw as SeenCache).hashes.filter((h) => typeof h === "string") };
264
+ }
265
+ } catch {
266
+ /* ignore */
267
+ }
268
+ return { hashes: [] };
269
+ }
270
+
271
+ function saveSeen(cache: SeenCache): void {
272
+ try {
273
+ mkdirSync(dirname(SEEN_PATH), { recursive: true });
274
+ writeFileSync(SEEN_PATH, JSON.stringify(cache), "utf-8");
275
+ } catch {
276
+ /* ignore */
277
+ }
278
+ }
279
+
280
+ function sha256(input: string): string {
281
+ return createHash("sha256").update(input).digest("hex");
282
+ }
283
+
284
+ /** ULID-like sortable id. Not a strict ULID — good enough for dedup + ordering. */
285
+ function makeId(): string {
286
+ const ts = Date.now().toString(36).padStart(10, "0");
287
+ const rand = Math.random().toString(36).slice(2, 10).padStart(8, "0");
288
+ return `${ts}${rand}`;
289
+ }
290
+
291
+ function buildSummary(text: string): string {
292
+ const flat = text.replace(/\s+/g, " ").trim();
293
+ if (flat.length <= 400) return flat;
294
+ return flat.slice(0, 400) + "…";
295
+ }
296
+
297
+ function buildRationale(toolName: string, text: string): string {
298
+ const match = text.match(SIGNAL_RE);
299
+ const keyword = match ? match[0] : "signal";
300
+ return `Auto-observed ${keyword} in ${toolName} result; worth preserving for future sessions.`;
301
+ }
302
+
303
+ async function readStdin(): Promise<string> {
304
+ const chunks: Buffer[] = [];
305
+ for await (const chunk of process.stdin as AsyncIterable<Buffer | string>) {
306
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
307
+ }
308
+ return Buffer.concat(chunks).toString("utf-8");
309
+ }
310
+
311
+ // ---------------------------------------------------------------------------
312
+ // Core
313
+ // ---------------------------------------------------------------------------
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // 5A Pre-filter helpers
317
+ // ---------------------------------------------------------------------------
318
+
319
+ /**
320
+ * Heuristic: does this content look like a raw stdout dump?
321
+ *
322
+ * We look for 2+ of the following markers:
323
+ * - Shell prompt prefix `$ `
324
+ * - Exit status tokens `exit 0` / `exit 1`
325
+ * - Claude compact marker `[compact saved`
326
+ * - Duration suffix (e.g. `123ms`)
327
+ * - High density of lines that look like file-system paths (> 30% of sampled lines)
328
+ * - Many lines with only trailing whitespace / empty lines (> 40% of sampled lines)
329
+ */
330
+ export function looksLikeStdoutDump(text: string): boolean {
331
+ let score = 0;
332
+
333
+ if (text.includes("$ ")) score += 1;
334
+ if (/\bexit [01]\b/.test(text)) score += 1;
335
+ if (text.includes("[compact saved")) score += 1;
336
+ if (/\d+ms\b/.test(text)) score += 1;
337
+
338
+ // Sample up to 200 lines for path / blank density checks.
339
+ const lines = text.split("\n").slice(0, 200);
340
+ const pathLike = lines.filter((l) => /^\s*(\/|~\/|\.\/|\.\.\/)/.test(l)).length;
341
+ if (lines.length > 0 && pathLike / lines.length > 0.3) score += 1;
342
+
343
+ const blankOrWhitespace = lines.filter((l) => l.trim() === "").length;
344
+ if (lines.length > 0 && blankOrWhitespace / lines.length > 0.4) score += 1;
345
+
346
+ return score >= 2;
347
+ }
348
+
349
+ /**
350
+ * Returns true if the source path of the proposal should be filtered out
351
+ * (generated dirs, vendored code, genome internals).
352
+ */
353
+ export function isFilteredPath(sourcePath: string | undefined): boolean {
354
+ if (!sourcePath) return false;
355
+ const normalized = sourcePath.replace(/\\/g, "/");
356
+ return FILTERED_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
357
+ }
358
+
359
+ /**
360
+ * Returns true if `contentHash` matches one of the last
361
+ * `RECENT_DEDUP_WINDOW` proposals in `proposalsPath`.
362
+ *
363
+ * Reads at most `RECENT_DEDUP_TAIL_BYTES` from the end of the file so it
364
+ * stays fast even for large proposals.jsonl files.
365
+ */
366
+ export function isRecentDuplicate(contentHash: string, proposalsPath: string): boolean {
367
+ if (!existsSync(proposalsPath)) return false;
368
+ try {
369
+ // Read tail efficiently.
370
+ const { openSync, fstatSync, readSync, closeSync } = require("fs") as typeof import("fs");
371
+ const fd = openSync(proposalsPath, "r");
372
+ const { size } = fstatSync(fd);
373
+ const readStart = Math.max(0, size - RECENT_DEDUP_TAIL_BYTES);
374
+ const buf = Buffer.alloc(Math.min(RECENT_DEDUP_TAIL_BYTES, size));
375
+ const bytesRead = readSync(fd, buf, 0, buf.length, readStart);
376
+ closeSync(fd);
377
+ const tail = buf.slice(0, bytesRead).toString("utf-8");
378
+
379
+ // Parse JSONL lines (last RECENT_DEDUP_WINDOW complete ones).
380
+ const lines = tail.split("\n").filter((l) => l.trim());
381
+ const recent = lines.slice(-RECENT_DEDUP_WINDOW);
382
+ for (const line of recent) {
383
+ try {
384
+ const rec = JSON.parse(line) as { contentHash?: string };
385
+ if (rec.contentHash === contentHash) return true;
386
+ } catch {
387
+ // Ignore malformed lines.
388
+ }
389
+ }
390
+ } catch {
391
+ // On any I/O error, allow the proposal through.
392
+ }
393
+ return false;
394
+ }
395
+
396
+ /**
397
+ * Compute the normalized-content hash used by the recent-dedup window.
398
+ * Normalized = collapse whitespace so minor whitespace differences don't
399
+ * bypass dedup.
400
+ */
401
+ export function normalizedContentHash(content: string): string {
402
+ const normalized = content.replace(/\s+/g, " ").trim();
403
+ return sha256(normalized);
404
+ }
405
+
406
+ export interface ProposeOptions {
407
+ cwd?: string;
408
+ seenPath?: string;
409
+ }
410
+
411
+ export interface ProposeOutcome {
412
+ wrote: boolean;
413
+ reason: string;
414
+ proposalId?: string;
415
+ genomeDir?: string;
416
+ }
417
+
418
+ export function shouldPropose(payload: PostToolUsePayload): {
419
+ ok: boolean;
420
+ reason: string;
421
+ text: string;
422
+ } {
423
+ if (!isAutoEnabled()) return { ok: false, reason: "auto-disabled", text: "" };
424
+ if (!isInterestingTool(payload.tool_name)) {
425
+ return { ok: false, reason: "tool-not-whitelisted", text: "" };
426
+ }
427
+ const text = extractText(payload);
428
+ if (text.length < MIN_CONTENT_LEN) {
429
+ return { ok: false, reason: "content-too-short", text };
430
+ }
431
+ if (!SIGNAL_RE.test(text)) return { ok: false, reason: "no-signal", text };
432
+ return { ok: true, reason: "match", text };
433
+ }
434
+
435
+ export function runPropose(
436
+ payload: PostToolUsePayload,
437
+ opts: ProposeOptions = {},
438
+ ): ProposeOutcome {
439
+ const decision = shouldPropose(payload);
440
+ if (!decision.ok) return { wrote: false, reason: decision.reason };
441
+
442
+ // -------------------------------------------------------------------------
443
+ // 5A Pre-filter gate (runs before genome lookup to fail fast)
444
+ // -------------------------------------------------------------------------
445
+
446
+ // 1. Stdout-dump heuristic: large payloads that look like raw tool output.
447
+ if (decision.text.length > STDOUT_DUMP_SIZE_THRESHOLD && looksLikeStdoutDump(decision.text)) {
448
+ stats.proposalsFiltered += 1;
449
+ process.stderr.write("[ashlr:genome-propose] filtered: stdout-dump heuristic\n");
450
+ return { wrote: false, reason: "filtered-stdout-dump" };
451
+ }
452
+
453
+ // 2. Path filter: source path contains generated/vendored directories.
454
+ const sourcePath =
455
+ typeof (payload.tool_input as Record<string, unknown> | undefined)?.path === "string"
456
+ ? (payload.tool_input as Record<string, string>).path
457
+ : undefined;
458
+ if (isFilteredPath(sourcePath)) {
459
+ stats.proposalsFiltered += 1;
460
+ process.stderr.write("[ashlr:genome-propose] filtered: path in excluded dir\n");
461
+ return { wrote: false, reason: "filtered-path" };
462
+ }
463
+
464
+ const startCwd =
465
+ opts.cwd ?? process.env.PROJECT_ROOT ?? process.cwd();
466
+ const genomeDir = findGenomeDir(startCwd);
467
+ if (!genomeDir) {
468
+ return { wrote: false, reason: "no-genome" };
469
+ }
470
+
471
+ // 3. Recent-dedup window: check last 5 proposals for content match.
472
+ const proposalsPath = join(genomeDir, "proposals.jsonl");
473
+ const contentHash = normalizedContentHash(decision.text);
474
+ if (isRecentDuplicate(contentHash, proposalsPath)) {
475
+ stats.proposalsFiltered += 1;
476
+ process.stderr.write("[ashlr:genome-propose] filtered: recent-dedup window hit\n");
477
+ return { wrote: false, reason: "filtered-recent-dedup" };
478
+ }
479
+
480
+ // -------------------------------------------------------------------------
481
+ // Existing gates (manifest overlap + global dedup seen-set)
482
+ // -------------------------------------------------------------------------
483
+
484
+ // Manifest-overlap gate: require the proposal to mention something the
485
+ // project already tracks. Skipped for fresh genomes (vocab empty when
486
+ // manifest has < MANIFEST_GATE_MIN_SECTIONS) or via env override.
487
+ if (overlapGateEnabled()) {
488
+ const vocab = buildManifestVocabulary(genomeDir);
489
+ if (!textOverlapsVocabulary(decision.text, vocab)) {
490
+ return { wrote: false, reason: "no-manifest-overlap", genomeDir };
491
+ }
492
+ }
493
+
494
+ // Dedup via SHA-256 of first 500 chars.
495
+ const head = decision.text.slice(0, 500);
496
+ const hash = sha256(head);
497
+ const seenPath = opts.seenPath ?? SEEN_PATH;
498
+ const seen = loadSeenFrom(seenPath);
499
+ if (seen.hashes.includes(hash)) {
500
+ return { wrote: false, reason: "dedup" };
501
+ }
502
+ seen.hashes.push(hash);
503
+ while (seen.hashes.length > MAX_SEEN) seen.hashes.shift();
504
+ saveSeenTo(seenPath, seen);
505
+
506
+ const generation = currentGeneration(genomeDir);
507
+ const proposal = {
508
+ id: makeId(),
509
+ agentId: "claude-code",
510
+ section: "knowledge/discoveries.md",
511
+ operation: "append" as const,
512
+ content: buildSummary(decision.text),
513
+ rationale: buildRationale(payload.tool_name ?? "unknown", decision.text),
514
+ timestamp: new Date().toISOString(),
515
+ generation,
516
+ contentHash,
517
+ };
518
+
519
+ try {
520
+ appendFileSync(proposalsPath, JSON.stringify(proposal) + "\n", "utf-8");
521
+ return {
522
+ wrote: true,
523
+ reason: "appended",
524
+ proposalId: proposal.id,
525
+ genomeDir,
526
+ };
527
+ } catch {
528
+ return { wrote: false, reason: "write-failed", genomeDir };
529
+ }
530
+ }
531
+
532
+ function loadSeenFrom(path: string): SeenCache {
533
+ if (path === SEEN_PATH) return loadSeen();
534
+ if (!existsSync(path)) return { hashes: [] };
535
+ try {
536
+ const raw = JSON.parse(readFileSync(path, "utf-8")) as unknown;
537
+ if (raw && typeof raw === "object" && Array.isArray((raw as SeenCache).hashes)) {
538
+ return { hashes: (raw as SeenCache).hashes.filter((h) => typeof h === "string") };
539
+ }
540
+ } catch {
541
+ /* ignore */
542
+ }
543
+ return { hashes: [] };
544
+ }
545
+
546
+ function saveSeenTo(path: string, cache: SeenCache): void {
547
+ try {
548
+ mkdirSync(dirname(path), { recursive: true });
549
+ writeFileSync(path, JSON.stringify(cache), "utf-8");
550
+ } catch {
551
+ /* ignore */
552
+ }
553
+ }
554
+
555
+ // ---------------------------------------------------------------------------
556
+ // CLI entry
557
+ // ---------------------------------------------------------------------------
558
+
559
+ async function main(): Promise<void> {
560
+ let payload: PostToolUsePayload = {};
561
+ try {
562
+ const raw = await readStdin();
563
+ if (raw.trim()) payload = JSON.parse(raw) as PostToolUsePayload;
564
+ } catch {
565
+ // Bad input — exit silently. Never block the agent.
566
+ process.exit(0);
567
+ }
568
+ try {
569
+ runPropose(payload);
570
+ } catch {
571
+ /* swallow */
572
+ }
573
+ process.exit(0);
574
+ }
575
+
576
+ if (import.meta.main) {
577
+ await main();
578
+ }