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
@@ -1,333 +0,0 @@
1
- /**
2
- * Vault asset type — secret storage backed by `.env` files.
3
- *
4
- * Invariant: vault values must never be written to stdout, returned through
5
- * the indexer, the `akm show` renderer, or any structured output channel.
6
- * The supported load paths are:
7
- *
8
- * - `eval "$(akm vault load vault:<name>)"` — `vault load` parses the vault
9
- * with dotenv (no shell expansion, no code execution), writes a safely
10
- * single-quote-escaped `export KEY='value'` script to a mode-0600 temp
11
- * file, and emits `. <tmp>; rm -f <tmp>` on stdout. Values reach bash
12
- * only via the temp file, never via akm's stdout.
13
- * - `injectIntoEnv(vaultPath, target)` — programmatic API for modules that
14
- * need values in a process environment.
15
- *
16
- * Value parsing is delegated to the `dotenv` package — we deliberately do not
17
- * implement our own quoting/escaping rules for security-sensitive content.
18
- */
19
- import fs from "node:fs";
20
- import path from "node:path";
21
- import dotenv from "dotenv";
22
- /** Matches a KEY=value assignment line, capturing only the key. */
23
- const ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
24
- /** Scan lines and return KEY names in file order, without duplicates. */
25
- function scanKeys(text) {
26
- const keys = [];
27
- const seen = new Set();
28
- for (const line of text.split(/\r?\n/)) {
29
- const m = line.match(ASSIGN_RE);
30
- if (!m)
31
- continue;
32
- const key = m[1];
33
- if (seen.has(key))
34
- continue;
35
- seen.add(key);
36
- keys.push(key);
37
- }
38
- return keys;
39
- }
40
- /**
41
- * Scan lines and return start-of-line `#` comments (with the leading `#` and
42
- * any leading whitespace stripped). Inline/trailing `#` after an assignment is
43
- * never extracted.
44
- */
45
- function scanComments(text) {
46
- const comments = [];
47
- for (const line of text.split(/\r?\n/)) {
48
- const trimmed = line.trimStart();
49
- if (trimmed.startsWith("#")) {
50
- comments.push(trimmed.slice(1).trimStart());
51
- }
52
- }
53
- return comments;
54
- }
55
- /**
56
- * Read and return ONLY non-secret metadata (keys + start-of-line comments).
57
- *
58
- * The function reads the whole file into memory (same as any dotenv parser)
59
- * but deliberately does not parse values — the LHS-only regex scanners above
60
- * ensure no value content is retained or returned. The guarantee is that
61
- * values never leave this function.
62
- */
63
- export function listKeys(vaultPath) {
64
- if (!fs.existsSync(vaultPath))
65
- return { keys: [], comments: [] };
66
- const text = fs.readFileSync(vaultPath, "utf8");
67
- return { keys: scanKeys(text), comments: scanComments(text) };
68
- }
69
- /**
70
- * Return structured `entries` pairing each key with the nearest preceding
71
- * comment line (if any). This replaces the parallel `keys[]` + `comments[]`
72
- * shape used internally by `listKeys` with a single merged array, which is
73
- * easier for callers to consume (QA #35).
74
- *
75
- * Values are never included — the same privacy guarantee as `listKeys`.
76
- */
77
- export function listEntries(vaultPath) {
78
- if (!fs.existsSync(vaultPath))
79
- return [];
80
- const text = fs.readFileSync(vaultPath, "utf8");
81
- const lines = text.split(/\r?\n/);
82
- const seen = new Set();
83
- const entries = [];
84
- let pendingComment;
85
- for (const line of lines) {
86
- const trimmed = line.trimStart();
87
- if (trimmed.startsWith("#")) {
88
- // Capture the most recent comment before a key
89
- pendingComment = trimmed.slice(1).trimStart() || undefined;
90
- continue;
91
- }
92
- const m = line.match(ASSIGN_RE);
93
- if (m) {
94
- const key = m[1];
95
- if (!seen.has(key)) {
96
- seen.add(key);
97
- const entry = { key };
98
- if (pendingComment)
99
- entry.comment = pendingComment;
100
- entries.push(entry);
101
- }
102
- pendingComment = undefined;
103
- }
104
- else {
105
- // Any non-comment, non-assignment line (including blank lines)
106
- // breaks "nearest preceding comment line" association.
107
- pendingComment = undefined;
108
- }
109
- }
110
- return entries;
111
- }
112
- /**
113
- * Read all KEY=value pairs from a vault file. Intended for programmatic
114
- * callers that need to inject values into a process environment. Callers
115
- * MUST NOT write the returned values to stdout or any logged output.
116
- *
117
- * Value parsing (quoting, escapes, multi-line, etc.) is delegated to dotenv.
118
- */
119
- export function loadEnv(vaultPath) {
120
- if (!fs.existsSync(vaultPath))
121
- return {};
122
- const buf = fs.readFileSync(vaultPath);
123
- return dotenv.parse(buf);
124
- }
125
- /**
126
- * Load a vault and assign its values into `target` (defaults to `process.env`).
127
- * Returns the list of keys that were set so the caller can log/observe without
128
- * touching values.
129
- *
130
- * Existing keys in `target` are overwritten — callers who want to preserve
131
- * pre-existing environment variables should filter before calling.
132
- */
133
- export function injectIntoEnv(vaultPath, target = process.env) {
134
- const env = loadEnv(vaultPath);
135
- for (const [key, value] of Object.entries(env)) {
136
- target[key] = value;
137
- }
138
- return Object.keys(env);
139
- }
140
- /**
141
- * Serialise a vault's values as a POSIX shell script of `export KEY='value'`
142
- * lines, with single-quote escaping (`'\''`). Every line is an assignment of
143
- * a literal string — there is no expansion, command substitution, or
144
- * non-assignment content, so sourcing the output is safe regardless of what
145
- * the vault file contains.
146
- *
147
- * Intended for use by `akm vault load`, which writes this to a mode-0600
148
- * temp file and emits only the path (never values) on stdout.
149
- */
150
- export function buildShellExportScript(vaultPath) {
151
- const env = loadEnv(vaultPath);
152
- const lines = [];
153
- for (const [key, value] of Object.entries(env)) {
154
- // Defence in depth: dotenv already validates key shape, but reject any
155
- // key we wouldn't be able to export safely.
156
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
157
- continue;
158
- const escaped = value.replace(/'/g, "'\\''");
159
- lines.push(`export ${key}='${escaped}'`);
160
- }
161
- return lines.length > 0 ? `${lines.join("\n")}\n` : "";
162
- }
163
- /**
164
- * Set a key in the vault file, preserving line order and comments. Creates
165
- * the file (and parent directory) if it does not exist.
166
- *
167
- * `quoteValue` picks the safest representation that dotenv round-trips:
168
- * single-quoted when the value has no `'`, double-quoted when it has `'` but
169
- * no `"` and no literal `\n`/`\r` escape sequences, and unquoted only for
170
- * values that contain no characters requiring escaping (see quoteValue for
171
- * the full rule set). Values containing newlines or both quote types are
172
- * rejected outright. Round-trip safety is enforced by the test suite.
173
- *
174
- * When `comment` is provided it is written as a `# <comment>` line
175
- * immediately before the `KEY=value` line:
176
- * - New key: the comment line is inserted just before the appended key.
177
- * - Existing key: if the preceding line is already a comment it is replaced
178
- * with the new comment; otherwise a new comment line is inserted.
179
- * When `comment` is absent the surrounding comment lines are left unchanged.
180
- */
181
- export function setKey(vaultPath, key, value, comment) {
182
- validateKeyName(key);
183
- ensureParentDir(vaultPath);
184
- const existing = fs.existsSync(vaultPath) ? fs.readFileSync(vaultPath, "utf8") : "";
185
- const lines = existing.length > 0 ? existing.split(/\r?\n/) : [];
186
- const formatted = `${key}=${quoteValue(value)}`;
187
- let replaced = false;
188
- for (let i = 0; i < lines.length; i++) {
189
- const m = lines[i].match(ASSIGN_RE);
190
- if (m && m[1] === key) {
191
- lines[i] = formatted;
192
- replaced = true;
193
- if (comment !== undefined) {
194
- const commentLine = `# ${comment}`;
195
- const prevIsComment = i > 0 && lines[i - 1].trimStart().startsWith("#");
196
- if (prevIsComment) {
197
- lines[i - 1] = commentLine;
198
- }
199
- else {
200
- lines.splice(i, 0, commentLine);
201
- }
202
- }
203
- break;
204
- }
205
- }
206
- if (!replaced) {
207
- if (comment !== undefined) {
208
- const commentLine = `# ${comment}`;
209
- if (lines.length > 0 && lines[lines.length - 1] === "") {
210
- lines[lines.length - 1] = commentLine;
211
- lines.push(formatted);
212
- lines.push("");
213
- }
214
- else {
215
- lines.push(commentLine);
216
- lines.push(formatted);
217
- }
218
- }
219
- else if (lines.length > 0 && lines[lines.length - 1] === "") {
220
- lines[lines.length - 1] = formatted;
221
- lines.push("");
222
- }
223
- else {
224
- lines.push(formatted);
225
- }
226
- }
227
- let out = lines.join("\n");
228
- if (!out.endsWith("\n"))
229
- out += "\n";
230
- writeFileAtomic(vaultPath, out);
231
- }
232
- /** Remove a key from the vault file. Returns true if the key was present. */
233
- export function unsetKey(vaultPath, key) {
234
- if (!fs.existsSync(vaultPath))
235
- return false;
236
- const text = fs.readFileSync(vaultPath, "utf8");
237
- const lines = text.split(/\r?\n/);
238
- const kept = [];
239
- let removed = false;
240
- for (const line of lines) {
241
- const m = line.match(ASSIGN_RE);
242
- if (m && m[1] === key) {
243
- removed = true;
244
- continue;
245
- }
246
- kept.push(line);
247
- }
248
- if (!removed)
249
- return false;
250
- let out = kept.join("\n");
251
- if (out.length > 0 && !out.endsWith("\n"))
252
- out += "\n";
253
- writeFileAtomic(vaultPath, out);
254
- return true;
255
- }
256
- /** Create an empty vault file (does nothing if it already exists). */
257
- export function createVault(vaultPath) {
258
- ensureParentDir(vaultPath);
259
- if (fs.existsSync(vaultPath))
260
- return;
261
- writeFileAtomic(vaultPath, "");
262
- }
263
- /**
264
- * Characters that are safe in an UNquoted dotenv value AND are not
265
- * metacharacters in POSIX shells. Anything outside this set forces quoting,
266
- * which is defense-in-depth for any caller that might ever `source` the
267
- * vault file directly instead of going through `akm vault load`.
268
- */
269
- const UNQUOTED_SAFE_RE = /^[A-Za-z0-9_.:/@%+,-]+$/;
270
- /**
271
- * Quote a value for safe storage in a .env file that round-trips through
272
- * `dotenv.parse` AND is safe if the file is ever `source`d by a POSIX shell.
273
- *
274
- * Strategy:
275
- * - empty → empty
276
- * - all-safe chars (alnum + `_.:/@%+,-`) → unquoted
277
- * - no `'` → single-quote (dotenv and shell both treat single-quoted
278
- * content literally: no expansion, no escapes)
279
- * - no `"` and no literal `\n`/`\r` escape sequence → double-quote
280
- * (dotenv unescapes `\n`/`\r` on read, so we
281
- * can't double-quote a value that contains
282
- * those literal sequences)
283
- * - newlines or both quote types → reject
284
- *
285
- * dotenv intentionally does NOT support `\"` inside double-quoted values, so
286
- * we never produce that pattern.
287
- */
288
- function quoteValue(value) {
289
- if (value.length === 0)
290
- return "";
291
- if (/[\n\r]/.test(value)) {
292
- throw new Error("Vault values cannot contain literal newlines.");
293
- }
294
- if (UNQUOTED_SAFE_RE.test(value))
295
- return value;
296
- if (!value.includes("'"))
297
- return `'${value}'`;
298
- if (!value.includes('"') && !/\\[nr]/.test(value))
299
- return `"${value}"`;
300
- throw new Error("Vault value contains both single and double quote characters; not supported.");
301
- }
302
- function validateKeyName(key) {
303
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
304
- throw new Error(`Invalid vault key name: "${key}". Must match [A-Za-z_][A-Za-z0-9_]*`);
305
- }
306
- }
307
- function ensureParentDir(filePath) {
308
- const dir = path.dirname(filePath);
309
- if (!fs.existsSync(dir))
310
- fs.mkdirSync(dir, { recursive: true });
311
- }
312
- function writeFileAtomic(filePath, content) {
313
- const tmp = `${filePath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
314
- try {
315
- fs.writeFileSync(tmp, content, { encoding: "utf8", mode: 0o600 });
316
- fs.renameSync(tmp, filePath);
317
- try {
318
- fs.chmodSync(filePath, 0o600);
319
- }
320
- catch {
321
- /* best-effort on platforms without chmod */
322
- }
323
- }
324
- catch (err) {
325
- try {
326
- fs.unlinkSync(tmp);
327
- }
328
- catch {
329
- /* ignore cleanup failure */
330
- }
331
- throw err;
332
- }
333
- }
@@ -1,100 +0,0 @@
1
- /**
2
- * Scaffolded content for a fresh `wikis/<name>/` directory.
3
- *
4
- * Inlined as TypeScript constants so they ship with the published bundle
5
- * (the build step is `tsc` — non-TS files are not copied to dist).
6
- *
7
- * The scaffold is deliberately minimal: akm does not prescribe conventions
8
- * beyond the three-layer layout. `schema.md` is the per-wiki rulebook the
9
- * agent reads first; authors customise it freely.
10
- */
11
- export function buildSchemaMd(wikiName) {
12
- return `---
13
- description: Rules that govern this wiki. Read before ingesting, searching, or editing pages.
14
- wikiRole: schema
15
- ---
16
-
17
- # ${wikiName} wiki schema
18
-
19
- This wiki follows the three-layer pattern:
20
-
21
- - \`raw/\` — immutable ingested sources (never edit)
22
- - \`<page>.md\` and \`<topic>/<page>.md\` — agent-authored pages
23
- - \`schema.md\` (this file), \`index.md\`, \`log.md\` — wiki-level metadata
24
-
25
- ## Page frontmatter
26
-
27
- Every page should carry frontmatter so akm can index and link it:
28
-
29
- \`\`\`yaml
30
- ---
31
- description: one-sentence summary used in search and lint
32
- pageKind: entity | concept | question | note | <your-custom-kind>
33
- xrefs:
34
- - wiki:${wikiName}/other-page
35
- sources:
36
- - raw/<slug>.md
37
- ---
38
- \`\`\`
39
-
40
- \`pageKind\` accepts any non-empty string. Add new categories freely; they
41
- will surface in \`index.md\` as new sections after the next \`akm index\` run.
42
-
43
- ## Three operations
44
-
45
- ### Ingest
46
-
47
- 1. Copy the new source into \`raw/\` with \`akm wiki stash ${wikiName} <path>\`.
48
- 2. Find related pages: \`akm wiki search ${wikiName} "<terms>"\`.
49
- 3. For each related page: append a section, note a contradiction, or create a
50
- new page. Update xrefs on both sides.
51
- 4. Cite the raw source in each touched page's \`sources:\` frontmatter.
52
- 5. Append one entry to \`log.md\` describing what was assimilated.
53
-
54
- ### Query
55
-
56
- 1. \`akm wiki search ${wikiName} "<question>"\` — find candidate pages.
57
- 2. \`akm show wiki:${wikiName}/<page>\` — read the top hits.
58
- 3. Compose the answer from the wiki; cite raw sources only when the wiki
59
- points at them.
60
-
61
- ### Lint
62
-
63
- 1. \`akm wiki lint ${wikiName}\` — deterministic structural checks.
64
- 2. Resolve each finding: link orphans, fix broken xrefs, add descriptions,
65
- cite uncited raws, refresh the index.
66
-
67
- ## Hard rules
68
-
69
- - \`raw/\` is immutable. Never edit ingested sources.
70
- - Cross-references must point at pages that actually exist.
71
- - Prefer appending to an existing page over duplicating one.
72
- - Cite the raw source id (e.g. \`raw/2026-04-foo.md\`) when copying claims.
73
- `;
74
- }
75
- export function buildIndexMd(wikiName) {
76
- return `---
77
- description: Catalog of pages in the ${wikiName} wiki. Regenerated by \`akm index\`.
78
- wikiRole: index
79
- ---
80
-
81
- # ${wikiName} — index
82
-
83
- _This file is regenerated on every \`akm index\` run. Manual edits are
84
- preserved until the next regeneration, then replaced._
85
-
86
- _(no pages yet — create one with your editor, or ingest a source with \`akm
87
- wiki stash ${wikiName} <path>\`.)_
88
- `;
89
- }
90
- export function buildLogMd(wikiName) {
91
- return `---
92
- description: Append-only log for the ${wikiName} wiki. Newest entries at the top.
93
- wikiRole: log
94
- ---
95
-
96
- # ${wikiName} — log
97
-
98
- _Each entry: ISO date, operation, brief summary._
99
- `;
100
- }