akm-cli 0.9.0-rc.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/CHANGELOG.md +1063 -44
  2. package/README.md +51 -25
  3. package/SECURITY.md +14 -1
  4. package/STABILITY.md +497 -0
  5. package/dist/akm +148 -35
  6. package/dist/{akm-migrate-storage → akm-migrate} +6 -9
  7. package/dist/assets/hints/cli-hints-full.md +223 -95
  8. package/dist/assets/hints/cli-hints-short.md +85 -22
  9. package/dist/assets/improve-strategies/default.json +1 -1
  10. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  11. package/dist/assets/prompts/memory-infer-user.md +2 -3
  12. package/dist/assets/stash-skeleton/README.md +6 -5
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
  24. package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
  25. package/dist/assets/tasks/core/extract.yml +1 -1
  26. package/dist/assets/tasks/core/version-check.yml +1 -1
  27. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  28. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  29. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  30. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  31. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  32. package/dist/assets/templates/html/health.html +1 -3
  33. package/dist/assets/workflows/workflow-template.md +32 -15
  34. package/dist/cli/invocation.js +40 -15
  35. package/dist/cli/parse-args.js +0 -22
  36. package/dist/cli/retired-commands.js +121 -0
  37. package/dist/cli/shared.js +154 -22
  38. package/dist/cli/unknown-flags.js +236 -0
  39. package/dist/cli-node.mjs +2 -1
  40. package/dist/cli.js +696 -258
  41. package/dist/commands/agent/agent-dispatch.js +14 -3
  42. package/dist/commands/agent/contribute-cli.js +73 -88
  43. package/dist/commands/completions.js +79 -22
  44. package/dist/commands/config-cli.js +17 -150
  45. package/dist/commands/env/env-cli.js +59 -143
  46. package/dist/commands/env/env.js +12 -163
  47. package/dist/commands/env/marker-path.js +6 -0
  48. package/dist/commands/env/secret-cli.js +36 -66
  49. package/dist/commands/env/secret.js +24 -57
  50. package/dist/commands/feedback-cli.js +141 -87
  51. package/dist/commands/health/accept-rate.js +58 -0
  52. package/dist/commands/health/advisories.js +3 -4
  53. package/dist/commands/health/checks.js +85 -23
  54. package/dist/commands/health/html-report.js +7 -10
  55. package/dist/commands/health/improve-metrics.js +25 -83
  56. package/dist/commands/health/md-report.js +5 -9
  57. package/dist/commands/health/metrics.js +62 -20
  58. package/dist/commands/health/renderers.js +47 -0
  59. package/dist/commands/health/report-view-model.js +4 -5
  60. package/dist/commands/health/stash-exposure.js +1 -1
  61. package/dist/commands/health/surfaces.js +3 -48
  62. package/dist/commands/health/task-runs.js +3 -67
  63. package/dist/commands/health/types-improve.js +7 -0
  64. package/dist/commands/health.js +99 -28
  65. package/dist/commands/improve/anti-collapse.js +2 -2
  66. package/dist/commands/improve/autonomy-gate.js +68 -0
  67. package/dist/commands/improve/collapse-detector.js +41 -40
  68. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  69. package/dist/commands/improve/consolidate/merge.js +4 -0
  70. package/dist/commands/improve/consolidate.js +140 -1000
  71. package/dist/commands/improve/distill/promote-memory.js +12 -12
  72. package/dist/commands/improve/distill/quality-gate.js +6 -6
  73. package/dist/commands/improve/distill.js +58 -69
  74. package/dist/commands/improve/eligibility.js +105 -57
  75. package/dist/commands/improve/extract-cli.js +14 -133
  76. package/dist/commands/improve/improve-cli.js +98 -114
  77. package/dist/commands/improve/improve-result-file.js +1 -28
  78. package/dist/commands/improve/improve-strategies.js +8 -5
  79. package/dist/commands/improve/improve.js +128 -91
  80. package/dist/commands/improve/loop-stages.js +182 -20
  81. package/dist/commands/improve/memory/derived-ref.js +45 -43
  82. package/dist/commands/improve/memory/memory-belief.js +1 -1
  83. package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
  84. package/dist/commands/improve/memory/memory-improve.js +6 -5
  85. package/dist/commands/improve/outcome-loop.js +22 -65
  86. package/dist/commands/improve/preparation.js +114 -123
  87. package/dist/commands/improve/proactive-maintenance.js +2 -5
  88. package/dist/commands/improve/reflect.js +56 -160
  89. package/dist/commands/improve/salience.js +11 -122
  90. package/dist/commands/improve/source-identity.js +10 -38
  91. package/dist/commands/lint/base-linter.js +20 -124
  92. package/dist/commands/lint/env-key-rules.js +31 -47
  93. package/dist/commands/lint/index.js +249 -43
  94. package/dist/commands/{events.js → log.js} +33 -38
  95. package/dist/commands/migrate-cli.js +92 -12
  96. package/dist/commands/migration-tool.js +46 -0
  97. package/dist/commands/observability-cli.js +70 -209
  98. package/dist/commands/proposal/drain.js +101 -29
  99. package/dist/commands/proposal/proposal-cli.js +76 -48
  100. package/dist/commands/proposal/proposal.js +54 -18
  101. package/dist/commands/proposal/propose-cli.js +88 -0
  102. package/dist/commands/proposal/propose.js +23 -15
  103. package/dist/commands/proposal/repository.js +701 -278
  104. package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
  105. package/dist/commands/proposal/validators/proposal-validators.js +55 -7
  106. package/dist/commands/proposal/validators/proposals.js +4 -7
  107. package/dist/commands/read/curate.js +34 -53
  108. package/dist/commands/read/knowledge.js +150 -95
  109. package/dist/commands/read/registry-search.js +2 -2
  110. package/dist/commands/read/remember-cli.js +42 -15
  111. package/dist/commands/read/search-cli.js +180 -78
  112. package/dist/commands/read/search.js +58 -43
  113. package/dist/commands/read/show.js +197 -141
  114. package/dist/commands/registry-cli.js +12 -51
  115. package/dist/commands/remember.js +14 -57
  116. package/dist/commands/sources/add-cli.js +100 -31
  117. package/dist/commands/sources/bundle-cli.js +166 -0
  118. package/dist/commands/sources/bundle-config-ops.js +7 -2
  119. package/dist/commands/sources/info.js +18 -5
  120. package/dist/commands/sources/init.js +12 -12
  121. package/dist/commands/sources/installed-stashes.js +382 -98
  122. package/dist/commands/sources/schema-repair.js +3 -2
  123. package/dist/commands/sources/self-update.js +131 -38
  124. package/dist/commands/sources/source-add.js +72 -17
  125. package/dist/commands/sources/source-clone.js +129 -45
  126. package/dist/commands/sources/source-manage.js +43 -23
  127. package/dist/commands/sources/sources-cli.js +57 -208
  128. package/dist/commands/sources/stash-cli.js +46 -53
  129. package/dist/commands/tasks/tasks-cli.js +91 -97
  130. package/dist/commands/tasks/tasks.js +276 -421
  131. package/dist/commands/workflow-cli.js +175 -450
  132. package/dist/core/adapter/adapters/akm-adapter.js +47 -28
  133. package/dist/core/adapter/adapters/akm-lint.js +42 -27
  134. package/dist/core/adapter/adapters/akm-metadata.js +15 -44
  135. package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
  136. package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
  137. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  138. package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
  139. package/dist/core/adapter/adapters/index.js +6 -6
  140. package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
  141. package/dist/core/adapter/adapters/okf-adapter.js +187 -19
  142. package/dist/core/adapter/adapters/shared.js +3 -19
  143. package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
  144. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
  145. package/dist/core/adapter/detect-adapter.js +17 -0
  146. package/dist/core/adapter/recognize-match.js +6 -4
  147. package/dist/core/adapter/validate-context.js +214 -0
  148. package/dist/core/asset/akm-markdown.js +63 -0
  149. package/dist/core/asset/asset-placement.js +20 -6
  150. package/dist/core/asset/asset-ref.js +11 -9
  151. package/dist/core/asset/frontmatter-lint.js +30 -0
  152. package/dist/core/asset/frontmatter.js +37 -9
  153. package/dist/core/asset/markdown.js +40 -51
  154. package/dist/core/asset/resolve-ref.js +89 -18
  155. package/dist/core/asset/stash-meta.js +1 -1
  156. package/dist/core/bundle-id.js +51 -0
  157. package/dist/core/common.js +152 -38
  158. package/dist/core/config/config-io.js +12 -1
  159. package/dist/core/config/config-schema.js +35 -8
  160. package/dist/core/config/config-sources.js +55 -11
  161. package/dist/core/config/config-walker.js +25 -9
  162. package/dist/core/config/config.js +9 -48
  163. package/dist/core/config/experimental.js +21 -0
  164. package/dist/core/config/schema/embedding.js +5 -1
  165. package/dist/core/config/schema/experimental.js +30 -0
  166. package/dist/core/config/schema/improve-processes.js +0 -6
  167. package/dist/core/config/schema/improve.js +21 -3
  168. package/dist/core/config/schema/index-config.js +8 -15
  169. package/dist/core/config/schema/output.js +4 -1
  170. package/dist/core/config/schema/setup.js +9 -18
  171. package/dist/core/config/schema/sources-bundles.js +49 -33
  172. package/dist/core/config/schema/workflow.js +3 -3
  173. package/dist/core/env-secret-ref.js +76 -46
  174. package/dist/core/errors.js +18 -12
  175. package/dist/core/events.js +46 -128
  176. package/dist/core/file-change.js +6 -5
  177. package/dist/core/fs-txn.js +83 -7
  178. package/dist/core/git-message.js +2 -2
  179. package/dist/core/improve-result.js +1 -100
  180. package/dist/core/lesson-lint.js +1 -17
  181. package/dist/core/logs-db.js +2 -1
  182. package/dist/core/migration-operation.js +16 -0
  183. package/dist/core/mutation-target.js +78 -0
  184. package/dist/core/parse.js +4 -1
  185. package/dist/core/paths.js +17 -20
  186. package/dist/core/recognition-util.js +12 -14
  187. package/dist/core/redaction.js +34 -0
  188. package/dist/core/standards/resolve-standards-context.js +2 -14
  189. package/dist/core/standards/resolve-stash-standards.js +2 -2
  190. package/dist/core/standards/resolve-type-conventions.js +2 -2
  191. package/dist/core/state/migrations.js +41 -18
  192. package/dist/core/state-db.js +5 -14
  193. package/dist/core/structured.js +1 -1
  194. package/dist/core/subprocess.js +6 -4
  195. package/dist/core/text-truncation.js +9 -5
  196. package/dist/core/type-presentation.js +3 -3
  197. package/dist/core/warn.js +0 -3
  198. package/dist/core/write-source.js +771 -95
  199. package/dist/indexer/bundle-identity-guard.js +3 -2
  200. package/dist/indexer/db/graph-db.js +0 -24
  201. package/dist/indexer/ensure-index.js +1 -0
  202. package/dist/indexer/graph/graph-boost.js +9 -34
  203. package/dist/indexer/graph/graph-extraction.js +8 -5
  204. package/dist/indexer/index-writer-lock.js +53 -17
  205. package/dist/indexer/index-written-assets.js +16 -22
  206. package/dist/indexer/indexer.js +497 -239
  207. package/dist/indexer/installations.js +14 -96
  208. package/dist/indexer/passes/dir-staleness.js +16 -9
  209. package/dist/indexer/passes/memory-inference.js +11 -9
  210. package/dist/indexer/passes/metadata.js +113 -47
  211. package/dist/indexer/scan/doc-to-entry.js +38 -1
  212. package/dist/indexer/scan/drain-dir.js +13 -23
  213. package/dist/indexer/search/db-search.js +99 -54
  214. package/dist/indexer/search/fts-query.js +47 -24
  215. package/dist/indexer/search/ranking-contributors.js +42 -20
  216. package/dist/indexer/search/ranking.js +18 -99
  217. package/dist/indexer/search/search-fields.js +7 -2
  218. package/dist/indexer/search/search-source.js +82 -93
  219. package/dist/indexer/usage/usage-events.js +0 -89
  220. package/dist/indexer/walk/file-context.js +2 -1
  221. package/dist/indexer/walk/matchers.js +30 -43
  222. package/dist/indexer/walk/path-resolver.js +7 -2
  223. package/dist/indexer/walk/walker.js +38 -12
  224. package/dist/integrations/agent/builders.js +0 -6
  225. package/dist/integrations/agent/config.js +2 -2
  226. package/dist/integrations/agent/detect.js +49 -19
  227. package/dist/integrations/agent/engine-fallback.js +76 -0
  228. package/dist/integrations/agent/profiles.js +14 -0
  229. package/dist/integrations/agent/prompts.js +12 -8
  230. package/dist/integrations/agent/runner-dispatch.js +4 -2
  231. package/dist/integrations/agent/runner.js +0 -1
  232. package/dist/integrations/agent/spawn.js +5 -6
  233. package/dist/integrations/github.js +1 -1
  234. package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
  235. package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
  236. package/dist/integrations/harnesses/claude/session-log.js +0 -10
  237. package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
  238. package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
  239. package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
  240. package/dist/integrations/harnesses/index.js +3 -7
  241. package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
  242. package/dist/integrations/harnesses/opencode/session-log.js +0 -15
  243. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
  244. package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
  245. package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
  246. package/dist/integrations/lockfile.js +101 -6
  247. package/dist/integrations/session-logs/index.js +3 -28
  248. package/dist/llm/client.js +136 -100
  249. package/dist/llm/embedders/remote.js +13 -5
  250. package/dist/llm/feature-gate.js +4 -12
  251. package/dist/llm/graph-extract.js +5 -11
  252. package/dist/llm/memory-infer.js +144 -1
  253. package/dist/llm/metadata-enhance.js +5 -7
  254. package/dist/llm/structured-call.js +1 -1
  255. package/dist/llm/usage-persist.js +26 -5
  256. package/dist/llm/usage-telemetry.js +25 -2
  257. package/dist/output/cli-hints.js +1 -2
  258. package/dist/output/context.js +22 -7
  259. package/dist/output/format-exempt.js +80 -0
  260. package/dist/output/generic-render.js +259 -0
  261. package/dist/output/render-registry.js +57 -0
  262. package/dist/output/renderers.js +14 -36
  263. package/dist/output/shapes/curate.js +10 -1
  264. package/dist/output/shapes/events.js +12 -7
  265. package/dist/output/shapes/helpers.js +56 -83
  266. package/dist/output/shapes/migrate.js +8 -0
  267. package/dist/output/shapes/passthrough.js +7 -41
  268. package/dist/output/shapes/proposal/producer.js +15 -7
  269. package/dist/output/shapes.js +2 -9
  270. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  271. package/dist/output/text/bundle-show.js +7 -0
  272. package/dist/output/text/command-format.js +164 -96
  273. package/dist/output/text/env.js +1 -3
  274. package/dist/output/text/events.js +8 -7
  275. package/dist/output/text/health-format.js +103 -0
  276. package/dist/output/text/health.js +7 -0
  277. package/dist/output/text/helpers.js +10 -8
  278. package/dist/output/text/lint-format.js +43 -0
  279. package/dist/output/text/{save.js → lint.js} +2 -2
  280. package/dist/output/text/migrate.js +88 -0
  281. package/dist/output/text/proposal/producer.js +4 -2
  282. package/dist/output/text/proposal-format.js +44 -72
  283. package/dist/output/text/registry-commands.js +1 -2
  284. package/dist/output/text/show-directives.js +15 -7
  285. package/dist/output/text/status-list.js +32 -0
  286. package/dist/output/text/sync.js +5 -0
  287. package/dist/output/text/workflow-format.js +24 -203
  288. package/dist/output/text/workflow.js +1 -7
  289. package/dist/output/text.js +16 -17
  290. package/dist/registry/factory.js +4 -6
  291. package/dist/registry/origin-resolve.js +16 -27
  292. package/dist/registry/providers/skills-sh.js +3 -3
  293. package/dist/registry/providers/static-index.js +13 -23
  294. package/dist/registry/resolve.js +42 -7
  295. package/dist/registry/semver.js +34 -84
  296. package/dist/runtime.js +2 -23
  297. package/dist/scripts/akm-migrate-node.js +60290 -0
  298. package/dist/scripts/akm-migrate.js +59628 -0
  299. package/dist/setup/detect.js +42 -15
  300. package/dist/setup/registry-stash-loader.js +2 -2
  301. package/dist/setup/setup.js +236 -136
  302. package/dist/setup/steps/connection.js +7 -9
  303. package/dist/setup/steps/platforms.js +9 -9
  304. package/dist/setup/steps/semantic.js +15 -3
  305. package/dist/setup/steps/sources.js +12 -13
  306. package/dist/setup/steps/stashdir.js +2 -3
  307. package/dist/setup/steps/tasks.js +237 -120
  308. package/dist/sources/freshness.js +1 -1
  309. package/dist/sources/provider-factory.js +11 -17
  310. package/dist/sources/providers/filesystem.js +2 -3
  311. package/dist/sources/providers/git-install.js +278 -34
  312. package/dist/sources/providers/git-provider.js +25 -23
  313. package/dist/sources/providers/git-stash.js +395 -106
  314. package/dist/sources/providers/git.js +2 -2
  315. package/dist/sources/providers/npm.js +16 -19
  316. package/dist/sources/providers/provider-utils.js +7 -4
  317. package/dist/sources/providers/sync-from-ref.js +3 -9
  318. package/dist/sources/providers/website.js +6 -1
  319. package/dist/sources/resolve.js +6 -5
  320. package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
  321. package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
  322. package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
  323. package/dist/sources/snapshot-fetchers/github.js +100 -0
  324. package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
  325. package/dist/sources/snapshot-fetchers/registry.js +17 -1
  326. package/dist/sources/snapshot-fetchers/robots.js +348 -0
  327. package/dist/sources/snapshot-fetchers/rss.js +282 -0
  328. package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
  329. package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
  330. package/dist/sources/snapshot-fetchers/x.js +910 -0
  331. package/dist/storage/database.js +7 -0
  332. package/dist/storage/engines/sqlite-migrations.js +23 -111
  333. package/dist/storage/managed-db.js +2 -2
  334. package/dist/storage/repositories/canaries-repository.js +1 -1
  335. package/dist/storage/repositories/events-repository.js +27 -11
  336. package/dist/storage/repositories/improve-runs-repository.js +6 -12
  337. package/dist/storage/repositories/index-connection.js +17 -6
  338. package/dist/storage/repositories/index-entries-repository.js +151 -240
  339. package/dist/storage/repositories/index-entry-mapper.js +15 -11
  340. package/dist/storage/repositories/index-fts-repository.js +5 -2
  341. package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
  342. package/dist/storage/repositories/index-meta-repository.js +2 -3
  343. package/dist/storage/repositories/index-schema.js +10 -25
  344. package/dist/storage/repositories/index-utility-repository.js +15 -28
  345. package/dist/storage/repositories/index-vec-repository.js +6 -1
  346. package/dist/storage/repositories/outcome-repository.js +119 -0
  347. package/dist/storage/repositories/proposals-repository.js +296 -59
  348. package/dist/storage/repositories/registry-cache.js +19 -0
  349. package/dist/storage/repositories/salience-repository.js +172 -0
  350. package/dist/storage/repositories/task-history-repository.js +15 -13
  351. package/dist/storage/repositories/workflow-runs-repository.js +52 -40
  352. package/dist/tasks/backends/cron.js +105 -15
  353. package/dist/tasks/backends/index.js +1 -1
  354. package/dist/tasks/backends/launchd.js +85 -38
  355. package/dist/tasks/backends/schtasks.js +135 -15
  356. package/dist/tasks/embedded.js +56 -40
  357. package/dist/tasks/parser.js +7 -157
  358. package/dist/tasks/resolve-akm-bin.js +137 -59
  359. package/dist/tasks/runner.js +79 -42
  360. package/dist/tasks/scheduler-invocation.js +220 -10
  361. package/dist/tasks/schema.js +24 -1
  362. package/dist/tasks/task-id.js +1 -3
  363. package/dist/tasks/validator.js +20 -6
  364. package/dist/workflows/authoring/authoring.js +94 -143
  365. package/dist/workflows/authoring/scope-key.js +1 -1
  366. package/dist/workflows/exec/frozen-judge.js +28 -2
  367. package/dist/workflows/exec/native-executor.js +77 -57
  368. package/dist/workflows/exec/param-secrets.js +9 -9
  369. package/dist/workflows/exec/run-workflow.js +133 -79
  370. package/dist/workflows/exec/step-work.js +219 -346
  371. package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
  372. package/dist/workflows/ir/compile.js +141 -270
  373. package/dist/workflows/ir/freeze.js +40 -30
  374. package/dist/workflows/ir/params.js +135 -11
  375. package/dist/workflows/ir/plan-hash.js +1 -1
  376. package/dist/workflows/ir/schema.js +25 -26
  377. package/dist/workflows/parser.js +872 -307
  378. package/dist/workflows/program/expressions.js +20 -208
  379. package/dist/workflows/program/schema.js +7 -10
  380. package/dist/workflows/renderer.js +95 -68
  381. package/dist/workflows/resource-limits.js +2 -0
  382. package/dist/workflows/runtime/checkin.js +3 -3
  383. package/dist/workflows/runtime/plan-classifier.js +16 -75
  384. package/dist/workflows/runtime/runs.js +186 -127
  385. package/dist/workflows/runtime/unit-checkin.js +1 -1
  386. package/dist/workflows/runtime/unit-phases.js +2 -2
  387. package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
  388. package/dist/workflows/schema.js +1 -11
  389. package/dist/workflows/validate-summary.js +30 -36
  390. package/dist/workflows/validator.js +21 -62
  391. package/docs/README.md +68 -0
  392. package/docs/migration/README.md +8 -0
  393. package/docs/migration/release-notes/0.7.0.md +11 -11
  394. package/docs/migration/release-notes/0.9.0.md +208 -27
  395. package/docs/migration/v0.7-to-v0.8.md +46 -47
  396. package/docs/migration/v0.8-to-v0.9.md +564 -208
  397. package/docs/migration/v0.9.0-troubleshooting.md +561 -0
  398. package/docs/reference/README.md +12 -0
  399. package/docs/reference/cli.md +2253 -0
  400. package/docs/reference/configuration.md +358 -0
  401. package/docs/reference/data-and-telemetry.md +105 -42
  402. package/docs/reference/workflows.md +647 -0
  403. package/package.json +22 -11
  404. package/schemas/akm-asset-envelope.json +93 -0
  405. package/schemas/akm-config.json +81 -128
  406. package/schemas/akm-workflow.json +74 -73
  407. package/dist/assets/tasks/core/backup.yml +0 -5
  408. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  409. package/dist/cli/config-migrate.js +0 -1806
  410. package/dist/cli/config-validate.js +0 -41
  411. package/dist/commands/backup-cli.js +0 -56
  412. package/dist/commands/bundle/bundle-cli.js +0 -68
  413. package/dist/commands/bundle/bundle.js +0 -219
  414. package/dist/commands/graph/graph-cli.js +0 -124
  415. package/dist/commands/graph/graph.js +0 -489
  416. package/dist/commands/improve/extract-watch.js +0 -140
  417. package/dist/commands/mv-cli.js +0 -1221
  418. package/dist/commands/sources/history.js +0 -201
  419. package/dist/commands/tasks/default-tasks.js +0 -186
  420. package/dist/core/migration-backup.js +0 -1234
  421. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
  422. package/dist/llm/memory-infer-impl.js +0 -138
  423. package/dist/migrate/legacy/config-source-migration.js +0 -223
  424. package/dist/migrate/legacy/content-migration.js +0 -305
  425. package/dist/migrate/legacy/legacy-layout.js +0 -779
  426. package/dist/migrate/legacy/legacy-paths.js +0 -25
  427. package/dist/migrate/legacy/legacy-stash-json.js +0 -72
  428. package/dist/migrate/legacy/proposal-fs-import.js +0 -168
  429. package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
  430. package/dist/migrate/legacy/three-db-cutover.js +0 -841
  431. package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
  432. package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
  433. package/dist/migrate/legacy-ref-grammar.js +0 -214
  434. package/dist/output/shapes/distill.js +0 -14
  435. package/dist/output/shapes/history.js +0 -11
  436. package/dist/output/text/distill.js +0 -6
  437. package/dist/output/text/enable-disable.js +0 -8
  438. package/dist/output/text/history.js +0 -6
  439. package/dist/registry/build-index.js +0 -382
  440. package/dist/schemas/akm-config.json +0 -4704
  441. package/dist/schemas/akm-task.json +0 -87
  442. package/dist/schemas/akm-workflow.json +0 -372
  443. package/dist/scripts/migrate-storage.js +0 -3816
  444. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  445. package/dist/workflows/cli.js +0 -53
  446. package/dist/workflows/exec/brief.js +0 -481
  447. package/dist/workflows/exec/report.js +0 -1460
  448. package/dist/workflows/exec/watch.js +0 -116
  449. package/dist/workflows/program/parser.js +0 -813
  450. package/dist/workflows/program/project.js +0 -104
