akm-cli 0.9.0-rc.0 → 0.9.0-rc.13

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 (598) hide show
  1. package/CHANGELOG.md +1283 -22
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -4,91 +4,199 @@
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
+ *
10
+ * 0.9.0 (spec §10.1/§10.2): the retired `installed[]` array is gone — a
11
+ * registry-managed source is now a `bundles.<slug>` entry (the desired locator)
12
+ * paired with a lock entry (the resolved `localRoot`/version). A bundle that has
13
+ * a lock entry is "managed" (installed from a registry and overwritten on
14
+ * `akm bundle update`); a bundle with no lock is a plain filesystem/git/website source.
9
15
  */
10
16
  import fs from "node:fs";
11
17
  import path from "node:path";
12
18
  import { isWithin, resolveStashDir } from "../../core/common.js";
13
19
  import { getSources, loadConfig } from "../../core/config/config.js";
14
- 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";
15
24
  import { akmIndex } from "../../indexer/indexer.js";
16
- import { removeLockEntry, upsertLockEntry } from "../../integrations/lockfile.js";
25
+ import { compareAndSwapLockfile, readLockfile } from "../../integrations/lockfile.js";
17
26
  import { parseRegistryRef } from "../../registry/resolve.js";
27
+ import { sha256Hex } from "../../runtime.js";
18
28
  import { parseGitRepoUrl, syncMirroredRepo } from "../../sources/providers/git.js";
19
29
  import { syncFromRef } from "../../sources/providers/sync-from-ref.js";
20
- import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests } from "../../sources/website-ingest.js";
21
- import { listWikis, resolveWikisRoot } from "../../wiki/wiki.js";
22
- import { removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./source-add.js";
30
+ import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests, } from "../../sources/snapshot-fetchers/website-ingest.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";
23
34
  import { removeStash } from "./source-manage.js";
35
+ /** Enumerate the registry-managed installs (lock-backed bundles) in a config. */
36
+ function listManagedInstalls(config) {
37
+ const bundles = config.bundles ?? {};
38
+ const locks = new Map(readLockfile().map((entry) => [entry.id, entry]));
39
+ const out = [];
40
+ for (const [key, bundle] of Object.entries(bundles)) {
41
+ const lock = locks.get(key);
42
+ if (!lock)
43
+ continue; // only lock-backed bundles are registry-managed
44
+ const componentWritable = Object.values(bundle.components ?? {})[0]?.writable;
45
+ out.push({
46
+ bundleKey: key,
47
+ installId: bundle.registryId ?? key,
48
+ source: lock.source,
49
+ ref: lock.ref,
50
+ localRoot: lock.localRoot ?? "",
51
+ resolvedVersion: lock.resolvedVersion,
52
+ resolvedRevision: lock.resolvedRevision,
53
+ writable: componentWritable ?? bundle.writable === true,
54
+ requiredRoots: lock.localRoot
55
+ ? Object.values(bundle.components ?? {}).map((component) => path.resolve(lock.localRoot, component.root ?? "."))
56
+ : [],
57
+ });
58
+ }
59
+ return out;
60
+ }
61
+ /** Resolve an `akm bundle remove`/`akm bundle update` target to a managed install, if any. */
62
+ function resolveManagedTarget(config, target) {
63
+ const installs = listManagedInstalls(config);
64
+ const byId = installs.find((m) => m.installId === target || m.bundleKey === target);
65
+ if (byId)
66
+ return byId;
67
+ const byRef = installs.find((m) => m.ref === target);
68
+ if (byRef)
69
+ return byRef;
70
+ const isUrl = target.startsWith("http://") || target.startsWith("https://");
71
+ if (!isUrl) {
72
+ const resolved = path.resolve(target);
73
+ const byPath = installs.find((m) => m.localRoot && path.resolve(m.localRoot) === resolved);
74
+ if (byPath)
75
+ return byPath;
76
+ }
77
+ let parsedId;
78
+ try {
79
+ parsedId = parseRegistryRef(target).id;
80
+ }
81
+ catch {
82
+ parsedId = undefined;
83
+ }
84
+ if (parsedId)
85
+ return installs.find((m) => m.installId === parsedId);
86
+ return undefined;
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
+ }
24
159
  export async function akmListSources(input) {
25
160
  const stashDir = input?.stashDir ?? resolveStashDir();
26
161
  const config = loadConfig();
27
162
  const kindFilter = input?.kind;
163
+ const locks = new Map(readLockfile().map((entry) => [entry.id, entry]));
164
+ const counts = readBundleCounts();
28
165
  const sources = [];
29
- // Stash entries — each entry exposes its provider type as kind (spec §2.1).
30
- // Writable defaults: true for filesystem, false for git/npm/website (CLAUDE.md "Writes").
31
- for (const stash of getSources(config)) {
32
- const kind = stash.type ?? "filesystem";
33
- if (kindFilter && !kindFilter.includes(kind))
166
+ for (const bundle of getSources(config)) {
167
+ const key = bundle.name ?? bundle.path ?? bundle.url ?? "unknown";
168
+ const configured = config.bundles?.[key];
169
+ if (!configured)
34
170
  continue;
35
- const isFilesystem = kind === "filesystem";
36
- const writableDefault = isFilesystem;
37
- const name = stash.name ?? stash.path ?? stash.url ?? "unknown";
38
- sources.push({
39
- name,
40
- kind,
41
- wiki: stash.wikiName,
42
- path: stash.path,
43
- provider: stash.url != null ? stash.type : undefined,
44
- writable: stash.writable !== undefined ? stash.writable : writableDefault,
45
- status: { exists: stash.path ? directoryExists(stash.path) : true },
46
- });
47
- }
48
- // Installed entries → managed sources
49
- for (const entry of config.installed ?? []) {
50
- const kind = "managed";
171
+ const lock = locks.get(key);
172
+ const kind = bundle.type;
51
173
  if (kindFilter && !kindFilter.includes(kind))
52
174
  continue;
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: {} };
53
178
  sources.push({
54
- name: entry.id,
179
+ name: key,
55
180
  kind,
56
- wiki: entry.wikiName,
57
- path: entry.stashRoot,
58
- ref: entry.ref,
59
- version: entry.resolvedVersion,
60
- writable: entry.writable === true,
61
- status: { exists: directoryExists(entry.stashRoot) },
181
+ default: key === config.defaultBundle,
182
+ source: describeBundleSource(configured),
183
+ ...(root ? { path: root } : {}),
184
+ ...(lock ? { ref: lock.ref } : {}),
185
+ provider: bundle.url != null ? bundle.type : undefined,
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 },
62
194
  });
63
195
  }
