akm-cli 0.8.0-rc1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +191 -3
  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 +93 -3
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2162 -1258
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +20 -12
  12. package/dist/commands/agent-support.js +11 -5
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +129 -517
  15. package/dist/commands/consolidate.js +1533 -144
  16. package/dist/commands/curate.js +44 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +5 -3
  19. package/dist/commands/distill.js +906 -100
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +3 -0
  22. package/dist/commands/events.js +3 -0
  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 +260 -5
  28. package/dist/commands/health.js +977 -51
  29. package/dist/commands/help/help-accept.md +6 -3
  30. package/dist/commands/help/help-improve.md +36 -8
  31. package/dist/commands/help/help-proposals.md +7 -4
  32. package/dist/commands/help/help-reject.md +5 -2
  33. package/dist/commands/history.js +51 -16
  34. package/dist/commands/improve-auto-accept.js +97 -0
  35. package/dist/commands/improve-cli.js +236 -0
  36. package/dist/commands/improve-profiles.js +184 -0
  37. package/dist/commands/improve-result-file.js +167 -0
  38. package/dist/commands/improve.js +1725 -332
  39. package/dist/commands/info.js +3 -0
  40. package/dist/commands/init.js +49 -1
  41. package/dist/commands/installed-stashes.js +6 -23
  42. package/dist/commands/knowledge.js +3 -0
  43. package/dist/commands/lint/agent-linter.js +3 -0
  44. package/dist/commands/lint/base-linter.js +233 -5
  45. package/dist/commands/lint/command-linter.js +3 -0
  46. package/dist/commands/lint/default-linter.js +3 -0
  47. package/dist/commands/lint/env-key-rules.js +154 -0
  48. package/dist/commands/lint/index.js +92 -3
  49. package/dist/commands/lint/knowledge-linter.js +3 -0
  50. package/dist/commands/lint/markdown-insertion.js +343 -0
  51. package/dist/commands/lint/memory-linter.js +3 -0
  52. package/dist/commands/lint/registry.js +3 -0
  53. package/dist/commands/lint/skill-linter.js +3 -0
  54. package/dist/commands/lint/task-linter.js +15 -12
  55. package/dist/commands/lint/types.js +3 -0
  56. package/dist/commands/lint/workflow-linter.js +3 -0
  57. package/dist/commands/lint.js +3 -0
  58. package/dist/commands/migration-help.js +5 -2
  59. package/dist/commands/proposal-drain-policies.js +128 -0
  60. package/dist/commands/proposal-drain.js +477 -0
  61. package/dist/commands/proposal.js +60 -6
  62. package/dist/commands/propose.js +24 -19
  63. package/dist/commands/reflect.js +1004 -94
  64. package/dist/commands/registry-cli.js +150 -0
  65. package/dist/commands/registry-search.js +3 -0
  66. package/dist/commands/remember-cli.js +257 -0
  67. package/dist/commands/remember.js +15 -6
  68. package/dist/commands/schema-repair.js +88 -15
  69. package/dist/commands/search.js +99 -14
  70. package/dist/commands/secret.js +173 -0
  71. package/dist/commands/self-update.js +3 -0
  72. package/dist/commands/show.js +32 -13
  73. package/dist/commands/source-add.js +7 -35
  74. package/dist/commands/source-clone.js +3 -0
  75. package/dist/commands/source-manage.js +3 -0
  76. package/dist/commands/tasks.js +161 -95
  77. package/dist/commands/url-checker.js +3 -0
  78. package/dist/core/action-contributors.js +3 -0
  79. package/dist/core/asset-ref.js +17 -2
  80. package/dist/core/asset-registry.js +9 -2
  81. package/dist/core/asset-serialize.js +88 -0
  82. package/dist/core/asset-spec.js +61 -5
  83. package/dist/core/common.js +93 -5
  84. package/dist/core/concurrent.js +3 -0
  85. package/dist/core/config-io.js +347 -0
  86. package/dist/core/config-migration.js +622 -0
  87. package/dist/core/config-schema.js +558 -0
  88. package/dist/core/config-sources.js +108 -0
  89. package/dist/core/config-types.js +4 -0
  90. package/dist/core/config-walker.js +337 -0
  91. package/dist/core/config.js +366 -1077
  92. package/dist/core/errors.js +42 -20
  93. package/dist/core/events.js +31 -25
  94. package/dist/core/file-lock.js +104 -0
  95. package/dist/core/frontmatter.js +75 -10
  96. package/dist/core/lesson-lint.js +3 -0
  97. package/dist/core/markdown.js +3 -0
  98. package/dist/core/memory-belief.js +62 -0
  99. package/dist/core/memory-contradiction-detect.js +274 -0
  100. package/dist/core/memory-improve.js +142 -14
  101. package/dist/core/parse.js +3 -0
  102. package/dist/core/paths.js +218 -50
  103. package/dist/core/proposal-quality-validators.js +380 -0
  104. package/dist/core/proposal-validators.js +11 -3
  105. package/dist/core/proposals.js +464 -5
  106. package/dist/core/state-db.js +349 -56
  107. package/dist/core/text-truncation.js +107 -0
  108. package/dist/core/time.js +3 -0
  109. package/dist/core/tty.js +59 -0
  110. package/dist/core/warn.js +7 -2
  111. package/dist/core/write-source.js +12 -0
  112. package/dist/indexer/db-backup.js +391 -0
  113. package/dist/indexer/db-search.js +136 -28
  114. package/dist/indexer/db.js +662 -166
  115. package/dist/indexer/ensure-index.js +3 -0
  116. package/dist/indexer/file-context.js +3 -0
  117. package/dist/indexer/graph-boost.js +162 -40
  118. package/dist/indexer/graph-db.js +241 -51
  119. package/dist/indexer/graph-dedup.js +3 -7
  120. package/dist/indexer/graph-extraction.js +242 -149
  121. package/dist/indexer/index-context.js +3 -9
  122. package/dist/indexer/indexer.js +84 -14
  123. package/dist/indexer/llm-cache.js +24 -19
  124. package/dist/indexer/manifest.js +3 -0
  125. package/dist/indexer/matchers.js +184 -11
  126. package/dist/indexer/memory-inference.js +94 -50
  127. package/dist/indexer/metadata-contributors.js +3 -0
  128. package/dist/indexer/metadata.js +114 -48
  129. package/dist/indexer/path-resolver.js +3 -0
  130. package/dist/indexer/project-context.js +192 -0
  131. package/dist/indexer/ranking-contributors.js +134 -7
  132. package/dist/indexer/ranking.js +8 -1
  133. package/dist/indexer/search-fields.js +5 -9
  134. package/dist/indexer/search-hit-enrichers.js +91 -2
  135. package/dist/indexer/search-source.js +20 -1
  136. package/dist/indexer/semantic-status.js +4 -1
  137. package/dist/indexer/staleness-detect.js +447 -0
  138. package/dist/indexer/usage-events.js +12 -9
  139. package/dist/indexer/walker.js +3 -0
  140. package/dist/integrations/agent/builders.js +135 -0
  141. package/dist/integrations/agent/config.js +121 -401
  142. package/dist/integrations/agent/detect.js +3 -0
  143. package/dist/integrations/agent/index.js +6 -14
  144. package/dist/integrations/agent/model-aliases.js +55 -0
  145. package/dist/integrations/agent/profiles.js +3 -0
  146. package/dist/integrations/agent/prompts.js +137 -8
  147. package/dist/integrations/agent/runner.js +208 -0
  148. package/dist/integrations/agent/sdk-runner.js +8 -2
  149. package/dist/integrations/agent/spawn.js +54 -14
  150. package/dist/integrations/github.js +3 -0
  151. package/dist/integrations/lockfile.js +22 -51
  152. package/dist/integrations/session-logs/index.js +4 -0
  153. package/dist/integrations/session-logs/inline-refs.js +35 -0
  154. package/dist/integrations/session-logs/pre-filter.js +152 -0
  155. package/dist/integrations/session-logs/providers/claude-code.js +226 -0
  156. package/dist/integrations/session-logs/providers/opencode.js +231 -25
  157. package/dist/integrations/session-logs/types.js +3 -0
  158. package/dist/llm/call-ai.js +14 -26
  159. package/dist/llm/client.js +16 -2
  160. package/dist/llm/embedder.js +20 -29
  161. package/dist/llm/embedders/cache.js +3 -7
  162. package/dist/llm/embedders/local.js +42 -1
  163. package/dist/llm/embedders/remote.js +20 -8
  164. package/dist/llm/embedders/types.js +3 -7
  165. package/dist/llm/feature-gate.js +92 -56
  166. package/dist/llm/graph-extract.js +401 -30
  167. package/dist/llm/index-passes.js +44 -29
  168. package/dist/llm/memory-infer.js +30 -2
  169. package/dist/llm/metadata-enhance.js +3 -7
  170. package/dist/llm/prompts/extract-session.md +80 -0
  171. package/dist/llm/prompts/graph-extract-user-prompt.md +24 -1
  172. package/dist/output/cli-hints-full.md +60 -32
  173. package/dist/output/cli-hints-short.md +10 -7
  174. package/dist/output/cli-hints.js +5 -2
  175. package/dist/output/context.js +60 -8
  176. package/dist/output/renderers.js +170 -194
  177. package/dist/output/shapes/curate.js +56 -0
  178. package/dist/output/shapes/distill.js +10 -0
  179. package/dist/output/shapes/env-list.js +19 -0
  180. package/dist/output/shapes/events.js +11 -0
  181. package/dist/output/shapes/helpers.js +424 -0
  182. package/dist/output/shapes/history.js +7 -0
  183. package/dist/output/shapes/passthrough.js +105 -0
  184. package/dist/output/shapes/proposal-accept.js +7 -0
  185. package/dist/output/shapes/proposal-diff.js +7 -0
  186. package/dist/output/shapes/proposal-list.js +7 -0
  187. package/dist/output/shapes/proposal-producer.js +11 -0
  188. package/dist/output/shapes/proposal-reject.js +7 -0
  189. package/dist/output/shapes/proposal-show.js +7 -0
  190. package/dist/output/shapes/registry-search.js +6 -0
  191. package/dist/output/shapes/registry.js +30 -0
  192. package/dist/output/shapes/search.js +6 -0
  193. package/dist/output/shapes/secret-list.js +19 -0
  194. package/dist/output/shapes/show.js +6 -0
  195. package/dist/output/shapes/vault-list.js +19 -0
  196. package/dist/output/shapes.js +51 -549
  197. package/dist/output/text/add.js +6 -0
  198. package/dist/output/text/clone.js +6 -0
  199. package/dist/output/text/config.js +6 -0
  200. package/dist/output/text/curate.js +6 -0
  201. package/dist/output/text/distill.js +7 -0
  202. package/dist/output/text/enable-disable.js +7 -0
  203. package/dist/output/text/events.js +10 -0
  204. package/dist/output/text/feedback.js +6 -0
  205. package/dist/output/text/helpers.js +1059 -0
  206. package/dist/output/text/history.js +7 -0
  207. package/dist/output/text/import.js +6 -0
  208. package/dist/output/text/index.js +6 -0
  209. package/dist/output/text/info.js +6 -0
  210. package/dist/output/text/init.js +6 -0
  211. package/dist/output/text/list.js +6 -0
  212. package/dist/output/text/proposal-producer.js +8 -0
  213. package/dist/output/text/proposal.js +12 -0
  214. package/dist/output/text/registry-commands.js +11 -0
  215. package/dist/output/text/registry.js +30 -0
  216. package/dist/output/text/remember.js +6 -0
  217. package/dist/output/text/remove.js +6 -0
  218. package/dist/output/text/save.js +6 -0
  219. package/dist/output/text/search.js +6 -0
  220. package/dist/output/text/show.js +6 -0
  221. package/dist/output/text/update.js +6 -0
  222. package/dist/output/text/upgrade.js +6 -0
  223. package/dist/output/text/vault.js +16 -0
  224. package/dist/output/text/wiki.js +15 -0
  225. package/dist/output/text/workflow.js +14 -0
  226. package/dist/output/text.js +44 -1329
  227. package/dist/registry/build-index.js +3 -0
  228. package/dist/registry/create-provider-registry.js +3 -0
  229. package/dist/registry/factory.js +4 -1
  230. package/dist/registry/origin-resolve.js +3 -0
  231. package/dist/registry/providers/index.js +3 -0
  232. package/dist/registry/providers/skills-sh.js +11 -2
  233. package/dist/registry/providers/static-index.js +10 -1
  234. package/dist/registry/providers/types.js +3 -24
  235. package/dist/registry/resolve.js +11 -16
  236. package/dist/registry/types.js +3 -0
  237. package/dist/scripts/migrate-storage.js +17767 -0
  238. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  239. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  240. package/dist/setup/detect.js +3 -0
  241. package/dist/setup/ripgrep-install.js +3 -0
  242. package/dist/setup/ripgrep-resolve.js +3 -0
  243. package/dist/setup/setup.js +306 -67
  244. package/dist/setup/steps.js +3 -15
  245. package/dist/sources/include.js +3 -0
  246. package/dist/sources/provider-factory.js +3 -11
  247. package/dist/sources/provider.js +3 -20
  248. package/dist/sources/providers/filesystem.js +19 -23
  249. package/dist/sources/providers/git.js +171 -21
  250. package/dist/sources/providers/index.js +3 -0
  251. package/dist/sources/providers/install-types.js +3 -13
  252. package/dist/sources/providers/npm.js +3 -4
  253. package/dist/sources/providers/provider-utils.js +3 -0
  254. package/dist/sources/providers/sync-from-ref.js +3 -11
  255. package/dist/sources/providers/tar-utils.js +3 -0
  256. package/dist/sources/providers/website.js +18 -22
  257. package/dist/sources/resolve.js +3 -0
  258. package/dist/sources/types.js +3 -0
  259. package/dist/sources/website-ingest.js +3 -0
  260. package/dist/tasks/backends/cron.js +3 -0
  261. package/dist/tasks/backends/exec-utils.js +3 -0
  262. package/dist/tasks/backends/index.js +3 -11
  263. package/dist/tasks/backends/launchd.js +3 -0
  264. package/dist/tasks/backends/schtasks.js +3 -0
  265. package/dist/tasks/parser.js +51 -38
  266. package/dist/tasks/resolveAkmBin.js +3 -0
  267. package/dist/tasks/runner.js +35 -9
  268. package/dist/tasks/schedule.js +20 -1
  269. package/dist/tasks/schema.js +5 -3
  270. package/dist/tasks/validator.js +6 -3
  271. package/dist/version.js +3 -0
  272. package/dist/wiki/wiki-templates.js +3 -0
  273. package/dist/wiki/wiki.js +3 -0
  274. package/dist/workflows/authoring.js +3 -0
  275. package/dist/workflows/cli.js +3 -0
  276. package/dist/workflows/db.js +140 -10
  277. package/dist/workflows/document-cache.js +3 -10
  278. package/dist/workflows/parser.js +3 -0
  279. package/dist/workflows/renderer.js +3 -0
  280. package/dist/workflows/runs.js +18 -1
  281. package/dist/workflows/schema.js +3 -0
  282. package/dist/workflows/scope-key.js +3 -0
  283. package/dist/workflows/validator.js +5 -9
  284. package/docs/README.md +7 -2
  285. package/docs/data-and-telemetry.md +225 -0
  286. package/docs/migration/release-notes/0.7.5.md +2 -2
  287. package/docs/migration/release-notes/0.8.0.md +57 -5
  288. package/docs/migration/v0.7-to-v0.8.md +1378 -0
  289. package/package.json +28 -11
  290. package/.github/LICENSE +0 -374
  291. package/dist/commands/install-audit.js +0 -385
  292. package/dist/commands/vault.js +0 -307
  293. package/dist/indexer/match-contributors.js +0 -141
  294. package/dist/integrations/agent/pipeline.js +0 -39
  295. package/dist/integrations/agent/runners.js +0 -31
