akm-cli 0.7.4 → 0.8.0-rc.10

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 (300) hide show
  1. package/CHANGELOG.md +224 -1
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2631 -1440
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +110 -0
  12. package/dist/commands/agent-support.js +68 -0
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +130 -534
  15. package/dist/commands/consolidate.js +2122 -0
  16. package/dist/commands/curate.js +45 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +660 -0
  19. package/dist/commands/distill.js +1081 -73
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +43 -0
  22. package/dist/commands/events.js +15 -24
  23. package/dist/commands/extract-cli.js +127 -0
  24. package/dist/commands/extract-prompt.js +204 -0
  25. package/dist/commands/extract.js +477 -0
  26. package/dist/commands/feedback-cli.js +331 -0
  27. package/dist/commands/graph.js +477 -0
  28. package/dist/commands/health.js +1302 -0
  29. package/dist/commands/help/help-accept.md +12 -0
  30. package/dist/commands/help/help-improve.md +69 -0
  31. package/dist/commands/help/help-proposals.md +18 -0
  32. package/dist/commands/help/help-propose.md +17 -0
  33. package/dist/commands/help/help-reject.md +11 -0
  34. package/dist/commands/history.js +54 -46
  35. package/dist/commands/improve-auto-accept.js +97 -0
  36. package/dist/commands/improve-cli.js +217 -0
  37. package/dist/commands/improve-profiles.js +166 -0
  38. package/dist/commands/improve-result-file.js +167 -0
  39. package/dist/commands/improve.js +2373 -0
  40. package/dist/commands/info.js +5 -2
  41. package/dist/commands/init.js +50 -2
  42. package/dist/commands/installed-stashes.js +102 -139
  43. package/dist/commands/knowledge.js +136 -0
  44. package/dist/commands/lint/agent-linter.js +49 -0
  45. package/dist/commands/lint/base-linter.js +479 -0
  46. package/dist/commands/lint/command-linter.js +49 -0
  47. package/dist/commands/lint/default-linter.js +16 -0
  48. package/dist/commands/lint/env-key-rules.js +154 -0
  49. package/dist/commands/lint/index.js +196 -0
  50. package/dist/commands/lint/knowledge-linter.js +16 -0
  51. package/dist/commands/lint/markdown-insertion.js +343 -0
  52. package/dist/commands/lint/memory-linter.js +61 -0
  53. package/dist/commands/lint/registry.js +36 -0
  54. package/dist/commands/lint/skill-linter.js +45 -0
  55. package/dist/commands/lint/task-linter.js +50 -0
  56. package/dist/commands/lint/types.js +4 -0
  57. package/dist/commands/lint/workflow-linter.js +56 -0
  58. package/dist/commands/lint.js +4 -0
  59. package/dist/commands/migration-help.js +3 -0
  60. package/dist/commands/proposal.js +67 -12
  61. package/dist/commands/propose.js +120 -45
  62. package/dist/commands/reflect.js +1104 -60
  63. package/dist/commands/registry-cli.js +150 -0
  64. package/dist/commands/registry-search.js +5 -2
  65. package/dist/commands/remember-cli.js +257 -0
  66. package/dist/commands/remember.js +70 -7
  67. package/dist/commands/schema-repair.js +203 -0
  68. package/dist/commands/search.js +115 -14
  69. package/dist/commands/secret.js +173 -0
  70. package/dist/commands/self-update.js +3 -0
  71. package/dist/commands/show.js +158 -60
  72. package/dist/commands/source-add.js +17 -45
  73. package/dist/commands/source-clone.js +3 -0
  74. package/dist/commands/source-manage.js +14 -19
  75. package/dist/commands/tasks.js +437 -0
  76. package/dist/commands/url-checker.js +42 -0
  77. package/dist/core/action-contributors.js +28 -0
  78. package/dist/core/asset-ref.js +17 -2
  79. package/dist/core/asset-registry.js +12 -17
  80. package/dist/core/asset-serialize.js +88 -0
  81. package/dist/core/asset-spec.js +67 -1
  82. package/dist/core/common.js +182 -0
  83. package/dist/core/concurrent.js +25 -0
  84. package/dist/core/config-io.js +347 -0
  85. package/dist/core/config-migration.js +622 -0
  86. package/dist/core/config-schema.js +534 -0
  87. package/dist/core/config-sources.js +108 -0
  88. package/dist/core/config-types.js +4 -0
  89. package/dist/core/config-walker.js +337 -0
  90. package/dist/core/config.js +364 -968
  91. package/dist/core/errors.js +42 -20
  92. package/dist/core/events.js +105 -135
  93. package/dist/core/file-lock.js +104 -0
  94. package/dist/core/frontmatter.js +75 -8
  95. package/dist/core/lesson-lint.js +3 -0
  96. package/dist/core/markdown.js +20 -0
  97. package/dist/core/memory-belief.js +62 -0
  98. package/dist/core/memory-contradiction-detect.js +274 -0
  99. package/dist/core/memory-improve.js +806 -0
  100. package/dist/core/parse.js +158 -0
  101. package/dist/core/paths.js +280 -14
  102. package/dist/core/proposal-quality-validators.js +380 -0
  103. package/dist/core/proposal-validators.js +69 -0
  104. package/dist/core/proposals.js +512 -42
  105. package/dist/core/state-db.js +1068 -0
  106. package/dist/core/text-truncation.js +107 -0
  107. package/dist/core/time.js +54 -0
  108. package/dist/core/tty.js +59 -0
  109. package/dist/core/warn.js +64 -1
  110. package/dist/core/write-source.js +3 -0
  111. package/dist/indexer/db-backup.js +391 -0
  112. package/dist/indexer/db-search.js +198 -489
  113. package/dist/indexer/db.js +990 -108
  114. package/dist/indexer/ensure-index.js +136 -0
  115. package/dist/indexer/file-context.js +3 -0
  116. package/dist/indexer/graph-boost.js +376 -101
  117. package/dist/indexer/graph-db.js +391 -0
  118. package/dist/indexer/graph-dedup.js +95 -0
  119. package/dist/indexer/graph-extraction.js +550 -114
  120. package/dist/indexer/index-context.js +4 -0
  121. package/dist/indexer/indexer.js +547 -309
  122. package/dist/indexer/llm-cache.js +52 -0
  123. package/dist/indexer/manifest.js +3 -0
  124. package/dist/indexer/matchers.js +167 -160
  125. package/dist/indexer/memory-inference.js +152 -74
  126. package/dist/indexer/metadata-contributors.js +29 -0
  127. package/dist/indexer/metadata.js +275 -196
  128. package/dist/indexer/path-resolver.js +92 -0
  129. package/dist/indexer/project-context.js +192 -0
  130. package/dist/indexer/ranking-contributors.js +331 -0
  131. package/dist/indexer/ranking.js +81 -0
  132. package/dist/indexer/search-fields.js +5 -9
  133. package/dist/indexer/search-hit-enrichers.js +111 -0
  134. package/dist/indexer/search-source.js +44 -10
  135. package/dist/indexer/semantic-status.js +6 -17
  136. package/dist/indexer/staleness-detect.js +447 -0
  137. package/dist/indexer/usage-events.js +12 -9
  138. package/dist/indexer/walker.js +28 -0
  139. package/dist/integrations/agent/builders.js +135 -0
  140. package/dist/integrations/agent/config.js +122 -230
  141. package/dist/integrations/agent/detect.js +3 -0
  142. package/dist/integrations/agent/index.js +7 -13
  143. package/dist/integrations/agent/model-aliases.js +55 -0
  144. package/dist/integrations/agent/profiles.js +70 -5
  145. package/dist/integrations/agent/prompts.js +250 -36
  146. package/dist/integrations/agent/runner.js +151 -0
  147. package/dist/integrations/agent/sdk-runner.js +126 -0
  148. package/dist/integrations/agent/spawn.js +183 -35
  149. package/dist/integrations/github.js +3 -0
  150. package/dist/integrations/lockfile.js +32 -69
  151. package/dist/integrations/session-logs/index.js +69 -0
  152. package/dist/integrations/session-logs/inline-refs.js +35 -0
  153. package/dist/integrations/session-logs/pre-filter.js +152 -0
  154. package/dist/integrations/session-logs/providers/claude-code.js +282 -0
  155. package/dist/integrations/session-logs/providers/opencode.js +258 -0
  156. package/dist/integrations/session-logs/types.js +4 -0
  157. package/dist/llm/call-ai.js +62 -0
  158. package/dist/llm/client.js +79 -88
  159. package/dist/llm/embedder.js +20 -29
  160. package/dist/llm/embedders/cache.js +3 -7
  161. package/dist/llm/embedders/local.js +42 -1
  162. package/dist/llm/embedders/remote.js +20 -8
  163. package/dist/llm/embedders/types.js +3 -7
  164. package/dist/llm/feature-gate.js +95 -48
  165. package/dist/llm/graph-extract.js +676 -72
  166. package/dist/llm/index-passes.js +44 -29
  167. package/dist/llm/memory-infer.js +80 -71
  168. package/dist/llm/metadata-enhance.js +42 -29
  169. package/dist/llm/prompts/extract-session.md +80 -0
  170. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  171. package/dist/output/cli-hints-full.md +292 -0
  172. package/dist/output/cli-hints-short.md +66 -0
  173. package/dist/output/cli-hints.js +7 -311
  174. package/dist/output/context.js +60 -8
  175. package/dist/output/renderers.js +306 -258
  176. package/dist/output/shapes/curate.js +56 -0
  177. package/dist/output/shapes/distill.js +10 -0
  178. package/dist/output/shapes/env-list.js +19 -0
  179. package/dist/output/shapes/events.js +11 -0
  180. package/dist/output/shapes/helpers.js +424 -0
  181. package/dist/output/shapes/history.js +7 -0
  182. package/dist/output/shapes/passthrough.js +102 -0
  183. package/dist/output/shapes/proposal-accept.js +7 -0
  184. package/dist/output/shapes/proposal-diff.js +7 -0
  185. package/dist/output/shapes/proposal-list.js +7 -0
  186. package/dist/output/shapes/proposal-producer.js +11 -0
  187. package/dist/output/shapes/proposal-reject.js +7 -0
  188. package/dist/output/shapes/proposal-show.js +7 -0
  189. package/dist/output/shapes/registry-search.js +6 -0
  190. package/dist/output/shapes/registry.js +30 -0
  191. package/dist/output/shapes/search.js +6 -0
  192. package/dist/output/shapes/secret-list.js +19 -0
  193. package/dist/output/shapes/show.js +6 -0
  194. package/dist/output/shapes/vault-list.js +19 -0
  195. package/dist/output/shapes.js +51 -511
  196. package/dist/output/text/add.js +6 -0
  197. package/dist/output/text/clone.js +6 -0
  198. package/dist/output/text/config.js +6 -0
  199. package/dist/output/text/curate.js +6 -0
  200. package/dist/output/text/distill.js +7 -0
  201. package/dist/output/text/enable-disable.js +7 -0
  202. package/dist/output/text/events.js +10 -0
  203. package/dist/output/text/feedback.js +6 -0
  204. package/dist/output/text/helpers.js +1039 -0
  205. package/dist/output/text/history.js +7 -0
  206. package/dist/output/text/import.js +6 -0
  207. package/dist/output/text/index.js +6 -0
  208. package/dist/output/text/info.js +6 -0
  209. package/dist/output/text/init.js +6 -0
  210. package/dist/output/text/list.js +6 -0
  211. package/dist/output/text/proposal-producer.js +8 -0
  212. package/dist/output/text/proposal.js +11 -0
  213. package/dist/output/text/registry-commands.js +11 -0
  214. package/dist/output/text/registry.js +30 -0
  215. package/dist/output/text/remember.js +6 -0
  216. package/dist/output/text/remove.js +6 -0
  217. package/dist/output/text/save.js +6 -0
  218. package/dist/output/text/search.js +6 -0
  219. package/dist/output/text/show.js +6 -0
  220. package/dist/output/text/update.js +6 -0
  221. package/dist/output/text/upgrade.js +6 -0
  222. package/dist/output/text/vault.js +16 -0
  223. package/dist/output/text/wiki.js +15 -0
  224. package/dist/output/text/workflow.js +14 -0
  225. package/dist/output/text.js +44 -1093
  226. package/dist/registry/build-index.js +3 -0
  227. package/dist/registry/create-provider-registry.js +3 -0
  228. package/dist/registry/factory.js +4 -1
  229. package/dist/registry/origin-resolve.js +3 -0
  230. package/dist/registry/providers/index.js +3 -0
  231. package/dist/registry/providers/skills-sh.js +71 -50
  232. package/dist/registry/providers/static-index.js +53 -48
  233. package/dist/registry/providers/types.js +3 -24
  234. package/dist/registry/resolve.js +11 -16
  235. package/dist/registry/types.js +3 -0
  236. package/dist/scripts/migrate-storage.js +17750 -0
  237. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  238. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  239. package/dist/setup/detect.js +3 -0
  240. package/dist/setup/ripgrep-install.js +3 -0
  241. package/dist/setup/ripgrep-resolve.js +3 -0
  242. package/dist/setup/setup.js +775 -37
  243. package/dist/setup/steps.js +3 -15
  244. package/dist/sources/include.js +3 -0
  245. package/dist/sources/provider-factory.js +5 -12
  246. package/dist/sources/provider.js +3 -20
  247. package/dist/sources/providers/filesystem.js +19 -23
  248. package/dist/sources/providers/git.js +179 -20
  249. package/dist/sources/providers/index.js +3 -0
  250. package/dist/sources/providers/install-types.js +3 -13
  251. package/dist/sources/providers/npm.js +3 -4
  252. package/dist/sources/providers/provider-utils.js +3 -0
  253. package/dist/sources/providers/sync-from-ref.js +3 -11
  254. package/dist/sources/providers/tar-utils.js +3 -0
  255. package/dist/sources/providers/website.js +18 -22
  256. package/dist/sources/resolve.js +3 -0
  257. package/dist/sources/types.js +3 -0
  258. package/dist/sources/website-ingest.js +7 -0
  259. package/dist/tasks/backends/cron.js +203 -0
  260. package/dist/tasks/backends/exec-utils.js +28 -0
  261. package/dist/tasks/backends/index.js +24 -0
  262. package/dist/tasks/backends/launchd-template.xml +19 -0
  263. package/dist/tasks/backends/launchd.js +187 -0
  264. package/dist/tasks/backends/schtasks-template.xml +29 -0
  265. package/dist/tasks/backends/schtasks.js +215 -0
  266. package/dist/tasks/parser.js +211 -0
  267. package/dist/tasks/resolveAkmBin.js +87 -0
  268. package/dist/tasks/runner.js +458 -0
  269. package/dist/tasks/schedule.js +227 -0
  270. package/dist/tasks/schema.js +15 -0
  271. package/dist/tasks/validator.js +62 -0
  272. package/dist/version.js +3 -0
  273. package/dist/wiki/index-template.md +12 -0
  274. package/dist/wiki/ingest-workflow-template.md +54 -0
  275. package/dist/wiki/log-template.md +8 -0
  276. package/dist/wiki/schema-template.md +61 -0
  277. package/dist/wiki/wiki-templates.js +15 -0
  278. package/dist/wiki/wiki.js +13 -61
  279. package/dist/workflows/authoring.js +8 -25
  280. package/dist/workflows/cli.js +3 -0
  281. package/dist/workflows/db.js +141 -2
  282. package/dist/workflows/document-cache.js +3 -10
  283. package/dist/workflows/parser.js +3 -0
  284. package/dist/workflows/renderer.js +11 -3
  285. package/dist/workflows/runs.js +91 -89
  286. package/dist/workflows/schema.js +3 -0
  287. package/dist/workflows/scope-key.js +79 -0
  288. package/dist/workflows/validator.js +4 -8
  289. package/dist/workflows/workflow-template.md +24 -0
  290. package/docs/README.md +10 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.0.md +1 -1
  293. package/docs/migration/release-notes/0.7.4.md +1 -1
  294. package/docs/migration/release-notes/0.7.5.md +20 -0
  295. package/docs/migration/release-notes/0.8.0.md +48 -0
  296. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  297. package/package.json +29 -11
  298. package/dist/commands/install-audit.js +0 -381
  299. package/dist/commands/vault.js +0 -333
  300. package/dist/templates/wiki-templates.js +0 -100