64
- if (!kindFilter || kindFilter.includes("filesystem")) {
65
- const wikisRoot = resolveWikisRoot(stashDir);
66
- const seenPaths = new Set(sources
67
- .map((source) => source.path)
68
- .filter((sourcePath) => typeof sourcePath === "string")
69
- .map((sourcePath) => path.resolve(sourcePath)));
70
- for (const wiki of listWikis(stashDir)) {
71
- // `listWikis()` also includes externally-registered wikis. `akm list`
72
- // should synthesize source entries here only for stash-owned wiki dirs.
73
- if (!isWithin(wiki.path, wikisRoot))
74
- continue;
75
- const resolvedPath = path.resolve(wiki.path);
76
- if (seenPaths.has(resolvedPath))
77
- continue;
78
- seenPaths.add(resolvedPath);
79
- sources.push({
80
- name: wiki.name,
81
- kind: "filesystem",
82
- wiki: wiki.name,
83
- path: wiki.path,
84
- writable: true,
85
- status: { exists: directoryExists(wiki.path) },
86
- });
87
- }
88
- }
89
196
  return {
90
197
  schemaVersion: 1,
91
- stashDir,
198
+ bundleDir: stashDir,
199
+ defaultBundle: config.defaultBundle ?? null,
92
200
  sources,
93
201
  totalSources: sources.length,
94
202
  };
