akm-cli 0.9.0-rc.9 → 0.9.1-beta.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 (477) hide show
  1. package/CHANGELOG.md +1770 -44
  2. package/README.md +38 -47
  3. package/SECURITY.md +14 -1
  4. package/STABILITY.md +499 -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 +74 -89
  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 +148 -88
  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/llm-usage.js +2 -1
  57. package/dist/commands/health/md-report.js +5 -9
  58. package/dist/commands/health/metrics.js +62 -20
  59. package/dist/commands/health/renderers.js +47 -0
  60. package/dist/commands/health/report-view-model.js +4 -5
  61. package/dist/commands/health/stash-exposure.js +1 -1
  62. package/dist/commands/health/surfaces.js +4 -122
  63. package/dist/commands/health/task-runs.js +3 -67
  64. package/dist/commands/health/types-improve.js +7 -0
  65. package/dist/commands/health.js +160 -35
  66. package/dist/commands/improve/anti-collapse.js +2 -2
  67. package/dist/commands/improve/autonomy-gate.js +68 -0
  68. package/dist/commands/improve/collapse-detector.js +41 -40
  69. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  70. package/dist/commands/improve/consolidate/merge.js +4 -0
  71. package/dist/commands/improve/consolidate.js +140 -1000
  72. package/dist/commands/improve/distill/promote-memory.js +12 -12
  73. package/dist/commands/improve/distill/quality-gate.js +11 -6
  74. package/dist/commands/improve/distill.js +58 -69
  75. package/dist/commands/improve/eligibility.js +101 -55
  76. package/dist/commands/improve/extract-cli.js +14 -133
  77. package/dist/commands/improve/improve-cli.js +98 -114
  78. package/dist/commands/improve/improve-result-file.js +1 -28
  79. package/dist/commands/improve/improve-strategies.js +8 -5
  80. package/dist/commands/improve/improve.js +254 -101
  81. package/dist/commands/improve/locks.js +7 -0
  82. package/dist/commands/improve/loop-stages.js +182 -20
  83. package/dist/commands/improve/memory/derived-ref.js +45 -43
  84. package/dist/commands/improve/memory/memory-belief.js +1 -1
  85. package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
  86. package/dist/commands/improve/memory/memory-improve.js +14 -5
  87. package/dist/commands/improve/outcome-loop.js +22 -65
  88. package/dist/commands/improve/preparation.js +114 -123
  89. package/dist/commands/improve/proactive-maintenance.js +2 -5
  90. package/dist/commands/improve/reflect.js +56 -160
  91. package/dist/commands/improve/run-context.js +5 -0
  92. package/dist/commands/improve/salience.js +11 -122
  93. package/dist/commands/improve/session-asset.js +4 -0
  94. package/dist/commands/improve/source-identity.js +10 -38
  95. package/dist/commands/lint/base-linter.js +51 -131
  96. package/dist/commands/lint/env-key-rules.js +31 -47
  97. package/dist/commands/lint/index.js +444 -88
  98. package/dist/commands/lint/types.js +22 -1
  99. package/dist/commands/{events.js → log.js} +33 -38
  100. package/dist/commands/migrate-cli.js +92 -12
  101. package/dist/commands/migration-tool.js +46 -0
  102. package/dist/commands/observability-cli.js +70 -209
  103. package/dist/commands/proposal/drain.js +101 -29
  104. package/dist/commands/proposal/proposal-cli.js +76 -48
  105. package/dist/commands/proposal/proposal.js +54 -18
  106. package/dist/commands/proposal/propose-cli.js +88 -0
  107. package/dist/commands/proposal/propose.js +23 -15
  108. package/dist/commands/proposal/repository.js +718 -279
  109. package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
  110. package/dist/commands/proposal/validators/proposal-validators.js +55 -7
  111. package/dist/commands/proposal/validators/proposals.js +4 -7
  112. package/dist/commands/read/curate.js +34 -53
  113. package/dist/commands/read/knowledge.js +150 -95
  114. package/dist/commands/read/registry-search.js +2 -2
  115. package/dist/commands/read/remember-cli.js +42 -15
  116. package/dist/commands/read/search-cli.js +180 -78
  117. package/dist/commands/read/search.js +58 -43
  118. package/dist/commands/read/show.js +197 -141
  119. package/dist/commands/registry-cli.js +12 -51
  120. package/dist/commands/remember.js +14 -57
  121. package/dist/commands/sources/add-cli.js +100 -31
  122. package/dist/commands/sources/bundle-cli.js +166 -0
  123. package/dist/commands/sources/bundle-config-ops.js +7 -2
  124. package/dist/commands/sources/info.js +30 -7
  125. package/dist/commands/sources/init.js +12 -12
  126. package/dist/commands/sources/installed-stashes.js +387 -98
  127. package/dist/commands/sources/schema-repair.js +3 -2
  128. package/dist/commands/sources/self-update.js +131 -38
  129. package/dist/commands/sources/source-add.js +72 -17
  130. package/dist/commands/sources/source-clone.js +129 -45
  131. package/dist/commands/sources/source-manage.js +43 -23
  132. package/dist/commands/sources/sources-cli.js +57 -208
  133. package/dist/commands/sources/stash-cli.js +46 -53
  134. package/dist/commands/tasks/tasks-cli.js +91 -97
  135. package/dist/commands/tasks/tasks.js +284 -423
  136. package/dist/commands/workflow-cli.js +181 -450
  137. package/dist/core/abort-deadline.js +28 -0
  138. package/dist/core/adapter/adapters/agent-skills-adapter.js +67 -5
  139. package/dist/core/adapter/adapters/akm-adapter.js +60 -38
  140. package/dist/core/adapter/adapters/akm-lint.js +109 -42
  141. package/dist/core/adapter/adapters/akm-metadata.js +15 -44
  142. package/dist/core/adapter/adapters/akm-task-adapter.js +54 -31
  143. package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
  144. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  145. package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
  146. package/dist/core/adapter/adapters/index.js +6 -6
  147. package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
  148. package/dist/core/adapter/adapters/okf-adapter.js +187 -19
  149. package/dist/core/adapter/adapters/shared.js +3 -19
  150. package/dist/core/adapter/adapters/tool-dir-shared.js +13 -6
  151. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
  152. package/dist/core/adapter/detect-adapter.js +17 -0
  153. package/dist/core/adapter/recognize-match.js +6 -4
  154. package/dist/core/adapter/validate-context.js +214 -0
  155. package/dist/core/asset/akm-markdown.js +63 -0
  156. package/dist/core/asset/asset-placement.js +20 -6
  157. package/dist/core/asset/asset-ref.js +11 -9
  158. package/dist/core/asset/frontmatter-lint.js +30 -0
  159. package/dist/core/asset/frontmatter.js +29 -9
  160. package/dist/core/asset/markdown.js +40 -51
  161. package/dist/core/asset/resolve-ref.js +89 -18
  162. package/dist/core/asset/stash-meta.js +1 -1
  163. package/dist/core/bundle-id.js +51 -0
  164. package/dist/core/common.js +218 -44
  165. package/dist/core/concurrent.js +32 -0
  166. package/dist/core/config/config-io.js +12 -1
  167. package/dist/core/config/config-schema.js +35 -8
  168. package/dist/core/config/config-sources.js +55 -11
  169. package/dist/core/config/config-walker.js +25 -9
  170. package/dist/core/config/config.js +9 -48
  171. package/dist/core/config/experimental.js +21 -0
  172. package/dist/core/config/schema/embedding.js +5 -1
  173. package/dist/core/config/schema/experimental.js +30 -0
  174. package/dist/core/config/schema/improve-processes.js +0 -6
  175. package/dist/core/config/schema/improve.js +21 -3
  176. package/dist/core/config/schema/index-config.js +8 -15
  177. package/dist/core/config/schema/output.js +4 -1
  178. package/dist/core/config/schema/setup.js +9 -18
  179. package/dist/core/config/schema/sources-bundles.js +49 -33
  180. package/dist/core/config/schema/workflow.js +14 -3
  181. package/dist/core/env-secret-ref.js +76 -46
  182. package/dist/core/errors.js +43 -12
  183. package/dist/core/events.js +76 -152
  184. package/dist/core/file-change.js +6 -5
  185. package/dist/core/file-lock.js +7 -1
  186. package/dist/core/fs-txn.js +83 -7
  187. package/dist/core/git-message.js +2 -2
  188. package/dist/core/improve-result.js +6 -100
  189. package/dist/core/json-schema.js +327 -9
  190. package/dist/core/lesson-lint.js +1 -17
  191. package/dist/core/logs-db.js +2 -1
  192. package/dist/core/loopback.js +89 -0
  193. package/dist/core/migration-operation.js +33 -2
  194. package/dist/core/mutation-target.js +78 -0
  195. package/dist/core/parse.js +4 -1
  196. package/dist/core/path-access.js +107 -0
  197. package/dist/core/paths.js +17 -20
  198. package/dist/core/recognition-util.js +12 -14
  199. package/dist/core/redaction.js +98 -17
  200. package/dist/core/spawn-env.js +234 -0
  201. package/dist/core/standards/resolve-standards-context.js +2 -14
  202. package/dist/core/standards/resolve-stash-standards.js +2 -2
  203. package/dist/core/standards/resolve-type-conventions.js +2 -2
  204. package/dist/core/state/migrations.js +41 -18
  205. package/dist/core/state-db-scope.js +134 -0
  206. package/dist/core/state-db.js +5 -14
  207. package/dist/core/structured.js +1 -1
  208. package/dist/core/subprocess.js +182 -36
  209. package/dist/core/text-truncation.js +9 -5
  210. package/dist/core/type-presentation.js +3 -3
  211. package/dist/core/warn.js +0 -3
  212. package/dist/core/write-provenance.js +85 -0
  213. package/dist/core/write-source.js +778 -95
  214. package/dist/indexer/bundle-identity-guard.js +3 -2
  215. package/dist/indexer/db/graph-db.js +17 -30
  216. package/dist/indexer/ensure-index.js +11 -3
  217. package/dist/indexer/graph/graph-boost.js +9 -34
  218. package/dist/indexer/graph/graph-extraction.js +8 -5
  219. package/dist/indexer/index-writer-lock.js +53 -17
  220. package/dist/indexer/index-written-assets.js +33 -24
  221. package/dist/indexer/indexer.js +551 -253
  222. package/dist/indexer/installations.js +14 -96
  223. package/dist/indexer/passes/dir-staleness.js +16 -9
  224. package/dist/indexer/passes/memory-inference.js +15 -9
  225. package/dist/indexer/passes/metadata.js +113 -47
  226. package/dist/indexer/scan/doc-to-entry.js +38 -1
  227. package/dist/indexer/scan/drain-dir.js +13 -23
  228. package/dist/indexer/search/db-search.js +107 -69
  229. package/dist/indexer/search/fts-query.js +47 -24
  230. package/dist/indexer/search/ranking-contributors.js +42 -20
  231. package/dist/indexer/search/ranking.js +18 -99
  232. package/dist/indexer/search/search-fields.js +7 -2
  233. package/dist/indexer/search/search-source.js +82 -93
  234. package/dist/indexer/usage/usage-events.js +0 -89
  235. package/dist/indexer/walk/file-context.js +2 -1
  236. package/dist/indexer/walk/matchers.js +30 -43
  237. package/dist/indexer/walk/path-resolver.js +7 -2
  238. package/dist/indexer/walk/walker.js +38 -12
  239. package/dist/integrations/agent/builders.js +0 -6
  240. package/dist/integrations/agent/config.js +2 -2
  241. package/dist/integrations/agent/detect.js +49 -19
  242. package/dist/integrations/agent/engine-fallback.js +76 -0
  243. package/dist/integrations/agent/engine-resolution.js +24 -11
  244. package/dist/integrations/agent/model-aliases.js +1 -1
  245. package/dist/integrations/agent/profiles.js +23 -1
  246. package/dist/integrations/agent/prompts.js +12 -8
  247. package/dist/integrations/agent/runner-dispatch.js +4 -2
  248. package/dist/integrations/agent/runner.js +0 -1
  249. package/dist/integrations/agent/spawn.js +20 -93
  250. package/dist/integrations/github.js +1 -1
  251. package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
  252. package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
  253. package/dist/integrations/harnesses/claude/session-log.js +0 -10
  254. package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
  255. package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
  256. package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
  257. package/dist/integrations/harnesses/index.js +3 -7
  258. package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
  259. package/dist/integrations/harnesses/opencode/session-log.js +0 -15
  260. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
  261. package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
  262. package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
  263. package/dist/integrations/lockfile.js +132 -8
  264. package/dist/integrations/session-logs/index.js +3 -28
  265. package/dist/llm/client.js +136 -100
  266. package/dist/llm/embedders/remote.js +13 -5
  267. package/dist/llm/feature-gate.js +4 -12
  268. package/dist/llm/graph-extract.js +5 -11
  269. package/dist/llm/memory-infer.js +144 -1
  270. package/dist/llm/metadata-enhance.js +5 -7
  271. package/dist/llm/structured-call.js +1 -1
  272. package/dist/llm/usage-persist.js +26 -5
  273. package/dist/llm/usage-telemetry.js +25 -2
  274. package/dist/output/cli-hints.js +1 -2
  275. package/dist/output/context.js +22 -7
  276. package/dist/output/format-exempt.js +80 -0
  277. package/dist/output/generic-render.js +259 -0
  278. package/dist/output/render-registry.js +57 -0
  279. package/dist/output/renderers.js +14 -36
  280. package/dist/output/shapes/curate.js +10 -1
  281. package/dist/output/shapes/events.js +12 -7
  282. package/dist/output/shapes/helpers.js +56 -83
  283. package/dist/output/shapes/migrate.js +8 -0
  284. package/dist/output/shapes/passthrough.js +7 -41
  285. package/dist/output/shapes/proposal/producer.js +15 -7
  286. package/dist/output/shapes.js +2 -9
  287. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  288. package/dist/output/text/bundle-show.js +7 -0
  289. package/dist/output/text/command-format.js +164 -96
  290. package/dist/output/text/env.js +1 -3
  291. package/dist/output/text/events.js +8 -7
  292. package/dist/output/text/health-format.js +103 -0
  293. package/dist/output/text/health.js +7 -0
  294. package/dist/output/text/helpers.js +10 -8
  295. package/dist/output/text/lint-format.js +56 -0
  296. package/dist/{migrate-storage-node.mjs → output/text/lint.js} +2 -5
  297. package/dist/output/text/migrate.js +88 -0
  298. package/dist/output/text/proposal/producer.js +4 -2
  299. package/dist/output/text/proposal-format.js +44 -72
  300. package/dist/output/text/registry-commands.js +1 -2
  301. package/dist/output/text/show-directives.js +15 -7
  302. package/dist/output/text/status-list.js +32 -0
  303. package/dist/output/text/{save.js → sync.js} +2 -2
  304. package/dist/output/text/workflow-format.js +24 -203
  305. package/dist/output/text/workflow.js +1 -7
  306. package/dist/output/text.js +16 -17
  307. package/dist/registry/factory.js +4 -6
  308. package/dist/registry/origin-resolve.js +16 -27
  309. package/dist/registry/providers/skills-sh.js +3 -3
  310. package/dist/registry/providers/static-index.js +13 -23
  311. package/dist/registry/resolve.js +42 -7
  312. package/dist/registry/semver.js +34 -84
  313. package/dist/runtime.js +2 -23
  314. package/dist/scripts/akm-migrate-node.js +60972 -0
  315. package/dist/scripts/akm-migrate.js +60310 -0
  316. package/dist/setup/detect.js +42 -15
  317. package/dist/setup/registry-stash-loader.js +2 -2
  318. package/dist/setup/setup.js +236 -136
  319. package/dist/setup/steps/connection.js +7 -9
  320. package/dist/setup/steps/platforms.js +9 -9
  321. package/dist/setup/steps/semantic.js +15 -3
  322. package/dist/setup/steps/sources.js +12 -13
  323. package/dist/setup/steps/stashdir.js +2 -3
  324. package/dist/setup/steps/tasks.js +237 -120
  325. package/dist/sources/freshness.js +1 -1
  326. package/dist/sources/provider-factory.js +11 -17
  327. package/dist/sources/providers/filesystem.js +2 -3
  328. package/dist/sources/providers/git-install.js +278 -34
  329. package/dist/sources/providers/git-provider.js +25 -23
  330. package/dist/sources/providers/git-stash.js +414 -106
  331. package/dist/sources/providers/git.js +2 -2
  332. package/dist/sources/providers/npm.js +16 -19
  333. package/dist/sources/providers/provider-utils.js +7 -4
  334. package/dist/sources/providers/sync-from-ref.js +3 -9
  335. package/dist/sources/providers/website.js +6 -1
  336. package/dist/sources/resolve.js +6 -5
  337. package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
  338. package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
  339. package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
  340. package/dist/sources/snapshot-fetchers/github.js +100 -0
  341. package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
  342. package/dist/sources/snapshot-fetchers/registry.js +17 -1
  343. package/dist/sources/snapshot-fetchers/robots.js +348 -0
  344. package/dist/sources/snapshot-fetchers/rss.js +282 -0
  345. package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
  346. package/dist/sources/snapshot-fetchers/website-ingest.js +686 -282
  347. package/dist/sources/snapshot-fetchers/x.js +910 -0
  348. package/dist/storage/database.js +13 -1
  349. package/dist/storage/engines/sqlite-migrations.js +23 -111
  350. package/dist/storage/managed-db.js +21 -2
  351. package/dist/storage/repositories/canaries-repository.js +1 -1
  352. package/dist/storage/repositories/events-repository.js +27 -11
  353. package/dist/storage/repositories/improve-runs-repository.js +6 -12
  354. package/dist/storage/repositories/index-connection.js +44 -8
  355. package/dist/storage/repositories/index-entries-repository.js +157 -241
  356. package/dist/storage/repositories/index-entry-mapper.js +15 -11
  357. package/dist/storage/repositories/index-fts-repository.js +5 -2
  358. package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
  359. package/dist/storage/repositories/index-meta-repository.js +2 -3
  360. package/dist/storage/repositories/index-schema.js +10 -25
  361. package/dist/storage/repositories/index-utility-repository.js +15 -28
  362. package/dist/storage/repositories/index-vec-repository.js +6 -1
  363. package/dist/storage/repositories/outcome-repository.js +119 -0
  364. package/dist/storage/repositories/proposals-repository.js +296 -59
  365. package/dist/storage/repositories/registry-cache.js +19 -0
  366. package/dist/storage/repositories/salience-repository.js +172 -0
  367. package/dist/storage/repositories/task-history-repository.js +15 -13
  368. package/dist/storage/repositories/workflow-runs-repository.js +118 -53
  369. package/dist/tasks/backends/cron.js +105 -15
  370. package/dist/tasks/backends/index.js +1 -1
  371. package/dist/tasks/backends/launchd.js +85 -38
  372. package/dist/tasks/backends/schtasks.js +135 -15
  373. package/dist/tasks/embedded.js +56 -40
  374. package/dist/tasks/log-redaction.js +156 -0
  375. package/dist/tasks/parser.js +87 -160
  376. package/dist/tasks/resolve-akm-bin.js +137 -59
  377. package/dist/tasks/runner.js +209 -44
  378. package/dist/tasks/scheduler-invocation.js +220 -10
  379. package/dist/tasks/schema.js +89 -1
  380. package/dist/tasks/task-id.js +1 -3
  381. package/dist/tasks/validator.js +20 -6
  382. package/dist/workflows/authoring/authoring.js +94 -143
  383. package/dist/workflows/authoring/scope-key.js +1 -1
  384. package/dist/workflows/concurrency-policy.js +95 -1
  385. package/dist/workflows/exec/dispatch-redaction.js +114 -0
  386. package/dist/workflows/exec/exec-unit.js +542 -0
  387. package/dist/workflows/exec/frozen-judge.js +130 -32
  388. package/dist/workflows/exec/native-executor.js +513 -274
  389. package/dist/workflows/exec/param-secrets.js +12 -11
  390. package/dist/workflows/exec/run-workflow.js +496 -237
  391. package/dist/workflows/exec/step-work.js +616 -412
  392. package/dist/workflows/exec/unit-dispatch.js +34 -0
  393. package/dist/workflows/exec/unit-writer.js +53 -13
  394. package/dist/workflows/exec/worktree.js +454 -41
  395. package/dist/workflows/ir/compile.js +166 -271
  396. package/dist/workflows/ir/freeze.js +119 -42
  397. package/dist/workflows/ir/params.js +135 -11
  398. package/dist/workflows/ir/plan-hash.js +1 -1
  399. package/dist/workflows/ir/schema.js +129 -45
  400. package/dist/workflows/parser.js +1070 -307
  401. package/dist/workflows/program/expressions.js +20 -208
  402. package/dist/workflows/program/schema.js +31 -10
  403. package/dist/workflows/renderer.js +123 -68
  404. package/dist/workflows/resource-limits.js +184 -0
  405. package/dist/workflows/runtime/checkin.js +3 -3
  406. package/dist/workflows/runtime/plan-classifier.js +16 -75
  407. package/dist/workflows/runtime/runs.js +331 -132
  408. package/dist/workflows/runtime/unit-checkin.js +1 -1
  409. package/dist/workflows/runtime/unit-phases.js +2 -2
  410. package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
  411. package/dist/workflows/schema.js +1 -11
  412. package/dist/workflows/validate-summary.js +47 -38
  413. package/dist/workflows/validator.js +21 -62
  414. package/docs/README.md +110 -0
  415. package/docs/migration/README.md +8 -0
  416. package/docs/migration/release-notes/0.7.0.md +11 -11
  417. package/docs/migration/release-notes/0.9.0.md +209 -27
  418. package/docs/migration/v0.7-to-v0.8.md +48 -48
  419. package/docs/migration/v0.8-to-v0.9.md +567 -209
  420. package/docs/migration/v0.9.0-troubleshooting.md +561 -0
  421. package/docs/reference/README.md +19 -0
  422. package/docs/reference/bundle-types.md +19 -0
  423. package/docs/reference/cli.md +2342 -0
  424. package/docs/reference/configuration.md +371 -0
  425. package/docs/reference/data-and-telemetry.md +129 -46
  426. package/docs/reference/supported-formats.md +50 -0
  427. package/docs/reference/workflow-schema.md +1014 -0
  428. package/docs/reference/workflows.md +51 -0
  429. package/package.json +31 -13
  430. package/schemas/akm-asset-envelope.json +93 -0
  431. package/schemas/akm-config.json +89 -128
  432. package/schemas/akm-task.json +27 -5
  433. package/schemas/akm-workflow.json +157 -81
  434. package/dist/assets/tasks/core/backup.yml +0 -5
  435. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  436. package/dist/cli/config-migrate.js +0 -1878
  437. package/dist/cli/config-validate.js +0 -41
  438. package/dist/commands/backup-cli.js +0 -56
  439. package/dist/commands/bundle/bundle-cli.js +0 -68
  440. package/dist/commands/bundle/bundle.js +0 -219
  441. package/dist/commands/graph/graph-cli.js +0 -124
  442. package/dist/commands/graph/graph.js +0 -489
  443. package/dist/commands/improve/extract-watch.js +0 -140
  444. package/dist/commands/mv-cli.js +0 -1221
  445. package/dist/commands/sources/history.js +0 -201
  446. package/dist/commands/tasks/default-tasks.js +0 -186
  447. package/dist/core/migration-backup.js +0 -1231
  448. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
  449. package/dist/llm/memory-infer-impl.js +0 -138
  450. package/dist/migrate/legacy/config-source-migration.js +0 -257
  451. package/dist/migrate/legacy/content-migration.js +0 -350
  452. package/dist/migrate/legacy/legacy-layout.js +0 -779
  453. package/dist/migrate/legacy/legacy-paths.js +0 -25
  454. package/dist/migrate/legacy/legacy-stash-json.js +0 -80
  455. package/dist/migrate/legacy/proposal-fs-import.js +0 -168
  456. package/dist/migrate/legacy/task-target-ref-migration.js +0 -278
  457. package/dist/migrate/legacy/three-db-cutover.js +0 -845
  458. package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
  459. package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
  460. package/dist/migrate/legacy-ref-grammar.js +0 -214
  461. package/dist/output/shapes/distill.js +0 -14
  462. package/dist/output/shapes/history.js +0 -11
  463. package/dist/output/text/distill.js +0 -6
  464. package/dist/output/text/enable-disable.js +0 -8
  465. package/dist/output/text/history.js +0 -6
  466. package/dist/registry/build-index.js +0 -382
  467. package/dist/schemas/akm-config.json +0 -4704
  468. package/dist/schemas/akm-task.json +0 -87
  469. package/dist/schemas/akm-workflow.json +0 -372
  470. package/dist/scripts/migrate-storage.js +0 -3816
  471. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  472. package/dist/workflows/cli.js +0 -53
  473. package/dist/workflows/exec/brief.js +0 -481
  474. package/dist/workflows/exec/report.js +0 -1460
  475. package/dist/workflows/exec/watch.js +0 -116
  476. package/dist/workflows/program/parser.js +0 -813
  477. package/dist/workflows/program/project.js +0 -104
