akm-cli 0.9.0-beta.9 → 0.9.0-rc.1

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 (381) hide show
  1. package/CHANGELOG.md +715 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  39. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  40. package/dist/assets/templates/html/health.html +281 -111
  41. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  42. package/dist/assets/wiki/schema-template.md +4 -4
  43. package/dist/cli/clack.js +56 -0
  44. package/dist/cli/config-migrate.js +7 -1
  45. package/dist/cli/confirm.js +1 -1
  46. package/dist/cli/parse-args.js +46 -1
  47. package/dist/cli/shared.js +28 -0
  48. package/dist/cli.js +25 -21
  49. package/dist/commands/agent/agent-dispatch.js +3 -2
  50. package/dist/commands/agent/agent-support.js +0 -7
  51. package/dist/commands/agent/contribute-cli.js +26 -7
  52. package/dist/commands/config-cli.js +26 -13
  53. package/dist/commands/env/child-env.js +47 -0
  54. package/dist/commands/env/env-binding.js +95 -0
  55. package/dist/commands/env/env-cli.js +228 -292
  56. package/dist/commands/env/env.js +14 -67
  57. package/dist/commands/env/secret-cli.js +140 -138
  58. package/dist/commands/feedback-cli.js +156 -155
  59. package/dist/commands/graph/graph-cli.js +5 -13
  60. package/dist/commands/graph/graph.js +3 -3
  61. package/dist/commands/health/advisories.js +151 -0
  62. package/dist/commands/health/checks.js +103 -16
  63. package/dist/commands/health/html-report.js +447 -81
  64. package/dist/commands/health/improve-metrics.js +771 -0
  65. package/dist/commands/health/llm-usage.js +65 -0
  66. package/dist/commands/health/md-report.js +103 -0
  67. package/dist/commands/health/metrics.js +278 -0
  68. package/dist/commands/health/stash-exposure.js +46 -0
  69. package/dist/commands/health/surfaces.js +216 -0
  70. package/dist/commands/health/task-runs.js +135 -0
  71. package/dist/commands/health/types.js +26 -0
  72. package/dist/commands/health/windows.js +195 -0
  73. package/dist/commands/health.js +91 -1091
  74. package/dist/commands/improve/anti-collapse.js +170 -0
  75. package/dist/commands/improve/calibration.js +161 -0
  76. package/dist/commands/improve/collapse-detector.js +421 -0
  77. package/dist/commands/improve/consolidate/chunking.js +141 -0
  78. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  79. package/dist/commands/improve/consolidate/merge.js +145 -0
  80. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  81. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  82. package/dist/commands/improve/consolidate.js +1295 -1277
  83. package/dist/commands/improve/dedup.js +482 -0
  84. package/dist/commands/improve/distill/content-repair.js +202 -0
  85. package/dist/commands/improve/distill/promote-memory.js +229 -0
  86. package/dist/commands/improve/distill/quality-gate.js +236 -0
  87. package/dist/commands/improve/distill-guards.js +127 -0
  88. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  89. package/dist/commands/improve/distill.js +228 -605
  90. package/dist/commands/improve/eligibility.js +434 -0
  91. package/dist/commands/improve/encoding-salience.js +205 -0
  92. package/dist/commands/improve/extract-cli.js +179 -59
  93. package/dist/commands/improve/extract-prompt.js +54 -3
  94. package/dist/commands/improve/extract-watch.js +140 -0
  95. package/dist/commands/improve/extract.js +409 -43
  96. package/dist/commands/improve/feedback-valence.js +54 -0
  97. package/dist/commands/improve/hot-probation.js +45 -0
  98. package/dist/commands/improve/improve-auto-accept.js +157 -10
  99. package/dist/commands/improve/improve-cli.js +115 -73
  100. package/dist/commands/improve/improve-profiles.js +28 -8
  101. package/dist/commands/improve/improve-result-file.js +15 -25
  102. package/dist/commands/improve/improve-session.js +58 -0
  103. package/dist/commands/improve/improve.js +485 -2764
  104. package/dist/commands/improve/locks.js +154 -0
  105. package/dist/commands/improve/loop-stages.js +1100 -0
  106. package/dist/commands/improve/memory/memory-belief.js +14 -15
  107. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  108. package/dist/commands/improve/memory/memory-improve.js +27 -27
  109. package/dist/commands/improve/outcome-loop.js +270 -0
  110. package/dist/commands/improve/preparation.js +2002 -0
  111. package/dist/commands/improve/proactive-maintenance.js +37 -35
  112. package/dist/commands/improve/procedural.js +398 -0
  113. package/dist/commands/improve/recombine.js +818 -0
  114. package/dist/commands/improve/reflect-noise.js +0 -0
  115. package/dist/commands/improve/reflect.js +206 -45
  116. package/dist/commands/improve/salience.js +455 -0
  117. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  118. package/dist/commands/improve/shared.js +51 -0
  119. package/dist/commands/improve/triage.js +93 -0
  120. package/dist/commands/lint/agent-linter.js +19 -24
  121. package/dist/commands/lint/base-linter.js +173 -60
  122. package/dist/commands/lint/command-linter.js +19 -24
  123. package/dist/commands/lint/env-key-rules.js +38 -1
  124. package/dist/commands/lint/fact-linter.js +39 -0
  125. package/dist/commands/lint/index.js +31 -13
  126. package/dist/commands/lint/memory-linter.js +1 -1
  127. package/dist/commands/lint/registry.js +7 -2
  128. package/dist/commands/lint/task-linter.js +3 -3
  129. package/dist/commands/lint/workflow-linter.js +26 -1
  130. package/dist/commands/observability-cli.js +4 -4
  131. package/dist/commands/proposal/drain-policies.js +13 -4
  132. package/dist/commands/proposal/drain.js +45 -51
  133. package/dist/commands/proposal/legacy-import.js +115 -0
  134. package/dist/commands/proposal/proposal-cli.js +24 -34
  135. package/dist/commands/proposal/proposal.js +2 -1
  136. package/dist/commands/proposal/propose.js +8 -3
  137. package/dist/commands/proposal/repository.js +829 -0
  138. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  139. package/dist/commands/proposal/validators/proposals.js +93 -895
  140. package/dist/commands/read/curate.js +410 -111
  141. package/dist/commands/read/knowledge.js +10 -3
  142. package/dist/commands/read/remember-cli.js +133 -138
  143. package/dist/commands/read/search-cli.js +15 -8
  144. package/dist/commands/read/search.js +22 -11
  145. package/dist/commands/read/show.js +106 -14
  146. package/dist/commands/registry-cli.js +76 -87
  147. package/dist/commands/remember.js +11 -12
  148. package/dist/commands/sources/add-cli.js +91 -95
  149. package/dist/commands/sources/history.js +1 -1
  150. package/dist/commands/sources/init.js +66 -18
  151. package/dist/commands/sources/installed-stashes.js +11 -3
  152. package/dist/commands/sources/migration-help.js +7 -4
  153. package/dist/commands/sources/schema-repair.js +44 -46
  154. package/dist/commands/sources/self-update.js +2 -2
  155. package/dist/commands/sources/source-add.js +7 -3
  156. package/dist/commands/sources/sources-cli.js +3 -3
  157. package/dist/commands/sources/stash-cli.js +19 -39
  158. package/dist/commands/sources/stash-skeleton.js +57 -8
  159. package/dist/commands/tasks/default-tasks.js +15 -2
  160. package/dist/commands/tasks/tasks-cli.js +20 -29
  161. package/dist/commands/tasks/tasks.js +39 -11
  162. package/dist/commands/wiki-cli.js +23 -38
  163. package/dist/commands/workflow-cli.js +291 -13
  164. package/dist/core/asset/asset-registry.js +3 -1
  165. package/dist/core/asset/asset-spec.js +79 -5
  166. package/dist/core/asset/frontmatter.js +188 -167
  167. package/dist/core/asset/markdown.js +8 -0
  168. package/dist/core/authoring-rules.js +92 -0
  169. package/dist/core/common.js +4 -23
  170. package/dist/core/concurrent.js +10 -1
  171. package/dist/core/config/config-io.js +10 -1
  172. package/dist/core/config/config-migration.js +18 -40
  173. package/dist/core/config/config-schema.js +403 -62
  174. package/dist/core/config/config-types.js +3 -3
  175. package/dist/core/config/config.js +67 -22
  176. package/dist/core/deep-merge.js +38 -0
  177. package/dist/core/errors.js +1 -0
  178. package/dist/core/eval/rank-metrics.js +113 -0
  179. package/dist/core/events.js +4 -7
  180. package/dist/core/improve-types.js +47 -8
  181. package/dist/core/json-schema.js +142 -0
  182. package/dist/core/logs-db.js +14 -75
  183. package/dist/core/parse.js +36 -16
  184. package/dist/core/paths.js +18 -18
  185. package/dist/core/standards/resolve-standards-context.js +87 -0
  186. package/dist/core/standards/resolve-stash-standards.js +99 -0
  187. package/dist/core/standards/resolve-type-conventions.js +66 -0
  188. package/dist/core/state/migrations.js +770 -0
  189. package/dist/core/state-db.js +132 -1126
  190. package/dist/core/structured.js +69 -0
  191. package/dist/core/time.js +53 -0
  192. package/dist/core/warn.js +21 -0
  193. package/dist/core/write-source.js +37 -0
  194. package/dist/indexer/db/db.js +261 -770
  195. package/dist/indexer/db/entry-mapper.js +41 -0
  196. package/dist/indexer/db/graph-db.js +129 -86
  197. package/dist/indexer/db/llm-cache.js +2 -2
  198. package/dist/indexer/db/schema.js +516 -0
  199. package/dist/indexer/ensure-index.js +36 -92
  200. package/dist/indexer/feedback/utility-policy.js +75 -0
  201. package/dist/indexer/graph/graph-boost.js +51 -41
  202. package/dist/indexer/graph/graph-extraction.js +207 -4
  203. package/dist/indexer/index-writer-lock.js +18 -11
  204. package/dist/indexer/index-written-assets.js +105 -0
  205. package/dist/indexer/indexer.js +182 -204
  206. package/dist/indexer/passes/dir-staleness.js +114 -0
  207. package/dist/indexer/passes/memory-inference.js +13 -5
  208. package/dist/indexer/passes/metadata.js +20 -0
  209. package/dist/indexer/read-preflight.js +23 -0
  210. package/dist/indexer/search/db-search.js +89 -13
  211. package/dist/indexer/search/fts-query.js +51 -0
  212. package/dist/indexer/search/ranking-contributors.js +95 -9
  213. package/dist/indexer/search/ranking.js +79 -3
  214. package/dist/indexer/search/search-fields.js +6 -0
  215. package/dist/indexer/search/search-source.js +32 -21
  216. package/dist/indexer/search/semantic-status.js +4 -0
  217. package/dist/indexer/walk/matchers.js +48 -0
  218. package/dist/indexer/walk/walker.js +21 -13
  219. package/dist/integrations/agent/builders.js +41 -13
  220. package/dist/integrations/agent/config.js +20 -59
  221. package/dist/integrations/agent/detect.js +9 -0
  222. package/dist/integrations/agent/index.js +3 -19
  223. package/dist/integrations/agent/model-aliases.js +16 -2
  224. package/dist/integrations/agent/profiles.js +79 -6
  225. package/dist/integrations/agent/prompts.js +75 -9
  226. package/dist/integrations/agent/runner-dispatch.js +83 -0
  227. package/dist/integrations/agent/runner.js +13 -9
  228. package/dist/integrations/agent/spawn.js +206 -81
  229. package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
  230. package/dist/integrations/harnesses/aider/index.js +58 -0
  231. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  232. package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
  233. package/dist/integrations/harnesses/amazonq/index.js +59 -0
  234. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  235. package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
  236. package/dist/integrations/harnesses/claude/index.js +27 -23
  237. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  238. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  239. package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
  240. package/dist/integrations/harnesses/codex/index.js +63 -0
  241. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  242. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  243. package/dist/integrations/harnesses/copilot/index.js +60 -0
  244. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  245. package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
  246. package/dist/integrations/harnesses/gemini/index.js +60 -0
  247. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  248. package/dist/integrations/harnesses/index.js +28 -7
  249. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  250. package/dist/integrations/harnesses/opencode/index.js +17 -16
  251. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  252. package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
  253. package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
  254. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
  255. package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
  256. package/dist/integrations/harnesses/openhands/index.js +58 -0
  257. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  258. package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
  259. package/dist/integrations/harnesses/pi/index.js +58 -0
  260. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  261. package/dist/integrations/harnesses/types.js +8 -0
  262. package/dist/integrations/session-logs/index.js +40 -11
  263. package/dist/llm/call-ai.js +2 -2
  264. package/dist/llm/client.js +34 -11
  265. package/dist/llm/embedder.js +67 -4
  266. package/dist/llm/embedders/cache.js +3 -1
  267. package/dist/llm/embedders/deterministic.js +66 -0
  268. package/dist/llm/embedders/local.js +73 -3
  269. package/dist/llm/feature-gate.js +16 -15
  270. package/dist/llm/graph-extract.js +67 -44
  271. package/dist/llm/memory-infer-impl.js +138 -0
  272. package/dist/llm/memory-infer.js +1 -127
  273. package/dist/llm/metadata-enhance.js +44 -31
  274. package/dist/llm/structured-call.js +49 -0
  275. package/dist/migrate-storage-node.mjs +8 -0
  276. package/dist/output/context.js +5 -5
  277. package/dist/output/renderers.js +87 -15
  278. package/dist/output/shapes/curate.js +14 -2
  279. package/dist/output/shapes/helpers.js +0 -3
  280. package/dist/output/shapes/passthrough.js +6 -1
  281. package/dist/output/text/helpers.js +241 -2
  282. package/dist/output/text/workflow.js +4 -1
  283. package/dist/registry/providers/skills-sh.js +21 -147
  284. package/dist/registry/providers/static-index.js +15 -157
  285. package/dist/registry/resolve.js +27 -9
  286. package/dist/runtime.js +25 -1
  287. package/dist/schemas/akm-config.json +14225 -0
  288. package/dist/schemas/akm-workflow.json +328 -0
  289. package/dist/scripts/migrate-storage.js +2743 -8390
  290. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
  291. package/dist/setup/detect.js +9 -0
  292. package/dist/setup/legacy-config.js +106 -0
  293. package/dist/setup/prompt.js +57 -0
  294. package/dist/setup/providers.js +14 -0
  295. package/dist/setup/registry-stash-loader.js +12 -0
  296. package/dist/setup/semantic-assets.js +124 -0
  297. package/dist/setup/setup.js +52 -1614
  298. package/dist/setup/steps/connection.js +734 -0
  299. package/dist/setup/steps/output.js +31 -0
  300. package/dist/setup/steps/platforms.js +124 -0
  301. package/dist/setup/steps/semantic.js +27 -0
  302. package/dist/setup/steps/sources.js +222 -0
  303. package/dist/setup/steps/stashdir.js +42 -0
  304. package/dist/setup/steps/tasks.js +152 -0
  305. package/dist/sources/include.js +6 -2
  306. package/dist/sources/providers/filesystem.js +0 -1
  307. package/dist/sources/providers/git-install.js +210 -0
  308. package/dist/sources/providers/git-provider.js +234 -0
  309. package/dist/sources/providers/git-stash.js +248 -0
  310. package/dist/sources/providers/git.js +10 -661
  311. package/dist/sources/providers/npm.js +2 -6
  312. package/dist/sources/providers/provider-utils.js +13 -7
  313. package/dist/sources/providers/sync-from-ref.js +9 -1
  314. package/dist/sources/providers/website.js +9 -5
  315. package/dist/sources/website-ingest.js +187 -29
  316. package/dist/sources/wiki-fetchers/registry.js +53 -0
  317. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  318. package/dist/storage/database.js +45 -10
  319. package/dist/storage/managed-db.js +82 -0
  320. package/dist/storage/repositories/canaries-repository.js +107 -0
  321. package/dist/storage/repositories/consolidation-repository.js +38 -0
  322. package/dist/storage/repositories/embeddings-repository.js +72 -0
  323. package/dist/storage/repositories/events-repository.js +187 -0
  324. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  325. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  326. package/dist/storage/repositories/index-db.js +14 -8
  327. package/dist/storage/repositories/proposals-repository.js +220 -0
  328. package/dist/storage/repositories/recombine-repository.js +213 -0
  329. package/dist/storage/repositories/registry-cache.js +93 -0
  330. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  331. package/dist/storage/repositories/task-history-repository.js +93 -0
  332. package/dist/storage/repositories/workflow-runs-repository.js +189 -1
  333. package/dist/storage/sqlite-pragmas.js +146 -0
  334. package/dist/tasks/backends/cron.js +1 -1
  335. package/dist/tasks/backends/index.js +9 -0
  336. package/dist/tasks/backends/launchd.js +1 -1
  337. package/dist/tasks/backends/schtasks.js +1 -1
  338. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  339. package/dist/tasks/runner.js +15 -13
  340. package/dist/text-import-hook.mjs +1 -1
  341. package/dist/wiki/wiki.js +52 -11
  342. package/dist/workflows/authoring/authoring.js +123 -10
  343. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  344. package/dist/workflows/cli.js +5 -0
  345. package/dist/workflows/db.js +138 -4
  346. package/dist/workflows/exec/brief.js +484 -0
  347. package/dist/workflows/exec/native-executor.js +975 -0
  348. package/dist/workflows/exec/param-secrets.js +115 -0
  349. package/dist/workflows/exec/report.js +1295 -0
  350. package/dist/workflows/exec/run-workflow.js +596 -0
  351. package/dist/workflows/exec/scheduler.js +100 -0
  352. package/dist/workflows/exec/step-work.js +1156 -0
  353. package/dist/workflows/exec/unit-writer.js +23 -0
  354. package/dist/workflows/exec/watch.js +116 -0
  355. package/dist/workflows/exec/worktree.js +171 -0
  356. package/dist/workflows/ir/compile.js +388 -0
  357. package/dist/workflows/ir/params.js +54 -0
  358. package/dist/workflows/ir/plan-hash.js +33 -0
  359. package/dist/workflows/ir/schema.js +4 -0
  360. package/dist/workflows/parser.js +3 -1
  361. package/dist/workflows/program/expressions.js +369 -0
  362. package/dist/workflows/program/parser.js +760 -0
  363. package/dist/workflows/program/project.js +105 -0
  364. package/dist/workflows/program/schema.js +54 -0
  365. package/dist/workflows/renderer.js +82 -5
  366. package/dist/workflows/runtime/agent-identity.js +59 -14
  367. package/dist/workflows/runtime/runs.js +248 -153
  368. package/dist/workflows/runtime/unit-checkin.js +45 -0
  369. package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
  370. package/dist/workflows/validate-summary.js +26 -10
  371. package/dist/workflows/validator.js +1 -1
  372. package/docs/README.md +69 -18
  373. package/docs/data-and-telemetry.md +7 -5
  374. package/docs/migration/release-notes/0.7.0.md +1 -1
  375. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  376. package/docs/migration/release-notes/0.9.0.md +39 -0
  377. package/package.json +10 -10
  378. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  379. package/dist/commands/db-cli.js +0 -23
  380. package/dist/indexer/db/db-backup.js +0 -376
  381. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -8,16 +8,23 @@