@@ -96,33 +204,30 @@ export async function akmListSources(input) {
96
204
  export async function akmRemove(input) {
97
205
  const target = input.target.trim();
98
206
  if (!target)
99
- 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`).");
100
208
  const stashDir = input.stashDir ?? resolveStashDir();
101
209
  const config = loadConfig();
102
- const installed = config.installed ?? [];
103
- // Try installed[] first (managed sources)
104
- const entry = tryResolveInstalledTarget(installed, target);
105
- if (entry) {
106
- const updatedConfig = removeInstalledRegistryEntry(entry.id);
107
- await removeLockEntry(entry.id);
108
- if (entry.source !== "local") {
109
- cleanupDirectoryBestEffort(entry.cacheDir);
210
+ // Registry-managed installs (lock-backed bundles) first.
211
+ const managed = resolveManagedTarget(config, target);
212
+ if (managed) {
213
+ const updatedConfig = await removeInstalledRegistryEntry(managed.installId);
214
+ if (managed.source !== "local" && managed.localRoot) {
215
+ cleanupDirectoryBestEffort(managed.localRoot, "remove");
110
216
  }
111
217
  const index = await akmIndex({ stashDir });
112
218
  return {
113
219
  schemaVersion: 1,
114
- stashDir,
220
+ bundleDir: stashDir,
115
221
  target,
116
222
  removed: {
117
- id: entry.id,
118
- source: entry.source,
119
- ref: entry.ref,
120
- cacheDir: entry.cacheDir,
121
- stashRoot: entry.stashRoot,
223
+ id: managed.installId,
224
+ source: managed.source,
225
+ ref: managed.ref,
226
+ cacheDir: managed.localRoot,
227
+ stashRoot: managed.localRoot,
122
228
  },
123
229
  config: {
124
230
  sourceCount: getSources(updatedConfig).length,
125
- installedKitCount: updatedConfig.installed?.length ?? 0,
126
231
  },
127
232
  index: {
128
233
  mode: index.mode,
@@ -132,7 +237,7 @@ export async function akmRemove(input) {
132
237
  },
133
238
  };
134
239
  }
135
- // Fall through to stashes[] (local/remote sources)
240
+ // Plain sources (filesystem/git/website bundles) via the bundle-map remover.
136
241
  const stashResult = removeStash(target);
137
242
  if (!stashResult.removed || !stashResult.entry) {
138
243
  throw new NotFoundError(`No matching source for target: ${target}`, "SOURCE_NOT_FOUND");
@@ -142,7 +247,7 @@ export async function akmRemove(input) {
142
247
  const updatedConfig = loadConfig();
143
248
  return {
144
249
  schemaVersion: 1,
145
- stashDir,
250
+ bundleDir: stashDir,
146
251
  target,
147
252
  removed: {
148
253
  id: removedEntry.name ?? removedEntry.path ?? removedEntry.url ?? target,
@@ -153,7 +258,6 @@ export async function akmRemove(input) {
153
258
  },
154
259
  config: {
155
260
  sourceCount: getSources(updatedConfig).length,
156
- installedKitCount: updatedConfig.installed?.length ?? 0,
157
261
  },
158
262
  index: {
159
263
  mode: index.mode,
@@ -165,18 +269,19 @@ export async function akmRemove(input) {
165
269
  }
166
270
  // ── akmUpdate helpers ────────────────────────────────────────────────────────
167
271
  /** Build a standard UpdateResponse summary block from the current config and index run. */
168
- async function buildUpdateResponse(stashDir, target, all, processed, full = false) {
169
- 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 } : {}) }));
170
274
  const finalConfig = loadConfig();
171
275
  return {
172
276
  schemaVersion: 1,
173
- stashDir,
277
+ bundleDir: stashDir,
174
278
  target,
175
279
  all,
176
280
  processed,
281
+ ...(opts?.plainSynced?.length ? { plainSynced: opts.plainSynced } : {}),
282
+ ...(opts?.skipped?.length ? { skipped: opts.skipped } : {}),
177
283
  config: {
178
284
  sourceCount: getSources(finalConfig).length,
179
- installedKitCount: finalConfig.installed?.length ?? 0,
180
285
  },
181
286
  index: {
182
287
  mode: index.mode,
@@ -186,34 +291,93 @@ async function buildUpdateResponse(stashDir, target, all, processed, full = fals
186
291
  },
187
292
  };
188
293
  }
189
- /** Sync a git-mirrored source and return an UpdateResponse. */
190
- 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) {
191
300
  await syncMirroredRepo(gitSource, { force: true, writable: gitSource.writable === true });
192
- return buildUpdateResponse(stashDir, target, all, [], true);
301
+ return { id: gitSource.name ?? gitSource.url ?? "", kind: "git", ref: gitSource.url ?? "" };
193
302
  }
194
- /** Re-crawl a website source and return an UpdateResponse. */
195
- async function updateWebsiteSource(stashDir, target, all, websiteSource) {
303
+ /** Re-crawl a website (plain) source in place. See {@link syncGitPlainSource}. */
304
+ async function syncWebsitePlainSource(websiteSource) {
196
305
  // TODO: full incremental re-crawl with delta tracking (#19)
197
306
  await ensureWebsiteMirror(websiteSource, {
198
307
  requireStashDir: true,
199
308
  force: true,
200
309
  ...(shouldAllowPrivateWebsiteUrlForTests(websiteSource.url ?? "") ? { allowPrivateHosts: true } : {}),
201
310
  });
202
- return buildUpdateResponse(stashDir, target, all, []);
311
+ return { id: websiteSource.name ?? websiteSource.url ?? "", kind: "website", ref: websiteSource.url ?? "" };
203
312
  }
204
- /** Sync a single installed registry entry and return the processed record. */
205
- async function updateRegistryEntry(entry, force) {
206
- if (force && shouldCleanupCache(entry)) {
207
- cleanupDirectoryBestEffort(entry.cacheDir);
208
- }
209
- const synced = await syncFromRef(entry.ref, { force });
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] });
322
+ }
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) {
363
+ // No pre-cleanup of the old root, even under --force: the providers already
364
+ // re-materialize staging-first (git clones into a `.tmp-*` sibling and swaps
365
+ // only on success), so destroying `managed.localRoot` BEFORE `syncFromRef`
366
+ // succeeds would turn any sync failure (network down, bad ref) into losing a
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
+ });
210
375
  const installedEntry = {
211
- id: synced.id,
376
+ id: managed.installId,
212
377
  // Preserve the original source classification. syncFromRef() re-derives the
213
378
  // source type from the ref scheme (e.g. "github:" → source: "github"), but
214
- // an update should not reclassify an existing entry. A writable entry stored
215
- // as source: "git" would fail config validation if rewritten to "github".
216
- source: entry.source,
379
+ // an update should not reclassify an existing entry.
380
+ source: managed.source,
217
381
  ref: synced.ref,
218
382
  artifactUrl: synced.artifactUrl,
219
383
  resolvedVersion: synced.resolvedVersion,
@@ -221,39 +385,102 @@ async function updateRegistryEntry(entry, force) {
221
385
  stashRoot: synced.contentDir,
222
386
  cacheDir: synced.cacheDir,
223
387
  installedAt: synced.syncedAt,
224
- writable: synced.writable ?? entry.writable,
225
- ...(entry.wikiName ? { wikiName: entry.wikiName } : {}),
388
+ writable: synced.writable ?? managed.writable,
226
389
  };
227
- upsertInstalledRegistryEntry(installedEntry);
228
- await upsertLockEntry({
229
- id: synced.id,
230
- source: synced.source,
231
- ref: synced.ref,
232
- resolvedVersion: synced.resolvedVersion,
233
- resolvedRevision: synced.resolvedRevision,
234
- integrity: synced.integrity ?? (synced.source === "local" ? "local" : undefined),
390
+ const movedRoot = managed.localRoot !== "" &&
391
+ path.resolve(managed.localRoot) !== path.resolve(synced.contentDir) &&
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.`);
399
+ }
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
+ };
235
456
  });
