akm-cli 0.7.4 → 0.8.0-rc1
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.
- package/{CHANGELOG.md → .github/CHANGELOG.md} +34 -1
- package/.github/LICENSE +374 -0
- package/dist/cli/parse-args.js +43 -0
- package/dist/cli.js +1007 -593
- package/dist/commands/agent-dispatch.js +102 -0
- package/dist/commands/agent-support.js +62 -0
- package/dist/commands/config-cli.js +68 -84
- package/dist/commands/consolidate.js +823 -0
- package/dist/commands/curate.js +1 -0
- package/dist/commands/distill-promotion-policy.js +658 -0
- package/dist/commands/distill.js +250 -48
- package/dist/commands/eval-cases.js +40 -0
- package/dist/commands/events.js +12 -24
- package/dist/commands/graph.js +222 -0
- package/dist/commands/health.js +376 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +53 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +3 -30
- package/dist/commands/improve.js +1170 -0
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.js +2 -2
- package/dist/commands/install-audit.js +5 -1
- package/dist/commands/installed-stashes.js +118 -138
- package/dist/commands/knowledge.js +133 -0
- package/dist/commands/lint/agent-linter.js +46 -0
- package/dist/commands/lint/base-linter.js +251 -0
- package/dist/commands/lint/command-linter.js +46 -0
- package/dist/commands/lint/default-linter.js +13 -0
- package/dist/commands/lint/index.js +107 -0
- package/dist/commands/lint/knowledge-linter.js +13 -0
- package/dist/commands/lint/memory-linter.js +58 -0
- package/dist/commands/lint/registry.js +33 -0
- package/dist/commands/lint/skill-linter.js +42 -0
- package/dist/commands/lint/task-linter.js +47 -0
- package/dist/commands/lint/types.js +1 -0
- package/dist/commands/lint/workflow-linter.js +53 -0
- package/dist/commands/lint.js +1 -0
- package/dist/commands/migration-help.js +2 -2
- package/dist/commands/proposal.js +8 -7
- package/dist/commands/propose.js +113 -43
- package/dist/commands/reflect.js +175 -41
- package/dist/commands/registry-search.js +2 -2
- package/dist/commands/remember.js +55 -1
- package/dist/commands/schema-repair.js +130 -0
- package/dist/commands/search.js +21 -5
- package/dist/commands/show.js +131 -52
- package/dist/commands/source-add.js +10 -10
- package/dist/commands/source-manage.js +11 -19
- package/dist/commands/tasks.js +385 -0
- package/dist/commands/url-checker.js +39 -0
- package/dist/commands/vault.js +7 -33
- package/dist/core/action-contributors.js +25 -0
- package/dist/core/asset-registry.js +5 -17
- package/dist/core/asset-spec.js +11 -1
- package/dist/core/common.js +94 -0
- package/dist/core/concurrent.js +22 -0
- package/dist/core/config.js +229 -122
- package/dist/core/events.js +87 -123
- package/dist/core/frontmatter.js +3 -1
- package/dist/core/markdown.js +17 -0
- package/dist/core/memory-improve.js +678 -0
- package/dist/core/parse.js +155 -0
- package/dist/core/paths.js +101 -3
- package/dist/core/proposal-validators.js +61 -0
- package/dist/core/proposals.js +49 -38
- package/dist/core/state-db.js +775 -0
- package/dist/core/time.js +51 -0
- package/dist/core/warn.js +59 -1
- package/dist/indexer/db-search.js +86 -472
- package/dist/indexer/db.js +392 -6
- package/dist/indexer/ensure-index.js +133 -0
- package/dist/indexer/graph-boost.js +247 -94
- package/dist/indexer/graph-db.js +201 -0
- package/dist/indexer/graph-dedup.js +99 -0
- package/dist/indexer/graph-extraction.js +417 -74
- package/dist/indexer/index-context.js +10 -0
- package/dist/indexer/indexer.js +466 -298
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/match-contributors.js +141 -0
- package/dist/indexer/matchers.js +24 -190
- package/dist/indexer/memory-inference.js +63 -29
- package/dist/indexer/metadata-contributors.js +26 -0
- package/dist/indexer/metadata.js +188 -175
- package/dist/indexer/path-resolver.js +89 -0
- package/dist/indexer/ranking-contributors.js +204 -0
- package/dist/indexer/ranking.js +74 -0
- package/dist/indexer/search-hit-enrichers.js +22 -0
- package/dist/indexer/search-source.js +24 -9
- package/dist/indexer/semantic-status.js +2 -16
- package/dist/indexer/walker.js +25 -0
- package/dist/integrations/agent/config.js +175 -3
- package/dist/integrations/agent/index.js +3 -1
- package/dist/integrations/agent/pipeline.js +39 -0
- package/dist/integrations/agent/profiles.js +67 -5
- package/dist/integrations/agent/prompts.js +114 -29
- package/dist/integrations/agent/runners.js +31 -0
- package/dist/integrations/agent/sdk-runner.js +120 -0
- package/dist/integrations/agent/spawn.js +136 -28
- package/dist/integrations/lockfile.js +10 -18
- package/dist/integrations/session-logs/index.js +65 -0
- package/dist/integrations/session-logs/providers/claude-code.js +56 -0
- package/dist/integrations/session-logs/providers/opencode.js +52 -0
- package/dist/integrations/session-logs/types.js +1 -0
- package/dist/llm/call-ai.js +74 -0
- package/dist/llm/client.js +63 -86
- package/dist/llm/feature-gate.js +27 -16
- package/dist/llm/graph-extract.js +297 -64
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +39 -22
- package/dist/llm/prompts/graph-extract-user-prompt.md +12 -0
- package/dist/output/cli-hints-full.md +277 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +2 -309
- package/dist/output/renderers.js +196 -124
- package/dist/output/shapes.js +41 -3
- package/dist/output/text.js +257 -21
- package/dist/registry/providers/skills-sh.js +61 -49
- package/dist/registry/providers/static-index.js +44 -48
- package/dist/setup/setup.js +510 -11
- package/dist/sources/provider-factory.js +2 -1
- package/dist/sources/providers/git.js +44 -2
- package/dist/sources/website-ingest.js +4 -0
- package/dist/tasks/backends/cron.js +200 -0
- package/dist/tasks/backends/exec-utils.js +25 -0
- package/dist/tasks/backends/index.js +32 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +184 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +212 -0
- package/dist/tasks/parser.js +198 -0
- package/dist/tasks/resolveAkmBin.js +84 -0
- package/dist/tasks/runner.js +432 -0
- package/dist/tasks/schedule.js +208 -0
- package/dist/tasks/schema.js +13 -0
- package/dist/tasks/validator.js +59 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +12 -0
- package/dist/wiki/wiki.js +10 -61
- package/dist/workflows/authoring.js +5 -25
- package/dist/workflows/db.js +9 -0
- package/dist/workflows/renderer.js +8 -3
- package/dist/workflows/runs.js +73 -88
- package/dist/workflows/scope-key.js +76 -0
- package/dist/workflows/validator.js +1 -1
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +3 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +43 -0
- package/package.json +4 -3
- package/dist/templates/wiki-templates.js +0 -100
package/dist/core/config.js
CHANGED
|
@@ -2,9 +2,9 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseAgentConfig } from "../integrations/agent/config";
|
|
5
|
-
import { filterNonEmptyStrings } from "./common";
|
|
5
|
+
import { asNonEmptyString, filterNonEmptyStrings, writeFileAtomic } from "./common";
|
|
6
6
|
import { ConfigError } from "./errors";
|
|
7
|
-
import {
|
|
7
|
+
import { getCacheDir, getConfigPath } from "./paths";
|
|
8
8
|
import { warn } from "./warn";
|
|
9
9
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
10
10
|
export const DEFAULT_CONFIG = {
|
|
@@ -19,19 +19,53 @@ export const DEFAULT_CONFIG = {
|
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
// ── Paths ───────────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// ── Private helpers ─────────────────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Returns `value` if it is a finite positive integer; otherwise `undefined`.
|
|
25
|
+
* Used to validate numeric config fields like `dimension`, `contextLength`,
|
|
26
|
+
* `timeoutMs`, `maxTokens`, and `ollamaOptions.num_ctx`.
|
|
27
|
+
*/
|
|
28
|
+
function parsePositiveInteger(_fieldPath, value) {
|
|
29
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
function parseNonNegativeNumber(value) {
|
|
35
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
36
|
+
return undefined;
|
|
37
|
+
return value;
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Returns `value` if it is a string present in `allowed`; otherwise `undefined`.
|
|
41
|
+
*/
|
|
42
|
+
function isOneOf(value, allowed) {
|
|
43
|
+
return typeof value === "string" && allowed.includes(value);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validates that `url` starts with `http://` or `https://`. Returns `url` on
|
|
47
|
+
* success and warns+returns `undefined` on failure. `fieldName` is used only
|
|
48
|
+
* in the warning message.
|
|
49
|
+
*/
|
|
50
|
+
function isValidHttpUrl(url, fieldName) {
|
|
51
|
+
if (typeof url !== "string" || !url)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
54
|
+
warn(`[akm] Ignoring ${fieldName}: endpoint must start with http:// or https://, got "${url}"`);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return url;
|
|
58
|
+
}
|
|
59
|
+
function clearAllCaches() {
|
|
60
|
+
cachedConfig = undefined;
|
|
61
|
+
cachedUserConfig = undefined;
|
|
27
62
|
}
|
|
28
63
|
// ── Load / Save / Update ────────────────────────────────────────────────────
|
|
29
64
|
const PROJECT_CONFIG_RELATIVE_PATH = path.join(".akm", "config.json");
|
|
30
65
|
let cachedConfig;
|
|
31
66
|
let cachedUserConfig;
|
|
32
67
|
export function resetConfigCache() {
|
|
33
|
-
|
|
34
|
-
cachedUserConfig = undefined;
|
|
68
|
+
clearAllCaches();
|
|
35
69
|
}
|
|
36
70
|
function hashString(text) {
|
|
37
71
|
// Simple, fast non-cryptographic hash (FNV-1a 32-bit) — sufficient to detect
|
|
@@ -85,6 +119,17 @@ export function loadUserConfig() {
|
|
|
85
119
|
};
|
|
86
120
|
return finalConfig;
|
|
87
121
|
}
|
|
122
|
+
export function getSources(config) {
|
|
123
|
+
return config.sources ?? [];
|
|
124
|
+
}
|
|
125
|
+
export function getEffectiveRegistries(config) {
|
|
126
|
+
return config.registries ?? DEFAULT_CONFIG.registries ?? [];
|
|
127
|
+
}
|
|
128
|
+
export function requireLlmConfig(config) {
|
|
129
|
+
if (!config.llm)
|
|
130
|
+
throw new ConfigError("LLM is not configured. Run `akm config set llm` to configure one.", "LLM_NOT_CONFIGURED");
|
|
131
|
+
return config.llm;
|
|
132
|
+
}
|
|
88
133
|
export function loadConfig() {
|
|
89
134
|
const configPaths = getEffectiveConfigPaths();
|
|
90
135
|
const signature = getConfigSignature(configPaths);
|
|
@@ -103,8 +148,7 @@ export function loadConfig() {
|
|
|
103
148
|
return finalConfig;
|
|
104
149
|
}
|
|
105
150
|
export function saveConfig(config) {
|
|
106
|
-
|
|
107
|
-
cachedUserConfig = undefined;
|
|
151
|
+
clearAllCaches();
|
|
108
152
|
const configPath = getConfigPath();
|
|
109
153
|
const dir = path.dirname(configPath);
|
|
110
154
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -143,31 +187,7 @@ function sanitizeConfigForWrite(config) {
|
|
|
143
187
|
}
|
|
144
188
|
export function updateConfig(partial) {
|
|
145
189
|
const current = loadUserConfig();
|
|
146
|
-
|
|
147
|
-
const merged = { ...current, ...partial };
|
|
148
|
-
// Deep-merge output — partial update should not wipe sibling keys
|
|
149
|
-
if (current.output && partial.output && partial.output !== current.output) {
|
|
150
|
-
merged.output = { ...current.output, ...partial.output };
|
|
151
|
-
}
|
|
152
|
-
// Deep-merge embedding — only when both sides are objects and partial does not intend to clear
|
|
153
|
-
if (current.embedding && partial.embedding && partial.embedding !== current.embedding) {
|
|
154
|
-
merged.embedding = { ...current.embedding, ...partial.embedding };
|
|
155
|
-
}
|
|
156
|
-
// Deep-merge llm — same pattern
|
|
157
|
-
if (current.llm && partial.llm && partial.llm !== current.llm) {
|
|
158
|
-
merged.llm = { ...current.llm, ...partial.llm };
|
|
159
|
-
}
|
|
160
|
-
// Deep-merge index per-pass entries so partial updates don't wipe siblings.
|
|
161
|
-
if (current.index && partial.index && partial.index !== current.index) {
|
|
162
|
-
const mergedIndex = { ...current.index };
|
|
163
|
-
for (const [passName, passOverride] of Object.entries(partial.index)) {
|
|
164
|
-
mergedIndex[passName] = { ...(mergedIndex[passName] ?? {}), ...passOverride };
|
|
165
|
-
}
|
|
166
|
-
merged.index = mergedIndex;
|
|
167
|
-
}
|
|
168
|
-
if (current.security && partial.security && partial.security !== current.security) {
|
|
169
|
-
merged.security = mergeSecurityConfig(current.security, partial.security);
|
|
170
|
-
}
|
|
190
|
+
const merged = mergeLoadedConfig(current, partial);
|
|
171
191
|
saveConfig(merged);
|
|
172
192
|
return merged;
|
|
173
193
|
}
|
|
@@ -179,11 +199,8 @@ export function updateConfig(partial) {
|
|
|
179
199
|
* combining multiple config sources so project config files only override what
|
|
180
200
|
* they set.
|
|
181
201
|
*/
|
|
182
|
-
function
|
|
202
|
+
function parseConfigLayer(raw) {
|
|
183
203
|
const config = {};
|
|
184
|
-
if (Array.isArray(raw.stashes)) {
|
|
185
|
-
throw new ConfigError("The legacy `stashes[]` config key is no longer supported; rename it to `sources[]`.", "INVALID_CONFIG_FILE", `Edit ${_getConfigPath()} and replace \`stashes\` with \`sources\`.`);
|
|
186
|
-
}
|
|
187
204
|
if (typeof raw.stashDir === "string" && raw.stashDir.trim()) {
|
|
188
205
|
config.stashDir = raw.stashDir.trim();
|
|
189
206
|
}
|
|
@@ -191,7 +208,7 @@ function pickKnownKeys(raw) {
|
|
|
191
208
|
if (typeof raw.semanticSearchMode === "boolean") {
|
|
192
209
|
config.semanticSearchMode = raw.semanticSearchMode ? "auto" : "off";
|
|
193
210
|
}
|
|
194
|
-
else if (raw.semanticSearchMode
|
|
211
|
+
else if (isOneOf(raw.semanticSearchMode, ["off", "auto"])) {
|
|
195
212
|
config.semanticSearchMode = raw.semanticSearchMode;
|
|
196
213
|
}
|
|
197
214
|
const embedding = parseEmbeddingConfig(raw.embedding);
|
|
@@ -209,9 +226,12 @@ function pickKnownKeys(raw) {
|
|
|
209
226
|
const registries = parseRegistriesConfig(raw.registries);
|
|
210
227
|
if (registries)
|
|
211
228
|
config.registries = registries;
|
|
212
|
-
if (raw.stashInheritance
|
|
229
|
+
if (isOneOf(raw.stashInheritance, ["replace", "merge"])) {
|
|
213
230
|
config.stashInheritance = raw.stashInheritance;
|
|
214
231
|
}
|
|
232
|
+
if (Array.isArray(raw.stashes)) {
|
|
233
|
+
throw new ConfigError("The legacy `stashes[]` config key is no longer supported. Rename it to `sources`.", "INVALID_CONFIG_FILE");
|
|
234
|
+
}
|
|
215
235
|
const sources = parseSourcesConfig(raw.sources);
|
|
216
236
|
if (sources) {
|
|
217
237
|
config.sources = sources;
|
|
@@ -236,35 +256,102 @@ function pickKnownKeys(raw) {
|
|
|
236
256
|
if (typeof raw.search === "object" && raw.search !== null && !Array.isArray(raw.search)) {
|
|
237
257
|
const searchRaw = raw.search;
|
|
238
258
|
const searchConfig = {};
|
|
259
|
+
for (const key of Object.keys(searchRaw)) {
|
|
260
|
+
if (key !== "minScore" && key !== "graphBoost") {
|
|
261
|
+
warn(`[akm] Ignoring unknown search key "${key}".`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
239
264
|
if (typeof searchRaw.minScore === "number" && Number.isFinite(searchRaw.minScore) && searchRaw.minScore >= 0) {
|
|
240
265
|
searchConfig.minScore = searchRaw.minScore;
|
|
241
266
|
}
|
|
267
|
+
if (typeof searchRaw.graphBoost === "object" &&
|
|
268
|
+
searchRaw.graphBoost !== null &&
|
|
269
|
+
!Array.isArray(searchRaw.graphBoost)) {
|
|
270
|
+
const graphBoostRaw = searchRaw.graphBoost;
|
|
271
|
+
const graphBoostConfig = {};
|
|
272
|
+
for (const key of Object.keys(graphBoostRaw)) {
|
|
273
|
+
if (key !== "directBoostPerEntity" &&
|
|
274
|
+
key !== "directBoostCap" &&
|
|
275
|
+
key !== "hopBoostPerEntity" &&
|
|
276
|
+
key !== "hopBoostCap" &&
|
|
277
|
+
key !== "maxHops" &&
|
|
278
|
+
key !== "confidenceMode" &&
|
|
279
|
+
key !== "confidenceWeight") {
|
|
280
|
+
warn(`[akm] Ignoring unknown search.graphBoost key "${key}".`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const directBoostPerEntity = parseNonNegativeNumber(graphBoostRaw.directBoostPerEntity);
|
|
284
|
+
if (directBoostPerEntity !== undefined)
|
|
285
|
+
graphBoostConfig.directBoostPerEntity = directBoostPerEntity;
|
|
286
|
+
const directBoostCap = parseNonNegativeNumber(graphBoostRaw.directBoostCap);
|
|
287
|
+
if (directBoostCap !== undefined)
|
|
288
|
+
graphBoostConfig.directBoostCap = directBoostCap;
|
|
289
|
+
const hopBoostPerEntity = parseNonNegativeNumber(graphBoostRaw.hopBoostPerEntity);
|
|
290
|
+
if (hopBoostPerEntity !== undefined)
|
|
291
|
+
graphBoostConfig.hopBoostPerEntity = hopBoostPerEntity;
|
|
292
|
+
const hopBoostCap = parseNonNegativeNumber(graphBoostRaw.hopBoostCap);
|
|
293
|
+
if (hopBoostCap !== undefined)
|
|
294
|
+
graphBoostConfig.hopBoostCap = hopBoostCap;
|
|
295
|
+
const maxHops = parsePositiveInteger("search.graphBoost.maxHops", graphBoostRaw.maxHops);
|
|
296
|
+
if (maxHops !== undefined)
|
|
297
|
+
graphBoostConfig.maxHops = Math.min(maxHops, 3);
|
|
298
|
+
if (isOneOf(graphBoostRaw.confidenceMode, ["off", "blend", "multiply"])) {
|
|
299
|
+
graphBoostConfig.confidenceMode = graphBoostRaw.confidenceMode;
|
|
300
|
+
}
|
|
301
|
+
const confidenceWeight = parseNonNegativeNumber(graphBoostRaw.confidenceWeight);
|
|
302
|
+
if (confidenceWeight !== undefined)
|
|
303
|
+
graphBoostConfig.confidenceWeight = Math.min(confidenceWeight, 1);
|
|
304
|
+
if (Object.keys(graphBoostConfig).length > 0)
|
|
305
|
+
searchConfig.graphBoost = graphBoostConfig;
|
|
306
|
+
}
|
|
242
307
|
if (Object.keys(searchConfig).length > 0)
|
|
243
308
|
config.search = searchConfig;
|
|
244
309
|
}
|
|
310
|
+
if (typeof raw.feedback === "object" && raw.feedback !== null && !Array.isArray(raw.feedback)) {
|
|
311
|
+
const feedbackRaw = raw.feedback;
|
|
312
|
+
const feedbackConfig = {};
|
|
313
|
+
if (typeof feedbackRaw.requireReason === "boolean") {
|
|
314
|
+
feedbackConfig.requireReason = feedbackRaw.requireReason;
|
|
315
|
+
}
|
|
316
|
+
if (Object.keys(feedbackConfig).length > 0)
|
|
317
|
+
config.feedback = feedbackConfig;
|
|
318
|
+
}
|
|
319
|
+
if (typeof raw.archiveRetentionDays === "number" &&
|
|
320
|
+
Number.isFinite(raw.archiveRetentionDays) &&
|
|
321
|
+
raw.archiveRetentionDays >= 0) {
|
|
322
|
+
config.archiveRetentionDays = raw.archiveRetentionDays;
|
|
323
|
+
}
|
|
245
324
|
return config;
|
|
246
325
|
}
|
|
247
|
-
function
|
|
248
|
-
const raw = readConfigObject(configPath);
|
|
249
|
-
const expanded = raw ? expandEnvVars(raw) : undefined;
|
|
250
|
-
return expanded ? pickKnownKeys(expanded) : undefined;
|
|
251
|
-
}
|
|
252
|
-
function readNormalizedConfigFromText(text) {
|
|
326
|
+
function parseConfigText(text) {
|
|
253
327
|
const raw = parseConfigObjectFromText(text);
|
|
254
328
|
if (!raw)
|
|
255
329
|
return undefined;
|
|
256
330
|
const expanded = expandEnvVars(raw);
|
|
257
|
-
return
|
|
331
|
+
return parseConfigLayer(expanded);
|
|
332
|
+
}
|
|
333
|
+
function readNormalizedConfig(configPath) {
|
|
334
|
+
let text;
|
|
335
|
+
try {
|
|
336
|
+
text = fs.readFileSync(configPath, "utf8");
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
return parseConfigText(text);
|
|
342
|
+
}
|
|
343
|
+
function readNormalizedConfigFromText(text) {
|
|
344
|
+
return parseConfigText(text);
|
|
258
345
|
}
|
|
259
346
|
function parseOutputConfig(value) {
|
|
260
347
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
261
348
|
return undefined;
|
|
262
349
|
const obj = value;
|
|
263
350
|
const output = {};
|
|
264
|
-
if (obj.format
|
|
351
|
+
if (isOneOf(obj.format, ["json", "yaml", "text"])) {
|
|
265
352
|
output.format = obj.format;
|
|
266
353
|
}
|
|
267
|
-
if (obj.detail
|
|
354
|
+
if (isOneOf(obj.detail, ["brief", "normal", "full"])) {
|
|
268
355
|
output.detail = obj.detail;
|
|
269
356
|
}
|
|
270
357
|
return Object.keys(output).length > 0 ? output : undefined;
|
|
@@ -313,15 +400,6 @@ function expandEnvVars(value, fieldName) {
|
|
|
313
400
|
}
|
|
314
401
|
return value;
|
|
315
402
|
}
|
|
316
|
-
function readConfigObject(configPath) {
|
|
317
|
-
try {
|
|
318
|
-
const text = fs.readFileSync(configPath, "utf8");
|
|
319
|
-
return parseConfigObjectFromText(text);
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
return undefined;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
403
|
function parseConfigObjectFromText(text) {
|
|
326
404
|
try {
|
|
327
405
|
const raw = JSON.parse(stripJsonComments(text));
|
|
@@ -334,20 +412,7 @@ function parseConfigObjectFromText(text) {
|
|
|
334
412
|
}
|
|
335
413
|
}
|
|
336
414
|
function writeConfigObject(configPath, config) {
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
fs.writeFileSync(tmpPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
340
|
-
fs.renameSync(tmpPath, configPath);
|
|
341
|
-
}
|
|
342
|
-
catch (err) {
|
|
343
|
-
try {
|
|
344
|
-
fs.unlinkSync(tmpPath);
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
/* ignore cleanup failure */
|
|
348
|
-
}
|
|
349
|
-
throw err;
|
|
350
|
-
}
|
|
415
|
+
writeFileAtomic(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
351
416
|
}
|
|
352
417
|
/**
|
|
353
418
|
* Strip JavaScript-style comments from a JSON string (JSONC support).
|
|
@@ -413,8 +478,7 @@ function parseEmbeddingConfig(value) {
|
|
|
413
478
|
}
|
|
414
479
|
return undefined;
|
|
415
480
|
}
|
|
416
|
-
if (!obj.endpoint
|
|
417
|
-
warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
481
|
+
if (!isValidHttpUrl(obj.endpoint, "embedding config")) {
|
|
418
482
|
// Still return localModel-only config if localModel was set
|
|
419
483
|
if (localModel) {
|
|
420
484
|
return { endpoint: "", model: "", localModel };
|
|
@@ -437,13 +501,10 @@ function parseEmbeddingConfig(value) {
|
|
|
437
501
|
result.provider = obj.provider;
|
|
438
502
|
}
|
|
439
503
|
if ("dimension" in obj) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
!Number.isInteger(obj.dimension) ||
|
|
443
|
-
obj.dimension <= 0) {
|
|
504
|
+
const dim = parsePositiveInteger("embedding.dimension", obj.dimension);
|
|
505
|
+
if (dim === undefined)
|
|
444
506
|
return undefined;
|
|
445
|
-
|
|
446
|
-
result.dimension = obj.dimension;
|
|
507
|
+
result.dimension = dim;
|
|
447
508
|
}
|
|
448
509
|
if (typeof obj.apiKey === "string" && obj.apiKey) {
|
|
449
510
|
result.apiKey = obj.apiKey;
|
|
@@ -452,22 +513,17 @@ function parseEmbeddingConfig(value) {
|
|
|
452
513
|
result.localModel = localModel;
|
|
453
514
|
}
|
|
454
515
|
if ("contextLength" in obj) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
!Number.isInteger(obj.contextLength) ||
|
|
458
|
-
obj.contextLength <= 0) {
|
|
516
|
+
const ctx = parsePositiveInteger("embedding.contextLength", obj.contextLength);
|
|
517
|
+
if (ctx === undefined)
|
|
459
518
|
return undefined;
|
|
460
|
-
|
|
461
|
-
result.contextLength = obj.contextLength;
|
|
519
|
+
result.contextLength = ctx;
|
|
462
520
|
}
|
|
463
521
|
if (typeof obj.ollamaOptions === "object" && obj.ollamaOptions !== null && !Array.isArray(obj.ollamaOptions)) {
|
|
464
522
|
const opts = obj.ollamaOptions;
|
|
465
523
|
const parsed = {};
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
opts.num_ctx > 0) {
|
|
470
|
-
parsed.num_ctx = opts.num_ctx;
|
|
524
|
+
const numCtx = parsePositiveInteger("embedding.ollamaOptions.num_ctx", opts.num_ctx);
|
|
525
|
+
if (numCtx !== undefined) {
|
|
526
|
+
parsed.num_ctx = numCtx;
|
|
471
527
|
}
|
|
472
528
|
if (Object.keys(parsed).length > 0) {
|
|
473
529
|
result.ollamaOptions = parsed;
|
|
@@ -481,10 +537,13 @@ function parseLlmConfig(value) {
|
|
|
481
537
|
const obj = value;
|
|
482
538
|
if (typeof obj.endpoint !== "string" || !obj.endpoint)
|
|
483
539
|
return undefined;
|
|
484
|
-
if (!obj.endpoint
|
|
485
|
-
warn(`[akm] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
|
|
540
|
+
if (!isValidHttpUrl(obj.endpoint, "llm config")) {
|
|
486
541
|
return undefined;
|
|
487
542
|
}
|
|
543
|
+
if (!obj.endpoint.endsWith("/chat/completions")) {
|
|
544
|
+
warn(`[akm] llm.endpoint "${obj.endpoint}" does not end in /chat/completions. ` +
|
|
545
|
+
`Did you mean "${obj.endpoint.replace(/\/+$/, "")}/chat/completions"?`);
|
|
546
|
+
}
|
|
488
547
|
const model = typeof obj.model === "string" ? obj.model : "";
|
|
489
548
|
const result = {
|
|
490
549
|
endpoint: obj.endpoint,
|
|
@@ -496,14 +555,28 @@ function parseLlmConfig(value) {
|
|
|
496
555
|
if (typeof obj.temperature === "number" && Number.isFinite(obj.temperature)) {
|
|
497
556
|
result.temperature = obj.temperature;
|
|
498
557
|
}
|
|
558
|
+
if ("timeoutMs" in obj) {
|
|
559
|
+
const t = parsePositiveInteger("llm.timeoutMs", obj.timeoutMs);
|
|
560
|
+
if (t === undefined)
|
|
561
|
+
return undefined;
|
|
562
|
+
result.timeoutMs = t;
|
|
563
|
+
}
|
|
564
|
+
if ("concurrency" in obj) {
|
|
565
|
+
const c = parsePositiveInteger("llm.concurrency", obj.concurrency);
|
|
566
|
+
if (c === undefined)
|
|
567
|
+
return undefined;
|
|
568
|
+
result.concurrency = c;
|
|
569
|
+
}
|
|
499
570
|
if ("maxTokens" in obj) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
!Number.isInteger(obj.maxTokens) ||
|
|
503
|
-
obj.maxTokens <= 0) {
|
|
571
|
+
const m = parsePositiveInteger("llm.maxTokens", obj.maxTokens);
|
|
572
|
+
if (m === undefined)
|
|
504
573
|
return undefined;
|
|
505
|
-
|
|
506
|
-
|
|
574
|
+
result.maxTokens = m;
|
|
575
|
+
}
|
|
576
|
+
if ("contextLength" in obj) {
|
|
577
|
+
const ctx = parsePositiveInteger("llm.contextLength", obj.contextLength);
|
|
578
|
+
if (ctx !== undefined)
|
|
579
|
+
result.contextLength = ctx;
|
|
507
580
|
}
|
|
508
581
|
if (typeof obj.apiKey === "string" && obj.apiKey) {
|
|
509
582
|
result.apiKey = obj.apiKey;
|
|
@@ -521,6 +594,9 @@ function parseLlmConfig(value) {
|
|
|
521
594
|
if (Object.keys(features).length > 0)
|
|
522
595
|
result.features = features;
|
|
523
596
|
}
|
|
597
|
+
if (typeof obj.judgeModel === "string" && obj.judgeModel.trim()) {
|
|
598
|
+
result.judgeModel = obj.judgeModel.trim();
|
|
599
|
+
}
|
|
524
600
|
if (typeof obj.extraParams === "object" && obj.extraParams !== null && !Array.isArray(obj.extraParams)) {
|
|
525
601
|
result.extraParams = obj.extraParams;
|
|
526
602
|
}
|
|
@@ -538,6 +614,9 @@ const LOCKED_LLM_FEATURE_KEYS = new Set([
|
|
|
538
614
|
"feedback_distillation",
|
|
539
615
|
"memory_inference",
|
|
540
616
|
"graph_extraction",
|
|
617
|
+
"memory_consolidation",
|
|
618
|
+
"lesson_quality_gate",
|
|
619
|
+
"metadata_enhance",
|
|
541
620
|
]);
|
|
542
621
|
function parseLlmFeatures(raw) {
|
|
543
622
|
const out = {};
|
|
@@ -550,22 +629,8 @@ function parseLlmFeatures(raw) {
|
|
|
550
629
|
warn(`[akm] Ignoring llm.features.${key}: expected boolean, got ${typeof value}.`);
|
|
551
630
|
continue;
|
|
552
631
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
out.memory_inference = value;
|
|
556
|
-
break;
|
|
557
|
-
case "graph_extraction":
|
|
558
|
-
out.graph_extraction = value;
|
|
559
|
-
break;
|
|
560
|
-
case "curate_rerank":
|
|
561
|
-
out.curate_rerank = value;
|
|
562
|
-
break;
|
|
563
|
-
case "feedback_distillation":
|
|
564
|
-
out.feedback_distillation = value;
|
|
565
|
-
break;
|
|
566
|
-
// No default: LOCKED_LLM_FEATURE_KEYS is the source of truth for which
|
|
567
|
-
// keys are accepted. Adding a new locked key requires an arm here AND a
|
|
568
|
-
// field on LlmFeatureFlags above.
|
|
632
|
+
if (LOCKED_LLM_FEATURE_KEYS.has(key)) {
|
|
633
|
+
out[key] = value;
|
|
569
634
|
}
|
|
570
635
|
}
|
|
571
636
|
return out;
|
|
@@ -586,6 +651,17 @@ const PROVIDER_CONFIG_KEYS = new Set([
|
|
|
586
651
|
"maxTokens",
|
|
587
652
|
"capabilities",
|
|
588
653
|
]);
|
|
654
|
+
const GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED = new Set([
|
|
655
|
+
"memory",
|
|
656
|
+
"knowledge",
|
|
657
|
+
"skill",
|
|
658
|
+
"command",
|
|
659
|
+
"agent",
|
|
660
|
+
"workflow",
|
|
661
|
+
"lesson",
|
|
662
|
+
"task",
|
|
663
|
+
"wiki",
|
|
664
|
+
]);
|
|
589
665
|
/**
|
|
590
666
|
* Parse the `index` config block. Each entry is a pass name → small object
|
|
591
667
|
* `{ llm?: boolean }`. Anything richer (a parallel provider config, unknown
|
|
@@ -611,8 +687,11 @@ function parseIndexConfig(value) {
|
|
|
611
687
|
throw new ConfigError(`Duplicate LLM provider configuration: \`index.${passName}.${key}\` is not allowed. ` +
|
|
612
688
|
"Configure provider/model/endpoint under top-level `llm` only; per-pass entries support `{ llm: false }` opt-out.", "INVALID_CONFIG_FILE", 'Move provider settings to the top-level "llm" block, then set `index.<pass>.llm = false` to opt a single pass out.');
|
|
613
689
|
}
|
|
614
|
-
if (key !== "llm"
|
|
615
|
-
|
|
690
|
+
if (key !== "llm" &&
|
|
691
|
+
key !== "graphExtractionBatchSize" &&
|
|
692
|
+
key !== "graphExtractionIncludeTypes" &&
|
|
693
|
+
key !== "memoryInferenceBatchSize") {
|
|
694
|
+
throw new ConfigError(`Unknown key \`index.${passName}.${key}\`. Per-pass entries support \`llm\` (boolean opt-out), \`graphExtractionBatchSize\`, \`graphExtractionIncludeTypes\`, and \`memoryInferenceBatchSize\`.`, "INVALID_CONFIG_FILE");
|
|
616
695
|
}
|
|
617
696
|
}
|
|
618
697
|
const passConfig = {};
|
|
@@ -623,6 +702,28 @@ function parseIndexConfig(value) {
|
|
|
623
702
|
}
|
|
624
703
|
passConfig.llm = llmFlag;
|
|
625
704
|
}
|
|
705
|
+
if ("graphExtractionBatchSize" in passRaw) {
|
|
706
|
+
const n = parsePositiveInteger(`index.${passName}.graphExtractionBatchSize`, passRaw.graphExtractionBatchSize);
|
|
707
|
+
if (n !== undefined)
|
|
708
|
+
passConfig.graphExtractionBatchSize = n;
|
|
709
|
+
}
|
|
710
|
+
if ("graphExtractionIncludeTypes" in passRaw) {
|
|
711
|
+
const rawTypes = passRaw.graphExtractionIncludeTypes;
|
|
712
|
+
if (!Array.isArray(rawTypes) || !rawTypes.every((t) => typeof t === "string" && t.trim().length > 0)) {
|
|
713
|
+
throw new ConfigError(`Invalid \`index.${passName}.graphExtractionIncludeTypes\`: expected a non-empty string array of asset types.`, "INVALID_CONFIG_FILE");
|
|
714
|
+
}
|
|
715
|
+
const normalized = rawTypes.map((t) => t.trim().toLowerCase());
|
|
716
|
+
const invalid = normalized.filter((t) => !GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED.has(t));
|
|
717
|
+
if (invalid.length > 0) {
|
|
718
|
+
throw new ConfigError(`Invalid \`index.${passName}.graphExtractionIncludeTypes\`: unsupported type(s): ${invalid.join(", ")}.`, "INVALID_CONFIG_FILE");
|
|
719
|
+
}
|
|
720
|
+
passConfig.graphExtractionIncludeTypes = normalized;
|
|
721
|
+
}
|
|
722
|
+
if ("memoryInferenceBatchSize" in passRaw) {
|
|
723
|
+
const n = parsePositiveInteger(`index.${passName}.memoryInferenceBatchSize`, passRaw.memoryInferenceBatchSize);
|
|
724
|
+
if (n !== undefined)
|
|
725
|
+
passConfig.memoryInferenceBatchSize = n;
|
|
726
|
+
}
|
|
626
727
|
out[passName] = passConfig;
|
|
627
728
|
}
|
|
628
729
|
return out;
|
|
@@ -673,9 +774,6 @@ function parseInstalledStashEntry(value) {
|
|
|
673
774
|
entry.wikiName = wikiName;
|
|
674
775
|
return entry;
|
|
675
776
|
}
|
|
676
|
-
function asNonEmptyString(value) {
|
|
677
|
-
return typeof value === "string" && value ? value : undefined;
|
|
678
|
-
}
|
|
679
777
|
/**
|
|
680
778
|
* Validate a legacy lockfile/installed-entry source string.
|
|
681
779
|
*
|
|
@@ -728,6 +826,9 @@ function parseInstallAuditConfig(value) {
|
|
|
728
826
|
if (typeof obj.blockUnlistedRegistries === "boolean")
|
|
729
827
|
config.blockUnlistedRegistries = obj.blockUnlistedRegistries;
|
|
730
828
|
const rawAllowlist = filterNonEmptyStrings(obj.registryAllowlist) ?? filterNonEmptyStrings(obj.registryWhitelist);
|
|
829
|
+
if (!obj.registryAllowlist && obj.registryWhitelist) {
|
|
830
|
+
warn("[akm] config: `registryWhitelist` is deprecated; rename it to `registryAllowlist`");
|
|
831
|
+
}
|
|
731
832
|
if (rawAllowlist) {
|
|
732
833
|
config.registryAllowlist = rawAllowlist;
|
|
733
834
|
}
|
|
@@ -773,7 +874,7 @@ function parseSourceConfigEntry(value) {
|
|
|
773
874
|
return undefined;
|
|
774
875
|
if (type === "openviking") {
|
|
775
876
|
const name = asNonEmptyString(obj.name) ?? "unnamed";
|
|
776
|
-
throw new ConfigError(`openviking is not supported in akm v1. API-backed sources will return as a\nseparate QuerySource tier post-v1. Remove the source named "${name}" from your config file\nor downgrade to 0.6.x. See docs/migration/v1.md.`, "INVALID_CONFIG_FILE", `Run \`akm remove ${name}\` then re-run, or edit your config file directly at ${
|
|
877
|
+
throw new ConfigError(`openviking is not supported in akm v1. API-backed sources will return as a\nseparate QuerySource tier post-v1. Remove the source named "${name}" from your config file\nor downgrade to 0.6.x. See docs/migration/v1.md.`, "INVALID_CONFIG_FILE", `Run \`akm remove ${name}\` then re-run, or edit your config file directly at ${getConfigPath()} to remove the openviking entry.`);
|
|
777
878
|
}
|
|
778
879
|
const entry = { type };
|
|
779
880
|
const entryPath = asNonEmptyString(obj.path);
|
|
@@ -952,6 +1053,12 @@ function mergeAgentConfig(base, override) {
|
|
|
952
1053
|
}
|
|
953
1054
|
merged.profiles = profiles;
|
|
954
1055
|
}
|
|
1056
|
+
// Shallow merge per-key: later layer wins per process name (same as profiles).
|
|
1057
|
+
const baseProcesses = base.processes;
|
|
1058
|
+
const overrideProcesses = override.processes;
|
|
1059
|
+
if (baseProcesses || overrideProcesses) {
|
|
1060
|
+
merged.processes = { ...(baseProcesses ?? {}), ...(overrideProcesses ?? {}) };
|
|
1061
|
+
}
|
|
955
1062
|
return merged;
|
|
956
1063
|
}
|
|
957
1064
|
function mergeSecurityConfig(base, override) {
|