@@ -5,21 +5,21 @@
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";
14
13
  import { parseBundleRef } from "../../core/asset/asset-ref.js";
15
14
  import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
16
15
  import { bestEffort } from "../../core/best-effort.js";
16
+ import { isPathAbsent } from "../../core/path-access.js";
17
17
  import { getStateDbPath, withStateDb } from "../../core/state-db.js";
18
18
  import { warn } from "../../core/warn.js";
19
19
  import { buildSearchText } from "../../indexer/search/search-fields.js";
20
20
  import { ENTRY_COLUMNS, rowToIndexedEntry } from "./index-entry-mapper.js";
21
21
  import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
22
- import { isVecAvailable } from "./index-vec-repository.js";
22
+ import { deleteEntryVectors, isVecAvailable } from "./index-vec-repository.js";
23
23
  // ── Entry operations ────────────────────────────────────────────────────────
24
24
  /**
25
25
  * Insert or update an entry in the `entries` table. Returns the row id.
@@ -29,11 +29,12 @@ import { isVecAvailable } from "./index-vec-repository.js";
29
29
  * reflect the changes.
30
30
  */
31
31
  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.
32
+ // Hot path during indexing — cache prepared statements per database
33
+ // connection so we don't pay the SQL parse/compile cost on every call.
36
34
  const stmts = getUpsertStmts(db);
