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,14 +1,16 @@
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 { randomBytes } from "node:crypto";
4
5
  import fs from "node:fs";
5
6
  import path from "node:path";
6
7
  import { akmAdapter } from "../../core/adapter/adapters/akm-adapter.js";
7
8
  import { stashDirNames } from "../../core/asset/asset-placement.js";
8
9
  import { resolveStashDir } from "../../core/common.js";
9
- import { getSources, loadConfig } from "../../core/config/config.js";
10
- import { UsageError } from "../../core/errors.js";
10
+ import { getSources, loadConfig, resolveConfiguredSources } from "../../core/config/config.js";
11
+ import { NotFoundError, UsageError } from "../../core/errors.js";
11
12
  import { sanitizeCommitMessage } from "../../core/git-message.js";
13
+ import { lockContentRootFor } from "../../integrations/lockfile.js";
12
14
  import { runGit } from "./git-install.js";
13
15
  import { getCachePaths, parseGitRepoUrl } from "./git-provider.js";
14
16
  /**
@@ -44,15 +46,25 @@ export function listGitChangedPaths(repoDir) {
44
46
  }
45
47
  return paths;
46
48
  }
49
+ export class GitStashPushError extends Error {
50
+ commit;
51
+ constructor(message, commit) {
52
+ super(message);
53
+ this.name = "GitStashPushError";
54
+ this.commit = commit;
55
+ }
56
+ }
57
+ const GIT_PUSH_TIMEOUT_MS = 120_000;
58
+ const ZERO_OID = "0000000000000000000000000000000000000000";
47
59
  /**
48
- * Resolve the writable-override flag for an end-of-run / `akm sync` commit on
49
- * the primary stash. Returns `true` when the root config explicitly marks the
50
- * primary stash writable, otherwise `undefined` (leave the per-stash default
51
- * untouched). Extracted so `akm sync`, `akm improve`'s end-of-run sync, and the
52
- * CLI body all derive this identically instead of re-copying the expression.
60
+ * Resolve the writable flag for an end-of-run / `akm sync` commit from the
61
+ * configured default bundle.
53
62
  */
54
63
  export function resolveWritableOverride(config) {
55
- return config.writable === true ? true : undefined;
64
+ const source = resolveConfiguredSources(config).find((entry) => entry.name === config.defaultBundle);
65
+ if (!source)
66
+ return undefined;
67
+ return (source.writable ?? source.type === "filesystem") ? true : undefined;
56
68
  }
57
69
  /**
58
70
  * Commit (and optionally push) local changes in a git-backed stash.
@@ -71,7 +83,7 @@ export function resolveWritableOverride(config) {
71
83
  * primary stash dir (e.g. `akm improve`'s end-of-run sync, whose pre-commit
72
84
  * gate validates that exact directory) pass it here so the gate and the commit
73
85
  * operate on the SAME directory instead of independently calling
74
- * `resolveStashDir({ readOnly: true })`. When absent, behaviour is unchanged.
86
+ * `resolveStashDir()`. When absent, behaviour is unchanged.
75
87
  */