@@ -5,9 +5,8 @@
5
5
  * `index.db` entries repository — CRUD, lookup, re-key, delete-cascade, and the
6
6
  * usage-event relink + workflow-document + tag-set reads that key on `entries`.
7
7
  *
8
- * Owns ALL raw SQL against the `entries` table (WS5). Extracted verbatim from
9
- * `src/indexer/db/db.ts` (WI-5a); the shared row/option shapes now come from the
10
- * leaf types + mapper modules rather than from the old `db.ts` hub.
8
+ * Owns ALL raw SQL against the `entries` table. The shared row/option shapes
9
+ * come from the leaf types + mapper modules.
11
10
  */
12
11
  import fs from "node:fs";
13
12
  import path from "node:path";
@@ -19,7 +18,7 @@ import { warn } from "../../core/warn.js";
19
18
  import { buildSearchText } from "../../indexer/search/search-fields.js";
20
19
  import { ENTRY_COLUMNS, rowToIndexedEntry } from "./index-entry-mapper.js";
21
20
  import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
22
- import { isVecAvailable } from "./index-vec-repository.js";
21
+ import { deleteEntryVectors, isVecAvailable } from "./index-vec-repository.js";
23
22
  // ── Entry operations ────────────────────────────────────────────────────────
24
23
  /**
25
24
  * Insert or update an entry in the `entries` table. Returns the row id.
@@ -29,11 +28,12 @@ import { isVecAvailable } from "./index-vec-repository.js";
29
28
  * reflect the changes.
30
29
  */