@@ -0,0 +1,111 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { makeAssetRef } from "../core/asset-ref";
5
+ import { getDerivedForParent } from "./db";
6
+ import { getRenderer } from "./file-context";
7
+ const rendererSearchHitEnricher = {
8
+ name: "renderer-search-hit-enricher",
9
+ appliesTo(ctx) {
10
+ return ctx.rendererRegistry.rendererNameFor(ctx.type) !== undefined;
11
+ },
12
+ async enrich(hit, ctx) {
13
+ const rendererName = ctx.rendererRegistry.rendererNameFor(ctx.type);
14
+ if (!rendererName)
15
+ return;
16
+ const renderer = await getRenderer(rendererName);
17
+ renderer?.enrichSearchHit?.(hit, ctx.stashDir);
18
+ },
19
+ };
20
+ /**
21
+ * Phase 5A / Advantage D5 — derived-memory enricher.
22
+ *
23
+ * When a parent memory has a `.derived` child indexed (the LLM-distilled
24
+ * lesson surface), this enricher rewrites the parent hit to surface the
25
+ * derived child's description / searchHints / tags AND sets `expandTo` to
26
+ * the derived child's ref so callers can fetch it via `akm show <ref>`.
27
+ *
28
+ * The parent ref is preserved on the hit — only the surface text is
29
+ * swapped, so links and provenance still point at the canonical parent.
30
+ *
31
+ * Skipped for:
32
+ * - non-memory hits
33
+ * - memory hits that are themselves derived children (name ends with
34
+ * `.derived`) — we never recurse parent→child→grandchild
35
+ * - contexts without an open DB connection
36
+ */
37
+ export const derivedMemoryEnricher = {
38
+ name: "derived-memory-enricher",
39
+ appliesTo(ctx) {
40
+ return ctx.type === "memory" && ctx.db !== undefined;
41
+ },
42
+ enrich(hit, ctx) {
43
+ if (!ctx.db)
44
+ return;
45
+ // Never recurse: a `.derived` hit is itself the child surface; leaving
46
+ // it untouched also avoids `<parent>.derived.derived` chains.
47
+ if (hit.name.toLowerCase().endsWith(".derived"))
48
+ return;
49
+ // Parent ref shape: `memory:<name>`. Re-build from the entry's name
50
+ // so we don't depend on whatever wiki/registry prefix `hit.ref` carries.
51
+ const parentRef = makeAssetRef("memory", hit.name);
52
+ const derived = getDerivedForParent(ctx.db, parentRef);
53
+ if (!derived)
54
+ return;
55
+ // Swap description / searchHints / tags from the derived child.
56
+ // The parent ref itself is preserved — only the surface text is swapped.
57
+ if (typeof derived.entry.description === "string" && derived.entry.description.length > 0) {
58
+ hit.description = derived.entry.description;
59
+ }
60
+ if (Array.isArray(derived.entry.searchHints) && derived.entry.searchHints.length > 0) {
61
+ // We don't have a `searchHints` field on SourceSearchHit today — it's
62
+ // only used inside ranking. The plan says to swap when present; we
63
+ // record it onto the hit only if a future renderer surfaces it. For
64
+ // now, treat as advisory (no-op when SearchHit lacks the field).
65
+ }
66
+ if (Array.isArray(derived.entry.tags) && derived.entry.tags.length > 0) {
67
+ hit.tags = derived.entry.tags;
68
+ }
69
+ hit.expandTo = makeAssetRef("memory", derived.entry.name);
70
+ },
71
+ };
72
+ /**
73
+ * Registry of additional enrichers — populated by
74
+ * {@link registerSearchHitEnricher} and consumed in addition to
75
+ * {@link defaultSearchHitEnrichers} when `enrichSearchHit` is invoked
76
+ * without an explicit enricher list.
77
+ *
78
+ * Kept module-local so callers must use `registerSearchHitEnricher` rather
79
+ * than mutating the array directly.
80
+ */
81
+ const additionalEnrichers = [];
82
+ export const defaultSearchHitEnrichers = [rendererSearchHitEnricher, derivedMemoryEnricher];
83
+ /**
84
+ * Register an additional enricher to be applied alongside the defaults.
85
+ *
86
+ * Idempotent on `name`: subsequent calls with the same name replace the
87
+ * previously-registered enricher (so tests can re-register cleanly without
88
+ * stacking duplicates).
89
+ */
90
+ export function registerSearchHitEnricher(enricher) {
91
+ const existingIndex = additionalEnrichers.findIndex((e) => e.name === enricher.name);
92
+ if (existingIndex >= 0) {
93
+ additionalEnrichers[existingIndex] = enricher;
94
+ }
95
+ else {
96
+ additionalEnrichers.push(enricher);
97
+ }
98
+ }
99
+ /**
100
+ * Test-only: clear the registered-enrichers list. Not part of the public API.
101
+ */
102
+ export function _resetRegisteredSearchHitEnrichers() {
103
+ additionalEnrichers.length = 0;
104
+ }
105
+ export async function enrichSearchHit(hit, ctx, enrichers = [...defaultSearchHitEnrichers, ...additionalEnrichers]) {
106
+ for (const enricher of enrichers) {
107
+ if (!enricher.appliesTo(ctx))
108
+ continue;
109
+ await enricher.enrich(hit, ctx);
110
+ }
111
+ }
@@ -1,7 +1,10 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  import fs from "node:fs";
2
5
  import path from "node:path";
