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
@@ -4,27 +4,33 @@
4
4
  /**
5
5
  * Source operations: list, remove, update.
6
6
  *
7
- * Provides unified operations across all source kinds (local, managed, remote).
8
- * The CLI's `akm list`, `akm remove`, and `akm update` commands are wired here.
7
+ * Provides unified operations across all configured bundle source providers.
8
+ * The CLI's `akm bundle list`, `akm bundle remove`, and `akm bundle update` commands are wired here.
9
9
  *
10
10
  * 0.9.0 (spec §10.1/§10.2): the retired `installed[]` array is gone — a
11
11
  * registry-managed source is now a `bundles.<slug>` entry (the desired locator)
12
12
  * paired with a lock entry (the resolved `localRoot`/version). A bundle that has
13
13
  * a lock entry is "managed" (installed from a registry and overwritten on
14
- * `akm update`); a bundle with no lock is a plain filesystem/git/website source.
14
+ * `akm bundle update`); a bundle with no lock is a plain filesystem/git/website source.
15
15
  */
16
16
  import fs from "node:fs";
17
17
  import path from "node:path";
18
- import { resolveStashDir } from "../../core/common.js";
18
+ import { isWithin, resolveStashDir } from "../../core/common.js";
19
19
  import { getSources, loadConfig } from "../../core/config/config.js";
20
- import { NotFoundError, UsageError } from "../../core/errors.js";
20
+ import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
21
+ import { getDbPath } from "../../core/paths.js";
22
+ import { warn } from "../../core/warn.js";
23
+ import { withAssetMutationLease } from "../../indexer/index-writer-lock.js";
21
24
  import { akmIndex } from "../../indexer/indexer.js";
22
- import { readLockfile, upsertLockEntry } from "../../integrations/lockfile.js";
25
+ import { compareAndSwapLockfile, readLockfile } from "../../integrations/lockfile.js";
23
26
  import { parseRegistryRef } from "../../registry/resolve.js";
27
+ import { sha256Hex } from "../../runtime.js";
24
28
  import { parseGitRepoUrl, syncMirroredRepo } from "../../sources/providers/git.js";
25
29
  import { syncFromRef } from "../../sources/providers/sync-from-ref.js";
26
30
  import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests, } from "../../sources/snapshot-fetchers/website-ingest.js";
