@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,320 @@
1
+ /**
2
+ * playbook.ts — Genome playbook synthesis (M16).
3
+ *
4
+ * Recalls similar past genome entries for a goal and synthesizes a concise
5
+ * "how we've approached this before — what worked / what failed / cost"
6
+ * string using the LOCAL provider. Falls back to a concatenated-recall
7
+ * synthesis on any failure, over-budget condition, or absent provider.
8
+ *
9
+ * Guardrails:
10
+ * - LOCAL-ONLY: no cloud calls; synthesis uses local provider best-effort.
11
+ * - Never throws; always returns a Playbook.
12
+ * - Bounded: synthesis prompt and output are capped; playbookText hard-truncates.
13
+ * - PRIVACY: works only from already-sanitised recall entries (genome never
14
+ * stores secrets/raw payloads per M7/M16 privacy discipline).
15
+ */
16
+ import { recall } from './recall.js';
17
+ // ---------------------------------------------------------------------------
18
+ // Constants
19
+ // ---------------------------------------------------------------------------
20
+ /** Default number of past entries to recall for the playbook. */
21
+ const DEFAULT_RECALL_LIMIT = 5;
22
+ /** Hard cap on the synthesis prompt text fed to the local model (chars). */
23
+ const SYNTHESIS_PROMPT_MAX_CHARS = 4000;
24
+ /** Hard cap on each recalled entry's text included in the prompt (chars). */
25
+ const ENTRY_TEXT_MAX_CHARS = 600;
26
+ /** Timeout (ms) for the local model synthesis call. */
27
+ const SYNTHESIS_TIMEOUT_MS = 20_000;
28
+ /** Elision marker appended when playbookText truncates. */
29
+ const ELISION = '… [truncated]';
30
+ // ---------------------------------------------------------------------------
31
+ // Fallback synthesis — pure, no I/O
32
+ // ---------------------------------------------------------------------------
33
+ /**
34
+ * Build a concatenated-recall synthesis string from recalled entries.
35
+ * Used when no local provider is available or the model call fails.
36
+ * Pure; never throws.
37
+ */
38
+ function fallbackSynthesis(goal, entries) {
39
+ if (entries.length === 0) {
40
+ return `No past entries found for goal: "${goal}". Proceeding without historical context.`;
41
+ }
42
+ const lines = [`Past approaches for: "${goal}"`, ''];
43
+ for (const hit of entries) {
44
+ const { entry } = hit;
45
+ const tags = entry.tags.length > 0 ? ` [${entry.tags.join(', ')}]` : '';
46
+ const ts = entry.ts ? ` (${entry.ts.slice(0, 10)})` : '';
47
+ const text = entry.text.slice(0, ENTRY_TEXT_MAX_CHARS);
48
+ lines.push(`• ${entry.title}${tags}${ts}`);
49
+ if (text)
50
+ lines.push(` ${text.replace(/\n/g, '\n ')}`);
51
+ lines.push('');
52
+ }
53
+ return lines.join('\n').trim();
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Prompt builder — pure, no I/O
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Build the synthesis prompt from recalled entries, capped at
60
+ * SYNTHESIS_PROMPT_MAX_CHARS. Pure; never throws.
61
+ */
62
+ function buildSynthesisPrompt(goal, entries) {
63
+ const header = [
64
+ `Goal: "${goal}"`,
65
+ '',
66
+ 'Past approaches (from genome memory):',
67
+ '',
68
+ ].join('\n');
69
+ let body = '';
70
+ for (const hit of entries) {
71
+ const { entry } = hit;
72
+ const tags = entry.tags.length > 0 ? ` [${entry.tags.join(', ')}]` : '';
73
+ const ts = entry.ts ? ` (${entry.ts.slice(0, 10)})` : '';
74
+ const text = entry.text.slice(0, ENTRY_TEXT_MAX_CHARS);
75
+ const chunk = `### ${entry.title}${tags}${ts}\n${text}\n\n`;
76
+ // Stop adding entries if we'd overflow the prompt cap
77
+ if ((header + body + chunk).length > SYNTHESIS_PROMPT_MAX_CHARS)
78
+ break;
79
+ body += chunk;
80
+ }
81
+ const instruction = [
82
+ 'Given these past approaches to similar goals, provide a concise playbook that covers:',
83
+ '- What worked well',
84
+ '- What failed or caused problems',
85
+ '- Recommended approach and any cost/time considerations',
86
+ '',
87
+ 'Be concise (3-5 bullet points or a short paragraph). Do not repeat the entries verbatim.',
88
+ ].join('\n');
89
+ return `${header}${body}\n${instruction}`;
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Local model synthesis — best-effort, never throws
93
+ // ---------------------------------------------------------------------------
94
+ /**
95
+ * Attempt a local model synthesis call via Ollama (POST /api/chat with
96
+ * stream:false). Returns null on any failure or timeout.
97
+ * LOCAL-ONLY. Never throws.
98
+ */
99
+ async function tryLocalSynthesis(prompt, cfg) {
100
+ try {
101
+ const ollamaBase = (cfg.models?.ollama ?? 'http://localhost:11434').replace(/\/+$/, '');
102
+ const chatUrl = `${ollamaBase}/api/chat`;
103
+ // Probe for available models first — quick check, bounded timeout.
104
+ const probeController = new AbortController();
105
+ const probeTimer = setTimeout(() => probeController.abort(), 3000);
106
+ let modelName = null;
107
+ try {
108
+ const tagsRes = await fetch(`${ollamaBase}/api/tags`, {
109
+ signal: probeController.signal,
110
+ headers: { Accept: 'application/json' },
111
+ });
112
+ if (tagsRes.ok) {
113
+ const body = (await tagsRes.json());
114
+ if (typeof body === 'object' &&
115
+ body !== null &&
116
+ Array.isArray(body['models'])) {
117
+ const models = body.models;
118
+ // Skip embedding-only models; prefer smallest chat model.
119
+ const isEmbed = (n) => /embed|bge|e5|nomic/i.test(n);
120
+ const chatModels = models.map((m) => m.name).filter((n) => !isEmbed(n));
121
+ if (chatModels.length > 0) {
122
+ // Pick smallest by parameter size heuristic
123
+ const sizeOf = (m) => {
124
+ const b = m.match(/(\d+(?:\.\d+)?)\s*b\b/i);
125
+ if (b)
126
+ return parseFloat(b[1]);
127
+ if (/mini|small|tiny|nano|phi/i.test(m))
128
+ return 3;
129
+ return 999;
130
+ };
131
+ chatModels.sort((a, b) => sizeOf(a) - sizeOf(b));
132
+ modelName = chatModels[0];
133
+ }
134
+ }
135
+ }
136
+ }
137
+ catch {
138
+ // Probe failed — no Ollama available
139
+ }
140
+ finally {
141
+ clearTimeout(probeTimer);
142
+ }
143
+ if (!modelName)
144
+ return null;
145
+ // Make the synthesis call with a bounded timeout.
146
+ const chatController = new AbortController();
147
+ const chatTimer = setTimeout(() => chatController.abort(), SYNTHESIS_TIMEOUT_MS);
148
+ try {
149
+ const res = await fetch(chatUrl, {
150
+ method: 'POST',
151
+ signal: chatController.signal,
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: JSON.stringify({
154
+ model: modelName,
155
+ messages: [{ role: 'user', content: prompt }],
156
+ stream: false,
157
+ }),
158
+ });
159
+ if (!res.ok)
160
+ return null;
161
+ const data = (await res.json());
162
+ if (typeof data !== 'object' || data === null)
163
+ return null;
164
+ const d = data;
165
+ const message = (d['message'] ?? {});
166
+ const content = typeof message['content'] === 'string' ? message['content'] : null;
167
+ return content && content.trim().length > 0 ? content.trim() : null;
168
+ }
169
+ catch {
170
+ return null;
171
+ }
172
+ finally {
173
+ clearTimeout(chatTimer);
174
+ }
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ }
180
+ // ---------------------------------------------------------------------------
181
+ // Also try LM Studio as a fallback local provider
182
+ // ---------------------------------------------------------------------------
183
+ /**
184
+ * Attempt synthesis via LM Studio (OpenAI-compat /v1/chat/completions).
185
+ * Returns null on any failure. LOCAL-ONLY. Never throws.
186
+ */
187
+ async function tryLmStudioSynthesis(prompt, cfg) {
188
+ try {
189
+ const lmBase = (cfg.models?.lmstudio ?? 'http://localhost:1234').replace(/\/+$/, '');
190
+ const modelsUrl = `${lmBase}/v1/models`;
191
+ const chatUrl = `${lmBase}/v1/chat/completions`;
192
+ // Probe for available models.
193
+ const probeController = new AbortController();
194
+ const probeTimer = setTimeout(() => probeController.abort(), 3000);
195
+ let modelName = null;
196
+ try {
197
+ const modelsRes = await fetch(modelsUrl, {
198
+ signal: probeController.signal,
199
+ headers: { Accept: 'application/json' },
200
+ });
201
+ if (modelsRes.ok) {
202
+ const body = (await modelsRes.json());
203
+ if (typeof body === 'object' &&
204
+ body !== null &&
205
+ Array.isArray(body['data'])) {
206
+ const data = body.data;
207
+ if (data.length > 0 && typeof data[0].id === 'string') {
208
+ modelName = data[0].id;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ catch {
214
+ // LM Studio not available
215
+ }
216
+ finally {
217
+ clearTimeout(probeTimer);
218
+ }
219
+ if (!modelName)
220
+ return null;
221
+ const chatController = new AbortController();
222
+ const chatTimer = setTimeout(() => chatController.abort(), SYNTHESIS_TIMEOUT_MS);
223
+ try {
224
+ const res = await fetch(chatUrl, {
225
+ method: 'POST',
226
+ signal: chatController.signal,
227
+ headers: { 'Content-Type': 'application/json' },
228
+ body: JSON.stringify({
229
+ model: modelName,
230
+ messages: [{ role: 'user', content: prompt }],
231
+ stream: false,
232
+ }),
233
+ });
234
+ if (!res.ok)
235
+ return null;
236
+ const data = (await res.json());
237
+ if (typeof data !== 'object' || data === null)
238
+ return null;
239
+ const d = data;
240
+ const choices = Array.isArray(d['choices']) ? d['choices'] : [];
241
+ const firstChoice = (choices[0] ?? {});
242
+ const message = (firstChoice['message'] ?? {});
243
+ const content = typeof message['content'] === 'string' ? message['content'] : null;
244
+ return content && content.trim().length > 0 ? content.trim() : null;
245
+ }
246
+ catch {
247
+ return null;
248
+ }
249
+ finally {
250
+ clearTimeout(chatTimer);
251
+ }
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ }
257
+ // ---------------------------------------------------------------------------
258
+ // Public API
259
+ // ---------------------------------------------------------------------------
260
+ /**
261
+ * Build a playbook for `goal` by recalling similar past entries and
262
+ * synthesizing a concise guidance string via the LOCAL provider.
263
+ *
264
+ * Falls back to a concatenated-recall synthesis on any failure, over-budget
265
+ * condition, or when no local provider is reachable.
266
+ *
267
+ * Never throws. Returns `{ goal, entries, synthesis }`.
268
+ */
269
+ export async function buildPlaybook(goal, cfg, opts) {
270
+ // Step 1: recall similar past entries
271
+ let entries = [];
272
+ try {
273
+ const limit = opts?.limit ?? cfg.genome?.maxRecall ?? DEFAULT_RECALL_LIMIT;
274
+ entries = await recall(goal, cfg, { limit });
275
+ }
276
+ catch {
277
+ // recall never throws per its own contract; guard defensively anyway
278
+ entries = [];
279
+ }
280
+ // If no entries recalled, return immediately with a minimal playbook
281
+ if (entries.length === 0) {
282
+ return {
283
+ goal,
284
+ entries,
285
+ synthesis: `No past entries found for goal: "${goal}". Proceeding without historical context.`,
286
+ };
287
+ }
288
+ // Step 2: build the synthesis prompt
289
+ const prompt = buildSynthesisPrompt(goal, entries);
290
+ // Step 3: attempt local synthesis (Ollama first, then LM Studio)
291
+ let synthesis = null;
292
+ synthesis = await tryLocalSynthesis(prompt, cfg);
293
+ if (!synthesis) {
294
+ synthesis = await tryLmStudioSynthesis(prompt, cfg);
295
+ }
296
+ // Step 4: fall back to concatenated-recall synthesis if model unavailable/failed
297
+ if (!synthesis) {
298
+ synthesis = fallbackSynthesis(goal, entries);
299
+ }
300
+ return { goal, entries, synthesis };
301
+ }
302
+ /**
303
+ * Render a playbook to an injection-ready string capped at `maxChars`.
304
+ * Appends an elision marker when truncated. Pure; never throws.
305
+ */
306
+ export function playbookText(p, maxChars) {
307
+ if (maxChars <= 0)
308
+ return '';
309
+ const header = `=== Playbook: ${p.goal} ===\n`;
310
+ const body = p.synthesis;
311
+ const full = header + body;
312
+ if (full.length <= maxChars)
313
+ return full;
314
+ // Hard truncate with elision marker
315
+ const cutoff = maxChars - ELISION.length;
316
+ if (cutoff <= 0)
317
+ return ELISION.slice(0, maxChars);
318
+ return full.slice(0, cutoff) + ELISION;
319
+ }
320
+ //# sourceMappingURL=playbook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playbook.js","sourceRoot":"","sources":["../../../src/core/genome/playbook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * recall.ts — Genome recall: keyword/TF-IDF scoring + optional Ollama embedding rerank.
3
+ *
4
+ * Rules:
5
+ * - keywordScore is pure, synchronous, deterministic; returns 0..∞ (0 = no overlap).
6
+ * - recall never throws; embedding rerank is best-effort, falls back to keyword silently.
7
+ * - Local-first: embeddings only via local Ollama /api/embeddings. Never cloud.
8
+ * - Offline-capable: keyword path always works without network.
9
+ * - Bounded: caps candidates sent to embedding reranker at EMBED_CANDIDATE_CAP.
10
+ */
11
+ import type { AshlrConfig, GenomeEntry, RecallHit } from '../types.js';
12
+ /**
13
+ * Compute a keyword overlap score between a query and a genome entry.
14
+ *
15
+ * Approach: TF-style term overlap.
16
+ * - Tokenise the query into a unique term set.
17
+ * - For each query term, sum its frequency in the entry's weighted text bag:
18
+ * title tokens × weight 3
19
+ * tag tokens × weight 2
20
+ * body tokens × weight 1
21
+ * - Normalise by (queryTermCount × maxPossibleHitsPerTerm) to keep roughly 0..1
22
+ * (may exceed 1 for very short queries with many hits — intentional).
23
+ *
24
+ * Returns 0 when there is no overlap at all.
25
+ */
26
+ export declare function keywordScore(query: string, entry: GenomeEntry): number;
27
+ /**
28
+ * Recall the top-k genome entries most relevant to `query`.
29
+ *
30
+ * 1. Load all genome entries via store.loadGenome (bounded, never throws).
31
+ * 2. Score all entries by keyword/TF-IDF overlap.
32
+ * 3. If opts.embeddings is true (or not explicitly false) AND an embedding
33
+ * model is available at the configured Ollama endpoint:
34
+ * a. Take up to EMBED_CANDIDATE_CAP top keyword candidates.
35
+ * b. Fetch embeddings for query + each candidate in parallel.
36
+ * c. Re-sort by cosine similarity; tag hits as method:'embedding'.
37
+ * d. On ANY failure, silently fall back to keyword results.
38
+ * 4. Return top `limit` hits, sorted descending by score, with method tag.
39
+ *
40
+ * Never throws. Never calls cloud. Offline-capable (keyword path).
41
+ */
42
+ export declare function recall(query: string, cfg: AshlrConfig, opts?: {
43
+ limit?: number;
44
+ embeddings?: boolean;
45
+ }): Promise<RecallHit[]>;
@@ -0,0 +1,293 @@
1
+ /**
2
+ * recall.ts — Genome recall: keyword/TF-IDF scoring + optional Ollama embedding rerank.
3
+ *
4
+ * Rules:
5
+ * - keywordScore is pure, synchronous, deterministic; returns 0..∞ (0 = no overlap).
6
+ * - recall never throws; embedding rerank is best-effort, falls back to keyword silently.
7
+ * - Local-first: embeddings only via local Ollama /api/embeddings. Never cloud.
8
+ * - Offline-capable: keyword path always works without network.
9
+ * - Bounded: caps candidates sent to embedding reranker at EMBED_CANDIDATE_CAP.
10
+ */
11
+ import { loadGenome } from './store.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Constants
14
+ // ---------------------------------------------------------------------------
15
+ /** Max candidates forwarded to embedding reranker to keep latency bounded. */
16
+ const EMBED_CANDIDATE_CAP = 20;
17
+ /** Timeout (ms) for a single Ollama /api/embeddings request. */
18
+ const EMBED_TIMEOUT_MS = 8000;
19
+ /** Minimum keyword score for an entry to be included at all (0 = include everything). */
20
+ const MIN_SCORE = 0;
21
+ // ---------------------------------------------------------------------------
22
+ // Tokenisation helpers
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Tokenise text: lowercase, split on non-alphanumeric boundaries, drop empties.
26
+ * Returns a deduplicated term array for IDF-style calcs + a full term-count map.
27
+ */
28
+ function tokenize(text) {
29
+ return text
30
+ .toLowerCase()
31
+ .split(/[^a-z0-9]+/)
32
+ .filter((t) => t.length > 0);
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // keywordScore — pure, synchronous, deterministic
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Compute a keyword overlap score between a query and a genome entry.
39
+ *
40
+ * Approach: TF-style term overlap.
41
+ * - Tokenise the query into a unique term set.
42
+ * - For each query term, sum its frequency in the entry's weighted text bag:
43
+ * title tokens × weight 3
44
+ * tag tokens × weight 2
45
+ * body tokens × weight 1
46
+ * - Normalise by (queryTermCount × maxPossibleHitsPerTerm) to keep roughly 0..1
47
+ * (may exceed 1 for very short queries with many hits — intentional).
48
+ *
49
+ * Returns 0 when there is no overlap at all.
50
+ */
51
+ export function keywordScore(query, entry) {
52
+ const queryTerms = new Set(tokenize(query));
53
+ if (queryTerms.size === 0)
54
+ return 0;
55
+ // Build a weighted token bag for the entry
56
+ const titleTokens = tokenize(entry.title);
57
+ const textTokens = tokenize(entry.text);
58
+ const tagTokens = entry.tags.flatMap((tag) => tokenize(tag));
59
+ // Weighted frequency map: title ×3, tags ×2, body ×1
60
+ const weightedFreq = new Map();
61
+ for (const t of titleTokens) {
62
+ weightedFreq.set(t, (weightedFreq.get(t) ?? 0) + 3);
63
+ }
64
+ for (const t of tagTokens) {
65
+ weightedFreq.set(t, (weightedFreq.get(t) ?? 0) + 2);
66
+ }
67
+ for (const t of textTokens) {
68
+ weightedFreq.set(t, (weightedFreq.get(t) ?? 0) + 1);
69
+ }
70
+ if (weightedFreq.size === 0)
71
+ return 0;
72
+ // Sum weighted hits for query terms
73
+ let hits = 0;
74
+ for (const term of queryTerms) {
75
+ hits += weightedFreq.get(term) ?? 0;
76
+ }
77
+ if (hits === 0)
78
+ return 0;
79
+ // Normalise: divide by queryTermCount to get average weighted hits per query term.
80
+ // This produces a score ≥ 0 where higher = more relevant.
81
+ return hits / queryTerms.size;
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Embedding helpers — best-effort, local Ollama only
85
+ // ---------------------------------------------------------------------------
86
+ /**
87
+ * Detect whether any embedding-capable model is available at the Ollama base URL.
88
+ * Heuristic: look for well-known embedding model name substrings in the /api/tags list.
89
+ * Returns { available: true, model } or { available: false }.
90
+ * Never throws.
91
+ */
92
+ async function detectEmbeddingModel(ollamaBase) {
93
+ const controller = new AbortController();
94
+ const timer = setTimeout(() => controller.abort(), 3000);
95
+ try {
96
+ const res = await fetch(`${ollamaBase.replace(/\/+$/, '')}/api/tags`, {
97
+ signal: controller.signal,
98
+ headers: { Accept: 'application/json' },
99
+ });
100
+ if (!res.ok)
101
+ return { available: false };
102
+ const body = (await res.json());
103
+ if (typeof body !== 'object' ||
104
+ body === null ||
105
+ !Array.isArray(body['models'])) {
106
+ return { available: false };
107
+ }
108
+ const models = body.models;
109
+ // Prefer bge, nomic-embed, mxbai-embed, all-minilm — common local embedding models
110
+ const EMBED_HINTS = ['bge', 'nomic-embed', 'mxbai-embed', 'all-minilm', 'embed'];
111
+ for (const hint of EMBED_HINTS) {
112
+ const found = models.find((m) => m.name.toLowerCase().includes(hint));
113
+ if (found)
114
+ return { available: true, model: found.name };
115
+ }
116
+ return { available: false };
117
+ }
118
+ catch {
119
+ return { available: false };
120
+ }
121
+ finally {
122
+ clearTimeout(timer);
123
+ }
124
+ }
125
+ /**
126
+ * Fetch an embedding vector for `text` from local Ollama.
127
+ * Uses POST /api/embeddings with { model, prompt }.
128
+ * Returns null on any error (never throws).
129
+ */
130
+ async function fetchEmbedding(ollamaBase, model, text) {
131
+ const controller = new AbortController();
132
+ const timer = setTimeout(() => controller.abort(), EMBED_TIMEOUT_MS);
133
+ try {
134
+ const res = await fetch(`${ollamaBase.replace(/\/+$/, '')}/api/embeddings`, {
135
+ method: 'POST',
136
+ signal: controller.signal,
137
+ headers: { 'Content-Type': 'application/json' },
138
+ body: JSON.stringify({ model, prompt: text }),
139
+ });
140
+ if (!res.ok)
141
+ return null;
142
+ const body = (await res.json());
143
+ if (typeof body !== 'object' ||
144
+ body === null ||
145
+ !Array.isArray(body['embedding'])) {
146
+ return null;
147
+ }
148
+ const embedding = body.embedding;
149
+ // Validate: all elements must be numbers
150
+ if (!embedding.every((v) => typeof v === 'number'))
151
+ return null;
152
+ return embedding;
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ finally {
158
+ clearTimeout(timer);
159
+ }
160
+ }
161
+ /**
162
+ * Compute cosine similarity between two vectors.
163
+ * Returns 0 on zero-length or mismatched vectors.
164
+ */
165
+ function cosine(a, b) {
166
+ if (a.length === 0 || a.length !== b.length)
167
+ return 0;
168
+ let dot = 0;
169
+ let normA = 0;
170
+ let normB = 0;
171
+ for (let i = 0; i < a.length; i++) {
172
+ dot += a[i] * b[i];
173
+ normA += a[i] * a[i];
174
+ normB += b[i] * b[i];
175
+ }
176
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
177
+ if (denom === 0)
178
+ return 0;
179
+ return dot / denom;
180
+ }
181
+ /**
182
+ * Build a single text blob from an entry for embedding (title + tags + text).
183
+ * Truncated to keep embedding calls bounded.
184
+ */
185
+ function entryToEmbedText(entry) {
186
+ const parts = [
187
+ entry.title,
188
+ entry.tags.join(' '),
189
+ entry.text,
190
+ ].filter(Boolean);
191
+ return parts.join(' ').slice(0, 2000);
192
+ }
193
+ // ---------------------------------------------------------------------------
194
+ // Main recall function
195
+ // ---------------------------------------------------------------------------
196
+ /**
197
+ * Recall the top-k genome entries most relevant to `query`.
198
+ *
199
+ * 1. Load all genome entries via store.loadGenome (bounded, never throws).
200
+ * 2. Score all entries by keyword/TF-IDF overlap.
201
+ * 3. If opts.embeddings is true (or not explicitly false) AND an embedding
202
+ * model is available at the configured Ollama endpoint:
203
+ * a. Take up to EMBED_CANDIDATE_CAP top keyword candidates.
204
+ * b. Fetch embeddings for query + each candidate in parallel.
205
+ * c. Re-sort by cosine similarity; tag hits as method:'embedding'.
206
+ * d. On ANY failure, silently fall back to keyword results.
207
+ * 4. Return top `limit` hits, sorted descending by score, with method tag.
208
+ *
209
+ * Never throws. Never calls cloud. Offline-capable (keyword path).
210
+ */
211
+ export async function recall(query, cfg, opts) {
212
+ const limit = opts?.limit ?? cfg.genome?.maxRecall ?? 5;
213
+ // Load all entries defensively (store never throws)
214
+ let entries;
215
+ try {
216
+ entries = loadGenome(cfg);
217
+ }
218
+ catch {
219
+ entries = [];
220
+ }
221
+ if (entries.length === 0 || query.trim().length === 0)
222
+ return [];
223
+ // --- Step 1: keyword scoring ---
224
+ const keywordHits = entries
225
+ .map((entry) => ({
226
+ entry,
227
+ score: keywordScore(query, entry),
228
+ method: 'keyword',
229
+ }))
230
+ .filter((h) => h.score > MIN_SCORE)
231
+ .sort((a, b) => b.score - a.score);
232
+ // Embeddings are strictly opt-in per the contract: only rerank when the
233
+ // caller explicitly passes embeddings:true. The default (and any non-true
234
+ // value) returns the keyword path immediately — keeping recall offline-first,
235
+ // deterministic, and never surfacing zero-keyword-overlap entries that an
236
+ // embedding rerank would otherwise pull in.
237
+ if (opts?.embeddings !== true) {
238
+ return keywordHits.slice(0, limit);
239
+ }
240
+ // --- Step 2: optional embedding rerank ---
241
+ // Take top candidates (by keyword score) for embedding reranking
242
+ // Include all entries with positive keyword score, then pad with remaining
243
+ // entries up to EMBED_CANDIDATE_CAP so we don't miss zero-keyword relevant entries.
244
+ const positiveHits = keywordHits.slice(0, EMBED_CANDIDATE_CAP);
245
+ const positiveIds = new Set(positiveHits.map((h) => h.entry.id));
246
+ // Fill remaining slots with entries that had no keyword overlap (sorted by ts desc)
247
+ const remaining = entries
248
+ .filter((e) => !positiveIds.has(e.id))
249
+ .slice(0, Math.max(0, EMBED_CANDIDATE_CAP - positiveHits.length))
250
+ .map((entry) => ({ entry, score: 0, method: 'keyword' }));
251
+ const candidates = [...positiveHits, ...remaining];
252
+ try {
253
+ const ollamaBase = cfg.models.ollama ?? 'http://localhost:11434';
254
+ const modelProbe = await detectEmbeddingModel(ollamaBase);
255
+ if (!modelProbe.available) {
256
+ // No embedding model — return keyword results
257
+ return keywordHits.slice(0, limit);
258
+ }
259
+ const { model } = modelProbe;
260
+ // Fetch query embedding + all candidate embeddings in parallel
261
+ const queryEmbedPromise = fetchEmbedding(ollamaBase, model, query.slice(0, 2000));
262
+ const candidateEmbedPromises = candidates.map((h) => fetchEmbedding(ollamaBase, model, entryToEmbedText(h.entry)));
263
+ const [queryVec, ...candidateVecs] = await Promise.all([
264
+ queryEmbedPromise,
265
+ ...candidateEmbedPromises,
266
+ ]);
267
+ // If query embedding failed, fall back to keyword
268
+ if (queryVec === null) {
269
+ return keywordHits.slice(0, limit);
270
+ }
271
+ // Build reranked hits; fall back per-entry to keyword score if embedding failed
272
+ const reranked = candidates.map((hit, i) => {
273
+ const vec = candidateVecs[i];
274
+ if (vec === null) {
275
+ // Embedding unavailable for this entry — keep keyword score
276
+ return hit;
277
+ }
278
+ return {
279
+ entry: hit.entry,
280
+ score: cosine(queryVec, vec),
281
+ method: 'embedding',
282
+ };
283
+ });
284
+ // Sort descending by embedding score
285
+ reranked.sort((a, b) => b.score - a.score);
286
+ return reranked.slice(0, limit);
287
+ }
288
+ catch {
289
+ // Any unexpected failure: return keyword results
290
+ return keywordHits.slice(0, limit);
291
+ }
292
+ }
293
+ //# sourceMappingURL=recall.js.map