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,140 @@
1
+ /**
2
+ * _genome-discoveries.ts — surface AI-synthesized discovery sections in
3
+ * ashlr__grep retrieval.
4
+ *
5
+ * Mirrors `_genome-commits.ts` but for `kind: "discovery"` sections produced
6
+ * by `scripts/genome-synthesizer.ts`. Reuses the keyword scorer exported by
7
+ * the commit module so we have a single source of truth for tag/title/summary
8
+ * scoring across all v2 section kinds.
9
+ *
10
+ * Discoveries are ADDITIVE — they extend the corpus, never replace it. The
11
+ * grep handler formats them with a distinct `[discovery <id> - <date>]`
12
+ * prefix and a `## Discoveries` header so users can tell synthesis insights
13
+ * from static code and from raw commit history.
14
+ */
15
+
16
+ import { readFile } from "fs/promises";
17
+ import { existsSync } from "fs";
18
+ import {
19
+ type DiscoverySection,
20
+ type SectionMetaV2,
21
+ discoverySectionAbsPath,
22
+ loadManifestV2,
23
+ } from "./_manifest-v2";
24
+ import { scoreSectionMeta, tokenize } from "./_genome-commits";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Public API
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface RetrievedDiscovery {
31
+ id: string;
32
+ shortId: string;
33
+ synthesizedAt: string;
34
+ summary: string;
35
+ evidence: DiscoverySection["evidence"];
36
+ sourceCommits: string[];
37
+ confidence: number;
38
+ score: number;
39
+ }
40
+
41
+ /**
42
+ * Retrieve up to `limit` discovery sections matching `query`.
43
+ *
44
+ * Returns an empty array when:
45
+ * - no genome exists,
46
+ * - the manifest has no discovery sections,
47
+ * - no discovery section's keywords overlap the query.
48
+ *
49
+ * Never throws — best-effort retrieval, callers should fall through to
50
+ * static + commit results when this returns [].
51
+ */
52
+ export async function retrieveDiscoverySections(
53
+ cwd: string,
54
+ query: string,
55
+ limit = 3,
56
+ ): Promise<RetrievedDiscovery[]> {
57
+ try {
58
+ const manifest = await loadManifestV2(cwd);
59
+ if (!manifest) return [];
60
+
61
+ const discoveryMetas = manifest.sections.filter((s) => s.kind === "discovery");
62
+ if (discoveryMetas.length === 0) return [];
63
+
64
+ const queryTerms = new Set(tokenize(query));
65
+ let scored: Array<{ meta: SectionMetaV2; score: number }>;
66
+
67
+ if (queryTerms.size === 0) {
68
+ // No meaningful query — surface the most recent discoveries.
69
+ scored = discoveryMetas.map((m) => ({ meta: m, score: 1 }));
70
+ } else {
71
+ scored = discoveryMetas
72
+ .map((m) => ({ meta: m, score: scoreSectionMeta(m, queryTerms) }))
73
+ .filter((s) => s.score > 0);
74
+ }
75
+
76
+ if (scored.length === 0) return [];
77
+
78
+ scored.sort((a, b) => {
79
+ if (b.score !== a.score) return b.score - a.score;
80
+ const ad = a.meta.lastUpdatedAt ?? a.meta.updatedAt;
81
+ const bd = b.meta.lastUpdatedAt ?? b.meta.updatedAt;
82
+ return bd > ad ? 1 : bd < ad ? -1 : 0;
83
+ });
84
+
85
+ const out: RetrievedDiscovery[] = [];
86
+ for (const { meta, score } of scored.slice(0, limit)) {
87
+ const id = meta.path.replace(/^discoveries\//, "").replace(/\.json$/, "");
88
+ const abs = discoverySectionAbsPath(cwd, id);
89
+ if (!existsSync(abs)) continue;
90
+ let payload: DiscoverySection;
91
+ try {
92
+ payload = JSON.parse(await readFile(abs, "utf-8")) as DiscoverySection;
93
+ } catch {
94
+ continue;
95
+ }
96
+ out.push({
97
+ id: payload.id,
98
+ shortId: payload.id.slice(0, 7),
99
+ synthesizedAt: payload.synthesizedAt,
100
+ summary: payload.summary,
101
+ evidence: payload.evidence,
102
+ sourceCommits: payload.sourceCommits,
103
+ confidence: payload.confidence,
104
+ score,
105
+ });
106
+ }
107
+ return out;
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Format a list of retrieved discovery sections as a markdown block suitable
115
+ * for prepending to grep output. Each discovery is clearly labeled with
116
+ * `[discovery <id> - <date>]` so users see it's AI-synthesized, not static
117
+ * documentation or raw commit history.
118
+ */
119
+ export function formatDiscoveriesForPrompt(discoveries: RetrievedDiscovery[]): string {
120
+ if (discoveries.length === 0) return "";
121
+ const parts = discoveries.map((d) => {
122
+ const dateShort = d.synthesizedAt ? d.synthesizedAt.slice(0, 10) : "unknown";
123
+ const evidence =
124
+ d.evidence.length === 0
125
+ ? ""
126
+ : `\n_evidence: ${d.evidence
127
+ .slice(0, 6)
128
+ .map((e) => (e.lineRange ? `${e.path}:${e.lineRange[0]}-${e.lineRange[1]}` : e.path))
129
+ .join(", ")}${d.evidence.length > 6 ? ` (+${d.evidence.length - 6})` : ""}_\n`;
130
+ const commits =
131
+ d.sourceCommits.length === 0
132
+ ? ""
133
+ : `_from commits: ${d.sourceCommits.map((s) => s.slice(0, 7)).join(", ")}_\n`;
134
+ const conf = `_confidence: ${d.confidence.toFixed(2)}_\n`;
135
+ return `### [discovery ${d.shortId} - ${dateShort}]
136
+ ${d.summary}
137
+ ${evidence}${commits}${conf}`;
138
+ });
139
+ return `## Discoveries\n\n${parts.join("\n\n---\n\n")}`;
140
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * _genome-embed-populator.ts — Populate the embedding cache from genome sections.
3
+ *
4
+ * Walks the genome manifest on first grep per session and upserts one
5
+ * embedding per section into the shared ContextDb. The cache is then
6
+ * consulted on every `ashlr__grep` call (see grep-server.ts:236–293) to
7
+ * either prepend a high-similarity match or fall through to the genome
8
+ * retrieval / ripgrep path.
9
+ *
10
+ * Historical note: a prior version of this module shipped empty — the cache
11
+ * was a placebo with no rows. The populator now seeds it on every grep and
12
+ * grep-server consumes the rows; the marketing claim ("genome-aware
13
+ * retrieval saves 50–90% on relevant queries") is now load-bearing. Kill
14
+ * the entire pipeline with `ASHLR_GENOME_RETRIEVAL=off` to fall back to
15
+ * bare ripgrep for diagnostics.
16
+ *
17
+ * Cost model:
18
+ * - First-call-after-manifest-change: O(sections) reads + embeds. For a
19
+ * typical genome (~15 sections, each < 4KB) this is a few ms in BM25 mode.
20
+ * - Steady state: O(1) watermark check, no disk reads.
21
+ *
22
+ * Watermark:
23
+ * Per-project-hash manifest-mtime stored at ~/.ashlr/embed-watermark.json.
24
+ * Re-population is triggered ONLY when the manifest mtime advances.
25
+ * Section-level deltas re-embed any section whose section-text hash changed
26
+ * since the last run.
27
+ *
28
+ * Non-goals:
29
+ * - Cross-session invalidation via hashes of source files (handled by the
30
+ * post-tool-use-embedding hook + embed-file-worker for code files).
31
+ * - AST chunking of markdown genome sections — chunks are a code concern.
32
+ */
33
+
34
+ import { existsSync, readFileSync, mkdirSync, renameSync, writeFileSync, statSync } from "fs";
35
+ import { createHash } from "crypto";
36
+ import { homedir } from "os";
37
+ import { dirname, join } from "path";
38
+
39
+ import { loadManifest, sectionPath } from "@ashlr/core-efficiency";
40
+ import type { ContextDb } from "./_embedding-cache";
41
+ import { embed, upsertCorpus } from "./_embedding-model";
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Watermark I/O
45
+ // ---------------------------------------------------------------------------
46
+
47
+ interface ProjectWatermark {
48
+ manifestMtime: number;
49
+ sections: Record<string, string>; // section path → content hash
50
+ }
51
+
52
+ interface WatermarkFile {
53
+ version: 1;
54
+ projects: Record<string, ProjectWatermark>;
55
+ }
56
+
57
+ function watermarkPath(home?: string): string {
58
+ return join(home ?? homedir(), ".ashlr", "embed-watermark.json");
59
+ }
60
+
61
+ function readWatermark(home?: string): WatermarkFile {
62
+ const path = watermarkPath(home);
63
+ if (!existsSync(path)) return { version: 1, projects: {} };
64
+ try {
65
+ const raw = readFileSync(path, "utf-8");
66
+ const parsed = JSON.parse(raw) as WatermarkFile;
67
+ if (parsed.version !== 1 || !parsed.projects) {
68
+ return { version: 1, projects: {} };
69
+ }
70
+ return parsed;
71
+ } catch {
72
+ return { version: 1, projects: {} };
73
+ }
74
+ }
75
+
76
+ function writeWatermark(wm: WatermarkFile, home?: string): void {
77
+ const path = watermarkPath(home);
78
+ const dir = dirname(path);
79
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
80
+ const tmp = `${path}.${process.pid}.tmp`;
81
+ writeFileSync(tmp, JSON.stringify(wm), "utf-8");
82
+ renameSync(tmp, path);
83
+ }
84
+
85
+ function manifestMtime(genomeRoot: string): number {
86
+ try {
87
+ return statSync(join(genomeRoot, ".ashlrcode", "genome", "manifest.json")).mtimeMs;
88
+ } catch {
89
+ return 0;
90
+ }
91
+ }
92
+
93
+ function sha256(s: string): string {
94
+ return createHash("sha256").update(s).digest("hex").slice(0, 16);
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // In-process guard: avoid concurrent populate runs for the same project.
99
+ // ---------------------------------------------------------------------------
100
+
101
+ const inflight = new Map<string, Promise<PopulateStats>>();
102
+
103
+ export interface PopulateStats {
104
+ /** Sections embedded (inserted or refreshed). */
105
+ embedded: number;
106
+ /** Sections that were up-to-date and skipped. */
107
+ skipped: number;
108
+ /** True when the manifest mtime was unchanged — the entire run is a no-op. */
109
+ unchanged: boolean;
110
+ }
111
+
112
+ export interface PopulateOptions {
113
+ /** ContextDb instance (injected so tests can use a fresh tmp home). */
114
+ ctxDb: ContextDb;
115
+ /** Override `~` so tests never touch the real watermark file. */
116
+ home?: string;
117
+ /** Already-computed stable 8-char project hash. */
118
+ projectHash: string;
119
+ }
120
+
121
+ /**
122
+ * Populate the embedding cache with every section in the genome manifest.
123
+ * Cheap + idempotent: the watermark ensures a no-op when nothing changed.
124
+ */
125
+ export async function populateGenomeEmbeddings(
126
+ genomeRoot: string,
127
+ opts: PopulateOptions,
128
+ ): Promise<PopulateStats> {
129
+ const key = `${opts.projectHash}\x00${genomeRoot}`;
130
+ const running = inflight.get(key);
131
+ if (running) return running;
132
+
133
+ const run = populateInner(genomeRoot, opts).finally(() => {
134
+ if (inflight.get(key)) inflight.delete(key);
135
+ });
136
+ inflight.set(key, run);
137
+ return run;
138
+ }
139
+
140
+ async function populateInner(
141
+ genomeRoot: string,
142
+ { ctxDb, home, projectHash }: PopulateOptions,
143
+ ): Promise<PopulateStats> {
144
+ const mtime = manifestMtime(genomeRoot);
145
+ if (mtime === 0) return { embedded: 0, skipped: 0, unchanged: true };
146
+
147
+ const wm = readWatermark(home);
148
+ const prev: ProjectWatermark = wm.projects[projectHash] ?? {
149
+ manifestMtime: 0,
150
+ sections: {},
151
+ };
152
+
153
+ if (prev.manifestMtime === mtime) {
154
+ return { embedded: 0, skipped: 0, unchanged: true };
155
+ }
156
+
157
+ const manifest = await loadManifest(genomeRoot);
158
+ if (!manifest || manifest.sections.length === 0) {
159
+ // Manifest is unreadable or empty — advance the watermark so we don't
160
+ // re-read every call, but don't record a bogus section map.
161
+ wm.projects[projectHash] = { manifestMtime: mtime, sections: {} };
162
+ try { writeWatermark(wm, home); } catch { /* best-effort */ }
163
+ return { embedded: 0, skipped: 0, unchanged: false };
164
+ }
165
+
166
+ let embedded = 0;
167
+ let skipped = 0;
168
+ const nextSections: Record<string, string> = {};
169
+
170
+ for (const section of manifest.sections) {
171
+ let content: string;
172
+ try {
173
+ const full = sectionPath(genomeRoot, section.path);
174
+ content = readFileSync(full, "utf-8");
175
+ } catch {
176
+ continue; // missing or invalid path — skip this section
177
+ }
178
+
179
+ const hash = sha256(content);
180
+ nextSections[section.path] = hash;
181
+
182
+ if (prev.sections[section.path] === hash) {
183
+ skipped++;
184
+ continue;
185
+ }
186
+
187
+ // Sections are typically < 4KB of markdown; embed the title + summary + body
188
+ // so terms like the filename and tags also contribute to retrieval.
189
+ const text = `${section.title}\n${section.summary}\n${content}`;
190
+ try {
191
+ upsertCorpus(text);
192
+ const vec = await embed(text);
193
+ ctxDb.upsertEmbedding({
194
+ projectHash,
195
+ sectionPath: `genome:${section.path}`,
196
+ sectionText: content.slice(0, 2000),
197
+ embedding: vec,
198
+ embeddingDim: vec.length,
199
+ source: "genome",
200
+ });
201
+ embedded++;
202
+ } catch {
203
+ // Never let one bad section abort the whole populate pass.
204
+ }
205
+ }
206
+
207
+ wm.projects[projectHash] = { manifestMtime: mtime, sections: nextSections };
208
+ try { writeWatermark(wm, home); } catch { /* best-effort */ }
209
+
210
+ return { embedded, skipped, unchanged: false };
211
+ }
212
+
213
+ /** Test helper — drop the in-process in-flight map. */
214
+ export function _clearInflightForTests(): void {
215
+ inflight.clear();
216
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * _genome-freshness.ts — render compact freshness badges for retrieved
3
+ * genome sections (Q2 prep, foundational work).
4
+ *
5
+ * The v2 manifest (`_manifest-v2.ts`) carries `lastUpdatedAt` on every
6
+ * section. Surfacing that age in grep output lets the human + the model
7
+ * see at a glance whether the retrieved context is current.
8
+ *
9
+ * Badge format (max ~15 chars):
10
+ * <1h → `[fresh]`
11
+ * <24h → `[fresh: Xh]`
12
+ * <7d → `[fresh: Xd]`
13
+ * <30d → `[stale: Xd]`
14
+ * ≥30d → `[stale: ≥30d]`
15
+ * undef → `""` (legacy v1 sections; render nothing — caller stays safe)
16
+ *
17
+ * Decoration is purely additive: legacy v1 sections still render unchanged
18
+ * because their entry in the freshness map is absent.
19
+ */
20
+
21
+ import { loadManifestV2 } from "./_manifest-v2";
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Pure helper — no I/O, fully tested.
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Format a freshness badge from an ISO timestamp.
29
+ *
30
+ * Returns "" when `lastUpdatedAt` is undefined OR fails to parse — callers
31
+ * can safely splice the result into any string template without guarding.
32
+ *
33
+ * Bucket boundaries are inclusive on the LOWER bound and exclusive on the
34
+ * UPPER bound. e.g. exactly 1h old → `[fresh: 1h]` (>= 1h, < 24h).
35
+ */
36
+ export function formatFreshness(
37
+ lastUpdatedAt: string | undefined,
38
+ now: Date = new Date(),
39
+ ): string {
40
+ if (!lastUpdatedAt) return "";
41
+ const t = Date.parse(lastUpdatedAt);
42
+ if (!Number.isFinite(t)) return "";
43
+
44
+ const ageMs = now.getTime() - t;
45
+ if (ageMs < 0) {
46
+ // Future timestamp — treat as fresh-just-now rather than emitting
47
+ // misleading "stale" text or empty string.
48
+ return "[fresh]";
49
+ }
50
+
51
+ const HOUR = 60 * 60 * 1000;
52
+ const DAY = 24 * HOUR;
53
+
54
+ if (ageMs < HOUR) return "[fresh]";
55
+ if (ageMs < DAY) {
56
+ const hours = Math.floor(ageMs / HOUR);
57
+ return `[fresh: ${hours}h]`;
58
+ }
59
+ if (ageMs < 7 * DAY) {
60
+ const days = Math.floor(ageMs / DAY);
61
+ return `[fresh: ${days}d]`;
62
+ }
63
+ if (ageMs < 30 * DAY) {
64
+ const days = Math.floor(ageMs / DAY);
65
+ return `[stale: ${days}d]`;
66
+ }
67
+ return "[stale: >=30d]";
68
+ }
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Manifest lookup
72
+ // ---------------------------------------------------------------------------
73
+
74
+ /**
75
+ * Build a (sectionPath → lastUpdatedAt) lookup from the v2 manifest at `cwd`.
76
+ *
77
+ * Returns an empty Map when:
78
+ * - no genome exists,
79
+ * - the manifest is unreadable,
80
+ * - sections have no lastUpdatedAt (legacy v1 not yet re-saved).
81
+ *
82
+ * Never throws. Safe to call on every grep retrieval.
83
+ */
84
+ export async function loadSectionFreshnessMap(
85
+ cwd: string,
86
+ ): Promise<Map<string, string>> {
87
+ const out = new Map<string, string>();
88
+ try {
89
+ const manifest = await loadManifestV2(cwd);
90
+ if (!manifest) return out;
91
+ for (const s of manifest.sections) {
92
+ if (s.lastUpdatedAt) out.set(s.path, s.lastUpdatedAt);
93
+ }
94
+ } catch {
95
+ // Best-effort — never break retrieval.
96
+ }
97
+ return out;
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Output decoration
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Inject freshness badges into core-efficiency's formatGenomeForPrompt
106
+ * output. Targets each `### <title> (<path>)` header — if the path is in
107
+ * `freshnessMap` we append ` <badge>`. Headers with no metadata are left
108
+ * untouched (legacy v1 safety net).
109
+ *
110
+ * Pure / synchronous. Idempotent — re-running on already-decorated output
111
+ * is a no-op because the regex matches only un-decorated headers.
112
+ */
113
+ export function decorateGenomeOutputWithFreshness(
114
+ formatted: string,
115
+ freshnessMap: Map<string, string>,
116
+ now: Date = new Date(),
117
+ ): string {
118
+ if (formatted.length === 0 || freshnessMap.size === 0) return formatted;
119
+
120
+ // Match: `### <title> (<path>)` at start of a line. Path is greedy-by-
121
+ // necessity (titles can contain parens), so we anchor to the LAST `(...)`
122
+ // on the line by requiring the trailing `)` to be followed by end-of-line
123
+ // or whitespace — works for the core-efficiency formatter's exact output.
124
+ return formatted.replace(
125
+ /^### (.+?) \(([^()\n]+)\)(?= *$)/gm,
126
+ (whole, title: string, path: string) => {
127
+ const ts = freshnessMap.get(path);
128
+ if (!ts) return whole;
129
+ const badge = formatFreshness(ts, now);
130
+ if (!badge) return whole;
131
+ return `### ${title} (${path}) ${badge}`;
132
+ },
133
+ );
134
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * _genome-issues.ts — surface closed-issue sections in ashlr__grep retrieval.
3
+ *
4
+ * Q3 sibling of _genome-prs.ts. Issue sections are written by
5
+ * scripts/genome-cloud-sync.ts when an `issue_closed` cloud delta arrives.
6
+ * The grep handler calls retrieveIssueSections() so `ashlr__grep "auth bug"`
7
+ * returns the recently-closed issues alongside code + PRs.
8
+ *
9
+ * Mirrors the PR retriever exactly — scans sections/issues/ directly rather
10
+ * than going through the v2 manifest (cloud-sync writes files out-of-band
11
+ * from manifest updates). Best-effort, fails-silent.
12
+ */
13
+
14
+ import { readFile, readdir, stat } from "fs/promises";
15
+ import { existsSync } from "fs";
16
+ import { join } from "path";
17
+ import { type IssueSection, issuesDir } from "./_manifest-v2";
18
+ import { scoreSectionMeta, tokenize } from "./_genome-commits";
19
+ import { formatFreshness } from "./_genome-freshness";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Public API
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export interface RetrievedIssue {
26
+ id: string;
27
+ title: string;
28
+ closedAt: string;
29
+ author: string;
30
+ labels: string[];
31
+ summary: string;
32
+ url: string;
33
+ score: number;
34
+ }
35
+
36
+ /**
37
+ * Retrieve up to `limit` issue sections from `sections/issues/` matching `query`.
38
+ *
39
+ * @param cwd genome root (the directory containing `.ashlrcode/`)
40
+ * @param query keyword query — same scorer commit/discovery/PR use
41
+ * @param limit max results (default 3)
42
+ * @param sinceDays optional freshness filter — drop issues closed longer
43
+ * than N days ago. `undefined` disables the filter.
44
+ */
45
+ export async function retrieveIssueSections(
46
+ cwd: string,
47
+ query: string,
48
+ limit = 3,
49
+ sinceDays?: number,
50
+ ): Promise<RetrievedIssue[]> {
51
+ try {
52
+ const dir = issuesDir(cwd);
53
+ if (!existsSync(dir)) return [];
54
+
55
+ let entries: string[];
56
+ try {
57
+ entries = await readdir(dir);
58
+ } catch {
59
+ return [];
60
+ }
61
+ const files = entries.filter((n) => n.endsWith(".json"));
62
+ if (files.length === 0) return [];
63
+
64
+ const cutoff =
65
+ typeof sinceDays === "number" && Number.isFinite(sinceDays) && sinceDays > 0
66
+ ? Date.now() - sinceDays * 24 * 60 * 60 * 1000
67
+ : null;
68
+
69
+ const loaded: Array<{ section: IssueSection; mtimeMs: number }> = [];
70
+ for (const file of files) {
71
+ const abs = join(dir, file);
72
+ try {
73
+ const [raw, st] = await Promise.all([readFile(abs, "utf-8"), stat(abs)]);
74
+ const section = JSON.parse(raw) as IssueSection;
75
+ if (!section || typeof section !== "object") continue;
76
+ loaded.push({ section, mtimeMs: st.mtimeMs });
77
+ } catch {
78
+ // Skip corrupt file.
79
+ }
80
+ }
81
+
82
+ const queryTerms = new Set(tokenize(query));
83
+
84
+ const scored = loaded
85
+ .map(({ section, mtimeMs }) => {
86
+ // Freshness filter — drop issues older than the cutoff.
87
+ if (cutoff !== null) {
88
+ const t = section.closedAt ? Date.parse(section.closedAt) : NaN;
89
+ if (Number.isFinite(t) && t < cutoff) {
90
+ return null;
91
+ }
92
+ }
93
+ const tags = [
94
+ "issue",
95
+ "bug",
96
+ section.id,
97
+ ...(section.author ? [section.author.toLowerCase()] : []),
98
+ ...section.labels.map((l) => l.toLowerCase()),
99
+ ];
100
+ const meta = {
101
+ path: `issues/${section.id}.json`,
102
+ title: section.title,
103
+ summary: section.summary,
104
+ tags,
105
+ tokens: Math.ceil((section.title.length + section.summary.length) / 4),
106
+ updatedAt: section.closedAt || new Date(mtimeMs).toISOString(),
107
+ };
108
+ let score: number;
109
+ if (queryTerms.size === 0) {
110
+ score = 1;
111
+ } else {
112
+ score = scoreSectionMeta(meta, queryTerms);
113
+ }
114
+ return { section, score, mtimeMs };
115
+ })
116
+ .filter(
117
+ (x): x is { section: IssueSection; score: number; mtimeMs: number } =>
118
+ x !== null && x.score > 0,
119
+ );
120
+
121
+ if (scored.length === 0) return [];
122
+
123
+ scored.sort((a, b) => {
124
+ if (b.score !== a.score) return b.score - a.score;
125
+ const ad = a.section.closedAt || new Date(a.mtimeMs).toISOString();
126
+ const bd = b.section.closedAt || new Date(b.mtimeMs).toISOString();
127
+ return bd > ad ? 1 : bd < ad ? -1 : 0;
128
+ });
129
+
130
+ return scored.slice(0, limit).map(({ section, score }) => ({
131
+ id: section.id,
132
+ title: section.title,
133
+ closedAt: section.closedAt,
134
+ author: section.author,
135
+ labels: section.labels,
136
+ summary: section.summary,
137
+ url: section.url,
138
+ score,
139
+ }));
140
+ } catch {
141
+ return [];
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Format a list of retrieved issue sections as a markdown block. Each
147
+ * issue gets `[Issue #<n> - <date>]` so it reads distinctly from PRs +
148
+ * commits + code in the same response.
149
+ */
150
+ export function formatIssuesForPrompt(
151
+ issues: RetrievedIssue[],
152
+ now: Date = new Date(),
153
+ ): string {
154
+ if (issues.length === 0) return "";
155
+ const parts = issues.map((i) => {
156
+ const dateShort = i.closedAt ? i.closedAt.slice(0, 10) : "unknown";
157
+ const badge = formatFreshness(i.closedAt || undefined, now);
158
+ const badgeFragment = badge ? ` · ${badge.slice(1, -1)}` : "";
159
+ const labels =
160
+ i.labels.length === 0
161
+ ? ""
162
+ : `\n_labels: ${i.labels.slice(0, 6).join(", ")}${
163
+ i.labels.length > 6 ? ` (+${i.labels.length - 6})` : ""
164
+ }_\n`;
165
+ const author = i.author ? `_author: ${i.author}_\n` : "";
166
+ const link = i.url ? `_${i.url}_\n` : "";
167
+ return `### [Issue #${i.id} - ${dateShort}${badgeFragment}] ${i.title}${labels}${author}${link}
168
+ ${i.summary}`;
169
+ });
170
+ return `## Issues\n\n${parts.join("\n\n---\n\n")}`;
171
+ }