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,744 @@
1
+ /**
2
+ * orchestrate-run.ts — Track B runner for the orchestration DAG.
3
+ *
4
+ * Walks the TaskGraph in dependency order, spawning a per-node Bun subprocess
5
+ * (stub for the MVP — real Claude orchestration ships in wk 7+), propagating
6
+ * handoff payloads downstream, and emitting per-node telemetry when consent
7
+ * is on.
8
+ *
9
+ * v1 (sequential): nodes ran one-at-a-time in topological order.
10
+ * v2 (Q1'27 wk 4-6, this file): PARALLEL-where-deps-allow scheduling.
11
+ * - Pro tier: maxConcurrency=3 (matches the 3-agent cap).
12
+ * - Team tier: maxConcurrency=10 (matches the 10-agent cap).
13
+ * - Override via RunOptions.maxConcurrency for tests.
14
+ * - maxConcurrency=1 produces sequential behavior identical to v1 (used by
15
+ * the existing __tests__/orchestrate-run.test.ts).
16
+ *
17
+ * Tier gates (top of run, BEFORE any subprocess):
18
+ * - free → ok=false error="free-tier"
19
+ * - pro, nodes.length > 3 → ok=false error="pro-tier-3-agent-cap"
20
+ * - team, nodes.length > 10 → ok=false error="team-tier-10-agent-cap"
21
+ *
22
+ * Tier resolution honors ASHLR_TEST_TIER ("free"|"pro"|"team") for tests; in
23
+ * production it falls back to isProSync. DI seams (_setIsProSyncForTests +
24
+ * _setIsTelemetryEnabledForTests) follow the v1.30 _setDailyHeartbeatFetch
25
+ * pattern in servers/_telemetry.ts.
26
+ *
27
+ * Shape compatibility: the existing Track C CLI (scripts/cli-orchestrate.ts)
28
+ * consumes RunResult fields `ok`, `totalDurationMs`, `totalTokens`, `nodes`,
29
+ * where each node carries `id/ok/durationMs/tokens/error`. We KEEP those for
30
+ * back-compat AND add the spec-required aliases (graphId, startedAt,
31
+ * finishedAt, nodeResults, totalTokensUsed, nodeId) so external callers can
32
+ * use either name. v2 adds `maxConcurrentWindows` — a timeline of running-set
33
+ * transitions for observability tools.
34
+ */
35
+
36
+ import * as fs from "fs";
37
+ import * as os from "os";
38
+ import * as path from "path";
39
+ import { executeNodeLlm } from "../servers/_orchestrate-executor.ts";
40
+ import { isProSync } from "../servers/_pro.ts";
41
+ import { isTelemetryEnabled, recordTelemetryEvent } from "../servers/_telemetry.ts";
42
+ import type { TaskGraph, TaskNode } from "../servers/_task-graph.ts";
43
+ import { emitOrchestrationRunTelemetry } from "./orchestrate-emit-telemetry.ts";
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Public types
47
+ // ---------------------------------------------------------------------------
48
+
49
+ export interface NodeResult {
50
+ /** Legacy field name kept for Track C cli-orchestrate compatibility. */
51
+ id: string;
52
+ /** Spec-required alias of `id`. */
53
+ nodeId: string;
54
+ ok: boolean;
55
+ durationMs: number;
56
+ /** Legacy field name. */
57
+ tokens: number;
58
+ /** Spec-required alias of `tokens`. */
59
+ tokensUsed: number;
60
+ output?: string;
61
+ error?: string;
62
+ /** Q1'27 wk 10-12 — retries after the initial attempt (0..NODE_MAX_ATTEMPTS-1). */
63
+ retryCount?: number;
64
+ /** Q1'27 wk 10-12 — total attempts run (1..NODE_MAX_ATTEMPTS). */
65
+ finalAttempt?: number;
66
+ }
67
+
68
+ /**
69
+ * Snapshot of which nodes were running at a given instant. Recorded only on
70
+ * transitions (a node starts or completes); lets observability tools
71
+ * reconstruct the parallel-execution timeline without polling.
72
+ */
73
+ export interface ConcurrencyWindow {
74
+ /** ISO timestamp of the transition. */
75
+ time: string;
76
+ /** Node IDs currently running at that instant. */
77
+ runningIds: string[];
78
+ }
79
+
80
+ export interface RunResult {
81
+ ok: boolean;
82
+ /** Stable ID of the source graph. */
83
+ graphId: string;
84
+ startedAt: string;
85
+ finishedAt: string;
86
+ /** Legacy total duration field kept for cli-orchestrate. */
87
+ totalDurationMs: number;
88
+ /** Legacy total token field kept for cli-orchestrate. */
89
+ totalTokens: number;
90
+ /** Spec-required alias of totalTokens. */
91
+ totalTokensUsed: number;
92
+ /** Legacy per-node list — cli-orchestrate iterates this. */
93
+ nodes: NodeResult[];
94
+ /** Spec-required alias of nodes. */
95
+ nodeResults: NodeResult[];
96
+ /**
97
+ * Timeline of running-set transitions. Each entry is captured when the
98
+ * running set changes (a node starts OR completes). Empty when no nodes
99
+ * ever ran (e.g., tier-rejected runs).
100
+ */
101
+ maxConcurrentWindows: ConcurrencyWindow[];
102
+ /** Optional reason when the run was rejected at the tier gate. */
103
+ error?: string;
104
+ /**
105
+ * Q1'27 wk 10-12 — count of handoff payloads that exceeded
106
+ * HANDOFF_BUDGET_BYTES (8KB) and were truncated before being passed to a
107
+ * successor node. Always present (0 when nothing truncated).
108
+ */
109
+ handoffsTruncated: number;
110
+ }
111
+
112
+ export interface RunOptions {
113
+ graph: TaskGraph;
114
+ dryRun?: boolean;
115
+ onNodeStart?: (node: TaskNode) => void;
116
+ onNodeComplete?: (node: TaskNode, result: NodeResult) => void;
117
+ cwd?: string;
118
+ /**
119
+ * Override the per-run concurrency cap. Defaults to the tier cap
120
+ * (Pro=3, Team=10). Tests use this to assert wave-by-wave timing.
121
+ * Must be >= 1.
122
+ */
123
+ maxConcurrency?: number;
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Constants
128
+ // ---------------------------------------------------------------------------
129
+
130
+ const PRO_NODE_CAP = 3;
131
+ const TEAM_NODE_CAP = 10;
132
+ const NODE_TIMEOUT_MS = 30_000;
133
+
134
+ // Q1'27 wk 10-12 — retry-with-backoff. A fully-failed node may take up to
135
+ // ~3 * NODE_TIMEOUT_MS + backoff sum (250 + 500 = 750ms) ≈ 90.75s wallclock.
136
+ const NODE_MAX_ATTEMPTS = 3;
137
+ const NODE_BACKOFF_BASE_MS = 250;
138
+
139
+ // Q1'27 wk 10-12 — handoff context budget. Predecessor output concatenated
140
+ // past this cap is truncated with a "[handoff truncated: was N bytes,
141
+ // capped at 8192]" marker so the successor knows the input was clipped.
142
+ const HANDOFF_BUDGET_BYTES = 8192;
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // DI seams — tests swap these via _setIsProSyncForTests / _setIsTelemetry...
146
+ // ---------------------------------------------------------------------------
147
+
148
+ type IsProFn = () => boolean;
149
+ type IsTelemetryFn = () => boolean;
150
+ type SpawnFn = typeof Bun.spawn;
151
+
152
+ let _isProImpl: IsProFn | null = null;
153
+ let _isTelemetryImpl: IsTelemetryFn | null = null;
154
+ let _spawnImpl: SpawnFn | null = null;
155
+
156
+ /** Test seam: override the pro-tier check. Pass null to restore default. */
157
+ export function _setIsProSyncForTests(fn: IsProFn | null): void {
158
+ _isProImpl = fn;
159
+ }
160
+
161
+ /** Test seam: override the telemetry-enabled check. Pass null to restore. */
162
+ export function _setIsTelemetryEnabledForTests(fn: IsTelemetryFn | null): void {
163
+ _isTelemetryImpl = fn;
164
+ }
165
+
166
+ /** Test seam: override Bun.spawn. Pass null to restore. */
167
+ export function _setSpawnForTests(fn: SpawnFn | null): void {
168
+ _spawnImpl = fn;
169
+ }
170
+
171
+ function isProGuarded(): boolean {
172
+ return _isProImpl ? _isProImpl() : isProSync();
173
+ }
174
+
175
+ function isTelemetryGuarded(): boolean {
176
+ return _isTelemetryImpl ? _isTelemetryImpl() : isTelemetryEnabled();
177
+ }
178
+
179
+ function spawnGuarded(...args: Parameters<SpawnFn>): ReturnType<SpawnFn> {
180
+ return (_spawnImpl ?? Bun.spawn)(...args);
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Tier resolution
185
+ // ---------------------------------------------------------------------------
186
+
187
+ export type Tier = "free" | "pro" | "team";
188
+
189
+ function resolveTierForRun(env: NodeJS.ProcessEnv = process.env): Tier {
190
+ const override = env["ASHLR_TEST_TIER"];
191
+ if (override === "free" || override === "pro" || override === "team") return override;
192
+ return isProGuarded() ? "pro" : "free";
193
+ }
194
+
195
+ function tierDefaultConcurrency(tier: Tier): number {
196
+ if (tier === "team") return TEAM_NODE_CAP;
197
+ if (tier === "pro") return PRO_NODE_CAP;
198
+ return 1; // free tier never reaches the scheduler, but be defensive.
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Topological sort — used as a TIEBREAKER for ready nodes so the runner has
203
+ // stable, deterministic ordering between otherwise-parallel-eligible peers.
204
+ // Duplicated here so the runner doesn't import the renderer (separation of
205
+ // concerns).
206
+ // ---------------------------------------------------------------------------
207
+
208
+ function topoSort(nodes: readonly TaskNode[]): TaskNode[] {
209
+ const byId = new Map<string, TaskNode>();
210
+ const indegree = new Map<string, number>();
211
+ const inputOrder = new Map<string, number>();
212
+ nodes.forEach((n, i) => {
213
+ byId.set(n.id, n);
214
+ inputOrder.set(n.id, i);
215
+ });
216
+ const successors = new Map<string, string[]>();
217
+ for (const n of nodes) {
218
+ const deps = (Array.isArray(n.deps) ? n.deps : []).filter((d) => byId.has(d));
219
+ indegree.set(n.id, deps.length);
220
+ for (const d of deps) {
221
+ const list = successors.get(d) ?? [];
222
+ list.push(n.id);
223
+ successors.set(d, list);
224
+ }
225
+ }
226
+ const ready: string[] = [];
227
+ for (const [id, deg] of indegree) if (deg === 0) ready.push(id);
228
+ ready.sort((a, b) => (inputOrder.get(a) ?? 0) - (inputOrder.get(b) ?? 0));
229
+
230
+ const out: TaskNode[] = [];
231
+ const visited = new Set<string>();
232
+ while (ready.length > 0) {
233
+ const id = ready.shift()!;
234
+ if (visited.has(id)) continue;
235
+ visited.add(id);
236
+ const n = byId.get(id);
237
+ if (n) out.push(n);
238
+ for (const s of successors.get(id) ?? []) {
239
+ const next = (indegree.get(s) ?? 0) - 1;
240
+ indegree.set(s, next);
241
+ if (next === 0 && !visited.has(s)) ready.push(s);
242
+ }
243
+ ready.sort((a, b) => (inputOrder.get(a) ?? 0) - (inputOrder.get(b) ?? 0));
244
+ }
245
+ if (out.length < nodes.length) {
246
+ for (const n of nodes) if (!visited.has(n.id)) out.push(n);
247
+ }
248
+ return out;
249
+ }
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // Result helpers
253
+ // ---------------------------------------------------------------------------
254
+
255
+ function tierRejectResult(graphId: string, reason: string): RunResult {
256
+ const now = new Date().toISOString();
257
+ return {
258
+ ok: false,
259
+ graphId,
260
+ startedAt: now,
261
+ finishedAt: now,
262
+ totalDurationMs: 0,
263
+ totalTokens: 0,
264
+ totalTokensUsed: 0,
265
+ nodes: [],
266
+ nodeResults: [],
267
+ maxConcurrentWindows: [],
268
+ error: reason,
269
+ handoffsTruncated: 0,
270
+ };
271
+ }
272
+
273
+ function makeNodeResult(
274
+ node: TaskNode,
275
+ partial: { ok: boolean; durationMs: number; tokens: number; output?: string; error?: string; retryCount?: number; finalAttempt?: number },
276
+ ): NodeResult {
277
+ return {
278
+ id: node.id,
279
+ nodeId: node.id,
280
+ ok: partial.ok,
281
+ durationMs: partial.durationMs,
282
+ tokens: partial.tokens,
283
+ tokensUsed: partial.tokens,
284
+ output: partial.output,
285
+ error: partial.error,
286
+ retryCount: partial.retryCount,
287
+ finalAttempt: partial.finalAttempt,
288
+ };
289
+ }
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // Subprocess execution
293
+ // ---------------------------------------------------------------------------
294
+
295
+ interface SpawnEnv {
296
+ NODE_ID: string;
297
+ NODE_GOAL: string;
298
+ NODE_SCOPE: string;
299
+ HANDOFF_PAYLOAD: string;
300
+ }
301
+
302
+ /**
303
+ * Execute a single node as a Bun subprocess. The MVP stub just echoes a
304
+ * canned message via `bun --eval`; the real implementation in wk 7+ will
305
+ * spawn a Claude Code subagent.
306
+ *
307
+ * Returns the node result. Per-node failures are recorded but never thrown —
308
+ * the caller continues running subsequent nodes.
309
+ */
310
+ async function executeNode(
311
+ node: TaskNode,
312
+ env: SpawnEnv,
313
+ cwd: string | undefined,
314
+ ): Promise<{ ok: boolean; output: string; error?: string; durationMs: number; tokensIn?: number; tokensOut?: number }> {
315
+ // Q1'27 wk 7+ — when ASHLR_ORCHESTRATE_REAL_LLM=1, dispatch to the real
316
+ // Claude executor in servers/_orchestrate-executor.ts (PR #93). The flag
317
+ // is a global kill-switch — the default (unset) path is the byte-identical
318
+ // Bun-subprocess stub below, so existing tests keep passing.
319
+ if (process.env["ASHLR_ORCHESTRATE_REAL_LLM"] === "1") {
320
+ const llm = await executeNodeLlm({
321
+ node,
322
+ handoffPayload: env.HANDOFF_PAYLOAD ?? "",
323
+ cwd: cwd ?? process.cwd(),
324
+ timeoutMs: NODE_TIMEOUT_MS,
325
+ });
326
+ return {
327
+ ok: llm.ok,
328
+ output: llm.output,
329
+ error: llm.error,
330
+ durationMs: llm.durationMs,
331
+ tokensIn: llm.tokensIn,
332
+ tokensOut: llm.tokensOut,
333
+ };
334
+ }
335
+
336
+ const t0 = Date.now();
337
+ try {
338
+ const proc = spawnGuarded(
339
+ [
340
+ "bun",
341
+ "--eval",
342
+ "console.log('STUB-NODE: ' + (process.env.NODE_ID || 'unknown'));",
343
+ ],
344
+ {
345
+ env: { ...process.env, ...env },
346
+ cwd,
347
+ stdout: "pipe",
348
+ stderr: "pipe",
349
+ },
350
+ );
351
+
352
+ // 30s wallclock per-node timeout.
353
+ let timedOut = false;
354
+ const timer = setTimeout(() => {
355
+ timedOut = true;
356
+ try {
357
+ proc.kill();
358
+ } catch {
359
+ /* best-effort */
360
+ }
361
+ }, NODE_TIMEOUT_MS);
362
+
363
+ let stdout = "";
364
+ let stderr = "";
365
+ try {
366
+ const stdoutPromise = proc.stdout instanceof ReadableStream
367
+ ? new Response(proc.stdout).text()
368
+ : Promise.resolve("");
369
+ const stderrPromise = proc.stderr instanceof ReadableStream
370
+ ? new Response(proc.stderr).text()
371
+ : Promise.resolve("");
372
+ stdout = await stdoutPromise;
373
+ stderr = await stderrPromise;
374
+ await proc.exited;
375
+ } finally {
376
+ clearTimeout(timer);
377
+ }
378
+
379
+ const durationMs = Date.now() - t0;
380
+
381
+ if (timedOut) {
382
+ return {
383
+ ok: false,
384
+ output: stdout,
385
+ error: `timeout after ${NODE_TIMEOUT_MS}ms`,
386
+ durationMs,
387
+ };
388
+ }
389
+
390
+ const exitCode = typeof proc.exitCode === "number" ? proc.exitCode : 0;
391
+ if (exitCode !== 0) {
392
+ return {
393
+ ok: false,
394
+ output: stdout,
395
+ error: stderr.trim() || `exit ${exitCode}`,
396
+ durationMs,
397
+ };
398
+ }
399
+
400
+ return { ok: true, output: stdout, durationMs };
401
+ } catch (err) {
402
+ return {
403
+ ok: false,
404
+ output: "",
405
+ error: err instanceof Error ? err.message : String(err),
406
+ durationMs: Date.now() - t0,
407
+ };
408
+ }
409
+ }
410
+
411
+ // ---------------------------------------------------------------------------
412
+ // Run result persistence — best-effort. Writes ~/.ashlr/orchestrations/<id>/
413
+ // result.json so /ashlr-orchestrate-status can inspect past runs offline.
414
+ // Failures are swallowed: the runner must never throw or block the return on
415
+ // filesystem I/O. Also prunes to the 50 most-recent run dirs (sort by mtime
416
+ // DESC, remove the rest) to bound disk usage.
417
+ // ---------------------------------------------------------------------------
418
+
419
+ const ORCH_PRUNE_KEEP = 50;
420
+
421
+ /** DI seam for tests so they can redirect away from the real ~/.ashlr/. */
422
+ let _homedirImpl: (() => string) | null = null;
423
+
424
+ /** Test seam: override the HOME directory. Pass null to restore. */
425
+ export function _setHomedirForTests(fn: (() => string) | null): void {
426
+ _homedirImpl = fn;
427
+ }
428
+
429
+ function resolveOrchestrationsDir(): string {
430
+ const home = _homedirImpl ? _homedirImpl() : (os.homedir?.() ?? process.env["HOME"] ?? "~");
431
+ return path.join(home, ".ashlr", "orchestrations");
432
+ }
433
+
434
+ /**
435
+ * Best-effort: write the run + graph to ~/.ashlr/orchestrations/<id>/result.json
436
+ * and prune older run dirs beyond the 50-most-recent. Never throws.
437
+ */
438
+ function persistRunResult(graph: TaskGraph, finalResult: RunResult): void {
439
+ try {
440
+ const base = resolveOrchestrationsDir();
441
+ const dir = path.join(base, graph.id);
442
+ fs.mkdirSync(dir, { recursive: true });
443
+ fs.writeFileSync(
444
+ path.join(dir, "result.json"),
445
+ JSON.stringify({ graph, result: finalResult }, null, 2),
446
+ );
447
+ } catch {
448
+ /* best-effort */
449
+ }
450
+
451
+ try {
452
+ const base = resolveOrchestrationsDir();
453
+ if (!fs.existsSync(base)) return;
454
+ const entries = fs.readdirSync(base, { withFileTypes: true })
455
+ .filter((d) => d.isDirectory())
456
+ .map((d) => {
457
+ const full = path.join(base, d.name);
458
+ let mtime = 0;
459
+ try {
460
+ mtime = fs.statSync(full).mtimeMs;
461
+ } catch {
462
+ /* skip */
463
+ }
464
+ return { name: d.name, full, mtime };
465
+ });
466
+ if (entries.length <= ORCH_PRUNE_KEEP) return;
467
+ entries.sort((a, b) => b.mtime - a.mtime);
468
+ const toRemove = entries.slice(ORCH_PRUNE_KEEP);
469
+ for (const e of toRemove) {
470
+ try {
471
+ fs.rmSync(e.full, { recursive: true, force: true });
472
+ } catch {
473
+ /* best-effort */
474
+ }
475
+ }
476
+ } catch {
477
+ /* best-effort */
478
+ }
479
+ }
480
+
481
+ // ---------------------------------------------------------------------------
482
+ // Public entry — runTaskGraph
483
+ // ---------------------------------------------------------------------------
484
+
485
+ export async function runTaskGraph(opts: RunOptions): Promise<RunResult> {
486
+ const g = opts.graph;
487
+ const startedAt = new Date().toISOString();
488
+ const t0 = Date.now();
489
+
490
+ // -- Tier gate ------------------------------------------------------------
491
+ const tier = resolveTierForRun();
492
+ if (tier === "free") {
493
+ return tierRejectResult(g.id, "free-tier");
494
+ }
495
+ if (tier === "pro" && g.nodes.length > PRO_NODE_CAP) {
496
+ return tierRejectResult(g.id, "pro-tier-3-agent-cap");
497
+ }
498
+ if (tier === "team" && g.nodes.length > TEAM_NODE_CAP) {
499
+ return tierRejectResult(g.id, "team-tier-10-agent-cap");
500
+ }
501
+
502
+ // -- Concurrency selection -------------------------------------------------
503
+ // The override clamps to >=1. Default mirrors the tier's agent cap so the
504
+ // scheduler can saturate every slot the tier paid for.
505
+ const maxConcurrency = Math.max(
506
+ 1,
507
+ Math.floor(opts.maxConcurrency ?? tierDefaultConcurrency(tier)),
508
+ );
509
+
510
+ // -- Scheduler state -------------------------------------------------------
511
+ // `topoOrder` is the tiebreaker for picking among multiple ready nodes —
512
+ // gives stable, deterministic output between parallel-eligible peers.
513
+ const topoOrder = topoSort(g.nodes);
514
+ const topoIndex = new Map<string, number>();
515
+ topoOrder.forEach((n, i) => topoIndex.set(n.id, i));
516
+ const byId = new Map(g.nodes.map((n) => [n.id, n] as const));
517
+
518
+ const results: NodeResult[] = [];
519
+ const resultById = new Map<string, NodeResult>();
520
+ const completed = new Set<string>();
521
+ const running = new Map<string, Promise<{ id: string; node: TaskNode; result: NodeResult }>>();
522
+ const remaining = new Set<string>(g.nodes.map((n) => n.id));
523
+ const windows: ConcurrencyWindow[] = [];
524
+ // Q1'27 wk 10-12 — counts handoff payloads truncated to HANDOFF_BUDGET_BYTES.
525
+ let runHandoffsTruncated = 0;
526
+
527
+ const snapshotWindow = (): void => {
528
+ windows.push({
529
+ time: new Date().toISOString(),
530
+ runningIds: Array.from(running.keys()),
531
+ });
532
+ };
533
+
534
+ const launch = (node: TaskNode): void => {
535
+ opts.onNodeStart?.(node);
536
+ emitTelemetry("orchestrate_node_start", {
537
+ node_id_hash: hashShort(node.id),
538
+ agent_kind: node.agentKind,
539
+ });
540
+
541
+ // Handoff payload: concatenated stdout of all completed predecessors,
542
+ // separated by node-id headers so receivers can disambiguate. Because we
543
+ // only launch nodes whose deps are ALL in `completed`, this is guaranteed
544
+ // to include every predecessor's terminal output (success OR failure).
545
+ const rawHandoff = (node.deps ?? [])
546
+ .map((dep) => resultById.get(dep))
547
+ .filter((r): r is NodeResult => !!r)
548
+ .map((r) => `[from ${r.id}]\n${r.output ?? ""}`)
549
+ .join("\n---\n");
550
+
551
+ // Q1'27 wk 10-12 — enforce HANDOFF_BUDGET_BYTES. Slice + append a marker
552
+ // so the successor knows its context was clipped; bump runHandoffsTruncated.
553
+ let handoff = rawHandoff;
554
+ const origBytes = Buffer.byteLength(rawHandoff, "utf8");
555
+ if (origBytes > HANDOFF_BUDGET_BYTES) {
556
+ handoff =
557
+ rawHandoff.slice(0, HANDOFF_BUDGET_BYTES) +
558
+ `\n\n[handoff truncated: was ${origBytes} bytes, capped at ${HANDOFF_BUDGET_BYTES}]\n`;
559
+ runHandoffsTruncated++;
560
+ }
561
+
562
+ const promise = (async () => {
563
+ let nodeResult: NodeResult;
564
+ if (opts.dryRun) {
565
+ nodeResult = makeNodeResult(node, {
566
+ ok: true,
567
+ durationMs: 0,
568
+ tokens: node.estimatedTokens ?? 0,
569
+ output: "[dry-run]",
570
+ retryCount: 0,
571
+ finalAttempt: 1,
572
+ });
573
+ } else {
574
+ // Q1'27 wk 10-12 — retry-with-backoff. Up to NODE_MAX_ATTEMPTS attempts
575
+ // with exponential backoff (250ms, 500ms). Per-attempt 30s wallclock
576
+ // cap means a fully-failed node can take up to ~90s + 750ms backoff.
577
+ let attempt = 0;
578
+ let exec: Awaited<ReturnType<typeof executeNode>>;
579
+ // eslint-disable-next-line no-constant-condition
580
+ while (true) {
581
+ exec = await executeNode(
582
+ node,
583
+ {
584
+ NODE_ID: node.id,
585
+ NODE_GOAL: node.goal,
586
+ NODE_SCOPE: (node.scope ?? []).join(":"),
587
+ HANDOFF_PAYLOAD: handoff,
588
+ },
589
+ opts.cwd,
590
+ );
591
+ if (exec.ok) break;
592
+ attempt++;
593
+ if (attempt >= NODE_MAX_ATTEMPTS) break;
594
+ await new Promise((r) => setTimeout(r, NODE_BACKOFF_BASE_MS * 2 ** (attempt - 1)));
595
+ }
596
+ // retryCount = retries that ran after the initial attempt.
597
+ // finalAttempt = total attempts run.
598
+ // - Success on initial: attempt=0 → retryCount=0, finalAttempt=1
599
+ // - Success after 2 retries: attempt=2 → retryCount=2, finalAttempt=3
600
+ // - 3 consecutive failures: attempt=3 → retryCount=2, finalAttempt=3
601
+ // (the third attempt was a retry, not a "retry of the retry").
602
+ const finalAttempt = exec.ok ? attempt + 1 : NODE_MAX_ATTEMPTS;
603
+ const retryCount = finalAttempt - 1;
604
+ nodeResult = makeNodeResult(node, {
605
+ ok: exec.ok,
606
+ durationMs: exec.durationMs,
607
+ tokens: exec.ok ? (node.estimatedTokens ?? 0) : 0,
608
+ output: exec.output,
609
+ error: exec.error,
610
+ retryCount,
611
+ finalAttempt,
612
+ });
613
+ }
614
+ return { id: node.id, node, result: nodeResult };
615
+ })();
616
+
617
+ running.set(node.id, promise);
618
+ snapshotWindow(); // transition: a node just started.
619
+ };
620
+
621
+ // -- Main scheduler loop --------------------------------------------------
622
+ // Invariants:
623
+ // - A node is in exactly one of {remaining, running, completed}.
624
+ // - We only `launch` a node whose every dep is in `completed`.
625
+ // - Each iteration starts as many ready nodes as `maxConcurrency` allows,
626
+ // then awaits the first one to finish (Promise.race). On finish, we
627
+ // record + drain, then loop.
628
+ while (remaining.size > 0 || running.size > 0) {
629
+ // Fill capacity with every ready-and-not-running node, preferring topo
630
+ // order for stable output among equivalents.
631
+ while (running.size < maxConcurrency) {
632
+ const ready: TaskNode[] = [];
633
+ for (const id of remaining) {
634
+ const n = byId.get(id);
635
+ if (!n) continue;
636
+ const depsDone = (n.deps ?? []).every((d) => completed.has(d) || !byId.has(d));
637
+ if (depsDone) ready.push(n);
638
+ }
639
+ if (ready.length === 0) break;
640
+ ready.sort((a, b) => (topoIndex.get(a.id) ?? 0) - (topoIndex.get(b.id) ?? 0));
641
+ const next = ready[0]!;
642
+ remaining.delete(next.id);
643
+ launch(next);
644
+ }
645
+
646
+ if (running.size === 0) {
647
+ // Defensive: nothing ready, nothing running — would deadlock. Break to
648
+ // exit cleanly; remaining nodes will be left unrun and `ok` will be
649
+ // false because any unmet deps mean the graph was malformed.
650
+ break;
651
+ }
652
+
653
+ // Wait for the FIRST running node to finish, then record + emit hooks.
654
+ const done = await Promise.race(running.values());
655
+ running.delete(done.id);
656
+ completed.add(done.id);
657
+ results.push(done.result);
658
+ resultById.set(done.id, done.result);
659
+
660
+ opts.onNodeComplete?.(done.node, done.result);
661
+ emitTelemetry("orchestrate_node_complete", {
662
+ node_id_hash: hashShort(done.id),
663
+ ok: done.result.ok,
664
+ duration_ms: done.result.durationMs,
665
+ retry_count: done.result.retryCount ?? 0,
666
+ final_attempt: done.result.finalAttempt ?? 1,
667
+ });
668
+ snapshotWindow(); // transition: a node just completed.
669
+ }
670
+
671
+ // -- Stable result ordering ----------------------------------------------
672
+ // `results` is in COMPLETION order (which depends on subprocess timing).
673
+ // Re-sort by topological order so external consumers see deterministic
674
+ // output regardless of scheduling jitter. cli-orchestrate iterates this.
675
+ results.sort((a, b) => (topoIndex.get(a.id) ?? 0) - (topoIndex.get(b.id) ?? 0));
676
+
677
+ const finishedAt = new Date().toISOString();
678
+ const totalDurationMs = Date.now() - t0;
679
+ const totalTokens = results.reduce((s, r) => s + (r.tokens ?? 0), 0);
680
+ const ok = results.length === g.nodes.length && results.every((r) => r.ok);
681
+
682
+ const finalResult: RunResult = {
683
+ ok,
684
+ graphId: g.id,
685
+ startedAt,
686
+ finishedAt,
687
+ totalDurationMs,
688
+ totalTokens,
689
+ totalTokensUsed: totalTokens,
690
+ nodes: results,
691
+ nodeResults: results,
692
+ maxConcurrentWindows: windows,
693
+ handoffsTruncated: runHandoffsTruncated,
694
+ };
695
+
696
+ // Q1'27 orchestration telemetry — best-effort, fire-and-forget. Gated on
697
+ // consent inside the emit module. Never blocks the return: the fetch is
698
+ // dispatched in the background with a 5s timeout and errors are swallowed.
699
+ // The mode flag reflects which executor path actually ran: 'real-llm' when
700
+ // ASHLR_ORCHESTRATE_REAL_LLM=1 dispatched to executeNodeLlm above, 'stub'
701
+ // for the byte-identical Bun-subprocess default.
702
+ const telemetryMode: "stub" | "real-llm" =
703
+ process.env["ASHLR_ORCHESTRATE_REAL_LLM"] === "1" ? "real-llm" : "stub";
704
+ try {
705
+ await emitOrchestrationRunTelemetry(finalResult, g, telemetryMode);
706
+ } catch {
707
+ /* never propagate */
708
+ }
709
+
710
+ // Persist the run for /ashlr-orchestrate-status — best-effort, never blocks.
711
+ persistRunResult(g, finalResult);
712
+
713
+ return finalResult;
714
+ }
715
+
716
+ // ---------------------------------------------------------------------------
717
+ // Telemetry — only emits when consent is on. We piggy-back on the generic
718
+ // recordTelemetryEvent so the buffer + privacy guards are inherited.
719
+ // ---------------------------------------------------------------------------
720
+
721
+ function emitTelemetry(kind: string, fields: Record<string, unknown>): void {
722
+ try {
723
+ if (!isTelemetryGuarded()) return;
724
+ // We use the `wizard_step` typed kind only to satisfy the type-checker —
725
+ // the underlying recordTelemetryEvent only cares about `kind` + spread.
726
+ // The server tolerates unknown kinds (rolls them up under "other").
727
+ recordTelemetryEvent({
728
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
729
+ kind: kind as any,
730
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
731
+ ...(fields as any),
732
+ } as never);
733
+ } catch {
734
+ /* never propagate */
735
+ }
736
+ }
737
+
738
+ function hashShort(s: string): string {
739
+ let h = 5381;
740
+ for (let i = 0; i < s.length; i++) {
741
+ h = ((h << 5) + h) ^ s.charCodeAt(i);
742
+ }
743
+ return (h >>> 0).toString(16).padStart(8, "0");
744
+ }