@ashlr/hub 2.2.0

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 (477) hide show
  1. package/CHANGELOG.md +1158 -0
  2. package/LICENSE +21 -0
  3. package/README.md +833 -0
  4. package/bin/ashlr +8 -0
  5. package/dist/api/core.d.ts +28 -0
  6. package/dist/api/core.js +42 -0
  7. package/dist/api/core.js.map +1 -0
  8. package/dist/api/index.d.ts +9 -0
  9. package/dist/api/index.js +9 -0
  10. package/dist/api/index.js.map +1 -0
  11. package/dist/api/plugin.d.ts +12 -0
  12. package/dist/api/plugin.js +13 -0
  13. package/dist/api/plugin.js.map +1 -0
  14. package/dist/api/types.d.ts +9 -0
  15. package/dist/api/types.js +9 -0
  16. package/dist/api/types.js.map +1 -0
  17. package/dist/cli/args.d.ts +20 -0
  18. package/dist/cli/args.js +23 -0
  19. package/dist/cli/args.js.map +1 -0
  20. package/dist/cli/ask.d.ts +22 -0
  21. package/dist/cli/ask.js +240 -0
  22. package/dist/cli/ask.js.map +1 -0
  23. package/dist/cli/audit.d.ts +26 -0
  24. package/dist/cli/audit.js +199 -0
  25. package/dist/cli/audit.js.map +1 -0
  26. package/dist/cli/backlog.d.ts +26 -0
  27. package/dist/cli/backlog.js +322 -0
  28. package/dist/cli/backlog.js.map +1 -0
  29. package/dist/cli/completions.d.ts +18 -0
  30. package/dist/cli/completions.js +156 -0
  31. package/dist/cli/completions.js.map +1 -0
  32. package/dist/cli/daemon.d.ts +31 -0
  33. package/dist/cli/daemon.js +334 -0
  34. package/dist/cli/daemon.js.map +1 -0
  35. package/dist/cli/demo-sandbox.d.ts +78 -0
  36. package/dist/cli/demo-sandbox.js +227 -0
  37. package/dist/cli/demo-sandbox.js.map +1 -0
  38. package/dist/cli/demo.d.ts +76 -0
  39. package/dist/cli/demo.js +285 -0
  40. package/dist/cli/demo.js.map +1 -0
  41. package/dist/cli/digest.d.ts +46 -0
  42. package/dist/cli/digest.js +352 -0
  43. package/dist/cli/digest.js.map +1 -0
  44. package/dist/cli/doctor-init.d.ts +28 -0
  45. package/dist/cli/doctor-init.js +335 -0
  46. package/dist/cli/doctor-init.js.map +1 -0
  47. package/dist/cli/genome.d.ts +62 -0
  48. package/dist/cli/genome.js +1061 -0
  49. package/dist/cli/genome.js.map +1 -0
  50. package/dist/cli/gh.d.ts +27 -0
  51. package/dist/cli/gh.js +322 -0
  52. package/dist/cli/gh.js.map +1 -0
  53. package/dist/cli/goals.d.ts +55 -0
  54. package/dist/cli/goals.js +667 -0
  55. package/dist/cli/goals.js.map +1 -0
  56. package/dist/cli/health.d.ts +56 -0
  57. package/dist/cli/health.js +407 -0
  58. package/dist/cli/health.js.map +1 -0
  59. package/dist/cli/help.d.ts +51 -0
  60. package/dist/cli/help.js +436 -0
  61. package/dist/cli/help.js.map +1 -0
  62. package/dist/cli/inbox.d.ts +28 -0
  63. package/dist/cli/inbox.js +589 -0
  64. package/dist/cli/inbox.js.map +1 -0
  65. package/dist/cli/index.d.ts +41 -0
  66. package/dist/cli/index.js +1208 -0
  67. package/dist/cli/index.js.map +1 -0
  68. package/dist/cli/knowledge.d.ts +33 -0
  69. package/dist/cli/knowledge.js +575 -0
  70. package/dist/cli/knowledge.js.map +1 -0
  71. package/dist/cli/mcp.d.ts +26 -0
  72. package/dist/cli/mcp.js +487 -0
  73. package/dist/cli/mcp.js.map +1 -0
  74. package/dist/cli/models.d.ts +26 -0
  75. package/dist/cli/models.js +435 -0
  76. package/dist/cli/models.js.map +1 -0
  77. package/dist/cli/new.d.ts +32 -0
  78. package/dist/cli/new.js +565 -0
  79. package/dist/cli/new.js.map +1 -0
  80. package/dist/cli/notify.d.ts +20 -0
  81. package/dist/cli/notify.js +188 -0
  82. package/dist/cli/notify.js.map +1 -0
  83. package/dist/cli/onboard.d.ts +85 -0
  84. package/dist/cli/onboard.js +305 -0
  85. package/dist/cli/onboard.js.map +1 -0
  86. package/dist/cli/open.d.ts +38 -0
  87. package/dist/cli/open.js +86 -0
  88. package/dist/cli/open.js.map +1 -0
  89. package/dist/cli/orient.d.ts +14 -0
  90. package/dist/cli/orient.js +110 -0
  91. package/dist/cli/orient.js.map +1 -0
  92. package/dist/cli/picker.d.ts +21 -0
  93. package/dist/cli/picker.js +177 -0
  94. package/dist/cli/picker.js.map +1 -0
  95. package/dist/cli/plugins.d.ts +21 -0
  96. package/dist/cli/plugins.js +394 -0
  97. package/dist/cli/plugins.js.map +1 -0
  98. package/dist/cli/preflight.d.ts +29 -0
  99. package/dist/cli/preflight.js +131 -0
  100. package/dist/cli/preflight.js.map +1 -0
  101. package/dist/cli/pulse.d.ts +26 -0
  102. package/dist/cli/pulse.js +482 -0
  103. package/dist/cli/pulse.js.map +1 -0
  104. package/dist/cli/reflect.d.ts +42 -0
  105. package/dist/cli/reflect.js +381 -0
  106. package/dist/cli/reflect.js.map +1 -0
  107. package/dist/cli/run.d.ts +29 -0
  108. package/dist/cli/run.js +687 -0
  109. package/dist/cli/run.js.map +1 -0
  110. package/dist/cli/sandbox.d.ts +20 -0
  111. package/dist/cli/sandbox.js +345 -0
  112. package/dist/cli/sandbox.js.map +1 -0
  113. package/dist/cli/seams.d.ts +41 -0
  114. package/dist/cli/seams.js +167 -0
  115. package/dist/cli/seams.js.map +1 -0
  116. package/dist/cli/serve.d.ts +25 -0
  117. package/dist/cli/serve.js +249 -0
  118. package/dist/cli/serve.js.map +1 -0
  119. package/dist/cli/ship.d.ts +33 -0
  120. package/dist/cli/ship.js +433 -0
  121. package/dist/cli/ship.js.map +1 -0
  122. package/dist/cli/spec.d.ts +19 -0
  123. package/dist/cli/spec.js +478 -0
  124. package/dist/cli/spec.js.map +1 -0
  125. package/dist/cli/swarm.d.ts +42 -0
  126. package/dist/cli/swarm.js +1365 -0
  127. package/dist/cli/swarm.js.map +1 -0
  128. package/dist/cli/telemetry.d.ts +26 -0
  129. package/dist/cli/telemetry.js +364 -0
  130. package/dist/cli/telemetry.js.map +1 -0
  131. package/dist/cli/tui.d.ts +17 -0
  132. package/dist/cli/tui.js +52 -0
  133. package/dist/cli/tui.js.map +1 -0
  134. package/dist/cli/ui.d.ts +53 -0
  135. package/dist/cli/ui.js +66 -0
  136. package/dist/cli/ui.js.map +1 -0
  137. package/dist/cli/update.d.ts +38 -0
  138. package/dist/cli/update.js +682 -0
  139. package/dist/cli/update.js.map +1 -0
  140. package/dist/cli/vercel.d.ts +20 -0
  141. package/dist/cli/vercel.js +193 -0
  142. package/dist/cli/vercel.js.map +1 -0
  143. package/dist/cli/verify-safety.d.ts +96 -0
  144. package/dist/cli/verify-safety.js +509 -0
  145. package/dist/cli/verify-safety.js.map +1 -0
  146. package/dist/cli/wire.d.ts +22 -0
  147. package/dist/cli/wire.js +205 -0
  148. package/dist/cli/wire.js.map +1 -0
  149. package/dist/core/classify.d.ts +51 -0
  150. package/dist/core/classify.js +450 -0
  151. package/dist/core/classify.js.map +1 -0
  152. package/dist/core/config.d.ts +63 -0
  153. package/dist/core/config.js +474 -0
  154. package/dist/core/config.js.map +1 -0
  155. package/dist/core/daemon/loop.d.ts +74 -0
  156. package/dist/core/daemon/loop.js +618 -0
  157. package/dist/core/daemon/loop.js.map +1 -0
  158. package/dist/core/daemon/state.d.ts +66 -0
  159. package/dist/core/daemon/state.js +197 -0
  160. package/dist/core/daemon/state.js.map +1 -0
  161. package/dist/core/dashboard.d.ts +40 -0
  162. package/dist/core/dashboard.js +463 -0
  163. package/dist/core/dashboard.js.map +1 -0
  164. package/dist/core/digest/build.d.ts +51 -0
  165. package/dist/core/digest/build.js +230 -0
  166. package/dist/core/digest/build.js.map +1 -0
  167. package/dist/core/digest/deliver.d.ts +47 -0
  168. package/dist/core/digest/deliver.js +230 -0
  169. package/dist/core/digest/deliver.js.map +1 -0
  170. package/dist/core/digest/store.d.ts +57 -0
  171. package/dist/core/digest/store.js +223 -0
  172. package/dist/core/digest/store.js.map +1 -0
  173. package/dist/core/doctor-fix.d.ts +21 -0
  174. package/dist/core/doctor-fix.js +440 -0
  175. package/dist/core/doctor-fix.js.map +1 -0
  176. package/dist/core/doctor.d.ts +18 -0
  177. package/dist/core/doctor.js +806 -0
  178. package/dist/core/doctor.js.map +1 -0
  179. package/dist/core/env-bridge.d.ts +64 -0
  180. package/dist/core/env-bridge.js +103 -0
  181. package/dist/core/env-bridge.js.map +1 -0
  182. package/dist/core/genome/capture.d.ts +49 -0
  183. package/dist/core/genome/capture.js +352 -0
  184. package/dist/core/genome/capture.js.map +1 -0
  185. package/dist/core/genome/consolidate.d.ts +38 -0
  186. package/dist/core/genome/consolidate.js +426 -0
  187. package/dist/core/genome/consolidate.js.map +1 -0
  188. package/dist/core/genome/export.d.ts +29 -0
  189. package/dist/core/genome/export.js +102 -0
  190. package/dist/core/genome/export.js.map +1 -0
  191. package/dist/core/genome/playbook.d.ts +33 -0
  192. package/dist/core/genome/playbook.js +320 -0
  193. package/dist/core/genome/playbook.js.map +1 -0
  194. package/dist/core/genome/recall.d.ts +45 -0
  195. package/dist/core/genome/recall.js +293 -0
  196. package/dist/core/genome/recall.js.map +1 -0
  197. package/dist/core/genome/store.d.ts +62 -0
  198. package/dist/core/genome/store.js +711 -0
  199. package/dist/core/genome/store.js.map +1 -0
  200. package/dist/core/git.d.ts +34 -0
  201. package/dist/core/git.js +115 -0
  202. package/dist/core/git.js.map +1 -0
  203. package/dist/core/goals/advance.d.ts +82 -0
  204. package/dist/core/goals/advance.js +263 -0
  205. package/dist/core/goals/advance.js.map +1 -0
  206. package/dist/core/goals/planner.d.ts +58 -0
  207. package/dist/core/goals/planner.js +233 -0
  208. package/dist/core/goals/planner.js.map +1 -0
  209. package/dist/core/goals/store.d.ts +128 -0
  210. package/dist/core/goals/store.js +442 -0
  211. package/dist/core/goals/store.js.map +1 -0
  212. package/dist/core/inbox/apply.d.ts +36 -0
  213. package/dist/core/inbox/apply.js +407 -0
  214. package/dist/core/inbox/apply.js.map +1 -0
  215. package/dist/core/inbox/notify-proposal.d.ts +13 -0
  216. package/dist/core/inbox/notify-proposal.js +21 -0
  217. package/dist/core/inbox/notify-proposal.js.map +1 -0
  218. package/dist/core/inbox/store.d.ts +67 -0
  219. package/dist/core/inbox/store.js +258 -0
  220. package/dist/core/inbox/store.js.map +1 -0
  221. package/dist/core/index-engine.d.ts +62 -0
  222. package/dist/core/index-engine.js +486 -0
  223. package/dist/core/index-engine.js.map +1 -0
  224. package/dist/core/integrations/desktop-notify.d.ts +18 -0
  225. package/dist/core/integrations/desktop-notify.js +42 -0
  226. package/dist/core/integrations/desktop-notify.js.map +1 -0
  227. package/dist/core/integrations/editors.d.ts +50 -0
  228. package/dist/core/integrations/editors.js +216 -0
  229. package/dist/core/integrations/editors.js.map +1 -0
  230. package/dist/core/integrations/github.d.ts +66 -0
  231. package/dist/core/integrations/github.js +332 -0
  232. package/dist/core/integrations/github.js.map +1 -0
  233. package/dist/core/integrations/identity.d.ts +29 -0
  234. package/dist/core/integrations/identity.js +359 -0
  235. package/dist/core/integrations/identity.js.map +1 -0
  236. package/dist/core/integrations/notify.d.ts +23 -0
  237. package/dist/core/integrations/notify.js +96 -0
  238. package/dist/core/integrations/notify.js.map +1 -0
  239. package/dist/core/integrations/vercel.d.ts +37 -0
  240. package/dist/core/integrations/vercel.js +192 -0
  241. package/dist/core/integrations/vercel.js.map +1 -0
  242. package/dist/core/knowledge/ask.d.ts +34 -0
  243. package/dist/core/knowledge/ask.js +325 -0
  244. package/dist/core/knowledge/ask.js.map +1 -0
  245. package/dist/core/knowledge/graph.d.ts +51 -0
  246. package/dist/core/knowledge/graph.js +564 -0
  247. package/dist/core/knowledge/graph.js.map +1 -0
  248. package/dist/core/knowledge/index.d.ts +74 -0
  249. package/dist/core/knowledge/index.js +586 -0
  250. package/dist/core/knowledge/index.js.map +1 -0
  251. package/dist/core/learn/playbooks.d.ts +84 -0
  252. package/dist/core/learn/playbooks.js +241 -0
  253. package/dist/core/learn/playbooks.js.map +1 -0
  254. package/dist/core/learn/reflect.d.ts +87 -0
  255. package/dist/core/learn/reflect.js +435 -0
  256. package/dist/core/learn/reflect.js.map +1 -0
  257. package/dist/core/learn/store.d.ts +51 -0
  258. package/dist/core/learn/store.js +165 -0
  259. package/dist/core/learn/store.js.map +1 -0
  260. package/dist/core/learn/tuning.d.ts +48 -0
  261. package/dist/core/learn/tuning.js +201 -0
  262. package/dist/core/learn/tuning.js.map +1 -0
  263. package/dist/core/lifecycle/scaffold.d.ts +43 -0
  264. package/dist/core/lifecycle/scaffold.js +260 -0
  265. package/dist/core/lifecycle/scaffold.js.map +1 -0
  266. package/dist/core/lifecycle/ship.d.ts +48 -0
  267. package/dist/core/lifecycle/ship.js +513 -0
  268. package/dist/core/lifecycle/ship.js.map +1 -0
  269. package/dist/core/lifecycle/templates.d.ts +20 -0
  270. package/dist/core/lifecycle/templates.js +605 -0
  271. package/dist/core/lifecycle/templates.js.map +1 -0
  272. package/dist/core/mcp-gateway.d.ts +59 -0
  273. package/dist/core/mcp-gateway.js +385 -0
  274. package/dist/core/mcp-gateway.js.map +1 -0
  275. package/dist/core/mcp-native.d.ts +53 -0
  276. package/dist/core/mcp-native.js +507 -0
  277. package/dist/core/mcp-native.js.map +1 -0
  278. package/dist/core/mcp-registry.d.ts +36 -0
  279. package/dist/core/mcp-registry.js +180 -0
  280. package/dist/core/mcp-registry.js.map +1 -0
  281. package/dist/core/observability/budget-alert.d.ts +18 -0
  282. package/dist/core/observability/budget-alert.js +90 -0
  283. package/dist/core/observability/budget-alert.js.map +1 -0
  284. package/dist/core/observability/estimate.d.ts +27 -0
  285. package/dist/core/observability/estimate.js +188 -0
  286. package/dist/core/observability/estimate.js.map +1 -0
  287. package/dist/core/observability/forecast.d.ts +19 -0
  288. package/dist/core/observability/forecast.js +101 -0
  289. package/dist/core/observability/forecast.js.map +1 -0
  290. package/dist/core/observability/governance.d.ts +28 -0
  291. package/dist/core/observability/governance.js +101 -0
  292. package/dist/core/observability/governance.js.map +1 -0
  293. package/dist/core/observability/otlp.d.ts +88 -0
  294. package/dist/core/observability/otlp.js +217 -0
  295. package/dist/core/observability/otlp.js.map +1 -0
  296. package/dist/core/observability/rollup.d.ts +35 -0
  297. package/dist/core/observability/rollup.js +311 -0
  298. package/dist/core/observability/rollup.js.map +1 -0
  299. package/dist/core/observability/telemetry-sink.d.ts +63 -0
  300. package/dist/core/observability/telemetry-sink.js +350 -0
  301. package/dist/core/observability/telemetry-sink.js.map +1 -0
  302. package/dist/core/observability/usage-source.d.ts +60 -0
  303. package/dist/core/observability/usage-source.js +347 -0
  304. package/dist/core/observability/usage-source.js.map +1 -0
  305. package/dist/core/onboard.d.ts +27 -0
  306. package/dist/core/onboard.js +288 -0
  307. package/dist/core/onboard.js.map +1 -0
  308. package/dist/core/orient.d.ts +23 -0
  309. package/dist/core/orient.js +135 -0
  310. package/dist/core/orient.js.map +1 -0
  311. package/dist/core/phantom.d.ts +23 -0
  312. package/dist/core/phantom.js +279 -0
  313. package/dist/core/phantom.js.map +1 -0
  314. package/dist/core/plugins/host-api.d.ts +29 -0
  315. package/dist/core/plugins/host-api.js +113 -0
  316. package/dist/core/plugins/host-api.js.map +1 -0
  317. package/dist/core/plugins/integrity.d.ts +36 -0
  318. package/dist/core/plugins/integrity.js +73 -0
  319. package/dist/core/plugins/integrity.js.map +1 -0
  320. package/dist/core/plugins/manifest.d.ts +31 -0
  321. package/dist/core/plugins/manifest.js +316 -0
  322. package/dist/core/plugins/manifest.js.map +1 -0
  323. package/dist/core/plugins/registry.d.ts +87 -0
  324. package/dist/core/plugins/registry.js +415 -0
  325. package/dist/core/plugins/registry.js.map +1 -0
  326. package/dist/core/plugins/types.d.ts +182 -0
  327. package/dist/core/plugins/types.js +40 -0
  328. package/dist/core/plugins/types.js.map +1 -0
  329. package/dist/core/plugins/wrappers.d.ts +40 -0
  330. package/dist/core/plugins/wrappers.js +229 -0
  331. package/dist/core/plugins/wrappers.js.map +1 -0
  332. package/dist/core/portfolio/backlog.d.ts +40 -0
  333. package/dist/core/portfolio/backlog.js +177 -0
  334. package/dist/core/portfolio/backlog.js.map +1 -0
  335. package/dist/core/portfolio/scanners.d.ts +21 -0
  336. package/dist/core/portfolio/scanners.js +600 -0
  337. package/dist/core/portfolio/scanners.js.map +1 -0
  338. package/dist/core/providers.d.ts +34 -0
  339. package/dist/core/providers.js +250 -0
  340. package/dist/core/providers.js.map +1 -0
  341. package/dist/core/quality/conventions.d.ts +35 -0
  342. package/dist/core/quality/conventions.js +267 -0
  343. package/dist/core/quality/conventions.js.map +1 -0
  344. package/dist/core/quality/fixes.d.ts +56 -0
  345. package/dist/core/quality/fixes.js +209 -0
  346. package/dist/core/quality/fixes.js.map +1 -0
  347. package/dist/core/quality/health.d.ts +69 -0
  348. package/dist/core/quality/health.js +350 -0
  349. package/dist/core/quality/health.js.map +1 -0
  350. package/dist/core/quality/store.d.ts +56 -0
  351. package/dist/core/quality/store.js +195 -0
  352. package/dist/core/quality/store.js.map +1 -0
  353. package/dist/core/readiness.d.ts +112 -0
  354. package/dist/core/readiness.js +431 -0
  355. package/dist/core/readiness.js.map +1 -0
  356. package/dist/core/run/agent-loop.d.ts +40 -0
  357. package/dist/core/run/agent-loop.js +291 -0
  358. package/dist/core/run/agent-loop.js.map +1 -0
  359. package/dist/core/run/budget.d.ts +47 -0
  360. package/dist/core/run/budget.js +113 -0
  361. package/dist/core/run/budget.js.map +1 -0
  362. package/dist/core/run/engines.d.ts +75 -0
  363. package/dist/core/run/engines.js +199 -0
  364. package/dist/core/run/engines.js.map +1 -0
  365. package/dist/core/run/model-manager.d.ts +64 -0
  366. package/dist/core/run/model-manager.js +339 -0
  367. package/dist/core/run/model-manager.js.map +1 -0
  368. package/dist/core/run/orchestrator.d.ts +100 -0
  369. package/dist/core/run/orchestrator.js +1515 -0
  370. package/dist/core/run/orchestrator.js.map +1 -0
  371. package/dist/core/run/provider-client.d.ts +46 -0
  372. package/dist/core/run/provider-client.js +796 -0
  373. package/dist/core/run/provider-client.js.map +1 -0
  374. package/dist/core/run/retry.d.ts +19 -0
  375. package/dist/core/run/retry.js +68 -0
  376. package/dist/core/run/retry.js.map +1 -0
  377. package/dist/core/run/router.d.ts +50 -0
  378. package/dist/core/run/router.js +257 -0
  379. package/dist/core/run/router.js.map +1 -0
  380. package/dist/core/run/self-heal.d.ts +52 -0
  381. package/dist/core/run/self-heal.js +181 -0
  382. package/dist/core/run/self-heal.js.map +1 -0
  383. package/dist/core/run/streaming.d.ts +31 -0
  384. package/dist/core/run/streaming.js +122 -0
  385. package/dist/core/run/streaming.js.map +1 -0
  386. package/dist/core/run/verify.d.ts +30 -0
  387. package/dist/core/run/verify.js +204 -0
  388. package/dist/core/run/verify.js.map +1 -0
  389. package/dist/core/sandbox/audit.d.ts +31 -0
  390. package/dist/core/sandbox/audit.js +169 -0
  391. package/dist/core/sandbox/audit.js.map +1 -0
  392. package/dist/core/sandbox/policy.d.ts +61 -0
  393. package/dist/core/sandbox/policy.js +211 -0
  394. package/dist/core/sandbox/policy.js.map +1 -0
  395. package/dist/core/sandbox/worktree.d.ts +175 -0
  396. package/dist/core/sandbox/worktree.js +673 -0
  397. package/dist/core/sandbox/worktree.js.map +1 -0
  398. package/dist/core/seams/backlog.d.ts +47 -0
  399. package/dist/core/seams/backlog.js +47 -0
  400. package/dist/core/seams/backlog.js.map +1 -0
  401. package/dist/core/seams/daemon-coordinator.d.ts +72 -0
  402. package/dist/core/seams/daemon-coordinator.js +76 -0
  403. package/dist/core/seams/daemon-coordinator.js.map +1 -0
  404. package/dist/core/seams/genome.d.ts +45 -0
  405. package/dist/core/seams/genome.js +53 -0
  406. package/dist/core/seams/genome.js.map +1 -0
  407. package/dist/core/seams/identity.d.ts +40 -0
  408. package/dist/core/seams/identity.js +44 -0
  409. package/dist/core/seams/identity.js.map +1 -0
  410. package/dist/core/seams/inbox.d.ts +60 -0
  411. package/dist/core/seams/inbox.js +66 -0
  412. package/dist/core/seams/inbox.js.map +1 -0
  413. package/dist/core/seams/index.d.ts +20 -0
  414. package/dist/core/seams/index.js +21 -0
  415. package/dist/core/seams/index.js.map +1 -0
  416. package/dist/core/seams/portfolio.d.ts +50 -0
  417. package/dist/core/seams/portfolio.js +61 -0
  418. package/dist/core/seams/portfolio.js.map +1 -0
  419. package/dist/core/seams/registry.d.ts +42 -0
  420. package/dist/core/seams/registry.js +128 -0
  421. package/dist/core/seams/registry.js.map +1 -0
  422. package/dist/core/seams/run-swarm.d.ts +66 -0
  423. package/dist/core/seams/run-swarm.js +74 -0
  424. package/dist/core/seams/run-swarm.js.map +1 -0
  425. package/dist/core/seams/types.d.ts +123 -0
  426. package/dist/core/seams/types.js +35 -0
  427. package/dist/core/seams/types.js.map +1 -0
  428. package/dist/core/spec/spec-store.d.ts +62 -0
  429. package/dist/core/spec/spec-store.js +359 -0
  430. package/dist/core/spec/spec-store.js.map +1 -0
  431. package/dist/core/swarm/gate.d.ts +45 -0
  432. package/dist/core/swarm/gate.js +114 -0
  433. package/dist/core/swarm/gate.js.map +1 -0
  434. package/dist/core/swarm/planner.d.ts +32 -0
  435. package/dist/core/swarm/planner.js +293 -0
  436. package/dist/core/swarm/planner.js.map +1 -0
  437. package/dist/core/swarm/rollback.d.ts +56 -0
  438. package/dist/core/swarm/rollback.js +266 -0
  439. package/dist/core/swarm/rollback.js.map +1 -0
  440. package/dist/core/swarm/runner.d.ts +62 -0
  441. package/dist/core/swarm/runner.js +1263 -0
  442. package/dist/core/swarm/runner.js.map +1 -0
  443. package/dist/core/swarm/sign.d.ts +71 -0
  444. package/dist/core/swarm/sign.js +362 -0
  445. package/dist/core/swarm/sign.js.map +1 -0
  446. package/dist/core/swarm/store.d.ts +52 -0
  447. package/dist/core/swarm/store.js +195 -0
  448. package/dist/core/swarm/store.js.map +1 -0
  449. package/dist/core/tidy.d.ts +32 -0
  450. package/dist/core/tidy.js +354 -0
  451. package/dist/core/tidy.js.map +1 -0
  452. package/dist/core/tools-registry.d.ts +16 -0
  453. package/dist/core/tools-registry.js +308 -0
  454. package/dist/core/tools-registry.js.map +1 -0
  455. package/dist/core/types.d.ts +2545 -0
  456. package/dist/core/types.js +9 -0
  457. package/dist/core/types.js.map +1 -0
  458. package/dist/core/web/api.d.ts +53 -0
  459. package/dist/core/web/api.js +698 -0
  460. package/dist/core/web/api.js.map +1 -0
  461. package/dist/core/web/public/app.js +1906 -0
  462. package/dist/core/web/public/index.html +721 -0
  463. package/dist/core/web/public/styles.css +2007 -0
  464. package/dist/core/web/server.d.ts +18 -0
  465. package/dist/core/web/server.js +122 -0
  466. package/dist/core/web/server.js.map +1 -0
  467. package/dist/core/web/static.d.ts +18 -0
  468. package/dist/core/web/static.js +122 -0
  469. package/dist/core/web/static.js.map +1 -0
  470. package/dist/tui/app.d.ts +33 -0
  471. package/dist/tui/app.js +350 -0
  472. package/dist/tui/app.js.map +1 -0
  473. package/dist/tui/render.d.ts +20 -0
  474. package/dist/tui/render.js +558 -0
  475. package/dist/tui/render.js.map +1 -0
  476. package/package.json +80 -0
  477. package/schema/config.schema.json +223 -0