35
+ const previous = (provenance?.itemRef
36
+ ? stmts.findByItemRef.get(provenance.itemRef)
37
+ : undefined) ?? stmts.findByEntryKey.get(entryKey);
37
38
  // Phase 5A / Advantage D5: surface derived memory parent ref into the
38
39
  // dedicated `derived_from` column so retrieval-time lookup (parent→child)
39
40
  // does not have to scan + JSON-decode every memory row.
@@ -45,22 +46,29 @@ export function upsertEntry(db, entryKey, dirPath, filePath, stashDir, entry, se
45
46
  // by the next full index). `content_hash` (F4a M-core-2) is `doc.hash` from
46
47
  // the diff-persist writer; a NULL passed here PRESERVES any existing hash (the
47
48
  // 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;
49
+ const apply = () => {
50
+ 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);
51
+ if (!result)
52
+ throw new Error("upsertEntry: entry_key not found after upsert");
53
+ if (previous?.id === result.id && previous.search_text !== searchText)
54
+ deleteEntryVectors(db, result.id);
55
+ // Mark this entry as FTS-dirty so `rebuildFts({ incremental: true })`
56
+ // only revisits entries that actually changed. INSERT OR IGNORE is
57
+ // idempotent across multiple upserts of the same row.
58
+ stmts.markDirty.run(result.id);
59
+ return result.id;
60
+ };
61
+ return previous && previous.search_text !== searchText ? db.transaction(apply)() : apply();
56
62
  }
