akm-cli 0.8.7 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -2,31 +2,34 @@
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
4
  /**
5
- * write-source — the only place in the codebase that branches on `source.kind`.
5
+ * write-source — the command-layer helper that performs asset writes.
6
6
  *
7
- * v1 architecture spec §2.6 / §2.7 / §10 step 5: writing to a source is *not*
8
- * a SourceProvider interface concern. It's a small command-layer helper that
9
- * does a plain filesystem write, plus a git-specific commit (and optional
10
- * push) when the source is backed by a git working tree.
7
+ * v1 architecture spec §2.6 / §2.7 / §10 step 5 (amended for 0.9.0): writing to
8
+ * a source is *not* a SourceProvider interface concern. It's a small
9
+ * command-layer helper that does a plain filesystem write for **every** kind.
11
10
  *
12
- * If a third kind ever needs special write handling, it gets added here. For
13
- * v1 there are exactly two cases. Adding more parallel scoring systems for
14
- * different provider kinds is explicitly disallowed by CLAUDE.md.
11
+ * 0.9.0 amendment (issue #507): the per-asset git commit/push path is retired.
12
+ * `writeAssetToSource` / `deleteAssetFromSource` no longer branch on `kind` for
13
+ * commit behaviour they only ever touch the filesystem. Git-backed targets
14
+ * are committed in a SINGLE batch at the operation boundary via
15
+ * {@link commitWriteTargetBoundary} (which delegates to `saveGitStash`). This
16
+ * stages `.akm/` + sibling assets together as one complete commit instead of
17
+ * one noisy, incomplete commit per asset.
15
18
  *
16
- * This module is the **single dispatch point** for `kind`-branching write
17
- * logic. Callers (remember, import, source-add, etc.) MUST go through
18
- * `writeAssetToSource` / `deleteAssetFromSource` rather than re-inlining the
19
- * filesystem-write + git-commit dance.
19
+ * This module is still the **single dispatch point** for write/delete: callers
20
+ * (remember, import, source-add, etc.) MUST go through `writeAssetToSource` /
21
+ * `deleteAssetFromSource` rather than re-inlining a filesystem write, and they
22
+ * fire {@link commitWriteTargetBoundary} once after a batch of mutations to a
23
+ * writable git target.
20
24
  */
21
- import { spawnSync } from "node:child_process";
22
25
  import fs from "node:fs";
23
26
  import path from "node:path";
24
- import { getCachePaths, parseGitRepoUrl } from "../sources/providers/git";
25
- import { makeAssetRef } from "./asset-ref";
26
- import { resolveAssetPathFromName, TYPE_DIRS } from "./asset-spec";
27
- import { isWithin, resolveStashDir } from "./common";
28
- import { resolveConfiguredSources } from "./config";
29
- import { ConfigError, UsageError } from "./errors";
27
+ import { getCachePaths, parseGitRepoUrl, saveGitStash } from "../sources/providers/git.js";
28
+ import { makeAssetRef } from "./asset/asset-ref.js";
29
+ import { resolveAssetPathFromName, TYPE_DIRS } from "./asset/asset-spec.js";
30
+ import { isWithin, resolveStashDir } from "./common.js";
31
+ import { resolveConfiguredSources } from "./config/config.js";
32
+ import { ConfigError, UsageError } from "./errors.js";
30
33
  /**
31
34
  * Source kinds that the loader is allowed to mark `writable: true`. Anything
32
35
  * else is rejected at config load (per locked decision 4) — see
@@ -114,41 +117,91 @@ export function assertWritableAllowedForKind(entry) {
114
117
  * `ref`. Always:
115
118
  *
116
119
  * 1. Refuses if `config.writable` is not truthy (per §5.4).
117
- * 2. Performs a plain filesystem write to `path.join(source.path, …)`.
120
+ * 2. Rejects unsupported kinds (anything but `filesystem` / `git`).
121
+ * 3. Performs a plain filesystem write to `path.join(source.path, …)`.
118
122
  *
119
- * For sources of `kind === "git"`, additionally:
120
- *
121
- * 3. `git -C <path> add <file>`
122
- * 4. `git -C <path> commit -m "Update <ref>"`
123
- * 5. `git -C <path> push` when `config.options.pushOnCommit` is truthy.
124
- *
125
- * Any other `kind` reaching this helper is a configuration bug — the loader
126
- * rejects unsupported writable kinds — so we throw {@link ConfigError}.
123
+ * No commit runs here — for **every** kind. Git-backed targets are committed in
124
+ * one batch at the operation boundary via {@link commitWriteTargetBoundary}
125
+ * (0.9.0 amendment, issue #507). The caller fires that boundary commit once
126
+ * after a batch of mutations to a writable git target.
127
127
  */