3
6
  import { resolveStashDir } from "../core/common";
4
- import { loadConfig } from "../core/config";
7
+ import { getSources, loadConfig } from "../core/config";
5
8
  import { resolveSourceProviderFactory } from "../sources/provider-factory";
6
9
  // Eager side-effect imports so all built-in source providers self-register
7
10
  // before resolveEntryContentDir() runs.
@@ -21,7 +24,7 @@ const GIT_STASH_TYPES = new Set(["git"]);
21
24
  * 1. The primary stash directory (the entry marked `primary: true`, or the
22
25
  * legacy top-level `stashDir`). Always emitted, even when the directory
23
26
  * does not yet exist on disk, so callers can use it as the clone target.
24
- * 2. Each entry in `config.sources ?? config.stashes[]` (in declared order), excluding the
27
+ * 2. Each entry in `config.sources[]` (in declared order), excluding the
25
28
  * one already emitted as the primary.
26
29
  * 3. Each entry in `config.installed[]` (registry-managed stashes).
27
30
  *
@@ -32,12 +35,29 @@ const GIT_STASH_TYPES = new Set(["git"]);
32
35
  export function resolveSourceEntries(overrideStashDir, existingConfig) {
33
36
  const stashDir = overrideStashDir ?? resolveStashDir();
34
37
  const config = existingConfig ?? loadConfig();
35
- const sources = [{ path: stashDir }];
38
+ // Primary stash is always writable.
39
+ const sources = [{ path: stashDir, writable: true }];
36
40
  const seen = new Set([path.resolve(stashDir)]);
37
- const addSource = (dir, registryId, wikiName) => {
41
+ const addSource = (dir, registryId, wikiName, writable) => {
38
42
  const resolved = path.resolve(dir);
39
- if (seen.has(resolved))
43
+ if (seen.has(resolved)) {
44
+ // Already in the source list — typically the primary stash injected at
45
+ // sources[0] before this loop. Enrich that entry with whatever metadata
46
+ // the matching config source carries so `--source <config-name>` can
47
+ // find it via registryId. Without this, the primary stash entry stays
48
+ // identity-less and a user-named primary source ("name": "my-stash")
49
+ // would validate but match zero entries when filtering.
50
+ const existing = sources.find((s) => s.path === resolved);
51
+ if (existing) {
52
+ if (registryId && !existing.registryId)
53
+ existing.registryId = registryId;
54
+ if (wikiName && !existing.wikiName)
55
+ existing.wikiName = wikiName;
56
+ if (writable && !existing.writable)
57
+ existing.writable = true;
58
+ }
40
59
  return;
60
+ }
41
61
  seen.add(resolved);
42
62
  if (isSuspiciousStashRoot(dir)) {
43
63
  warn(`Warning: stash root "${dir}" appears to be a system directory. This may be unintentional.`);
@@ -47,13 +67,14 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
47
67
  path: resolved,
48
68
  ...(registryId ? { registryId } : {}),
49
69
  ...(wikiName ? { wikiName } : {}),
70
+ ...(writable ? { writable: true } : {}),
50
71
  });
51
72
  }
52
73
  };