57
63
  const upsertStmtsByDb = new WeakMap();
58
64
  // 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`).
65
+ // conflict targets below stay byte-identical. `item_ref` is durable identity;
66
+ // update `entry_key` when adapter ownership changes its
67
+ // spelling so directory pruning retains the upserted row.
61
68
  // `content_hash` COALESCEs so a NULL passed by the LLM-enhance re-upsert cannot
62
69
  // wipe a previously-persisted hash.
63
70
  const UPSERT_SET_CLAUSE = `SET
71
+ entry_key = excluded.entry_key,
64
72
  dir_path = excluded.dir_path,
65
73
  file_path = excluded.file_path,
66
74
  stash_dir = excluded.stash_dir,
@@ -75,53 +83,14 @@ const UPSERT_SET_CLAUSE = `SET
75
83
  adapter_id = excluded.adapter_id,
76
84
  type = excluded.type,
77
85
  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
86
  function getUpsertStmts(db) {
98
87
  const existing = upsertStmtsByDb.get(db);
99
88
  if (existing)
100
89
  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}`;
90
+ // Durable identity is the primary conflict target. `entry_key` remains the
91
+ // internal conflict key for low-level entries without provenance.
92
+ const conflictClause = `ON CONFLICT(item_ref) DO UPDATE ${UPSERT_SET_CLAUSE}
93
+ ON CONFLICT(entry_key) DO UPDATE ${UPSERT_SET_CLAUSE}`;
125
94
  const stmts = {
126
95
  // RETURNING id handles ON CONFLICT DO UPDATE correctly — no second
127
96
  // SELECT round-trip needed (last_insert_rowid() is unreliable for
@@ -136,6 +105,8 @@ function getUpsertStmts(db) {
136
105
  RETURNING id
137
106
  `),