128
128
  export async function writeAssetToSource(source, config, ref, content) {
129
129
  ensureWritable(source, config);
130
+ assertSupportedKind(source);
130
131
  const filePath = resolveAssetFilePath(source, ref);
131
132
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
132
133
  const normalized = content.endsWith("\n") ? content : `${content}\n`;
133
134
  fs.writeFileSync(filePath, normalized, "utf8");
134
- await runKindSpecificCommit(source, config, filePath, `Update ${formatRefForMessage(ref)}`);
135
135
  return { path: filePath, ref: makeAssetRef(ref.type, ref.name, ref.origin) };
136
136
  }
137
137
  /**
138
138
  * Delete the asset at `ref` from `source`. Symmetric to
139
- * {@link writeAssetToSource}: same writable check, same git-commit-and-push
140
- * convenience for `kind === "git"`.
139
+ * {@link writeAssetToSource}: same writable check, same unsupported-kind guard,
140
+ * a plain `unlink` with no commit. Git-backed targets are committed once at the
141
+ * operation boundary via {@link commitWriteTargetBoundary}.
141
142
  */
142
143
  export async function deleteAssetFromSource(source, config, ref) {
143
144
  ensureWritable(source, config);
145
+ assertSupportedKind(source);
144
146
  const filePath = resolveAssetFilePath(source, ref);
145
147
  if (!fs.existsSync(filePath)) {
146
148
  throw new UsageError(`Asset "${formatRefForMessage(ref)}" not found in source "${source.name}" (expected at ${filePath}).`, "MISSING_REQUIRED_ARGUMENT");
147
149
  }
148
150
  fs.unlinkSync(filePath);
149
- await runKindSpecificCommit(source, config, filePath, `Remove ${formatRefForMessage(ref)}`);
150
151
  return { path: filePath, ref: makeAssetRef(ref.type, ref.name, ref.origin) };
151
152
  }