8
8
  * high-signal set of stash + registry hits and enrich each with the data
9
9
  * needed to act (ref, run, parameters, follow-up command).
10
10
  *
11
- * The exported `akmCurate()` API is the single entry point. Internal
12
- * helpers stay private. Tests can drive the public API or call the smaller
13
- * pure helpers (`curateSearchResults`, `orderCuratedTypes`,
14
- * `deriveCurateFallbackQueries`) by importing them directly.
11
+ * The exported `akmCurate()` API is the single entry point. Internal helpers
12
+ * stay private. Tests can drive the public API or call the smaller pure
13
+ * helpers (`curateSearchResults`, `deriveCurateFallbackQueries`,
14
+ * `mergeCurateSearchResponses`) by importing them directly.
15
15
  */
16
+ import fs from "node:fs";
17
+ import { parseAssetRef } from "../../core/asset/asset-ref.js";
18
+ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
19
+ import { getIndexPassConfig, loadConfig } from "../../core/config/config.js";
16
20
  import { rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
17
21
  import { appendEvent } from "../../core/events.js";
18
- import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
22
+ import { computeBodyHash } from "../../indexer/db/db.js";
23
+ import { enqueueGraphExtraction, hasGraphData } from "../../indexer/db/graph-db.js";
24
+ import { findSourceForPath, resolveSourceEntries } from "../../indexer/search/search-source.js";
19
25
  import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
20
26
  import { truncateDescription } from "../../output/shapes.js";
27
+ import { TELEMETRY_BUSY_TIMEOUT_MS, withIndexDb } from "../../storage/repositories/index-db.js";
21
28
  import { akmSearch, parseSearchSource } from "./search.js";
22
29
  import { akmShowUnified } from "./show.js";
23
30
  const CURATE_FALLBACK_FILTER_WORDS = new Set([
@@ -34,29 +41,31 @@ const CURATE_FALLBACK_FILTER_WORDS = new Set([
34
41
  "to",
35
42
  "with",
36
43
  ]);
37
- const CURATED_TYPE_FALLBACK_ORDER = ["skill", "command", "script", "knowledge", "agent", "memory"];
38
- const CURATED_TYPE_FALLBACK_INDEX = new Map(CURATED_TYPE_FALLBACK_ORDER.map((type, index) => [type, index]));
44
+ const CURATE_SHORT_FALLBACK_TOKENS = new Set(["ai", "ci", "cd", "go", "js", "ts"]);
39
45
  const MIN_CURATE_FALLBACK_TOKEN_LENGTH = 3;
40
46
  const MAX_CURATE_FALLBACK_KEYWORDS = 6;
41
47
  export const CURATE_SEARCH_LIMIT_MULTIPLIER = 4;
42
48
  export const MIN_CURATE_SEARCH_LIMIT = 12;
43
49
  const DEFAULT_CURATE_LIMIT = 4;
50
+ const CURATE_CLOSE_SCORE_BAND = 0.12;
51
+ const CURATE_TAIL_SCORE_FLOOR = 0.35;
52
+ const CURATE_RELATIVE_SCORE_FLOOR = 0.7;
53
+ const CURATE_FALLBACK_TOP_SCORE_THRESHOLD = 0.8;
54
+ const CURATE_FALLBACK_STRONG_SCORE_FLOOR = 0.35;
55
+ const MAX_CURATE_SUPPORT_REFS = 2;
56
+ const CURATE_REFERENCE_QUERY_RE = /\b(?:reference|docs?|guide|how|explain|learn|readme|why)\b/;
44
57
  /**
45
58
  * Fire-and-forget: log a curate event to the usage_events table and events.jsonl.
46
59
  * Never blocks the caller; errors are silently ignored.
47
60
  */
48
- function logCurateEvent(query, result) {
61
+ function logCurateEvent(query, result, eventSource = "user") {
49
62
  const itemRefs = result.items.map((item) => ("ref" in item ? item.ref : `registry:${item.id}`));
50
63
  appendEvent({
51
64
  eventType: "curate",
52
65
  metadata: { query, itemCount: result.items.length, itemRefs },
53
66
  });
54
67
  try {
55
- const db = openExistingDatabase();
56
- try {
57
- // Summary row (entry_ref = NULL): preserves the query → itemRefs audit
58
- // trail. Retrieval counting ignores NULL-ref rows, so this row is purely
59
- // informational.
68
+ withIndexDb((db) => {
60
69
  insertUsageEvent(db, {
61
70
  event_type: "curate",
62
71
  query,
@@ -64,12 +73,8 @@ function logCurateEvent(query, result) {
64
73
  itemCount: result.items.length,
65
74
  itemRefs,
66
75
  }),
67
- source: "user",
76
+ source: eventSource,
68
77
  });
69
- // Per-item rows with entry_ref populated so curation registers as a real
70
- // retrieval signal in getRetrievalCounts (which counts 'curate' events).
71
- // Only stash items expose a canonical asset ref; registry hits
72
- // (`registry:<id>`) have no asset ref and are skipped here.
73
78
  for (const item of result.items) {
74
79
  if (!("ref" in item) || typeof item.ref !== "string")
75
80
  continue;
@@ -77,23 +82,15 @@ function logCurateEvent(query, result) {
77
82
  event_type: "curate",
78
83
  query,
79
84
  entry_ref: item.ref,
80
- source: "user",
85
+ source: eventSource,
81
86
  });
82
87
  }
83
- }
84
- finally {
85
- closeDatabase(db);
86
- }
88
+ }, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
87
89
  }
88
90
  catch (err) {
89
91
  rethrowIfTestIsolationError(err);
90
- /* ignore logging failures */
91
92
  }
92
93
  }
93
- /**
94
- * Public curate entry point. Performs the search itself when
95
- * `options.searchResponse` is not supplied.
96
- */
97
94
  export async function akmCurate(options) {
98
95
  const trimmedQuery = options.query.trim();
99
96
  if (!trimmedQuery) {
@@ -105,36 +102,33 @@ export async function akmCurate(options) {
105
102
  (await searchForCuration({
106
103
  query: options.query,
107
104
  type: options.type,
108
- // Search deeper than the final curated count so we can pick one strong
109
- // match per type and still have room for fallback retries.
110
105
  limit: Math.max(limit * CURATE_SEARCH_LIMIT_MULTIPLIER, MIN_CURATE_SEARCH_LIMIT),
111
106
  source,
112
107
  }));
113
108
  const result = await curateSearchResults(options.query, searchResponse, limit, options.type);
114
- logCurateEvent(options.query, result);
109
+ logCurateEvent(options.query, result, options.eventSource);
115
110
  return result;
116
111
  }
117
112
  export async function curateSearchResults(query, result, limit, selectedType) {
118
113
  const stashHits = result.hits.filter((hit) => hit.type !== "registry");
119
114
  const registryHits = result.registryHits ?? [];
120
115
  let selectedStashHits;
116
+ let supportRefsByRef = new Map();
121
117
  if (selectedType && selectedType !== "any") {
122
118
  selectedStashHits = stashHits.slice(0, limit);
123
119
  }
124
120
  else {
125
- const bestByType = new Map();
126
- for (const hit of stashHits) {
127
- if (!bestByType.has(hit.type))
128
- bestByType.set(hit.type, hit);
129
- }
130
- const orderedTypes = orderCuratedTypes(query, Array.from(bestByType.keys()));
131
- selectedStashHits = orderedTypes
132
- .map((type) => bestByType.get(type))
133
- .filter((hit) => Boolean(hit));
121
+ const selected = selectCuratedStashHits(query, stashHits, limit);
122
+ const preferred = preferBroadRootRepresentative(query, selected.selected, stashHits, selected.supportRefsByRef);
123
+ selectedStashHits = preferred.selected;
124
+ supportRefsByRef = preferred.supportRefsByRef;
134
125
  }
135
126
  const selectedRegistryHits = selectedStashHits.length >= limit ? [] : registryHits.slice(0, Math.min(2, limit - selectedStashHits.length));
127
+ const selectedRefs = new Set(selectedStashHits.map((hit) => hit.ref));
136
128
  const items = [
137
- ...(await Promise.all(selectedStashHits.slice(0, limit).map((hit) => enrichCuratedStashHit(query, hit)))),
129
+ ...(await Promise.all(selectedStashHits
130
+ .slice(0, limit)
131
+ .map((hit) => enrichCuratedStashHit(query, hit, supportRefsByRef.get(hit.ref) ?? [], selectedRefs)))),
138
132
  ...selectedRegistryHits.map((hit) => buildCuratedRegistryItem(query, hit)),
139
133
  ].slice(0, limit);
140
134
  return {
@@ -145,37 +139,7 @@ export async function curateSearchResults(query, result, limit, selectedType) {
145
139
  ...(result.tip ? { tip: result.tip } : {}),
146
140
  };
147
141
  }
148
- export function orderCuratedTypes(query, types) {
149
- const lower = query.toLowerCase();
150
- const boosts = new Map();
151
- const addBoost = (type, amount) => boosts.set(type, (boosts.get(type) ?? 0) + amount);
152
- if (/(run|script|bash|shell|cli|execute|automation|deploy|build|test|lint)/.test(lower)) {
153
- addBoost("script", 6);
154
- addBoost("command", 4);
155
- }
156
- if (/(guide|docs?|readme|reference|how|explain|learn|why)/.test(lower)) {
157
- addBoost("knowledge", 6);
158
- addBoost("skill", 4);
159
- }
160
- if (/(agent|assistant|planner|review|analy[sz]e|architect|prompt)/.test(lower)) {
161
- addBoost("agent", 6);
162
- addBoost("skill", 3);
163
- }
164
- if (/(config|template|release|generate|command)/.test(lower)) {
165
- addBoost("command", 5);
166
- }
167
- if (/(memory|context|recall|remember)/.test(lower)) {
168
- addBoost("memory", 6);
169
- }
170
- return [...types].sort((a, b) => {
171
- const boostDiff = (boosts.get(b) ?? 0) - (boosts.get(a) ?? 0);
172
- if (boostDiff !== 0)
173
- return boostDiff;
174
- return ((CURATED_TYPE_FALLBACK_INDEX.get(a) ?? Number.MAX_SAFE_INTEGER) -
175
- (CURATED_TYPE_FALLBACK_INDEX.get(b) ?? Number.MAX_SAFE_INTEGER));
176
- });
177
- }
178
- async function enrichCuratedStashHit(query, hit) {
142
+ async function enrichCuratedStashHit(query, hit, supportRefs, selectedRefs) {
179
143
  let shown;
180
144
  try {
181
145
  shown = await akmShowUnified({ ref: hit.ref });
@@ -183,8 +147,14 @@ async function enrichCuratedStashHit(query, hit) {
183
147
  catch {
184
148
  shown = undefined;
185
149
  }
150
+ // #624-P3: when lazy graph extraction is opted in, enqueue an ungraphed
151
+ // asset for a later pass to extract. Fire-and-forget, non-blocking, NO inline
152
+ // extraction and NO LLM call here. Default-off (flag unset) = byte-identical.
153
+ if (shown?.path)
154
+ maybeEnqueueLazyGraph(shown.path);
186
155
  const description = shown?.description ?? hit.description;
187
156
  const preview = buildCuratedPreview(shown, hit);
157
+ const mergedSupportRefs = mergeCurateSupportRefs(supportRefs, shown?.related?.hits, selectedRefs, hit.ref);
188
158
  return {
189
159
  source: "stash",
190
160
  type: shown?.type ?? hit.type,
@@ -195,11 +165,50 @@ async function enrichCuratedStashHit(query, hit) {
195
165
  ...(shown?.keys?.length ? { keys: shown.keys } : {}),
196
166
  ...(shown?.parameters?.length ? { parameters: shown.parameters } : {}),
197
167
  ...(shown?.run ? { run: shown.run } : {}),
168
+ ...(mergedSupportRefs.length > 0 ? { supportRefs: mergedSupportRefs } : {}),
198
169
  followUp: `akm show ${hit.ref}`,
199
170
  reason: buildCuratedReason(query, shown?.type ?? hit.type),
200
171
  ...(hit.score !== undefined ? { score: hit.score } : {}),
201
172
  };
202
173
  }
174
+ /**
175
+ * #624-P3 — enqueue an ungraphed asset for lazy graph extraction when the
176
+ * `index.graph.lazyGraphExtraction` flag is on. Pure side-effect, fully
177
+ * best-effort: any failure (config, fs, db) is swallowed so curate never fails
178
+ * on it. NO LLM call and NO inline extraction — only a cheap queue insert.
179
+ * Default-off (flag unset) returns immediately = byte-identical behavior.
180
+ */
181
+ function maybeEnqueueLazyGraph(assetPath) {
182
+ try {
183
+ const config = loadConfig();
184
+ if (getIndexPassConfig(config.index, "graph")?.lazyGraphExtraction !== true)
185
+ return;
186
+ const sources = resolveSourceEntries();
187
+ const source = findSourceForPath(assetPath, sources);
188
+ const stashRoot = source?.path;
189
+ if (!stashRoot)
190
+ return;
191
+ let raw;
192
+ try {
193
+ raw = fs.readFileSync(assetPath, "utf8");
194
+ }
195
+ catch {
196
+ return;
197
+ }
198
+ const body = parseFrontmatter(raw).content.trim();
199
+ if (!body)
200
+ return;
201
+ const bodyHash = computeBodyHash(body);
202
+ withIndexDb((db) => {
203
+ if (!hasGraphData(db, stashRoot, assetPath)) {
204
+ enqueueGraphExtraction(db, stashRoot, assetPath, bodyHash, 0);
205
+ }
206
+ }, { busyTimeoutMs: TELEMETRY_BUSY_TIMEOUT_MS });
207
+ }
208
+ catch (err) {
209
+ rethrowIfTestIsolationError(err);
210
+ }
211
+ }
203
212
  function buildCuratedRegistryItem(query, hit) {
204
213
  return {
205
214
  source: "registry",
@@ -226,19 +235,19 @@ function buildCuratedPreview(shown, hit) {
226
235
  function buildCuratedReason(query, type) {
227
236
  switch (type) {
228
237
  case "script":
229
- return `Best runnable script match for "${query}".`;
238
+ return `Strong runnable script match for "${query}".`;
230
239
  case "command":
231
- return `Best reusable command/template match for "${query}".`;
240
+ return `Strong reusable command/template match for "${query}".`;
232
241
  case "knowledge":
233
- return `Best reference document match for "${query}".`;
242
+ return `Strong reference document match for "${query}".`;
234
243
  case "skill":
235
- return `Best instructions/workflow match for "${query}".`;
244
+ return `Strong instructions/workflow match for "${query}".`;
236
245
  case "agent":
237
- return `Best specialized agent prompt match for "${query}".`;
246
+ return `Strong specialized agent prompt match for "${query}".`;
238
247
  case "memory":
239
- return `Best saved context match for "${query}".`;
248
+ return `Strong saved context match for "${query}".`;
240
249
  default:
241
- return `Best ${type} match for "${query}".`;
250
+ return `Strong ${type} match for "${query}".`;
242
251
  }
243
252
  }
244
253
  function buildCurateSummary(query, items) {
@@ -246,56 +255,108 @@ function buildCurateSummary(query, items) {
246
255
  return `No curated assets were selected for "${query}".`;
247
256
  }
248
257
  const labels = items.map((item) => `${item.type}:${item.name}`);
249
- return `Selected ${items.length} high-signal result${items.length === 1 ? "" : "s"}: ${labels.join(", ")}.`;
250
- }
251
- function hasSearchResults(result) {
252
- return result.hits.length > 0 || (result.registryHits?.length ?? 0) > 0;
258
+ return `Selected ${items.length} curated result${items.length === 1 ? "" : "s"}: ${labels.join(", ")}.`;
253
259
  }
254
- /**
255
- * Extract a small set of fallback keywords when a prompt-style curate query
256
- * returns no hits as a whole phrase.
257
- *
258
- * We keep up to MAX_CURATE_FALLBACK_KEYWORDS distinct keywords and drop short
259
- * or common filler words so follow-up searches stay inexpensive while focusing
260
- * on higher-signal terms.
261
- */
262
260
  export function deriveCurateFallbackQueries(query) {
263
- return Array.from(new Set(query
261
+ const normalizedWhole = query
262
+ .toLowerCase()
263
+ .replace(/[^a-z0-9]+/g, " ")
264
+ .trim();
265
+ const tokens = Array.from(new Set(query
264
266
  .toLowerCase()
265
267
  .split(/[^a-z0-9]+/)
266
268
  .map((token) => token.trim())
267
- // Keep longer tokens so fallback stays focused on higher-signal terms
268
- // and avoids broad one- and two-letter matches that overwhelm curation.
269
- .filter((token) => token.length >= MIN_CURATE_FALLBACK_TOKEN_LENGTH && !CURATE_FALLBACK_FILTER_WORDS.has(token)))).slice(0, MAX_CURATE_FALLBACK_KEYWORDS);
269
+ .filter((token) => token.length > 0 &&
270
+ !CURATE_FALLBACK_FILTER_WORDS.has(token) &&
271
+ (token.length >= MIN_CURATE_FALLBACK_TOKEN_LENGTH || CURATE_SHORT_FALLBACK_TOKENS.has(token))))).slice(0, MAX_CURATE_FALLBACK_KEYWORDS);
272
+ if (tokens.length === 1 && tokens[0] === normalizedWhole)
273
+ return [];
274
+ return tokens;
270
275
  }
271
276
  export function mergeCurateSearchResponses(base, extras) {
272
- const hitsByRef = new Map();
277
+ // The base (full-query) ranking is the relevance signal — keyword fallback
278
+ // searches exist only to ADD recall when that ranking is thin, never to
279
+ // re-rank it. So we PRESERVE base order and APPEND fallback-only hits below
280
+ // it. A single-token fallback match on an exact title/path normalizes to a
281
+ // high FTS score, but those scores are not comparable to the full-query
282
+ // (hybrid) scores; re-sorting the union by raw score (the prior behaviour)
283
+ // let that keyword junk leapfrog the contextually-relevant full-query hits.
284
+ // Dup refs (present in both base and a fallback) keep their base POSITION but
285
+ // take the MAX score, since matching both the full query and a key term is a
286
+ // stronger relevance signal for the downstream score floor.
287
+ const bestExtraStashScore = new Map();
288
+ for (const result of extras) {
289
+ for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
290
+ const prev = bestExtraStashScore.get(hit.ref);
291
+ if (prev === undefined || (hit.score ?? 0) > prev)
292
+ bestExtraStashScore.set(hit.ref, hit.score ?? 0);
293
+ }
294
+ }
295
+ const baseRefs = new Set();
296
+ const baseStash = [];
273
297
  for (const hit of base.hits.filter((entry) => entry.type !== "registry")) {
274
- hitsByRef.set(hit.ref, hit);
298
+ baseRefs.add(hit.ref);
299
+ const extraScore = bestExtraStashScore.get(hit.ref);
300
+ baseStash.push(extraScore !== undefined && extraScore > (hit.score ?? 0) ? { ...hit, score: extraScore } : hit);
275
301
  }
302
+ const extraOnly = new Map();
276
303
  for (const result of extras) {
277
304
  for (const hit of result.hits.filter((entry) => entry.type !== "registry")) {
278
- const existing = hitsByRef.get(hit.ref);
279
- if (!existing || (hit.score ?? 0) > (existing.score ?? 0)) {
280
- hitsByRef.set(hit.ref, hit);
281
- }
305
+ if (baseRefs.has(hit.ref))
306
+ continue;
307
+ const existing = extraOnly.get(hit.ref);
308
+ if (!existing || (hit.score ?? 0) > (existing.score ?? 0))
309
+ extraOnly.set(hit.ref, hit);
282
310
  }
283
311
  }
284
- const registryById = new Map();
312
+ // Fallback-only hits must rank BELOW every full-query hit through the rest of
313
+ // the pipeline. The downstream selector (`selectCuratedStashHits`) RE-SORTS by
314
+ // score and derives its relevance floor from the top score, so preserving
315
+ // order here is not enough — a single-token FTS match (normalized ~0.9) would
316
+ // otherwise become the leader and evict the contextual full-query memories.
317
+ // We therefore restamp fallback-only scores into a band strictly below the
318
+ // minimum base score (keeping their own relative order). When there are no
319
+ // base hits, fallback IS the result, so scores are kept as-is.
320
+ const sortedExtra = [...extraOnly.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
321
+ const minBaseScore = baseStash.length
322
+ ? Math.min(...baseStash.map((hit) => hit.score ?? 0))
323
+ : Number.POSITIVE_INFINITY;
324
+ const cappedExtra = baseStash.length
325
+ ? sortedExtra.map((hit, i) => ({ ...hit, score: minBaseScore - 1e-6 * (i + 1) }))
326
+ : sortedExtra;
327
+ const mergedHits = [...baseStash, ...cappedExtra];
328
+ // Registry hits are supplemental fill — same rule: base first (max score on
329
+ // dups), then fallback-only registry hits appended by score.
330
+ const bestExtraRegScore = new Map();
331
+ for (const result of extras) {
332
+ for (const hit of result.registryHits ?? []) {
333
+ const prev = bestExtraRegScore.get(hit.id);
334
+ if (prev === undefined || (hit.score ?? 0) > prev)
335
+ bestExtraRegScore.set(hit.id, hit.score ?? 0);
336
+ }
337
+ }
338
+ const baseRegIds = new Set();
339
+ const baseReg = [];
285
340
  for (const hit of base.registryHits ?? []) {
286
- registryById.set(hit.id, hit);
341
+ baseRegIds.add(hit.id);
342
+ const extraScore = bestExtraRegScore.get(hit.id);
343
+ baseReg.push(extraScore !== undefined && extraScore > (hit.score ?? 0) ? { ...hit, score: extraScore } : hit);
287
344
  }
345
+ const extraRegOnly = new Map();
288
346
  for (const result of extras) {
289
347
  for (const hit of result.registryHits ?? []) {
290
- const existing = registryById.get(hit.id);
291
- if (!existing || (hit.score ?? 0) > (existing.score ?? 0)) {
292
- registryById.set(hit.id, hit);
293
- }
348
+ if (baseRegIds.has(hit.id))
349
+ continue;
350
+ const existing = extraRegOnly.get(hit.id);
351
+ if (!existing || (hit.score ?? 0) > (existing.score ?? 0))
352
+ extraRegOnly.set(hit.id, hit);
294
353
  }
295
354
  }
296
355
  const warnings = Array.from(new Set([...(base.warnings ?? []), ...extras.flatMap((result) => result.warnings ?? [])]));
297
- const mergedHits = [...hitsByRef.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
298
- const mergedRegistryHits = [...registryById.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
356
+ const mergedRegistryHits = [
357
+ ...baseReg,
358
+ ...[...extraRegOnly.values()].sort((a, b) => (b.score ?? 0) - (a.score ?? 0)),
359
+ ];
299
360
  return {
300
361
  ...base,
301
362
  hits: mergedHits,
@@ -306,10 +367,10 @@ export function mergeCurateSearchResponses(base, extras) {
306
367
  }
307
368
  export async function searchForCuration(input) {
308
369
  const initial = await akmSearch({ ...input, skipLogging: true });
309
- if (hasSearchResults(initial))
370
+ if (!shouldRunCurateFallback(initial, input.limit))
310
371
  return initial;
311
372
  const fallbackQueries = deriveCurateFallbackQueries(input.query);
312
- if (fallbackQueries.length <= 1)
373
+ if (fallbackQueries.length === 0)
313
374
  return initial;
314
375
  const fallbackResults = await Promise.all(fallbackQueries.map((token) => akmSearch({
315
376
  query: token,
@@ -320,3 +381,241 @@ export async function searchForCuration(input) {
320
381
  })));
321
382
  return mergeCurateSearchResponses(initial, fallbackResults);
322
383
  }
384
+ function parseCurateIntent(query) {
385
+ const lower = query.toLowerCase();
386
+ return {
387
+ executionHeavy: /(run|script|bash|shell|cli|execute|automation|deploy|build|test|lint)/.test(lower),
388
+ multiStep: /(plan|workflow|steps?|procedure|rollout|review|migration|release|checklist)/.test(lower),
389
+ delegation: /(agent|assistant|planner|reviewer|architect|prompt)/.test(lower),
390
+ recall: /(memory|context|recall|remember)/.test(lower),
391
+ reference: CURATE_REFERENCE_QUERY_RE.test(lower),
392
+ };
393
+ }
394
+ function computeCurateTypeNudge(type, intent) {
395
+ let nudge = 0;
396
+ if (intent.executionHeavy) {
397
+ if (type === "script")
398
+ nudge += 0.06;
399
+ else if (type === "command")
400
+ nudge += 0.04;
401
+ else if (type === "memory")
402
+ nudge -= 0.04;
403
+ }
404
+ if (intent.multiStep) {
405
+ if (type === "workflow")
406
+ nudge += 0.06;
407
+ else if (type === "skill")
408
+ nudge += 0.04;
409
+ else if (type === "knowledge")
410
+ nudge -= 0.02;
411
+ }
412
+ if (intent.delegation && type === "agent")
413
+ nudge += 0.06;
414
+ if (intent.recall && type === "memory")
415
+ nudge += 0.08;
416
+ if (intent.reference) {
417
+ if (type === "knowledge")
418
+ nudge += 0.05;
419
+ else if (type === "skill")
420
+ nudge += 0.02;
421
+ }
422
+ return nudge;
423
+ }
424
+ function getCurateFamily(ref) {
425
+ try {
426
+ const parsed = parseAssetRef(ref);
427
+ if (parsed.type === "skill") {
428
+ return { key: parsed.name, role: "root" };
429
+ }
430
+ if (parsed.type !== "knowledge")
431
+ return undefined;
432
+ const match = /^skills\/(.+?)\/references\/(.+)$/.exec(parsed.name);
433
+ if (!match)
434
+ return undefined;
435
+ return {
436
+ key: match[1],
437
+ role: "reference",
438
+ topicTokens: match[2]
439
+ .split(/[^a-z0-9]+/i)
440
+ .map((token) => token.trim().toLowerCase())
441
+ .filter(Boolean),
442
+ };
443
+ }
444
+ catch {
445
+ return undefined;
446
+ }
447
+ }
448
+ function annotateCurateHit(query, hit, index, intent) {
449
+ const rawScore = hit.score ?? 0;
450
+ const family = getCurateFamily(hit.ref);
451
+ let adjustedScore = rawScore + computeCurateTypeNudge(hit.type, intent);
452
+ if (family?.role === "root" && !isNarrowReferenceFamilyQuery(query, family))
453
+ adjustedScore += 0.07;
454
+ if (family?.role === "reference" && isNarrowReferenceFamilyQuery(query, family))
455
+ adjustedScore += 0.07;
456
+ return {
457
+ hit,
458
+ rawScore,
459
+ adjustedScore,
460
+ originalIndex: index,
461
+ family,
462
+ };
463
+ }
464
+ function compareCurateHits(a, b) {
465
+ const rawDiff = b.rawScore - a.rawScore;
466
+ if (Math.abs(rawDiff) > CURATE_CLOSE_SCORE_BAND)
467
+ return rawDiff;
468
+ const adjustedDiff = b.adjustedScore - a.adjustedScore;
469
+ if (adjustedDiff !== 0)
470
+ return adjustedDiff;
471
+ if (rawDiff !== 0)
472
+ return rawDiff;
473
+ return a.originalIndex - b.originalIndex;
474
+ }
475
+ function passesCurateScoreFloor(hit, leaderScore) {
476
+ if (leaderScore === undefined)
477
+ return true;
478
+ return hit.rawScore >= Math.max(CURATE_TAIL_SCORE_FLOOR, leaderScore * CURATE_RELATIVE_SCORE_FLOOR);
479
+ }
480
+ function isNarrowReferenceFamilyQuery(query, family) {
481
+ if (!family || family.role !== "reference")
482
+ return false;
483
+ const lower = query.toLowerCase();
484
+ if (CURATE_REFERENCE_QUERY_RE.test(lower))
485
+ return true;
486
+ return family.topicTokens.some((token) => token.length >= 3 && lower.includes(token));
487
+ }
488
+ function appendCurateSupportRef(supportRefsByRef, ownerRef, supportRef) {
489
+ const existing = supportRefsByRef.get(ownerRef) ?? [];
490
+ if (existing.some((entry) => entry.ref === supportRef.ref))
491
+ return;
492
+ supportRefsByRef.set(ownerRef, [...existing, supportRef]);
493
+ }
494
+ function selectCuratedStashHits(query, hits, limit) {
495
+ const intent = parseCurateIntent(query);
496
+ const collapsed = collapseCurateFamilies(query, hits);
497
+ const ranked = collapsed.hits
498
+ .map(({ hit, originalIndex }) => annotateCurateHit(query, hit, originalIndex, intent))
499
+ .sort(compareCurateHits);
500
+ const selected = [];
501
+ const supportRefsByRef = collapsed.supportRefsByRef;
502
+ let leaderScore;
503
+ for (const candidate of ranked) {
504
+ if (!passesCurateScoreFloor(candidate, leaderScore))
505
+ continue;
506
+ selected.push(candidate);
507
+ if (leaderScore === undefined)
508
+ leaderScore = candidate.rawScore;
509
+ if (selected.length >= limit)
510
+ break;
511
+ }
512
+ return { selected: selected.map((entry) => entry.hit), supportRefsByRef };
513
+ }
514
+ function collapseCurateFamilies(query, hits) {
515
+ const passthrough = [];
516
+ const supportRefsByRef = new Map();
517
+ const groups = new Map();
518
+ for (const [index, hit] of hits.entries()) {
519
+ const family = getCurateFamily(hit.ref);
520
+ if (!family) {
521
+ passthrough.push({ hit, originalIndex: index });
522
+ continue;
523
+ }
524
+ const group = groups.get(family.key) ?? { references: [] };
525
+ if (family.role === "root") {
526
+ if (!group.root)
527
+ group.root = { hit, originalIndex: index };
528
+ }
529
+ else {
530
+ group.references.push({ hit, originalIndex: index });
531
+ }
532
+ groups.set(family.key, group);
533
+ }
534
+ const collapsedFamilies = [];
535
+ for (const group of groups.values()) {
536
+ const bestReference = group.references[0];
537
+ const representative = group.root && !isNarrowReferenceFamilyQuery(query, getCurateFamily(bestReference?.hit.ref ?? group.root.hit.ref))
538
+ ? group.root
539
+ : (bestReference ?? group.root);
540
+ if (!representative)
541
+ continue;
542
+ collapsedFamilies.push(representative);
543
+ const supportCandidates = [group.root, ...group.references].filter((entry) => {
544
+ return entry !== undefined && entry.hit.ref !== representative.hit.ref;
545
+ });
546
+ for (const support of supportCandidates) {
547
+ appendCurateSupportRef(supportRefsByRef, representative.hit.ref, {
548
+ ref: support.hit.ref,
549
+ type: support.hit.type,
550
+ reason: "Related family asset to inspect next.",
551
+ });
552
+ }
553
+ }
554
+ return {
555
+ hits: [...passthrough, ...collapsedFamilies].sort((a, b) => a.originalIndex - b.originalIndex),
556
+ supportRefsByRef,
557
+ };
558
+ }
559
+ function preferBroadRootRepresentative(query, selected, allHits, supportRefsByRef) {
560
+ const first = selected[0];
561
+ if (!first)
562
+ return { selected, supportRefsByRef };
563
+ const match = /^knowledge:skills\/(.+?)\/references\/(.+)$/.exec(first.ref);
564
+ if (!match)
565
+ return { selected, supportRefsByRef };
566
+ const lower = query.toLowerCase();
567
+ const topicTokens = match[2].split(/[^a-z0-9]+/i).filter(Boolean);
568
+ const wantsReference = CURATE_REFERENCE_QUERY_RE.test(lower) ||
569
+ topicTokens.some((token) => token.length >= 3 && lower.includes(token.toLowerCase()));
570
+ if (wantsReference)
571
+ return { selected, supportRefsByRef };
572
+ const rootRef = `skill:${match[1]}`;
573
+ const rootHit = allHits.find((hit) => hit.ref === rootRef);
574
+ if (!rootHit)
575
+ return { selected, supportRefsByRef };
576
+ const next = [rootHit, ...selected.filter((hit) => hit.ref !== first.ref && hit.ref !== rootRef)];
577
+ const merged = new Map(supportRefsByRef);
578
+ const priorSupport = merged.get(first.ref) ?? [];
579
+ for (const entry of priorSupport)
580
+ appendCurateSupportRef(merged, rootRef, entry);
581
+ appendCurateSupportRef(merged, rootRef, {
582
+ ref: first.ref,
583
+ type: first.type,
584
+ reason: "Related family asset to inspect next.",
585
+ });
586
+ merged.delete(first.ref);
587
+ return { selected: next, supportRefsByRef: merged };
588
+ }
589
+ function mergeCurateSupportRefs(seeded, relatedHits, selectedRefs, ownerRef) {
590
+ const merged = [];
591
+ for (const entry of seeded) {
592
+ if (entry.ref === ownerRef || selectedRefs.has(entry.ref))
593
+ continue;
594
+ if (merged.some((existing) => existing.ref === entry.ref))
595
+ continue;
596
+ merged.push(entry);
597
+ if (merged.length >= MAX_CURATE_SUPPORT_REFS)
598
+ return merged;
599
+ }
600
+ if (!Array.isArray(relatedHits))
601
+ return merged;
602
+ for (const hit of relatedHits) {
603
+ if (!hit.ref || hit.ref === ownerRef || selectedRefs.has(hit.ref))
604
+ continue;
605
+ if (merged.some((existing) => existing.ref === hit.ref))
606
+ continue;
607
+ merged.push({ ref: hit.ref, type: hit.type, reason: "Related asset via shared entities." });
608
+ if (merged.length >= MAX_CURATE_SUPPORT_REFS)
609
+ break;
610
+ }
611
+ return merged;
612
+ }
613
+ function shouldRunCurateFallback(initial, desiredCount) {
614
+ const stashHits = initial.hits.filter((hit) => hit.type !== "registry");
615
+ if (stashHits.length === 0)
616
+ return true;
617
+ const topScore = stashHits[0]?.score ?? 0;
618
+ const strongFloor = Math.max(CURATE_FALLBACK_STRONG_SCORE_FLOOR, topScore * CURATE_RELATIVE_SCORE_FLOOR);
619
+ const strongCount = stashHits.filter((hit) => (hit.score ?? 0) >= strongFloor).length;
620
+ return !(topScore >= CURATE_FALLBACK_TOP_SCORE_THRESHOLD && strongCount >= Math.min(2, desiredCount));
621
+ }