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
@@ -10,9 +10,10 @@ import { getDbPath } from "../core/paths.js";
10
10
  import { isVerbose, warn, warnVerbose } from "../core/warn.js";
11
11
  import { resolveIndexPassLLM } from "../llm/index-passes.js";
12
12
  import { takeWorkflowDocument } from "../workflows/runtime/document-cache.js";
13
- import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddableEntryCount, getEmbeddingCount, getEntriesByDir, getEntryCount, getIndexDirState, getMeta, isVecAvailable, openDatabase, openExistingDatabase, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db/db.js";
13
+ import { clearStaleCacheEntries, closeDatabase, deleteEntriesByDir, deleteEntriesByIds, deleteEntriesByStashDir, deleteIndexDirStatesByStashDir, getAllEntriesForEmbedding, getEmbeddableEntryCount, getEmbeddingCount, getEntryCount, getMeta, isVecAvailable, openExistingDatabase, openIndexDatabase, purgeEmbeddings, rebuildFts, relinkUsageEvents, setMeta, upsertEmbedding, upsertEntry, upsertIndexDirState, upsertUtilityScore, upsertWorkflowDocument, warnIfVecMissing, } from "./db/db.js";
14
14
  import { deleteStoredGraph } from "./db/graph-db.js";
15
15
  import { withIndexWriterLease } from "./index-writer-lock.js";
16
+ import { canUseIncrementalSkip, computeDirFingerprint, getCachedZeroRowDirState, getDirIndexState, inferZeroRowReason, } from "./passes/dir-staleness.js";
16
17
  import { applyCuratedFrontmatter, applyWikiFrontmatter, generateMetadataFlat, isEnrichmentComplete, isWorkflowSkipWarning, loadStashFile, shouldIndexStashFile, } from "./passes/metadata.js";
17
18
  import { buildSearchText } from "./search/search-fields.js";