153
+ /**
154
+ * Fire the one-shot batch-at-boundary commit for a resolved write target.
155
+ *
156
+ * 0.9.0 (issue #507): replaces the retired per-asset git commit. Callers invoke
157
+ * this EXACTLY ONCE after a batch of writes/deletes to a resolved write target.
158
+ * It is a no-op for any non-git target (plain filesystem sources and the
159
+ * primary stash stay non-committing here — the primary stash is committed by
160
+ * the existing improve auto-sync boundary).
161
+ *
162
+ * For a git target it delegates to `saveGitStash(name, message, writable, …)`,
163
+ * which stages `.akm/` + sibling assets together (`git add -A`), commits once,
164
+ * and pushes when the target is writable, has a remote, and `push !== false`.
165
+ *
166
+ * The push intent honours a deprecated `options.pushOnCommit` on the source
167
+ * config (mapped onto the batch push gate) when `push` is not explicitly set.
168
+ */
169
+ export function commitWriteTargetBoundary(target, message, options) {
170
+ if (target.source.kind !== "git")
171
+ return;
172
+ warnIfPushOnCommit(target.config);
173
+ // Map the deprecated per-asset `pushOnCommit` intent onto the batch push gate
174
+ // when the caller did not pass an explicit push toggle. `saveGitStash` still
175
+ // gates the actual push on writable + remote, so this only ever opts *in*.
176
+ const push = options?.push ?? (target.config.options?.pushOnCommit === true ? true : undefined);
177
+ const writable = resolveWritable(target.config);
178
+ // Commit against the already-resolved repo directory (target.source.path)
179
+ // rather than re-resolving the stash by name through config. The write helper
180
+ // resolved this exact path; the boundary commit must operate on the SAME
181
+ // directory so the staged batch matches what was just written.
182
+ saveGitStash(undefined, message, writable, {
183
+ repoDir: target.source.path,
184
+ ...(push === undefined ? {} : { push }),
185
+ });
186
+ }
187
+ /**
188
+ * Emit a one-time deprecation warning the first time a source config carrying
189
+ * `options.pushOnCommit` is encountered. The field still parses (for old
190
+ * configs) but its per-asset push-on-commit behaviour is retired; its intent is
191
+ * now honoured via the batch push gate (writable + remote + push toggle).
192
+ */
193
+ let pushOnCommitWarned = false;
194
+ function warnIfPushOnCommit(config) {
195
+ if (config.options?.pushOnCommit === undefined)
196
+ return;
197
+ if (pushOnCommitWarned)
198
+ return;
199
+ pushOnCommitWarned = true;
200
+ const label = config.name ? ` on source "${config.name}"` : "";
201
+ process.stderr.write(`warning: \`options.pushOnCommit\`${label} is deprecated (0.9.0) and no longer commits per asset. ` +
202
+ "akm now commits writes in a single batch at the operation boundary and pushes when the target is " +
203
+ "writable with a remote. Remove the option or rely on sync push instead.\n");
204
+ }
152
205
  /**
153
206
  * Resolve the destination for a write per locked decision 3:
154
207
  *
@@ -201,12 +254,10 @@ export function resolveWriteTarget(akmConfig, explicitTarget) {
201
254
  //
202
255
  // The primary stash stays `kind: "filesystem"` on purpose, even when it is a
203
256
  // git repo on disk (recognized elsewhere via isGitBackedStash). Returning
204
- // `kind: "git"` here would route every asset write through the per-asset
205
- // runGitCommit, which is INCOMPLETE (stages only the single asset file,
206
- // leaving .akm/proposals + other state dirty) and NOISY (one commit per
207
- // asset, ~25 per improve run). Recognition is decoupled: per-write stays
208
- // non-committing, and the primary stash is committed in a single batch at
209
- // operation boundaries (e.g. the end-of-run improve auto-sync via saveGitStash).
257
+ // `kind: "git"` here would fire the boundary commit on every write through
258
+ // this resolver, double-committing the primary stash which is already
259
+ // committed in a single batch at operation boundaries (e.g. the end-of-run
260
+ // improve auto-sync via saveGitStash). Per-write stays non-committing.
210
261
  try {
211
262
  const stashDir = resolveStashDir({ readOnly: true });
212
263
  return {
@@ -241,57 +292,19 @@ function resolveAssetFilePath(source, ref) {
241
292
  }
242
293
  return assetPath;
243
294
  }
244
- async function runKindSpecificCommit(source, config, filePath, message) {
245
- if (source.kind === "filesystem") {
246
- return; // No commit step.
247
- }
248
- if (source.kind === "git") {
249
- runGitCommit(source.path, filePath, message);
250
- if (config.options?.pushOnCommit) {
251
- runGitPush(source.path);
252
- }
295
+ /**
296
+ * Reject any kind reaching the write/delete helpers other than the two
297
+ * supported writable kinds. The config loader is the first line of defence
298
+ * (assertWritableAllowedForKind), but we throw here so external callers that
299
+ * bypass the loader still get a clear error.
300
+ */
301
+ function assertSupportedKind(source) {
302
+ if (source.kind === "filesystem" || source.kind === "git")
253
303
  return;
254
- }
255
- // Reject any other kind reaching the helper. The config loader is the
256
- // first line of defence (assertWritableAllowedForKind), but we throw here
257
- // so external callers that bypass the loader still get a clear error.
258
304
  throw new ConfigError(`write-source: unsupported kind "${source.kind}" for source "${source.name}". ` +
259
305
  "Writes are only defined for `filesystem` and `git` sources.", "INVALID_CONFIG_FILE", 'Set `kind: "filesystem"` (or `kind: "git"`) on the source, or add a parallel filesystem entry.');
260
306
  }