236
- if (entry.cacheDir !== synced.cacheDir && shouldCleanupCache(entry)) {
237
- cleanupDirectoryBestEffort(entry.cacheDir);
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
+ }
238
474
  }
239
- const versionChanged = (entry.resolvedVersion ?? "") !== (synced.resolvedVersion ?? "");
240
- const revisionChanged = (entry.resolvedRevision ?? "") !== (synced.resolvedRevision ?? "");
241
- return {
242
- id: entry.id,
243
- source: entry.source,
244
- ref: entry.ref,
245
- previous: {
246
- resolvedVersion: entry.resolvedVersion,
247
- resolvedRevision: entry.resolvedRevision,
248
- cacheDir: entry.cacheDir,
249
- },
250
- installed: { ...installedEntry, extractedDir: synced.extractedDir },
251
- changed: {
252
- version: versionChanged,
253
- revision: revisionChanged,
254
- any: versionChanged || revisionChanged,
255
- },
256
- };
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.");
257
484
  }
258
485
  // ── akmUpdate dispatcher ─────────────────────────────────────────────────────
259
486
  export async function akmUpdate(input) {
@@ -261,11 +488,17 @@ export async function akmUpdate(input) {
261
488
  const target = input?.target?.trim();
262
489
  const all = input?.all === true;
263
490
  const force = input?.force === true;
491
+ const yes = input?.yes === true;
264
492
  const config = loadConfig();
265
- const installedEntries = config.installed ?? [];
266
- // Check if the target refers to a git or website source — those are stored
267
- // in sources[] not installed[] and need a different update path.
493
+ const managedInstalls = listManagedInstalls(config);
268
494
  if (target && !all) {
495
+ // Registry-managed install (lock-backed) — re-download from its locator.
496
+ const managed = resolveManagedTarget(config, target);
497
+ if (managed) {
498
+ const updated = await updateManagedInstall(managed, force, yes, stashDir);
499
+ return buildUpdateResponse(stashDir, target, all, [updated.item], { index: updated.index });
500
+ }
501
+ // Plain git / website source (bundles without a lock) — provider re-sync.
269
502
  const stashes = getSources(config);
270
503
  const isUrl = target.startsWith("http://") || target.startsWith("https://");
271
504
  const resolvedPath = !isUrl ? path.resolve(target) : undefined;
@@ -305,28 +538,107 @@ export async function akmUpdate(input) {
305
538
  });
306
539
  if (websiteMatch)
307
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
+ }
308
558
  }
