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,534 @@
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
+ /**
5
+ * Zod schema for AkmConfig — the single source of truth for the on-disk shape.
6
+ *
7
+ * Two responsibilities:
8
+ * 1. **Validate + transform** the raw JSON-parsed config object into the runtime
9
+ * `AkmConfig` shape consumed by the rest of the codebase. Replaces the
10
+ * ~1.4k LOC of legacy per-shape parsers (parseLlmConfig, parseEmbeddingConfig,
11
+ * parseIndexConfig, etc.) — see `loadConfig` in `./config.ts`.
12
+ * 2. **Reject hard-errored values** (openviking source type, legacy
13
+ * `stashes[]` key) at load time via `superRefine`.
14
+ *
15
+ * Design rules:
16
+ * - Top-level uses `.passthrough()` so unknown future keys round-trip intact on
17
+ * read; `sanitizeConfigForWrite` decides what to persist.
18
+ * - Most nested sub-objects use `.catch(undefined)` so malformed entries are
19
+ * silently dropped (matches the legacy parser's warn-and-ignore semantics for
20
+ * field-level shape errors — keeps cold-start working when a user has a
21
+ * typo in their config).
22
+ * - Two exceptions (hard-rejected): openviking source type and legacy
23
+ * `stashes[]` key. Both have explicit migration paths; silently dropping
24
+ * would mask user data loss.
25
+ * - `.strict()` walls still gate `registries[]`, `sources[]`, `profiles.*`
26
+ * sub-shapes so typos in those structured records are caught (#462).
27
+ * - `defaultWriteTarget` resolution and similar cross-field invariants are
28
+ * enforced at save time via `superRefine` on the top-level schema.
29
+ */
30
+ import { z } from "zod";
31
+ // ── Reusable atomic schemas ─────────────────────────────────────────────────
32
+ /** Positive integer (used for tokens, timeouts, batch sizes). */
33
+ const positiveInt = z.number().int().positive();
34
+ /** Non-negative finite number (used for scores, weights, days). */
35
+ const nonNegativeNumber = z.number().finite().min(0);
36
+ /** Non-empty string (rejects "" and whitespace-only). */
37
+ const nonEmptyString = z
38
+ .string()
39
+ .min(1)
40
+ .refine((v) => v.trim().length > 0, { message: "expected a non-empty string" });
41
+ /** HTTP(S) URL string. */
42
+ const httpUrl = z.string().refine((v) => v.startsWith("http://") || v.startsWith("https://"), {
43
+ message: "endpoint must start with http:// or https://",
44
+ });
45
+ // ── Feedback failure modes ──────────────────────────────────────────────────
46
+ export const FEEDBACK_FAILURE_MODES = ["incorrect", "outdated", "dangerous", "incomplete", "redundant"];
47
+ // ── Connection configs (LLM / embedding) ────────────────────────────────────
48
+ const LlmCapabilitiesSchema = z
49
+ .object({
50
+ structuredOutput: z.boolean().optional(),
51
+ })
52
+ .strict();
53
+ /**
54
+ * Connection config used for both top-level `llm` (after migration) and
55
+ * `profiles.llm[*]`. `model` is required at schema level — partial entries
56
+ * created by `akm config set llm.endpoint <url>` (where model is left absent)
57
+ * are normalized to `model: ""` *before* Zod sees them by the load-time
58
+ * pre-Zod migrator hook, so this strict shape gates CLI writes without
59
+ * breaking legacy load-time partial configs.
60
+ */
61
+ export const LlmConnectionConfigSchema = z
62
+ .object({
63
+ provider: z.string().optional(),
64
+ endpoint: z.string(),
65
+ model: z.string(),
66
+ apiKey: z.string().optional(),
67
+ temperature: z.number().finite().optional(),
68
+ maxTokens: positiveInt.optional(),
69
+ timeoutMs: positiveInt.optional(),
70
+ concurrency: positiveInt.optional(),
71
+ capabilities: LlmCapabilitiesSchema.optional(),
72
+ extraParams: z.record(z.unknown()).optional(),
73
+ contextLength: positiveInt.optional(),
74
+ judgeModel: z.string().min(1).optional(),
75
+ enableThinking: z.boolean().optional(),
76
+ })
77
+ .strict();
78
+ export const LlmProfileConfigSchema = LlmConnectionConfigSchema.extend({
79
+ supportsJsonSchema: z.boolean().optional(),
80
+ }).strict();
81
+ const EmbeddingOllamaOptionsSchema = z
82
+ .object({
83
+ num_ctx: positiveInt.optional(),
84
+ })
85
+ .strict();
86
+ /**
87
+ * Embedding connection config. Both `endpoint` and `model` are optional:
88
+ * - Remote: provide `endpoint` (http/https URL) + `model`.
89
+ * - Local-only: omit `endpoint`/`model`; set `localModel` (or fall back to
90
+ * {@link DEFAULT_LOCAL_MODEL}).
91
+ *
92
+ * Consumers route via `hasRemoteEndpoint()` which checks for an http(s)
93
+ * endpoint — absent fields take the local path naturally, no sentinels needed.
94
+ */
95
+ export const EmbeddingConnectionConfigSchema = z
96
+ .object({
97
+ provider: z.string().optional(),
98
+ endpoint: z.string().optional(),
99
+ model: z.string().optional(),
100
+ apiKey: z.string().optional(),
101
+ dimension: positiveInt.optional(),
102
+ localModel: z.string().min(1).optional(),
103
+ maxTokens: positiveInt.optional(),
104
+ batchSize: positiveInt.optional(),
105
+ chunkSize: positiveInt.optional(),
106
+ contextLength: positiveInt.optional(),
107
+ ollamaOptions: EmbeddingOllamaOptionsSchema.optional(),
108
+ })
109
+ .strict();
110
+ // ── Agent profiles ──────────────────────────────────────────────────────────
111
+ const AgentPlatformSchema = z.enum(["opencode", "claude", "opencode-sdk"]);
112
+ export const AgentProfileConfigSchema = z
113
+ .object({
114
+ platform: AgentPlatformSchema,
115
+ bin: z.string().min(1).optional(),
116
+ args: z.array(z.string()).optional(),
117
+ workspace: z.string().min(1).optional(),
118
+ model: z.string().min(1).optional(),
119
+ })
120
+ .strict();
121
+ // ── Improve profile / process ──────────────────────────────────────────────
122
+ export const ImproveProcessConfigSchema = z
123
+ .object({
124
+ enabled: z.boolean().optional(),
125
+ mode: z.enum(["llm", "agent", "sdk"]).optional(),
126
+ profile: z.string().min(1).optional(),
127
+ timeoutMs: z.union([positiveInt, z.null()]).optional(),
128
+ allowedTypes: z.array(z.string().min(1)).optional(),
129
+ qualityGate: z.object({ enabled: z.boolean().optional() }).strict().optional(),
130
+ contradictionDetection: z.object({ enabled: z.boolean().optional() }).strict().optional(),
131
+ // Extract process config (only meaningful for extract process)
132
+ defaultSince: z.string().min(1).optional(),
133
+ maxTotalChars: positiveInt.optional(),
134
+ maxChunkSize: z.number().int().min(1).max(50).optional(),
135
+ })
136
+ .strict();
137
+ const ImproveProfileProcessesSchema = z
138
+ .object({
139
+ reflect: ImproveProcessConfigSchema.optional(),
140
+ distill: ImproveProcessConfigSchema.optional(),
141
+ consolidate: ImproveProcessConfigSchema.optional(),
142
+ memoryInference: ImproveProcessConfigSchema.optional(),
143
+ graphExtraction: ImproveProcessConfigSchema.optional(),
144
+ validation: ImproveProcessConfigSchema.optional(),
145
+ })
146
+ .passthrough()
147
+ .superRefine((val, ctx) => {
148
+ // 0.8.0 removed the duplicated `feedbackDistillation` process key — it was
149
+ // a thin wrapper around `processes.distill.enabled`. Single source of truth.
150
+ const raw = val;
151
+ if ("feedbackDistillation" in raw) {
152
+ ctx.addIssue({
153
+ code: z.ZodIssueCode.custom,
154
+ message: "feedbackDistillation was removed in 0.8.0 — use processes.distill.enabled instead. " +
155
+ "It now controls both the orchestration gate and the LLM-call gate.",
156
+ });
157
+ return;
158
+ }
159
+ const allowed = new Set([
160
+ "reflect",
161
+ "distill",
162
+ "consolidate",
163
+ "memoryInference",
164
+ "graphExtraction",
165
+ "validation",
166
+ "extract",
167
+ ]);
168
+ for (const k of Object.keys(raw)) {
169
+ if (!allowed.has(k)) {
170
+ ctx.addIssue({
171
+ code: z.ZodIssueCode.unrecognized_keys,
172
+ keys: [k],
173
+ message: `Unrecognized improve process key: "${k}".`,
174
+ });
175
+ }
176
+ }
177
+ });
178
+ export const ImproveProfileConfigSchema = z
179
+ .object({
180
+ description: z.string().min(1).optional(),
181
+ processes: ImproveProfileProcessesSchema.optional(),
182
+ autoAccept: nonNegativeNumber.optional(),
183
+ limit: positiveInt.optional(),
184
+ })
185
+ .strict();
186
+ // ── Profiles / defaults ────────────────────────────────────────────────────
187
+ export const ProfilesSchema = z
188
+ .object({
189
+ llm: z.record(z.string(), LlmProfileConfigSchema).optional(),
190
+ agent: z.record(z.string(), AgentProfileConfigSchema).optional(),
191
+ improve: z.record(z.string(), ImproveProfileConfigSchema).optional(),
192
+ })
193
+ .strict();
194
+ export const DefaultsSchema = z
195
+ .object({
196
+ llm: z.string().min(1).optional(),
197
+ agent: z.string().min(1).optional(),
198
+ improve: z.string().min(1).optional(),
199
+ })
200
+ .strict();
201
+ // ── Sources / registries / installed ────────────────────────────────────────
202
+ const SourceConfigEntryOptionsSchema = z
203
+ .object({
204
+ pushOnCommit: z.boolean().optional(),
205
+ })
206
+ .passthrough();
207
+ export const SourceConfigEntrySchema = z
208
+ .object({
209
+ type: nonEmptyString,
210
+ path: z.string().min(1).optional(),
211
+ url: z.string().min(1).optional(),
212
+ name: z.string().min(1).optional(),
213
+ enabled: z.boolean().optional(),
214
+ writable: z.boolean().optional(),
215
+ primary: z.boolean().optional(),
216
+ options: SourceConfigEntryOptionsSchema.optional(),
217
+ wikiName: z.string().min(1).optional(),
218
+ })
219
+ .strict()
220
+ .superRefine((entry, ctx) => {
221
+ if (entry.writable === true && (entry.type === "website" || entry.type === "npm")) {
222
+ ctx.addIssue({
223
+ code: z.ZodIssueCode.custom,
224
+ message: `writable: true is only supported on filesystem and git sources (got "${entry.type}"` +
225
+ (entry.name ? ` on source "${entry.name}"` : "") +
226
+ ").",
227
+ });
228
+ }
229
+ });
230
+ export const RegistryConfigEntrySchema = z
231
+ .object({
232
+ url: httpUrl,
233
+ name: z.string().min(1).optional(),
234
+ enabled: z.boolean().optional(),
235
+ provider: z.string().min(1).optional(),
236
+ options: z.record(z.unknown()).optional(),
237
+ })
238
+ .strict();
239
+ const KitSourceSchema = z.enum(["filesystem", "git", "npm", "github", "website", "local"]);
240
+ export const InstalledStashEntrySchema = z
241
+ .object({
242
+ id: nonEmptyString,
243
+ source: KitSourceSchema,
244
+ ref: nonEmptyString,
245
+ artifactUrl: nonEmptyString,
246
+ stashRoot: nonEmptyString,
247
+ cacheDir: nonEmptyString,
248
+ installedAt: nonEmptyString,
249
+ writable: z.boolean().optional(),
250
+ resolvedVersion: z.string().min(1).optional(),
251
+ resolvedRevision: z.string().min(1).optional(),
252
+ wikiName: z.string().min(1).optional(),
253
+ })
254
+ .strict()
255
+ .superRefine((entry, ctx) => {
256
+ if (entry.writable === true && entry.source !== "git" && entry.source !== "filesystem") {
257
+ ctx.addIssue({
258
+ code: z.ZodIssueCode.custom,
259
+ message: `writable: true is only supported on filesystem and git sources (got "${entry.source}" on installed entry "${entry.id}").`,
260
+ });
261
+ }
262
+ });
263
+ // ── Output ──────────────────────────────────────────────────────────────────
264
+ export const OutputConfigSchema = z
265
+ .object({
266
+ format: z.enum(["json", "yaml", "text"]).optional(),
267
+ detail: z.enum(["brief", "normal", "full"]).optional(),
268
+ })
269
+ .strict();
270
+ // ── Search ──────────────────────────────────────────────────────────────────
271
+ const SearchGraphBoostSchema = z
272
+ .object({
273
+ directBoostPerEntity: nonNegativeNumber.optional(),
274
+ directBoostCap: nonNegativeNumber.optional(),
275
+ hopBoostPerEntity: nonNegativeNumber.optional(),
276
+ hopBoostCap: nonNegativeNumber.optional(),
277
+ /** Hard-capped at 3; values > 3 hard-error so users see the typo. */
278
+ maxHops: positiveInt.max(3).optional(),
279
+ confidenceMode: z.enum(["off", "blend", "multiply"]).default("blend").optional(),
280
+ /** Range [0, 1]; values > 1 hard-error (no silent clamp). */
281
+ confidenceWeight: z.number().finite().min(0).max(1).default(0.2).optional(),
282
+ })
283
+ .strict();
284
+ export const SearchConfigSchema = z
285
+ .object({
286
+ minScore: nonNegativeNumber.optional(),
287
+ curateRerank: z.object({ enabled: z.boolean().optional() }).strict().optional(),
288
+ graphBoost: SearchGraphBoostSchema.optional(),
289
+ })
290
+ .strict();
291
+ // ── Feedback ────────────────────────────────────────────────────────────────
292
+ export const FeedbackConfigSchema = z
293
+ .object({
294
+ requireReason: z.boolean().optional(),
295
+ allowedFailureModes: z.array(nonEmptyString).optional(),
296
+ })
297
+ .strict();
298
+ // ── Improve top-level (utility decay, event retention) ─────────────────────
299
+ const ImproveUtilityDecaySchema = z
300
+ .object({
301
+ halfLifeDays: z.number().finite().min(0.1).optional(),
302
+ feedbackStabilityBoost: z.number().finite().min(1).optional(),
303
+ })
304
+ .strict();
305
+ export const ImproveConfigSchema = z
306
+ .object({
307
+ utilityDecay: ImproveUtilityDecaySchema.optional(),
308
+ eventRetentionDays: nonNegativeNumber.optional(),
309
+ })
310
+ .strict();
311
+ // ── Index / per-pass ────────────────────────────────────────────────────────
312
+ const GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED = [
313
+ "memory",
314
+ "knowledge",
315
+ "skill",
316
+ "command",
317
+ "agent",
318
+ "workflow",
319
+ "lesson",
320
+ "task",
321
+ "wiki",
322
+ ];
323
+ const INDEX_PASS_PROVIDER_KEYS = new Set([
324
+ "endpoint",
325
+ "model",
326
+ "provider",
327
+ "apiKey",
328
+ "baseUrl",
329
+ "temperature",
330
+ "maxTokens",
331
+ "capabilities",
332
+ ]);
333
+ const INDEX_PASS_KNOWN_KEYS = new Set([
334
+ "llm",
335
+ "graphExtractionBatchSize",
336
+ "graphExtractionIncludeTypes",
337
+ "memoryInferenceBatchSize",
338
+ ]);
339
+ /**
340
+ * Per-pass `index.<pass>` entry. Uses preprocess + manual validation so we can
341
+ * emit the legacy parser's targeted error messages ("Duplicate LLM provider
342
+ * configuration", "Unknown key `index.<pass>.<key>`", "expected a boolean")
343
+ * instead of Zod's generic `Unrecognized key` / `Expected boolean, received
344
+ * string` strings — keeps `akm` startup errors actionable.
345
+ */
346
+ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
347
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
348
+ return raw; // let z.object below produce the type error
349
+ }
350
+ const obj = raw;
351
+ for (const key of Object.keys(obj)) {
352
+ if (INDEX_PASS_PROVIDER_KEYS.has(key)) {
353
+ ctx.addIssue({
354
+ code: z.ZodIssueCode.custom,
355
+ message: `Duplicate LLM provider configuration: \`${[...(ctx.path ?? []), key].join(".")}\` is not allowed. ` +
356
+ "Configure provider/model/endpoint under `profiles.llm` only; per-pass entries support `{ llm: false }` opt-out.",
357
+ });
358
+ return raw;
359
+ }
360
+ if (!INDEX_PASS_KNOWN_KEYS.has(key)) {
361
+ ctx.addIssue({
362
+ code: z.ZodIssueCode.custom,
363
+ message: `Unknown key \`${[...(ctx.path ?? []), key].join(".")}\`. Per-pass entries support \`llm\` ` +
364
+ "(boolean opt-out), `graphExtractionBatchSize`, `graphExtractionIncludeTypes`, and " +
365
+ "`memoryInferenceBatchSize`.",
366
+ });
367
+ return raw;
368
+ }
369
+ }
370
+ if ("llm" in obj && typeof obj.llm !== "boolean") {
371
+ ctx.addIssue({
372
+ code: z.ZodIssueCode.custom,
373
+ message: `Invalid \`${[...(ctx.path ?? []), "llm"].join(".")}\`: expected a boolean (true to use the default LLM profile, false to opt out). Got ${typeof obj.llm}.`,
374
+ });
375
+ return raw;
376
+ }
377
+ return raw;
378
+ }, z
379
+ .object({
380
+ llm: z.boolean().optional(),
381
+ graphExtractionBatchSize: positiveInt.optional(),
382
+ graphExtractionIncludeTypes: z.array(z.enum(GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED)).nonempty().optional(),
383
+ memoryInferenceBatchSize: positiveInt.optional(),
384
+ })
385
+ .passthrough());
386
+ const MetadataEnhanceSchema = z.object({ enabled: z.boolean().optional() }).strict();
387
+ const StalenessDetectionSchema = z
388
+ .object({
389
+ enabled: z.boolean().optional(),
390
+ thresholdDays: positiveInt.optional(),
391
+ })
392
+ .strict();
393
+ /**
394
+ * Index config is a union of reserved feature sections and per-pass entries.
395
+ * Passthrough so per-pass entries (keyed by arbitrary pass names like `graph`,
396
+ * `enrichment`) can live next to the reserved keys.
397
+ *
398
+ * The outer preprocess emits the legacy parser's actionable error messages
399
+ * for the two most common type-shape mistakes:
400
+ * - An array at the `index` block.
401
+ * - A non-object at `index.<passName>`.
402
+ * Inner field validation (graphExtractionIncludeTypes enum, llm boolean,
403
+ * provider-key rejection) is delegated to {@link IndexPassConfigSchema}.
404
+ */
405
+ export const IndexConfigSchema = z.preprocess((raw, ctx) => {
406
+ if (raw === undefined || raw === null)
407
+ return raw;
408
+ if (Array.isArray(raw)) {
409
+ ctx.addIssue({
410
+ code: z.ZodIssueCode.custom,
411
+ message: 'Invalid `index` config: expected an object keyed by pass name (e.g. `{ "enrichment": { "llm": false } }`).',
412
+ });
413
+ return raw;
414
+ }
415
+ if (typeof raw !== "object")
416
+ return raw;
417
+ for (const [passName, value] of Object.entries(raw)) {
418
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
419
+ ctx.addIssue({
420
+ code: z.ZodIssueCode.custom,
421
+ message: `Invalid \`index.${passName}\` config: expected an object like \`{ "llm": false }\`.`,
422
+ });
423
+ return raw;
424
+ }
425
+ if (passName !== "metadataEnhance" &&
426
+ passName !== "stalenessDetection" &&
427
+ Array.isArray(value.graphExtractionIncludeTypes)) {
428
+ const arr = value.graphExtractionIncludeTypes;
429
+ const invalid = [];
430
+ for (const t of arr) {
431
+ if (typeof t === "string" &&
432
+ !GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED.includes(t.toLowerCase())) {
433
+ invalid.push(t);
434
+ }
435
+ }
436
+ if (invalid.length > 0) {
437
+ ctx.addIssue({
438
+ code: z.ZodIssueCode.custom,
439
+ message: `Invalid \`index.${passName}.graphExtractionIncludeTypes\`: unsupported type(s): ${invalid.join(", ")}.`,
440
+ });
441
+ return raw;
442
+ }
443
+ }
444
+ }
445
+ return raw;
446
+ }, z
447
+ .object({
448
+ metadataEnhance: MetadataEnhanceSchema.optional(),
449
+ stalenessDetection: StalenessDetectionSchema.optional(),
450
+ })
451
+ .catchall(IndexPassConfigSchema));
452
+ // ── Top-level AkmConfig ────────────────────────────────────────────────────
453
+ /**
454
+ * Base object schema used both as the top-level shape and as the source of
455
+ * truth for {@link listTopLevelConfigKeys}. {@link AkmConfigSchema} wraps this
456
+ * with cross-field refinements (`.superRefine()`).
457
+ *
458
+ * All fields validate loudly — typos and shape errors throw at load time. The
459
+ * legacy parser's warn-and-drop tolerance was a frequent source of silent
460
+ * configuration loss; the migration module ({@link migrateConfigShape}) handles
461
+ * one-time 0.7→0.8 input transforms before the schema sees the value.
462
+ */
463
+ export const AkmConfigShape = {
464
+ configVersion: z.union([z.string().min(1), z.number()]).optional(),
465
+ profiles: ProfilesSchema.optional(),
466
+ defaults: DefaultsSchema.optional(),
467
+ stashDir: nonEmptyString.optional(),
468
+ semanticSearchMode: z.enum(["off", "auto"]).default("auto"),
469
+ embedding: EmbeddingConnectionConfigSchema.optional(),
470
+ index: IndexConfigSchema.optional(),
471
+ installed: z.array(InstalledStashEntrySchema).optional(),
472
+ registries: z.array(RegistryConfigEntrySchema).optional(),
473
+ sources: z.array(SourceConfigEntrySchema).optional(),
474
+ output: OutputConfigSchema.optional(),
475
+ writable: z.boolean().optional(),
476
+ defaultWriteTarget: nonEmptyString.optional(),
477
+ search: SearchConfigSchema.optional(),
478
+ feedback: FeedbackConfigSchema.optional(),
479
+ archiveRetentionDays: nonNegativeNumber.optional(),
480
+ improve: ImproveConfigSchema.optional(),
481
+ };
482
+ export const AkmConfigBaseSchema = z.object(AkmConfigShape).strict();
483
+ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
484
+ // #464.a: defaultWriteTarget must name a configured source when sources
485
+ // are present. With no sources configured, error out instead of silently
486
+ // accepting (no implicit "first writable" fallback — see locked decision 3).
487
+ if (config.defaultWriteTarget !== undefined) {
488
+ const knownNames = (config.sources ?? [])
489
+ .map((s) => s.name)
490
+ .filter((n) => typeof n === "string" && n.length > 0);
491
+ if (knownNames.length === 0) {
492
+ ctx.addIssue({
493
+ code: z.ZodIssueCode.custom,
494
+ path: ["defaultWriteTarget"],
495
+ message: `defaultWriteTarget "${config.defaultWriteTarget}" cannot be resolved: no sources configured. ` +
496
+ "Add at least one entry to `sources` with a matching `name` first.",
497
+ });
498
+ }
499
+ else if (!knownNames.includes(config.defaultWriteTarget)) {
500
+ ctx.addIssue({
501
+ code: z.ZodIssueCode.custom,
502
+ path: ["defaultWriteTarget"],
503
+ message: `defaultWriteTarget "${config.defaultWriteTarget}" does not match any configured source name: ${knownNames.map((n) => `"${n}"`).join(", ")}.`,
504
+ });
505
+ }
506
+ }
507
+ });
508
+ /**
509
+ * Validate a raw object against {@link AkmConfigSchema}. Returns a structured
510
+ * result so callers can render errors as a list (instead of throwing on the
511
+ * first issue).
512
+ */
513
+ export function validateConfigShape(raw) {
514
+ const result = AkmConfigSchema.safeParse(raw);
515
+ if (result.success) {
516
+ return { ok: true, value: result.data, errors: [] };
517
+ }
518
+ return {
519
+ ok: false,
520
+ errors: result.error.issues.map((issue) => ({
521
+ path: issue.path.join("."),
522
+ message: issue.message,
523
+ })),
524
+ };
525
+ }
526
+ // ── Top-level key listing (for hint messages) ───────────────────────────────
527
+ /**
528
+ * Return the sorted list of top-level config keys recognized by the schema.
529
+ * Used by error hints so the list stays in sync with the schema automatically
530
+ * (#460).
531
+ */
532
+ export function listTopLevelConfigKeys() {
533
+ return Object.keys(AkmConfigShape).sort();
534
+ }
@@ -0,0 +1,108 @@
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
+ /**
5
+ * Runtime helpers that derive {@link ConfiguredSource} values from the
6
+ * persisted {@link SourceConfigEntry} / {@link InstalledStashEntry} shapes
7
+ * in an {@link AkmConfig}.
8
+ */
9
+ import { createHash } from "node:crypto";
10
+ /**
11
+ * Synthesize a stable identifier when a {@link SourceConfigEntry} omits its
12
+ * `name`. Uses a short hash of the discriminating fields so two equivalent
13
+ * entries collapse to the same generated name.
14
+ */
15
+ function deriveStashEntryName(entry) {
16
+ if (entry.name)
17
+ return entry.name;
18
+ const seed = JSON.stringify({
19
+ type: entry.type,
20
+ path: entry.path ?? null,
21
+ url: entry.url ?? null,
22
+ });
23
+ const hash = createHash("sha256").update(seed).digest("hex").slice(0, 8);
24
+ return `${entry.type}-${hash}`;
25
+ }
26
+ /**
27
+ * Convert a persisted {@link SourceConfigEntry} into the runtime
28
+ * {@link SourceSpec} discriminated union. Returns `undefined` when the entry
29
+ * is missing the fields its provider type requires (e.g. a `filesystem`
30
+ * entry with no `path`); callers should drop or warn for those.
31
+ */
32
+ export function parseSourceSpec(entry) {
33
+ switch (entry.type) {
34
+ case "filesystem":
35
+ return entry.path ? { type: "filesystem", path: entry.path } : undefined;
36
+ case "git":
37
+ return entry.url ? { type: "git", url: entry.url } : undefined;
38
+ case "website":
39
+ return entry.url
40
+ ? {
41
+ type: "website",
42
+ url: entry.url,
43
+ ...(typeof entry.options?.maxPages === "number" ? { maxPages: entry.options.maxPages } : {}),
44
+ }
45
+ : undefined;
46
+ case "npm":
47
+ return entry.path ? { type: "npm", package: entry.path } : undefined;
48
+ default:
49
+ // Unknown provider — best-effort fallback so callers still get something.
50
+ return entry.path ? { type: "filesystem", path: entry.path } : undefined;
51
+ }
52
+ }
53
+ /**
54
+ * Build the full ordered list of runtime {@link ConfiguredSource} values from
55
+ * a loaded {@link AkmConfig}:
56
+ * 1. The entry marked `primary: true` (or a synthetic entry from `stashDir`).
57
+ * 2. Remaining `sources[]` entries in declared order.
58
+ * 3. Legacy `installed[]` entries, mapped into runtime entries.
59
+ *
60
+ * Entries with `enabled: false` are still emitted — callers decide whether to
61
+ * honour the flag. Entries that fail {@link parseSourceSpec} drop silently.
62
+ */
63
+ export function resolveConfiguredSources(config) {
64
+ const entries = [];
65
+ const sources = config.sources ?? [];
66
+ let primary = sources.find((entry) => entry.primary === true);
67
+ if (!primary && config.stashDir) {
68
+ primary = { type: "filesystem", path: config.stashDir, primary: true };
69
+ }
70
+ if (primary) {
71
+ const runtime = toConfiguredSource(primary, true);
72
+ if (runtime)
73
+ entries.push(runtime);
74
+ }
75
+ for (const entry of sources) {
76
+ if (entry === primary)
77
+ continue;
78
+ const runtime = toConfiguredSource(entry, false);
79
+ if (runtime)
80
+ entries.push(runtime);
81
+ }
82
+ for (const installed of config.installed ?? []) {
83
+ entries.push({
84
+ name: installed.id,
85
+ type: "filesystem",
86
+ source: { type: "filesystem", path: installed.stashRoot },
87
+ enabled: true,
88
+ writable: installed.writable,
89
+ ...(installed.wikiName ? { wikiName: installed.wikiName } : {}),
90
+ });
91
+ }
92
+ return entries;
93
+ }
94
+ function toConfiguredSource(persisted, isPrimary) {
95
+ const source = parseSourceSpec(persisted);
96
+ if (!source)
97
+ return undefined;
98
+ return {
99
+ name: deriveStashEntryName(persisted),
100
+ type: persisted.type,
101
+ source,
102
+ ...(persisted.enabled !== undefined ? { enabled: persisted.enabled } : {}),
103
+ ...(persisted.writable !== undefined ? { writable: persisted.writable } : {}),
104
+ ...(isPrimary || persisted.primary ? { primary: true } : {}),
105
+ ...(persisted.options ? { options: persisted.options } : {}),
106
+ ...(persisted.wikiName ? { wikiName: persisted.wikiName } : {}),
107
+ };
108
+ }
@@ -0,0 +1,4 @@
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
+ export {};