31
30
  export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, searchText, provenance, contentHash) {
32
- // Hot path during indexing — cache the two prepared statements per
33
- // database connection so we don't pay the SQL parse/compile cost on
34
- // every call. The dirty-mark INSERT and the upsert-with-RETURNING
35
- // share the same WeakMap so they live and die with the connection.
31
+ // Hot path during indexing — cache prepared statements per database
32
+ // connection so we don't pay the SQL parse/compile cost on every call.
36
33
  const stmts = getUpsertStmts(db);
34
+ const previous = (provenance?.itemRef
35
+ ? stmts.findByItemRef.get(provenance.itemRef)
36
+ : undefined) ?? stmts.findByEntryKey.get(entryKey);
37
37
  // Phase 5A / Advantage D5: surface derived memory parent ref into the
38
38
  // dedicated `derived_from` column so retrieval-time lookup (parent→child)
39
39
  // does not have to scan + JSON-decode every memory row.
@@ -45,22 +45,29 @@ export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, se
45
45
  // by the next full index). `content_hash` (F4a M-core-2) is `doc.hash` from
46
46
  // the diff-persist writer; a NULL passed here PRESERVES any existing hash (the
47
47
  // ON CONFLICT COALESCE below) so the LLM-enrichment re-upsert cannot wipe it.
48
- const result = stmts.upsert.get(entryKey, dirPath, filePath, stashDir, JSON.stringify(entry), searchText, entry.type, derivedFrom, provenance?.itemRef ?? null, provenance?.bundleId ?? null, provenance?.componentId ?? null, provenance?.conceptId ?? null, provenance?.adapterId ?? null, entry.type, contentHash ?? null);
49
- if (!result)
50
- throw new Error("upsertEntry: entry_key not found after upsert");
51
- // Mark this entry as FTS-dirty so `rebuildFts({ incremental: true })`
52
- // only revisits entries that actually changed. INSERT OR IGNORE is
53
- // idempotent across multiple upserts of the same row.
54
- stmts.markDirty.run(result.id);
55
- return result.id;
48
+ const apply = () => {
49
+ const result = stmts.upsert.get(entryKey, dirPath, filePath, stashDir, JSON.stringify(entry), searchText, entry.type, derivedFrom, provenance?.itemRef ?? null, provenance?.bundleId ?? null, provenance?.componentId ?? null, provenance?.conceptId ?? null, provenance?.adapterId ?? null, entry.type, contentHash ?? null);
50
+ if (!result)
51
+ throw new Error("upsertEntry: entry_key not found after upsert");
52
+ if (previous?.id === result.id && previous.search_text !== searchText)
53
+ deleteEntryVectors(db, result.id);
54
+ // Mark this entry as FTS-dirty so `rebuildFts({ incremental: true })`
55
+ // only revisits entries that actually changed. INSERT OR IGNORE is
56
+ // idempotent across multiple upserts of the same row.
57
+ stmts.markDirty.run(result.id);
58
+ return result.id;
59
+ };
60
+ return previous && previous.search_text !== searchText ? db.transaction(apply)() : apply();
56
61
  }