138
107
  markDirty: db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)"),
108
+ findByItemRef: db.prepare("SELECT id, search_text FROM entries WHERE item_ref = ? ORDER BY id ASC LIMIT 1"),
109
+ findByEntryKey: db.prepare("SELECT id, search_text FROM entries WHERE entry_key = ? LIMIT 1"),
139
110
  };
140
111
  upsertStmtsByDb.set(db, stmts);
141
112
  return stmts;
@@ -153,25 +124,18 @@ function getUpsertStmts(db) {
153
124
  export function getDerivedForParent(db, parentRef, stashDir) {
154
125
  if (!parentRef)
155
126
  return null;
156
- try {
157
- const sourceScope = stashDir ? "AND stash_dir = ?" : "";
158
- const row = db
159
- .prepare(`SELECT ${ENTRY_COLUMNS}
127
+ const sourceScope = stashDir ? "AND stash_dir = ?" : "";
128
+ const row = db
129
+ .prepare(`SELECT ${ENTRY_COLUMNS}
160
130
  FROM entries
161
131
  WHERE derived_from = ?
162
132
  ${sourceScope}
163
133
  ORDER BY id DESC
164
134
  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". */
135
+ .get(parentRef, ...(stashDir ? [stashDir] : []));
136
+ if (!row)
173
137
  return null;
174
- }
138
+ return rowToIndexedEntry(row, "getDerivedForParent");
175
139
  }
176
140
  /**
177
141
  * 03-R3: for the given derived-twin row ids, fetch each twin's BASE memory
@@ -198,33 +162,21 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
198
162
  const chunk = twinIds.slice(i, i + SQLITE_CHUNK_SIZE);
199
163
  const placeholders = chunk.map(() => "?").join(",");
200
164
  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
165
  const rows = db
211
166
  .prepare(`SELECT twin.id AS twin_id, json_extract(base.entry_json, '$.beliefState') AS belief
212
167
  FROM entries twin
213
168
  JOIN entries base
214
169
  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
- )
170
+ AND base.item_ref = substr(twin.item_ref, 1, length(twin.item_ref) - length('.derived'))
219
171
  WHERE twin.id IN (${placeholders})
220
- AND (twin.entry_key LIKE '%.derived' OR twin.item_ref LIKE '%.derived')
172
+ AND twin.item_ref LIKE '%.derived'
221
173
  AND json_extract(base.entry_json, '$.beliefState') IS NOT NULL`)
222
174
  .all(...chunk);
223
175
  for (const r of rows) {
224
176
  if (typeof r.belief === "string" && r.belief.trim().length > 0)
225
177
  out.set(r.twin_id, r.belief.trim());
226
178
  }
227
- }, "legacy DB / entry_json without beliefState — treat as no twin inheritance");
179
+ }, "belief-state inheritance is best-effort");
228
180
  }
229
181
  return out;
230
182
  }
@@ -242,10 +194,8 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
242
194
  * name; the row is marked FTS-dirty for the caller's
243
195
  * `rebuildFts({incremental: true})`.
244
196
  *
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
197
+ * Bundle-qualified `usage_events.entry_ref` rows for the old conceptId are
198
+ * rewritten to the new item ref. Without this, events keep the old
249
199
  * ref, `relinkUsageEvents` finds no matching entry after the next full
250
200
  * rebuild, and the utility history the re-key exists to preserve silently
251
201
  * resets. DETACHED orphan events already sitting at the new ref (entry_id
@@ -266,14 +216,13 @@ export function getBaseBeliefStatesForDerivedTwins(db, twinIds) {
266
216
  * full `akm index` picks the file up as a fresh entry).
267
217
  */
268
218
  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.
219
+ const oldItemRef = `${opts.sourceName}//${opts.oldRef}`;
271
220
  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);
221
+ .prepare("SELECT id, stash_dir, entry_json, search_text, entry_type FROM entries WHERE item_ref = ?")
222
+ .get(oldItemRef);
274
223
  if (!row)
275
224
  return null;
276
- if (opts.sourceRoot && path.resolve(row.stash_dir) !== path.resolve(opts.sourceRoot)) {
225
+ if (path.resolve(row.stash_dir) !== path.resolve(opts.sourceRoot)) {
277
226
  throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: source root does not match.`);
278
227
  }
279
228
  // Patch the JSON payload. On corrupt entry_json still re-key key + paths so
@@ -293,19 +242,11 @@ export function rekeyEntryInPlace(db, opts) {
293
242
  catch {
294
243
  /* corrupt entry_json — key/path-only re-key */
295
244
  }
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;
245
+ const expectedNewRef = conceptIdFromTypeName(row.entry_type, opts.newName);
246
+ if (opts.newRef !== expectedNewRef) {
247
+ throw new Error(`Refusing to re-key entry ${opts.oldEntryKey}: target ref does not match the entry type and name.`);
308
248
  }
249
+ const newItemRef = `${opts.sourceName}//${opts.newRef}`;
309
250
  db.transaction(() => {
310
251
  const stale = db.prepare("SELECT id FROM entries WHERE entry_key = ?").get(opts.newEntryKey);
311
252
  if (stale && stale.id !== row.id) {
@@ -317,13 +258,10 @@ export function rekeyEntryInPlace(db, opts) {
317
258
  deleteRelatedRows(db, [{ id: stale.id }]);
318
259
  db.prepare("DELETE FROM entries WHERE id = ?").run(stale.id);
319
260
  }
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);
261
+ 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
262
  if (opts.newDerivedFrom !== undefined) {
322
263
  db.prepare("UPDATE entries SET derived_from = ? WHERE id = ?").run(opts.newDerivedFrom, row.id);
323
264
  }
324
- if (newItemRef !== null) {
325
- db.prepare("UPDATE entries SET item_ref = ?, concept_id = ? WHERE id = ?").run(newItemRef, newConceptId, row.id);
326
- }
327
265
  db.prepare("INSERT OR IGNORE INTO entries_fts_dirty (entry_id) VALUES (?)").run(row.id);
328
266
  })();
329
267
  // Re-point usage history at the new ref. Chunk-8 WI-8.3: usage_events lives in
@@ -336,28 +274,19 @@ export function rekeyEntryInPlace(db, opts) {
336
274
  }
337
275
  /**
338
276
  * 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
277
+ * state.db. DETACHED orphan events (entry_id NULL) already sitting AT the new
341
278
  * 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.
279
+ * history (live-asset-wins). Best-effort + guarded on state.db's existence.
344
280
  */
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
281
  function rewriteUsageEventRefForMove(opts) {
353
- if (!fs.existsSync(getStateDbPath()))
282
+ // Every other failure in here throws (see the catch below) precisely because
283
+ // a move that quietly drops its usage history is a wrong answer wearing a
284
+ // success. An unreadable state.db must not be the one silent exception —
285
+ // only a state.db that was never created skips (#791).
286
+ if (isPathAbsent(getStateDbPath()))
354
287
  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);
288
+ // `usage_events.entry_ref` is the fully-qualified item_ref
289
+ // (`<bundle>//<conceptId>`).
361
290
  const rename = (stateDb, oldR, newR) => {
362
291
  stateDb.prepare("DELETE FROM usage_events WHERE entry_id IS NULL AND entry_ref = ?").run(newR);
363
292
  stateDb.prepare("UPDATE usage_events SET entry_ref = ? WHERE entry_ref = ?").run(newR, oldR);
@@ -365,33 +294,13 @@ function rewriteUsageEventRefForMove(opts) {
365
294
  try {
366
295
  withStateDb((stateDb) => {
367
296
  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
- }
297
+ rename(stateDb, `${opts.sourceName}//${opts.oldRef}`, `${opts.sourceName}//${opts.newRef}`);
385
298
  })();
386
299
  });
387
300
  }
388
301
  catch (error) {
389
302
  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;
303
+ throw new Error(`Failed to rewrite usage events for move: ${message}`, { cause: error });
395
304
  }
396
305
  }
397
306
  /**
@@ -435,7 +344,7 @@ export function getPositiveFeedbackCountsByIds(ids) {
435
344
  }
436
345
  }
437
346
  });
438
- }, "usage_events table may be missing on legacy state.db — treat as zero counts");
347
+ }, "positive feedback counts are best-effort");
439
348
  return result;
440
349
  }
441
350
  function deleteEntriesWhere(db, column, value) {
@@ -448,6 +357,16 @@ function deleteEntriesWhere(db, column, value) {
448
357
  export function deleteEntriesByDir(db, dirPath) {
449
358
  deleteEntriesWhere(db, "dir_path", dirPath);
450
359
  }
360
+ export function deleteEntriesByDirAndStash(db, dirPath, stashDir, options = {}) {
361
+ return db.transaction(() => {
362
+ const ids = db
363
+ .prepare("SELECT id FROM entries WHERE dir_path = ? AND stash_dir = ?")
364
+ .all(dirPath, stashDir);
365
+ deleteRelatedRows(db, ids, options);
366
+ db.prepare("DELETE FROM entries WHERE dir_path = ? AND stash_dir = ?").run(dirPath, stashDir);
367
+ return ids.map((row) => row.id);
368
+ })();
369
+ }
451
370
  /**
452
371
  * Delete every entry (and its child rows) under a source's stash root.
453
372
  *
@@ -471,28 +390,28 @@ export function deleteEntriesByStashDir(db, stashDir) {
471
390
  * to prune only the DEPARTED rows. The net row-state of `dir_path` is identical
472
391
  * to delete-then-reinsert; the win is that unchanged rows keep their id.
473
392
  *
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.
393
+ * Keyed on `entry_key` because it mirrors the directory upsert key precisely.
394
+ * Both directory- and source-scoped so overlapping source roots cannot prune
395
+ * one another's rows.
480
396
  */
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);
397
+ export function deleteEntriesByDirExceptKeys(db, dirPath, stashDir, keepKeys, options = {}) {
398
+ return db.transaction(() => {
399
+ const rows = db
400
+ .prepare("SELECT id, entry_key FROM entries WHERE dir_path = ? AND stash_dir = ?")
401
+ .all(dirPath, stashDir);
484
402
  const doomed = rows.filter((r) => !keepKeys.has(r.entry_key));
485
403
  if (doomed.length === 0)
486
- return;
487
- deleteRelatedRows(db, doomed);
404
+ return [];
405
+ deleteRelatedRows(db, doomed, options);
488
406
  for (let i = 0; i < doomed.length; i += SQLITE_CHUNK_SIZE) {
489
407
  const chunk = doomed.slice(i, i + SQLITE_CHUNK_SIZE);
490
408
  const placeholders = chunk.map(() => "?").join(",");
491
409
  db.prepare(`DELETE FROM entries WHERE id IN (${placeholders})`).run(...chunk.map((r) => r.id));
492
410
  }
411
+ return doomed.map((row) => row.id);
493
412
  })();
494
413
  }