309
- const selectedEntries = selectTargets(installedEntries, 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);
310
563
  const processed = [];
311
- for (const entry of selectedEntries) {
312
- processed.push(await updateRegistryEntry(entry, force));
564
+ let latestManagedIndex;
565
+ for (const managed of selected) {
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
+ }
313
621
  }
314
- 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
+ });
315
628
  }
316
- function selectTargets(installed, target, all) {
629
+ function selectManagedTargets(config, installs, target, all) {
317
630
  if (all && target) {
318
631
  throw new UsageError("Specify either <target> or --all, not both.", "MISSING_OR_AMBIGUOUS_TARGET");
319
632
  }
320
633
  if (all)
321
- return installed;
634
+ return installs;
322
635
  if (!target) {
323
636
  throw new UsageError("Either <target> or --all is required.", "MISSING_OR_AMBIGUOUS_TARGET");
324
637
  }
325
- const found = tryResolveInstalledTarget(installed, target);
638
+ const found = resolveManagedTarget(config, target);
326
639
  if (found)
327
640
  return [found];
328
- // Check if target matches a stash source and give a helpful message
329
- const config = loadConfig();
641
+ // Give a helpful message when the target names a plain (non-managed) source.
330
642
  const stashes = getSources(config);
331
643
  const isUrl = target.startsWith("http://") || target.startsWith("https://");
332
644
  const resolvedPath = !isUrl ? path.resolve(target) : undefined;
@@ -341,47 +653,32 @@ function selectTargets(installed, target, all) {
341
653
  });
342
654
  if (stashMatch) {
343
655
  if (stashMatch.type === "website") {
344
- // Website sources should be handled before reaching selectTargets.
345
- // This path should not be reached; surface a clear message if it is.
346
656
  throw new UsageError(`"${target}" is a website source — website caching not yet implemented for --all. ` +
347
- `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");
348
658
  }
349
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");
350
660
  }
351
661
  throw new NotFoundError(`No matching source for target: ${target}`, "SOURCE_NOT_FOUND");
352
662
  }
353
- function tryResolveInstalledTarget(installed, target) {
354
- const byId = installed.find((entry) => entry.id === target);
355
- if (byId)
356
- return byId;
357
- const byRef = installed.find((entry) => entry.ref === target);
358
- if (byRef)
359
- return byRef;
360
- let parsedId;
361
- try {
362
- parsedId = parseRegistryRef(target).id;
363
- }
364
- catch {
365
- parsedId = undefined;
366
- }
367
- if (parsedId) {
368
- const byParsedId = installed.find((entry) => entry.id === parsedId);
369
- if (byParsedId)
370
- return byParsedId;
371
- }
372
- return undefined;
373
- }
374
- 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) {
375
674
  try {
376
675
  fs.rmSync(target, { recursive: true, force: true });
377
676
  }
378
- catch {
379
- // 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.");
380
680
  }
381
681
  }
382
- function shouldCleanupCache(entry) {
383
- return entry.source !== "local";
384
- }
385
682
  function directoryExists(target) {
386
683
  try {
387
684
  return fs.statSync(target).isDirectory();