57
62
  const upsertStmtsByDb = new WeakMap();
58
63
  // The ON CONFLICT DO UPDATE column assignments — factored out so the two
59
- // conflict targets below stay byte-identical. `entry_key` is deliberately NOT
60
- // updated (it is an identity column; renames go through `rekeyEntryInPlace`).
64
+ // conflict targets below stay byte-identical. `item_ref` is durable identity;
65
+ // update `entry_key` when adapter ownership changes its
66
+ // spelling so directory pruning retains the upserted row.
61
67
  // `content_hash` COALESCEs so a NULL passed by the LLM-enhance re-upsert cannot
62
68
  // wipe a previously-persisted hash.
63
69
  const UPSERT_SET_CLAUSE = `SET
70
+ entry_key = excluded.entry_key,
64
71
  dir_path = excluded.dir_path,
65
72
  file_path = excluded.file_path,
66
73
  stash_dir = excluded.stash_dir,
@@ -75,53 +82,14 @@ const UPSERT_SET_CLAUSE = `SET
75
82
  adapter_id = excluded.adapter_id,
76
83
  type = excluded.type,
77
84
  content_hash = COALESCE(excluded.content_hash, content_hash)`;
78
- /**
79
- * Whether `entries.item_ref` currently carries its UNIQUE index (the v19
80
- * `idx_entries_item_ref`, built by `ensureUniqueItemRefIndex`). On a
81
- * partially-migrated DB with a duplicate non-NULL item_ref, that build falls
82
- * back to a NON-unique index — and `ON CONFLICT(item_ref)` is only a legal
83
- * conflict target when a UNIQUE index/constraint backs the column (otherwise
84
- * the upsert throws AT PREPARE TIME). This gate lets {@link getUpsertStmts}
85
- * degrade to the always-present `entry_key` target until a rebuild restores
86
- * uniqueness. Best-effort: any probe failure treats item_ref as non-unique.
87
- */
88
- function itemRefHasUniqueIndex(db) {
89
- try {
90
- const indexes = db.prepare("PRAGMA index_list(entries)").all();
91
- return indexes.some((idx) => idx.name === "idx_entries_item_ref" && Number(idx.unique) === 1);
92
- }
93
- catch {
94
- return false;
95
- }
96
- }
97
85
  function getUpsertStmts(db) {
98
86
  const existing = upsertStmtsByDb.get(db);
99
87
  if (existing)
100
88
  return existing;
101
- // Conflict target (spec §3.3): the UNIQUE `item_ref` is THE intended clean
102
- // dedupe key, so it is the PRIMARY target — a row carrying its durable
103
- // identity dedupes on `item_ref`. `entry_key` is retained as a SECOND,
104
- // NULL-safe fallback target because a legacy write path can still upsert an
105
- // EXISTING row with a NULL `item_ref` (SQLite treats NULLs as distinct in a
106
- // UNIQUE index, so an item_ref-only target would miss the row, fall through
107
- // to a plain INSERT, and ABORT on the `entry_key NOT NULL UNIQUE` constraint).
108
- // Concretely, the out-of-scope LLM metadata-enhance re-upsert (indexer.ts
109
- // `enhanceDirsWithLlm`) re-writes already-indexed rows without provenance →
110
- // NULL item_ref; the `entry_key` arm keeps that re-upsert an UPDATE, not a
111
- // crash. Both arms run the IDENTICAL assignment block, so the outcome is the
112
- // same regardless of which constraint matches. The `entry_key` fallback is
113
- // deletable once every write path sets `item_ref`.
114
- //
115
- // When item_ref lacks its UNIQUE index (the ensureUniqueItemRefIndex fallback
116
- // on a partially-migrated DB), `ON CONFLICT(item_ref)` is not a legal target
117
- // and would fail at prepare time — so we degrade to the entry_key-only upsert
118
- // (behaviour-identical to the pre-repoint key) until the next rebuild restores
119
- // uniqueness; without this the very rebuild meant to heal the duplicate could
120
- // not run.
121
- const conflictClause = itemRefHasUniqueIndex(db)
122
- ? `ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
123
- ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`
124
- : `ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`;
89
+ // Durable identity is the primary conflict target. `entry_key` remains the
90
+ // internal conflict key for low-level entries without provenance.
91
+ const conflictClause = `ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
92
+ ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`;
125
93
  const stmts = {
126
94
  // RETURNING id handles ON CONFLICT DO UPDATE correctly — no second
127
95
  // SELECT round-trip needed (last_insert_rowid() is unreliable for
@@ -136,6 +104,8 @@ function getUpsertStmts(db) {
136
104
  RETURNING id
137
105
  `),