@@ -0,0 +1,558 @@
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
+ // Triage process config (only meaningful for the `triage` process)
136
+ applyMode: z.enum(["queue", "promote"]).optional(),
137
+ policy: z.string().min(1).optional(),
138
+ maxAcceptsPerRun: positiveInt.optional(),
139
+ maxDiffLines: positiveInt.optional(),
140
+ rejectEmpty: z.boolean().optional(),
141
+ judgment: z
142
+ .object({
143
+ mode: z.enum(["llm", "agent", "sdk"]).optional(),
144
+ profile: z.string().min(1).optional(),
145
+ timeoutMs: z.union([positiveInt, z.null()]).optional(),
146
+ })
147
+ .strict()
148
+ .optional(),
149
+ })
150
+ .strict();
151
+ const ImproveProfileProcessesSchema = z
152
+ .object({
153
+ reflect: ImproveProcessConfigSchema.optional(),
154
+ distill: ImproveProcessConfigSchema.optional(),
155
+ consolidate: ImproveProcessConfigSchema.optional(),
156
+ memoryInference: ImproveProcessConfigSchema.optional(),
157
+ graphExtraction: ImproveProcessConfigSchema.optional(),
158
+ validation: ImproveProcessConfigSchema.optional(),
159
+ triage: ImproveProcessConfigSchema.optional(),
160
+ })
161
+ .passthrough()
162
+ .superRefine((val, ctx) => {
163
+ // 0.8.0 removed the duplicated `feedbackDistillation` process key — it was
164
+ // a thin wrapper around `processes.distill.enabled`. Single source of truth.
165
+ const raw = val;
166
+ if ("feedbackDistillation" in raw) {
167
+ ctx.addIssue({
168
+ code: z.ZodIssueCode.custom,
169
+ message: "feedbackDistillation was removed in 0.8.0 — use processes.distill.enabled instead. " +
170
+ "It now controls both the orchestration gate and the LLM-call gate.",
171
+ });
172
+ return;
173
+ }
174
+ const allowed = new Set([
175
+ "reflect",
176
+ "distill",
177
+ "consolidate",
178
+ "memoryInference",
179
+ "graphExtraction",
180
+ "validation",
181
+ "extract",
182
+ "triage",
183
+ ]);
184
+ for (const k of Object.keys(raw)) {
185
+ if (!allowed.has(k)) {
186
+ ctx.addIssue({
187
+ code: z.ZodIssueCode.unrecognized_keys,
188
+ keys: [k],
189
+ message: `Unrecognized improve process key: "${k}".`,
190
+ });
191
+ }
192
+ }
193
+ });
194
+ export const ImproveProfileConfigSchema = z
195
+ .object({
196
+ description: z.string().min(1).optional(),
197
+ processes: ImproveProfileProcessesSchema.optional(),
198
+ autoAccept: nonNegativeNumber.optional(),
199
+ limit: positiveInt.optional(),
200
+ sync: z
201
+ .object({
202
+ enabled: z.boolean().optional(),
203
+ push: z.boolean().optional(),
204
+ message: z.string().min(1).optional(),
205
+ })
206
+ .strict()
207
+ .optional(),
208
+ })
209
+ .strict();
210
+ // ── Profiles / defaults ────────────────────────────────────────────────────
211
+ export const ProfilesSchema = z
212
+ .object({
213
+ llm: z.record(z.string(), LlmProfileConfigSchema).optional(),
214
+ agent: z.record(z.string(), AgentProfileConfigSchema).optional(),
215
+ improve: z.record(z.string(), ImproveProfileConfigSchema).optional(),
216
+ })
217
+ .strict();
218
+ export const DefaultsSchema = z
219
+ .object({
220
+ llm: z.string().min(1).optional(),
221
+ agent: z.string().min(1).optional(),
222
+ improve: z.string().min(1).optional(),
223
+ })
224
+ .strict();
225
+ // ── Sources / registries / installed ────────────────────────────────────────
226
+ const SourceConfigEntryOptionsSchema = z
227
+ .object({
228
+ pushOnCommit: z.boolean().optional(),
229
+ })
230
+ .passthrough();
231
+ export const SourceConfigEntrySchema = z
232
+ .object({
233
+ type: nonEmptyString,
234
+ path: z.string().min(1).optional(),
235
+ url: z.string().min(1).optional(),
236
+ name: z.string().min(1).optional(),
237
+ enabled: z.boolean().optional(),
238
+ writable: z.boolean().optional(),
239
+ primary: z.boolean().optional(),
240
+ options: SourceConfigEntryOptionsSchema.optional(),
241
+ wikiName: z.string().min(1).optional(),
242
+ })
243
+ .strict()
244
+ .superRefine((entry, ctx) => {
245
+ if (entry.writable === true && (entry.type === "website" || entry.type === "npm")) {
246
+ ctx.addIssue({
247
+ code: z.ZodIssueCode.custom,
248
+ message: `writable: true is only supported on filesystem and git sources (got "${entry.type}"` +
249
+ (entry.name ? ` on source "${entry.name}"` : "") +
250
+ ").",
251
+ });
252
+ }
253
+ });
254
+ export const RegistryConfigEntrySchema = z
255
+ .object({
256
+ url: httpUrl,
257
+ name: z.string().min(1).optional(),
258
+ enabled: z.boolean().optional(),
259
+ provider: z.string().min(1).optional(),
260
+ options: z.record(z.unknown()).optional(),
261
+ })
262
+ .strict();
263
+ const KitSourceSchema = z.enum(["filesystem", "git", "npm", "github", "website", "local"]);
264
+ export const InstalledStashEntrySchema = z
265
+ .object({
266
+ id: nonEmptyString,
267
+ source: KitSourceSchema,
268
+ ref: nonEmptyString,
269
+ artifactUrl: nonEmptyString,
270
+ stashRoot: nonEmptyString,
271
+ cacheDir: nonEmptyString,
272
+ installedAt: nonEmptyString,
273
+ writable: z.boolean().optional(),
274
+ resolvedVersion: z.string().min(1).optional(),
275
+ resolvedRevision: z.string().min(1).optional(),
276
+ wikiName: z.string().min(1).optional(),
277
+ })
278
+ .strict()
279
+ .superRefine((entry, ctx) => {
280
+ if (entry.writable === true && entry.source !== "git" && entry.source !== "filesystem") {
281
+ ctx.addIssue({
282
+ code: z.ZodIssueCode.custom,
283
+ message: `writable: true is only supported on filesystem and git sources (got "${entry.source}" on installed entry "${entry.id}").`,
284
+ });
285
+ }
286
+ });
287
+ // ── Output ──────────────────────────────────────────────────────────────────
288
+ export const OutputConfigSchema = z
289
+ .object({
290
+ format: z.enum(["json", "yaml", "text"]).optional(),
291
+ detail: z.enum(["brief", "normal", "full"]).optional(),
292
+ })
293
+ .strict();
294
+ // ── Search ──────────────────────────────────────────────────────────────────
295
+ const SearchGraphBoostSchema = z
296
+ .object({
297
+ directBoostPerEntity: nonNegativeNumber.optional(),
298
+ directBoostCap: nonNegativeNumber.optional(),
299
+ hopBoostPerEntity: nonNegativeNumber.optional(),
300
+ hopBoostCap: nonNegativeNumber.optional(),
301
+ /** Hard-capped at 3; values > 3 hard-error so users see the typo. */
302
+ maxHops: positiveInt.max(3).optional(),
303
+ confidenceMode: z.enum(["off", "blend", "multiply"]).default("blend").optional(),
304
+ /** Range [0, 1]; values > 1 hard-error (no silent clamp). */
305
+ confidenceWeight: z.number().finite().min(0).max(1).default(0.2).optional(),
306
+ })
307
+ .strict();
308
+ export const SearchConfigSchema = z
309
+ .object({
310
+ minScore: nonNegativeNumber.optional(),
311
+ curateRerank: z.object({ enabled: z.boolean().optional() }).strict().optional(),
312
+ graphBoost: SearchGraphBoostSchema.optional(),
313
+ })
314
+ .strict();
315
+ // ── Feedback ────────────────────────────────────────────────────────────────
316
+ export const FeedbackConfigSchema = z
317
+ .object({
318
+ requireReason: z.boolean().optional(),
319
+ allowedFailureModes: z.array(nonEmptyString).optional(),
320
+ })
321
+ .strict();
322
+ // ── Improve top-level (utility decay, event retention) ─────────────────────
323
+ const ImproveUtilityDecaySchema = z
324
+ .object({
325
+ halfLifeDays: z.number().finite().min(0.1).optional(),
326
+ feedbackStabilityBoost: z.number().finite().min(1).optional(),
327
+ })
328
+ .strict();
329
+ export const ImproveConfigSchema = z
330
+ .object({
331
+ utilityDecay: ImproveUtilityDecaySchema.optional(),
332
+ eventRetentionDays: nonNegativeNumber.optional(),
333
+ })
334
+ .strict();
335
+ // ── Index / per-pass ────────────────────────────────────────────────────────
336
+ const GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED = [
337
+ "memory",
338
+ "knowledge",
339
+ "skill",
340
+ "command",
341
+ "agent",
342
+ "workflow",
343
+ "lesson",
344
+ "task",
345
+ "wiki",
346
+ ];
347
+ const INDEX_PASS_PROVIDER_KEYS = new Set([
348
+ "endpoint",
349
+ "model",
350
+ "provider",
351
+ "apiKey",
352
+ "baseUrl",
353
+ "temperature",
354
+ "maxTokens",
355
+ "capabilities",
356
+ ]);
357
+ const INDEX_PASS_KNOWN_KEYS = new Set([
358
+ "llm",
359
+ "graphExtractionBatchSize",
360
+ "graphExtractionIncludeTypes",
361
+ "memoryInferenceBatchSize",
362
+ ]);
363
+ /**
364
+ * Per-pass `index.<pass>` entry. Uses preprocess + manual validation so we can
365
+ * emit the legacy parser's targeted error messages ("Duplicate LLM provider
366
+ * configuration", "Unknown key `index.<pass>.<key>`", "expected a boolean")
367
+ * instead of Zod's generic `Unrecognized key` / `Expected boolean, received
368
+ * string` strings — keeps `akm` startup errors actionable.
369
+ */
370
+ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
371
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
372
+ return raw; // let z.object below produce the type error
373
+ }
374
+ const obj = raw;
375
+ for (const key of Object.keys(obj)) {
376
+ if (INDEX_PASS_PROVIDER_KEYS.has(key)) {
377
+ ctx.addIssue({
378
+ code: z.ZodIssueCode.custom,
379
+ message: `Duplicate LLM provider configuration: \`${[...(ctx.path ?? []), key].join(".")}\` is not allowed. ` +
380
+ "Configure provider/model/endpoint under `profiles.llm` only; per-pass entries support `{ llm: false }` opt-out.",
381
+ });
382
+ return raw;
383
+ }
384
+ if (!INDEX_PASS_KNOWN_KEYS.has(key)) {
385
+ ctx.addIssue({
386
+ code: z.ZodIssueCode.custom,
387
+ message: `Unknown key \`${[...(ctx.path ?? []), key].join(".")}\`. Per-pass entries support \`llm\` ` +
388
+ "(boolean opt-out), `graphExtractionBatchSize`, `graphExtractionIncludeTypes`, and " +
389
+ "`memoryInferenceBatchSize`.",
390
+ });
391
+ return raw;
392
+ }
393
+ }
394
+ if ("llm" in obj && typeof obj.llm !== "boolean") {
395
+ ctx.addIssue({
396
+ code: z.ZodIssueCode.custom,
397
+ message: `Invalid \`${[...(ctx.path ?? []), "llm"].join(".")}\`: expected a boolean (true to use the default LLM profile, false to opt out). Got ${typeof obj.llm}.`,
398
+ });
399
+ return raw;
400
+ }
401
+ return raw;
402
+ }, z
403
+ .object({
404
+ llm: z.boolean().optional(),
405
+ graphExtractionBatchSize: positiveInt.optional(),
406
+ graphExtractionIncludeTypes: z.array(z.enum(GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED)).nonempty().optional(),
407
+ memoryInferenceBatchSize: positiveInt.optional(),
408
+ })
409
+ .passthrough());
410
+ const MetadataEnhanceSchema = z.object({ enabled: z.boolean().optional() }).strict();
411
+ const StalenessDetectionSchema = z
412
+ .object({
413
+ enabled: z.boolean().optional(),
414
+ thresholdDays: positiveInt.optional(),
415
+ })
416
+ .strict();
417
+ /**
418
+ * Index config is a union of reserved feature sections and per-pass entries.
419
+ * Passthrough so per-pass entries (keyed by arbitrary pass names like `graph`,
420
+ * `enrichment`) can live next to the reserved keys.
421
+ *
422
+ * The outer preprocess emits the legacy parser's actionable error messages
423
+ * for the two most common type-shape mistakes:
424
+ * - An array at the `index` block.
425
+ * - A non-object at `index.<passName>`.
426
+ * Inner field validation (graphExtractionIncludeTypes enum, llm boolean,
427
+ * provider-key rejection) is delegated to {@link IndexPassConfigSchema}.
428
+ */
429
+ export const IndexConfigSchema = z.preprocess((raw, ctx) => {
430
+ if (raw === undefined || raw === null)
431
+ return raw;
432
+ if (Array.isArray(raw)) {
433
+ ctx.addIssue({
434
+ code: z.ZodIssueCode.custom,
435
+ message: 'Invalid `index` config: expected an object keyed by pass name (e.g. `{ "enrichment": { "llm": false } }`).',
436
+ });
437
+ return raw;
438
+ }
439
+ if (typeof raw !== "object")
440
+ return raw;
441
+ for (const [passName, value] of Object.entries(raw)) {
442
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
443
+ ctx.addIssue({
444
+ code: z.ZodIssueCode.custom,
445
+ message: `Invalid \`index.${passName}\` config: expected an object like \`{ "llm": false }\`.`,
446
+ });
447
+ return raw;
448
+ }
449
+ if (passName !== "metadataEnhance" &&
450
+ passName !== "stalenessDetection" &&
451
+ Array.isArray(value.graphExtractionIncludeTypes)) {
452
+ const arr = value.graphExtractionIncludeTypes;
453
+ const invalid = [];
454
+ for (const t of arr) {
455
+ if (typeof t === "string" &&
456
+ !GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED.includes(t.toLowerCase())) {
457
+ invalid.push(t);
458
+ }
459
+ }
460
+ if (invalid.length > 0) {
461
+ ctx.addIssue({
462
+ code: z.ZodIssueCode.custom,
463
+ message: `Invalid \`index.${passName}.graphExtractionIncludeTypes\`: unsupported type(s): ${invalid.join(", ")}.`,
464
+ });
465
+ return raw;
466
+ }
467
+ }
468
+ }
469
+ return raw;
470
+ }, z
471
+ .object({
472
+ metadataEnhance: MetadataEnhanceSchema.optional(),
473
+ stalenessDetection: StalenessDetectionSchema.optional(),
474
+ })
475
+ .catchall(IndexPassConfigSchema));
476
+ // ── Top-level AkmConfig ────────────────────────────────────────────────────
477
+ /**
478
+ * Base object schema used both as the top-level shape and as the source of
479
+ * truth for {@link listTopLevelConfigKeys}. {@link AkmConfigSchema} wraps this
480
+ * with cross-field refinements (`.superRefine()`).
481
+ *
482
+ * All fields validate loudly — typos and shape errors throw at load time. The
483
+ * legacy parser's warn-and-drop tolerance was a frequent source of silent
484
+ * configuration loss; the migration module ({@link migrateConfigShape}) handles
485
+ * one-time 0.7→0.8 input transforms before the schema sees the value.
486
+ */
487
+ export const AkmConfigShape = {
488
+ configVersion: z.union([z.string().min(1), z.number()]).optional(),
489
+ profiles: ProfilesSchema.optional(),
490
+ defaults: DefaultsSchema.optional(),
491
+ stashDir: nonEmptyString.optional(),
492
+ semanticSearchMode: z.enum(["off", "auto"]).default("auto"),
493
+ embedding: EmbeddingConnectionConfigSchema.optional(),
494
+ index: IndexConfigSchema.optional(),
495
+ installed: z.array(InstalledStashEntrySchema).optional(),
496
+ registries: z.array(RegistryConfigEntrySchema).optional(),
497
+ sources: z.array(SourceConfigEntrySchema).optional(),
498
+ output: OutputConfigSchema.optional(),
499
+ writable: z.boolean().optional(),
500
+ defaultWriteTarget: nonEmptyString.optional(),
501
+ search: SearchConfigSchema.optional(),
502
+ feedback: FeedbackConfigSchema.optional(),
503
+ archiveRetentionDays: nonNegativeNumber.optional(),
504
+ improve: ImproveConfigSchema.optional(),
505
+ };
506
+ export const AkmConfigBaseSchema = z.object(AkmConfigShape).strict();
507
+ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
508
+ // #464.a: defaultWriteTarget must name a configured source when sources
509
+ // are present. With no sources configured, error out instead of silently
510
+ // accepting (no implicit "first writable" fallback — see locked decision 3).
511
+ if (config.defaultWriteTarget !== undefined) {
512
+ const knownNames = (config.sources ?? [])
513
+ .map((s) => s.name)
514
+ .filter((n) => typeof n === "string" && n.length > 0);
515
+ if (knownNames.length === 0) {
516
+ ctx.addIssue({
517
+ code: z.ZodIssueCode.custom,
518
+ path: ["defaultWriteTarget"],
519
+ message: `defaultWriteTarget "${config.defaultWriteTarget}" cannot be resolved: no sources configured. ` +
520
+ "Add at least one entry to `sources` with a matching `name` first.",
521
+ });
522
+ }
523
+ else if (!knownNames.includes(config.defaultWriteTarget)) {
524
+ ctx.addIssue({
525
+ code: z.ZodIssueCode.custom,
526
+ path: ["defaultWriteTarget"],
527
+ message: `defaultWriteTarget "${config.defaultWriteTarget}" does not match any configured source name: ${knownNames.map((n) => `"${n}"`).join(", ")}.`,
528
+ });
529
+ }
530
+ }
531
+ });
532
+ /**
533
+ * Validate a raw object against {@link AkmConfigSchema}. Returns a structured
534
+ * result so callers can render errors as a list (instead of throwing on the
535
+ * first issue).
536
+ */
537
+ export function validateConfigShape(raw) {
538
+ const result = AkmConfigSchema.safeParse(raw);
539
+ if (result.success) {
540
+ return { ok: true, value: result.data, errors: [] };
541
+ }
542
+ return {
543
+ ok: false,
544
+ errors: result.error.issues.map((issue) => ({
545
+ path: issue.path.join("."),
546
+ message: issue.message,
547
+ })),
548
+ };
549
+ }
550
+ // ── Top-level key listing (for hint messages) ───────────────────────────────
551
+ /**
552
+ * Return the sorted list of top-level config keys recognized by the schema.
553
+ * Used by error hints so the list stays in sync with the schema automatically
554
+ * (#460).
555
+ */
556
+ export function listTopLevelConfigKeys() {
557
+ return Object.keys(AkmConfigShape).sort();
558
+ }
@@ -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 {};