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,587 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * genome-cloud-sync — Q2 plugin-side cloud delta sync.
4
+ *
5
+ * Polls GET /genome/cloud-deltas?genome_id=X&since_cursor=N from the Pro
6
+ * backend and writes each returned delta into the local genome:
7
+ *
8
+ * commit → .ashlrcode/genome/sections/commits/<sha>.json
9
+ * pr_merged → .ashlrcode/genome/sections/prs/<number>.json (Q3)
10
+ * issue_closed → .ashlrcode/genome/sections/issues/<number>.json (Q3)
11
+ *
12
+ * Q3 update: PR / issue deltas now write into dedicated retrieval-friendly
13
+ * subdirs (prs/ + issues/) with structured PrSection / IssueSection payloads
14
+ * instead of envelope-wrapped raw deltas in cloud/. The retrievers in
15
+ * _genome-prs.ts + _genome-issues.ts consume those files directly. Older
16
+ * envelope files in cloud/ are left untouched for forward-compat with users
17
+ * mid-upgrade — retrieval only looks at prs/ + issues/.
18
+ *
19
+ * Cursor state lives at `.ashlrcode/genome/_cloud-cursor.json` and is only
20
+ * advanced on a fully successful pull. Network failures leave the cursor
21
+ * untouched so the next session retries.
22
+ *
23
+ * Tier gate (HARDCODED — first line of syncCloudDeltas):
24
+ * free → returns { skipped: true, reason: "free-tier" } immediately
25
+ * pro / team → proceeds
26
+ *
27
+ * Best-effort budget: SessionStart calls this with a 2s hard ceiling. The
28
+ * caller (hooks/session-start.ts) is responsible for the timeout; this
29
+ * function itself just tries the network and writes whatever it can.
30
+ *
31
+ * CLI:
32
+ * bun scripts/genome-cloud-sync.ts # actually sync
33
+ * bun scripts/genome-cloud-sync.ts --dry-run # print plan, write nothing
34
+ * bun scripts/genome-cloud-sync.ts --limit=50 # cap per-request page size
35
+ *
36
+ * Privacy: cloud sections store only what the server returned — title,
37
+ * message, file paths, optional 2-3 sentence summary. No diff content.
38
+ */
39
+
40
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync } from "fs";
41
+ import { homedir } from "os";
42
+ import { dirname, join } from "path";
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Constants
46
+ // ---------------------------------------------------------------------------
47
+
48
+ const API_URL_DEFAULT = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
49
+
50
+ /** Hard cap on cloud sections kept on disk (across all kinds). */
51
+ export const CLOUD_RETENTION_LIMIT = 200;
52
+
53
+ /** Default per-request page size when --limit isn't supplied. */
54
+ export const DEFAULT_LIMIT = 100;
55
+
56
+ /** Subdirectory under .ashlrcode/genome/sections/ for non-commit cloud entries. */
57
+ export const CLOUD_SUBDIR = "cloud";
58
+
59
+ /** Subdirectory for commit deltas — shared with the local commit watcher. */
60
+ export const COMMITS_SUBDIR = "commits";
61
+
62
+ /** Q3 — dedicated subdir for `pr_merged` deltas, consumed by _genome-prs.ts. */
63
+ export const PRS_SUBDIR = "prs";
64
+
65
+ /** Q3 — dedicated subdir for `issue_closed` deltas, consumed by _genome-issues.ts. */
66
+ export const ISSUES_SUBDIR = "issues";
67
+
68
+ /** Q3 — per-subdir cap (independent of CLOUD_RETENTION_LIMIT). */
69
+ export const PR_RETENTION_LIMIT = 100;
70
+ export const ISSUE_RETENTION_LIMIT = 100;
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Types
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export type Tier = "free" | "pro" | "team";
77
+
78
+ export type DeltaKind = "commit" | "pr_merged" | "issue_closed";
79
+
80
+ export interface CloudDelta {
81
+ id: number;
82
+ cursor: number;
83
+ kind: DeltaKind;
84
+ sourceSha: string;
85
+ recordedAt: string;
86
+ payload: unknown;
87
+ }
88
+
89
+ export interface CloudDeltasResponse {
90
+ deltas: CloudDelta[];
91
+ nextCursor: number;
92
+ }
93
+
94
+ export interface CloudCursorFile {
95
+ genomeId: string | null;
96
+ cursor: number;
97
+ lastSyncedAt: string;
98
+ }
99
+
100
+ export interface SyncResult {
101
+ skipped?: true;
102
+ reason?: string;
103
+ written?: number;
104
+ cursorBefore?: number;
105
+ cursorAfter?: number;
106
+ pruned?: number;
107
+ }
108
+
109
+ type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Path helpers
113
+ // ---------------------------------------------------------------------------
114
+
115
+ export function genomeRoot(cwd: string): string {
116
+ return join(cwd, ".ashlrcode", "genome");
117
+ }
118
+
119
+ export function sectionsRoot(cwd: string): string {
120
+ return join(genomeRoot(cwd), "sections");
121
+ }
122
+
123
+ export function commitsDir(cwd: string): string {
124
+ return join(sectionsRoot(cwd), COMMITS_SUBDIR);
125
+ }
126
+
127
+ export function cloudDir(cwd: string): string {
128
+ return join(sectionsRoot(cwd), CLOUD_SUBDIR);
129
+ }
130
+
131
+ export function prsDir(cwd: string): string {
132
+ return join(sectionsRoot(cwd), PRS_SUBDIR);
133
+ }
134
+
135
+ export function issuesDir(cwd: string): string {
136
+ return join(sectionsRoot(cwd), ISSUES_SUBDIR);
137
+ }
138
+
139
+ export function cursorPath(cwd: string): string {
140
+ return join(genomeRoot(cwd), "_cloud-cursor.json");
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Cursor I/O
145
+ // ---------------------------------------------------------------------------
146
+
147
+ export function readCursor(cwd: string): CloudCursorFile | null {
148
+ const p = cursorPath(cwd);
149
+ if (!existsSync(p)) return null;
150
+ try {
151
+ return JSON.parse(readFileSync(p, "utf-8")) as CloudCursorFile;
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ export function writeCursor(cwd: string, c: CloudCursorFile): void {
158
+ const p = cursorPath(cwd);
159
+ mkdirSync(dirname(p), { recursive: true });
160
+ writeFileSync(p, JSON.stringify(c, null, 2), "utf-8");
161
+ }
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Pro token + genome-id discovery
165
+ // ---------------------------------------------------------------------------
166
+
167
+ function readProToken(home: string): string {
168
+ try {
169
+ return readFileSync(join(home, ".ashlr", "pro-token"), "utf-8").trim();
170
+ } catch {
171
+ return "";
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Discover the cloud genome id for the current repo. Reads the marker file
177
+ * written by genome-cloud-pull (`~/.ashlr/genomes/<hash>/.ashlr-cloud-genome`).
178
+ *
179
+ * Returns null when no marker exists — meaning the user hasn't yet completed
180
+ * a full cloud-pull for this repo, so there's no genome to sync deltas for.
181
+ */
182
+ export function findGenomeIdForCwd(home: string, cwd: string): string | null {
183
+ try {
184
+ const { spawnSync } = require("child_process") as typeof import("child_process");
185
+ const res = spawnSync("git", ["remote", "get-url", "origin"], {
186
+ cwd,
187
+ timeout: 2000,
188
+ encoding: "utf-8",
189
+ });
190
+ if (res.status !== 0 || !res.stdout) return null;
191
+ const remote = (res.stdout as string).trim();
192
+ // Canonicalize: lowercase, strip .git, ssh→https. Match server-side rules.
193
+ let url = remote;
194
+ const ssh = url.match(/^git@([^:]+):(.+)$/);
195
+ if (ssh) url = `https://${ssh[1]}/${ssh[2]}`;
196
+ url = url.toLowerCase().replace(/\/+$/, "");
197
+ if (url.endsWith(".git")) url = url.slice(0, -4);
198
+ url = url.replace(/\/+$/, "");
199
+
200
+ const { createHash } = require("crypto") as typeof import("crypto");
201
+ const hash = createHash("sha256").update(url).digest("hex").slice(0, 8);
202
+ const marker = join(home, ".ashlr", "genomes", hash, ".ashlr-cloud-genome");
203
+ if (!existsSync(marker)) return null;
204
+ const parsed = JSON.parse(readFileSync(marker, "utf-8")) as { genomeId?: string };
205
+ return parsed.genomeId ?? null;
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Section writers (one per delta kind)
213
+ // ---------------------------------------------------------------------------
214
+
215
+ function writeCommitSection(cwd: string, delta: CloudDelta): string {
216
+ const dir = commitsDir(cwd);
217
+ mkdirSync(dir, { recursive: true });
218
+ const p = join(dir, `${delta.sourceSha}.json`);
219
+ writeFileSync(p, JSON.stringify(delta.payload, null, 2), "utf-8");
220
+ return p;
221
+ }
222
+
223
+ /**
224
+ * Best-effort GitHub URL builder. The webhook payload doesn't include the
225
+ * canonical URL, so we reconstruct from the cwd's git remote (origin). When
226
+ * we can't read it we still write the section — just with an empty url string.
227
+ */
228
+ function buildGithubUrl(cwd: string, kind: "pull" | "issues", num: number | string): string {
229
+ try {
230
+ const { spawnSync } = require("child_process") as typeof import("child_process");
231
+ const res = spawnSync("git", ["remote", "get-url", "origin"], {
232
+ cwd,
233
+ timeout: 2000,
234
+ encoding: "utf-8",
235
+ });
236
+ if (res.status !== 0 || !res.stdout) return "";
237
+ let url = (res.stdout as string).trim();
238
+ const ssh = url.match(/^git@([^:]+):(.+)$/);
239
+ if (ssh) url = `https://${ssh[1]}/${ssh[2]}`;
240
+ if (url.endsWith(".git")) url = url.slice(0, -4);
241
+ url = url.replace(/\/+$/, "");
242
+ return `${url}/${kind}/${num}`;
243
+ } catch {
244
+ return "";
245
+ }
246
+ }
247
+
248
+ interface PrPayloadShape {
249
+ kind?: string;
250
+ number?: number;
251
+ title?: string;
252
+ mergedSha?: string;
253
+ author?: string;
254
+ mergedAt?: string;
255
+ filesChanged?: string[];
256
+ summary?: string;
257
+ }
258
+
259
+ interface IssuePayloadShape {
260
+ kind?: string;
261
+ number?: number;
262
+ title?: string;
263
+ closedAt?: string;
264
+ author?: string;
265
+ labels?: string[];
266
+ summary?: string;
267
+ }
268
+
269
+ /**
270
+ * Q3: write a structured PR section to sections/prs/<number>.json.
271
+ * Schema matches PrSection in _manifest-v2.ts so retrieval can read it back.
272
+ */
273
+ function writePrSection(cwd: string, delta: CloudDelta): string {
274
+ const dir = prsDir(cwd);
275
+ mkdirSync(dir, { recursive: true });
276
+ const p = (delta.payload ?? {}) as PrPayloadShape;
277
+ const number = p.number ?? Number(delta.sourceSha) ?? 0;
278
+ const id = String(number);
279
+ const section = {
280
+ id,
281
+ title: (p.title ?? "").slice(0, 512),
282
+ mergedAt: p.mergedAt ?? "",
283
+ author: p.author ?? "",
284
+ filesChanged: Array.isArray(p.filesChanged) ? p.filesChanged : [],
285
+ summary: (p.summary ?? "").slice(0, 1024),
286
+ url: buildGithubUrl(cwd, "pull", number),
287
+ };
288
+ const out = join(dir, `${id}.json`);
289
+ writeFileSync(out, JSON.stringify(section, null, 2), "utf-8");
290
+ return out;
291
+ }
292
+
293
+ /**
294
+ * Q3: write a structured Issue section to sections/issues/<number>.json.
295
+ * Schema matches IssueSection in _manifest-v2.ts.
296
+ */
297
+ function writeIssueSection(cwd: string, delta: CloudDelta): string {
298
+ const dir = issuesDir(cwd);
299
+ mkdirSync(dir, { recursive: true });
300
+ const p = (delta.payload ?? {}) as IssuePayloadShape;
301
+ const number = p.number ?? Number(delta.sourceSha) ?? 0;
302
+ const id = String(number);
303
+ const section = {
304
+ id,
305
+ title: (p.title ?? "").slice(0, 512),
306
+ closedAt: p.closedAt ?? "",
307
+ author: p.author ?? "",
308
+ labels: Array.isArray(p.labels) ? p.labels : [],
309
+ summary: (p.summary ?? "").slice(0, 1024),
310
+ url: buildGithubUrl(cwd, "issues", number),
311
+ };
312
+ const out = join(dir, `${id}.json`);
313
+ writeFileSync(out, JSON.stringify(section, null, 2), "utf-8");
314
+ return out;
315
+ }
316
+
317
+ function applyDelta(cwd: string, delta: CloudDelta): string | null {
318
+ if (delta.kind === "commit") return writeCommitSection(cwd, delta);
319
+ if (delta.kind === "pr_merged") return writePrSection(cwd, delta);
320
+ if (delta.kind === "issue_closed") return writeIssueSection(cwd, delta);
321
+ return null;
322
+ }
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // Pruner — keep last CLOUD_RETENTION_LIMIT total cloud sections by mtime
326
+ // ---------------------------------------------------------------------------
327
+
328
+ export function pruneCloudSections(cwd: string, limit = CLOUD_RETENTION_LIMIT): number {
329
+ const dirs = [commitsDir(cwd), cloudDir(cwd)];
330
+ type Entry = { path: string; mtimeMs: number };
331
+ const all: Entry[] = [];
332
+ for (const d of dirs) {
333
+ if (!existsSync(d)) continue;
334
+ for (const name of readdirSync(d)) {
335
+ if (!name.endsWith(".json")) continue;
336
+ const p = join(d, name);
337
+ try {
338
+ const st = statSync(p);
339
+ all.push({ path: p, mtimeMs: st.mtimeMs });
340
+ } catch {
341
+ // ignore
342
+ }
343
+ }
344
+ }
345
+ if (all.length <= limit) return 0;
346
+ all.sort((a, b) => b.mtimeMs - a.mtimeMs); // newest first
347
+ const toDrop = all.slice(limit);
348
+ let dropped = 0;
349
+ for (const ent of toDrop) {
350
+ try {
351
+ unlinkSync(ent.path);
352
+ dropped++;
353
+ } catch {
354
+ // ignore
355
+ }
356
+ }
357
+ return dropped;
358
+ }
359
+
360
+ /**
361
+ * Q3: prune a single retrieval subdir (prs/ or issues/) to the last `limit`
362
+ * files by mtime. Independent of the aggregate CLOUD_RETENTION_LIMIT —
363
+ * PR + issue retrieval expects a deeper history (100 each) so users can
364
+ * still find "auth bug" from 3 weeks ago.
365
+ *
366
+ * Returns number of files dropped. No-op when the dir doesn't exist or
367
+ * holds fewer files than `limit`.
368
+ */
369
+ export function pruneSubdir(cwd: string, subdir: string, limit: number): number {
370
+ const dir = join(sectionsRoot(cwd), subdir);
371
+ if (!existsSync(dir)) return 0;
372
+ type Entry = { path: string; mtimeMs: number };
373
+ const all: Entry[] = [];
374
+ for (const name of readdirSync(dir)) {
375
+ if (!name.endsWith(".json")) continue;
376
+ const p = join(dir, name);
377
+ try {
378
+ const st = statSync(p);
379
+ all.push({ path: p, mtimeMs: st.mtimeMs });
380
+ } catch {
381
+ // ignore
382
+ }
383
+ }
384
+ if (all.length <= limit) return 0;
385
+ all.sort((a, b) => b.mtimeMs - a.mtimeMs);
386
+ const toDrop = all.slice(limit);
387
+ let dropped = 0;
388
+ for (const ent of toDrop) {
389
+ try {
390
+ unlinkSync(ent.path);
391
+ dropped++;
392
+ } catch {
393
+ // ignore
394
+ }
395
+ }
396
+ return dropped;
397
+ }
398
+
399
+ // ---------------------------------------------------------------------------
400
+ // Main sync function
401
+ // ---------------------------------------------------------------------------
402
+
403
+ export interface SyncOpts {
404
+ tier: Tier;
405
+ /** Override for tests / CLI --dry-run. */
406
+ dryRun?: boolean;
407
+ /** Per-request page size (server caps at 500). */
408
+ limit?: number;
409
+ /** Override cwd (default process.cwd()). */
410
+ cwd?: string;
411
+ /** Override $HOME (default homedir()). */
412
+ home?: string;
413
+ /** Override fetch (tests inject a mock). */
414
+ fetchFn?: FetchFn;
415
+ /** Override API base URL. */
416
+ apiUrl?: string;
417
+ /** Explicit genome id (skips marker-file discovery). */
418
+ genomeId?: string;
419
+ /** Explicit pro-token (skips file read). */
420
+ proToken?: string;
421
+ }
422
+
423
+ export async function syncCloudDeltas(opts: SyncOpts): Promise<SyncResult> {
424
+ // ---- Tier gate — FIRST line of real work. ----
425
+ if (opts.tier === "free") {
426
+ return { skipped: true, reason: "free-tier" };
427
+ }
428
+
429
+ // Kill switch — same flag the rest of the cloud pipeline honors.
430
+ if (process.env["ASHLR_CLOUD_GENOME_DISABLE"] === "1") {
431
+ return { skipped: true, reason: "kill-switch" };
432
+ }
433
+
434
+ const cwd = opts.cwd ?? process.cwd();
435
+ const home = opts.home ?? homedir();
436
+ const doFetch: FetchFn = opts.fetchFn ?? fetch;
437
+ const apiUrl = opts.apiUrl ?? API_URL_DEFAULT;
438
+ const limit = Math.max(1, Math.min(500, opts.limit ?? DEFAULT_LIMIT));
439
+
440
+ // No genome → nothing to sync. This is the steady state for users who
441
+ // haven't initialised a local genome yet, so it must be a quiet no-op.
442
+ if (!existsSync(genomeRoot(cwd))) {
443
+ return { skipped: true, reason: "no-local-genome" };
444
+ }
445
+
446
+ const proToken = opts.proToken ?? readProToken(home);
447
+ if (!proToken) return { skipped: true, reason: "no-pro-token" };
448
+
449
+ const genomeId = opts.genomeId ?? findGenomeIdForCwd(home, cwd);
450
+ if (!genomeId) return { skipped: true, reason: "no-cloud-genome" };
451
+
452
+ const cursorFile = readCursor(cwd);
453
+ const cursorBefore =
454
+ cursorFile && cursorFile.genomeId === genomeId ? cursorFile.cursor : 0;
455
+
456
+ // ---- Network ----
457
+ let res: Response;
458
+ try {
459
+ res = await doFetch(
460
+ `${apiUrl}/genome/cloud-deltas?genome_id=${encodeURIComponent(genomeId)}&since_cursor=${cursorBefore}&limit=${limit}`,
461
+ {
462
+ headers: {
463
+ Authorization: `Bearer ${proToken}`,
464
+ Accept: "application/json",
465
+ },
466
+ },
467
+ );
468
+ } catch {
469
+ // Network failure: leave the cursor untouched. Next session retries.
470
+ return { skipped: true, reason: "network-error", cursorBefore };
471
+ }
472
+ if (!res.ok) {
473
+ return {
474
+ skipped: true,
475
+ reason: `http-${res.status}`,
476
+ cursorBefore,
477
+ };
478
+ }
479
+
480
+ let body: CloudDeltasResponse;
481
+ try {
482
+ body = (await res.json()) as CloudDeltasResponse;
483
+ } catch {
484
+ return { skipped: true, reason: "bad-json", cursorBefore };
485
+ }
486
+
487
+ if (!Array.isArray(body.deltas) || body.deltas.length === 0) {
488
+ // Empty page — still advance cursor if server bumped nextCursor.
489
+ const nextCursor = Number.isFinite(body.nextCursor) ? body.nextCursor : cursorBefore;
490
+ if (!opts.dryRun && nextCursor > cursorBefore) {
491
+ writeCursor(cwd, {
492
+ genomeId,
493
+ cursor: nextCursor,
494
+ lastSyncedAt: new Date().toISOString(),
495
+ });
496
+ }
497
+ return { written: 0, cursorBefore, cursorAfter: nextCursor, pruned: 0 };
498
+ }
499
+
500
+ // ---- Apply ----
501
+ let written = 0;
502
+ if (!opts.dryRun) {
503
+ for (const delta of body.deltas) {
504
+ try {
505
+ if (applyDelta(cwd, delta)) written++;
506
+ } catch {
507
+ // Individual section write failure shouldn't tank the batch.
508
+ }
509
+ }
510
+ } else {
511
+ written = body.deltas.length; // dry-run reports "would-write" count
512
+ }
513
+
514
+ // Cursor advance — use the server's nextCursor, falling back to the last
515
+ // applied delta's cursor.
516
+ const lastCursor = body.deltas[body.deltas.length - 1]!.cursor;
517
+ const cursorAfter = Number.isFinite(body.nextCursor)
518
+ ? Math.max(body.nextCursor, lastCursor)
519
+ : lastCursor;
520
+
521
+ let pruned = 0;
522
+ if (!opts.dryRun) {
523
+ writeCursor(cwd, {
524
+ genomeId,
525
+ cursor: cursorAfter,
526
+ lastSyncedAt: new Date().toISOString(),
527
+ });
528
+ pruned = pruneCloudSections(cwd);
529
+ // Q3: keep the per-subdir caps independent from the aggregate so PR +
530
+ // issue history remains deep enough to be useful for retrieval.
531
+ pruned += pruneSubdir(cwd, PRS_SUBDIR, PR_RETENTION_LIMIT);
532
+ pruned += pruneSubdir(cwd, ISSUES_SUBDIR, ISSUE_RETENTION_LIMIT);
533
+ }
534
+
535
+ return { written, cursorBefore, cursorAfter, pruned };
536
+ }
537
+
538
+ // ---------------------------------------------------------------------------
539
+ // CLI
540
+ // ---------------------------------------------------------------------------
541
+
542
+ function parseCliArgs(argv: string[]): { dryRun: boolean; limit?: number } {
543
+ let dryRun = false;
544
+ let limit: number | undefined;
545
+ for (const a of argv) {
546
+ if (a === "--dry-run") dryRun = true;
547
+ else if (a.startsWith("--limit=")) {
548
+ const n = Number.parseInt(a.slice("--limit=".length), 10);
549
+ if (Number.isFinite(n) && n > 0) limit = n;
550
+ }
551
+ }
552
+ return { dryRun, limit };
553
+ }
554
+
555
+ async function detectTier(): Promise<Tier> {
556
+ // Mirror scripts/check-tier.ts but inline so this script has zero relative
557
+ // imports (it can run from anywhere and be vendored into the hook tree).
558
+ try {
559
+ const cachePath = join(homedir(), ".ashlr", "pro-token-cache.json");
560
+ if (!existsSync(cachePath)) return "free";
561
+ const c = JSON.parse(readFileSync(cachePath, "utf-8")) as {
562
+ valid?: boolean;
563
+ validatedAt?: string;
564
+ plan?: string;
565
+ };
566
+ if (c.valid !== true || !c.validatedAt) return "free";
567
+ const age = Date.now() - new Date(c.validatedAt).getTime();
568
+ if (age >= 7 * 24 * 60 * 60 * 1000) return "free";
569
+ if (c.plan === "team") return "team";
570
+ if (c.plan === "pro") return "pro";
571
+ return "free";
572
+ } catch {
573
+ return "free";
574
+ }
575
+ }
576
+
577
+ if (import.meta.main) {
578
+ const args = parseCliArgs(process.argv.slice(2));
579
+ const tier = await detectTier();
580
+ const result = await syncCloudDeltas({
581
+ tier,
582
+ dryRun: args.dryRun,
583
+ ...(args.limit !== undefined ? { limit: args.limit } : {}),
584
+ });
585
+ process.stdout.write(JSON.stringify(result) + "\n");
586
+ process.exit(0);
587
+ }