138
106
  markDirty: db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)"),
107
+ findByItemRef: db.prepare("SELECT id, search_text FROM entries WHERE item_ref = ? ORDER BY id ASC LIMIT 1"),
108
+ findByEntryKey: db.prepare("SELECT id, search_text FROM entries WHERE entry_key = ? LIMIT 1"),
139
109
  };
140
110
  upsertStmtsByDb.set(db, stmts);
141
111
  return stmts;
@@ -153,25 +123,18 @@ function getUpsertStmts(db) {
153
123
  export function getDerivedForParent(db, parentRef, stashDir) {
154
124
  if (!parentRef)
155
125
  return null;
156
- try {
157
- const sourceScope = stashDir ? "AND stash_dir = ?" : "";
158
- const row = db
159
- .prepare(`SELECT ${ENTRY_COLUMNS}
126
+ const sourceScope = stashDir ? "AND stash_dir = ?" : "";
127
+ const row = db
128
+ .prepare(`SELECT ${ENTRY_COLUMNS}
160
129
  FROM entries
161
130
  WHERE derived_from = ?
162
131
  ${sourceScope}
163
132
  ORDER BY id DESC
164
133
  LIMIT 1`)
165
- .get(parentRef, ...(stashDir ? [stashDir] : []));
166
- if (!row)
167
- return null;
168
- return rowToIndexedEntry(row, "getDerivedForParent");
169
- }
170
- catch {
171
- /* `derived_from` column may not exist on legacy DBs that haven't been
172
- rebuilt; treat as "no derived child". */
134
+ .get(parentRef, ...(stashDir ? [stashDir] : []));
135
+ if (!row)
173
136
  return null;
174
- }
137
+ return rowToIndexedEntry(row, "getDerivedForParent");
175
138
  }
176
139
  /**
177
140
  * 03-R3: for the given derived-twin row ids, fetch each twin's BASE memory
@@ -198,33 +161,21 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
198
161
  const chunk = twinIds.slice(i, i + SQLITE_CHUNK_SIZE);
199
162
  const placeholders = chunk.map(() => "?").join(",");
200
163
  bestEffort(() => {
201
- // Chunk-5 flip F1: a twin's identity suffix is `.derived` on BOTH the
202
- // legacy `entry_key` (`<stash>:memory:<name>.derived`) and the new
203
- // `item_ref` (`<bundle>//memories/<name>.derived`). Join the base by
204
- // stripping that suffix on EITHER column so both fully-migrated rows
205
- // (item_ref path) and NULL-`item_ref` rows (legacy path) inherit. On a
206
- // consistent index both predicates select the SAME base row, so the OR
207
- // never double-counts; `substr(NULL, …)` is NULL and matches nothing, so
208
- // a NULL `item_ref` simply falls through to the entry_key predicate.
209
- // F5: the `entry_key` arm is the deletable legacy shim.
210
164
  const rows = db
211
165
  .prepare(`SELECT twin.id AS twin_id, json_extract(base.entry_json, '$.beliefState') AS belief
212
166
  FROM entries twin
213
167
  JOIN entries base
214
168
  ON base.entry_type = 'memory'
215
- AND (
216
- base.entry_key = substr(twin.entry_key, 1, length(twin.entry_key) - length('.derived'))
217
- OR base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
218
- )
169
+ AND base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
219
170
  WHERE twin.id IN (${placeholders})
220
- AND (twin.entry_key LIKE '%.derived' OR twin.item_ref LIKE '%.derived')
171
+ AND twin.item_ref LIKE '%.derived'
221
172
  AND json_extract(base.entry_json, '$.beliefState') IS NOT NULL`)
222
173
  .all(...chunk);
223
174
  for (const r of rows) {
224
175
  if (typeof r.belief === "string" && r.belief.trim().length > 0)
225
176
  out.set(r.twin_id, r.belief.trim());
226
177
  }
227
- }, "legacy DB / entry_json without beliefState — treat as no twin inheritance");
178
+ }, "belief-state inheritance is best-effort");
228
179
  }
229
180
  return out;
230
181
  }
@@ -242,10 +193,8 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
242
193
  * name; the row is marked FTS-dirty for the caller's
243
194
  * `rebuildFts({incremental: true})`.
244
195
  *
245
- * `usage_events.entry_ref` rows for the old ref are rewritten to the new ref
246
- * in the same transaction both the bare `type:name` spelling and the
247
- * origin-qualified `origin//type:name` spelling (search/show writers persist
248
- * either, see {@link getRetrievalCounts}). Without this, events keep the old
196
+ * Bundle-qualified `usage_events.entry_ref` rows for the old conceptId are
197
+ * rewritten to the new item ref. Without this, events keep the old
249
198
  * ref, `relinkUsageEvents` finds no matching entry after the next full
250
199
  * rebuild, and the utility history the re-key exists to preserve silently
251
200
  * resets. DETACHED orphan events already sitting at the new ref (entry_id
@@ -266,14 +215,13 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
266
215
  * full `akm index` picks the file up as a fresh entry).
267
216
  */