495
- function deleteRelatedRows(db, ids) {
414
+ function deleteRelatedRows(db, ids, options = {}) {
496
415
  if (ids.length === 0)
497
416
  return;
498
417
  const numericIds = ids.map((r) => r.id);
@@ -518,22 +437,11 @@ function deleteRelatedRows(db, ids) {
518
437
  bestEffort(() => db.prepare(`DELETE FROM utility_scores WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores for entries");
519
438
  bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
520
439
  }
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
- }
440
+ // usage_events lives in state.db, outside this transaction. Index persistence
441
+ // disables this cleanup and runs it only after its index.db transaction
442
+ // commits; standalone delete callers retain the immediate behavior.
443
+ if (options.cleanupUsageEvents !== false)
444
+ deleteUsageEventsByEntryIds(numericIds);
537
445
  // #624-P1: graph_files is NO LONGER keyed on entries.id, so deleting an
538
446
  // entries row must NOT wipe the extracted graph (that is the whole point —
539
447
  // the graph survives a reindex when body_hash is unchanged). We therefore do
@@ -567,6 +475,19 @@ function deleteRelatedRows(db, ids) {
567
475
  .run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
568
476
  }
569
477
  }
478
+ export function deleteUsageEventsByEntryIds(entryIds) {
479
+ if (entryIds.length === 0 || !fs.existsSync(getStateDbPath()))
480
+ return;
481
+ bestEffort(() => {
482
+ withStateDb((stateDb) => {
483
+ for (let i = 0; i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
484
+ const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
485
+ const placeholders = chunk.map(() => "?").join(",");
486
+ stateDb.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk);
487
+ }
488
+ });
489
+ }, "delete usage_events (state.db) for entries");
490
+ }
570
491
  /**
571
492
  * Delete entries by their primary key IDs, along with all related rows
572
493
  * (embeddings, entries_vec, entries_fts, utility_scores, usage_events).
@@ -621,7 +542,7 @@ export function getAllEntries(db, entryType, excludeTypes) {
621
542
  /**
622
543
  * Resolve a single `entries.id` from a new-grammar `[bundle//]conceptId` ref,
623
544
  * 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
545
+ * The optional `stashDir` scopes the
625
546
  * match to one source root.
626
547
  */
627
548
  export function findEntryIdByRef(db, ref, stashDir) {
@@ -632,9 +553,8 @@ function withMdVariants(name) {
632
553
  return name.endsWith(".md") ? [name, name.slice(0, -3)] : [name, `${name}.md`];
633
554
  }
634
555
  /**
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.
556
+ * Current (`[bundle//]conceptId`) id lookup: match `item_ref` exactly when
557
+ * bundle-qualified or by `//conceptId` suffix when short.
638
558
  */
639
559
  function findEntryIdByBundleRef(db, ref, stashDir) {
640
560
  const parsed = parseBundleRef(ref);
@@ -669,9 +589,7 @@ function findEntryIdByBundleRef(db, ref, stashDir) {
669
589
  * precedence-ordered scan would pick — while staying a pure config-free leaf
670
590
  * (true installation-priority resolution against an injected bundle list is
671
591
  * `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.
592
+ * arm is single-row under the UNIQUE `item_ref` index.
675
593
  */
676
594
  function matchIdByItemRef(db, bundle, conceptId, stashDir) {
677
595
  const scope = stashDir ? "AND stash_dir = ?" : "";
@@ -697,11 +615,29 @@ export function getEntryCount(db) {
697
615
  const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries").get();
698
616
  return row.cnt;
699
617
  }
618
+ /**
619
+ * Per-asset-type entry counts (keyed by `entry_type`, e.g. "skill",
620
+ * "knowledge", "memory"). Used by `akm info` to break down the aggregate
621
+ * `indexStats.entryCount` (R-057). Rows with a null/empty `entry_type` are
622
+ * omitted rather than surfaced under a synthetic key.
623
+ */
624
+ export function getEntryCountByType(db) {
625
+ const rows = db
626
+ .prepare("SELECT entry_type AS type, COUNT(*) AS cnt FROM entries WHERE entry_type IS NOT NULL GROUP BY entry_type")
627
+ .all();
628
+ const out = {};
629
+ for (const row of rows) {
630
+ out[row.type] = row.cnt;
631
+ }
632
+ return out;
633
+ }
700
634
  export function getEmbeddableEntryCount(db) {
701
635
  return getEntryCount(db);
702
636
  }
703
637
  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);
638
+ const row = db
639
+ .prepare("SELECT file_path, stash_dir, entry_json, item_ref, bundle_id, concept_id FROM entries WHERE id = ?")
640
+ .get(id);
705
641
  if (!row)
706
642
  return undefined;
707
643
  // Guard against corrupt JSON
@@ -713,12 +649,29 @@ export function getEntryById(db, id) {
713
649
  warn(`[db] getEntryById: skipping entry id=${id} — corrupt entry_json`);
714
650
  return undefined;
715
651
  }
716
- return { filePath: row.file_path, stashDir: row.stash_dir, entry, itemRef: row.item_ref };
652
+ return {
653
+ filePath: row.file_path,
654
+ stashDir: row.stash_dir,
655
+ entry,
656
+ itemRef: row.item_ref,
657
+ bundleId: row.bundle_id,
658
+ conceptId: row.concept_id,
659
+ };
717
660
  }
718
661
  export function getEntriesByDir(db, dirPath) {
719
662
  const rows = db.prepare(`SELECT ${ENTRY_COLUMNS} FROM entries WHERE dir_path = ?`).all(dirPath);
720
663
  return parseEntryRows(rows, "getEntriesByDir");
721
664
  }
665
+ /** Return every directory previously indexed for one physical source root. */
666
+ export function getIndexedDirPathsByStashDir(db, stashDir) {
667
+ const rows = db.prepare("SELECT DISTINCT dir_path FROM entries WHERE stash_dir = ?").all(stashDir);
668
+ return rows.map((row) => row.dir_path);
669
+ }
670
+ /** Return every persisted source owner for one physical directory. */
671
+ export function getIndexedStashDirsByDir(db, dirPath) {
672
+ const rows = db.prepare("SELECT DISTINCT stash_dir FROM entries WHERE dir_path = ?").all(dirPath);
673
+ return rows.map((row) => row.stash_dir);
674
+ }
722
675
  /**
723
676
  * Resolve a single `entries.id` by exact `file_path` (the canonical on-disk
724
677
  * path), or `undefined` if no row matches.
@@ -848,14 +801,15 @@ export function getItemRefById(db, id) {
848
801
  return row?.item_ref ?? null;
849
802
  }
850
803
  /**
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.
804
+ * Resolve a `usage_events.entry_ref` to its live `entries.id`. `entry_ref` is
805
+ * the fully-qualified `bundle//conceptId` `item_ref` spelling, so this keys
806
+ * directly on the globally-unique `item_ref`. Bare durable refs are invalid and
807
+ * remain detached.
856
808
  */
857
- function resolveUsageEventEntryId(db, ref, options) {
858
- return findEntryIdByRef(db, ref, parseBundleRef(ref).bundle ? undefined : options.defaultStashDir);
809
+ function resolveUsageEventEntryId(db, ref) {
810
+ if (parseBundleRef(ref).bundle === undefined)
811
+ return undefined;
812
+ return findEntryIdByRef(db, ref);
859
813
  }
860
814
  /**
861
815
  * Re-link detached usage_events to their current entry_ids via entry_ref.
@@ -869,7 +823,7 @@ function resolveUsageEventEntryId(db, ref, options) {
869
823
  * distinct linked entry_ids in usage_events is small — and the re-resolution
870
824
  * reads `entries` from `indexDb`.
871
825
  */
872
- export function relinkUsageEvents(indexDb, stateDb, options = {}) {
826
+ export function relinkUsageEvents(indexDb, stateDb, _options = {}) {
873
827
  bestEffort(() => {
874
828
  // Step 1: null out stale entry_ids (entry was deleted, re-keyed, etc).
875
829
  // Leaving them in place would let `recomputeUtilityScores` aggregate by an
@@ -888,10 +842,8 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
888
842
  });
889
843
  nullTx();
890
844
  }
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.
845
+ // Step 2: re-resolve each fully-qualified ref. Bare rows are not current
846
+ // durable identities and remain detached.
895
847
  const refs = stateDb
896
848
  .prepare("SELECT DISTINCT entry_ref AS ref FROM usage_events WHERE entry_id IS NULL AND entry_ref IS NOT NULL")
897
849
  .all();
@@ -900,7 +852,7 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
900
852
  for (const { ref } of refs) {
901
853
  let id;
902
854
  try {
903
- id = resolveUsageEventEntryId(indexDb, ref, options);
855
+ id = resolveUsageEventEntryId(indexDb, ref);
904
856
  }
905
857
  catch (err) {
906
858
  if (err instanceof Error && err.name === "UsageError")
@@ -914,39 +866,3 @@ export function relinkUsageEvents(indexDb, stateDb, options = {}) {
914
866
  relinkTx();
915
867
  }, "usage_events table may not exist yet during entry_id re-resolution");
916
868
  }
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
- }