261
- function runGitCommit(repoDir, filePath, message) {
262
- // Stage the specific file rather than `add -A` so unrelated working-tree
263
- // changes don't get folded into the asset commit.
264
- const relPath = path.relative(repoDir, filePath) || filePath;
265
- const addResult = spawnSync("git", ["-C", repoDir, "add", "--", relPath], { encoding: "utf8" });
266
- if (addResult.status !== 0) {
267
- throw new Error(`git add failed: ${addResult.stderr?.trim() || "unknown error"}`);
268
- }
269
- // Defense in depth: sanitize the commit subject one more time at the spawn
270
- // boundary. Callers should already pass sanitized strings (via
271
- // formatRefForMessage / saveGitStash), but this guards against future
272
- // refactors that forget. Empty after sanitize falls back to a safe stub.
273
- const safeMessage = sanitizeCommitMessage(message) || "akm update";
274
- // Provide a fallback identity so fresh CI/test environments without
275
- // user.name/user.email configured can always commit.
276
- const commitResult = spawnSync("git", ["-C", repoDir, "-c", "user.name=akm", "-c", "user.email=akm@local", "commit", "-m", safeMessage], { encoding: "utf8" });
277
- if (commitResult.status !== 0) {
278
- // `nothing to commit` is a no-op success — the file may have matched the
279
- // existing tree exactly. Surface other errors verbatim.
280
- const stderr = commitResult.stderr ?? "";
281
- if (/nothing to commit|no changes added/i.test(stderr) ||
282
- /nothing to commit|no changes added/i.test(commitResult.stdout ?? "")) {
283
- return;
284
- }
285
- throw new Error(`git commit failed: ${stderr.trim() || "unknown error"}`);
286
- }
287
- }
288
- function runGitPush(repoDir) {
289
- const pushResult = spawnSync("git", ["-C", repoDir, "push"], { encoding: "utf8", timeout: 120_000 });
290
- if (pushResult.status !== 0) {
291
- throw new Error(`git push failed: ${pushResult.stderr?.trim() || "unknown error"}`);
292
- }
293
- }
294
- function formatRefForMessage(ref) {
307
+ export function formatRefForMessage(ref) {
295
308
  // Sanitize each component independently. `ref.origin` originates from user
296
309
  // config and could contain CR/LF that would otherwise be smuggled into the
297
310
  // commit subject and forge trailers downstream. `ref.type` and `ref.name`
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * MVP data-directory backup for AKM.
6
6
  *
7
- * The DB upgrade path in `src/indexer/db.ts` `handleVersionUpgrade()` is
7
+ * The DB upgrade path in `src/indexer/db/db.ts` `handleVersionUpgrade()` is
8
8
  * intentionally destructive: when `DB_VERSION` bumps and a stored DB is at an
9
9
  * older version, ~17 tables are dropped and recreated. Until 0.9.0 ships a
10
10
  * full migration framework, this MVP captures a recursive copy of the entire
@@ -27,7 +27,8 @@
27
27
  */
28
28
  import fs from "node:fs";
29
29
  import path from "node:path";
30
- import { warn } from "../core/warn";
30
+ import { bestEffort } from "../../core/best-effort.js";
31
+ import { warn } from "../../core/warn.js";
31
32
  /** Default reason recorded for backups that don't override it. */
32
33
  export const DEFAULT_BACKUP_REASON = "version-upgrade";
33
34
  /** Reason recorded for backups taken before the embedding-dim drop path. */
@@ -95,12 +96,9 @@ export function measureDataDirSize(dirPath) {
95
96
  stack.push(full);
96
97
  }
97
98
  else if (entry.isFile()) {
98
- try {
99
+ bestEffort(() => {
99
100
  total += fs.statSync(full).size;
100
- }
101
- catch {
102
- // File vanished between readdir and stat — ignore.
103
- }
101
+ }, "file vanished between readdir and stat");
104
102
  }
105
103
  }
106
104
  }
@@ -154,7 +152,7 @@ export function listBackups(dataDir) {
154
152
  let sizeBytes;
155
153
  let reason = DEFAULT_BACKUP_REASON;
156
154
  if (fs.existsSync(metaPath)) {
157
- try {
155
+ bestEffort(() => {
158
156
  const raw = fs.readFileSync(metaPath, "utf8");
159
157
  const parsed = JSON.parse(raw);
160
158
  if (typeof parsed.createdAt === "string")
@@ -167,10 +165,7 @@ export function listBackups(dataDir) {
167
165
  sizeBytes = parsed.sizeBytes;
168
166
  if (typeof parsed.reason === "string" && parsed.reason.length > 0)
169
167
  reason = parsed.reason;
170
- }
171
- catch {
172
- // Malformed metadata — fall back to filesystem-derived values.
173
- }
168
+ }, "malformed backup metadata — fall back to filesystem-derived values");
174
169
  }
175
170
  if (!createdAt) {
176
171
  try {
@@ -286,12 +281,7 @@ export function backupDataDir(opts) {
286
281
  catch (err) {
287
282
  warn("[akm] data dir backup failed — %s; upgrade will proceed without a snapshot", err instanceof Error ? err.message : String(err));
288
283
  // Best-effort cleanup of the partial copy so we don't litter the data dir.
289
- try {
290
- fs.rmSync(finalDest, { recursive: true, force: true });
291
- }
292
- catch {
293
- /* ignore */
294
- }
284
+ bestEffort(() => fs.rmSync(finalDest, { recursive: true, force: true }), "cleanup partial backup copy");
295
285
  return null;
296
286
  }
297
287
  const createdAt = now.toISOString();
@@ -369,12 +359,7 @@ function copyDataDirExcludingBackups(srcRoot, destRoot) {
369
359
  // dir occasionally carries symlinked source roots; following them
370
360
  // could explode the backup size unexpectedly.
371
361
  const target = fs.readlinkSync(srcPath);
372
- try {
373
- fs.symlinkSync(target, destPath);
374
- }
375
- catch {
376
- /* ignore — symlink creation can fail on Windows without admin */
377
- }
362
+ bestEffort(() => fs.symlinkSync(target, destPath), "symlink creation can fail on Windows without admin");
378
363
  }
379
364
  // Other entry types (block/character/fifo/socket) are silently skipped.
380
365
  }