76
88
  export function saveGitStash(name, message, writableOverride, options) {
77
89
  // `push: false` (from `akm sync --no-push`) commits but never pushes, even
@@ -84,27 +96,45 @@ export function saveGitStash(name, message, writableOverride, options) {
84
96
  // Empty result falls back to the timestamped default.
85
97
  const sanitized = message ? sanitizeCommitMessage(message) : "";
86
98
  const commitMessage = sanitized || `akm save ${timestamp}`;
99
+ const transactionId = options?.transactionId;
100
+ if (transactionId !== undefined && !/^[A-Za-z0-9._-]{1,128}$/.test(transactionId)) {
101
+ throw new Error("Invalid Git transaction identifier.");
102
+ }
87
103
  let repoDir;
88
104
  let writable = false;
105
+ let managedContentRoot;
89
106
  if (name) {
90
107
  const config = loadConfig();
91
108
  const stash = findGitStashByTarget(getSources(config), name);
109
+ // NotFoundError (exit 1), not UsageError (exit 2): the argument is
110
+ // well-formed, the bundle just isn't configured.
92
111
  if (!stash)
93
- throw new UsageError(`No git stash found with name "${name}"`);
112
+ throw new NotFoundError(`No git bundle found with name "${name}"`, "SOURCE_NOT_FOUND");
94
113
  if (stash.type !== "git") {
95
114
  throw new UsageError(`Stash "${name}" is not a git stash (type: ${stash.type})`);
96
115
  }
97
- if (!stash.url)
98
- throw new UsageError(`Stash "${name}" has no URL configured`);
99
- const repo = parseGitRepoUrl(stash.url);
100
- repoDir = getCachePaths(repo.canonicalUrl).repoDir;
116
+ const lockedRoot = lockContentRootFor(stash.name, stash.type);
117
+ if (lockedRoot) {
118
+ const topLevel = runGit(["-C", lockedRoot, "rev-parse", "--show-toplevel"]);
119
+ if (topLevel.status !== 0 || !topLevel.stdout.trim()) {
120
+ throw new UsageError(`Managed Git stash "${name}" is not a checkout at ${lockedRoot}`);
121
+ }
122
+ repoDir = path.resolve(topLevel.stdout.trim());
123
+ managedContentRoot = path.resolve(lockedRoot);
124
+ }
125
+ else {
126
+ if (!stash.url)
127
+ throw new UsageError(`Stash "${name}" has no URL configured`);
128
+ const repo = parseGitRepoUrl(stash.url);
129
+ repoDir = getCachePaths(repo.canonicalUrl).repoDir;
130
+ }
101
131
  writable = stash.writable === true;
102
132
  }
103
133
  else {
104
134
  // Honour an explicit primary-stash dir override (keeps the improve gate and
105
135
  // the commit on the same directory); otherwise resolve the default.
106
- repoDir = options?.repoDir ?? resolveStashDir({ readOnly: true });
107
- // Allow caller to override writable for the primary stash (e.g. from root config.writable)
136
+ repoDir = options?.repoDir ?? resolveStashDir();
137
+ // Allow the caller to pass the configured default bundle's writability.
108
138
  if (writableOverride !== undefined) {
109
139
  writable = writableOverride;
110
140
  }
@@ -113,12 +143,11 @@ export function saveGitStash(name, message, writableOverride, options) {
113
143
  if (!isGitBackedStash(repoDir)) {
114
144
  return { committed: false, pushed: false, skipped: true, reason: "not a git repository", output: "" };
115
145
  }
116
- // Nothing to commit?
117
146
  const statusResult = runGit(["-C", repoDir, "status", "--porcelain"]);
118
147
  if (statusResult.error || statusResult.status !== 0) {
119
148
  throw new Error(`git status failed: ${statusResult.error?.message || statusResult.stderr?.trim() || "unknown error"}`);
120
149
  }
121
- if (!statusResult.stdout.trim()) {
150
+ if (!statusResult.stdout.trim() && options?.paths === undefined) {
122
151
  return { committed: false, pushed: false, skipped: false, output: "nothing to commit, working tree clean" };
123
152
  }
124
153
  // Scoped staging (#476 + the auto-sync incident): NEVER refuse akm's commit
@@ -129,123 +158,383 @@ export function saveGitStash(name, message, writableOverride, options) {
129
158
  //
130
159
  // Precedence:
131
160
  // 1. Explicit modified-file list (`options.paths`) — stage exactly those.
132
- // 2. Managed pathspecs (placement stash-subdir names + `.akm`) that exist on disk
133
- // stages everything akm owns and, by construction, never stages non-akm
134
- // WIP. This preserves the #476 protection WITHOUT refusing.
161
+ // 2. A lock-backed content root, or managed adapter pathspecs for an
162
+ // ordinary stash. Both stay below AKM's resolved content boundary.
135
163
  // 3. No resolvable managed path — no commit. Broad staging is never safe
136
164
  // because it can absorb unrelated work already present in the index.
137
- const staged = stageScopedChanges(repoDir, options?.paths);
138
- if (!staged.ok) {
139
- throw new Error(`git add failed while staging akm changes in ${repoDir}`);
140
- }
141
- if (staged.pathspecs && staged.pathspecs.length === 0) {
142
- return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
143
- }
144
- // Nothing actually staged → don't create an empty commit. This happens when
145
- // only non-akm files were dirty (precedence 2 staged nothing).
146
- const stagedResult = runGit([
147
- "-C",
148
- repoDir,
149
- "diff",
150
- "--cached",
151
- "--quiet",
152
- ...(staged.pathspecs ? ["--", ...staged.pathspecs] : []),
153
- ]);
154
- if (stagedResult.status === 0) {
165
+ const managedRelativeRoot = managedContentRoot
166
+ ? path.relative(repoDir, managedContentRoot).replaceAll(path.sep, "/")
167
+ : "";
168
+ const changedPaths = listGitChangedPaths(repoDir);
169
+ const requestedPaths = normalizeExactPaths(repoDir, options?.paths ??
170
+ (managedRelativeRoot && managedRelativeRoot !== ".." && !managedRelativeRoot.startsWith("../")
171
+ ? changedPaths.filter((changedPath) => changedPath === managedRelativeRoot || changedPath.startsWith(`${managedRelativeRoot}/`))
172
+ : managedFallbackPaths(repoDir, changedPaths)));
173
+ if (requestedPaths.length === 0) {
155
174
  return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
156
175
  }
157
- // Commit — supply fallback identity so fresh environments without
158
- // user.name/user.email configured can always commit to the default stash.
159
- const commitResult = runGit([
160
- "-C",
161
- repoDir,
162
- "-c",
163
- "user.name=akm",
164
- "-c",
165
- "user.email=akm@local",
166
- "commit",
167
- "-m",
168
- commitMessage,
169
- ...(staged.pathspecs ? ["--only", "--", ...staged.pathspecs] : []),
170
- ]);
171
- if (commitResult.status !== 0) {
172
- throw new Error(`git commit failed: ${commitResult.stderr?.trim() || "unknown error"}`);
176
+ assertNoIgnoredExactPaths(repoDir, requestedPaths);
177
+ const baseHead = readOptionalHead(repoDir);
178
+ if (options?.expectedBaseHead !== undefined && (baseHead ?? null) !== options.expectedBaseHead) {
179
+ throw new Error(`Git target advanced before its exact-path commit could be created.`);
173
180
  }
174
- // Push only when there is a remote AND the stash is marked writable
175
181
  const remoteResult = runGit(["-C", repoDir, "remote"]);
176
182
  if (remoteResult.status !== 0) {
177
183
  throw new Error(`git remote failed: ${remoteResult.stderr?.trim() || "unknown error"}`);
178
184
  }
179
185
  const hasRemote = remoteResult.stdout.trim().length > 0;
180
- if (!hasRemote || !writable || !allowPush) {
181
- return { committed: true, pushed: false, skipped: false, output: commitResult.stdout.trim() };
186
+ const pushTarget = hasRemote && writable && allowPush ? readActualUpstream(repoDir, baseHead) : undefined;
187
+ const exactCommit = createExactPathCommit(repoDir, {
188
+ baseHead,
189
+ commitMessage,
190
+ paths: requestedPaths,
191
+ transactionId,
192
+ expectedSnapshots: options?.expectedSnapshots,
193
+ });
194
+ if (!exactCommit) {
195
+ return { committed: false, pushed: false, skipped: false, output: "nothing to commit" };
196
+ }
197
+ if (!pushTarget) {
198
+ return {
199
+ committed: true,
200
+ pushed: false,
201
+ skipped: false,
202
+ output: `commit ${exactCommit}`,
203
+ commit: exactCommit,
204
+ };
182
205
  }
183
- const pushResult = runGit(["-C", repoDir, "push"], { timeout: 120_000 });
206
+ const pushResult = runGit([
207
+ "-C",
208
+ repoDir,
209
+ "push",
210
+ `--force-with-lease=${pushTarget.mergeRef}:${pushTarget.upstreamHead}`,
211
+ pushTarget.remote,
212
+ `${exactCommit}:${pushTarget.mergeRef}`,
213
+ ], { timeout: GIT_PUSH_TIMEOUT_MS });
184
214
  if (pushResult.status !== 0) {
185
- throw new Error(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`);
215
+ throw new GitStashPushError(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`, exactCommit);
186
216
  }
187
217
  return {
188
218
  committed: true,
189
219
  pushed: true,
190
220
  skipped: false,
191
- output: (commitResult.stdout + pushResult.stdout).trim() || "changes committed and pushed",
221
+ output: pushResult.stdout.trim() || "changes committed and pushed",
222
+ commit: exactCommit,
192
223
  };
193
224
  }
194
- /**
195
- * Stage akm's changes in `repoDir` using the scoped-staging precedence
196
- * documented at the call site (#476). The returned pathspecs are reduced to
197
- * exact staged files so `git commit --only` cannot absorb unrelated index state.
198
- *
199
- * @param paths Optional explicit repo-relative paths akm wrote this run. When
200
- * provided and non-empty, exactly those are staged (chunked to stay under
201
- * argv length limits). Otherwise we fall back to the managed pathspecs, and
202
- * skip the commit when no managed pathspec exists on disk.
203
- */
204
- function stageScopedChanges(repoDir, paths) {
205
- if (paths !== undefined && paths.length === 0)
206
- return { ok: true, pathspecs: [] };
207
- // Precedence 1: explicit modified-file list.
208
- const explicit = (paths ?? []).filter((p) => typeof p === "string" && p.length > 0);
209
- if (explicit.length > 0) {
210
- const ok = addPathspecsChunked(repoDir, explicit);
211
- return { ok, pathspecs: ok ? listStagedPaths(repoDir, explicit) : [] };
212
- }
213
- // Precedence 2: managed pathspecs that exist on disk (adapter-owned stash
214
- // subdirs + `.akm`). WI-3.1: the owned subdirs are now sourced from the `akm`
215
- // adapter's `directoryList()` — behavior-identical to the placement
216
- // stash-subdir set, with `stashDirNames()` kept live as the fallback.
225
+ function managedFallbackPaths(repoDir, changedPaths) {
217
226
  const ownedDirs = akmAdapter.directoryList?.({ id: "akm", adapter: "akm", root: repoDir, writable: false }) ?? stashDirNames();
218
- const managed = [...ownedDirs, ".akm"].filter((dir) => fs.existsSync(path.join(repoDir, dir)));
219
- if (managed.length > 0) {
220
- const ok = addPathspecsChunked(repoDir, managed);
221
- return { ok, pathspecs: ok ? listStagedPaths(repoDir, managed) : [] };
227
+ const prefixes = [...ownedDirs, ".akm"];
228
+ return changedPaths.filter((changedPath) => prefixes.some((prefix) => changedPath === prefix || changedPath.startsWith(`${prefix}/`)));
229
+ }
230
+ function normalizeExactPaths(repoDir, paths) {
231
+ const normalized = [...new Set(paths.map((value) => value.replaceAll(path.sep, "/")))];
232
+ for (const filePath of normalized) {
233
+ const absolute = path.resolve(repoDir, filePath);
234
+ const relative = path.relative(repoDir, absolute).replaceAll(path.sep, "/");
235
+ if (!filePath ||
236
+ filePath.includes("\0") ||
237
+ path.isAbsolute(filePath) ||
238
+ relative !== filePath ||
239
+ relative === ".." ||
240
+ relative.startsWith("../")) {
241
+ throw new Error(`Unsafe exact Git path: ${filePath || "<empty>"}`);
242
+ }
222
243
  }
223
- // No managed target means there is no safe commit scope.
224
- return { ok: true, pathspecs: [] };
244
+ return normalized;
225
245
  }
226
- function listStagedPaths(repoDir, pathspecs) {
227
- const result = runGit(["-C", repoDir, "diff", "--cached", "--name-only", "--", ...pathspecs]);
228
- if (result.status !== 0)
246
+ function runExactPathChunks(repoDir, command, paths, options = {}) {
247
+ const CHUNK = 500;
248
+ const results = [];
249
+ for (let i = 0; i < paths.length; i += CHUNK) {
250
+ results.push(runGit(["--literal-pathspecs", "-C", repoDir, ...command, "--", ...paths.slice(i, i + CHUNK)], {
251
+ env: options.env,
252
+ }));
253
+ }
254
+ return results;
255
+ }
256
+ export function listIgnoredExactPaths(repoDir, paths) {
257
+ if (paths.length === 0)
229
258
  return [];
230
- return result.stdout
231
- .split("\n")
232
- .map((value) => value.trim())
233
- .filter(Boolean);
259
+ const input = `${paths.join("\0")}\0`;
260
+ const result = runGit(["-C", repoDir, "check-ignore", "-z", "--stdin"], { input });
261
+ if (result.status === 1)
262
+ return [];
263
+ if (result.status !== 0) {
264
+ throw new Error(`git check-ignore failed: ${result.stderr.trim() || "unknown error"}`);
265
+ }
266
+ return result.stdout.split("\0").filter(Boolean);
234
267
  }
235
- /**
236
- * Run `git add -- <pathspec>...` in chunks so a very large path list never
237
- * exceeds the OS argv-length limit. Each chunk must succeed.
238
- */
239
- function addPathspecsChunked(repoDir, pathspecs) {
240
- const CHUNK = 500;
241
- for (let i = 0; i < pathspecs.length; i += CHUNK) {
242
- const chunk = pathspecs.slice(i, i + CHUNK);
243
- const result = runGit(["-C", repoDir, "add", "--", ...chunk]);
268
+ export function assertNoIgnoredExactPaths(repoDir, paths) {
269
+ const ignored = listIgnoredExactPaths(repoDir, normalizeExactPaths(repoDir, paths));
270
+ if (ignored.length > 0) {
271
+ throw new UsageError(`Exact Git publication path is ignored: ${ignored[0]}. Update .gitignore or choose a tracked destination before writing.`);
272
+ }
273
+ }
274
+ /** Reject exact staged/unstaged paths before an AKM filesystem mutation. */
275
+ export function assertGitExactPathsClean(repoDir, paths) {
276
+ const normalized = normalizeExactPaths(repoDir, paths);
277
+ assertNoIgnoredExactPaths(repoDir, normalized);
278
+ for (const result of runExactPathChunks(repoDir, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], normalized)) {
279
+ if (result.status !== 0) {
280
+ throw new Error(`git status failed: ${result.stderr.trim() || "unknown error"}`);
281
+ }
282
+ if (result.stdout.length > 0) {
283
+ const record = result.stdout.split("\0").find(Boolean) ?? "";
284
+ throw new UsageError(`Exact Git operation path has staged or unstaged work: ${record.slice(3)}. Commit, stash, or discard that path before retrying.`);
285
+ }
286
+ }
287
+ }
288
+ function readOptionalHead(repoDir) {
289
+ const result = runGit(["-C", repoDir, "rev-parse", "--verify", "HEAD"]);
290
+ return result.status === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
291
+ }
292
+ function readBranchRef(repoDir) {
293
+ const result = runGit(["-C", repoDir, "symbolic-ref", "--quiet", "HEAD"]);
294
+ if (result.status !== 0 || !result.stdout.trim()) {
295
+ throw new UsageError(`Writable Git target at ${repoDir} is detached from a branch.`);
296
+ }
297
+ return result.stdout.trim();
298
+ }
299
+ function readActualUpstream(repoDir, baseHead) {
300
+ if (!baseHead)
301
+ throw new UsageError(`Writable Git target at ${repoDir} has no commit to publish.`);
302
+ const branchRef = readBranchRef(repoDir);
303
+ const branch = branchRef.replace(/^refs\/heads\//, "");
304
+ const remote = runGit(["-C", repoDir, "config", "--get", `branch.${branch}.remote`]);
305
+ const merge = runGit(["-C", repoDir, "config", "--get", `branch.${branch}.merge`]);
306
+ const upstream = runGit(["-C", repoDir, "rev-parse", "--verify", "@{u}"]);
307
+ if (remote.status !== 0 || merge.status !== 0 || upstream.status !== 0) {
308
+ throw new UsageError(`Writable Git target at ${repoDir} has no configured upstream branch.`);
309
+ }
310
+ const upstreamHead = upstream.stdout.trim();
311
+ if (!upstreamHead || upstreamHead !== baseHead) {
312
+ throw new UsageError(`Writable Git target at ${repoDir} is not synchronized with its actual upstream.`);
313
+ }
314
+ return { remote: remote.stdout.trim(), mergeRef: merge.stdout.trim(), upstreamHead };
315
+ }
316
+ function readIndexEntries(repoDir, paths) {
317
+ const entries = new Map(paths.map((filePath) => [filePath, []]));
318
+ for (const result of runExactPathChunks(repoDir, ["ls-files", "--stage", "-z"], paths)) {
319
+ if (result.status !== 0)
320
+ throw new Error(`Cannot inspect Git index: ${result.stderr.trim() || "unknown error"}`);
321
+ for (const record of result.stdout.split("\0")) {
322
+ if (!record)
323
+ continue;
324
+ const tab = record.indexOf("\t");
325
+ const match = record.slice(0, tab).match(/^(\d+) ([0-9a-f]+) (\d+)$/);
326
+ if (tab < 0 || !match)
327
+ throw new Error(`Cannot parse Git index entry.`);
328
+ const filePath = record.slice(tab + 1);
329
+ entries.get(filePath)?.push({ mode: match[1], oid: match[2], stage: Number(match[3]) });
330
+ }
331
+ }
332
+ return entries;
333
+ }
334
+ function readTreeStates(repoDir, treeish, paths) {
335
+ const states = new Map(paths.map((filePath) => [filePath, null]));
336
+ if (!treeish)
337
+ return states;
338
+ for (const result of runExactPathChunks(repoDir, ["ls-tree", "-z", treeish], paths)) {
244
339
  if (result.status !== 0)
340
+ throw new Error(`Cannot inspect Git tree: ${result.stderr.trim() || "unknown error"}`);
341
+ for (const record of result.stdout.split("\0")) {
342
+ if (!record)
343
+ continue;
344
+ const tab = record.indexOf("\t");
345
+ const match = record.slice(0, tab).match(/^(\d+) blob ([0-9a-f]+)$/);
346
+ if (tab < 0 || !match)
347
+ throw new Error(`Exact Git path is not a blob.`);
348
+ const mode = match[1];
349
+ if (mode !== "100644" && mode !== "100755" && mode !== "120000") {
350
+ throw new Error(`Unsupported Git mode ${mode}.`);
351
+ }
352
+ states.set(record.slice(tab + 1), { mode, oid: match[2] });
353
+ }
354
+ }
355
+ return states;
356
+ }
357
+ function assertIndexMatchesBase(paths, indexEntries, baseStates) {
358
+ if (indexMatchesTree(paths, indexEntries, baseStates))
359
+ return;
360
+ const dirtyPath = paths.find((filePath) => {
361
+ const entries = indexEntries.get(filePath) ?? [];
362
+ const base = baseStates.get(filePath) ?? null;
363
+ return !(base === null
364
+ ? entries.length === 0
365
+ : entries.length === 1 &&
366
+ entries[0]?.stage === 0 &&
367
+ entries[0]?.mode === base.mode &&
368
+ entries[0]?.oid === base.oid);
369
+ });
370
+ throw new UsageError(`Exact Git operation path has staged work: ${dirtyPath ?? paths[0]}. Commit, stash, or discard that path before retrying.`);
371
+ }
372
+ function indexMatchesTree(paths, indexEntries, treeStates) {
373
+ for (const filePath of paths) {
374
+ const entries = indexEntries.get(filePath) ?? [];
375
+ const state = treeStates.get(filePath) ?? null;
376
+ if (!(state === null
377
+ ? entries.length === 0
378
+ : entries.length === 1 &&
379
+ entries[0]?.stage === 0 &&
380
+ entries[0]?.mode === state.mode &&
381
+ entries[0]?.oid === state.oid)) {
245
382
  return false;
383
+ }
246
384
  }
247
385
  return true;
248
386
  }
387
+ /** Repair only the stale base index left by a crash after an exact commit CAS. */
388
+ export function reconcileGitExactPathIndex(repoDir, baseCommit, commit, paths) {
389
+ const normalized = normalizeExactPaths(repoDir, paths);
390
+ const current = readIndexEntries(repoDir, normalized);
391
+ const committedStates = readTreeStates(repoDir, commit, normalized);
392
+ if (indexMatchesTree(normalized, current, committedStates))
393
+ return;
394
+ const baseStates = readTreeStates(repoDir, baseCommit, normalized);
395
+ if (!indexMatchesTree(normalized, current, baseStates)) {
396
+ throw new UsageError(`Exact Git operation path has staged work after transaction recovery. Commit, stash, or discard it before retrying.`);
397
+ }
398
+ for (const result of runExactPathChunks(repoDir, ["reset", "--quiet", commit], normalized)) {
399
+ if (result.status !== 0)
400
+ throw new Error(`git index cleanup failed during exact transaction recovery`);
401
+ }
402
+ }
403
+ function captureWorktreeState(repoDir, filePath) {
404
+ const absolutePath = path.join(repoDir, filePath);
405
+ let stat;
406
+ try {
407
+ stat = fs.lstatSync(absolutePath);
408
+ }
409
+ catch (error) {
410
+ if (error.code === "ENOENT")
411
+ return null;
412
+ throw error;
413
+ }
414
+ let mode;
415
+ let result;
416
+ if (stat.isSymbolicLink()) {
417
+ mode = "120000";
418
+ result = runGit(["-C", repoDir, "hash-object", "--stdin"], { input: fs.readlinkSync(absolutePath) });
419
+ }
420
+ else if (stat.isFile()) {
421
+ mode = stat.mode & 0o111 ? "100755" : "100644";
422
+ result = runGit(["-C", repoDir, "hash-object", `--path=${filePath}`, "--", absolutePath]);
423
+ }
424
+ else {
425
+ throw new Error(`Exact Git publication path is not a file: ${filePath}`);
426
+ }
427
+ if (result.status !== 0 || !result.stdout.trim()) {
428
+ throw new Error(`Cannot hash exact Git publication path: ${filePath}`);
429
+ }
430
+ return { oid: result.stdout.trim(), mode };
431
+ }
432
+ function samePathState(left, right) {
433
+ return left === null ? right === null : right !== null && left.oid === right.oid && left.mode === right.mode;
434
+ }
435
+ function assertWorktreeMatchesExpected(repoDir, paths, expected) {
436
+ for (const filePath of paths) {
437
+ if (!samePathState(captureWorktreeState(repoDir, filePath), expected.get(filePath) ?? null)) {
438
+ throw new Error(`Exact Git publication path changed while committing: ${filePath}`);
439
+ }
440
+ }
441
+ }
442
+ function sameIndexEntries(left, right) {
443
+ return JSON.stringify([...left]) === JSON.stringify([...right]);
444
+ }
445
+ function resetRealIndexPaths(repoDir, commit, paths, initial) {
446
+ const current = readIndexEntries(repoDir, paths);
447
+ if (!sameIndexEntries(initial, current))
448
+ return;
449
+ for (const result of runExactPathChunks(repoDir, ["reset", "--quiet", commit], paths)) {
450
+ if (result.status !== 0)
451
+ throw new Error(`git index cleanup failed after exact commit`);
452
+ }
453
+ }
454
+ function createExactPathCommit(repoDir, options) {
455
+ const branchRef = readBranchRef(repoDir);
456
+ const baseStates = readTreeStates(repoDir, options.baseHead, options.paths);
457
+ const initialIndex = readIndexEntries(repoDir, options.paths);
458
+ assertIndexMatchesBase(options.paths, initialIndex, baseStates);
459
+ const expected = new Map();
460
+ for (const filePath of options.paths) {
461
+ expected.set(filePath, options.expectedSnapshots && Object.hasOwn(options.expectedSnapshots, filePath)
462
+ ? (options.expectedSnapshots[filePath] ?? null)
463
+ : captureWorktreeState(repoDir, filePath));
464
+ }
465
+ const gitIndex = runGit(["-C", repoDir, "rev-parse", "--git-path", "index"]);
466
+ if (gitIndex.status !== 0 || !gitIndex.stdout.trim())
467
+ throw new Error(`Cannot resolve Git index path.`);
468
+ const realIndexPath = path.resolve(repoDir, gitIndex.stdout.trim());
469
+ const temporaryIndex = `${realIndexPath}.akm-${process.pid}-${randomBytes(6).toString("hex")}`;
470
+ const env = { GIT_INDEX_FILE: temporaryIndex };
471
+ try {
472
+ const seeded = options.baseHead
473
+ ? runGit(["-C", repoDir, "read-tree", options.baseHead], { env })
474
+ : runGit(["-C", repoDir, "read-tree", "--empty"], { env });
475
+ if (seeded.status !== 0)
476
+ throw new Error(`Cannot seed temporary Git index: ${seeded.stderr.trim()}`);
477
+ const stagePaths = options.paths.filter((filePath) => (expected.get(filePath) ?? null) !== null || (baseStates.get(filePath) ?? null) !== null);
478
+ for (const result of runExactPathChunks(repoDir, ["add", "-A"], stagePaths, { env })) {
479
+ if (result.status !== 0)
480
+ throw new Error(`Cannot stage exact Git paths: ${result.stderr.trim()}`);
481
+ }
482
+ const tree = runGit(["-C", repoDir, "write-tree"], { env });
483
+ if (tree.status !== 0 || !tree.stdout.trim())
484
+ throw new Error(`Cannot write exact Git tree.`);
485
+ const treeOid = tree.stdout.trim();
486
+ const committedStates = readTreeStates(repoDir, treeOid, options.paths);
487
+ for (const filePath of options.paths) {
488
+ if (!samePathState(committedStates.get(filePath) ?? null, expected.get(filePath) ?? null)) {
489
+ throw new Error(`Exact Git publication path changed while committing: ${filePath}`);
490
+ }
491
+ }
492
+ assertWorktreeMatchesExpected(repoDir, options.paths, expected);
493
+ const baseTreeResult = options.baseHead
494
+ ? runGit(["-C", repoDir, "rev-parse", `${options.baseHead}^{tree}`])
495
+ : undefined;
496
+ if (baseTreeResult && (baseTreeResult.status !== 0 || !baseTreeResult.stdout.trim())) {
497
+ throw new Error(`Cannot inspect base Git tree.`);
498
+ }
499
+ const baseTree = baseTreeResult?.stdout.trim() ?? "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
500
+ if (treeOid === baseTree)
501
+ return null;
502
+ const body = options.transactionId
503
+ ? `${options.commitMessage}\n\nAKM-Transaction: ${options.transactionId}\n`
504
+ : `${options.commitMessage}\n`;
505
+ const commit = runGit([
506
+ "-C",
507
+ repoDir,
508
+ "-c",
509
+ "user.name=akm",
510
+ "-c",
511
+ "user.email=akm@local",
512
+ "commit-tree",
513
+ treeOid,
514
+ ...(options.baseHead ? ["-p", options.baseHead] : []),
515
+ ], { input: body });
516
+ if (commit.status !== 0 || !commit.stdout.trim()) {
517
+ throw new Error(`git commit-tree failed: ${commit.stderr.trim() || "unknown error"}`);
518
+ }
519
+ const commitOid = commit.stdout.trim();
520
+ const currentBranch = readBranchRef(repoDir);
521
+ const currentHead = readOptionalHead(repoDir);
522
+ if (currentBranch !== branchRef || currentHead !== options.baseHead) {
523
+ throw new Error(`Git target changed before its exact commit could be attached.`);
524
+ }
525
+ assertWorktreeMatchesExpected(repoDir, options.paths, expected);
526
+ const update = runGit(["-C", repoDir, "update-ref", branchRef, commitOid, options.baseHead ?? ZERO_OID]);
527
+ if (update.status !== 0) {
528
+ throw new Error(`Git target advanced before its exact commit could be attached.`);
529
+ }
530
+ resetRealIndexPaths(repoDir, commitOid, options.paths, initialIndex);
531
+ return commitOid;
532
+ }
533
+ finally {
534
+ fs.rmSync(temporaryIndex, { force: true });
535
+ fs.rmSync(`${temporaryIndex}.lock`, { force: true });
536
+ }
537
+ }
249
538
  function findGitStashByTarget(stashes, target) {
250
539
  return stashes.find((stash) => matchesGitStashTarget(stash, target));
251
540
  }
@@ -8,6 +8,6 @@
8
8
  //
9
9
  // Re-exported here so every consumer (and every `spyOn(gitProvider, …)` test)
10
10
  // keeps importing from a single module namespace.
11
- export { classifyCloneFailure, cloneRepo, syncRegistryGitRef } from "./git-install.js";
11
+ export { classifyCloneFailure, cloneRepo, inspectGitUpstream, runGit, syncExistingWritableCheckout, } from "./git-install.js";
12
12
  export { ensureGitMirror, GitSourceProvider, getCachePaths, parseGitRepoUrl, syncMirroredRepo, } from "./git-provider.js";
13
- export { isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "./git-stash.js";
13
+ export { GitStashPushError, isGitBackedStash, listGitChangedPaths, resolveWritableOverride, saveGitStash, } from "./git-stash.js";