27
- import { removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./source-add.js";
31
+ import { closeDatabase, openReadonlyExistingDatabase } from "../../storage/repositories/index-connection.js";
32
+ import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
33
+ import { removeInstalledRegistryEntry } from "./source-add.js";
28
34
  import { removeStash } from "./source-manage.js";
29
35
  /** Enumerate the registry-managed installs (lock-backed bundles) in a config. */
30
36
  function listManagedInstalls(config) {
@@ -35,6 +41,7 @@ function listManagedInstalls(config) {
35
41
  const lock = locks.get(key);
36
42
  if (!lock)
37
43
  continue; // only lock-backed bundles are registry-managed
44
+ const componentWritable = Object.values(bundle.components ?? {})[0]?.writable;
38
45
  out.push({
39
46
  bundleKey: key,
40
47
  installId: bundle.registryId ?? key,
@@ -43,12 +50,15 @@ function listManagedInstalls(config) {
43
50
  localRoot: lock.localRoot ?? "",
44
51
  resolvedVersion: lock.resolvedVersion,
45
52
  resolvedRevision: lock.resolvedRevision,
46
- writable: bundle.writable === true,
53
+ writable: componentWritable ?? bundle.writable === true,
54
+ requiredRoots: lock.localRoot
55
+ ? Object.values(bundle.components ?? {}).map((component) => path.resolve(lock.localRoot, component.root ?? "."))
56
+ : [],
47
57
  });
48
58
  }
49
59
  return out;
50
60
  }
51
- /** Resolve an `akm remove`/`akm update` target to a managed install, if any. */
61
+ /** Resolve an `akm bundle remove`/`akm bundle update` target to a managed install, if any. */
52
62
  function resolveManagedTarget(config, target) {
53
63
  const installs = listManagedInstalls(config);
54
64
  const byId = installs.find((m) => m.installId === target || m.bundleKey === target);
@@ -75,49 +85,118 @@ function resolveManagedTarget(config, target) {
75
85
  return installs.find((m) => m.installId === parsedId);
76
86
  return undefined;
77
87
  }
88
+ function describeBundleSource(entry) {
89
+ if (entry.path)
90
+ return { kind: "path", locator: entry.path };
91
+ if (entry.git)
92
+ return { kind: "git", locator: entry.git };
93
+ if (entry.website) {
94
+ return {
95
+ kind: "website",
96
+ locator: entry.website.url,
97
+ ...(entry.website.maxPages !== undefined ? { maxPages: entry.website.maxPages } : {}),
98
+ };
99
+ }
100
+ return { kind: "npm", locator: entry.npm ?? "" };
101
+ }
102
+ function describeComponents(entry) {
103
+ return Object.entries(entry.components ?? {}).map(([name, component]) => ({
104
+ name,
105
+ ...(component.root !== undefined ? { root: component.root } : {}),
106
+ ...(component.adapter !== undefined ? { adapter: component.adapter } : {}),
107
+ ...(component.writable !== undefined ? { writable: component.writable } : {}),
108
+ }));
109
+ }
110
+ function describeLock(entry) {
111
+ if (!entry)
112
+ return null;
113
+ return {
114
+ source: entry.source,
115
+ ref: entry.ref,
116
+ ...(entry.resolvedVersion !== undefined ? { resolvedVersion: entry.resolvedVersion } : {}),
117
+ ...(entry.resolvedRevision !== undefined ? { resolvedRevision: entry.resolvedRevision } : {}),
118
+ ...(entry.integrity !== undefined ? { integrity: entry.integrity } : {}),
119
+ ...(entry.localRoot !== undefined ? { localRoot: entry.localRoot } : {}),
120
+ ...(entry.manifestDigest !== undefined ? { manifestDigest: entry.manifestDigest } : {}),
121
+ ...(entry.adapterIds !== undefined ? { adapterIds: entry.adapterIds } : {}),
122
+ ...(entry.installedAt !== undefined ? { installedAt: entry.installedAt } : {}),
123
+ };
124
+ }
125
+ function readBundleCounts() {
126
+ const counts = new Map();
127
+ const dbPath = getDbPath();
128
+ if (!fs.existsSync(dbPath))
129
+ return counts;
130
+ let db;
131
+ try {
132
+ db = openReadonlyExistingDatabase(dbPath);
133
+ if (!db)
134
+ return counts;
135
+ for (const row of getAllEntries(db)) {
136
+ if (!row.bundleId)
137
+ continue;
138
+ const count = counts.get(row.bundleId) ?? { itemCount: 0, byType: {} };
139
+ count.itemCount += 1;
140
+ count.byType[row.entry.type] = (count.byType[row.entry.type] ?? 0) + 1;
141
+ counts.set(row.bundleId, count);
142
+ }
143
+ }
144
+ catch (error) {
145
+ warn(`[akm bundle list] failed to read bundle counts from ${dbPath}: ${String(error)}`);
146
+ }
147
+ finally {
148
+ if (db) {
149
+ try {
150
+ closeDatabase(db);
151
+ }
152
+ catch {
153
+ // The inventory read is already complete; a close failure is non-fatal.
154
+ }
155
+ }
156
+ }
157
+ return counts;
158
+ }
78
159
  export async function akmListSources(input) {
79
160
  const stashDir = input?.stashDir ?? resolveStashDir();
80
161
  const config = loadConfig();
81
162
  const kindFilter = input?.kind;
82
163
  const locks = new Map(readLockfile().map((entry) => [entry.id, entry]));
164
+ const counts = readBundleCounts();
83
165
  const sources = [];
84
- // Every source is a bundle. A bundle with a lock entry is registry-managed;
85
- // otherwise it is a plain filesystem/git/website source.
86
166
  for (const bundle of getSources(config)) {
87
167
  const key = bundle.name ?? bundle.path ?? bundle.url ?? "unknown";
88
- const lock = bundle.name ? locks.get(bundle.name) : undefined;
89
- if (lock) {
90
- const kind = "managed";
91
- if (kindFilter && !kindFilter.includes(kind))
92
- continue;
93
- const root = lock.localRoot ?? bundle.path ?? "";
94
- sources.push({
95
- name: key,
96
- kind,
97
- ...(root ? { path: root } : {}),
98
- ref: lock.ref,
99
- version: lock.resolvedVersion,
100
- writable: bundle.writable === true,
101
- status: { exists: root ? directoryExists(root) : false },
102
- });
168
+ const configured = config.bundles?.[key];
169
+ if (!configured)
103
170
  continue;
104
- }
105
- const kind = bundle.type ?? "filesystem";
171
+ const lock = locks.get(key);
172
+ const kind = bundle.type;
106
173
  if (kindFilter && !kindFilter.includes(kind))
107
174
  continue;
108
- const isFilesystem = kind === "filesystem";
175
+ const root = lock?.localRoot ?? bundle.path ?? "";
176
+ const componentWritable = Object.values(configured.components ?? {})[0]?.writable;
177
+ const bundleCounts = counts.get(key) ?? { itemCount: 0, byType: {} };
109
178
  sources.push({
110
179
  name: key,
111
180
  kind,
112
- path: bundle.path,
181
+ default: key === config.defaultBundle,
182
+ source: describeBundleSource(configured),
183
+ ...(root ? { path: root } : {}),
184
+ ...(lock ? { ref: lock.ref } : {}),
113
185
  provider: bundle.url != null ? bundle.type : undefined,
114
- writable: bundle.writable !== undefined ? bundle.writable : isFilesystem,
115
- status: { exists: bundle.path ? directoryExists(bundle.path) : true },
186
+ ...(lock?.resolvedVersion !== undefined ? { version: lock.resolvedVersion } : {}),
187
+ writable: componentWritable ?? bundle.writable ?? kind === "filesystem",
188
+ ...(configured.registryId !== undefined ? { registryId: configured.registryId } : {}),
189
+ components: describeComponents(configured),
190
+ lock: describeLock(lock),
191
+ itemCount: bundleCounts.itemCount,
192
+ byType: bundleCounts.byType,
193
+ status: { exists: root ? directoryExists(root) : true },
116
194
  });
117
195
  }
118
196
  return {
119
197
  schemaVersion: 1,
120
- stashDir,
198
+ bundleDir: stashDir,
199
+ defaultBundle: config.defaultBundle ?? null,
121
200
  sources,
122
201
  totalSources: sources.length,
123
202
  };
@@ -125,7 +204,7 @@ export async function akmListSources(input) {
125
204
  export async function akmRemove(input) {
126
205
  const target = input.target.trim();
127
206
  if (!target)
128
- throw new UsageError("Target is required. Provide the source id, ref, path, URL, or name (e.g. `akm remove npm:@scope/stash` or `akm remove ~/my-stash`).");
207
+ throw new UsageError("Target is required. Provide the source id, ref, path, URL, or name (e.g. `akm bundle remove npm:@scope/stash` or `akm bundle remove ~/my-stash`).");
129
208
  const stashDir = input.stashDir ?? resolveStashDir();
130
209
  const config = loadConfig();
131
210
  // Registry-managed installs (lock-backed bundles) first.
@@ -133,12 +212,12 @@ export async function akmRemove(input) {
133
212
  if (managed) {
134
213
  const updatedConfig = await removeInstalledRegistryEntry(managed.installId);
135
214
  if (managed.source !== "local" && managed.localRoot) {
136
- cleanupDirectoryBestEffort(managed.localRoot);
215
+ cleanupDirectoryBestEffort(managed.localRoot, "remove");
137
216
  }
138
217
  const index = await akmIndex({ stashDir });
139
218
  return {
140
219
  schemaVersion: 1,
141
- stashDir,
220
+ bundleDir: stashDir,
142
221
  target,
143
222
  removed: {
144
223
  id: managed.installId,
@@ -149,7 +228,6 @@ export async function akmRemove(input) {
149
228
  },
150
229
  config: {
151
230
  sourceCount: getSources(updatedConfig).length,
152
- installedKitCount: readLockfile().length,
153
231
  },
154
232
  index: {
155
233
  mode: index.mode,
@@ -169,7 +247,7 @@ export async function akmRemove(input) {
169
247
  const updatedConfig = loadConfig();
170
248
  return {
171
249
  schemaVersion: 1,
172
- stashDir,
250
+ bundleDir: stashDir,
173
251
  target,
174
252
  removed: {
175
253
  id: removedEntry.name ?? removedEntry.path ?? removedEntry.url ?? target,
@@ -180,7 +258,6 @@ export async function akmRemove(input) {
180
258
  },
181
259
  config: {
182
260
  sourceCount: getSources(updatedConfig).length,
183
- installedKitCount: readLockfile().length,
184
261
  },
185
262
  index: {
186
263
  mode: index.mode,
@@ -192,18 +269,19 @@ export async function akmRemove(input) {
192
269
  }
193
270
  // ── akmUpdate helpers ────────────────────────────────────────────────────────
194
271
  /** Build a standard UpdateResponse summary block from the current config and index run. */
195
- async function buildUpdateResponse(stashDir, target, all, processed, full = false) {
196
- const index = await akmIndex({ stashDir, ...(full ? { full: true } : {}) });
272
+ async function buildUpdateResponse(stashDir, target, all, processed, opts) {
273
+ const index = opts?.index ?? (await akmIndex({ stashDir, ...(opts?.full ? { full: true } : {}) }));
197
274
  const finalConfig = loadConfig();
198
275
  return {
199
276
  schemaVersion: 1,
200
- stashDir,
277
+ bundleDir: stashDir,
201
278
  target,
202
279
  all,
203
280
  processed,
281
+ ...(opts?.plainSynced?.length ? { plainSynced: opts.plainSynced } : {}),
282
+ ...(opts?.skipped?.length ? { skipped: opts.skipped } : {}),
204
283
  config: {
205
284
  sourceCount: getSources(finalConfig).length,
206
- installedKitCount: readLockfile().length,
207
285
  },
208
286
  index: {
209
287
  mode: index.mode,
@@ -213,30 +291,87 @@ async function buildUpdateResponse(stashDir, target, all, processed, full = fals
213
291
  },
214
292
  };
215
293
  }
216
- /** Sync a git-mirrored (plain) source and return an UpdateResponse. */
217
- async function updateGitSource(stashDir, target, all, gitSource) {
294
+ /**
295
+ * Sync a git-mirrored (plain) source in place. Returns the {@link UpdatePlainSyncedItem}
296
+ * record instead of building the full response, so `--all` can batch several of
297
+ * these into one response alongside managed installs (R-015).
298
+ */
299
+ async function syncGitPlainSource(gitSource) {
218
300
  await syncMirroredRepo(gitSource, { force: true, writable: gitSource.writable === true });
219
- return buildUpdateResponse(stashDir, target, all, [], true);
301
+ return { id: gitSource.name ?? gitSource.url ?? "", kind: "git", ref: gitSource.url ?? "" };
220
302
  }
221
- /** Re-crawl a website (plain) source and return an UpdateResponse. */
222
- async function updateWebsiteSource(stashDir, target, all, websiteSource) {
303
+ /** Re-crawl a website (plain) source in place. See {@link syncGitPlainSource}. */
304
+ async function syncWebsitePlainSource(websiteSource) {
223
305
  // TODO: full incremental re-crawl with delta tracking (#19)
224
306
  await ensureWebsiteMirror(websiteSource, {
225
307
  requireStashDir: true,
226
308
  force: true,
227
309
  ...(shouldAllowPrivateWebsiteUrlForTests(websiteSource.url ?? "") ? { allowPrivateHosts: true } : {}),
228
310
  });
229
- return buildUpdateResponse(stashDir, target, all, []);
311
+ return { id: websiteSource.name ?? websiteSource.url ?? "", kind: "website", ref: websiteSource.url ?? "" };
312
+ }
313
+ /** Sync a git-mirrored (plain) source and return an UpdateResponse (single-target path). */
314
+ async function updateGitSource(stashDir, target, all, gitSource) {
315
+ const synced = await syncGitPlainSource(gitSource);
316
+ return buildUpdateResponse(stashDir, target, all, [], { full: true, plainSynced: [synced] });
317
+ }
318
+ /** Re-crawl a website (plain) source and return an UpdateResponse (single-target path). */
319
+ async function updateWebsiteSource(stashDir, target, all, websiteSource) {
320
+ const synced = await syncWebsitePlainSource(websiteSource);
321
+ return buildUpdateResponse(stashDir, target, all, [], { plainSynced: [synced] });
230
322
  }
231
- /** Sync a single registry-managed install and return the processed record. */
232
- async function updateManagedInstall(managed, force) {
323
+ /**
324
+ * A plain (lockless) npm bundle has no deterministic content path — unlike
325
+ * git/website, resolving an npm package requires a registry round-trip to
326
+ * pick a concrete version/tarball, which is exactly what the lock records.
327
+ * So a plain npm source is synced via the same registry-install pipeline as
328
+ * `akm bundle add <package>` and PROMOTED to a registry-managed (lock-backed)
329
+ * install as a side effect of its first successful sync; it is reported via
330
+ * `processed` like any other managed install from then on. Building a
331
+ * {@link ManagedInstall} view onto the plain entry lets this reuse
332
+ * {@link updateManagedInstall} verbatim rather than duplicating its lock/config
333
+ * bookkeeping.
334
+ */
335
+ function managedInstallViewOfPlainNpm(npmSource) {
336
+ const spec = npmSource.path ?? "";
337
+ const ref = spec.startsWith("npm:") ? spec : `npm:${spec}`;
338
+ const id = npmSource.name ?? ref;
339
+ return {
340
+ bundleKey: id,
341
+ installId: id,
342
+ source: "npm",
343
+ ref,
344
+ localRoot: "",
345
+ resolvedVersion: undefined,
346
+ resolvedRevision: undefined,
347
+ writable: false,
348
+ requiredRoots: [],
349
+ };
350
+ }
351
+ /**
352
+ * Sync a single registry-managed install and return the processed record.
353
+ *
354
+ * `yes` gates ONLY the destructive branch below (deleting a previous
355
+ * `localRoot` whose content moved) — it has no effect on the sync itself. A
356
+ * normal refresh that resolves the SAME content directory (the overwhelming
357
+ * majority of updates) never reaches that branch, so it never prompts and
358
+ * never requires `--yes` (F1/R-058: `update` must stay usable bare in
359
+ * scripts; only the branch that can `rm -rf` needs a gate, not the whole
360
+ * command).
361
+ */
362
+ async function updateManagedInstall(managed, force, yes, stashDir) {
233
363
  // No pre-cleanup of the old root, even under --force: the providers already
234
364
  // re-materialize staging-first (git clones into a `.tmp-*` sibling and swaps
235
365
  // only on success), so destroying `managed.localRoot` BEFORE `syncFromRef`
236
366
  // succeeds would turn any sync failure (network down, bad ref) into losing a
237
- // previously-working install. The old root is cleaned up below, after the
238
- // lock points at the new content.
239
- const synced = await syncFromRef(managed.ref, { force });
367
+ // previously-working install. The old root is cleaned up below only after
368
+ // config/lock publication and reindexing both succeed.
369
+ const synced = await syncFromRef(managed.ref, {
370
+ force,
371
+ writable: managed.writable,
372
+ ...(managed.writable && managed.localRoot ? { writableRoot: managed.localRoot } : {}),
373
+ ...(managed.writable && managed.requiredRoots.length > 0 ? { writableRequiredRoots: managed.requiredRoots } : {}),
374
+ });
240
375
  const installedEntry = {
241
376
  id: managed.installId,
242
377
  // Preserve the original source classification. syncFromRef() re-derives the
@@ -252,44 +387,100 @@ async function updateManagedInstall(managed, force) {
252
387
  installedAt: synced.syncedAt,
253
388
  writable: synced.writable ?? managed.writable,
254
389
  };
255
- const { bundleId } = upsertInstalledRegistryEntry(installedEntry);
256
- await upsertLockEntry({
257
- id: bundleId,
258
- // Preserve the STORED install kind: a `github:`-ref entry recorded as
259
- // source "git" must not be reclassified by the sync flow's re-derivation
260
- // (the issue this file's update pin exists for).
261
- source: managed.source,
262
- ref: synced.ref,
263
- resolvedVersion: synced.resolvedVersion,
264
- resolvedRevision: synced.resolvedRevision,
265
- integrity: synced.integrity ?? (synced.source === "local" ? "local" : undefined),
266
- // §10.2 resolved lock state the sync flow has on hand.
267
- localRoot: synced.contentDir,
268
- installedAt: synced.syncedAt,
269
- });
270
- if (managed.localRoot &&
390
+ const movedRoot = managed.localRoot !== "" &&
271
391
  path.resolve(managed.localRoot) !== path.resolve(synced.contentDir) &&
272
- managed.source !== "local") {
273
- cleanupDirectoryBestEffort(managed.localRoot);
392
+ managed.source !== "local" &&
393
+ !managed.writable;
394
+ if (movedRoot) {
395
+ const { confirmDestructive } = await import("../../cli/confirm.js");
396
+ const confirmed = await confirmDestructive(`Update resolved a new content directory for "${managed.installId}" (${synced.contentDir}) and would delete the previous install directory at ${managed.localRoot}. This cannot be undone.`, { yes });
397
+ if (!confirmed)
398
+ throw new UsageError(`Update cancelled for "${managed.installId}"; no configuration was changed.`);
274
399
  }
275
- const versionChanged = (managed.resolvedVersion ?? "") !== (synced.resolvedVersion ?? "");
276
- const revisionChanged = (managed.resolvedRevision ?? "") !== (synced.resolvedRevision ?? "");
277
- return {
278
- id: managed.installId,
279
- source: managed.source,
280
- ref: managed.ref,
281
- previous: {
282
- resolvedVersion: managed.resolvedVersion,
283
- resolvedRevision: managed.resolvedRevision,
284
- cacheDir: managed.localRoot,
285
- },
286
- installed: { ...installedEntry, extractedDir: synced.extractedDir },
287
- changed: {
288
- version: versionChanged,
289
- revision: revisionChanged,
290
- any: versionChanged || revisionChanged,
291
- },
292
- };
400
+ return withAssetMutationLease("source-update", async () => {
401
+ const config = loadConfig();
402
+ const oldLocks = readLockfile();
403
+ if (!config.bundles?.[managed.bundleKey]) {
404
+ throw new ConfigError(`Managed source "${managed.installId}" disappeared before update publication.`);
405
+ }
406
+ const desiredLock = {
407
+ id: managed.bundleKey,
408
+ // Preserve the STORED install kind: a `github:`-ref entry recorded as
409
+ // source "git" must not be reclassified by the sync flow's re-derivation.
410
+ source: managed.source,
411
+ ref: synced.ref,
412
+ resolvedVersion: synced.resolvedVersion,
413
+ resolvedRevision: synced.resolvedRevision,
414
+ integrity: synced.integrity ?? (synced.source === "local" ? "local" : undefined),
415
+ localRoot: synced.contentDir,
416
+ installedAt: synced.syncedAt,
417
+ };
418
+ const desiredLocks = [...oldLocks.filter((entry) => entry.id !== desiredLock.id), desiredLock];
419
+ const desiredLockGeneration = generationHash(desiredLocks);
420
+ if (!(await compareAndSwapLockfile(oldLocks, desiredLocks)))
421
+ throw concurrentGenerationError(managed);
422
+ const index = await akmIndex({ stashDir });
423
+ if (generationHash(readLockfile()) !== desiredLockGeneration)
424
+ throw concurrentGenerationError(managed);
425
+ if (movedRoot) {
426
+ const currentConfig = loadConfig();
427
+ const currentLocks = readLockfile();
428
+ if (referencesRoot(managed.localRoot, currentConfig, currentLocks)) {
429
+ warn(`[akm bundle update] kept previous install directory at ${managed.localRoot} because another configured or locked bundle still references it.`);
430
+ }
431
+ else {
432
+ cleanupDirectoryBestEffort(managed.localRoot, "update");
433
+ }
434
+ }
435
+ const versionChanged = (managed.resolvedVersion ?? "") !== (synced.resolvedVersion ?? "");
436
+ const revisionChanged = (managed.resolvedRevision ?? "") !== (synced.resolvedRevision ?? "");
437
+ return {
438
+ index,
439
+ item: {
440
+ id: managed.installId,
441
+ source: managed.source,
442
+ ref: managed.ref,
443
+ previous: {
444
+ resolvedVersion: managed.resolvedVersion,
445
+ resolvedRevision: managed.resolvedRevision,
446
+ cacheDir: managed.localRoot,
447
+ },
448
+ installed: { ...installedEntry, extractedDir: synced.extractedDir },
449
+ changed: {
450
+ version: versionChanged,
451
+ revision: revisionChanged,
452
+ any: versionChanged || revisionChanged,
453
+ },
454
+ },
455
+ };
456
+ });
457
+ }
458
+ function generationHash(value) {
459
+ return sha256Hex(JSON.stringify(value));
460
+ }
461
+ function referencesRoot(target, config, locks) {
462
+ const resolvedTarget = path.resolve(target);
463
+ const locksById = new Map(locks.map((entry) => [entry.id, entry]));
464
+ for (const [bundleId, bundle] of Object.entries(config.bundles ?? {})) {
465
+ const root = bundle.path ?? locksById.get(bundleId)?.localRoot;
466
+ if (!root)
467
+ continue;
468
+ if (isRootAtOrBelow(root, resolvedTarget))
469
+ return true;
470
+ for (const component of Object.values(bundle.components ?? {})) {
471
+ if (isRootAtOrBelow(path.resolve(root, component.root ?? "."), resolvedTarget))
472
+ return true;
473
+ }
474
+ }
475
+ return locks.some((entry) => entry.localRoot && isRootAtOrBelow(entry.localRoot, resolvedTarget));
476
+ }
477
+ function isRootAtOrBelow(candidate, root) {
478
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
479
+ const lexicallyWithin = relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
480
+ return lexicallyWithin || isWithin(candidate, root);
481
+ }
482
+ function concurrentGenerationError(managed) {
483
+ return new ConfigError(`Managed source "${managed.installId}" changed concurrently; retained both materialized roots and deleted nothing.`, "INVALID_CONFIG_FILE", "Inspect `akm bundle list` and retry the update after the concurrent source operation completes.");
293
484
  }
294
485
  // ── akmUpdate dispatcher ─────────────────────────────────────────────────────
295
486
  export async function akmUpdate(input) {
@@ -297,13 +488,15 @@ export async function akmUpdate(input) {
297
488
  const target = input?.target?.trim();
298
489
  const all = input?.all === true;
299
490
  const force = input?.force === true;
491
+ const yes = input?.yes === true;
300
492
  const config = loadConfig();
301
493
  const managedInstalls = listManagedInstalls(config);
302
494
  if (target && !all) {
303
495
  // Registry-managed install (lock-backed) — re-download from its locator.
304
496
  const managed = resolveManagedTarget(config, target);
305
497
  if (managed) {
306
- return buildUpdateResponse(stashDir, target, all, [await updateManagedInstall(managed, force)]);
498
+ const updated = await updateManagedInstall(managed, force, yes, stashDir);
499
+ return buildUpdateResponse(stashDir, target, all, [updated.item], { index: updated.index });
307
500
  }
308
501
  // Plain git / website source (bundles without a lock) — provider re-sync.
309
502
  const stashes = getSources(config);
@@ -345,13 +538,93 @@ export async function akmUpdate(input) {
345
538
  });
346
539
  if (websiteMatch)
347
540
  return updateWebsiteSource(stashDir, target, all, websiteMatch);
541
+ // Plain npm source (bundle without a lock) — sync via the registry
542
+ // pipeline and promote to a managed install (see
543
+ // managedInstallViewOfPlainNpm's doc comment for why npm can't stay
544
+ // plain the way git/website do).
545
+ const npmMatch = stashes.find((s) => {
546
+ if (s.type !== "npm")
547
+ return false;
548
+ if (s.name === target)
549
+ return true;
550
+ if (s.path === target)
551
+ return true;
552
+ return false;
553
+ });
554
+ if (npmMatch) {
555
+ const updated = await updateManagedInstall(managedInstallViewOfPlainNpm(npmMatch), force, yes, stashDir);
556
+ return buildUpdateResponse(stashDir, target, all, [updated.item], { index: updated.index });
557
+ }
348
558
  }
349
- const selected = selectManagedTargets(config, managedInstalls, target, all);
559
+ const enabledManagedInstalls = all
560
+ ? managedInstalls.filter((managed) => config.bundles?.[managed.bundleKey]?.enabled !== false)
561
+ : managedInstalls;
562
+ const selected = selectManagedTargets(config, enabledManagedInstalls, target, all);
350
563
  const processed = [];
564
+ let latestManagedIndex;
351
565
  for (const managed of selected) {
352
- processed.push(await updateManagedInstall(managed, force));
566
+ const updated = await updateManagedInstall(managed, force, yes, stashDir);
567
+ processed.push(updated.item);
568
+ latestManagedIndex = updated.index;
569
+ }
570
+ // `--all` must account for EVERY configured source, not only the
571
+ // registry-managed (lock-backed) ones (R-015) — `selectManagedTargets`
572
+ // above returns only `installs` for `all`, so plain sources were
573
+ // previously never even looked at. Git/npm plain sources are synced here
574
+ // too (git in place; npm promoted to managed, same as the single-target
575
+ // path above); website/filesystem sources have no `--all` sync path, so
576
+ // they are reported as skipped with the same explanatory wording
577
+ // `selectManagedTargets` already uses for a single unmatched target.
578
+ let plainSynced;
579
+ let skipped;
580
+ let sawGitSync = false;
581
+ if (all) {
582
+ const managedKeys = new Set(managedInstalls.map((m) => m.bundleKey));
583
+ const plainSources = getSources(config).filter((source) => source.enabled !== false && !managedKeys.has(source.name ?? ""));
584
+ plainSynced = [];
585
+ skipped = [];
586
+ for (const plain of plainSources) {
587
+ const id = plain.name ?? plain.path ?? plain.url ?? "";
588
+ try {
589
+ if (plain.type === "git") {
590
+ plainSynced.push(await syncGitPlainSource(plain));
591
+ sawGitSync = true;
592
+ }
593
+ else if (plain.type === "npm") {
594
+ const updated = await updateManagedInstall(managedInstallViewOfPlainNpm(plain), force, yes, stashDir);
595
+ processed.push(updated.item);
596
+ latestManagedIndex = updated.index;
597
+ }
598
+ else if (plain.type === "website") {
599
+ skipped.push({
600
+ id,
601
+ kind: "website",
602
+ reason: `website caching not yet implemented for --all; run \`akm bundle update ${id}\` to re-mirror this source individually.`,
603
+ });
604
+ }
605
+ else {
606
+ skipped.push({
607
+ id,
608
+ kind: plain.type,
609
+ reason: "reflects your files in place and has no remote to sync; run `akm index` to refresh the search index.",
610
+ });
611
+ }
612
+ }
613
+ catch (err) {
614
+ skipped.push({
615
+ id,
616
+ kind: plain.type,
617
+ reason: `sync failed: ${err instanceof Error ? err.message : String(err)}`,
618
+ });
619
+ }
620
+ }
353
621
  }
354
- return buildUpdateResponse(stashDir, target, all, processed);
622
+ return buildUpdateResponse(stashDir, target, all, processed, {
623
+ full: sawGitSync,
624
+ plainSynced,
625
+ skipped,
626
+ ...(!sawGitSync && latestManagedIndex ? { index: latestManagedIndex } : {}),
627
+ });
355
628
  }
356
629
  function selectManagedTargets(config, installs, target, all) {
357
630
  if (all && target) {
@@ -381,18 +654,29 @@ function selectManagedTargets(config, installs, target, all) {
381
654
  if (stashMatch) {
382
655
  if (stashMatch.type === "website") {
383
656
  throw new UsageError(`"${target}" is a website source — website caching not yet implemented for --all. ` +
384
- `Run \`akm update ${target}\` to re-mirror this source individually.`, "TARGET_NOT_UPDATABLE");
657
+ `Run \`akm bundle update ${target}\` to re-mirror this source individually.`, "TARGET_NOT_UPDATABLE");
385
658
  }
386
659
  throw new UsageError(`"${target}" is a local directory — it reflects your files in place. To refresh the search index, run: akm index`, "TARGET_NOT_UPDATABLE");
387
660
  }
388
661
  throw new NotFoundError(`No matching source for target: ${target}`, "SOURCE_NOT_FOUND");
389
662
  }
390
- function cleanupDirectoryBestEffort(target) {
663
+ /**
664
+ * Best-effort removal of a directory that is no longer referenced by config
665
+ * or the lockfile. `context` labels the call site (e.g. "remove", "update")
666
+ * in the warning so an operator can tell which command left the directory
667
+ * behind. Failure does not throw — callers already committed the config/lock
668
+ * change that made `target` orphaned — but it must not be silent either
669
+ * (F1/R-058): a swallowed `rmSync` error used to leave no trace anywhere the
670
+ * caller could inspect, so a confirmed deletion that then failed (permission
671
+ * error, file in use, …) looked identical to a successful one.
672
+ */
673
+ function cleanupDirectoryBestEffort(target, context) {
391
674
  try {
392
675
  fs.rmSync(target, { recursive: true, force: true });
393
676
  }
394
- catch {
395
- // Best-effort cleanup only.
677
+ catch (err) {
678
+ warn(`[akm ${context}] failed to remove directory ${target}: ${err instanceof Error ? err.message : String(err)}. ` +
679
+ "Remove it manually if it is no longer needed.");
396
680
  }
397
681
  }
398
682
  function directoryExists(target) {