@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,711 @@
1
+ /**
2
+ * store.ts — Genome store for M7 shared memory.
3
+ *
4
+ * Aggregates GenomeEntry records from two sources:
5
+ * (a) Per-project .ashlrcode/genome/ directories in indexed repos
6
+ * (manifest.json + section .md/.json files).
7
+ * (b) The hub store at ~/.ashlr/genome/hub.jsonl (append-only JSONL).
8
+ *
9
+ * GUARDRAILS:
10
+ * - Never throws — all I/O is wrapped defensively.
11
+ * - Append-only: appendHubEntry never overwrites or deletes.
12
+ * - Bounded: caps total entries and bytes read for perf.
13
+ * - Local-only: embeddings availability probed via local Ollama only.
14
+ * - Privacy: no secrets, no exfiltration.
15
+ */
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import { createHash } from 'node:crypto';
20
+ import { execFileSync } from 'node:child_process';
21
+ import { loadIndex } from '../index-engine.js';
22
+ // ---------------------------------------------------------------------------
23
+ // Constants
24
+ // ---------------------------------------------------------------------------
25
+ /** Max entries to read from hub.jsonl (bounded read). */
26
+ const HUB_MAX_ENTRIES = 2000;
27
+ /** Max bytes to read from a single section file (cap long docs). */
28
+ const SECTION_MAX_BYTES = 8000;
29
+ /** Max total entries returned from loadGenome (prevents runaway memory). */
30
+ const LOAD_MAX_TOTAL = 5000;
31
+ /** Embedding-capable model name substrings (Ollama model names). */
32
+ const EMBED_MODEL_HINTS = ['bge', 'nomic-embed', 'mxbai-embed', 'all-minilm', 'embed'];
33
+ /** Max directories to scan when walking cfg.roots for project genome dirs. */
34
+ const ROOT_SCAN_MAX_DIRS = 5000;
35
+ /** Max depth to descend under each configured root when discovering repos. */
36
+ const ROOT_SCAN_MAX_DEPTH = 4;
37
+ // ---------------------------------------------------------------------------
38
+ // Path helpers
39
+ // ---------------------------------------------------------------------------
40
+ /** Absolute path to ~/.ashlr/genome/hub.jsonl */
41
+ export function hubStorePath() {
42
+ return path.join(homedir(), '.ashlr', 'genome', 'hub.jsonl');
43
+ }
44
+ /** Absolute path to ~/.ashlr/genome/ directory */
45
+ function hubStoreDir() {
46
+ return path.join(homedir(), '.ashlr', 'genome');
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // ID generation
50
+ // ---------------------------------------------------------------------------
51
+ /**
52
+ * Generate a stable, URL-safe slug id from a seed string.
53
+ * Combines a short slug (for readability) with an 8-char SHA-256 prefix
54
+ * (for collision resistance).
55
+ */
56
+ function makeId(seed) {
57
+ const hash = createHash('sha256').update(seed).digest('hex').slice(0, 8);
58
+ const slug = seed
59
+ .toLowerCase()
60
+ .replace(/[^a-z0-9]+/g, '-')
61
+ .replace(/^-+|-+$/g, '')
62
+ .slice(0, 40);
63
+ return slug ? `${slug}-${hash}` : hash;
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Safe I/O helpers
67
+ // ---------------------------------------------------------------------------
68
+ /**
69
+ * Read up to `maxBytes` of a file. Returns null on any error or if path is
70
+ * not a regular file.
71
+ */
72
+ function safeReadFile(filePath, maxBytes = SECTION_MAX_BYTES) {
73
+ try {
74
+ const stat = fs.statSync(filePath);
75
+ if (!stat.isFile())
76
+ return null;
77
+ const size = Math.min(stat.size, maxBytes);
78
+ if (size === 0)
79
+ return '';
80
+ const buf = Buffer.alloc(size);
81
+ const fd = fs.openSync(filePath, 'r');
82
+ try {
83
+ fs.readSync(fd, buf, 0, size, 0);
84
+ }
85
+ finally {
86
+ fs.closeSync(fd);
87
+ }
88
+ return buf.toString('utf8');
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ /** Get file mtime as ISO string; returns `fallback` on any error. */
95
+ function safeMtime(filePath, fallback) {
96
+ try {
97
+ return fs.statSync(filePath).mtime.toISOString();
98
+ }
99
+ catch {
100
+ return fallback;
101
+ }
102
+ }
103
+ /** JSON.parse that returns null on any error instead of throwing. */
104
+ function safeParseJson(text) {
105
+ try {
106
+ return JSON.parse(text);
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ /** Extract the sections array from a parsed manifest object. */
113
+ function parseManifestSections(manifest) {
114
+ if (typeof manifest !== 'object' || manifest === null)
115
+ return [];
116
+ const sections = manifest['sections'];
117
+ if (!Array.isArray(sections))
118
+ return [];
119
+ return sections;
120
+ }
121
+ /** Derive the project name from the manifest, falling back to the repo basename. */
122
+ function manifestProjectName(manifest, dirBasename) {
123
+ if (typeof manifest === 'object' && manifest !== null) {
124
+ const p = manifest['project'];
125
+ if (typeof p === 'string' && p.trim())
126
+ return p.trim();
127
+ }
128
+ return dirBasename;
129
+ }
130
+ /**
131
+ * Load GenomeEntry records from <repo>/.ashlrcode/genome/.
132
+ *
133
+ * Pass 1: manifest-listed sections (path, title, tags, updatedAt).
134
+ * Pass 2: any un-manifested .md/.json files found by walking the genome dir.
135
+ *
136
+ * Skips unreadable files silently. Never throws.
137
+ */
138
+ function loadProjectGenome(repoPath) {
139
+ const genomeDir = path.join(repoPath, '.ashlrcode', 'genome');
140
+ try {
141
+ if (!fs.existsSync(genomeDir))
142
+ return [];
143
+ }
144
+ catch {
145
+ return [];
146
+ }
147
+ const entries = [];
148
+ const seenRelPaths = new Set();
149
+ // --- Parse manifest ---
150
+ let manifest = null;
151
+ const manifestPath = path.join(genomeDir, 'manifest.json');
152
+ const rawManifest = safeReadFile(manifestPath, 128_000);
153
+ if (rawManifest)
154
+ manifest = safeParseJson(rawManifest);
155
+ const projectName = manifestProjectName(manifest, path.basename(repoPath));
156
+ // --- Pass 1: manifest-listed sections ---
157
+ for (const sec of parseManifestSections(manifest)) {
158
+ const relPath = typeof sec.path === 'string' ? sec.path : null;
159
+ if (!relPath)
160
+ continue;
161
+ const absPath = path.join(genomeDir, relPath);
162
+ const normRel = relPath.replace(/\\/g, '/');
163
+ const title = typeof sec.title === 'string' && sec.title.trim()
164
+ ? sec.title.trim()
165
+ : path.basename(relPath, path.extname(relPath)).replace(/[-_]/g, ' ');
166
+ const tags = Array.isArray(sec.tags)
167
+ ? sec.tags
168
+ .filter((t) => typeof t === 'string')
169
+ .map((t) => t.trim())
170
+ .filter(Boolean)
171
+ : [];
172
+ const ts = typeof sec.updatedAt === 'string' && sec.updatedAt
173
+ ? sec.updatedAt
174
+ : safeMtime(absPath, new Date().toISOString());
175
+ const text = safeReadFile(absPath);
176
+ if (text === null)
177
+ continue; // unreadable — skip
178
+ seenRelPaths.add(normRel);
179
+ entries.push({
180
+ id: makeId(`${projectName}:${normRel}`),
181
+ project: projectName,
182
+ source: 'project',
183
+ title,
184
+ text: text.trim().slice(0, SECTION_MAX_BYTES),
185
+ tags,
186
+ ts,
187
+ });
188
+ }
189
+ // --- Pass 2: un-manifested .md/.json files ---
190
+ try {
191
+ collectGenomeFiles(genomeDir, genomeDir, projectName, seenRelPaths, entries);
192
+ }
193
+ catch {
194
+ // Best-effort — never fail on extra files.
195
+ }
196
+ return entries;
197
+ }
198
+ /**
199
+ * Recursively walk `dir` and collect .md/.json files not already in `seen`.
200
+ * Skips manifest.json. Mutates `entries` in place.
201
+ */
202
+ function collectGenomeFiles(dir, genomeRoot, projectName, seen, entries) {
203
+ let dirents;
204
+ try {
205
+ dirents = fs.readdirSync(dir, { withFileTypes: true });
206
+ }
207
+ catch {
208
+ return;
209
+ }
210
+ for (const dirent of dirents) {
211
+ const absPath = path.join(dir, dirent.name);
212
+ if (dirent.isDirectory()) {
213
+ collectGenomeFiles(absPath, genomeRoot, projectName, seen, entries);
214
+ continue;
215
+ }
216
+ if (!dirent.isFile())
217
+ continue;
218
+ const ext = path.extname(dirent.name).toLowerCase();
219
+ if (ext !== '.md' && ext !== '.json')
220
+ continue;
221
+ if (dirent.name === 'manifest.json')
222
+ continue;
223
+ const relPath = path.relative(genomeRoot, absPath).replace(/\\/g, '/');
224
+ if (seen.has(relPath))
225
+ continue;
226
+ seen.add(relPath);
227
+ const text = safeReadFile(absPath);
228
+ if (text === null || !text.trim())
229
+ continue;
230
+ const title = path.basename(dirent.name, ext).replace(/[-_]/g, ' ');
231
+ const ts = safeMtime(absPath, new Date().toISOString());
232
+ entries.push({
233
+ id: makeId(`${projectName}:${relPath}`),
234
+ project: projectName,
235
+ source: 'project',
236
+ title,
237
+ text: text.trim().slice(0, SECTION_MAX_BYTES),
238
+ tags: [],
239
+ ts,
240
+ });
241
+ }
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // Hub JSONL parsing
245
+ // ---------------------------------------------------------------------------
246
+ /**
247
+ * Parse ~/.ashlr/genome/hub.jsonl into GenomeEntry records.
248
+ * Skips malformed or incomplete lines silently. Caps at HUB_MAX_ENTRIES.
249
+ */
250
+ function loadHubEntries() {
251
+ const storePath = hubStorePath();
252
+ const entries = [];
253
+ try {
254
+ if (!fs.existsSync(storePath))
255
+ return [];
256
+ const raw = fs.readFileSync(storePath, 'utf8');
257
+ for (const line of raw.split('\n')) {
258
+ if (entries.length >= HUB_MAX_ENTRIES)
259
+ break;
260
+ const trimmed = line.trim();
261
+ if (!trimmed)
262
+ continue;
263
+ const parsed = safeParseJson(trimmed);
264
+ if (typeof parsed !== 'object' || parsed === null)
265
+ continue;
266
+ const e = parsed;
267
+ // All required fields must be present and correctly typed.
268
+ if (typeof e['id'] !== 'string' ||
269
+ typeof e['title'] !== 'string' ||
270
+ typeof e['text'] !== 'string' ||
271
+ typeof e['ts'] !== 'string') {
272
+ continue;
273
+ }
274
+ entries.push({
275
+ id: e['id'],
276
+ project: typeof e['project'] === 'string' ? e['project'] : null,
277
+ source: 'hub',
278
+ title: e['title'],
279
+ text: e['text'],
280
+ tags: Array.isArray(e['tags'])
281
+ ? e['tags'].filter((t) => typeof t === 'string')
282
+ : [],
283
+ ts: e['ts'],
284
+ });
285
+ }
286
+ }
287
+ catch {
288
+ // Any error: return whatever we parsed so far.
289
+ }
290
+ return entries;
291
+ }
292
+ // ---------------------------------------------------------------------------
293
+ // Public: loadGenome
294
+ // ---------------------------------------------------------------------------
295
+ /**
296
+ * Discover absolute paths of directories under `roots` that contain a
297
+ * `.ashlrcode/genome/` dir. Shallow, bounded walk (depth + total-dir caps).
298
+ * Skips noisy/heavy dirs (node_modules, .git, dist, etc.). Never throws.
299
+ *
300
+ * This makes loadGenome self-contained and hermetically testable: callers can
301
+ * point cfg.roots at a fixture tree and genome dirs are found without relying
302
+ * on a pre-built index. In real use, roots come from the user's config.
303
+ */
304
+ function discoverGenomeRepos(roots) {
305
+ const found = [];
306
+ const seen = new Set();
307
+ let scanned = 0;
308
+ const SKIP_DIRS = new Set([
309
+ 'node_modules',
310
+ '.git',
311
+ 'dist',
312
+ 'build',
313
+ 'out',
314
+ '.next',
315
+ '.cache',
316
+ 'coverage',
317
+ '.venv',
318
+ 'venv',
319
+ '__pycache__',
320
+ ]);
321
+ const walk = (dir, depth) => {
322
+ if (scanned >= ROOT_SCAN_MAX_DIRS)
323
+ return;
324
+ if (depth > ROOT_SCAN_MAX_DEPTH)
325
+ return;
326
+ // A repo is any directory that has a .ashlrcode/genome/ dir.
327
+ try {
328
+ const genomeDir = path.join(dir, '.ashlrcode', 'genome');
329
+ if (fs.existsSync(genomeDir) && fs.statSync(genomeDir).isDirectory()) {
330
+ if (!seen.has(dir)) {
331
+ seen.add(dir);
332
+ found.push(dir);
333
+ }
334
+ }
335
+ }
336
+ catch {
337
+ // ignore — keep walking
338
+ }
339
+ let dirents;
340
+ try {
341
+ dirents = fs.readdirSync(dir, { withFileTypes: true });
342
+ }
343
+ catch {
344
+ return;
345
+ }
346
+ for (const dirent of dirents) {
347
+ if (scanned >= ROOT_SCAN_MAX_DIRS)
348
+ return;
349
+ if (!dirent.isDirectory())
350
+ continue;
351
+ if (dirent.name.startsWith('.') && dirent.name !== '.') {
352
+ // Don't descend into dotdirs (incl. .ashlrcode itself) — but the
353
+ // genome check above already handled the current dir.
354
+ continue;
355
+ }
356
+ if (SKIP_DIRS.has(dirent.name))
357
+ continue;
358
+ scanned++;
359
+ walk(path.join(dir, dirent.name), depth + 1);
360
+ }
361
+ };
362
+ for (const root of roots) {
363
+ if (!root || typeof root !== 'string')
364
+ continue;
365
+ try {
366
+ if (!fs.existsSync(root))
367
+ continue;
368
+ }
369
+ catch {
370
+ continue;
371
+ }
372
+ walk(root, 0);
373
+ }
374
+ return found;
375
+ }
376
+ /**
377
+ * Aggregate GenomeEntry records from all sources:
378
+ * (a) Each project's .ashlrcode/genome/ directory — discovered by scanning
379
+ * cfg.roots AND (best-effort) the pre-built index.
380
+ * (b) ~/.ashlr/genome/hub.jsonl
381
+ *
382
+ * Never throws. Bounded to LOAD_MAX_TOTAL entries total.
383
+ * De-duplicates by entry id across sources.
384
+ */
385
+ export function loadGenome(cfg) {
386
+ const entries = [];
387
+ const seenIds = new Set();
388
+ // --- Source (b): hub store ---
389
+ try {
390
+ for (const e of loadHubEntries()) {
391
+ if (entries.length >= LOAD_MAX_TOTAL)
392
+ break;
393
+ if (!seenIds.has(e.id)) {
394
+ seenIds.add(e.id);
395
+ entries.push(e);
396
+ }
397
+ }
398
+ }
399
+ catch {
400
+ // Defensive — loadHubEntries already catches internally.
401
+ }
402
+ // --- Source (a): per-project genome dirs ---
403
+ // Collect repo paths from two discovery mechanisms, de-duplicated:
404
+ // 1. Scanning cfg.roots for .ashlrcode/genome/ dirs (self-contained).
405
+ // 2. The pre-built index (real-world convenience; may be absent).
406
+ const repoPaths = new Set();
407
+ try {
408
+ for (const p of discoverGenomeRepos(Array.isArray(cfg.roots) ? cfg.roots : [])) {
409
+ repoPaths.add(p);
410
+ }
411
+ }
412
+ catch {
413
+ // Root scan is best-effort.
414
+ }
415
+ try {
416
+ const index = loadIndex();
417
+ if (index) {
418
+ for (const item of index.items) {
419
+ if (item.kind === 'repo')
420
+ repoPaths.add(item.path);
421
+ }
422
+ }
423
+ }
424
+ catch {
425
+ // Index unavailable — continue with whatever roots gave us.
426
+ }
427
+ for (const repoPath of repoPaths) {
428
+ if (entries.length >= LOAD_MAX_TOTAL)
429
+ break;
430
+ try {
431
+ for (const e of loadProjectGenome(repoPath)) {
432
+ if (entries.length >= LOAD_MAX_TOTAL)
433
+ break;
434
+ if (!seenIds.has(e.id)) {
435
+ seenIds.add(e.id);
436
+ entries.push(e);
437
+ }
438
+ }
439
+ }
440
+ catch {
441
+ // One bad repo must not break the whole load.
442
+ }
443
+ }
444
+ return entries;
445
+ }
446
+ // ---------------------------------------------------------------------------
447
+ // Public: appendHubEntry
448
+ // ---------------------------------------------------------------------------
449
+ /**
450
+ * Append one GenomeEntry to ~/.ashlr/genome/hub.jsonl.
451
+ *
452
+ * GUARDRAILS:
453
+ * - APPEND only — never overwrites or deletes existing data.
454
+ * - Creates ~/.ashlr/genome/ if absent.
455
+ * - Optionally drops a NEW note file under the resolved project's
456
+ * .ashlrcode/genome/hub-notes/ — never modifies existing genome files.
457
+ * - Returns the written entry (source 'hub').
458
+ */
459
+ export function appendHubEntry(input) {
460
+ const now = new Date().toISOString();
461
+ // Derive title: supplied title, else first non-empty line of text.
462
+ const title = (input.title ?? '').trim() ||
463
+ input.text
464
+ .trim()
465
+ .split('\n')[0]
466
+ ?.replace(/^#+\s*/, '')
467
+ .trim()
468
+ .slice(0, 80) ||
469
+ 'Note';
470
+ const project = (input.project ?? '').trim() || null;
471
+ const tags = (input.tags ?? []).map((t) => t.trim()).filter(Boolean);
472
+ const text = input.text.trim();
473
+ // Id: stable slug from title + timestamp (unique per call).
474
+ const id = makeId(`hub:${title}:${now}`);
475
+ const entry = {
476
+ id,
477
+ project,
478
+ source: 'hub',
479
+ title,
480
+ text,
481
+ tags,
482
+ ts: now,
483
+ };
484
+ // Ensure hub store directory exists.
485
+ try {
486
+ fs.mkdirSync(hubStoreDir(), { recursive: true });
487
+ }
488
+ catch {
489
+ // Best-effort mkdir.
490
+ }
491
+ // Append as a single JSON line (never overwrites).
492
+ try {
493
+ fs.appendFileSync(hubStorePath(), JSON.stringify(entry) + '\n', 'utf8');
494
+ }
495
+ catch {
496
+ // Append failure — still return the entry so callers can display it.
497
+ }
498
+ // Optionally drop a note into the resolved project's genome dir.
499
+ // Skipped entirely when input.hubOnly is set (M16 auto-capture) so a
500
+ // completed run/swarm never writes a file into the user's repo working tree.
501
+ if (project && !input.hubOnly) {
502
+ try {
503
+ maybeWriteProjectNote(project, entry);
504
+ }
505
+ catch {
506
+ // Best-effort only — never fail the learn operation.
507
+ }
508
+ }
509
+ return entry;
510
+ }
511
+ /**
512
+ * If `projectName` matches an indexed repo that already has a
513
+ * .ashlrcode/genome/ directory, write a NEW note file under hub-notes/.
514
+ * Never modifies any existing genome files.
515
+ */
516
+ function maybeWriteProjectNote(projectName, entry) {
517
+ const index = loadIndex();
518
+ if (!index)
519
+ return;
520
+ const lc = projectName.toLowerCase();
521
+ const repo = index.items.find((item) => item.kind === 'repo' && path.basename(item.path).toLowerCase() === lc);
522
+ if (!repo)
523
+ return;
524
+ const genomeDir = path.join(repo.path, '.ashlrcode', 'genome');
525
+ try {
526
+ if (!fs.existsSync(genomeDir))
527
+ return;
528
+ }
529
+ catch {
530
+ return;
531
+ }
532
+ const notesDir = path.join(genomeDir, 'hub-notes');
533
+ fs.mkdirSync(notesDir, { recursive: true });
534
+ // Timestamp-based filename to guarantee uniqueness.
535
+ const safeName = entry.id.slice(0, 48).replace(/[^a-z0-9-]/g, '-');
536
+ const noteFile = path.join(notesDir, `${safeName}.md`);
537
+ try {
538
+ if (fs.existsSync(noteFile))
539
+ return;
540
+ }
541
+ catch {
542
+ return;
543
+ }
544
+ const body = [
545
+ `# ${entry.title}`,
546
+ '',
547
+ entry.text,
548
+ '',
549
+ `<!-- ashlr-learn: ${entry.ts}${entry.tags.length ? ` tags=${entry.tags.join(',')}` : ''} -->`,
550
+ ].join('\n') + '\n';
551
+ fs.writeFileSync(noteFile, body, 'utf8');
552
+ }
553
+ // ---------------------------------------------------------------------------
554
+ // Embeddings availability probe (synchronous)
555
+ // ---------------------------------------------------------------------------
556
+ /**
557
+ * Synchronously probe Ollama /api/tags using `curl` via execFileSync to
558
+ * detect whether an embedding-capable model is loaded.
559
+ *
560
+ * genomeHealth() is synchronous per the contract, so we use a child process
561
+ * rather than async fetch. Best-effort — returns false on any error.
562
+ * LOCAL ONLY — never contacts any cloud endpoint.
563
+ */
564
+ function probeEmbeddingsSync(ollamaBase) {
565
+ try {
566
+ const url = ollamaBase.replace(/\/+$/, '') + '/api/tags';
567
+ const output = execFileSync('curl', ['-sf', '--max-time', '2', url], {
568
+ encoding: 'utf8',
569
+ timeout: 2500,
570
+ });
571
+ const body = safeParseJson(output);
572
+ if (typeof body !== 'object' ||
573
+ body === null ||
574
+ !Array.isArray(body['models'])) {
575
+ return false;
576
+ }
577
+ const models = body.models;
578
+ for (const m of models) {
579
+ if (typeof m !== 'object' || m === null)
580
+ continue;
581
+ const name = m['name'];
582
+ if (typeof name !== 'string')
583
+ continue;
584
+ if (EMBED_MODEL_HINTS.some((hint) => name.toLowerCase().includes(hint)))
585
+ return true;
586
+ }
587
+ return false;
588
+ }
589
+ catch {
590
+ return false;
591
+ }
592
+ }
593
+ // ---------------------------------------------------------------------------
594
+ // Public: genomeHealth
595
+ // ---------------------------------------------------------------------------
596
+ /**
597
+ * Return a HUB-ONLY GenomeHealth roll-up read solely from the local hub store
598
+ * (~/.ashlr/genome/hub.jsonl). UNLIKE genomeHealth(), this does NOT call
599
+ * loadGenome()/discoverGenomeRepos() and therefore performs NO recursive
600
+ * portfolio disk scan — it reads only the user's OWN local hub metadata.
601
+ *
602
+ * Used by M26 reflect to honour the "no portfolio disk scan" invariant: it
603
+ * reports hub entry count, hub store size, and last-learned timestamp, and
604
+ * mirrors those into totalEntries/projects (hub is the only counted source).
605
+ *
606
+ * Never throws.
607
+ */
608
+ export function genomeHubHealth() {
609
+ let hubEntries = 0;
610
+ let sizeBytes = 0;
611
+ let lastLearnedAt = null;
612
+ try {
613
+ const storePath = hubStorePath();
614
+ if (fs.existsSync(storePath)) {
615
+ sizeBytes = fs.statSync(storePath).size;
616
+ const raw = fs.readFileSync(storePath, 'utf8');
617
+ for (const line of raw.split('\n')) {
618
+ const trimmed = line.trim();
619
+ if (!trimmed)
620
+ continue;
621
+ const parsed = safeParseJson(trimmed);
622
+ if (typeof parsed !== 'object' || parsed === null)
623
+ continue;
624
+ const ts = parsed['ts'];
625
+ if (typeof ts !== 'string')
626
+ continue;
627
+ hubEntries++;
628
+ if (!lastLearnedAt || ts > lastLearnedAt)
629
+ lastLearnedAt = ts;
630
+ }
631
+ }
632
+ }
633
+ catch {
634
+ // Best-effort — leave counters at defaults.
635
+ }
636
+ return {
637
+ // Hub is the only counted source here (no portfolio walk).
638
+ totalEntries: hubEntries,
639
+ projects: 0,
640
+ hubEntries,
641
+ sizeBytes,
642
+ lastLearnedAt,
643
+ // No network probe on the reflect path — keep it fully local + offline.
644
+ embeddingsAvailable: false,
645
+ };
646
+ }
647
+ /**
648
+ * Return a GenomeHealth roll-up for the aggregated genome.
649
+ *
650
+ * Collects: total entries, distinct projects, hub entry count, hub store
651
+ * size on disk, most-recent learn timestamp, and whether a local embedding
652
+ * model is available via Ollama.
653
+ *
654
+ * Never throws.
655
+ */
656
+ export function genomeHealth(cfg) {
657
+ // --- Hub store stats (direct file inspection for accuracy) ---
658
+ let hubEntries = 0;
659
+ let sizeBytes = 0;
660
+ let lastLearnedAt = null;
661
+ try {
662
+ const storePath = hubStorePath();
663
+ if (fs.existsSync(storePath)) {
664
+ sizeBytes = fs.statSync(storePath).size;
665
+ const raw = fs.readFileSync(storePath, 'utf8');
666
+ for (const line of raw.split('\n')) {
667
+ const trimmed = line.trim();
668
+ if (!trimmed)
669
+ continue;
670
+ const parsed = safeParseJson(trimmed);
671
+ if (typeof parsed !== 'object' || parsed === null)
672
+ continue;
673
+ const ts = parsed['ts'];
674
+ if (typeof ts !== 'string')
675
+ continue;
676
+ hubEntries++;
677
+ if (!lastLearnedAt || ts > lastLearnedAt) {
678
+ lastLearnedAt = ts;
679
+ }
680
+ }
681
+ }
682
+ }
683
+ catch {
684
+ // Best-effort — leave counters at defaults.
685
+ }
686
+ // --- Total entries + distinct projects (aggregated across all sources) ---
687
+ let totalEntries = 0;
688
+ const projectSet = new Set();
689
+ try {
690
+ const allEntries = loadGenome(cfg);
691
+ totalEntries = allEntries.length;
692
+ for (const e of allEntries) {
693
+ if (e.project)
694
+ projectSet.add(e.project);
695
+ }
696
+ }
697
+ catch {
698
+ // loadGenome never throws, but guard anyway.
699
+ }
700
+ // --- Embeddings availability (sync probe via curl) ---
701
+ const embeddingsAvailable = probeEmbeddingsSync(cfg.models?.ollama ?? 'http://localhost:11434');
702
+ return {
703
+ totalEntries,
704
+ projects: projectSet.size,
705
+ hubEntries,
706
+ sizeBytes,
707
+ lastLearnedAt,
708
+ embeddingsAvailable,
709
+ };
710
+ }
711
+ //# sourceMappingURL=store.js.map