53
74
  // (1) + (2) Single pass over declared stashes — primary first if present,
54
75
  // then the rest in declared order. The primary's directory is already
55
76
  // injected as `sources[0]` above, so we only need to dedupe the source set.
56
- const stashes = config.sources ?? config.stashes ?? [];
77
+ const stashes = getSources(config);
57
78
  const primaryIdx = stashes.findIndex((entry) => entry.primary === true);
58
79
  const ordered = [];
59
80
  if (primaryIdx >= 0) {
@@ -72,11 +93,12 @@ export function resolveSourceEntries(overrideStashDir, existingConfig) {
72
93
  const dir = resolveEntryContentDir(entry);
73
94
  if (dir == null)
74
95
  continue;
75
- addSource(dir, entry.name, entry.wikiName);
96
+ addSource(dir, entry.name, entry.wikiName, entry.writable === true);
76
97
  }
77
98
  // (3) Installed stashes (registry-managed). Always last.
99
+ // Only installed entries explicitly marked writable: true are considered writable.
78
100
  for (const entry of config.installed ?? []) {
79
- addSource(entry.stashRoot, entry.id, entry.wikiName);
101
+ addSource(entry.stashRoot, entry.id, entry.wikiName, entry.writable === true);
80
102
  }
81
103
  return sources;
82
104
  }