@@ -0,0 +1,2545 @@
1
+ /**
2
+ * THE CONTRACT.
3
+ *
4
+ * Every type that crosses a module boundary in ashlr-hub lives here.
5
+ * Downstream agents (git, classify, index-engine, tidy, cli, raycast)
6
+ * import from this file and MUST NOT redefine these shapes.
7
+ */
8
+ /** A single tidy rule: how to match a file/dir and where it should go. */
9
+ export interface TidyRule {
10
+ /** Glob, regex source, or bare extension depending on `matchType`. */
11
+ match: string;
12
+ /** How `match` is interpreted: shell glob, RegExp source, or file extension. */
13
+ matchType: 'glob' | 'regex' | 'ext';
14
+ /** Destination directory (relative to root or absolute) the match moves into. */
15
+ dest: string;
16
+ /** Optional human-readable explanation of the rule's intent. */
17
+ description?: string;
18
+ }
19
+ /** Persisted configuration for the hub. Lives at ~/.ashlr/config.json. */
20
+ export interface AshlrConfig {
21
+ /** Schema/config version for forward migration. */
22
+ version: number;
23
+ /** Absolute roots to scan (typically the Desktop and github/). */
24
+ roots: string[];
25
+ /** Preferred editor for deep links. */
26
+ editor: 'cursor' | 'vscode';
27
+ /** Days without modification before an item is considered stale/inactive. */
28
+ staleDays: number;
29
+ /** Map of category name -> absolute folder path (e.g. "dev-tools" -> ".../github/dev-tools"). */
30
+ categories: Record<string, string>;
31
+ /** Ordered tidy rules applied to loose top-level files. */
32
+ tidyRules: TidyRule[];
33
+ /** Absolute paths or basenames that must never be moved/tidied. */
34
+ keepers: string[];
35
+ /** Local + remote model configuration and provider preference order. */
36
+ models: {
37
+ lmstudio: string;
38
+ ollama: string;
39
+ providerChain: string[];
40
+ /**
41
+ * Optional per-task routing rules (M15). Each rule maps a goal/task match
42
+ * to a preferred model. First matching rule wins; falls back to local-first
43
+ * chain selection when none match. Never forces a cloud provider on its own.
44
+ */
45
+ routing?: RoutingRule[];
46
+ /**
47
+ * Optional auto-escalation policy (M15). When `onFailure` is true, a LOCAL
48
+ * task that fails/verify-fails (or exceeds `latencyMs`) MAY escalate to a
49
+ * cloud provider for one routed retry — but ONLY when --allow-cloud is set
50
+ * and a cloud key is present. Never enables silent cloud spend on its own.
51
+ */
52
+ escalate?: {
53
+ onFailure: boolean;
54
+ latencyMs?: number;
55
+ };
56
+ };
57
+ /** Telemetry hooks (e.g. Pulse) + local budget caps. All fields optional. */
58
+ telemetry: {
59
+ pulse?: string;
60
+ /** Optional spend cap (USD) for the budget window; alerts when over/near. */
61
+ budgetUsd?: number;
62
+ /** Optional token cap (in + out) for the budget window. */
63
+ budgetTokens?: number;
64
+ /** Window the budget caps apply to (default '7d' when caps are set). */
65
+ budgetWindow?: "1d" | "7d" | "30d";
66
+ /**
67
+ * M19 spend governance action when the period spend exceeds the cap.
68
+ * 'warn' (default): advisory only — print a prominent warning, never block.
69
+ * 'block': additionally require an explicit --over-budget flag to proceed.
70
+ * Governance NEVER silently blocks; the per-run hard budget remains the
71
+ * only hard ceiling.
72
+ */
73
+ govAction?: "warn" | "block";
74
+ };
75
+ /** Map of integration name -> resolved executable path (entire, aw, claude, ...). */
76
+ tools: Record<string, string>;
77
+ /** Optional Phantom secrets integration toggle. */
78
+ phantom?: {
79
+ enabled: boolean;
80
+ };
81
+ /**
82
+ * Shared-memory / genome settings (M7). Controls recall limits and whether
83
+ * the orchestrator injects recall hits into sub-agent prompts.
84
+ */
85
+ genome?: {
86
+ /** Max number of recall hits returned/injected (default 5). */
87
+ maxRecall: number;
88
+ /** Whether `ashlr run` injects top-k recall into sub-agent prompts (default true). */
89
+ injectOnRun: boolean;
90
+ /**
91
+ * Whether a completed run/swarm auto-captures a summary GenomeEntry (M16).
92
+ * Default true. Summary/metadata only; fire-and-forget; opt out per-call
93
+ * via --no-capture. Never blocks/slows a run.
94
+ */
95
+ autoCapture?: boolean;
96
+ /**
97
+ * Whether `runGoal` synthesizes + injects a bounded playbook from past
98
+ * similar entries into planning context (M16). Default true.
99
+ */
100
+ playbookOnRun?: boolean;
101
+ };
102
+ /**
103
+ * Optional outward notification targets (M18). When a webhook is set, a
104
+ * concise run/swarm COMPLETION summary MAY be posted to it (no secrets).
105
+ * Entirely opt-in: a no-op when unset — notify() never posts without one.
106
+ */
107
+ notify?: NotifyTarget;
108
+ /**
109
+ * M24: optional autonomous-operator (daemon) tuning. When unset, the daemon
110
+ * falls back to its hard-coded conservative defaults. This NEVER widens the
111
+ * daemon's authority — the daemon is proposal-only by construction; these
112
+ * fields only bound HOW MUCH it may propose (budget/items/parallel/interval).
113
+ */
114
+ daemon?: Partial<DaemonConfig>;
115
+ /**
116
+ * M33: optional plugin-system configuration. Additive — absent in old configs
117
+ * and defaults to { enabled: [], settings: {}, integrity: {} } via defaultConfig().
118
+ * DEFAULT EMPTY: enabled:[] means NO plugins load.
119
+ */
120
+ plugins?: {
121
+ /** Plugin names that are permitted to load (default empty = load nothing). */
122
+ enabled: string[];
123
+ /** Per-plugin key/value settings. Key = plugin name, value = arbitrary object. */
124
+ settings: Record<string, Record<string, unknown>>;
125
+ /**
126
+ * Per-plugin integrity pins. Key = plugin name.
127
+ * Value = "sha256:<64-hex-char>" hash of the plugin's entry file.
128
+ * A missing pin causes the plugin to be refused at load time.
129
+ */
130
+ integrity: Record<string, string>;
131
+ };
132
+ }
133
+ /** Result of probing a single local-model/provider endpoint. Never throws. */
134
+ export interface ProviderEndpoint {
135
+ /** Stable provider id ('lmstudio' | 'ollama' | custom). */
136
+ id: 'lmstudio' | 'ollama' | string;
137
+ /** Base/probe URL that was queried. */
138
+ url: string;
139
+ /** Whether the endpoint responded successfully. */
140
+ up: boolean;
141
+ /** Model ids/names reported by the endpoint (empty when down). */
142
+ models: string[];
143
+ /** Probe error message when `up` is false, else absent. */
144
+ error?: string;
145
+ }
146
+ /** Aggregated view of all configured providers + the resolved active one. */
147
+ export interface ProviderRegistry {
148
+ /** All probed endpoints, in chain order where possible. */
149
+ providers: ProviderEndpoint[];
150
+ /** Id of the first up provider in the chain, or null if none are up. */
151
+ activeProvider: string | null;
152
+ /** The configured provider preference chain (from cfg.models.providerChain). */
153
+ chain: string[];
154
+ }
155
+ /** Read-only status of the Phantom secrets CLI. NEVER carries secret values. */
156
+ export interface PhantomStatus {
157
+ /** Whether the `phantom` binary is on PATH. */
158
+ installed: boolean;
159
+ /** Reported version string, or null if unknown/unavailable. */
160
+ version: string | null;
161
+ /** Whether a Phantom vault/identity is initialized. */
162
+ initialized: boolean;
163
+ /** Secret NAMES only (never values). Empty when uninitialized/unavailable. */
164
+ secretNames: string[];
165
+ /** Error message when status could not be fully determined, else absent. */
166
+ error?: string;
167
+ }
168
+ /** Outcome of a single doctor health check. */
169
+ export type DoctorCheckStatus = 'pass' | 'warn' | 'fail';
170
+ /** A single health check produced by `runDoctor`. */
171
+ export interface DoctorCheck {
172
+ /** Stable check id (e.g. 'config', 'phantom', 'provider:ollama'). */
173
+ id: string;
174
+ /** Human-readable label for the check. */
175
+ label: string;
176
+ /** pass | warn | fail. */
177
+ status: DoctorCheckStatus;
178
+ /** One-line detail describing the observed state. */
179
+ detail: string;
180
+ /** Optional suggested remediation command/hint. */
181
+ fix?: string;
182
+ }
183
+ /** Full one-glance health report from `ashlr doctor`. */
184
+ export interface DoctorReport {
185
+ /** ISO timestamp the report was generated. */
186
+ generatedAt: string;
187
+ /** All checks performed, in display order. */
188
+ checks: DoctorCheck[];
189
+ /** Roll-up counts by status. */
190
+ summary: {
191
+ pass: number;
192
+ warn: number;
193
+ fail: number;
194
+ };
195
+ }
196
+ /** What an indexed entry fundamentally is. */
197
+ export type ItemKind = 'repo' | 'doc-folder' | 'doc' | 'asset' | 'symlink' | 'other';
198
+ /** Git working-tree + remote-tracking summary for a repo. */
199
+ export interface GitStatus {
200
+ /** Current branch name (or detached HEAD label). */
201
+ branch: string;
202
+ /** Count of dirty (modified/staged/untracked) paths. */
203
+ dirty: number;
204
+ /** Commits ahead of upstream. */
205
+ ahead: number;
206
+ /** Commits behind upstream. */
207
+ behind: number;
208
+ /** ISO timestamp of the last commit, or null if no commits yet. */
209
+ lastCommit: string | null;
210
+ }
211
+ /** A single thing on the Desktop that the hub knows about. */
212
+ export interface IndexedItem {
213
+ /** Stable identifier (derived from the absolute path). */
214
+ id: string;
215
+ /** Display name (basename). */
216
+ name: string;
217
+ /** Absolute filesystem path. */
218
+ path: string;
219
+ /** Fundamental kind of the item. */
220
+ kind: ItemKind;
221
+ /** Category bucket (e.g. "dev-tools", "Business"), or null if uncategorized. */
222
+ category: string | null;
223
+ /** One-line description (README h1 / package.json description), or null. */
224
+ description: string | null;
225
+ /** Git org parsed from the remote (ashlrai, masonwyatt23, ...), or null. */
226
+ org: string | null;
227
+ /** Raw git remote URL, or null. */
228
+ remote: string | null;
229
+ /** Primary language guess, or null. */
230
+ language: string | null;
231
+ /** ISO timestamp of last modification. */
232
+ lastModified: string;
233
+ /** Whether the item is "active" (modified within staleDays). */
234
+ active: boolean;
235
+ /** Size in bytes, when cheaply available. */
236
+ sizeBytes?: number;
237
+ /** Git status, present only for repos. */
238
+ git?: GitStatus;
239
+ /** Resolved target path, present only for symlinks. */
240
+ linkTarget?: string;
241
+ }
242
+ /** The full on-disk index. Lives at ~/.ashlr/index.json. */
243
+ export interface AshlrIndex {
244
+ /** Index format version. */
245
+ version: number;
246
+ /** ISO timestamp the index was generated. */
247
+ generatedAt: string;
248
+ /** Absolute root the index was built from (informational). */
249
+ root: string;
250
+ /** All indexed items. */
251
+ items: IndexedItem[];
252
+ }
253
+ /** A single planned move during tidy. */
254
+ export interface TidyMove {
255
+ /** Absolute source path. */
256
+ from: string;
257
+ /** Absolute destination path. */
258
+ to: string;
259
+ /** Identifier/description of the rule that produced this move. */
260
+ rule: string;
261
+ }
262
+ /** The output of planning a tidy pass (dry run). */
263
+ export interface TidyPlan {
264
+ /** Moves that would be applied. */
265
+ moves: TidyMove[];
266
+ /** Paths intentionally not moved, with a reason (keeper, no-match, etc.). */
267
+ skipped: {
268
+ path: string;
269
+ reason: string;
270
+ }[];
271
+ }
272
+ /** A single discovered MCP server spec (one entry under an "mcpServers" map). */
273
+ export interface McpServerSpec {
274
+ /** Unique server name (dedupe key across all discovered configs). */
275
+ name: string;
276
+ /** Executable to launch the stdio MCP server. */
277
+ command: string;
278
+ /** Arguments passed to `command`. */
279
+ args: string[];
280
+ /** Environment overrides for the child process. Values redacted when printed. */
281
+ env?: Record<string, string>;
282
+ /** Where this spec was discovered (config path / logical source label). */
283
+ source: string;
284
+ }
285
+ /** All MCP servers discovered on this machine, deduped by name. */
286
+ export interface McpRegistry {
287
+ /** Discovered server specs in stable order. */
288
+ servers: McpServerSpec[];
289
+ }
290
+ /** One downstream tool surfaced through the gateway, namespaced for routing. */
291
+ export interface AggregatedTool {
292
+ /** Owning downstream server name. */
293
+ server: string;
294
+ /** Original (downstream) tool name. */
295
+ name: string;
296
+ /** Gateway-facing name: `<server>__<tool>`. */
297
+ namespaced: string;
298
+ /** Tool description as reported by the downstream, if any. */
299
+ description?: string;
300
+ }
301
+ /** Health probe result for a single downstream MCP server. */
302
+ export interface McpServerHealth {
303
+ /** Server name probed. */
304
+ name: string;
305
+ /** Whether the server started and listed its tools successfully. */
306
+ ok: boolean;
307
+ /** Number of tools the server exposes (0 when not ok). */
308
+ toolCount: number;
309
+ /** Tool names reported by the server (empty when not ok). */
310
+ tools: string[];
311
+ /** Failure reason when `ok` is false, else absent. */
312
+ error?: string;
313
+ }
314
+ /** Detection result for a single ecosystem CLI tool. */
315
+ export interface ToolInfo {
316
+ /** Stable tool id (e.g. 'phantom', 'ashlr-plugin', 'stack'). */
317
+ id: string;
318
+ /** Display name for the tool. */
319
+ name: string;
320
+ /** Whether the tool was found on PATH. */
321
+ installed: boolean;
322
+ /** Reported version string, or null if unknown/not installed. */
323
+ version: string | null;
324
+ /** Resolved executable path, or null if not installed. */
325
+ path: string | null;
326
+ }
327
+ /** Roll-up of all detected ecosystem tools. */
328
+ export interface ToolsRegistry {
329
+ /** All probed tools in display order. */
330
+ tools: ToolInfo[];
331
+ /** Count of tools where `installed` is true. */
332
+ installedCount: number;
333
+ }
334
+ /** Hard guardrails for a single run. Budget/steps abort the run when exceeded. */
335
+ export interface RunBudget {
336
+ /** Maximum total tokens (in + out) before the run aborts. */
337
+ maxTokens: number;
338
+ /** Maximum number of model/agent steps before the run aborts. */
339
+ maxSteps: number;
340
+ /** Whether cloud providers are permitted (default false = local-first refuse). */
341
+ allowCloud: boolean;
342
+ }
343
+ /** Token + step accounting for a task or whole run. */
344
+ export interface RunUsage {
345
+ /** Prompt/input tokens consumed. */
346
+ tokensIn: number;
347
+ /** Completion/output tokens produced. */
348
+ tokensOut: number;
349
+ /** Number of steps taken. */
350
+ steps: number;
351
+ /** Estimated USD cost (0 for local providers). */
352
+ estCostUsd: number;
353
+ }
354
+ /** Lifecycle state of a single task in the run graph. */
355
+ export type RunTaskStatus = 'pending' | 'running' | 'done' | 'failed' | 'skipped';
356
+ /** A single node in the run task-graph (DAG). */
357
+ export interface RunTask {
358
+ /** Stable task id (unique within the run). */
359
+ id: string;
360
+ /** The sub-goal this task must accomplish. */
361
+ goal: string;
362
+ /** Ids of tasks that must complete before this one runs. */
363
+ deps: string[];
364
+ /** Current lifecycle status. */
365
+ status: RunTaskStatus;
366
+ /** Final task result text, present when done. */
367
+ result?: string;
368
+ /** Token/step usage attributed to this task. */
369
+ usage?: RunUsage;
370
+ /** Failure reason when status is 'failed', else absent. */
371
+ error?: string;
372
+ }
373
+ /** An append-only event recorded during a run for audit/resume. */
374
+ export interface RunStep {
375
+ /** ISO timestamp the step occurred. */
376
+ ts: string;
377
+ /** Id of the task this step belongs to. */
378
+ taskId: string;
379
+ /** What kind of step this was. */
380
+ kind: 'plan' | 'model' | 'tool' | 'synthesize';
381
+ /** One-line human-readable summary of the step. */
382
+ summary: string;
383
+ /** Usage incurred by this step, if any. */
384
+ usage?: RunUsage;
385
+ }
386
+ /** Full persisted state of a run. Lives at ~/.ashlr/runs/<id>.json. */
387
+ export interface RunState {
388
+ /** Stable run id. */
389
+ id: string;
390
+ /** The original top-level goal. */
391
+ goal: string;
392
+ /** Engine that executed the run ('builtin' | 'ashlrcode' | 'aw'). */
393
+ engine: string;
394
+ /** Active provider id used for the run. */
395
+ provider: string;
396
+ /** ISO timestamp the run was created. */
397
+ createdAt: string;
398
+ /** ISO timestamp of the last update (written after each step). */
399
+ updatedAt: string;
400
+ /** Guardrails in effect for this run. */
401
+ budget: RunBudget;
402
+ /** Cumulative usage across the whole run. */
403
+ usage: RunUsage;
404
+ /** The task-graph (DAG). */
405
+ tasks: RunTask[];
406
+ /** Append-only step log. */
407
+ steps: RunStep[];
408
+ /** Current run status. */
409
+ status: 'running' | 'done' | 'aborted' | 'failed';
410
+ /** Synthesized final answer, present when done. */
411
+ result?: string;
412
+ }
413
+ /** Options accepted by `runGoal` / the `ashlr run` CLI. */
414
+ export interface RunOptions {
415
+ /** Partial budget overrides (merged over defaults). */
416
+ budget?: Partial<RunBudget>;
417
+ /** Max independent tasks to execute in parallel. */
418
+ parallel?: number;
419
+ /** Engine selector ('builtin' | 'ashlrcode' | 'aw'). */
420
+ engine?: string;
421
+ /** Whether to load aggregated MCP tools (default true). */
422
+ tools?: boolean;
423
+ /** Whether cloud providers are permitted. */
424
+ allowCloud?: boolean;
425
+ /**
426
+ * Absolute working directory the run operates in (e.g. a swarm task's target
427
+ * project dir). When set, engine delegation uses this as the spawn cwd so the
428
+ * agent acts WITHIN the intended project, not wherever the parent launched.
429
+ * Defaults to process.cwd() when unset.
430
+ */
431
+ cwd?: string;
432
+ /** Existing run id to resume from cache. */
433
+ resumeId?: string;
434
+ /** Emit machine-readable JSON instead of human output. */
435
+ json?: boolean;
436
+ /** Disable genome recall injection into the sub-agent system prompt (M7). */
437
+ noMemory?: boolean;
438
+ /**
439
+ * Enable the optional cheap MODEL verification check after each builtin task
440
+ * (M11). Default false → heuristic-only verification (no extra model calls,
441
+ * preserving deterministic usage accounting). When true, verifyTask may make
442
+ * one cheap model call per task plus one verify-driven retry, all bounded by
443
+ * the global budget.
444
+ */
445
+ verifyModel?: boolean;
446
+ }
447
+ /** A single message in a chat exchange with a provider. */
448
+ export interface ChatMessage {
449
+ /** Message author role. */
450
+ role: 'system' | 'user' | 'assistant' | 'tool';
451
+ /** Message text content. */
452
+ content: string;
453
+ /** Tool-call id this message responds to (for role 'tool'). */
454
+ toolCallId?: string;
455
+ /** Tool/function name (for role 'tool'). */
456
+ name?: string;
457
+ }
458
+ /** Result of a single chat completion call. */
459
+ export interface ChatResult {
460
+ /** Assistant text content (may be empty when only tool calls returned). */
461
+ content: string;
462
+ /** Tool calls the model requested, if any. */
463
+ toolCalls?: {
464
+ id: string;
465
+ name: string;
466
+ arguments: unknown;
467
+ }[];
468
+ /** Token accounting for this call. */
469
+ usage: {
470
+ tokensIn: number;
471
+ tokensOut: number;
472
+ };
473
+ }
474
+ /** Thin chat client over the active local provider (Ollama / LM Studio). */
475
+ export interface ProviderClient {
476
+ /** Provider id this client targets. */
477
+ id: string;
478
+ /** Whether the underlying model/provider supports tool calls. */
479
+ supportsTools: boolean;
480
+ /** Send a chat exchange (optionally with tool specs) and get a result. */
481
+ chat(messages: ChatMessage[], tools?: unknown[]): Promise<ChatResult>;
482
+ /**
483
+ * Streaming chat (M11): invoke `onDelta(textChunk)` for each incremental
484
+ * content token, resolving to the SAME ChatResult shape as `chat()`
485
+ * (final content + toolCalls + usage). Implementations fall back to `chat()`
486
+ * (emitting the full content via a single `onDelta`) when the provider/model
487
+ * does not support streaming or streaming errors.
488
+ *
489
+ * Optional at the type level so the M11 contract typechecks before the
490
+ * provider agent implements it; the provider agent makes it concrete on both
491
+ * the Ollama and LM Studio clients (callers must `?.`-guard until then).
492
+ */
493
+ chatStream?(messages: ChatMessage[], tools: unknown[] | undefined, onDelta: (t: string) => void): Promise<ChatResult>;
494
+ }
495
+ /**
496
+ * One normalized usage data point. METADATA ONLY — never carries message
497
+ * content. Sourced from Claude Code transcripts ('claude') or local agent
498
+ * runs ('run').
499
+ */
500
+ export interface UsageEvent {
501
+ /** ISO timestamp of the event. */
502
+ ts: string;
503
+ /** Absolute project path this usage belongs to, or null if unknown. */
504
+ project: string | null;
505
+ /** Model id that produced the usage. */
506
+ model: string;
507
+ /** Where the event came from. */
508
+ source: "claude" | "run";
509
+ /** Prompt/input tokens. */
510
+ tokensIn: number;
511
+ /** Completion/output tokens. */
512
+ tokensOut: number;
513
+ /** Cache-read input tokens (0 when unavailable). */
514
+ cacheRead: number;
515
+ /** Cache-creation (write) input tokens (0 when unavailable). */
516
+ cacheWrite: number;
517
+ }
518
+ /** Per-project activity roll-up within a window. */
519
+ export interface ProjectActivity {
520
+ /** Absolute project path (or label). */
521
+ project: string;
522
+ /** Number of distinct sessions attributed to the project. */
523
+ sessions: number;
524
+ /** Number of git commits in the window. */
525
+ commits: number;
526
+ /** Total input tokens. */
527
+ tokensIn: number;
528
+ /** Total output tokens. */
529
+ tokensOut: number;
530
+ /** Estimated USD cost. */
531
+ estCostUsd: number;
532
+ /** ISO timestamp of the most recent activity, or null. */
533
+ lastActive: string | null;
534
+ }
535
+ /** Per-day usage roll-up within a window. */
536
+ export interface DailyUsage {
537
+ /** Calendar day (YYYY-MM-DD). */
538
+ day: string;
539
+ /** Total input tokens for the day. */
540
+ tokensIn: number;
541
+ /** Total output tokens for the day. */
542
+ tokensOut: number;
543
+ /** Estimated USD cost for the day. */
544
+ estCostUsd: number;
545
+ /** Number of sessions active that day. */
546
+ sessions: number;
547
+ }
548
+ /** Per-model usage roll-up within a window. */
549
+ export interface ModelUsage {
550
+ /** Model id. */
551
+ model: string;
552
+ /** Total input tokens. */
553
+ tokensIn: number;
554
+ /** Total output tokens. */
555
+ tokensOut: number;
556
+ /** Estimated USD cost. */
557
+ estCostUsd: number;
558
+ /** Number of calls attributed to the model. */
559
+ calls: number;
560
+ }
561
+ /** Budget evaluation for a spend/token cap over a window. */
562
+ export interface BudgetAlert {
563
+ /** ok (under), warn (near cap), or over (exceeded). */
564
+ level: "ok" | "warn" | "over";
565
+ /** Window the alert applies to (e.g. '7d'). */
566
+ window: string;
567
+ /** USD spent in the window. */
568
+ spentUsd: number;
569
+ /** Configured USD cap, or null if none set. */
570
+ capUsd: number | null;
571
+ /** Tokens (in + out) spent in the window. */
572
+ spentTokens: number;
573
+ /** Configured token cap, or null if none set. */
574
+ capTokens: number | null;
575
+ /** Human-readable status message. */
576
+ message: string;
577
+ }
578
+ /** The full observability roll-up for a window. */
579
+ export interface ActivityRollup {
580
+ /** Window label (e.g. '1d' | '7d' | '30d'). */
581
+ window: string;
582
+ /** ISO timestamp marking the start of the window. */
583
+ since: string;
584
+ /** Window totals across all projects/models. */
585
+ totals: {
586
+ tokensIn: number;
587
+ tokensOut: number;
588
+ estCostUsd: number;
589
+ sessions: number;
590
+ commits: number;
591
+ };
592
+ /** Per-project breakdown, sorted by activity. */
593
+ byProject: ProjectActivity[];
594
+ /** Per-day breakdown, ascending by day. */
595
+ byDay: DailyUsage[];
596
+ /** Per-model breakdown, sorted by cost/tokens. */
597
+ byModel: ModelUsage[];
598
+ /** Budget evaluation for the window. */
599
+ budget: BudgetAlert;
600
+ }
601
+ /** A single file emitted by a project template. */
602
+ export interface TemplateFile {
603
+ /** Path relative to the project root (POSIX-style separators). */
604
+ path: string;
605
+ /** Full file contents to write. */
606
+ content: string;
607
+ /** Optional octal file mode (e.g. 0o755 for executables). */
608
+ mode?: number;
609
+ }
610
+ /** A complete agentic-engineering starter template. */
611
+ export interface ProjectTemplate {
612
+ /** Stable template id (e.g. 'node-cli', 'mcp-server', 'next-app', 'minimal'). */
613
+ id: string;
614
+ /** Display title for the template. */
615
+ title: string;
616
+ /** One-line description of what the template scaffolds. */
617
+ description: string;
618
+ /** Produce the template's files for a given project name + category. */
619
+ files(ctx: {
620
+ name: string;
621
+ category: string;
622
+ }): TemplateFile[];
623
+ }
624
+ /** Fully-resolved instructions for a single scaffold operation. */
625
+ export interface ScaffoldSpec {
626
+ /** Project name (basename of the new directory). */
627
+ name: string;
628
+ /** Category bucket (e.g. 'side-projects', 'dev-tools'). */
629
+ category: string;
630
+ /** Id of the template to scaffold from. */
631
+ templateId: string;
632
+ /** Absolute target directory (must not already exist). */
633
+ dir: string;
634
+ /** Whether to run `git init` in the new project. */
635
+ git: boolean;
636
+ /** Optional `stack` recipe id to provision after scaffolding. */
637
+ stackRecipe?: string;
638
+ /**
639
+ * Opt out of the in-tree write guard that confines scaffolding to
640
+ * ~/Desktop/github (or the cwd for --here). ONLY for hermetic tests that
641
+ * scaffold into os.tmpdir(). Never set by the CLI for real scaffolds.
642
+ */
643
+ allowAnyRoot?: boolean;
644
+ }
645
+ /** Outcome of a scaffold operation. Never throws; failure is reported here. */
646
+ export interface ScaffoldResult {
647
+ /** Whether the project was scaffolded successfully. */
648
+ ok: boolean;
649
+ /** Absolute directory the project was (or would be) created in. */
650
+ dir: string;
651
+ /** Absolute paths of files written. */
652
+ filesWritten: string[];
653
+ /** Whether `git init` ran successfully. */
654
+ gitInitialized: boolean;
655
+ /** Whether the ashlr MCP gateway was wired into .mcp.json. */
656
+ mcpWired: boolean;
657
+ /** Whether the project was registered in the index. */
658
+ registered: boolean;
659
+ /** Error message when `ok` is false, else absent. */
660
+ error?: string;
661
+ /** Non-fatal warnings collected during scaffolding. */
662
+ warnings: string[];
663
+ }
664
+ /** A single pre-ship gate check. */
665
+ export interface ShipCheck {
666
+ /** Stable check id (e.g. 'supply-chain', 'test', 'lint', 'build'). */
667
+ id: string;
668
+ /** Human-readable label for the check. */
669
+ label: string;
670
+ /** pass | warn | fail | skip. */
671
+ status: 'pass' | 'warn' | 'fail' | 'skip';
672
+ /** One-line detail describing the observed state. */
673
+ detail: string;
674
+ /** Optional suggested remediation command/hint. */
675
+ fix?: string;
676
+ }
677
+ /** The full pre-ship gate report. */
678
+ export interface ShipGate {
679
+ /** All checks performed, in display order. */
680
+ checks: ShipCheck[];
681
+ /** Roll-up counts by status. */
682
+ summary: {
683
+ pass: number;
684
+ warn: number;
685
+ fail: number;
686
+ skip: number;
687
+ };
688
+ /** Whether the gate passed overall (no fails, or non-strict). */
689
+ passed: boolean;
690
+ }
691
+ /** Outcome of `ashlr ship`: the gate plus optional (dry-run) deploy. */
692
+ export interface ShipResult {
693
+ /** The pre-ship gate report. */
694
+ gate: ShipGate;
695
+ /** Deploy target name, or null when no deploy was requested. */
696
+ deployTarget: string | null;
697
+ /** Whether the deploy was a dry-run (true unless --confirm passed). */
698
+ deployDryRun: boolean;
699
+ /** Whether the deploy actually ran (only when confirmed). */
700
+ deployRan: boolean;
701
+ /** Human-readable detail of what was (or would be) deployed. */
702
+ deployDetail: string;
703
+ }
704
+ /**
705
+ * A single unit of shared memory in the aggregated genome. Sourced either
706
+ * from a per-project `.ashlrcode/genome/` directory ('project') or from the
707
+ * hub store at ~/.ashlr/genome/hub.jsonl ('hub'). User's own notes/summaries
708
+ * only — never carries secrets.
709
+ */
710
+ export interface GenomeEntry {
711
+ /** Stable identifier (derived from content/source; unique within the aggregate). */
712
+ id: string;
713
+ /** Project name this entry belongs to, or null when not project-scoped. */
714
+ project: string | null;
715
+ /** Where the entry came from. */
716
+ source: 'project' | 'hub';
717
+ /** Short human-readable title/heading for the entry. */
718
+ title: string;
719
+ /** Body text of the memory (the actual note/summary). */
720
+ text: string;
721
+ /** Free-form tags for filtering/grouping. */
722
+ tags: string[];
723
+ /** ISO timestamp the entry was created/learned. */
724
+ ts: string;
725
+ }
726
+ /** A single recall result: a genome entry plus its relevance score + method. */
727
+ export interface RecallHit {
728
+ /** The matched genome entry. */
729
+ entry: GenomeEntry;
730
+ /** Relevance score (higher is more relevant). */
731
+ score: number;
732
+ /** How the score was computed. */
733
+ method: 'keyword' | 'embedding';
734
+ }
735
+ /** Status/health roll-up for the aggregated genome (`ashlr genome`). */
736
+ export interface GenomeHealth {
737
+ /** Total entries across all sources (project + hub). */
738
+ totalEntries: number;
739
+ /** Number of distinct projects covered by the genome. */
740
+ projects: number;
741
+ /** Number of entries in the hub store (~/.ashlr/genome/hub.jsonl). */
742
+ hubEntries: number;
743
+ /** Total size in bytes of the hub store on disk. */
744
+ sizeBytes: number;
745
+ /** ISO timestamp of the most recently learned entry, or null if empty. */
746
+ lastLearnedAt: string | null;
747
+ /** Whether a local embedding-capable model is available for reranking. */
748
+ embeddingsAvailable: boolean;
749
+ }
750
+ /** Input accepted by `ashlr learn` / `appendHubEntry`. */
751
+ export interface LearnInput {
752
+ /** Body text of the memory to store (required). */
753
+ text: string;
754
+ /** Optional short title/heading (derived from text when omitted). */
755
+ title?: string;
756
+ /** Optional project name to scope the entry to. */
757
+ project?: string;
758
+ /** Optional tags to attach to the entry. */
759
+ tags?: string[];
760
+ /**
761
+ * When true, append to the hub store ONLY — never drop a note file into the
762
+ * resolved project's `.ashlrcode/genome/hub-notes/` working tree. M16
763
+ * auto-capture sets this so a completed run/swarm never emits a file inside
764
+ * the user's repo (which could be git-committed); the project-note drop is
765
+ * reserved for explicit `genome --teach` / `learn`.
766
+ */
767
+ hubOnly?: boolean;
768
+ }
769
+ /**
770
+ * A flat map of environment-variable name -> value. Used by the env-bridge
771
+ * (core/env-bridge.ts) to project the unified ~/.ashlr/config.json into the
772
+ * environment of spawned ecosystem tools. NON-SECRET ONLY — endpoints, model
773
+ * names, paths, and flags. Never carries secret VALUES (phantom owns secrets).
774
+ */
775
+ export type ToolEnv = Record<string, string>;
776
+ /**
777
+ * A single live event emitted during a run so the CLI can stream progress to
778
+ * the user as it happens (model deltas, task lifecycle, tool calls, retries,
779
+ * verification, free-form logs) instead of only printing at the end.
780
+ *
781
+ * METADATA + USER-FACING TEXT ONLY — never carries secret values.
782
+ */
783
+ export interface RunStreamEvent {
784
+ /** What kind of event this is. */
785
+ kind: 'task-start' | 'model-delta' | 'tool-call' | 'task-done' | 'retry' | 'verify' | 'log';
786
+ /** Id of the task this event belongs to, when applicable. */
787
+ taskId?: string;
788
+ /** Human-readable / model-delta text payload, when applicable. */
789
+ text?: string;
790
+ /** Structured payload (e.g. tool args, verdict, usage), when applicable. */
791
+ data?: unknown;
792
+ /** ISO timestamp the event was emitted. */
793
+ ts: string;
794
+ }
795
+ /** Bounded retry policy for a single task. Caps attempts and backoff base. */
796
+ export interface RetryPolicy {
797
+ /** Maximum number of attempts (>=1; total tries including the first). */
798
+ maxAttempts: number;
799
+ /** Base delay in ms for exponential backoff between attempts. */
800
+ baseDelayMs: number;
801
+ }
802
+ /** Verdict from verifying that a task result plausibly satisfies its goal. */
803
+ export interface VerifyVerdict {
804
+ /** Whether the result is judged to satisfy the task goal. */
805
+ ok: boolean;
806
+ /** One-line human-readable reason for the verdict. */
807
+ reason: string;
808
+ /** How the verdict was reached. */
809
+ method: 'heuristic' | 'model';
810
+ }
811
+ /** The set of engines `ashlr run` can delegate to (or run locally). */
812
+ export type EngineId = 'builtin' | 'ashlrcode' | 'aw' | 'claude';
813
+ /** A fully-resolved external engine invocation (exact argv + optional cwd). */
814
+ export interface EngineCommand {
815
+ /** Executable to spawn (e.g. 'claude', 'aw', 'ac', or 'phantom' when wrapped). */
816
+ bin: string;
817
+ /** Exact argument vector passed to `bin`. */
818
+ args: string[];
819
+ /** Working directory for the spawned process, when set. */
820
+ cwd?: string;
821
+ }
822
+ /**
823
+ * A first-class, versioned END-STATE SPEC artifact. The markdown body lives at
824
+ * <project>/.ashlr/specs/<slug>-v<N>.md with this sidecar metadata alongside
825
+ * it as <slug>-v<N>.json. Versioning is never destructive: refining produces
826
+ * a new version (v+1) rather than overwriting.
827
+ */
828
+ export interface SpecArtifact {
829
+ /** Stable spec id (slug derived from the goal; shared across versions). */
830
+ id: string;
831
+ /** The original authoring goal/prompt for the spec. */
832
+ goal: string;
833
+ /** Monotonic version number (1-based; refine produces v+1). */
834
+ version: number;
835
+ /** Absolute project path the spec is scoped to, or null when global. */
836
+ project: string | null;
837
+ /** Absolute path to the markdown body file for this version. */
838
+ path: string;
839
+ /** Lifecycle status of the spec. */
840
+ status: 'draft' | 'active' | 'archived';
841
+ /** ISO timestamp the spec (this version) was created. */
842
+ createdAt: string;
843
+ /** ISO timestamp the spec was last updated. */
844
+ updatedAt: string;
845
+ }
846
+ /**
847
+ * Tamper-evident signature over a swarm task's output.
848
+ * Contains ONLY hashes — never any payload secret. `hash` is a content digest
849
+ * of the signed text; `sig` is the keyed signature (HMAC or phantom-derived).
850
+ * `alg` records how it was produced; 'phantom' uses a phantom-sourced key
851
+ * best-effort, 'hmac-sha256' uses the local auto-generated key.
852
+ */
853
+ export interface OutputSignature {
854
+ /** Signing algorithm / key source. */
855
+ alg: 'hmac-sha256' | 'phantom';
856
+ /** Content digest (hex) of the signed text. */
857
+ hash: string;
858
+ /** Keyed signature (hex) over the content. NEVER a secret value. */
859
+ sig: string;
860
+ /** Opaque signer identity (e.g. 'local' or a phantom key id) — no secrets. */
861
+ signer: string;
862
+ /** ISO timestamp the signature was produced. */
863
+ ts: string;
864
+ }
865
+ /** Why a swarm escalation gate tripped. */
866
+ export type EscalationReasonKind = 'verify-failed' | 'over-budget' | 'tamper' | 'risk' | 'low-confidence';
867
+ /**
868
+ * A single escalation gate trip. The swarm persists this and STOPS
869
+ * (status 'needs-approval'); only an explicit `ashlr swarm approve <id>` resumes.
870
+ */
871
+ export interface EscalationEvent {
872
+ /** Task that triggered the gate, or null for swarm-level (e.g. over-budget). */
873
+ taskId: string | null;
874
+ /** Which gate tripped. */
875
+ kind: EscalationReasonKind;
876
+ /** Human-readable explanation (no secrets). */
877
+ detail: string;
878
+ /** ISO timestamp the gate tripped. */
879
+ ts: string;
880
+ }
881
+ /**
882
+ * Read-only git snapshot of a project, taken before a swarm operates in it.
883
+ * Used by the CONFIRM-gated `ashlr swarm rollback <id>`. NEVER carries secrets.
884
+ */
885
+ export interface RollbackSnapshot {
886
+ /** Absolute project path, or null when the swarm has no project. */
887
+ project: string | null;
888
+ /** Whether `project` is a git repository. */
889
+ isRepo: boolean;
890
+ /** Recorded HEAD commit sha, or null when not a repo / unresolved. */
891
+ head: string | null;
892
+ /**
893
+ * Branch name HEAD pointed at when the snapshot was taken, or null when HEAD
894
+ * was already detached / unresolved. Used so a non-force rollback can return
895
+ * the repo to the original branch rather than leaving it in detached HEAD.
896
+ */
897
+ branch?: string | null;
898
+ /** Whether the working tree was dirty at snapshot time. */
899
+ dirty: boolean;
900
+ /** Ref/name of the stash holding the dirty tree, or null when clean/none. */
901
+ stashRef: string | null;
902
+ /** ISO timestamp the snapshot was taken. */
903
+ ts: string;
904
+ }
905
+ /** The ordered phases of a contracts-first swarm. */
906
+ export type SwarmPhaseName = 'scaffold' | 'build' | 'integrate' | 'verify' | 'review';
907
+ /** A single planned task within a swarm phase (a unit of agent work). */
908
+ export interface SwarmTaskSpec {
909
+ /** Stable task id (unique within the swarm). */
910
+ id: string;
911
+ /** Which phase this task belongs to. */
912
+ phase: SwarmPhaseName;
913
+ /** The sub-goal this task must accomplish. */
914
+ goal: string;
915
+ /** Ids of tasks that must complete before this one runs. */
916
+ deps: string[];
917
+ }
918
+ /** Execution state of a single swarm task. */
919
+ export interface SwarmTaskRun {
920
+ /** Stable task id (matches its SwarmTaskSpec.id). */
921
+ id: string;
922
+ /** Which phase this task belongs to. */
923
+ phase: SwarmPhaseName;
924
+ /** Current lifecycle status. */
925
+ status: 'pending' | 'running' | 'done' | 'failed' | 'skipped';
926
+ /** Final task result text, present when done. */
927
+ result?: string;
928
+ /** Token/step usage attributed to this task. */
929
+ usage?: RunUsage;
930
+ /** Failure reason when status is 'failed', else absent. */
931
+ error?: string;
932
+ /**
933
+ * M17: tamper-evident signature over this task's `result`, computed when the
934
+ * task completes. Downstream tasks verify this before consuming the output.
935
+ */
936
+ signature?: OutputSignature;
937
+ /**
938
+ * M17: set true when a human explicitly approved this task past an escalation
939
+ * gate via `ashlr swarm approve <id>`. The runner SKIPS re-scanning this
940
+ * task's goal-risk on the resumed run so an approved goal-risk escalation
941
+ * does not re-trip the same gate and loop forever. Cleared/absent otherwise.
942
+ */
943
+ approved?: boolean;
944
+ }
945
+ /** The planned swarm: a decomposition of a goal/spec into phased tasks. */
946
+ export interface SwarmPlan {
947
+ /** Source spec id this plan derives from, or null when goal-only. */
948
+ specId: string | null;
949
+ /** The top-level goal the swarm pursues. */
950
+ goal: string;
951
+ /** All planned tasks across phases (caps tasks per phase <= 6). */
952
+ tasks: SwarmTaskSpec[];
953
+ }
954
+ /** Full persisted state of a swarm. Lives at ~/.ashlr/swarms/<id>.json. */
955
+ export interface SwarmRun {
956
+ /** Stable swarm id. */
957
+ id: string;
958
+ /** The original top-level goal. */
959
+ goal: string;
960
+ /** Source spec id, or null when goal-only. */
961
+ specId: string | null;
962
+ /** Absolute project path the swarm operates in, or null. */
963
+ project: string | null;
964
+ /** ISO timestamp the swarm was created. */
965
+ createdAt: string;
966
+ /** ISO timestamp of the last update (written after each step). */
967
+ updatedAt: string;
968
+ /** HARD total guardrails in effect across the whole swarm. */
969
+ budget: RunBudget;
970
+ /** Cumulative usage across all tasks (sum of per-task usage). */
971
+ usage: RunUsage;
972
+ /** Bounded concurrency for the parallel BUILD phase. */
973
+ parallel: number;
974
+ /**
975
+ * Current swarm status. M17 adds 'needs-approval': the swarm PAUSED at an
976
+ * escalation gate and STOPPED; an explicit `ashlr swarm approve <id>` resumes it.
977
+ */
978
+ status: 'planning' | 'running' | 'done' | 'aborted' | 'failed' | 'needs-approval';
979
+ /** The planned decomposition. */
980
+ plan: SwarmPlan;
981
+ /** Per-task execution state, in plan order. */
982
+ tasks: SwarmTaskRun[];
983
+ /** Aggregated final result/summary, present when done. */
984
+ result?: string;
985
+ /**
986
+ * M17: ordered log of escalation gate trips. Each entry records why the swarm
987
+ * paused (verify-failed, over-budget, tamper, risk, low-confidence). Append-only.
988
+ */
989
+ escalations?: EscalationEvent[];
990
+ /**
991
+ * M17: read-only git snapshot of the project taken before the swarm operated
992
+ * in it. Drives the CONFIRM-gated `ashlr swarm rollback <id>`. Absent if the
993
+ * swarm has no project or the project is not a git repo.
994
+ */
995
+ rollback?: RollbackSnapshot;
996
+ }
997
+ /** Options accepted by `runSwarm` / the `ashlr swarm` CLI. */
998
+ export interface SwarmOptions {
999
+ /** Partial budget overrides (merged over defaults) — the HARD total ceiling. */
1000
+ budget?: Partial<RunBudget>;
1001
+ /** Bounded concurrency for the BUILD phase (default 3, max 8). */
1002
+ parallel?: number;
1003
+ /** Launch a detached background worker and return the swarm id immediately. */
1004
+ background?: boolean;
1005
+ /** Existing swarm id to resume from persisted state. */
1006
+ resumeId?: string;
1007
+ /** Plan only — produce the SwarmPlan without executing any task. */
1008
+ dryRun?: boolean;
1009
+ /** Permit cloud providers for tasks (default false = local-first). */
1010
+ allowCloud?: boolean;
1011
+ /** Absolute target project directory the swarm operates in. */
1012
+ project?: string;
1013
+ /**
1014
+ * M17: when set alongside resumeId, resumes a swarm paused in 'needs-approval'
1015
+ * — set ONLY by `ashlr swarm approve <id>` (explicit human action). Threads the
1016
+ * approval into the runner so a goal-risk escalation can actually be cleared
1017
+ * (the runner skips re-scanning approved tasks). Never set on a fresh run.
1018
+ */
1019
+ approved?: boolean;
1020
+ /**
1021
+ * M21: when true, the swarm runs inside an isolated git-worktree sandbox
1022
+ * (created under ~/.ashlr/sandboxes/) instead of the user's working tree.
1023
+ * SEAM ONLY — plumbed here so a future daemon (M24) can wire it; defaults to
1024
+ * OFF, in which case the swarm behaves exactly as it does today.
1025
+ */
1026
+ sandbox?: boolean;
1027
+ /**
1028
+ * M24: when true (alongside sandbox), the swarm's captured patch is recorded
1029
+ * as a PENDING inbox proposal rather than left as a bare worktree diff. SEAM
1030
+ * ONLY — grants NO outward authority: a PENDING proposal is applied LATER only
1031
+ * by an explicit human `inbox approve`. Defaults to OFF.
1032
+ */
1033
+ propose?: boolean;
1034
+ /**
1035
+ * M24: when true (alongside sandbox), the sandbox is MANDATORY — if the
1036
+ * isolated git-worktree cannot be created (worktree module absent, source is
1037
+ * not a git repo, HEAD unresolvable, `git worktree add` fails, or a kill-switch
1038
+ * race), the swarm ABORTS with status 'failed' and executes ZERO tasks rather
1039
+ * than silently falling back to the user's working tree. The autonomous daemon
1040
+ * ALWAYS sets this so its work can NEVER touch a real repo's working tree.
1041
+ * Defaults to OFF (preserves the legacy non-strict fallback for non-daemon callers).
1042
+ */
1043
+ requireSandbox?: boolean;
1044
+ }
1045
+ /**
1046
+ * A single bounded, read-only aggregate of the whole hub at one instant.
1047
+ * Built from index/git, runs, swarms, the observability rollup, MCP health,
1048
+ * the ecosystem tools registry, and genome health. Drives every TUI tab and
1049
+ * the Raycast surfaces. NEVER throws — missing/unavailable sources degrade to
1050
+ * zeroed/empty fields. METADATA ONLY — never carries secret values.
1051
+ */
1052
+ export interface DashboardSnapshot {
1053
+ /** ISO timestamp the snapshot was generated. */
1054
+ generatedAt: string;
1055
+ /** Repo roll-up: total indexed repos, dirty working trees, stale/inactive. */
1056
+ repos: {
1057
+ total: number;
1058
+ dirty: number;
1059
+ stale: number;
1060
+ };
1061
+ /** Ecosystem tools roll-up: installed vs. total probed. */
1062
+ tools: {
1063
+ installed: number;
1064
+ total: number;
1065
+ };
1066
+ /** Activity roll-up over the dashboard window (sessions/tokens/cost/commits). */
1067
+ activity: {
1068
+ sessions: number;
1069
+ tokens: number;
1070
+ estCostUsd: number;
1071
+ commits: number;
1072
+ };
1073
+ /** Recent runs (most-recent first), each with status + cumulative tokens. */
1074
+ runs: {
1075
+ id: string;
1076
+ goal: string;
1077
+ status: string;
1078
+ tokens: number;
1079
+ }[];
1080
+ /** Active/recent swarms with live task burndown + optional current phase. */
1081
+ swarms: {
1082
+ id: string;
1083
+ goal: string;
1084
+ status: string;
1085
+ tasksDone: number;
1086
+ tasksTotal: number;
1087
+ phase?: string;
1088
+ }[];
1089
+ /** MCP server health: name, reachable/ok, and tool count. */
1090
+ mcp: {
1091
+ name: string;
1092
+ ok: boolean;
1093
+ tools: number;
1094
+ }[];
1095
+ /** Genome roll-up: total entries and distinct projects covered. */
1096
+ genome: {
1097
+ entries: number;
1098
+ projects: number;
1099
+ };
1100
+ /** M23: number of proposals awaiting Mason's approval in the inbox gate. */
1101
+ inbox: {
1102
+ pending: number;
1103
+ };
1104
+ /**
1105
+ * M24: autonomous-operator (daemon) roll-up. `running` reflects daemon state;
1106
+ * `todaySpentUsd` is the operator's spend so far today (resets per day);
1107
+ * `pendingProposals` mirrors inbox.pending for the daemon surface. READ-ONLY
1108
+ * — surfacing daemon status NEVER applies a proposal or mutates a repo.
1109
+ * Optional so existing snapshot producers stay valid until the M24 surface
1110
+ * populates it; absent => treat as not running / no daemon spend.
1111
+ */
1112
+ daemon?: {
1113
+ running: boolean;
1114
+ todaySpentUsd: number;
1115
+ pendingProposals: number;
1116
+ };
1117
+ /**
1118
+ * M29: OPTIONAL org-level portfolio roll-up. ABSENT on existing producers /
1119
+ * tests (so they stay valid); populated by buildSnapshot when the v2 sources
1120
+ * are present. READ-ONLY aggregation over already-local state — health (M27,
1121
+ * ENROLLMENT-SCOPED via computeReport), in-flight goals (M28), top backlog
1122
+ * (M22), cost+forecast (M19 rollup/forecast over the local index), and the
1123
+ * effectiveness headline (M26 reflect). Each sub-source degrades to its
1124
+ * empty/zeroed default on failure; an empty enrollment leaves the
1125
+ * enrollment-scoped sections empty with NO portfolio disk scan.
1126
+ */
1127
+ portfolio?: PortfolioSummary;
1128
+ }
1129
+ /**
1130
+ * The selectable tabs of the interactive TUI dashboard.
1131
+ *
1132
+ * M29 adds 'portfolio' — a READ-ONLY org-level surface rendered from the
1133
+ * optional `DashboardSnapshot.portfolio` section (health summary, in-flight
1134
+ * goals, top backlog, cost+forecast, effectiveness headline, and a "today"
1135
+ * delta block). The tab renders nothing destructive; it only displays the
1136
+ * already-aggregated read-only snapshot.
1137
+ */
1138
+ export type TuiTab = 'overview' | 'runs' | 'swarms' | 'pulse' | 'mcp' | 'inbox' | 'portfolio';
1139
+ /** Options controlling how the local web dashboard server starts. */
1140
+ export interface WebServerOptions {
1141
+ /** TCP port to bind on 127.0.0.1 (default chosen by the CLI, e.g. 7777). */
1142
+ port: number;
1143
+ /** Whether to open the default browser to the served URL after start. */
1144
+ open: boolean;
1145
+ /**
1146
+ * Whether to expose the guarded, token-protected mutating dispatch route
1147
+ * (POST /api/run). When false (the default), the server has NO mutating
1148
+ * endpoints — read-only API + SSE + static assets only.
1149
+ */
1150
+ allowDispatch: boolean;
1151
+ }
1152
+ /** A handle to a running web dashboard server. Returned by `startServer`. */
1153
+ export interface WebServerHandle {
1154
+ /** The actual port the server bound on 127.0.0.1. */
1155
+ port: number;
1156
+ /**
1157
+ * Per-session secret token. Printed by `ashlr serve` and REQUIRED (in a
1158
+ * request header) for the guarded POST /api/run dispatch route. Defeats
1159
+ * CSRF / drive-by POSTs. Empty/unused when allowDispatch is false.
1160
+ */
1161
+ token: string;
1162
+ /** The localhost URL the dashboard is served at (e.g. http://127.0.0.1:7777). */
1163
+ url: string;
1164
+ /** Stop the server cleanly (closes listeners + bounded SSE pollers). */
1165
+ close(): Promise<void>;
1166
+ }
1167
+ /** Whether a routed model runs LOCALLY (Ollama/LM Studio) or in the CLOUD. */
1168
+ export type ModelTier = 'local' | 'cloud';
1169
+ /** The router's decision for a single task attempt: which provider+model + why. */
1170
+ export interface RouteDecision {
1171
+ /** Provider id the task should run on (e.g. 'ollama', 'lmstudio', 'anthropic'). */
1172
+ provider: string;
1173
+ /** Concrete model id/name to use on that provider. */
1174
+ model: string;
1175
+ /** Whether this route is local-first ($0) or an escalated cloud route. */
1176
+ tier: ModelTier;
1177
+ /** One-line human-readable explanation of why this route was chosen. */
1178
+ reason: string;
1179
+ }
1180
+ /** A single per-task routing rule: match a goal/task to a preferred model. */
1181
+ export interface RoutingRule {
1182
+ /** Match expression against the task goal (substring/keyword/kind label). */
1183
+ match: string;
1184
+ /** Preferred model id/name when the rule matches. */
1185
+ model: string;
1186
+ }
1187
+ /** Why a task escalated (or 'none' when it is a normal first-attempt route). */
1188
+ export type EscalationReason = 'task-failed' | 'verify-failed' | 'latency' | 'none';
1189
+ /** A single local model discovered on Ollama or LM Studio. */
1190
+ export interface LocalModelInfo {
1191
+ /** Which local provider exposes this model. */
1192
+ provider: 'ollama' | 'lmstudio';
1193
+ /** Model name/id as reported by the provider's /api/tags (or equivalent). */
1194
+ name: string;
1195
+ /** Optional human-readable size label (e.g. '4.7 GB'), when available. */
1196
+ sizeLabel?: string;
1197
+ /** Whether this is the active/default model for its provider. */
1198
+ active: boolean;
1199
+ }
1200
+ /** Cost attribution + forward forecast for a recent usage window (M15). */
1201
+ export interface CostForecast {
1202
+ /** Window label the forecast is built from (e.g. '7d' | '30d'). */
1203
+ window: string;
1204
+ /** Actual USD spent in the window (local providers contribute $0). */
1205
+ spentUsd: number;
1206
+ /**
1207
+ * Estimated USD that the SAME local tokens WOULD have cost on cloud — the
1208
+ * savings from staying local. Clearly an estimate, never fabricated precision.
1209
+ */
1210
+ localSavingsUsd: number;
1211
+ /** Simple projected monthly USD spend extrapolated from the window's rate. */
1212
+ projectedMonthlyUsd: number;
1213
+ }
1214
+ /**
1215
+ * The summary-only payload captured from a completed run/swarm (or an explicit
1216
+ * teach) before it is appended to the genome. METADATA/SUMMARY ONLY — never
1217
+ * carries secrets, raw prompts/completions, tool args, or file contents.
1218
+ */
1219
+ export interface GenomeCapture {
1220
+ /** The top-level goal the run/swarm pursued (or the teach note's subject). */
1221
+ goal: string;
1222
+ /** Absolute project path this capture is scoped to, or null when global. */
1223
+ project: string | null;
1224
+ /** Concise approach/outcome summary (capped length; secret-free). */
1225
+ summary: string;
1226
+ /** Tags for filtering/grouping (e.g. project, status, engine, source). */
1227
+ tags: string[];
1228
+ /** Terminal outcome of the work being captured. */
1229
+ outcome: 'done' | 'aborted' | 'failed';
1230
+ /** Where the capture originated. */
1231
+ source: 'run' | 'swarm' | 'teach';
1232
+ }
1233
+ /**
1234
+ * A synthesized "how we've approached this before" playbook for a goal: the
1235
+ * recalled past entries plus a concise synthesis of what worked / what failed
1236
+ * / cost. Bounded; injected into planning context. LOCAL synthesis with a
1237
+ * concatenated-recall fallback.
1238
+ */
1239
+ export interface Playbook {
1240
+ /** The goal the playbook was built for. */
1241
+ goal: string;
1242
+ /** The recalled past entries the playbook synthesizes (ranked). */
1243
+ entries: RecallHit[];
1244
+ /** Concise synthesized guidance (what worked / failed / cost), or fallback. */
1245
+ synthesis: string;
1246
+ }
1247
+ /**
1248
+ * Outcome of `ashlr genome consolidate`. A timestamped backup of hub.jsonl is
1249
+ * written BEFORE any merge; near-duplicate entries are merged into canonical
1250
+ * entries that preserve provenance (count + first/last seen + merged tags) so
1251
+ * information is never irrecoverably dropped.
1252
+ */
1253
+ export interface ConsolidationResult {
1254
+ /** Entry count before consolidation. */
1255
+ before: number;
1256
+ /** Entry count after consolidation. */
1257
+ after: number;
1258
+ /** Number of entries merged away into canonical entries. */
1259
+ merged: number;
1260
+ /** Absolute path of the timestamped hub.jsonl backup written first. */
1261
+ backupPath: string;
1262
+ }
1263
+ /**
1264
+ * Read-only snapshot of the current repo's GitHub state (M18), derived from
1265
+ * the `gh` CLI. Surfaced in `ashlr status` when cwd is a gh repo. Never thrown
1266
+ * from a producer — degrades to a safe "not a repo / unknown" shape instead.
1267
+ */
1268
+ export interface GithubStatus {
1269
+ /** Whether cwd resolves to a GitHub repo reachable via `gh`. */
1270
+ isRepo: boolean;
1271
+ /** Count of open pull requests (0 when unknown/not a repo). */
1272
+ openPrs: number;
1273
+ /** Count of open issues (0 when unknown/not a repo). */
1274
+ openIssues: number;
1275
+ /** Aggregate CI/checks state for the default/most-recent ref. */
1276
+ ci: 'passing' | 'failing' | 'pending' | 'none';
1277
+ /** "owner/name" of the repo, or null when not a repo / unresolved. */
1278
+ repo: string | null;
1279
+ }
1280
+ /**
1281
+ * Read-only snapshot of the linked Vercel project's latest deploy (M18),
1282
+ * derived from the `vercel` CLI. Surfaced in `ashlr status` when a project is
1283
+ * linked. Producer must never throw — degrades to an "unlinked" shape.
1284
+ */
1285
+ export interface VercelStatus {
1286
+ /** Whether a Vercel project is linked for cwd. */
1287
+ linked: boolean;
1288
+ /** Latest deployment build state (e.g. "READY", "BUILDING"), or null. */
1289
+ latestState: string | null;
1290
+ /** Latest preview/deploy URL, or null when none/unlinked. */
1291
+ url: string | null;
1292
+ }
1293
+ /**
1294
+ * Read-only caller identity (M18), derived from `phantom` cloud status/team.
1295
+ * NAMES/status only — never secret values. Degrades to a logged-out shape when
1296
+ * phantom is absent or not logged in. Producer must never throw.
1297
+ */
1298
+ export interface Identity {
1299
+ /** Whether phantom reports an authenticated session. */
1300
+ loggedIn: boolean;
1301
+ /** Account id/handle, or null when logged out/unknown. */
1302
+ user: string | null;
1303
+ /** Tier/plan name, or null when logged out/unknown. */
1304
+ tier: string | null;
1305
+ /** Team name, or null when none/logged out/unknown. */
1306
+ team: string | null;
1307
+ }
1308
+ /**
1309
+ * Opt-in outward notification targets (M18). A webhook is a URL only — no
1310
+ * secret payloads. When unset, notify() is a strict no-op (never posts).
1311
+ */
1312
+ export interface NotifyTarget {
1313
+ /** Slack incoming-webhook URL. Posts a concise completion summary when set. */
1314
+ slackWebhook?: string;
1315
+ /** Discord webhook URL. Posts a concise completion summary when set. */
1316
+ discordWebhook?: string;
1317
+ /**
1318
+ * M32: macOS desktop notification on new PENDING proposals (osascript;
1319
+ * metadata only — never the diff). OPT-IN: strict no-op unless true.
1320
+ */
1321
+ desktop?: boolean;
1322
+ }
1323
+ /**
1324
+ * M19: one GenAI span derived from a completed run/swarm task. METADATA ONLY —
1325
+ * carries token counts, cost, ids, status, and timing; NEVER prompts,
1326
+ * completions, tool args, file contents, or secrets. The single normalized
1327
+ * shape that both the OTLP emitter and the local-file sink consume.
1328
+ */
1329
+ export interface GenAiSpan {
1330
+ /** Span name (e.g. the operation/task identifier — metadata, not content). */
1331
+ name: string;
1332
+ /** Owning run or swarm id this span belongs to. */
1333
+ runId: string;
1334
+ /** Model id used (maps to gen_ai.request.model). */
1335
+ model: string;
1336
+ /** Provider id used (maps to gen_ai.system). */
1337
+ provider: string;
1338
+ /** Routing tier (e.g. 'local' | 'cloud' or model-tier label). */
1339
+ tier: string;
1340
+ /** Prompt/input tokens (maps to gen_ai.usage.input_tokens). */
1341
+ tokensIn: number;
1342
+ /** Completion/output tokens (maps to gen_ai.usage.output_tokens). */
1343
+ tokensOut: number;
1344
+ /** Estimated USD cost for this span. */
1345
+ estCostUsd: number;
1346
+ /** Terminal status string (e.g. 'done' | 'failed' | 'aborted'). */
1347
+ status: string;
1348
+ /** ISO start timestamp. */
1349
+ startTs: string;
1350
+ /** ISO end timestamp. */
1351
+ endTs: string;
1352
+ }
1353
+ /**
1354
+ * M19: result of emitting spans through a TelemetrySink. Best-effort —
1355
+ * `ok:false` records a failure detail (logged to stderr only) and is NEVER
1356
+ * allowed to block or throw out of a run/swarm.
1357
+ */
1358
+ export interface TelemetryEmitResult {
1359
+ /** Which sink handled the emit. */
1360
+ sink: "local" | "otlp";
1361
+ /** Whether the emit succeeded (best-effort; failures never block). */
1362
+ ok: boolean;
1363
+ /** Human-readable detail — NEVER contains the PAT, prompts, or content. */
1364
+ detail: string;
1365
+ }
1366
+ /**
1367
+ * M19: spend-governance verdict for the configured budget window. Advisory by
1368
+ * default ('warn'); 'over' may require --over-budget when cfg.telemetry
1369
+ * govAction is 'block'. Governance NEVER silently blocks a run.
1370
+ */
1371
+ export interface GovernanceStatus {
1372
+ /** ok < 80% of cap, warn >= 80% of cap, over > cap. */
1373
+ level: "ok" | "warn" | "over";
1374
+ /** Spend (USD) over the window, from the forecast/rollup. */
1375
+ spentUsd: number;
1376
+ /** Configured spend cap (USD) for the window, or null when none is set. */
1377
+ capUsd: number | null;
1378
+ /** The budget window the verdict applies to (e.g. '7d'). */
1379
+ window: string;
1380
+ /** Human-readable summary — metadata only, never secrets. */
1381
+ message: string;
1382
+ }
1383
+ /**
1384
+ * M20: outcome of a single `doctor --fix` remediation attempt.
1385
+ *
1386
+ * One FixAction is produced per failing/warn DoctorCheck that `fixDoctor`
1387
+ * considers. SAFE + LOCAL + non-destructive only: create missing config from
1388
+ * defaults, rebuild a stale/missing index, create the ~/.local/bin symlink,
1389
+ * create the genome dir, register the ashlr MCP gateway (backup-first). NEVER
1390
+ * deletes/overwrites user data, NEVER auto-downloads models, NEVER touches
1391
+ * secrets. `applied` is whether the fix was performed; `manual` is true when
1392
+ * the check is fixable in principle but requires human action (left untouched).
1393
+ */
1394
+ export interface FixAction {
1395
+ /** DoctorCheck.id this action corresponds to (e.g. 'config', 'index', 'local-bin', 'genome-memory', 'mcp-plugin'). */
1396
+ checkId: string;
1397
+ /** Human-readable label for what was (or would be) fixed. */
1398
+ label: string;
1399
+ /** Whether a safe automated remediation was actually performed. */
1400
+ applied: boolean;
1401
+ /** One-line detail: what was fixed, or why it was left for manual action. */
1402
+ detail: string;
1403
+ /** True when the check needs manual/human action and was deliberately not auto-fixed. */
1404
+ manual: boolean;
1405
+ }
1406
+ /**
1407
+ * M20: status of a single onboarding step produced by `onboard`.
1408
+ *
1409
+ * 'ok' — already in the desired state / safe ensure succeeded.
1410
+ * 'wired' — a mutating wire step completed (e.g. editor MCP registered).
1411
+ * 'detected' — something was detected + reported, no mutation performed.
1412
+ * 'skipped' — step intentionally skipped (e.g. wire not requested).
1413
+ * 'manual' — step needs human action (printed as guidance, never auto-done).
1414
+ */
1415
+ export interface OnboardStep {
1416
+ /** Stable step name (e.g. 'config', 'models', 'editors', 'symlink', 'genome', 'phantom', 'doctor'). */
1417
+ name: string;
1418
+ /** Outcome of the step. */
1419
+ status: 'ok' | 'wired' | 'detected' | 'skipped' | 'manual';
1420
+ /** One-line human-readable detail — metadata only, never secrets. */
1421
+ detail: string;
1422
+ }
1423
+ /**
1424
+ * M20: full result of an idempotent, non-TTY-safe `ashlr init` onboarding run.
1425
+ */
1426
+ export interface OnboardResult {
1427
+ /** All onboarding steps performed, in display order. */
1428
+ steps: OnboardStep[];
1429
+ /** True when the setup is complete enough to run (no blocking failures). */
1430
+ ready: boolean;
1431
+ /** Crisp next-step guidance lines (e.g. 'try: ashlr run / ashlr swarm / ashlr tui'). */
1432
+ nextSteps: string[];
1433
+ }
1434
+ /**
1435
+ * M20: bounds for self-healing runtime wrappers. ALL heal behavior is bounded
1436
+ * by these caps — there is never an unbounded restart/downgrade/backoff loop.
1437
+ */
1438
+ export interface HealPolicy {
1439
+ /** Hard max number of heal-triggered retries (restart/downgrade/backoff). Bounded; never infinite. */
1440
+ maxRestarts: number;
1441
+ /** Whether OOM/model-error may downgrade to a SMALLER LOCAL model for a bounded retry. */
1442
+ allowDowngrade: boolean;
1443
+ }
1444
+ /**
1445
+ * M20: one self-heal event, surfaced to the caller's `onHeal` callback for
1446
+ * logging. Metadata only — never secrets.
1447
+ *
1448
+ * 'mcp-restart' — a crashed MCP downstream was restarted (extends M3 skip-on-failure).
1449
+ * 'model-downgrade' — a local model OOM/error downgraded to a smaller local model.
1450
+ * 'rate-backoff' — a cloud rate-limit triggered exponential backoff (only when allowCloud).
1451
+ */
1452
+ export interface HealEvent {
1453
+ /** What kind of heal occurred. */
1454
+ kind: 'mcp-restart' | 'model-downgrade' | 'rate-backoff';
1455
+ /** One-line human-readable detail — metadata only, never secrets. */
1456
+ detail: string;
1457
+ /** 1-based attempt number that triggered this heal event. */
1458
+ attempt: number;
1459
+ }
1460
+ /**
1461
+ * M21: one isolated git-worktree sandbox of a source repo. Created under
1462
+ * ~/.ashlr/sandboxes/<id>/ on a NEW scratch branch off the source repo's
1463
+ * current HEAD, so autonomous edits NEVER touch the user's working tree, index,
1464
+ * HEAD, or their checked-out branch. Bounded — created, used, then discarded
1465
+ * (git worktree remove + scratch-branch delete). METADATA ONLY.
1466
+ */
1467
+ export interface Sandbox {
1468
+ /** Opaque sandbox id; also the directory name under ~/.ashlr/sandboxes/. */
1469
+ id: string;
1470
+ /** Absolute path to the source repo this sandbox was forked from. */
1471
+ sourceRepo: string;
1472
+ /** Absolute path to the isolated worktree (~/.ashlr/sandboxes/<id>/). */
1473
+ worktreePath: string;
1474
+ /** Name of the scratch branch created for this sandbox (deleted on cleanup). */
1475
+ branch: string;
1476
+ /** The source repo HEAD commit the scratch branch was forked from. */
1477
+ baseHead: string;
1478
+ /** ISO timestamp the sandbox was created. */
1479
+ createdAt: string;
1480
+ /**
1481
+ * H5 — pid of the process that created this sandbox (a POSITIVE liveness
1482
+ * marker). The orphan sweep / disk-cap pre-sweep SKIP a sandbox whose
1483
+ * `ownerPid` is still alive (process.kill(pid,0) succeeds) regardless of age,
1484
+ * so a LIVE in-flight worktree is NEVER force-removed out from under a running
1485
+ * swarm — even a long-running cross-process one older than ORPHAN_STALE_MS.
1486
+ * Optional for back-compat: older metadata (and crash-simulation fixtures that
1487
+ * model a GONE owner) omit it, in which case the conservative createdAt-age
1488
+ * staleMs guard governs reclaim instead.
1489
+ */
1490
+ ownerPid?: number;
1491
+ }
1492
+ /**
1493
+ * M21: the captured result of work done inside a sandbox — the git diff of the
1494
+ * worktree vs. its base. This is what an autonomous run PROPOSES; proposal-only
1495
+ * is the default posture (nothing is applied to the source repo). METADATA +
1496
+ * patch text only.
1497
+ */
1498
+ export interface SandboxDiff {
1499
+ /** Id of the sandbox this diff was captured from. */
1500
+ sandboxId: string;
1501
+ /** Number of files changed. */
1502
+ files: number;
1503
+ /** Total inserted lines across the diff. */
1504
+ insertions: number;
1505
+ /** Total deleted lines across the diff. */
1506
+ deletions: number;
1507
+ /** The unified diff patch text (git diff output). */
1508
+ patch: string;
1509
+ }
1510
+ /**
1511
+ * M21: one append-only audit record of an autonomous/sandbox action. Written to
1512
+ * ~/.ashlr/audit/<date>.jsonl — never deleted, never holds secrets. Read back
1513
+ * via `ashlr audit`.
1514
+ */
1515
+ export interface AuditEntry {
1516
+ /** ISO timestamp the action occurred (set by `audit()`, not the caller). */
1517
+ ts: string;
1518
+ /** Short action verb, e.g. 'sandbox.create', 'enroll.add', 'kill.set'. */
1519
+ action: string;
1520
+ /** Absolute source repo path the action concerned, or null if not repo-scoped. */
1521
+ repo: string | null;
1522
+ /** Sandbox id the action concerned, or null if not sandbox-scoped. */
1523
+ sandboxId: string | null;
1524
+ /** One-line human-readable summary — metadata only, never secrets. */
1525
+ summary: string;
1526
+ /** Outcome of the action. */
1527
+ result: 'ok' | 'refused' | 'error';
1528
+ }
1529
+ /**
1530
+ * M21: the enrollment registry — which repos are ENROLLED for autonomous work.
1531
+ * DEFAULT EMPTY: nothing enrolled => nothing autonomous can mutate any real
1532
+ * repo. Persisted in cfg.autonomy / ~/.ashlr/enrollment.json.
1533
+ */
1534
+ export interface Enrollment {
1535
+ /** Absolute paths of repos enrolled for autonomous/sandbox mutation. */
1536
+ repos: string[];
1537
+ }
1538
+ /**
1539
+ * M22: WORK DISCOVERY — `ashlr backlog`.
1540
+ * A scored, prioritized work queue derived READ-ONLY across ENROLLED repos.
1541
+ */
1542
+ /** The kind of source a WorkItem was derived from. */
1543
+ export type WorkSource = 'issue' | 'todo' | 'test' | 'dep' | 'doc' | 'security' | 'plugin';
1544
+ /**
1545
+ * A single discovered, scored unit of work. Produced by a scanner over a
1546
+ * single enrolled repo. Contains NO secrets. Pure analysis — never implies a
1547
+ * mutation was performed.
1548
+ */
1549
+ export interface WorkItem {
1550
+ /** Stable, deterministic id (e.g. `${repo}:${source}:${hash}`). */
1551
+ id: string;
1552
+ /** Absolute path of the enrolled repo this item belongs to. */
1553
+ repo: string;
1554
+ /** Which scanner produced this item. */
1555
+ source: WorkSource;
1556
+ /** Short, human-readable title. */
1557
+ title: string;
1558
+ /** Longer detail / context (no secrets). */
1559
+ detail: string;
1560
+ /** Estimated value of doing the work, 1 (low) .. 5 (high). */
1561
+ value: number;
1562
+ /** Estimated effort to do the work, 1 (low) .. 5 (high). */
1563
+ effort: number;
1564
+ /** Priority score; higher = do first. score = scoreItem(value, effort). */
1565
+ score: number;
1566
+ /** Free-form tags (e.g. ['security','npm-audit']). */
1567
+ tags: string[];
1568
+ /** ISO timestamp this item was generated. */
1569
+ ts: string;
1570
+ }
1571
+ /**
1572
+ * The aggregated, persisted backlog. Written to ~/.ashlr/backlog.json by
1573
+ * buildBacklog(). Covers only ENROLLED repos (DEFAULT EMPTY => empty items).
1574
+ */
1575
+ export interface Backlog {
1576
+ /** ISO timestamp the backlog was generated. */
1577
+ generatedAt: string;
1578
+ /** Absolute paths of the repos that were scanned. */
1579
+ repos: string[];
1580
+ /** All discovered work items, deduped and scored. */
1581
+ items: WorkItem[];
1582
+ }
1583
+ /**
1584
+ * M23: what kind of outward action a Proposal represents.
1585
+ * 'patch' — a unified diff to be applied on a NEW branch in the target repo.
1586
+ * 'pr' — a branch+commit then a gated `gh pr create` (the M18 createPr).
1587
+ * 'deploy' — the gated ship/deploy path.
1588
+ * 'note' — a no-op record (decision/observation only; never mutates).
1589
+ */
1590
+ export type ProposalKind = 'patch' | 'pr' | 'deploy' | 'note';
1591
+ /**
1592
+ * M23: lifecycle of a Proposal through the approval inbox gate.
1593
+ * 'pending' — created, awaiting Mason's explicit decision (NEVER auto-applies).
1594
+ * 'approved' — Mason approved; eligible for applyProposal (still confirm-gated).
1595
+ * 'rejected' — Mason rejected; discarded, never applied.
1596
+ * 'applied' — the approved outward action was performed successfully.
1597
+ * 'failed' — apply was attempted (approved+confirmed) but errored.
1598
+ */
1599
+ export type ProposalStatus = 'pending' | 'approved' | 'rejected' | 'applied' | 'failed';
1600
+ /**
1601
+ * M23: a single PROPOSED outward action awaiting Mason's approval. The inbox is
1602
+ * the SINGLE human control plane through which EVERY outward mutation (PR, merge,
1603
+ * deploy, patch-applied-to-a-real-branch) must pass. The autonomous org (M24+)
1604
+ * creates these; nothing outward happens until Mason explicitly approves.
1605
+ * Persisted at ~/.ashlr/inbox/<id>.json. METADATA + diff/patch text only —
1606
+ * NEVER carries secret values.
1607
+ */
1608
+ export interface Proposal {
1609
+ /** Stable unique id; also the inbox filename stem (~/.ashlr/inbox/<id>.json). */
1610
+ id: string;
1611
+ /** Absolute path of the target repo, or null when not repo-scoped (e.g. note). */
1612
+ repo: string | null;
1613
+ /**
1614
+ * Where the proposal came from: the backlog, an autonomous swarm, manual
1615
+ * creation, or an agent session via the native MCP tool `ashlr_inbox_propose`
1616
+ * (M31). Agent-originated proposals are created 'pending' like every other —
1617
+ * the origin tag exists so the inbox can display provenance.
1618
+ */
1619
+ origin: 'backlog' | 'swarm' | 'manual' | 'agent';
1620
+ /** What kind of outward action this represents. */
1621
+ kind: ProposalKind;
1622
+ /** Short human-readable title for inbox lists. */
1623
+ title: string;
1624
+ /** Longer human-readable summary of what + why. */
1625
+ summary: string;
1626
+ /** Optional unified diff (from a sandbox) — the patch a 'patch'/'pr' applies. */
1627
+ diff?: string;
1628
+ /** Optional id of the sandbox the diff was captured from (M21). */
1629
+ sandboxId?: string;
1630
+ /** Current lifecycle status. Created as 'pending'; NEVER auto-advances. */
1631
+ status: ProposalStatus;
1632
+ /** ISO timestamp the proposal was created. */
1633
+ createdAt: string;
1634
+ /** ISO timestamp Mason approved/rejected (set on the decision). */
1635
+ decidedAt?: string;
1636
+ /** Outcome detail recorded by applyProposal (branch name, PR url, error). */
1637
+ result?: string;
1638
+ }
1639
+ /**
1640
+ * M23: outcome of applyProposal — the ONLY outward path. Never thrown; failure
1641
+ * is reported here with status 'failed' and a detail. METADATA ONLY — no secrets.
1642
+ */
1643
+ export interface ApplyResult {
1644
+ /** True only when the outward action completed successfully. */
1645
+ ok: boolean;
1646
+ /** The resulting proposal status: 'applied' on success, 'failed' otherwise. */
1647
+ status: ProposalStatus;
1648
+ /** Human-readable detail (branch created, PR url, refusal reason, error). */
1649
+ detail: string;
1650
+ }
1651
+ /**
1652
+ * M24: bounding configuration for the autonomous operator. Every field caps HOW
1653
+ * MUCH the daemon may propose — none of them grant any outward authority (the
1654
+ * daemon is proposal-only by construction). Sourced from cfg.daemon (partial),
1655
+ * merged over conservative hard-coded defaults.
1656
+ */
1657
+ export interface DaemonConfig {
1658
+ /** HARD daily spend ceiling (USD). When today's spend reaches it, the daemon
1659
+ * idles/stops. Resets per calendar day. Default modest. */
1660
+ dailyBudgetUsd: number;
1661
+ /** Max number of backlog items processed per tick (per-tick item cap). */
1662
+ perTickItems: number;
1663
+ /** Bounded concurrency: max sandboxed swarms run simultaneously in a tick. */
1664
+ parallel: number;
1665
+ /** Interval between ticks in `daemon start` loop mode (ms). */
1666
+ intervalMs: number;
1667
+ }
1668
+ /**
1669
+ * M24: the record of a single operator cycle (one `tick`). Pure accounting of
1670
+ * what was considered + proposed + spent. Creating a tick NEVER applies anything.
1671
+ */
1672
+ export interface DaemonTick {
1673
+ /** ISO timestamp the tick ran. */
1674
+ ts: string;
1675
+ /** How many backlog items were considered (post budget/cap selection). */
1676
+ itemsConsidered: number;
1677
+ /** How many PENDING proposals this tick created in the inbox. */
1678
+ proposalsCreated: number;
1679
+ /** Estimated USD spent during this tick. */
1680
+ spentUsd: number;
1681
+ /** Why the tick did what it did (e.g. 'ok', 'kill-switch', 'budget-exhausted',
1682
+ * 'no-enrolled-repos', 'no-backlog', 'dry-run'). */
1683
+ reason: string;
1684
+ }
1685
+ /**
1686
+ * M24: persisted daemon state at ~/.ashlr/daemon.json. Tracks run/loop status,
1687
+ * today's spend (reset per day), cumulative items processed, and recent ticks.
1688
+ * METADATA ONLY — no secrets, no diffs. Mutating this NEVER mutates a user repo.
1689
+ */
1690
+ export interface DaemonState {
1691
+ /** Whether the daemon loop is currently running. */
1692
+ running: boolean;
1693
+ /** OS pid of the running daemon process, or null when not running. */
1694
+ pid: number | null;
1695
+ /** ISO timestamp the current/last run started, or null. */
1696
+ startedAt: string | null;
1697
+ /** ISO timestamp of the most recent tick, or null. */
1698
+ lastTickAt: string | null;
1699
+ /** Calendar day (YYYY-MM-DD) the spend counters apply to; null until first tick. */
1700
+ todayDate: string | null;
1701
+ /** Estimated USD spent so far today; reset when todayDate rolls over. */
1702
+ todaySpentUsd: number;
1703
+ /** Cumulative count of backlog items processed across all ticks. */
1704
+ itemsProcessed: number;
1705
+ /** Bounded history of recent ticks (most-recent last). */
1706
+ ticks: DaemonTick[];
1707
+ }
1708
+ /**
1709
+ * M25 (Portfolio Intelligence): a single chunk of source extracted from an
1710
+ * ENROLLED repo during a read-only knowledge walk. Persisted as JSONL under
1711
+ * `~/.ashlr/knowledge/<repo-hash>/*.jsonl`. Secrets are scrubbed BEFORE a chunk
1712
+ * is created/embedded; no chunk ever contains .env contents or secret-shaped
1713
+ * tokens. `vector` is present only when local Ollama embeddings succeeded;
1714
+ * otherwise retrieval falls back to keyword/TF-IDF scoring over `text`.
1715
+ */
1716
+ export interface KnowledgeChunk {
1717
+ /** Absolute path of the enrolled repo this chunk came from. */
1718
+ repo: string;
1719
+ /** Repo-relative path of the source file. */
1720
+ file: string;
1721
+ /** 1-based first line of the chunk span within the file. */
1722
+ startLine: number;
1723
+ /** 1-based last line of the chunk span within the file (inclusive). */
1724
+ endLine: number;
1725
+ /** Scrubbed source text of the chunk (no secrets). */
1726
+ text: string;
1727
+ /** Local embedding vector, present only when Ollama embeddings succeeded. */
1728
+ vector?: number[];
1729
+ /** Optional short local-model summary of the chunk. */
1730
+ summary?: string;
1731
+ }
1732
+ /**
1733
+ * M25: one retrieved chunk plus its relevance score for an `ashlr ask` query.
1734
+ * Score is cosine similarity (embedding path) or normalized keyword/TF-IDF
1735
+ * score (fallback path); higher is more relevant.
1736
+ */
1737
+ export interface AskHit {
1738
+ /** The retrieved knowledge chunk. */
1739
+ chunk: KnowledgeChunk;
1740
+ /** Relevance score (embedding cosine or keyword score); higher = better. */
1741
+ score: number;
1742
+ }
1743
+ /**
1744
+ * M25: result of `ashlr ask "<question>"` — a LOCAL RAG answer synthesized from
1745
+ * retrieved portfolio chunks with explicit source citations. `local` MUST be
1746
+ * true unless --allow-cloud was explicitly passed AND a key exists; the default
1747
+ * path keeps all private code on the machine.
1748
+ */
1749
+ export interface AskResult {
1750
+ /** The original question text. */
1751
+ question: string;
1752
+ /** Synthesized natural-language answer. */
1753
+ answer: string;
1754
+ /** Cited sources backing the answer (repo / file:line). */
1755
+ sources: {
1756
+ repo: string;
1757
+ file: string;
1758
+ line: number;
1759
+ }[];
1760
+ /** Retrieval method used: local embeddings or keyword/TF-IDF fallback. */
1761
+ method: "embedding" | "keyword";
1762
+ /** True when synthesis ran entirely on the LOCAL model (no code sent to cloud). */
1763
+ local: boolean;
1764
+ }
1765
+ /**
1766
+ * M25: result of `ashlr impact <file|symbol>` — where a target is referenced
1767
+ * and what depends on it, within and across ENROLLED repos. Pure read-only
1768
+ * analysis; never mutates a repo.
1769
+ */
1770
+ export interface ImpactResult {
1771
+ /** The file path or symbol that was analyzed. */
1772
+ target: string;
1773
+ /** Locations that reference the target (repo / file:line). */
1774
+ references: {
1775
+ repo: string;
1776
+ file: string;
1777
+ line: number;
1778
+ }[];
1779
+ /** Identifiers (repo/module/dep node ids) that depend on the target. */
1780
+ dependents: string[];
1781
+ }
1782
+ /**
1783
+ * M25: a lightweight cross-portfolio knowledge graph over ENROLLED repos.
1784
+ * Nodes are repos/modules/key deps; edges capture imports/depends/shared-dep
1785
+ * relationships. `crossRepo` surfaces signals spanning repos (e.g. the same
1786
+ * outdated/vulnerable dependency, or a duplicated pattern). Built read-only.
1787
+ */
1788
+ export interface KnowledgeGraph {
1789
+ /** Graph nodes: repos, modules, and key dependencies. */
1790
+ nodes: {
1791
+ id: string;
1792
+ kind: string;
1793
+ label: string;
1794
+ }[];
1795
+ /** Directed edges between nodes (imports / depends / shared-dep). */
1796
+ edges: {
1797
+ from: string;
1798
+ to: string;
1799
+ kind: string;
1800
+ }[];
1801
+ /** Cross-repo findings (shared/duplicated deps or patterns) and the repos involved. */
1802
+ crossRepo: {
1803
+ kind: string;
1804
+ detail: string;
1805
+ repos: string[];
1806
+ }[];
1807
+ }
1808
+ /**
1809
+ * M26: one clustered failure mode distilled from failed/aborted swarms and
1810
+ * failed tasks. Built deterministically by clustering normalized task.error
1811
+ * strings / failed phase names. No LLM. METADATA ONLY.
1812
+ */
1813
+ export interface FailureMode {
1814
+ /** Stable cluster key (normalized error signature or phase name). */
1815
+ key: string;
1816
+ /** Human-readable label for the cluster. */
1817
+ label: string;
1818
+ /** Number of failed tasks/swarms that fell into this cluster. */
1819
+ count: number;
1820
+ /** Which swarm phase(s) this failure most often occurred in. */
1821
+ phases: string[];
1822
+ /** A few representative swarm ids exhibiting this failure (bounded sample). */
1823
+ exampleSwarmIds: string[];
1824
+ }
1825
+ /**
1826
+ * M26: per-goal-category aggregation — the slowest / most-expensive kinds of
1827
+ * work, derived deterministically by bucketing swarm goals into coarse
1828
+ * categories (keyword heuristic). No LLM. METADATA ONLY.
1829
+ */
1830
+ export interface GoalCategoryStat {
1831
+ /** Coarse category label (e.g. 'refactor', 'feature', 'bugfix', 'docs', 'other'). */
1832
+ category: string;
1833
+ /** Number of swarms in this category within the window. */
1834
+ swarms: number;
1835
+ /** Mean estimated USD cost per swarm in this category. */
1836
+ avgCostUsd: number;
1837
+ /** Mean total tokens (in+out) per swarm in this category. */
1838
+ avgTokens: number;
1839
+ /** Success rate (done / total) for this category, 0..1. */
1840
+ successRate: number;
1841
+ }
1842
+ /**
1843
+ * M26: week-over-week (snapshot-over-snapshot) deltas vs the previous persisted
1844
+ * ReflectionReport. All deltas are computed deterministically by diffing the
1845
+ * current metrics against the prior snapshot loaded from
1846
+ * ~/.ashlr/learn/reports/. Absent fields => no prior snapshot to compare.
1847
+ */
1848
+ export interface ReflectionDelta {
1849
+ /** ISO timestamp of the prior snapshot this delta compares against, or null. */
1850
+ previousAt: string | null;
1851
+ /**
1852
+ * Change in effectiveness, expressed as a signed percentage-point delta of
1853
+ * success rate (e.g. +12 means "12 points more effective"). null when no prior.
1854
+ */
1855
+ effectivenessPct: number | null;
1856
+ /**
1857
+ * Change in average cost per swarm, expressed as a signed percentage
1858
+ * (e.g. -18 means "18% cheaper"). null when no prior.
1859
+ */
1860
+ costPct: number | null;
1861
+ /** Signed percentage-point change in local-vs-cloud share. null when no prior. */
1862
+ localSharePct: number | null;
1863
+ /** One-line human summary of the headline movements (deterministic template). */
1864
+ headline: string;
1865
+ }
1866
+ /**
1867
+ * M26: the deterministic reflection report. Persisted as a snapshot under
1868
+ * ~/.ashlr/learn/reports/<ts>.json and used as the prior for the next run's
1869
+ * week-over-week deltas. Computed entirely WITHOUT an LLM (an optional
1870
+ * narrative field may be added later by playbooks.ts, but is never required).
1871
+ * METADATA ONLY — never carries secret values or raw code/payloads.
1872
+ */
1873
+ export interface ReflectionReport {
1874
+ /** ISO timestamp the report was generated. */
1875
+ generatedAt: string;
1876
+ /** ISO lower bound of the analysis window (inclusive). */
1877
+ since: string;
1878
+ /** Window label when derived from --since (e.g. '7d'/'30d'), else null. */
1879
+ window: string | null;
1880
+ /** How many swarms were actually read (bounded by maxRuns/since). */
1881
+ swarmsAnalyzed: number;
1882
+ /** Count of swarms with status 'done'. */
1883
+ swarmsDone: number;
1884
+ /** Count of swarms with status 'failed' or 'aborted'. */
1885
+ swarmsFailed: number;
1886
+ /** Success rate: swarmsDone / swarmsAnalyzed (0..1; 0 when none). */
1887
+ successRate: number;
1888
+ /** Mean estimated USD cost per analyzed swarm. */
1889
+ avgCostUsd: number;
1890
+ /** Mean total tokens (in+out) per analyzed swarm. */
1891
+ avgTokens: number;
1892
+ /** Total estimated USD cost across analyzed swarms. */
1893
+ totalCostUsd: number;
1894
+ /** Share of token usage served by LOCAL providers (0..1) from usage events. */
1895
+ localShare: number;
1896
+ /** Top clustered failure modes, most frequent first (bounded). */
1897
+ topFailures: FailureMode[];
1898
+ /** Slowest / most-expensive goal categories, most-expensive first (bounded). */
1899
+ goalCategories: GoalCategoryStat[];
1900
+ /** Week-over-week deltas vs the prior snapshot (templated, deterministic). */
1901
+ delta: ReflectionDelta;
1902
+ /** Genome health snapshot at report time (entry counts etc.). */
1903
+ genome: GenomeHealth;
1904
+ /**
1905
+ * Optional LLM-assisted narrative summary. ABSENT on the default path
1906
+ * (deterministic-only). Populated ONLY when narrative generation is requested
1907
+ * and a provider is reachable (local unless --allow-cloud + key). When set,
1908
+ * `narrativeLocal` records whether it was produced by a local model.
1909
+ */
1910
+ narrative?: string;
1911
+ /** True when `narrative` was produced by a LOCAL model; absent when no narrative. */
1912
+ narrativeLocal?: boolean;
1913
+ }
1914
+ /**
1915
+ * M26: a single PROPOSAL-ONLY tuning suggestion derived from a ReflectionReport.
1916
+ * These NEVER auto-apply: emitTuningProposals() routes each one to the M23
1917
+ * Approval Inbox as a PENDING proposal (kind 'note' — a no-op record that
1918
+ * mutates nothing), or the report prints them. There is NO code path that writes
1919
+ * config.json / router policy / prompts. METADATA ONLY.
1920
+ */
1921
+ export interface TuningProposal {
1922
+ /** Stable suggestion key (e.g. 'routing.local-first-threshold'). */
1923
+ key: string;
1924
+ /** What aspect this suggestion concerns (purely descriptive; never applied). */
1925
+ area: 'routing' | 'policy' | 'prompt' | 'playbook';
1926
+ /** Short human-readable title for the inbox / report. */
1927
+ title: string;
1928
+ /** Longer rationale grounded in the report's deterministic metrics. */
1929
+ rationale: string;
1930
+ /** Confidence in the suggestion (0..1), derived from sample size / effect. */
1931
+ confidence: number;
1932
+ }
1933
+ /** Options accepted by `buildReflection` (the deterministic metrics engine). */
1934
+ export interface ReflectionOptions {
1935
+ /** Analyze only swarms created at/after this epoch-ms lower bound. */
1936
+ sinceMs?: number;
1937
+ /** Hard cap on how many recent swarms to read (bounds I/O). */
1938
+ maxRuns?: number;
1939
+ /** Window label to record on the report (purely informational). */
1940
+ window?: string | null;
1941
+ }
1942
+ /**
1943
+ * M27: the quality dimensions a HealthScore is decomposed into. Each maps
1944
+ * naturally onto one M22 scanner (tests/docs/deps/security/code-debt/issues-CI)
1945
+ * plus a `conventions` dimension fed by the read-only convention probes.
1946
+ */
1947
+ export type HealthDimension = 'tests' | 'docs' | 'deps' | 'security' | 'codeDebt' | 'issuesCi' | 'conventions';
1948
+ /** A letter grade derived deterministically from a 0..100 score. */
1949
+ export type HealthGrade = 'A' | 'B' | 'C' | 'D' | 'F';
1950
+ /**
1951
+ * M27: a single read-only project-standards probe result for one repo.
1952
+ * Produced by conventions.ts via pure FS reads (presence/size checks). Carries
1953
+ * NO secrets and NEVER implies a mutation was performed.
1954
+ */
1955
+ export interface ConventionFinding {
1956
+ /** Stable probe key, e.g. 'license' | 'gitignore' | 'lockfile' | 'ci' | 'readme' | 'testdir'. */
1957
+ key: string;
1958
+ /** Short human-readable label for the probe (e.g. 'LICENSE file'). */
1959
+ label: string;
1960
+ /** True when the convention is satisfied (e.g. the file/dir/script exists). */
1961
+ ok: boolean;
1962
+ /** Severity weight of a MISS (1 low .. 5 high); ignored when ok=true. */
1963
+ weight: number;
1964
+ /** Longer detail / remediation hint (no secrets). */
1965
+ detail: string;
1966
+ }
1967
+ /**
1968
+ * M27: the per-dimension contribution to a repo's HealthScore. Deterministic.
1969
+ */
1970
+ export interface HealthDimensionScore {
1971
+ /** Which dimension this entry scores. */
1972
+ dimension: HealthDimension;
1973
+ /** Normalized dimension score, 0 (worst) .. 100 (best). */
1974
+ score: number;
1975
+ /** Relative weight of this dimension in the overall 0..100 roll-up. */
1976
+ weight: number;
1977
+ /** Count of underlying findings (WorkItems / failed convention probes) feeding it. */
1978
+ findingCount: number;
1979
+ /** Short, deterministic human-readable summary line (no secrets). */
1980
+ summary: string;
1981
+ }
1982
+ /**
1983
+ * M27: the per-repo HEALTH SCORE. Produced by computeHealth(repo) from the six
1984
+ * M22 scanners + conventions probes. Deterministic, READ-ONLY, NO LLM.
1985
+ * METADATA ONLY — never carries secret values.
1986
+ */
1987
+ export interface HealthScore {
1988
+ /** Absolute path of the enrolled repo this score belongs to. */
1989
+ repo: string;
1990
+ /** Weighted overall score, 0 (worst) .. 100 (best). */
1991
+ score: number;
1992
+ /** Letter grade derived from `score` (A>=90, B>=80, C>=70, D>=60, else F). */
1993
+ grade: HealthGrade;
1994
+ /** Per-dimension breakdown (one entry per HealthDimension). */
1995
+ dimensions: HealthDimensionScore[];
1996
+ /** Convention probe results for this repo (read-only FS probes). */
1997
+ conventions: ConventionFinding[];
1998
+ /**
1999
+ * The worst offenders — the highest-priority WorkItems (by WorkItem.score)
2000
+ * dragging this repo's grade down, bounded to a small cap. METADATA ONLY.
2001
+ */
2002
+ worstOffenders: WorkItem[];
2003
+ /** ISO timestamp this score was computed. */
2004
+ ts: string;
2005
+ }
2006
+ /**
2007
+ * M27: the portfolio-wide HEALTH REPORT. Produced by computeReport({ repos? })
2008
+ * over enrolled repos (default listEnrolled(); explicit repos filtered through
2009
+ * isEnrolled()). Persisted under ~/.ashlr/quality/ for trend tracking.
2010
+ * METADATA ONLY — never carries secret values.
2011
+ */
2012
+ export interface HealthReport {
2013
+ /** ISO timestamp the report was generated. */
2014
+ generatedAt: string;
2015
+ /** Absolute paths of the enrolled repos that were scored. */
2016
+ repos: string[];
2017
+ /** Per-repo scores, ranked worst-first (lowest score first) by default. */
2018
+ scores: HealthScore[];
2019
+ /** Mean overall score across all scored repos (0..100), or 0 when empty. */
2020
+ averageScore: number;
2021
+ /** Letter grade derived from `averageScore`. */
2022
+ averageGrade: HealthGrade;
2023
+ /**
2024
+ * Per-repo overall-score delta vs the previous persisted report
2025
+ * (loadPreviousReport), keyed by absolute repo path. Positive = improved.
2026
+ * Absent entries have no prior snapshot to compare against.
2027
+ */
2028
+ delta: Record<string, number>;
2029
+ /**
2030
+ * Optional LLM-assisted narrative summary. ABSENT on the default path
2031
+ * (deterministic-only). Populated ONLY when narrative generation is requested
2032
+ * and a provider is reachable (local unless --allow-cloud + key). When set,
2033
+ * `narrativeLocal` records whether it was produced by a local model.
2034
+ */
2035
+ narrative?: string;
2036
+ /** True when `narrative` was produced by a LOCAL model; absent when no narrative. */
2037
+ narrativeLocal?: boolean;
2038
+ }
2039
+ /**
2040
+ * M27: a single deterministic, advisory SAFE FIX derived from a HealthScore's
2041
+ * findings (e.g. "add a LICENSE", "add .gitignore", "pin/upgrade vulnerable dep
2042
+ * X", "add a test for Y"). emitFixProposals() routes each to the M23 Approval
2043
+ * Inbox as a PENDING proposal (kind 'note' by default — a no-op advisory record
2044
+ * that mutates nothing; origin 'manual'). M27 NEVER auto-applies a fix and NEVER
2045
+ * mutates a repo. METADATA ONLY.
2046
+ */
2047
+ export interface SafeFix {
2048
+ /** Absolute path of the repo this fix targets. */
2049
+ repo: string;
2050
+ /** The dimension this fix improves. */
2051
+ dimension: HealthDimension;
2052
+ /** Stable fix key (e.g. 'docs.add-license', 'conventions.add-gitignore'). */
2053
+ key: string;
2054
+ /** Short human-readable title for the inbox / report. */
2055
+ title: string;
2056
+ /** Longer rationale grounded in the repo's deterministic findings (no secrets). */
2057
+ rationale: string;
2058
+ /**
2059
+ * Whether this fix is purely advisory ('note') or could carry a deterministic
2060
+ * sandbox-generated diff ('patch'). Default 'note'; 'patch' is a documented
2061
+ * STRETCH only — any diff MUST be produced in an M21 sandbox worktree and
2062
+ * attached as a PENDING proposal, NEVER written to the real tree.
2063
+ */
2064
+ proposalKind: Extract<ProposalKind, 'note' | 'patch'>;
2065
+ }
2066
+ /** Options accepted by `computeReport` (the deterministic health engine). */
2067
+ export interface HealthOptions {
2068
+ /**
2069
+ * Explicit repo list. When provided, EACH entry MUST be filtered through
2070
+ * isEnrolled() (resolve() first) — non-enrolled paths HARD-ERROR. When
2071
+ * omitted, defaults to listEnrolled() (DEFAULT EMPTY => empty report).
2072
+ */
2073
+ repos?: string[];
2074
+ /** Hard cap on how many repos to score in one run (bounds work). */
2075
+ maxRepos?: number;
2076
+ }
2077
+ /**
2078
+ * Lifecycle status of a single Milestone.
2079
+ * - 'pending' : not yet advanced; eligible to be the next actionable one.
2080
+ * - 'in-progress' : a sandboxed, proposal-only swarm is currently running.
2081
+ * - 'proposed' : the swarm produced a PENDING inbox proposal (linked via
2082
+ * proposalId). This is the terminal "success" state M28
2083
+ * drives to — a human approves the proposal out-of-band.
2084
+ * - 'paused' : the human paused this milestone; it is skipped by
2085
+ * nextActionableMilestone() until resumed.
2086
+ * - 'skipped' : the human skipped this milestone permanently.
2087
+ * - 'blocked' : an advance attempt failed/escalated (swarm 'failed' /
2088
+ * 'aborted' / 'needs-approval'); requires human attention.
2089
+ * - 'done' : the milestone's proposal was approved+applied out-of-band
2090
+ * (set by a read-only reconcile against inbox state, never
2091
+ * by M28 mutating the proposal itself).
2092
+ */
2093
+ export type MilestoneStatus = 'pending' | 'in-progress' | 'proposed' | 'paused' | 'skipped' | 'blocked' | 'done';
2094
+ /**
2095
+ * Lifecycle status of a whole Goal (objective). Derived/rolled-up from its
2096
+ * milestones by progressOf(), but persisted for cheap listing.
2097
+ * - 'planning' : created; milestones not yet decomposed (no plan yet).
2098
+ * - 'active' : has milestones; at least one is pending/in-progress.
2099
+ * - 'paused' : the human paused the entire goal (no milestone advances).
2100
+ * - 'done' : every non-skipped milestone is 'done'.
2101
+ * - 'archived' : the human retired the goal (read-only henceforth).
2102
+ */
2103
+ export type GoalStatus = 'planning' | 'active' | 'paused' | 'done' | 'archived';
2104
+ /**
2105
+ * M28: a single MILESTONE within a Goal. Each milestone is an ordered unit of
2106
+ * work that authors/links a versioned SpecArtifact and is advanced by a single
2107
+ * sandboxed, proposal-only swarm. Milestones are TRACKED over time and the
2108
+ * human STEERS them (reorder/pause/skip). METADATA ONLY — no secrets.
2109
+ */
2110
+ export interface Milestone {
2111
+ /** Stable, deterministic milestone id (unique within its Goal). */
2112
+ id: string;
2113
+ /** Short human-readable title (the decomposed sub-objective). */
2114
+ title: string;
2115
+ /** Longer detail / acceptance hint for this milestone (no secrets). */
2116
+ detail: string;
2117
+ /** Explicit ordering key; lower = earlier. Reorder mutates these. */
2118
+ order: number;
2119
+ /** Current lifecycle status. Created as 'pending'. */
2120
+ status: MilestoneStatus;
2121
+ /**
2122
+ * Id of the versioned SpecArtifact this milestone authors/links (via
2123
+ * authorSpec), or null until `goals plan` has run. NEVER an outward action.
2124
+ */
2125
+ specId: string | null;
2126
+ /**
2127
+ * Id of the SwarmRun produced by the most recent advance of this milestone,
2128
+ * or null if never advanced. READ-ONLY tracking handle (loadSwarm(swarmId)).
2129
+ */
2130
+ swarmId: string | null;
2131
+ /**
2132
+ * Id of the PENDING inbox Proposal the swarm emitted (its ONLY execution
2133
+ * sink), or null. READ-ONLY tracking handle (loadProposal(proposalId)). M28
2134
+ * NEVER approves/applies this proposal.
2135
+ */
2136
+ proposalId: string | null;
2137
+ /** ISO timestamp the milestone was created. */
2138
+ createdAt: string;
2139
+ /** ISO timestamp the milestone was last updated. */
2140
+ updatedAt: string;
2141
+ }
2142
+ /**
2143
+ * M28: a high-level OBJECTIVE the org decomposes into ordered Milestones.
2144
+ * Persisted one-file-per-goal at ~/.ashlr/goals/<id>.json (atomic JSON, mirror
2145
+ * of the learn/quality stores). PLANNING + TRACKING data only — creating or
2146
+ * editing a Goal NEVER touches a user repo, never runs a swarm, and never
2147
+ * emits an outward action. METADATA ONLY — no secrets.
2148
+ */
2149
+ export interface Goal {
2150
+ /** Stable unique id; also the file stem (~/.ashlr/goals/<id>.json). */
2151
+ id: string;
2152
+ /** The high-level objective text the goal was created from. */
2153
+ objective: string;
2154
+ /**
2155
+ * Absolute path of the ENROLLED repo this goal is bound to, or null when the
2156
+ * goal is repo-agnostic (planning-only; cannot be advanced). When set, it
2157
+ * MUST be filtered through isEnrolled() (resolve() first) at BOTH the core
2158
+ * advance path and the CLI before any swarm starts.
2159
+ */
2160
+ project: string | null;
2161
+ /** Rolled-up lifecycle status. Created as 'planning'. */
2162
+ status: GoalStatus;
2163
+ /** Ordered milestones (sorted by `order`). Empty until `goals plan` runs. */
2164
+ milestones: Milestone[];
2165
+ /** ISO timestamp the goal was created. */
2166
+ createdAt: string;
2167
+ /** ISO timestamp the goal was last updated. */
2168
+ updatedAt: string;
2169
+ }
2170
+ /**
2171
+ * M28: options for the deterministic-by-default decomposition of an objective
2172
+ * into Milestones (planner.decomposeGoal). LOCAL-FIRST: no model is used unless
2173
+ * `allowCloud` opens the local-first provider chain (Ollama/LM Studio only
2174
+ * unless a cloud key is configured). BOUNDED by `maxMilestones`.
2175
+ */
2176
+ export interface DecomposeOptions {
2177
+ /**
2178
+ * Permit an optional LLM-assisted refinement of the deterministic split,
2179
+ * routed through getActiveClient(cfg, { allowCloud }). Default false =
2180
+ * deterministic, local-only, ZERO non-localhost connections.
2181
+ */
2182
+ allowCloud?: boolean;
2183
+ /** Hard cap on how many milestones to produce (bounds the plan). */
2184
+ maxMilestones?: number;
2185
+ }
2186
+ /**
2187
+ * M28: options for advancing a single milestone (advance.advanceGoal). The
2188
+ * advance ALWAYS runs runSwarm with { sandbox:true, requireSandbox:true,
2189
+ * propose:true } — these are NOT configurable here; only the bound/test-seam
2190
+ * knobs below are exposed.
2191
+ */
2192
+ export interface AdvanceOptions {
2193
+ /**
2194
+ * Partial budget override merged over the M28 default HARD per-advance
2195
+ * ceiling. The advance NEVER runs unbounded.
2196
+ */
2197
+ budget?: Partial<RunBudget>;
2198
+ /**
2199
+ * Permit a CLOUD model inside the advanced swarm (default false =
2200
+ * local-first). Threaded into SwarmOptions.allowCloud only.
2201
+ */
2202
+ allowCloud?: boolean;
2203
+ /**
2204
+ * TEST SEAM only — forwarded to assertMayMutate(repo, { allowAnyRepo }) so
2205
+ * tests can advance a goal bound to a tmp repo without enrolling it. NEVER
2206
+ * bypasses the kill switch. Defaults to undefined (real enrollment enforced).
2207
+ *
2208
+ * HARDENED (M28 final fix): advanceGoal honors this ONLY when the process
2209
+ * ALSO sets the env var ASHLR_TEST_ALLOW_ANY_REPO=1. A production / in-process
2210
+ * caller passing { allowAnyRepo: true } WITHOUT that env var CANNOT bypass the
2211
+ * enrollment check — so enrollment-scoping (invariant #2) holds on every
2212
+ * shipped codepath. It also never reaches the runner, which re-enforces
2213
+ * enrollment at swarm start regardless.
2214
+ */
2215
+ allowAnyRepo?: boolean;
2216
+ }
2217
+ /**
2218
+ * M28: read-only roll-up of a Goal's progress (progressOf). Pure analysis over
2219
+ * the goal record + swarm/inbox state — mutates NOTHING. METADATA ONLY.
2220
+ */
2221
+ export interface GoalProgress {
2222
+ /** The goal id this roll-up describes. */
2223
+ goalId: string;
2224
+ /** Total milestone count. */
2225
+ total: number;
2226
+ /** Count of milestones in each status (sparse — only non-zero keys present). */
2227
+ byStatus: Partial<Record<MilestoneStatus, number>>;
2228
+ /** Count of milestones that have produced a PENDING proposal ('proposed'). */
2229
+ proposed: number;
2230
+ /** Count of milestones fully 'done'. */
2231
+ done: number;
2232
+ /** Fraction complete (done / (total - skipped)), 0..1; 0 when nothing to do. */
2233
+ fractionDone: number;
2234
+ /**
2235
+ * The next actionable milestone id (the lowest-order 'pending' milestone when
2236
+ * the goal is not paused), or null when there is nothing to advance.
2237
+ */
2238
+ nextActionableId: string | null;
2239
+ }
2240
+ /**
2241
+ * M29: org-level HEALTH summary distilled from the M27 HealthReport over
2242
+ * ENROLLED repos. Empty (zeros + empty list) when nothing is enrolled or the
2243
+ * M27 source is unavailable. METADATA ONLY.
2244
+ */
2245
+ export interface PortfolioHealthSummary {
2246
+ /** Number of enrolled repos that were scored (0 when none enrolled). */
2247
+ reposScored: number;
2248
+ /** Portfolio mean overall score, 0..100 (0 when none). */
2249
+ averageScore: number;
2250
+ /** Letter grade derived from `averageScore`. */
2251
+ averageGrade: HealthGrade;
2252
+ /**
2253
+ * The worst-scoring repos (lowest score first), bounded to a small cap. Each
2254
+ * entry is a compact handle — repo path label, score, grade. METADATA ONLY.
2255
+ */
2256
+ worstRepos: {
2257
+ repo: string;
2258
+ score: number;
2259
+ grade: HealthGrade;
2260
+ }[];
2261
+ }
2262
+ /**
2263
+ * M29: a single IN-FLIGHT goal surfaced in the portfolio. Derived from the M28
2264
+ * Goal record + progressOf() — pure read-only roll-up; references the next
2265
+ * actionable milestone by title (read-only handle). METADATA ONLY.
2266
+ */
2267
+ export interface PortfolioGoalInFlight {
2268
+ /** The goal id (read-only handle into the M28 goals store). */
2269
+ goalId: string;
2270
+ /** The high-level objective text. */
2271
+ objective: string;
2272
+ /** Rolled-up goal lifecycle status. */
2273
+ status: GoalStatus;
2274
+ /** Fraction of milestones complete, 0..1. */
2275
+ fractionDone: number;
2276
+ /** Count of milestones that produced a PENDING proposal. */
2277
+ proposed: number;
2278
+ /** Total milestone count. */
2279
+ totalMilestones: number;
2280
+ /**
2281
+ * Title of the next actionable milestone (lowest-order 'pending'), or null
2282
+ * when there is nothing to advance. Read-only display handle.
2283
+ */
2284
+ nextActionable: string | null;
2285
+ }
2286
+ /**
2287
+ * M29: a single top BACKLOG item surfaced in the portfolio. Compact projection
2288
+ * of an M22 WorkItem — title, repo label, and score. METADATA ONLY.
2289
+ */
2290
+ export interface PortfolioBacklogItem {
2291
+ /** Short human-readable title of the work item. */
2292
+ title: string;
2293
+ /** Absolute path / label of the repo the item belongs to, or null. */
2294
+ repo: string | null;
2295
+ /** The item's priority score (higher = more important). */
2296
+ score: number;
2297
+ }
2298
+ /**
2299
+ * M29: the COST block of the portfolio — actual spend for the window (from the
2300
+ * M19 rollup) plus the M19 CostForecast (local savings + monthly projection).
2301
+ * All figures are ESTIMATES. Zeroed on failure. METADATA ONLY.
2302
+ */
2303
+ export interface PortfolioCost {
2304
+ /** Window label the cost block is built from (e.g. '7d' | '30d'). */
2305
+ window: string;
2306
+ /** Actual USD spent in the window (local providers contribute $0). */
2307
+ spentUsd: number;
2308
+ /** Estimated USD the same local tokens WOULD have cost on cloud (savings). */
2309
+ localSavingsUsd: number;
2310
+ /** Simple projected monthly USD spend extrapolated from the window's rate. */
2311
+ projectedMonthlyUsd: number;
2312
+ }
2313
+ /**
2314
+ * M29: the EFFECTIVENESS headline — a one-line read-only projection of the most
2315
+ * recent M26 ReflectionReport + its week-over-week delta. Absent when there is
2316
+ * no reflect report. METADATA ONLY.
2317
+ */
2318
+ export interface PortfolioEffectiveness {
2319
+ /** Success rate from the latest reflection report (0..1). */
2320
+ successRate: number;
2321
+ /** Signed effectiveness delta in percentage points vs prior, or null. */
2322
+ effectivenessDeltaPct: number | null;
2323
+ /** Deterministic one-line headline (templated by M26's computeDelta). */
2324
+ headline: string;
2325
+ }
2326
+ /**
2327
+ * M29: the "today" DELTA block — day-over-day movements computed against the
2328
+ * previous persisted digest (loadPreviousDigest). All deltas are signed and
2329
+ * null when there is no prior digest to compare against. Pure read-only
2330
+ * arithmetic — mutates NOTHING. METADATA ONLY.
2331
+ */
2332
+ export interface PortfolioTodayDelta {
2333
+ /** ISO timestamp of the prior digest this block compares against, or null. */
2334
+ previousAt: string | null;
2335
+ /** Signed change in pending inbox proposals since the prior digest, or null. */
2336
+ pendingProposalsDelta: number | null;
2337
+ /** Signed change in dirty repos since the prior digest, or null. */
2338
+ dirtyReposDelta: number | null;
2339
+ /** Signed change in window spend (USD) since the prior digest, or null. */
2340
+ spendUsdDelta: number | null;
2341
+ /** Signed change in portfolio average health score since the prior, or null. */
2342
+ healthScoreDelta: number | null;
2343
+ /** Signed change in count of in-flight goals since the prior digest, or null. */
2344
+ goalsInFlightDelta: number | null;
2345
+ }
2346
+ /**
2347
+ * M29: the OPTIONAL org-level portfolio section embedded in DashboardSnapshot.
2348
+ * Each field is independently degradable — an empty enrollment / missing source
2349
+ * leaves that field at its empty/zeroed default with NO disk scan. READ-ONLY
2350
+ * aggregation only. METADATA ONLY — never carries secret values.
2351
+ */
2352
+ export interface PortfolioSummary {
2353
+ /** Health roll-up over ENROLLED repos (M27). Empty when none enrolled. */
2354
+ health: PortfolioHealthSummary;
2355
+ /** In-flight goals (M28), bounded to a small cap, most-progressed first. */
2356
+ goalsInFlight: PortfolioGoalInFlight[];
2357
+ /** Top scored backlog work items (M22), bounded to a small cap. */
2358
+ backlogTop: PortfolioBacklogItem[];
2359
+ /** Cost + forecast for the dashboard window (M19). */
2360
+ cost: PortfolioCost;
2361
+ /** Effectiveness headline from the latest reflection report (M26), or null. */
2362
+ effectiveness: PortfolioEffectiveness | null;
2363
+ /** Day-over-day "today" deltas vs the previous digest (M29 store). */
2364
+ today: PortfolioTodayDelta;
2365
+ }
2366
+ /**
2367
+ * M29: window accepted by the digest + portfolio cost block. Mirrors the M19
2368
+ * forecast windows. Defaults to '7d'.
2369
+ */
2370
+ export type DigestWindow = '7d' | '30d';
2371
+ /**
2372
+ * M29: options for buildDigest. LOCAL-FIRST: no model is used unless
2373
+ * `allowCloud` opens the local-first provider chain (Ollama/LM Studio only
2374
+ * unless a cloud key is configured). `window` controls the cost block.
2375
+ */
2376
+ export interface DigestOptions {
2377
+ /** Cost/forecast window. Default '7d'. */
2378
+ window?: DigestWindow;
2379
+ /**
2380
+ * OPT-IN: attempt an optional LLM-assisted narrative. Default false =
2381
+ * deterministic-only, NO model is ever constructed (mirrors the M26 reflect
2382
+ * `narrative` gate). Even a reachable LOCAL provider is NOT consulted unless
2383
+ * this is true — so the default `ashlr digest` path makes ZERO model calls.
2384
+ */
2385
+ narrative?: boolean;
2386
+ /**
2387
+ * When `narrative` is true, permit a CLOUD model for it (routed through
2388
+ * getActiveClient(cfg, { allowCloud })). Default false = local-only (Ollama/
2389
+ * LM Studio), ZERO non-localhost connections. Has NO effect unless
2390
+ * `narrative` is also true.
2391
+ */
2392
+ allowCloud?: boolean;
2393
+ }
2394
+ /**
2395
+ * M29: the deterministic DAILY DIGEST report. Built from a portfolio snapshot
2396
+ * (DashboardSnapshot incl. its `portfolio` section) plus day-over-day deltas vs
2397
+ * the previous persisted digest. Persisted as JSON + markdown under
2398
+ * ~/.ashlr/digests/ and used as the prior for the next day's deltas. Computed
2399
+ * entirely WITHOUT an LLM on the default path. METADATA ONLY — no secrets.
2400
+ */
2401
+ export interface DigestReport {
2402
+ /** ISO timestamp the digest was generated. */
2403
+ generatedAt: string;
2404
+ /** Calendar day (YYYY-MM-DD) the digest summarizes. */
2405
+ date: string;
2406
+ /** Cost/forecast window the digest's cost figures use. */
2407
+ window: DigestWindow;
2408
+ /**
2409
+ * The portfolio section that backs this digest (snapshot of the org view at
2410
+ * generation time). Carries health/goals/backlog/cost/effectiveness/today.
2411
+ */
2412
+ portfolio: PortfolioSummary;
2413
+ /** Compact repo roll-up at generation time (from the base DashboardSnapshot). */
2414
+ repos: {
2415
+ total: number;
2416
+ dirty: number;
2417
+ stale: number;
2418
+ };
2419
+ /** Pending inbox proposals awaiting approval at generation time (M23). */
2420
+ pendingProposals: number;
2421
+ /** Operator (daemon) status at generation time (M24), or null when absent. */
2422
+ daemon: {
2423
+ running: boolean;
2424
+ todaySpentUsd: number;
2425
+ } | null;
2426
+ /**
2427
+ * Deterministic one-line human headline summarizing the day (templated; no
2428
+ * LLM). Always present.
2429
+ */
2430
+ headline: string;
2431
+ /**
2432
+ * Optional LLM-assisted narrative summary. ABSENT on the default path
2433
+ * (deterministic-only). Populated ONLY when narrative generation is requested
2434
+ * and a provider is reachable (local unless --allow-cloud + key). When set,
2435
+ * `narrativeLocal` records whether it was produced by a local model.
2436
+ */
2437
+ narrative?: string;
2438
+ /** True when `narrative` was produced by a LOCAL model; absent when none. */
2439
+ narrativeLocal?: boolean;
2440
+ }
2441
+ /**
2442
+ * M29: outcome of deliverDigest — exactly what happened. The local artifact is
2443
+ * ALWAYS written; `notified` is true ONLY when `notify:true` was passed AND
2444
+ * notify() actually delivered to a configured webhook. METADATA ONLY.
2445
+ */
2446
+ export interface DigestDeliveryResult {
2447
+ /** Absolute path of the JSON artifact written, or null on write failure. */
2448
+ jsonPath: string | null;
2449
+ /** Absolute path of the markdown artifact written, or null on write failure. */
2450
+ markdownPath: string | null;
2451
+ /**
2452
+ * Whether the digest was delivered outward via notify(). FALSE on the default
2453
+ * path (no --notify) and when no webhook is configured. The ONLY outward path.
2454
+ */
2455
+ notified: boolean;
2456
+ }
2457
+ /**
2458
+ * M31: safety classification of a native MCP tool. The gate is STRUCTURAL —
2459
+ * `callNativeTool` enforces it before any handler runs:
2460
+ * 'read' — pure read of local stores; allowed even when the kill switch is on.
2461
+ * 'append' — append-only write under ~/.ashlr/ (genome hub); REFUSED when KILL.
2462
+ * 'proposal' — creates a PENDING inbox Proposal; REFUSED when KILL. There is
2463
+ * deliberately NO 'approve'/'apply' class — approval is human-only.
2464
+ */
2465
+ export type NativeToolSafety = 'read' | 'append' | 'proposal';
2466
+ /**
2467
+ * M31: one native tool served by the MCP gateway itself (SDK-free definition;
2468
+ * the gateway is the only adapter). `inputSchema` is plain JSON Schema.
2469
+ */
2470
+ export interface NativeToolDef {
2471
+ /** Tool name, `ashlr_<verb>` — single underscore; downstream tools are `<server>__<tool>`. */
2472
+ name: string;
2473
+ /** Human/agent-facing description shown in tools/list. */
2474
+ description: string;
2475
+ /** JSON Schema for the arguments (always `type: 'object'`). */
2476
+ inputSchema: object;
2477
+ /** Safety class enforced by the call pipeline (see NativeToolSafety). */
2478
+ safety: NativeToolSafety;
2479
+ }
2480
+ /** M32: a percentile triple used by RunEstimate. */
2481
+ export interface PercentileTriple {
2482
+ p25: number;
2483
+ median: number;
2484
+ p75: number;
2485
+ }
2486
+ /**
2487
+ * M32: pre-flight cost estimate for a run/swarm, derived from persisted
2488
+ * history (read-only, never throws — zeroed with confidence 'low' when no
2489
+ * history exists). Produced by core/observability/estimate.ts.
2490
+ */
2491
+ export interface RunEstimate {
2492
+ kind: 'run' | 'swarm';
2493
+ goal: string;
2494
+ /** How many history samples informed the estimate. */
2495
+ sampleSize: number;
2496
+ /** low (<3 samples) · medium (<10) · high (≥10). */
2497
+ confidence: 'low' | 'medium' | 'high';
2498
+ tokens: PercentileTriple;
2499
+ steps: PercentileTriple;
2500
+ estCostUsd: PercentileTriple;
2501
+ /** Reference cloud cost of the median token volume (context for local $0). */
2502
+ wouldBeCloudUsd: number;
2503
+ durationMs: PercentileTriple;
2504
+ /** True when the requested budget capped the percentiles. */
2505
+ budgetClamped: boolean;
2506
+ generatedAt: string;
2507
+ }
2508
+ /**
2509
+ * M31: composite session-start orientation — "what should I know before I
2510
+ * start working here". Every section is BEST-EFFORT (empty on failure); the
2511
+ * builder never throws. READ-ONLY: derived entirely from local stores.
2512
+ */
2513
+ export interface OrientResult {
2514
+ /** ISO timestamp the orientation was generated. */
2515
+ generatedAt: string;
2516
+ /** Absolute repo path the orientation is scoped to, or null for portfolio-wide. */
2517
+ repo: string | null;
2518
+ /** Top genome memory hits relevant to the repo/query (bounded). */
2519
+ genomeHits: {
2520
+ title: string;
2521
+ text: string;
2522
+ score: number;
2523
+ project: string | null;
2524
+ }[];
2525
+ /** Latest health score for the repo (null when none recorded / not enrolled). */
2526
+ health: {
2527
+ score: number;
2528
+ grade: string;
2529
+ worstDimensions: string[];
2530
+ } | null;
2531
+ /** Top persisted backlog items for the repo (empty when no backlog built). */
2532
+ backlogItems: {
2533
+ id: string;
2534
+ source: string;
2535
+ title: string;
2536
+ score: number;
2537
+ }[];
2538
+ /** Number of PENDING inbox proposals awaiting the human. */
2539
+ pendingProposals: number;
2540
+ /** Portfolio attention summary from the index (dirty/stale repo counts). */
2541
+ attention: {
2542
+ dirtyRepos: number;
2543
+ staleRepos: number;
2544
+ } | null;
2545
+ }