18
19
  import { classifySemanticFailure, clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "./search/semantic-status.js";
@@ -117,7 +118,7 @@ async function runWalkPhase(ctx) {
117
118
  ctx.timing.tWalkEnd = Date.now();
118
119
  throwIfAborted(signal);
119
120
  // LLM enrichment for directories that need it
120
- await enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, true, reEnrich);
121
+ await enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, reEnrich);
121
122
  onProgress({
122
123
  phase: "llm",
123
124
  message: resolveIndexPassLLM("enrichment", config)
@@ -142,6 +143,7 @@ async function runEmbeddingPhase(ctx) {
142
143
  */
143
144
  async function runFinalizePhase(ctx) {
144
145
  const { db, config, sources, sourceDirs, isIncremental, stashDir, signal, onProgress } = ctx;
146
+ ctx.timing.tFinalizeStart = Date.now();
145
147
  // Rebuild FTS after all inserts. Use incremental mode when this whole
146
148
  // index run is incremental — only entries touched by `upsertEntry`
147
149
  // since the last rebuild are re-indexed.
@@ -152,10 +154,13 @@ async function runFinalizePhase(ctx) {
152
154
  });
153
155
  ctx.timing.tFtsEnd = Date.now();
154
156
  // Re-link detached usage_events and recompute utility scores.
157
+ onProgress({ phase: "finalize", message: "Relinking usage events." });
155
158
  relinkUsageEvents(db);
159
+ onProgress({ phase: "finalize", message: "Recomputing utility scores." });
156
160
  recomputeUtilityScores(db);
157
161
  // Purge LLM cache entries for assets that no longer exist in the index.
158
162
  try {
163
+ onProgress({ phase: "finalize", message: "Clearing stale LLM cache entries." });
159
164
  clearStaleCacheEntries(db);
160
165
  }
161
166
  catch {
@@ -163,6 +168,7 @@ async function runFinalizePhase(ctx) {
163
168
  }
164
169
  // Regenerate each wiki's index.md from its pages' frontmatter. Best-effort.
165
170
  try {
171
+ onProgress({ phase: "finalize", message: "Regenerating wiki indexes." });
166
172
  const { regenerateAllWikiIndexes } = await import("../wiki/wiki.js");
167
173
  regenerateAllWikiIndexes(stashDir);
168
174
  }
@@ -179,6 +185,7 @@ async function runFinalizePhase(ctx) {
179
185
  warnIfVecMissing(db);
180
186
  const totalEntries = getEntryCount(db);
181
187
  const semanticEntryCount = getEmbeddableEntryCount(db);
188
+ onProgress({ phase: "finalize", message: "Verifying semantic search state." });
182
189
  const verification = verifyIndexState(db, config, semanticEntryCount, embeddingResult);
183
190
  if (config.semanticSearchMode === "off") {
184
191
  clearSemanticStatus();
@@ -196,8 +203,9 @@ async function runFinalizePhase(ctx) {
196
203
  }
197
204
  onProgress({ phase: "verify", message: verification.message });
198
205
  // Store verification result and totalEntries on ctx for the caller to use
199
- ctx._verification = verification;
200
- ctx._totalEntries = totalEntries;
206
+ ctx.verification = verification;
207
+ ctx.totalEntries = totalEntries;
208
+ ctx.timing.tFinalizeEnd = Date.now();
201
209
  // suppress unused warning — sources was previously used inline
202
210
  void sources;
203
211
  }
@@ -225,8 +233,34 @@ function runCleanPass(db, dryRun) {
225
233
  };
226
234
  }
227
235
  // ── Indexer ──────────────────────────────────────────────────────────────────
236
+ // ── Test seam ────────────────────────────────────────────────────────────────
237
+ // Swap-and-restore override. Inert in production; only tests call the setter.
238
+ let akmIndexOverride;
239
+ /** TEST-ONLY. Swap the implementation of `akmIndex`; pass undefined to restore. */
240
+ export function _setAkmIndexForTests(fake) {
241
+ akmIndexOverride = fake;
242
+ }
228
243
  export async function akmIndex(options) {
229
- return withIndexWriterLease({ purpose: "akm-index", signal: options?.signal }, async () => {
244
+ if (akmIndexOverride)
245
+ return akmIndexOverride(options);
246
+ return akmIndexReal(options);
247
+ }
248
+ async function akmIndexReal(options) {
249
+ const requestedAt = Date.now();
250
+ let acquiredAt = requestedAt;
251
+ return withIndexWriterLease({
252
+ purpose: "akm-index",
253
+ signal: options?.signal,
254
+ onWait: ({ waitedMs }) => {
255
+ options?.onProgress?.({
256
+ phase: "preflight",
257
+ message: `Waiting for index writer lease (${Math.round(waitedMs / 1000)}s elapsed).`,
258
+ });
259
+ },
260
+ onAcquired: ({ waitedMs }) => {
261
+ acquiredAt = requestedAt + waitedMs;
262
+ },
263
+ }, async () => {
230
264
  const stashDir = options?.stashDir || resolveStashDir();
231
265
  const onProgress = options?.onProgress ?? (() => { });
232
266
  const signal = options?.signal;
@@ -245,15 +279,22 @@ export async function akmIndex(options) {
245
279
  warnOnUnmigratedVaults(stashDir);
246
280
  // Ensure git stash caches are extracted before resolving stash dirs,
247
281
  // so their content directories exist on disk for the walker to discover.
282
+ const sourceCacheStart = Date.now();
283
+ onProgress({ phase: "preflight", message: "Hydrating source caches." });
248
284
  const { ensureSourceCaches, resolveSourceEntries } = await import("./search/search-source.js");
249
285
  await ensureSourceCaches(config, { force: full });
286
+ const sourceCacheEnd = Date.now();
250
287
  const allSourceEntries = resolveSourceEntries(stashDir, config);
251
288
  const allSourceDirs = allSourceEntries.map((s) => s.path);
289
+ onProgress({
290
+ phase: "preflight",
291
+ message: `Resolved ${allSourceDirs.length} stash source${allSourceDirs.length === 1 ? "" : "s"}.`,
292
+ });
252
293
  const t0 = Date.now();
253
294
  // Open database — pass embedding dimension from config if available
254
295
  const dbPath = getDbPath();
255
296
  const embeddingDim = config.embedding?.dimension;
256
- const db = openDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined);
297
+ const db = openIndexDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined);
257
298
  try {
258
299
  // Determine incremental vs full mode
259
300
  const prevStashDir = getMeta(db, "stashDir");
@@ -278,6 +319,8 @@ export async function akmIndex(options) {
278
319
  tLlmEnd: t0,
279
320
  tFtsEnd: t0,
280
321
  tEmbedEnd: t0,
322
+ tFinalizeStart: t0,
323
+ tFinalizeEnd: t0,
281
324
  },
282
325
  isIncremental,
283
326
  builtAtMs,
@@ -288,7 +331,6 @@ export async function akmIndex(options) {
288
331
  walkWarnings: [],
289
332
  dirsNeedingLlm: [],
290
333
  embeddingResult: null,
291
- graphExtractionResult: null,
292
334
  };
293
335
  onProgress({
294
336
  phase: "summary",
@@ -307,15 +349,23 @@ export async function akmIndex(options) {
307
349
  await runEmbeddingPhase(ctx);
308
350
  await runFinalizePhase(ctx);
309
351
  // ────────────────────────────────────────────────────────────────────────
310
- const { _verification: verification, _totalEntries: totalEntries } = ctx;
352
+ // runFinalizePhase always populates these before returning.
353
+ const verification = ctx.verification;
354
+ const totalEntries = ctx.totalEntries;
311
355
  const { timing } = ctx;
312
356
  // ── Clean pass ───────────────────────────────────────────────────────────
313
357
  // After the normal index completes, remove entries whose source files no
314
358
  // longer exist on disk. Remote entries (empty file_path) are skipped.
315
359
  let cleanResult;
360
+ const cleanStart = Date.now();
316
361
  if (clean) {
362
+ onProgress({
363
+ phase: "finalize",
364
+ message: dryRun ? "Scanning for stale index entries (dry run)." : "Removing stale index entries.",
365
+ });
317
366
  cleanResult = runCleanPass(db, dryRun);
318
367
  }
368
+ const cleanEnd = Date.now();
319
369
  // ────────────────────────────────────────────────────────────────────────
320
370
  return {
321
371
  stashDir,
@@ -333,6 +383,12 @@ export async function akmIndex(options) {
333
383
  llmMs: timing.tLlmEnd - timing.tWalkEnd,
334
384
  embedMs: timing.tEmbedEnd - timing.tLlmEnd,
335
385
  ftsMs: timing.tFtsEnd - timing.tEmbedEnd,
386
+ finalizeMs: timing.tFinalizeEnd - timing.tFinalizeStart,
387
+ cleanMs: clean ? cleanEnd - cleanStart : 0,
388
+ preflightMs: timing.t0 - requestedAt,
389
+ leaseWaitMs: acquiredAt - requestedAt,
390
+ sourceCacheMs: sourceCacheEnd - sourceCacheStart,
391
+ endToEndMs: Date.now() - requestedAt,
336
392
  },
337
393
  ...(cleanResult !== undefined ? { clean: cleanResult } : {}),
338
394
  };
@@ -342,14 +398,21 @@ export async function akmIndex(options) {
342
398
  }
343
399
  });
344
400
  }
345
- // ── Extracted helpers for indexing ────────────────────────────────────────────
346
- async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, doFullDelete = false, onProgress) {
401
+ /**
402
+ * Phase 1 (async): walk every source directory and pre-generate all metadata
403
+ * outside any transaction, producing the per-directory scan records that
404
+ * {@link persistDirRecords} later writes.
405
+ *
406
+ * generateMetadataFlat is async (uses dynamic import for the matcher/renderer
407
+ * registry), so it cannot run inside a db.transaction() callback — hence the
408
+ * split into a pure-ish scan pass and a synchronous persist pass.
409
+ */
410
+ async function scanSourceDirs(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, onProgress) {
347
411
  let scannedDirs = 0;
348
412
  let skippedDirs = 0;
349
413
  let generatedCount = 0;
350
414
  const warnings = [];
351
415
  const seenPaths = new Set();
352
- const dirsNeedingLlm = [];
353
416
  const dirRecords = [];
354
417
  let processedDirs = 0;
355
418
  let priorDirsChanged = hadRemovedSources;
@@ -369,6 +432,53 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
369
432
  reportScanProgress(`${kind === "scan" ? "Rescanning" : "Skipping"} ${path.relative(currentStashDir, dirPath) || "."} ` +
370
433
  `from ${currentStashDir}: ${reason.kind}${detail}${rowInfo}`);
371
434
  };
435
+ // Duplicate-directory guard shared by the wiki-root and normal branches. A
436
+ // dir may surface via multiple stash roots; only the first occurrence is
437
+ // indexed, and the later ones are recorded as skips. Returns true when the
438
+ // dir was already seen (caller should skip further processing).
439
+ const markSeenOrSkipDuplicate = (dirPath, currentStashDir, files) => {
440
+ const resolved = path.resolve(dirPath);
441
+ if (seenPaths.has(resolved)) {
442
+ const reason = { kind: "duplicate-dir" };
443
+ dirRecords.push({ dirPath, currentStashDir, files, stash: null, skip: true, reason });
444
+ reportDirDecision("skip", dirPath, currentStashDir, reason);
445
+ return true;
446
+ }
447
+ seenPaths.add(resolved);
448
+ return false;
449
+ };
450
+ // Incremental freshness gate shared by both branches: consult the persisted
451
+ // dir state and record either a skip (unchanged + eligible for incremental
452
+ // skip) or a scan record carrying the candidate stash.
453
+ const recordFreshnessDecision = (dirPath, currentStashDir, stateFiles, stash) => {
454
+ const previousState = getDirIndexState(db, dirPath, stateFiles, builtAtMs);
455
+ if (isIncremental && !previousState.stale && canUseIncrementalSkip(previousState, priorDirsChanged)) {
456
+ skippedDirs++;
457
+ dirRecords.push({
458
+ dirPath,
459
+ currentStashDir,
460
+ files: stateFiles,
461
+ stash: null,
462
+ skip: true,
463
+ reason: previousState.reason,
464
+ });
465
+ reportDirDecision("skip", dirPath, currentStashDir, previousState.reason, previousState.persistedRowCount);
466
+ return;
467
+ }
468
+ scannedDirs++;
469
+ priorDirsChanged = true;
470
+ const reason = isIncremental ? previousState.reason : { kind: "full-rebuild" };
471
+ dirRecords.push({
472
+ dirPath,
473
+ currentStashDir,
474
+ files: stateFiles,
475
+ stash,
476
+ skip: false,
477
+ reason,
478
+ persistedRowCount: previousState.persistedRowCount,
479
+ });
480
+ reportDirDecision("scan", dirPath, currentStashDir, reason, previousState.persistedRowCount);
481
+ };
372
482
  for (const sourceAdded of allSourceEntries) {
373
483
  const currentStashDir = sourceAdded.path;
374
484
  const fileContexts = walkStashFlat(currentStashDir);
@@ -406,33 +516,9 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
406
516
  }
407
517
  }
408
518
  for (const [dirPath, { files, entries }] of wikiDirGroups) {
409
- if (seenPaths.has(path.resolve(dirPath))) {
410
- const reason = { kind: "duplicate-dir" };
411
- dirRecords.push({ dirPath, currentStashDir, files, stash: null, skip: true, reason });
412
- reportDirDecision("skip", dirPath, currentStashDir, reason);
413
- continue;
414
- }
415
- seenPaths.add(path.resolve(dirPath));
416
- const previousState = getDirIndexState(db, dirPath, files, builtAtMs);
417
- if (isIncremental && !previousState.stale && canUseIncrementalSkip(previousState, priorDirsChanged)) {
418
- skippedDirs++;
419
- dirRecords.push({ dirPath, currentStashDir, files, stash: null, skip: true, reason: previousState.reason });
420
- reportDirDecision("skip", dirPath, currentStashDir, previousState.reason, previousState.persistedRowCount);
519
+ if (markSeenOrSkipDuplicate(dirPath, currentStashDir, files))
421
520
  continue;
422
- }
423
- scannedDirs++;
424
- priorDirsChanged = true;
425
- const reason = isIncremental ? previousState.reason : { kind: "full-rebuild" };
426
- dirRecords.push({
427
- dirPath,
428
- currentStashDir,
429
- files,
430
- stash: { entries },
431
- skip: false,
432
- reason,
433
- persistedRowCount: previousState.persistedRowCount,
434
- });
435
- reportDirDecision("scan", dirPath, currentStashDir, reason, previousState.persistedRowCount);
521
+ recordFreshnessDecision(dirPath, currentStashDir, files, { entries });
436
522
  }
437
523
  continue;
438
524
  }
@@ -447,13 +533,8 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
447
533
  }
448
534
  for (const [dirPath, files] of dirGroups) {
449
535
  const indexableFiles = files.filter((file) => shouldIndexStashFile(currentStashDir, file));
450
- if (seenPaths.has(path.resolve(dirPath))) {
451
- const reason = { kind: "duplicate-dir" };
452
- dirRecords.push({ dirPath, currentStashDir, files: indexableFiles, stash: null, skip: true, reason });
453
- reportDirDecision("skip", dirPath, currentStashDir, reason);
536
+ if (markSeenOrSkipDuplicate(dirPath, currentStashDir, indexableFiles))
454
537
  continue;
455
- }
456
- seenPaths.add(path.resolve(dirPath));
457
538
  if (indexableFiles.length === 0) {
458
539
  skippedDirs++;
459
540
  const reason = { kind: "no-indexable-files" };
@@ -483,37 +564,17 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
483
564
  if (generated.entries.length > 0) {
484
565
  generatedCount += generated.entries.length;
485
566
  }
486
- const previousState = getDirIndexState(db, dirPath, staleFiles, builtAtMs);
487
- if (isIncremental && !previousState.stale && canUseIncrementalSkip(previousState, priorDirsChanged)) {
488
- skippedDirs++;
489
- dirRecords.push({
490
- dirPath,
491
- currentStashDir,
492
- files: staleFiles,
493
- stash: null,
494
- skip: true,
495
- reason: previousState.reason,
496
- });
497
- reportDirDecision("skip", dirPath, currentStashDir, previousState.reason, previousState.persistedRowCount);
498
- continue;
499
- }
500
- scannedDirs++;
501
- priorDirsChanged = true;
502
- const reason = isIncremental ? previousState.reason : { kind: "full-rebuild" };
503
- dirRecords.push({
504
- dirPath,
505
- currentStashDir,
506
- files: staleFiles,
507
- stash,
508
- skip: false,
509
- reason,
510
- persistedRowCount: previousState.persistedRowCount,
511
- });
512
- reportDirDecision("scan", dirPath, currentStashDir, reason, previousState.persistedRowCount);
567
+ recordFreshnessDecision(dirPath, currentStashDir, staleFiles, stash);
513
568
  }
514
569
  }
515
- // Phase 2 (sync): write all pre-generated metadata inside a single transaction.
516
- //
570
+ return { dirRecords, scannedDirs, skippedDirs, generatedCount, warnings };
571
+ }
572
+ /**
573
+ * Phase 2 (sync): write all pre-generated scan records inside a single
574
+ * transaction, returning the directories that still need LLM enrichment.
575
+ */
576
+ function persistDirRecords(db, dirRecords, doFullDelete, warnings) {
577
+ const dirsNeedingLlm = [];
517
578
  // Cross-stash dedup: track indexed assets by content identity
518
579
  // (type + filename + description) so the same asset from a lower-priority
519
580
  // stash root is skipped when a higher-priority root already covers it.
@@ -635,101 +696,17 @@ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadR
635
696
  }
636
697
  });
637
698
  insertTransaction();
638
- return { scannedDirs, skippedDirs, generatedCount, warnings, dirsNeedingLlm };
699
+ return { dirsNeedingLlm };
639
700
  }
640
- function getDirIndexState(db, dirPath, files, builtAtMs) {
641
- const prevEntries = getEntriesByDir(db, dirPath);
642
- const fingerprint = computeDirFingerprint(dirPath, files);
643
- if (prevEntries.length > 0) {
644
- const staleReason = getDirStaleReason(dirPath, files, prevEntries, builtAtMs);
645
- if (!staleReason) {
646
- return { stale: false, reason: { kind: "unchanged" }, persistedRowCount: prevEntries.length };
647
- }
648
- return { stale: true, reason: staleReason, persistedRowCount: prevEntries.length };
649
- }
650
- const cachedState = getIndexDirState(db, dirPath);
651
- if (cachedState &&
652
- cachedState.fileSetHash === fingerprint.fileSetHash &&
653
- cachedState.fileMtimeMaxMs === fingerprint.fileMtimeMaxMs) {
654
- return {
655
- stale: false,
656
- reason: { kind: "cached-zero-row-state", detail: cachedState.reason },
657
- persistedRowCount: 0,
658
- };
659
- }
660
- return {
661
- stale: true,
662
- reason: { kind: "no-previous-rows", detail: cachedState ? `cached=${cachedState.reason}` : undefined },
663
- persistedRowCount: 0,
664
- };
665
- }
666
- function getCachedZeroRowDirState(db, dirPath, files, builtAtMs, priorDirsChanged) {
667
- const state = getDirIndexState(db, dirPath, files, builtAtMs);
668
- if (state.stale || state.reason.kind !== "cached-zero-row-state")
669
- return undefined;
670
- if (!canUseIncrementalSkip(state, priorDirsChanged))
671
- return undefined;
672
- return state;
673
- }
674
- function canUseIncrementalSkip(state, priorDirsChanged) {
675
- return !(priorDirsChanged &&
676
- state.reason.kind === "cached-zero-row-state" &&
677
- state.reason.detail === "deduped-zero-row");
678
- }
679
- function computeDirFingerprint(_dirPath, files) {
680
- const normalizedFiles = [...new Set(files.map((file) => path.basename(file)))].sort();
681
- let fileMtimeMaxMs = 0;
682
- for (const file of files) {
683
- try {
684
- fileMtimeMaxMs = Math.max(fileMtimeMaxMs, fs.statSync(file).mtimeMs);
685
- }
686
- catch {
687
- fileMtimeMaxMs = Number.POSITIVE_INFINITY;
688
- break;
689
- }
690
- }
691
- return {
692
- fileSetHash: normalizedFiles.join("\0"),
693
- fileMtimeMaxMs,
694
- };
695
- }
696
- function getDirStaleReason(_dirPath, currentFiles, previousEntries, builtAtMs) {
697
- const prevFileNames = new Set(previousEntries
698
- .map((ie) => {
699
- const fromPath = path.basename(ie.filePath);
700
- return fromPath || ie.entry.filename;
701
- })
702
- .filter((e) => !!e));
703
- const currFileNames = new Set(currentFiles.map((f) => path.basename(f)));
704
- if (prevFileNames.size !== currFileNames.size) {
705
- return { kind: "file-set-changed", detail: `${prevFileNames.size} -> ${currFileNames.size} files` };
706
- }
707
- for (const name of currFileNames) {
708
- if (!prevFileNames.has(name))
709
- return { kind: "file-set-changed", detail: name };
710
- }
711
- for (const file of currentFiles) {
712
- try {
713
- if (fs.statSync(file).mtimeMs > builtAtMs)
714
- return { kind: "mtime-changed", detail: path.basename(file) };
715
- }
716
- catch {
717
- return { kind: "missing-file", detail: path.basename(file) };
718
- }
719
- }
720
- return undefined;
721
- }
722
- function inferZeroRowReason(stash, priorReason, warnings, dirPath, dedupedRows) {
723
- if (dedupedRows > 0)
724
- return "deduped-zero-row";
725
- const workflowNoise = warnings.some((warning) => warning.startsWith("Skipped workflow ") && warning.includes(dirPath));
726
- if (workflowNoise)
727
- return "workflow-noise";
728
- if (!stash || stash.entries.length === 0)
729
- return "empty-generated-set";
730
- return `zero-row:${priorReason?.kind ?? "unknown"}`;
701
+ async function indexEntries(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, doFullDelete = false, onProgress) {
702
+ // Phase 1 (async): walk directories and pre-generate all metadata outside the
703
+ // transaction.
704
+ const { dirRecords, scannedDirs, skippedDirs, generatedCount, warnings } = await scanSourceDirs(db, allSourceEntries, isIncremental, builtAtMs, hadRemovedSources, onProgress);
705
+ // Phase 2 (sync): write all pre-generated metadata inside a single transaction.
706
+ const { dirsNeedingLlm } = persistDirRecords(db, dirRecords, doFullDelete, warnings);
707
+ return { scannedDirs, skippedDirs, generatedCount, warnings, dirsNeedingLlm };
731
708
  }
732
- async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, _enrich = false, reEnrich = false) {
709
+ async function enhanceDirsWithLlm(db, config, dirsNeedingLlm, onProgress, signal, reEnrich = false) {
733
710
  // Resolve per-pass LLM config via the unified shim. Returns undefined when
734
711
  // either no `akm.llm` is configured or the user opted this pass out via
735
712
  // `index.enrichment.llm = false`. (#208)
@@ -899,21 +876,9 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
899
876
  const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
900
877
  const storedFingerprint = getMeta(db, "embeddingFingerprint");
901
878
  if (storedFingerprint && storedFingerprint !== currentFingerprint) {
902
- try {
903
- db.exec("DELETE FROM embeddings");
904
- }
905
- catch {
906
- /* ignore */
907
- }
908
- if (isVecAvailable(db)) {
909
- try {
910
- db.exec("DELETE FROM entries_vec");
911
- }
912
- catch {
913
- /* ignore */
914
- }
915
- }
916
- setMeta(db, "hasEmbeddings", "0");
879
+ // Model/provider changed → stored vectors are incompatible. Clear them
880
+ // (same dimension, so keep the vec table); re-embedded by this index run.
881
+ purgeEmbeddings(db);
917
882
  }
918
883
  try {
919
884
  const { embedBatch } = await import("../llm/embedder.js");
@@ -943,31 +908,44 @@ async function generateEmbeddingsForDb(db, config, onProgress, signal) {
943
908
  warnVerbose(`[embed] ${ref} (${chars} chars, est. ${tokens} tokens) → batch ${batchNum}/${totalBatches}`);
944
909
  }
945
910
  }
946
- const embeddings = await embedBatch(texts, config.embedding, signal);
947
- throwIfAborted(signal);
948
- // Wrap all embedding upserts in a single transaction so partial
949
- // state is rolled back on failure rather than leaving the table half-filled.
950
- let storedCount = 0;
951
- let skippedCount = 0;
952
- db.transaction(() => {
953
- for (let i = 0; i < allEntries.length; i++) {
954
- if (upsertEmbedding(db, allEntries[i].id, embeddings[i])) {
955
- storedCount++;
956
- }
957
- else {
958
- skippedCount++;
911
+ let heartbeatTimer;
912
+ try {
913
+ heartbeatTimer = setInterval(() => {
914
+ onProgress({
915
+ phase: "embeddings",
916
+ message: `Still generating embeddings for ${allEntries.length} entr${allEntries.length === 1 ? "y" : "ies"}; waiting on embedding provider.`,
917
+ });
918
+ }, 15000);
919
+ const embeddings = await embedBatch(texts, config.embedding, signal);
920
+ throwIfAborted(signal);
921
+ // Wrap all embedding upserts in a single transaction so partial
922
+ // state is rolled back on failure rather than leaving the table half-filled.
923
+ let storedCount = 0;
924
+ let skippedCount = 0;
925
+ db.transaction(() => {
926
+ for (let i = 0; i < allEntries.length; i++) {
927
+ if (upsertEmbedding(db, allEntries[i].id, embeddings[i])) {
928
+ storedCount++;
929
+ }
930
+ else {
931
+ skippedCount++;
932
+ }
959
933
  }
934
+ })();
935
+ if (skippedCount > 0) {
936
+ warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
960
937
  }
961
- })();
962
- if (skippedCount > 0) {
963
- warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
938
+ onProgress({
939
+ phase: "embeddings",
940
+ message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
941
+ });
942
+ setMeta(db, "embeddingFingerprint", currentFingerprint);
943
+ return { success: true };
944
+ }
945
+ finally {
946
+ if (heartbeatTimer)
947
+ clearInterval(heartbeatTimer);
964
948
  }
965
- onProgress({
966
- phase: "embeddings",
967
- message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"}.`,
968
- });
969
- setMeta(db, "embeddingFingerprint", currentFingerprint);
970
- return { success: true };
971
949
  }
972
950
  catch (error) {
973
951
  const message = error instanceof Error ? error.message : String(error);
@@ -1078,7 +1056,7 @@ function resolveIndexedFiles(dirPath, files, stash) {
1078
1056
  for (const entry of stash.entries) {
1079
1057
  const entryPath = entry.filename
1080
1058
  ? path.join(dirPath, entry.filename)
1081
- : matchEntryToFile(entry.name, fileBasenameMap, files);
1059
+ : matchEntryToFile(entry.name, fileBasenameMap);
1082
1060
  if (entryPath)
1083
1061
  resolved.add(entryPath);
1084
1062
  }
@@ -1197,7 +1175,7 @@ export function buildFileBasenameMap(files) {
1197
1175
  * try matching the last segment
1198
1176
  * 3. No implicit file fallback: ambiguous legacy entries are skipped
1199
1177
  */
1200
- export function matchEntryToFile(entryName, fileMap, _files) {
1178
+ export function matchEntryToFile(entryName, fileMap) {
1201
1179
  // Exact match on entry name
1202
1180
  const exact = fileMap.get(entryName);
1203
1181
  if (exact)