@@ -132,6 +154,19 @@ function resolveEntryContentDir(entry) {
132
154
  export function resolveAllStashDirs(overrideStashDir) {
133
155
  return resolveSourceEntries(overrideStashDir).map((s) => s.path);
134
156
  }
157
+ /**
158
+ * Return the resolved absolute paths of all writable stash sources.
159
+ *
160
+ * The primary stash is always writable. Filesystem/git sources that have
161
+ * `writable: true` in config are also included. Registry-cached sources
162
+ * (installed without `writable: true`) are excluded because they are
163
+ * overwritten on `akm update` and must never be mutated.
164
+ */
165
+ export function getWritableStashDirs(overrideStashDir, existingConfig) {
166
+ return resolveSourceEntries(overrideStashDir, existingConfig)
167
+ .filter((s) => s.writable === true)
168
+ .map((s) => s.path);
169
+ }
135
170
  /**
136
171
  * Find which source a file path belongs to.
137
172
  */
@@ -226,8 +261,7 @@ function isValidDirectory(dir) {
226
261
  export async function ensureSourceCaches(config, options) {
227
262
  const cfg = config ?? loadConfig();
228
263
  const force = options?.force === true;
229
- // Use sources[] (current key) with fallback to stashes[] (deprecated, one-release compat).
230
- const entries = cfg.sources ?? cfg.stashes ?? [];
264
+ const entries = getSources(cfg);
231
265
  for (const entry of entries) {
232
266
  if (!GIT_STASH_TYPES.has(entry.type) || !entry.url || entry.enabled === false)
233
267
  continue;
@@ -1,7 +1,10 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
1
4
  import fs from "node:fs";
2
- import path from "node:path";
5
+ import { writeFileAtomic } from "../core/common";
3
6
  import { getCacheDir, getSemanticStatusPath } from "../core/paths";
4
- import { DEFAULT_LOCAL_MODEL } from "../llm/embedder";
7
+ import { DEFAULT_LOCAL_MODEL } from "../llm/embedders/local";
5
8
  export function deriveSemanticProviderFingerprint(embedding) {
6
9
  if (embedding?.endpoint) {
7
10
  return `remote:${embedding.endpoint}|${embedding.model}|${embedding.dimension ?? "default"}`;
@@ -41,21 +44,7 @@ export function readSemanticStatus() {
41
44
  export function writeSemanticStatus(status) {
42
45
  const dir = getCacheDir();
43
46
  fs.mkdirSync(dir, { recursive: true });
44
- const filePath = getSemanticStatusPath();
45
- const tmpPath = path.join(dir, `semantic-status.json.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`);
46
- fs.writeFileSync(tmpPath, `${JSON.stringify(status, null, 2)}\n`, "utf8");
47
- try {
48
- fs.renameSync(tmpPath, filePath);
49
- }
50
- catch (err) {
51
- try {
52
- fs.unlinkSync(tmpPath);
53
- }
54
- catch {
55
- /* ignore cleanup failure */
56
- }
57
- throw err;
58
- }
47
+ writeFileAtomic(getSemanticStatusPath(), `${JSON.stringify(status, null, 2)}\n`);
59
48
  }
60
49
  export function clearSemanticStatus() {
61
50
  try {