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
@@ -1,7 +1,17 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { getSources } from "../core/config/config.js";
4
+ /**
5
+ * Source provider factory map.
6
+ *
7
+ * Maps source kind identifiers (e.g. "filesystem", "git", "website", "npm")
8
+ * to factory functions that build {@link SourceProvider} instances from a
9
+ * {@link SourceConfigEntry}.
10
+ *
11
+ * Distinct from the registry-discovery factory (`registry/factory.ts`).
12
+ * Both share `create-provider-registry.ts` for the underlying string→factory
13
+ * map.
14
+ */
5
15
  import { createProviderRegistry } from "../registry/create-provider-registry.js";
6
16
  // ── Factory map ─────────────────────────────────────────────────────────────
7
17
  const registry = createProviderRegistry();
@@ -11,19 +21,3 @@ export function registerSourceProvider(type, factory) {
11
21
  export function resolveSourceProviderFactory(type) {
12
22
  return registry.resolve(type);
13
23
  }
14
- /**
15
- * Build a {@link SourceProvider} for every enabled source in the config that
16
- * has a registered factory.
17
- */
18
- export function resolveSourceProviders(config) {
19
- const providers = [];
20
- for (const entry of getSources(config)) {
21
- if (entry.enabled === false)
22
- continue;
23
- const factory = registry.resolve(entry.type);
24
- if (factory) {
25
- providers.push(factory(entry));
26
- }
27
- }
28
- return providers;
29
- }
@@ -7,9 +7,8 @@ import { registerSourceProvider } from "../provider-factory.js";
7
7
  /**
8
8
  * Filesystem source — points at a directory the user already manages.
9
9
  *
10
- * Implements the v1 {@link SourceProvider} interface (spec §2.1, §2.4):
11
- * just `{ name, kind, init, path }`. No `sync()` — content is the user's
12
- * own directory, never refreshed by akm.
10
+ * Implements {@link SourceProvider} with `{ name, kind, path }`. No `sync()`:
11
+ * content is the user's own directory, never refreshed by akm.
13
12
  */
14
13
  registerSourceProvider("filesystem", (entry) => {
15
14
  if (entry.type !== "filesystem") {
@@ -5,10 +5,11 @@ import { spawnSync } from "node:child_process";
5
5
  import { randomBytes } from "node:crypto";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
+ import { isWithin } from "../../core/common.js";
8
9
  import { UsageError } from "../../core/errors.js";
9
10
  import { getRegistryCacheDir } from "../../core/paths.js";
10
11
  import { parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl } from "../../registry/resolve.js";
11
- import { applyAkmIncludeConfig, buildInstallCacheDir, copyDirectoryContents, detectStashRoot, isDirectory, } from "./provider-utils.js";
12
+ import { applyAkmIncludeConfig, buildInstallCacheDir, detectStashRoot, isDirectory } from "./provider-utils.js";
12
13
  /**
13
14
  * Shared subprocess wrapper for `git` invocations. Disables git's interactive
14
15
  * terminal prompt so a missing credential never hangs the process.
@@ -20,12 +21,109 @@ export function runGit(args, options) {
20
21
  env: { ...process.env, ...options?.env, GIT_TERMINAL_PROMPT: "0" },
21
22
  });
22
23
  }
24
+ /** Fetch and classify the current branch against its upstream without changing the worktree. */
25
+ export function inspectGitUpstream(repoDir) {
26
+ const remotes = runGit(["-C", repoDir, "remote"]);
27
+ if (remotes.status !== 0)
28
+ throw new UsageError(`Cannot inspect Git remotes at ${repoDir}: ${remotes.stderr.trim()}`);
29
+ if (!remotes.stdout.trim())
30
+ return { hasRemote: false, ahead: 0, behind: 0 };
31
+ const fetch = runGit(["-C", repoDir, "fetch", "--prune"], { timeout: 120_000 });
32
+ if (fetch.status !== 0)
33
+ throw new UsageError(`Cannot refresh Git target at ${repoDir}: ${fetch.stderr.trim()}`);
34
+ const upstream = runGit(["-C", repoDir, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
35
+ if (upstream.status !== 0 || !upstream.stdout.trim()) {
36
+ throw new UsageError(`Git target at ${repoDir} has a remote but no upstream branch; configure one before writing.`);
37
+ }
38
+ const relation = gitRelation(repoDir, upstream.stdout.trim());
39
+ return { hasRemote: true, upstream: upstream.stdout.trim(), ...relation };
40
+ }
23
41
  /**
24
- * Sync mode for a one-shot install ref (`akm add github:owner/repo` or
25
- * `akm add git:url`). Runs the clone → strip → include-filter pipeline that
26
- * historically lived in `installRegistryRef()`.
42
+ * Verify the actually-cloned HEAD matches the revision resolved before the
43
+ * clone ran (R-011). This is a presence/identity check only — full
44
+ * content-digest verification across install/update is separate, larger
45
+ * scope work. `expectedRevision` may be an annotated tag's OBJECT id (which
46
+ * `git ls-remote` reports, distinct from the commit it points to), so the
47
+ * comparison peels it (`^{commit}`) before comparing against HEAD; a plain
48
+ * commit SHA peels to itself.
27
49
  */
28
- export async function syncRegistryGitRef(ref, options) {
50
+ export function verifyClonedRevision(cloneDir, url, expectedRevision) {
51
+ if (!expectedRevision)
52
+ return;
53
+ const head = runGit(["-C", cloneDir, "rev-parse", "HEAD"]);
54
+ if (head.status !== 0 || !head.stdout.trim()) {
55
+ throw new UsageError(`Failed to read cloned HEAD at ${cloneDir}: ${head.stderr.trim() || "rev-parse failed"}`);
56
+ }
57
+ const actual = head.stdout.trim();
58
+ const peeled = runGit(["-C", cloneDir, "rev-parse", `${expectedRevision}^{commit}`]);
59
+ const expectedCommit = peeled.status === 0 ? peeled.stdout.trim() : expectedRevision;
60
+ if (actual !== expectedCommit) {
61
+ throw new UsageError(`Cloned HEAD ${actual} at ${cloneDir} does not match the revision resolved from ${url} (${expectedRevision}); refusing to install a mismatched checkout.`);
62
+ }
63
+ }
64
+ function gitRelation(repoDir, target) {
65
+ const result = runGit(["-C", repoDir, "rev-list", "--left-right", "--count", `HEAD...${target}`]);
66
+ const match = result.stdout.trim().match(/^(\d+)\s+(\d+)$/);
67
+ if (result.status !== 0 || !match) {
68
+ throw new UsageError(`Cannot compare Git history at ${repoDir}: ${result.stderr.trim() || "unknown relation"}`);
69
+ }
70
+ return { ahead: Number(match[1]), behind: Number(match[2]) };
71
+ }
72
+ /** Reject a fast-forward that would replace an ignored, untracked local path. */
73
+ export function assertNoIgnoredPathOverwrite(repoDir, targetRevision) {
74
+ const ignored = runGit(["-C", repoDir, "ls-files", "--others", "--ignored", "--exclude-standard", "-z"]);
75
+ if (ignored.status !== 0) {
76
+ throw new UsageError(`Cannot inspect ignored files at ${repoDir}: ${ignored.stderr.trim()}`);
77
+ }
78
+ const ignoredPaths = ignored.stdout.split("\0").filter(Boolean);
79
+ if (ignoredPaths.length === 0)
80
+ return;
81
+ const changed = runGit(["-C", repoDir, "diff", "--name-only", "--no-renames", "-z", "HEAD", targetRevision]);
82
+ if (changed.status !== 0) {
83
+ throw new UsageError(`Cannot inspect incoming Git paths at ${repoDir}: ${changed.stderr.trim()}`);
84
+ }
85
+ const changedPaths = changed.stdout.split("\0").filter(Boolean);
86
+ const conflict = ignoredPaths.find((ignoredPath) => changedPaths.some((changedPath) => changedPath === ignoredPath ||
87
+ changedPath.startsWith(`${ignoredPath}/`) ||
88
+ ignoredPath.startsWith(`${changedPath}/`)));
89
+ if (conflict) {
90
+ throw new UsageError(`Git update would overwrite ignored local path ${path.join(repoDir, conflict)}; move or remove it before update.`);
91
+ }
92
+ }
93
+ function normalizeRemoteUrl(value) {
94
+ return value
95
+ .trim()
96
+ .replace(/\/+$/, "")
97
+ .replace(/\.git$/i, "");
98
+ }
99
+ function replaceDirectory(stagedDir, destination) {
100
+ const backup = `${destination}.backup-${randomBytes(4).toString("hex")}`;
101
+ const hadDestination = fs.existsSync(destination);
102
+ if (hadDestination)
103
+ fs.renameSync(destination, backup);
104
+ try {
105
+ fs.renameSync(stagedDir, destination);
106
+ }
107
+ catch (error) {
108
+ if (hadDestination && fs.existsSync(backup))
109
+ fs.renameSync(backup, destination);
110
+ throw error;
111
+ }
112
+ if (hadDestination) {
113
+ try {
114
+ fs.rmSync(backup, { recursive: true, force: true });
115
+ }
116
+ catch {
117
+ // The new destination is live; a stale backup is safer than reporting a
118
+ // failed replacement after the swap already succeeded.
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Materialize a Git install ref (`akm bundle add github:owner/repo` or
124
+ * `akm bundle add git:url`) through the clone, strip, and include-filter pipeline.
125
+ */
126
+ export async function syncGitRef(ref, options) {
29
127
  const parsed = parseRegistryRef(ref);
30
128
  if (parsed.source === "github") {
31
129
  const githubRef = {
@@ -39,22 +137,40 @@ export async function syncRegistryGitRef(ref, options) {
39
137
  return { ...result, source: "github" };
40
138
  }
41
139
  if (parsed.source !== "git") {
42
- throw new UsageError(`syncRegistryGitRef requires a git: or github: ref, got "${ref}"`);
140
+ throw new UsageError(`syncGitRef requires a git: or github: ref, got "${ref}"`);
43
141
  }
44
142
  return doSyncGit(parsed, options);
45
143
  }
46
144
  async function doSyncGit(parsed, options) {
145
+ validateGitUrl(parsed.url);
146
+ if (parsed.requestedRef)
147
+ validateGitRef(parsed.requestedRef);
47
148
  const resolved = await resolveRegistryArtifact(parsed);
48
149
  const syncedAt = (options?.now ?? new Date()).toISOString();
150
+ if (options?.writable && options.writableRoot) {
151
+ return syncExistingWritableCheckout(parsed, resolved, options.writableRoot, syncedAt, options.writableRequiredRoots);
152
+ }
49
153
  const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheDir();
50
- const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id, resolved.resolvedRevision);
154
+ const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id, options?.writable ? "writable" : resolved.resolvedRevision);
51
155
  const cloneDir = path.join(cacheDir, "clone");
52
156
  const extractedDir = path.join(cacheDir, "extracted");
53
- // Cache hit
54
- if (!options?.force && isDirectory(extractedDir)) {
157
+ // Cache hit. Writable installs must remain real checkouts so every mutation
158
+ // can be committed; an older extracted snapshot is not eligible.
159
+ if (isDirectory(extractedDir) && (!options?.writable || isDirectory(path.join(extractedDir, ".git")))) {
160
+ if (options?.writable) {
161
+ const provisionalBundleRoot = detectStashRoot(extractedDir);
162
+ const installRoot = applyAkmIncludeConfig(provisionalBundleRoot, cacheDir, extractedDir) ?? provisionalBundleRoot;
163
+ if (installRoot !== provisionalBundleRoot) {
164
+ throw new UsageError("Writable Git installs do not support akm.include (package.json) filtered snapshots.");
165
+ }
166
+ return syncExistingWritableCheckout(parsed, resolved, detectStashRoot(installRoot), syncedAt, options.writableRequiredRoots);
167
+ }
55
168
  try {
56
- const provisionalKitRoot = detectStashRoot(extractedDir);
57
- const installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
169
+ if (options?.force) {
170
+ throw new Error("refresh read-only snapshot");
171
+ }
172
+ const provisionalBundleRoot = detectStashRoot(extractedDir);
173
+ const installRoot = applyAkmIncludeConfig(provisionalBundleRoot, cacheDir, extractedDir) ?? provisionalBundleRoot;
58
174
  const stashRoot = detectStashRoot(installRoot);
59
175
  if (stashRoot) {
60
176
  return {
@@ -72,20 +188,20 @@ async function doSyncGit(parsed, options) {
72
188
  };
73
189
  }
74
190
  }
75
- catch {
191
+ catch (error) {
76
192
  // Cache invalid, re-clone
77
193
  }
78
194
  }
195
+ const cacheExisted = fs.existsSync(cacheDir);
79
196
  fs.mkdirSync(cacheDir, { recursive: true });
80
- // Validate URL and ref before passing to git to prevent command injection
81
- validateGitUrl(parsed.url);
82
- if (parsed.requestedRef)
83
- validateGitRef(parsed.requestedRef);
84
- let provisionalKitRoot;
197
+ fs.rmSync(cloneDir, { recursive: true, force: true });
198
+ let provisionalBundleRoot;
85
199
  let installRoot;
86
200
  let stashRoot;
87
201
  try {
88
- const cloneArgs = ["clone", "--depth", "1"];
202
+ const cloneArgs = ["clone"];
203
+ if (!options?.writable)
204
+ cloneArgs.push("--depth", "1");
89
205
  if (parsed.requestedRef) {
90
206
  cloneArgs.push("--branch", parsed.requestedRef);
91
207
  }
@@ -94,20 +210,40 @@ async function doSyncGit(parsed, options) {
94
210
  if (cloneResult.status !== 0) {
95
211
  throw new Error(classifyCloneFailure(parsed.url, cloneResult.stderr, cloneResult.error));
96
212
  }
97
- // Copy contents to extracted dir without .git
98
- fs.mkdirSync(extractedDir, { recursive: true });
99
- copyDirectoryContents(cloneDir, extractedDir);
100
- // Clean up the clone dir
101
- fs.rmSync(cloneDir, { recursive: true, force: true });
102
- provisionalKitRoot = detectStashRoot(extractedDir);
103
- installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
213
+ // R-011: `resolved.resolvedRevision` was resolved via a SEPARATE
214
+ // `git ls-remote` round-trip before this clone ran (resolveGitArtifact /
215
+ // resolveGithubArtifact in registry/resolve.ts) and was never checked
216
+ // against what actually got cloned. Verify it now, while `.git` still
217
+ // exists (the read-only branch below strips it).
218
+ verifyClonedRevision(cloneDir, parsed.url, resolved.resolvedRevision);
219
+ if (options?.writable) {
220
+ const branch = runGit(["-C", cloneDir, "branch", "--show-current"]);
221
+ if (branch.status !== 0 || !branch.stdout.trim()) {
222
+ throw new UsageError("Writable Git installs require a branch ref; tags and detached revisions are read-only.");
223
+ }
224
+ const stagedRoot = detectStashRoot(cloneDir);
225
+ if (applyAkmIncludeConfig(stagedRoot, cacheDir, cloneDir)) {
226
+ throw new UsageError("Writable Git installs do not support akm.include (package.json) filtered snapshots.");
227
+ }
228
+ replaceDirectory(cloneDir, extractedDir);
229
+ }
230
+ else {
231
+ // Read-only installs are immutable snapshots and do not retain Git metadata.
232
+ fs.rmSync(path.join(cloneDir, ".git"), { recursive: true, force: true });
233
+ replaceDirectory(cloneDir, extractedDir);
234
+ }
235
+ provisionalBundleRoot = detectStashRoot(extractedDir);
236
+ installRoot = applyAkmIncludeConfig(provisionalBundleRoot, cacheDir, extractedDir) ?? provisionalBundleRoot;
237
+ if (options?.writable && installRoot !== provisionalBundleRoot) {
238
+ throw new UsageError("Writable Git installs do not support akm.include (package.json) filtered snapshots.");
239
+ }
104
240
  stashRoot = detectStashRoot(installRoot);
105
241
  }
106
242
  catch (err) {
107
- // Clean up the cache directory so stale or partially-cloned artifacts
108
- // don't cause false cache hits on the next install attempt.
243
+ fs.rmSync(cloneDir, { recursive: true, force: true });
109
244
  try {
110
- fs.rmSync(cacheDir, { recursive: true, force: true });
245
+ if (!cacheExisted)
246
+ fs.rmSync(cacheDir, { recursive: true, force: true });
111
247
  }
112
248
  catch {
113
249
  /* best-effort */
@@ -128,12 +264,123 @@ async function doSyncGit(parsed, options) {
128
264
  syncedAt,
129
265
  };
130
266
  }
267
+ export function syncExistingWritableCheckout(parsed, resolved, contentRoot, syncedAt, requiredRoots = []) {
268
+ const root = path.resolve(contentRoot);
269
+ const repoResult = runGit(["-C", root, "rev-parse", "--show-toplevel"]);
270
+ if (repoResult.status !== 0 || !repoResult.stdout.trim()) {
271
+ throw new UsageError(`Writable Git install at ${root} is not a checkout; refusing to replace it because it may contain local work.`);
272
+ }
273
+ const repoDir = path.resolve(repoResult.stdout.trim());
274
+ if (!isWithin(root, repoDir)) {
275
+ throw new UsageError(`Writable Git content root ${root} resolves outside its checkout at ${repoDir}.`);
276
+ }
277
+ const remote = runGit(["-C", repoDir, "remote", "get-url", "origin"]);
278
+ if (remote.status !== 0 || normalizeRemoteUrl(remote.stdout) !== normalizeRemoteUrl(parsed.url)) {
279
+ throw new UsageError(`Writable Git install at ${root} points at a different origin; refusing to update or replace the checkout.`);
280
+ }
281
+ const status = runGit(["-C", repoDir, "status", "--porcelain"]);
282
+ if (status.status !== 0 || status.stdout.trim()) {
283
+ throw new UsageError(`Writable Git install at ${root} has uncommitted changes; commit or discard them before update.`);
284
+ }
285
+ if (parsed.requestedRef) {
286
+ const branch = runGit(["-C", repoDir, "branch", "--show-current"]);
287
+ const expectedBranch = parsed.requestedRef.replace(/^refs\/heads\//, "");
288
+ if (branch.status !== 0 || !branch.stdout.trim() || branch.stdout.trim() !== expectedBranch) {
289
+ throw new UsageError(`Writable Git install at ${root} is checked out on a different branch than requested ref "${parsed.requestedRef}".`);
290
+ }
291
+ }
292
+ const shallow = runGit(["-C", repoDir, "rev-parse", "--is-shallow-repository"]);
293
+ const fetchArgs = ["-C", repoDir, "fetch", "--prune", "--tags"];
294
+ if (shallow.status === 0 && shallow.stdout.trim() === "true")
295
+ fetchArgs.push("--unshallow");
296
+ fetchArgs.push("origin");
297
+ if (parsed.requestedRef)
298
+ fetchArgs.push(parsed.requestedRef);
299
+ const fetch = runGit(fetchArgs, { timeout: 120_000 });
300
+ if (fetch.status !== 0) {
301
+ throw new UsageError(`Writable Git install at ${root} could not fetch its expected origin; local work was preserved. ${fetch.stderr.trim()}`);
302
+ }
303
+ let targetRevision = resolved.resolvedRevision;
304
+ if (targetRevision) {
305
+ const resolvedTarget = runGit(["-C", repoDir, "rev-parse", "--verify", `${targetRevision}^{commit}`]);
306
+ if (resolvedTarget.status === 0)
307
+ targetRevision = resolvedTarget.stdout.trim();
308
+ else
309
+ targetRevision = undefined;
310
+ }
311
+ if (!targetRevision) {
312
+ const fallbackTarget = runGit([
313
+ "-C",
314
+ repoDir,
315
+ "rev-parse",
316
+ "--verify",
317
+ parsed.requestedRef ? "FETCH_HEAD" : "@{u}",
318
+ ]);
319
+ if (fallbackTarget.status !== 0 || !fallbackTarget.stdout.trim()) {
320
+ throw new UsageError(`Writable Git install at ${root} has no verifiable upstream revision.`);
321
+ }
322
+ targetRevision = fallbackTarget.stdout.trim();
323
+ }
324
+ const relation = gitRelation(repoDir, targetRevision);
325
+ if (relation.ahead > 0) {
326
+ throw new UsageError(`Writable Git install at ${root} has local commits that are not in the requested upstream revision; push or reconcile them before update.`);
327
+ }
328
+ assertNoIgnoredPathOverwrite(repoDir, targetRevision);
329
+ const rootsToPreserve = [...new Set([root, ...requiredRoots.map((candidate) => path.resolve(candidate))])];
330
+ for (const requiredRoot of rootsToPreserve) {
331
+ if (!isWithin(requiredRoot, repoDir)) {
332
+ throw new UsageError(`Configured Git component root ${requiredRoot} resolves outside ${repoDir}.`);
333
+ }
334
+ const relative = path.relative(repoDir, requiredRoot).replaceAll(path.sep, "/");
335
+ if (!relative)
336
+ continue;
337
+ const tree = runGit(["-C", repoDir, "ls-tree", "-d", "-z", "--name-only", targetRevision, "--", relative]);
338
+ const names = tree.stdout.split("\0").filter(Boolean);
339
+ if (tree.status !== 0 || !names.includes(relative)) {
340
+ throw new UsageError(`Writable Git update would remove configured content root ${requiredRoot}; the existing checkout was left unchanged.`);
341
+ }
342
+ }
343
+ if (relation.behind > 0) {
344
+ const statusBeforeMerge = runGit(["-C", repoDir, "status", "--porcelain"]);
345
+ if (statusBeforeMerge.status !== 0 || statusBeforeMerge.stdout.trim()) {
346
+ throw new UsageError(`Writable Git install at ${root} changed while its update was prepared; local work was preserved.`);
347
+ }
348
+ assertNoIgnoredPathOverwrite(repoDir, targetRevision);
349
+ const merge = runGit(["-C", repoDir, "merge", "--ff-only", "--no-overwrite-ignore", targetRevision], {
350
+ timeout: 120_000,
351
+ });
352
+ if (merge.status !== 0) {
353
+ throw new UsageError(`Writable Git install at ${root} cannot fast-forward; local commits were preserved. ${merge.stderr.trim()}`);
354
+ }
355
+ }
356
+ for (const requiredRoot of rootsToPreserve) {
357
+ if (!isDirectory(requiredRoot)) {
358
+ throw new UsageError(`Writable Git update did not preserve configured content root ${requiredRoot}.`);
359
+ }
360
+ }
361
+ const head = runGit(["-C", repoDir, "rev-parse", "HEAD"]);
362
+ return {
363
+ id: resolved.id,
364
+ source: resolved.source,
365
+ ref: resolved.ref,
366
+ artifactUrl: resolved.artifactUrl,
367
+ resolvedVersion: resolved.resolvedVersion,
368
+ resolvedRevision: head.status === 0 ? head.stdout.trim() : targetRevision,
369
+ contentDir: root,
370
+ cacheDir: repoDir,
371
+ extractedDir: repoDir,
372
+ writable: true,
373
+ syncedAt,
374
+ };
375
+ }
131
376
  export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
132
377
  // Stage the clone into a sibling temp dir so that a failed clone never
133
378
  // destroys a previously-valid destDir (e.g. when the remote is temporarily
134
379
  // unreachable and we have a valid cached copy).
135
380
  const tmpDir = `${destDir}.tmp-${randomBytes(4).toString("hex")}`;
136
- const args = ["clone", "--depth", "1"];
381
+ const args = ["clone"];
382
+ if (!writable)
383
+ args.push("--depth", "1");
137
384
  if (ref)
138
385
  args.push("--branch", ref);
139
386
  args.push(cloneUrl, tmpDir);
@@ -150,10 +397,7 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
150
397
  if (fs.existsSync(gitDir))
151
398
  fs.rmSync(gitDir, { recursive: true, force: true });
152
399
  }
153
- // Swap: remove the old destDir (if any) then atomically rename tmpDir into place.
154
- if (fs.existsSync(destDir))
155
- fs.rmSync(destDir, { recursive: true, force: true });
156
- fs.renameSync(tmpDir, destDir);
400
+ replaceDirectory(tmpDir, destDir);
157
401
  }
158
402
  catch (err) {
159
403
  // Post-clone steps failed — clean up the temp dir to avoid orphaned dirs.
@@ -164,7 +408,7 @@ export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
164
408
  // ── Clone-failure classification (#487) ─────────────────────────────────────
165
409
  /**
166
410
  * Translate git's stderr into an actionable message. Without this, a user
167
- * who passes a nonexistent or private repo to `akm add` sees:
411
+ * who passes a nonexistent or private repo to `akm bundle add` sees:
168
412
  *
169
413
  * "could not read Username for 'https://github.com': No such device or
170
414
  * address"
@@ -11,7 +11,7 @@ import { getRegistryIndexCacheDir } from "../../core/paths.js";
11
11
  import { validateGitUrl } from "../../registry/resolve.js";
12
12
  import { withFreshnessCache } from "../freshness.js";
13
13
  import { registerSourceProvider } from "../provider-factory.js";
14
- import { cloneRepo, runGit, syncRegistryGitRef } from "./git-install.js";
14
+ import { assertNoIgnoredPathOverwrite, cloneRepo, inspectGitUpstream, runGit } from "./git-install.js";
15
15
  import { sanitizeString } from "./provider-utils.js";
16
16
  /** Cache TTL before refreshing the mirrored repo (12 hours). */
17
17
  const CACHE_TTL_MS = 12 * 60 * 60 * 1000;
@@ -19,12 +19,11 @@ const CACHE_TTL_MS = 12 * 60 * 60 * 1000;
19
19
  const CACHE_STALE_MS = 7 * 24 * 60 * 60 * 1000;
20
20
  /**
21
21
  * Git source provider — clones (and re-pulls) a remote repo into a local
22
- * cache directory. Implements the v1 {@link SourceProvider} interface (spec
23
- * §2.1, §2.5): `{ name, kind, init, path, sync }`.
22
+ * cache directory. Implements the {@link SourceProvider} interface.
24
23
  *
25
24
  * Reading is the indexer's job — this class doesn't implement `search` or
26
- * `show`. The install-time helpers `syncRegistryGitRef` / `syncMirroredRepo`
27
- * live below as standalone functions used by `akm add` / `akm update`.
25
+ * `show`. Install refs are materialized by `syncFromRef`; this provider only
26
+ * refreshes configured Git sources.
28
27
  */
29
28
  export class GitSourceProvider {
30
29
  kind = "git";
@@ -37,23 +36,11 @@ export class GitSourceProvider {
37
36
  }
38
37
  path() {
39
38
  if (this.#path == null) {
40
- // Lazy resolution: providers are sometimes constructed without an
41
- // explicit init() call (e.g. by legacy callers that just want the
42
- // path). Resolve on demand and cache.
43
39
  this.#path = resolveGitContentDir(this.#config);
44
40
  }
45
41
  return this.#path;
46
42
  }
47
43
  async sync(options) {
48
- // Two execution modes:
49
- // 1. Long-lived configured source (config.url) — mirror into the
50
- // registry-index cache and serve as a read-only working tree.
51
- // 2. One-shot install ref (options.ref like "git:..." / "github:...") —
52
- // delegate to the install-time pipeline.
53
- if (typeof this.#config.options?.ref === "string" && this.#config.options.ref) {
54
- await syncRegistryGitRef(String(this.#config.options.ref), { force: options?.force });
55
- return;
56
- }
57
44
  await syncMirroredRepo(this.#config, { force: options?.force });
58
45
  }
59
46
  }
@@ -88,6 +75,7 @@ export async function ensureGitMirror(repo, cachePaths, options) {
88
75
  ttlMs: CACHE_TTL_MS,
89
76
  staleMs: CACHE_STALE_MS,
90
77
  force: options?.force === true,
78
+ allowStaleOnRefreshFailure: !(options?.force === true && writable),
91
79
  isUsable: () => !requireRepoDir || hasExtractedRepo(cachePaths.repoDir),
92
80
  refresh: async () => {
93
81
  fs.mkdirSync(cachePaths.rootDir, { recursive: true });
@@ -134,12 +122,26 @@ export async function syncMirroredRepo(config, options) {
134
122
  };
135
123
  }
136
124
  function pullRepo(repoDir) {
137
- const result = runGit(["-C", repoDir, "pull", "--ff-only"], {
138
- timeout: 120_000,
139
- });
140
- if (result.status !== 0) {
141
- const err = result.stderr?.trim() || result.error?.message || "unknown error";
142
- throw new Error(`Failed to pull ${repoDir}: ${err}`);
125
+ const status = runGit(["-C", repoDir, "status", "--porcelain"]);
126
+ if (status.status !== 0 || status.stdout.trim()) {
127
+ throw new UsageError(`Writable Git source at ${repoDir} has uncommitted changes; refusing to update it.`);
128
+ }
129
+ const relation = inspectGitUpstream(repoDir);
130
+ if (relation.ahead > 0) {
131
+ throw new UsageError(`Writable Git source at ${repoDir} has unpushed commits; refusing to update it.`);
132
+ }
133
+ if (relation.behind > 0 && relation.upstream) {
134
+ const statusBeforeMerge = runGit(["-C", repoDir, "status", "--porcelain"]);
135
+ if (statusBeforeMerge.status !== 0 || statusBeforeMerge.stdout.trim()) {
136
+ throw new UsageError(`Writable Git source at ${repoDir} changed while its update was prepared; refusing to merge.`);
137
+ }
138
+ assertNoIgnoredPathOverwrite(repoDir, relation.upstream);
139
+ const merge = runGit(["-C", repoDir, "merge", "--ff-only", "--no-overwrite-ignore", relation.upstream], {
140
+ timeout: 120_000,
141
+ });
142
+ if (merge.status !== 0) {
143
+ throw new Error(`Failed to fast-forward ${repoDir}: ${merge.stderr?.trim() || "unknown error"}`);
144
+ }
143
145
  }
144
146
  }
145
147
  function hasExtractedRepo(repoDir) {