268
217
  export function rekeyEntryInPlace(db, opts) {
269
- // Chunk-8: the row is located by its legacy `entry_key`; the caller (mv)
270
- // always has it and it is the UNIQUE identity column until the re-key.
218
+ const oldItemRef = `${opts.sourceName}//${opts.oldRef}`;
271
219
  const row = db
272
- .prepare("SELECT id, stash_dir, entry_json, search_text, entry_type, item_ref FROM entries WHERE entry_key = ?")
273
- .get(opts.oldEntryKey);
220
+ .prepare("SELECT id, stash_dir, entry_json, search_text, entry_type FROM entries WHERE item_ref = ?")
221
+ .get(oldItemRef);
274
222
  if (!row)
275
223
  return null;
276
- if (opts.sourceRoot && path.resolve(row.stash_dir) !== path.resolve(opts.sourceRoot)) {
224
+ if (path.resolve(row.stash_dir) !== path.resolve(opts.sourceRoot)) {
277
225
  throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
278
226
  }
279
227
  // Patch the JSON payload. On corrupt entry_json still re-key key + paths so
@@ -293,19 +241,11 @@ export function rekeyEntryInPlace(db, opts) {
293
241
  catch {
294
242
  /* corrupt entry_json — key/path-only re-key */
295
243
  }
296
- // Chunk-5 flip F1: keep `item_ref`/`concept_id` consistent with the rename so
297
- // a post-mv new-grammar lookup finds the moved row by its NEW conceptId. The
298
- // bundle prefix is carried over from the existing item_ref unchanged (mv never
299
- // crosses bundles); a NULL item_ref (write-back row) stays NULL and is healed
300
- // on the next full index, exactly like the legacy columns before the flip.
301
- let newItemRef = null;
302
- let newConceptId = null;
303
- if (row.item_ref) {
304
- const boundary = row.item_ref.indexOf("//");
305
- const bundle = boundary >= 0 ? row.item_ref.slice(0, boundary) : undefined;
306
- newConceptId = conceptIdFromTypeName(row.entry_type, opts.newName);
307
- newItemRef = bundle !== undefined ? `${bundle}//${newConceptId}` : newConceptId;
244
+ const expectedNewRef = conceptIdFromTypeName(row.entry_type, opts.newName);
245
+ if (opts.newRef !== expectedNewRef) {
246
+ throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: target ref does not match the entry type and name.`);
308
247
  }
248
+ const newItemRef = `${opts.sourceName}//${opts.newRef}`;
309
249
  db.transaction(() => {
310
250
  const stale = db.prepare("SELECT id FROM entries WHERE entry_key = ?").get(opts.newEntryKey);
311
251
  if (stale && stale.id !== row.id) {
@@ -317,13 +257,10 @@ export function rekeyEntryInPlace(db, opts) {
317
257
  deleteRelatedRows(db, [{ id: stale.id }]);
318
258
  db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
319
259
  }
320
- db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ? WHERE id = ?").run(opts.newEntryKey, path.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, row.id);
260
+ db.prepare("UPDATE entries SET entry_key = ?, dir_path = ?, file_path = ?, entry_json = ?, search_text = ?, item_ref = ?, concept_id = ? WHERE id = ?").run(opts.newEntryKey, path.dirname(opts.newFilePath), opts.newFilePath, entryJson, searchText, newItemRef, opts.newRef, row.id);
321
261
  if (opts.newDerivedFrom !== undefined) {
322
262
  db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
323
263
  }
324
- if (newItemRef !== null) {
325
- db.prepare("UPDATE entries SET item_ref = ?, concept_id = ? WHERE id = ?").run(newItemRef, newConceptId, row.id);
326
- }
327
264
  db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
328
265
  })();
329
266
  // Re-point usage history at the new ref. Chunk-8 WI-8.3: usage_events lives in
@@ -336,28 +273,15 @@ export function rekeyEntryInPlace(db, opts) {
336
273
  }
337
274
  /**
338
275
  * Rewrite `usage_events.entry_ref` from `opts.oldRef` to `opts.newRef` in
339
- * state.db (both the bare `type:name` and the origin-qualified `origin//type:name`
340
- * spellings). DETACHED orphan events (entry_id NULL) already sitting AT the new
276
+ * state.db. DETACHED orphan events (entry_id NULL) already sitting AT the new
341
277
  * ref are evicted first so the moved asset never adopts a deleted stranger's
342
- * history (live-asset-wins). Best-effort + guarded on state.db's existence; a
343
- * legacy state.db predating usage_events is tolerated.
278
+ * history (live-asset-wins). Best-effort + guarded on state.db's existence.
344
279
  */
345
- /** The conceptId for a legacy `type:name` ref (`memory:foo` → `memories/foo`), or undefined. */
346
- function conceptIdFromTypeNameRef(ref) {
347
- const colon = ref.indexOf(":");
348
- if (colon <= 0)
349
- return undefined;
350
- return conceptIdFromTypeName(ref.slice(0, colon), ref.slice(colon + 1));
351
- }
352
280
  function rewriteUsageEventRefForMove(opts) {
353
281
  if (!fs.existsSync(getStateDbPath()))
354
282
  return;
355
- // Post-cutover, `usage_events.entry_ref` is the fully-qualified item_ref
356
- // (`<bundle>//<conceptId>`); the pre-flip legacy `type:name` spellings survive
357
- // only on rows written before the cutover. Rewrite BOTH: `opts.oldRef`/`newRef`
358
- // are the legacy `type:name` forms, so the conceptId siblings are derived here.
359
- const oldConcept = conceptIdFromTypeNameRef(opts.oldRef);
360
- const newConcept = conceptIdFromTypeNameRef(opts.newRef);
283
+ // `usage_events.entry_ref` is the fully-qualified item_ref
284
+ // (`<bundle>//<conceptId>`).
361
285
  const rename = (stateDb, oldR, newR) => {
362
286
  stateDb.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newR);
363
287
  stateDb.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newR, oldR);
@@ -365,33 +289,13 @@ function rewriteUsageEventRefForMove(opts) {
365
289
  try {
366
290
  withStateDb((stateDb) => {
367
291
  stateDb.transaction(() => {
368
- if (opts.sourceName) {
369
- const origins = new Set([opts.sourceName]);
370
- if (opts.sourceName === "stash" && opts.includeLegacyBare)
371
- origins.add("local");
372
- for (const origin of origins) {
373
- rename(stateDb, `${origin}//${opts.oldRef}`, `${origin}//${opts.newRef}`);
374
- // The item_ref spelling (`<bundle>//<conceptId>`) — the durable
375
- // post-cutover key. Origins double as the bundle id for the stash.
376
- if (oldConcept !== undefined && newConcept !== undefined)
377
- rename(stateDb, `${origin}//${oldConcept}`, `${origin}//${newConcept}`);
378
- }
379
- }
380
- if (opts.includeLegacyBare || !opts.sourceName) {
381
- rename(stateDb, opts.oldRef, opts.newRef);
382
- if (oldConcept !== undefined && newConcept !== undefined)
383
- rename(stateDb, oldConcept, newConcept);
384
- }
292
+ rename(stateDb, `${opts.sourceName}//${opts.oldRef}`, `${opts.sourceName}//${opts.newRef}`);
385
293
  })();
386
294
  });
387
295
  }
388
296
  catch (error) {
389
297
  const message = error instanceof Error ? error.message : String(error);
390
- const missingLegacyUsageSchema = /no such table:\s*(?:main\.)?usage_events\b/i.test(message) ||
391
- /no such column:\s*(?:usage_events\.)?(?:entry_id|entry_ref)\b/i.test(message) ||
392
- /table\s+usage_events\s+has no column named\s+(?:entry_id|entry_ref)\b/i.test(message);
393
- if (!missingLegacyUsageSchema)
394
- throw error;
298
+ throw new Error(`Failed to rewrite usage events for move: ${message}`, { cause: error });
395
299
  }
396
300
  }
397
301
  /**
@@ -435,7 +339,7 @@ export function getPositiveFeedbackCountsByIds(ids) {
435
339
  }
436
340
  }
437
341
  });
438
- }, "usage_events table may be missing on legacy state.db — treat as zero counts");
342
+ }, "positive feedback counts are best-effort");
439
343
  return result;
440
344
  }
441
345
  function deleteEntriesWhere(db, column, value) {
@@ -448,6 +352,16 @@ function deleteEntriesWhere(db, column, value) {
448
352
  export function deleteEntriesByDir(db, dirPath) {
449
353
  deleteEntriesWhere(db, "dir_path", dirPath);
450
354
  }
355
+ export function deleteEntriesByDirAndStash(db, dirPath, stashDir, options = {}) {
356
+ return db.transaction(() => {
357
+ const ids = db
358
+ .prepare("SELECT id FROM entries WHERE dir_path = ? AND stash_dir = ?")
359
+ .all(dirPath, stashDir);
360
+ deleteRelatedRows(db, ids, options);
361
+ db.prepare("DELETE FROM entries WHERE dir_path = ? AND stash_dir = ?").run(dirPath, stashDir);
362
+ return ids.map((row) => row.id);
363
+ })();
364
+ }
451
365
  /**
452
366
  * Delete every entry (and its child rows) under a source's stash root.
453
367
  *
@@ -471,28 +385,28 @@ export function deleteEntriesByStashDir(db, stashDir) {
471
385
  * to prune only the DEPARTED rows. The net row-state of `dir_path` is identical
472
386
  * to delete-then-reinsert; the win is that unchanged rows keep their id.
473
387
  *
474
- * Keyed on `entry_key` rather than `item_ref`: the diff must be exact regardless
475
- * of whether a row's `item_ref` is populated (legacy rows and NULL-provenance
476
- * write-back rows exist), and `entry_key` is never NULL and mirrors the upsert
477
- * conflict target precisely. Dir-scoped (not stash-dir-scoped) so it is safe
478
- * under the per-dir incremental freshness gate — untouched (skipped) sibling
479
- * directories are never in scope.
388
+ * Keyed on `entry_key` because it mirrors the directory upsert key precisely.
389
+ * Both directory- and source-scoped so overlapping source roots cannot prune
390
+ * one another's rows.
480
391
  */
481
- export function deleteEntriesByDirExceptKeys(db, dirPath, keepKeys) {
482
- db.transaction(() => {
483
- const rows = db.prepare("SELECT id, entry_key FROM entries WHERE dir_path = ?").all(dirPath);
392
+ export function deleteEntriesByDirExceptKeys(db, dirPath, stashDir, keepKeys, options = {}) {
393
+ return db.transaction(() => {
394
+ const rows = db
395
+ .prepare("SELECT id, entry_key FROM entries WHERE dir_path = ? AND stash_dir = ?")
396
+ .all(dirPath, stashDir);
484
397
  const doomed = rows.filter((r) => !keepKeys.has(r.entry_key));
485
398
  if (doomed.length === 0)
486
- return;
487
- deleteRelatedRows(db, doomed);
399
+ return [];
400
+ deleteRelatedRows(db, doomed, options);
488
401
  for (let i = 0; i < doomed.length; i += SQLITE_CHUNK_SIZE) {
489
402
  const chunk = doomed.slice(i, i + SQLITE_CHUNK_SIZE);
490
403
  const placeholders = chunk.map(() => "?").join(",");
491
404
  db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk.map((r) => r.id));
492
405
  }
406
+ return doomed.map((row) => row.id);
493
407
  })();
494
408
  }
495
- function deleteRelatedRows(db, ids) {
409
+ function deleteRelatedRows(db, ids, options = {}) {
496
410
  if (ids.length === 0)
497
411
  return;
498
412
  const numericIds = ids.map((r) => r.id);
@@ -518,22 +432,11 @@ function deleteRelatedRows(db, ids) {
518
432
  bestEffort(() => db.prepare(`DELETE FROM utility_scores WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores for entries");
519
433
  bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
520
434
  }
521
- // Clean up usage events for the deleted entries. Chunk-8 WI-8.3: usage_events
522
- // lives in state.db now, so this is a SEPARATE cross-DB delete (guarded on
523
- // state.db's existence; only runs when there ARE deletions, so the extra open
524
- // is bounded). Live-asset-wins collision eviction (the moved asset must not
525
- // adopt a deleted stranger's id-linked history) depends on this delete.
526
- if (fs.existsSync(getStateDbPath())) {
527
- bestEffort(() => {
528
- withStateDb((stateDb) => {
529
- for (let i = 0; i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
530
- const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
531
- const placeholders = chunk.map(() => "?").join(",");
532
- stateDb.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk);
533
- }
534
- });
535
- }, "delete usage_events (state.db) for entries");
536
- }
435
+ // usage_events lives in state.db, outside this transaction. Index persistence
436
+ // disables this cleanup and runs it only after its index.db transaction
437
+ // commits; standalone delete callers retain the immediate behavior.
438
+ if (options.cleanupUsageEvents !== false)
439
+ deleteUsageEventsByEntryIds(numericIds);
537
440
  // #624-P1: graph_files is NO LONGER keyed on entries.id, so deleting an
538
441
  // entries row must NOT wipe the extracted graph (that is the whole point —
539
442
  // the graph survives a reindex when body_hash is unchanged). We therefore do
@@ -567,6 +470,19 @@ function deleteRelatedRows(db, ids) {
567
470
  .run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
568
471
  }
569
472
  }
473
+ export function deleteUsageEventsByEntryIds(entryIds) {
474
+ if (entryIds.length === 0 || !fs.existsSync(getStateDbPath()))
475
+ return;
476
+ bestEffort(() => {
477
+ withStateDb((stateDb) => {
478
+ for (let i = 0; i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
479
+ const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
480
+ const placeholders = chunk.map(() => "?").join(",");
481
+ stateDb.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk);
482
+ }
483
+ });
484
+ }, "delete usage_events (state.db) for entries");
485
+ }
570
486
  /**
571
487
  * Delete entries by their primary key IDs, along with all related rows
572
488
  * (embeddings, entries_vec, entries_fts, utility_scores, usage_events).
@@ -621,7 +537,7 @@ export function getAllEntries(db, entryType, excludeTypes) {
621
537
  /**
622
538
  * Resolve a single `entries.id` from a new-grammar `[bundle//]conceptId` ref,
623
539
  * keying on the canonical stored `item_ref` (ref-grammar decision D-R1/D-R4).
624
- * All refs are the new grammar post-Chunk-8. The optional `stashDir` scopes the
540
+ * The optional `stashDir` scopes the
625
541
  * match to one source root.
626
542
  */
627
543
  export function findEntryIdByRef(db, ref, stashDir) {
@@ -632,9 +548,8 @@ function withMdVariants(name) {
632
548
  return name.endsWith(".md") ? [name, name.slice(0, -3)] : [name, `${name}.md`];
633
549
  }
634
550
  /**
635
- * New-grammar (`[bundle//]conceptId`) id lookup: match `item_ref` first (exact
636
- * when bundle-qualified, `//conceptId`-suffix when short), then fall back to the
637
- * legacy `entry_key` predicate for NULL-`item_ref` rows.
551
+ * Current (`[bundle//]conceptId`) id lookup: match `item_ref` exactly when
552
+ * bundle-qualified or by `//conceptId` suffix when short.
638
553
  */
639
554
  function findEntryIdByBundleRef(db, ref, stashDir) {
640
555
  const parsed = parseBundleRef(ref);
@@ -669,9 +584,7 @@ function findEntryIdByBundleRef(db, ref, stashDir) {
669
584
  * precedence-ordered scan would pick — while staying a pure config-free leaf
670
585
  * (true installation-priority resolution against an injected bundle list is
671
586
  * `resolveRef`'s job; this DB helper takes no config handle). The exact-bundle
672
- * arm is single-row under the UNIQUE `item_ref` index; `ORDER BY id ASC` there
673
- * keeps it deterministic on a partially-migrated DB whose fallback index is
674
- * non-unique.
587
+ * arm is single-row under the UNIQUE `item_ref` index.
675
588
  */
676
589
  function matchIdByItemRef(db, bundle, conceptId, stashDir) {
677
590
  const scope = stashDir ? "AND stash_dir = ?" : "";
@@ -697,11 +610,29 @@ export function getEntryCount(db) {
697
610
  const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries").get();
698
611
  return row.cnt;
699
612
  }
613
+ /**
614
+ * Per-asset-type entry counts (keyed by `entry_type`, e.g. "skill",
615
+ * "knowledge", "memory"). Used by `akm info` to break down the aggregate
616
+ * `indexStats.entryCount` (R-057). Rows with a null/empty `entry_type` are
617
+ * omitted rather than surfaced under a synthetic key.
618
+ */
619
+ export function getEntryCountByType(db) {
620
+ const rows = db
621
+ .prepare("SELECT entry_type AS type, COUNT(*) AS cnt FROM entries WHERE entry_type IS NOT NULL GROUP BY entry_type")
622
+ .all();
623
+ const out = {};
624
+ for (const row of rows) {
625
+ out[row.type] = row.cnt;
626
+ }
627
+ return out;
628
+ }
700
629
  export function getEmbeddableEntryCount(db) {
701
630
  return getEntryCount(db);
702
631
  }
703
632
  export function getEntryById(db, id) {
704
- const row = db.prepare("SELECT file_path, stash_dir, entry_json, item_ref FROM entries WHERE id = ?").get(id);
633
+ const row = db
634
+ .prepare("SELECT file_path, stash_dir, entry_json, item_ref, bundle_id, concept_id FROM entries WHERE id = ?")
635
+ .get(id);
705
636
  if (!row)
706
637
  return undefined;
707
638
  // Guard against corrupt JSON
@@ -713,12 +644,29 @@ export function getEntryById(db, id) {
713
644
  warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
714
645
  return undefined;
715
646
  }
716
- return { filePath: row.file_path, stashDir: row.stash_dir, entry, itemRef: row.item_ref };
647
+ return {
648
+ filePath: row.file_path,
649
+ stashDir: row.stash_dir,
650
+ entry,
651
+ itemRef: row.item_ref,
652
+ bundleId: row.bundle_id,
653
+ conceptId: row.concept_id,
654
+ };
717
655
  }
718
656
  export function getEntriesByDir(db, dirPath) {
719
657
  const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
720
658
  return parseEntryRows(rows, "getEntriesByDir");
721
659
  }
660
+ /** Return every directory previously indexed for one physical source root. */
661
+ export function getIndexedDirPathsByStashDir(db, stashDir) {
662
+ const rows = db.prepare("SELECT DISTINCT dir_path FROM entries WHERE stash_dir = ?").all(stashDir);
663
+ return rows.map((row) => row.dir_path);
664
+ }
665
+ /** Return every persisted source owner for one physical directory. */
666
+ export function getIndexedStashDirsByDir(db, dirPath) {
667
+ const rows = db.prepare("SELECT DISTINCT stash_dir FROM entries WHERE dir_path = ?").all(dirPath);
668
+ return rows.map((row) => row.stash_dir);
669
+ }
722
670
  /**
723
671
  * Resolve a single `entries.id` by exact `file_path` (the canonical on-disk
724
672
  * path), or `undefined` if no row matches.
@@ -848,14 +796,15 @@ export function getItemRefById(db, id) {
848
796
  return row?.item_ref ?? null;
849
797
  }
850
798
  /**
851
- * Resolve a `usage_events.entry_ref` to its live `entries.id`. Post-Chunk-8 every
852
- * `entry_ref` is the fully-qualified `bundle//conceptId` `item_ref` spelling
853
- * (the one-time state.db cutover re-keyed the historical legacy rows), so this
854
- * keys directly on the globally-unique `item_ref` — no origin→root scoping. A
855
- * short conceptId (no bundle) falls back to the default-stash scope.
799
+ * Resolve a `usage_events.entry_ref` to its live `entries.id`. `entry_ref` is
800
+ * the fully-qualified `bundle//conceptId` `item_ref` spelling, so this keys
801
+ * directly on the globally-unique `item_ref`. Bare durable refs are invalid and
802
+ * remain detached.
856
803
  */
857
- function resolveUsageEventEntryId(db, ref, options) {
858
- return findEntryIdByRef(db, ref, parseBundleRef(ref).bundle ? undefined : options.defaultStashDir);
804
+ function resolveUsageEventEntryId(db, ref) {
805
+ if (parseBundleRef(ref).bundle === undefined)
806
+ return undefined;
807
+ return findEntryIdByRef(db, ref);
859
808
  }
860
809
  /**
861
810
  * Re-link detached usage_events to their current entry_ids via entry_ref.
@@ -869,7 +818,7 @@ function resolveUsageEventEntryId(db, ref, options) {
869
818
  * distinct linked entry_ids in usage_events is small — and the re-resolution
870
819
  * reads `entries` from `indexDb`.
871
820
  */
872
- export function relinkUsageEvents(indexDb, stateDb, options = {}) {
821
+ export function relinkUsageEvents(indexDb, stateDb, _options = {}) {
873
822
  bestEffort(() => {
874
823
  // Step 1: null out stale entry_ids (entry was deleted, re-keyed, etc).
875
824
  // Leaving them in place would let `recomputeUtilityScores` aggregate by an
@@ -888,10 +837,8 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
888
837
  });
889
838
  nullTx();
890
839
  }
891
- // Step 2: re-resolve each distinct ref inside its source boundary. Qualified
892
- // refs require an origin→root mapping; bare legacy refs require the explicit
893
- // historical/default root. This keeps duplicate refs from adopting whichever
894
- // entries row SQLite happens to return first while retaining indexed lookups.
840
+ // Step 2: re-resolve each fully-qualified ref. Bare rows are not current
841
+ // durable identities and remain detached.
895
842
  const refs = stateDb
896
843
  .prepare("SELECT DISTINCT entry_ref AS ref FROM usage_events WHERE entry_id IS NULL AND entry_ref IS NOT NULL")
897
844
  .all();
@@ -900,7 +847,7 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
900
847
  for (const { ref } of refs) {
901
848
  let id;
902
849
  try {
903
- id = resolveUsageEventEntryId(indexDb, ref, options);
850
+ id = resolveUsageEventEntryId(indexDb, ref);
904
851
  }
905
852
  catch (err) {
906
853
  if (err instanceof Error && err.name === "UsageError")
@@ -914,39 +861,3 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
914
861
  relinkTx();
915
862
  }, "usage_events table may not exist yet during entry_id re-resolution");
916
863
  }
917
- /**
918
- * Walk indexed entries and collect a deduplicated set of tags. When
919
- * `entryType` is provided, only entries of that type contribute tags.
920
- *
921
- * Pure read; never mutates the DB. Used by `akm lessons coverage` (Phase 7A)
922
- * to compute the diff between all-asset tags and lesson tags. Tags are
923
- * normalised by trimming and lower-casing, and blank tags are dropped.
924
- *
925
- * SQL owner: this module owns ALL raw SQL against the `entries` table (WS5),
926
- * so the `lessons coverage` read lives here rather than leaking into cli.ts.
927
- * The result set is fully materialised (`.all()` then iterate) before return.
928
- */
929
- export function collectTagSetFromEntries(db, entryType) {
930
- const tags = new Set();
931
- const stmt = entryType
932
- ? db.prepare("SELECT entry_json FROM entries WHERE entry_type = ?")
933
- : db.prepare("SELECT entry_json FROM entries");
934
- const rows = (entryType ? stmt.all(entryType) : stmt.all());
935
- for (const row of rows) {
936
- let parsed;
937
- try {
938
- parsed = JSON.parse(row.entry_json);
939
- }
940
- catch {
941
- continue;
942
- }
943
- if (!Array.isArray(parsed.tags))
944
- continue;
945
- for (const tag of parsed.tags) {
946
- if (typeof tag === "string" && tag.trim().length > 0) {
947
- tags.add(tag.trim().toLowerCase());
948
- }
949
- }
950
- }
951
- return tags;
952
- }