akm-cli 0.9.10 → 0.9.12
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 +226 -0
- package/STABILITY.md +28 -15
- package/dist/assets/hints/cli-hints-full.md +1 -1
- package/dist/assets/improve-strategies/consolidate.json +1 -1
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/thorough.json +1 -2
- package/dist/cli/shared.js +16 -4
- package/dist/cli.js +15 -13
- package/dist/commands/agent/agent-dispatch.js +8 -0
- package/dist/commands/command/execution-source-loader.js +25 -22
- package/dist/commands/command/portable-template.js +4 -26
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env-binding.js +10 -3
- package/dist/commands/env/env-cli.js +7 -0
- package/dist/commands/env/secret-cli.js +15 -4
- package/dist/commands/health/checks.js +209 -78
- package/dist/commands/health/improve-metrics.js +12 -0
- package/dist/commands/health.js +16 -4
- package/dist/commands/improve/distill/quality-gate.js +15 -7
- package/dist/commands/improve/distill.js +28 -12
- package/dist/commands/improve/eval-cases.js +9 -2
- package/dist/commands/improve/execution.js +1 -2
- package/dist/commands/improve/extract.js +82 -56
- package/dist/commands/improve/improve-strategies.js +26 -8
- package/dist/commands/improve/improve.js +32 -4
- package/dist/commands/improve/loop-stages.js +13 -3
- package/dist/commands/improve/preparation.js +9 -6
- package/dist/commands/improve/reflect.js +61 -77
- package/dist/commands/lint/index.js +3 -1
- package/dist/commands/migrate-cli.js +6 -4
- package/dist/commands/proposal/drain-policies.js +22 -2
- package/dist/commands/proposal/repository.js +4 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
- package/dist/commands/proposal/validators/proposals.js +10 -19
- package/dist/commands/read/show.js +42 -31
- package/dist/commands/registry-cli.js +4 -2
- package/dist/commands/sources/init.js +4 -8
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-clone.js +5 -7
- package/dist/commands/sources/sources-cli.js +3 -5
- package/dist/commands/tasks/tasks-cli.js +36 -12
- package/dist/commands/tasks/tasks.js +38 -35
- package/dist/commands/tasks/validate.js +186 -0
- package/dist/commands/url-checker.js +75 -16
- package/dist/commands/workflow-cli.js +17 -15
- package/dist/core/activation-policy.js +31 -3
- package/dist/core/adapter/execution-source.js +39 -11
- package/dist/core/asset/stash-meta.js +7 -41
- package/dist/core/bundle-id.js +7 -1
- package/dist/core/common.js +8 -17
- package/dist/core/config/config-schema.js +3 -23
- package/dist/core/config/config-walker.js +50 -4
- package/dist/core/config/config.js +21 -5
- package/dist/core/config/legacy-source-shape-shim.js +79 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/engines.js +19 -2
- package/dist/core/config/schema/index-config.js +19 -21
- package/dist/core/config/schema/primitives.js +21 -10
- package/dist/core/config/schema/sources-bundles.js +1 -6
- package/dist/core/errors.js +2 -3
- package/dist/core/improve-result.js +8 -0
- package/dist/core/improve-types.js +17 -0
- package/dist/core/json-schema.js +1 -11
- package/dist/core/maintenance-barrier.js +17 -2
- package/dist/core/paths.js +124 -15
- package/dist/core/state/migrations.js +28 -0
- package/dist/core/state-db.js +28 -1
- package/dist/core/write-source.js +6 -6
- package/dist/indexer/bundle-identity-guard.js +3 -0
- package/dist/indexer/ensure-index.js +5 -0
- package/dist/indexer/indexer.js +11 -3
- package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
- package/dist/indexer/passes/metadata.js +16 -5
- package/dist/indexer/search/search-fields.js +1 -30
- package/dist/indexer/search/search-source.js +3 -2
- package/dist/integrations/agent/engine-resolution.js +107 -4
- package/dist/integrations/agent/execution-lowering.js +15 -2
- package/dist/integrations/agent/model-map.js +16 -10
- package/dist/integrations/agent/prompts.js +13 -6
- package/dist/integrations/agent/runner-dispatch.js +16 -3
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/integrations/lockfile.js +22 -7
- package/dist/llm/client.js +16 -0
- package/dist/llm/index-passes.js +3 -2
- package/dist/output/shapes/passthrough.js +10 -3
- package/dist/output/shapes.js +5 -3
- package/dist/output/text/workflow-format.js +8 -1
- package/dist/scripts/akm-migrate-node.js +2028 -1613
- package/dist/scripts/akm-migrate.js +2027 -1612
- package/dist/setup/setup.js +14 -21
- package/dist/sources/include.js +150 -20
- package/dist/sources/providers/git-install.js +14 -12
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
- package/dist/sources/website-url.js +12 -4
- package/dist/storage/engines/sqlite-migrations.js +40 -10
- package/dist/storage/like-pattern.js +7 -0
- package/dist/storage/repositories/extract-sessions-repository.js +23 -0
- package/dist/storage/repositories/index-connection.js +27 -10
- package/dist/storage/repositories/index-entry-schema.js +19 -2
- package/dist/storage/repositories/index-schema.js +30 -9
- package/dist/storage/repositories/proposals-repository.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +14 -7
- package/dist/storage/repositories/workflow-runs-repository.js +15 -1
- package/dist/storage/sqlite-read-snapshot.js +11 -9
- package/dist/tasks/backends/cron.js +34 -5
- package/dist/tasks/backends/launchd.js +23 -26
- package/dist/tasks/backends/schtasks.js +50 -3
- package/dist/tasks/frozen-script.js +2 -0
- package/dist/tasks/prepare/prepare.js +2 -7
- package/dist/tasks/prepare/script-capture.js +38 -6
- package/dist/tasks/schedule.js +154 -13
- package/dist/tasks/scheduler-sync.js +51 -25
- package/dist/tasks/source/task-source-v3-frozen.js +0 -1
- package/dist/tasks/source/task-source-v4.js +0 -1
- package/dist/workflows/exec/child-workflow.js +2 -3
- package/dist/workflows/exec/dispatch-redaction.js +21 -7
- package/dist/workflows/exec/exec-unit.js +3 -4
- package/dist/workflows/exec/run-workflow.js +19 -10
- package/dist/workflows/exec/step-work.js +35 -56
- package/dist/workflows/freeze/resolve-steps.js +19 -11
- package/dist/workflows/freeze/source-freeze.js +7 -0
- package/dist/workflows/freeze/targets/child-workflow.js +12 -18
- package/dist/workflows/freeze/targets/command.js +14 -2
- package/dist/workflows/ir/environment-v4.js +4 -2
- package/dist/workflows/ir/freeze-v4.js +2 -5
- package/dist/workflows/ir/plan-hash.js +0 -3
- package/dist/workflows/ir/schema-v4.js +14 -9
- package/dist/workflows/ir/schema.js +1 -3
- package/dist/workflows/resource-limits.js +35 -48
- package/dist/workflows/runtime/plan-classifier.js +89 -41
- package/dist/workflows/runtime/run-outputs.js +1 -21
- package/dist/workflows/runtime/runs.js +76 -150
- package/dist/workflows/source-files.js +28 -54
- package/dist/workflows/source-ir/program.js +2 -2
- package/dist/workflows/source-ir/semantics.js +5 -23
- package/docs/integration/bundling-akm.md +1 -1
- package/docs/migration/v0.8-to-v0.9.md +32 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
- package/docs/reference/cli.md +105 -22
- package/docs/reference/configuration.md +12 -2
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/tasks.md +8 -0
- package/package.json +1 -1
- package/schemas/akm-config.json +11 -8
package/dist/core/common.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { migrateLegacySourceShape } from "./config/legacy-source-shape-shim.js";
|
|
7
8
|
import { ConfigError } from "./errors.js";
|
|
8
9
|
import { getConfigPath, getDefaultStashDir, getRegistryCacheDir, getRegistryIndexCacheDir } from "./paths.js";
|
|
9
10
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
@@ -302,23 +303,20 @@ function readStashDirFromConfig() {
|
|
|
302
303
|
// in use. Parsing it raw here threw, the catch swallowed it, and every
|
|
303
304
|
// caller silently fell back — operating on the wrong bundle or failing with
|
|
304
305
|
// STASH_DIR_NOT_FOUND despite a perfectly good config.
|
|
305
|
-
const
|
|
306
|
-
if (typeof
|
|
306
|
+
const parsed = JSON.parse(stripJsonComments(text));
|
|
307
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
307
308
|
return undefined;
|
|
309
|
+
const raw = migrateLegacySourceShape(parsed, configPath);
|
|
308
310
|
// 0.9.0 config-shape cutover (spec §10.1): the primary stash is the
|
|
309
311
|
// `defaultBundle`'s filesystem `path`. Read it directly (no config module
|
|
310
312
|
// import) so the primary-stash location survives the stashDir → bundles
|
|
311
313
|
// migration without a runtime rewire.
|
|
312
314
|
const bundles = raw.bundles;
|
|
313
315
|
const defaultBundle = raw.defaultBundle;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
typeof bundles[defaultBundle] === "object" &&
|
|
319
|
-
typeof bundles[defaultBundle].path === "string" &&
|
|
320
|
-
bundles[defaultBundle].path.trim()) {
|
|
321
|
-
const bundle = bundles[defaultBundle];
|
|
316
|
+
const selectedBundle = typeof defaultBundle === "string" ? bundles?.[defaultBundle] : undefined;
|
|
317
|
+
const selectedBundlePath = typeof selectedBundle?.path === "string" ? selectedBundle.path : undefined;
|
|
318
|
+
if (bundles && typeof bundles === "object" && typeof defaultBundle === "string" && selectedBundlePath?.trim()) {
|
|
319
|
+
const bundle = selectedBundle;
|
|
322
320
|
const bundlePath = bundle.path.trim();
|
|
323
321
|
if (bundle.components !== undefined) {
|
|
324
322
|
if (typeof bundle.components !== "object" || bundle.components === null) {
|
|
@@ -343,13 +341,6 @@ function readStashDirFromConfig() {
|
|
|
343
341
|
}
|
|
344
342
|
return bundlePath;
|
|
345
343
|
}
|
|
346
|
-
// Retired pre-cutover shapes are not runtime inputs. Refuse them instead
|
|
347
|
-
// of silently resolving old keys.
|
|
348
|
-
for (const key of ["stashDir", "sources", "installed"]) {
|
|
349
|
-
if (key in raw && raw[key] !== undefined) {
|
|
350
|
-
throw new ConfigError(`${key} is not supported; configure the current bundles shape`, "INVALID_CONFIG_FILE");
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
344
|
}
|
|
354
345
|
catch (err) {
|
|
355
346
|
// An unsupported-shape refusal must reach the caller; genuine missing/invalid
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
* enforced at save time via `superRefine` on the top-level schema.
|
|
42
42
|
*/
|
|
43
43
|
import { z } from "zod";
|
|
44
|
+
import { warnOnce } from "../warn.js";
|
|
44
45
|
import { BUILTIN_IMPROVE_STRATEGY_NAMES, IMPROVE_PROCESS_ENGINE_CAPABILITIES } from "./engine-semantics.js";
|
|
45
46
|
import { EmbeddingConnectionConfigSchema } from "./schema/embedding.js";
|
|
46
47
|
import { EnginesSchema } from "./schema/engines.js";
|
|
@@ -121,25 +122,11 @@ const RETIRED_SOURCE_SHAPE_KEY_MESSAGES = {
|
|
|
121
122
|
};
|
|
122
123
|
export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) => {
|
|
123
124
|
const raw = config;
|
|
124
|
-
for (const key of ["profiles", "llm", "agent", "features", "stashes", "modelAliases"]) {
|
|
125
|
+
for (const key of ["profiles", "llm", "agent", "features", "stashes", "modelAliases", "bindings", "writable"]) {
|
|
125
126
|
if (key in raw) {
|
|
126
|
-
|
|
127
|
-
code: z.ZodIssueCode.custom,
|
|
128
|
-
path: [key],
|
|
129
|
-
message: `${key} is retired in 0.9; configure engines and improve.strategies instead`,
|
|
130
|
-
});
|
|
127
|
+
warnOnce(`config:retired-key:${key}`, `Config key "${key}" is retired in 0.9 and is ignored; configure engines/improve.strategies/bundles.<id> instead.`);
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
// `bindings` (spec §10.1) is Tier B — never emitted, never accepted. The
|
|
134
|
-
// top-level schema is `.passthrough()`, so without this it would round-trip
|
|
135
|
-
// silently; reject it loudly so a stray/hand-written bindings block is caught.
|
|
136
|
-
if ("bindings" in raw) {
|
|
137
|
-
ctx.addIssue({
|
|
138
|
-
code: z.ZodIssueCode.custom,
|
|
139
|
-
path: ["bindings"],
|
|
140
|
-
message: "bindings is not supported in 0.9.0 (Tier B); it is neither emitted nor accepted",
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
130
|
// Only the current source shape enters the runtime. There is no config
|
|
144
131
|
// compatibility path; `bundles` + `defaultBundle` fully supersede these keys.
|
|
145
132
|
for (const key of ["stashDir", "sources", "installed"]) {
|
|
@@ -151,13 +138,6 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
|
|
|
151
138
|
});
|
|
152
139
|
}
|
|
153
140
|
}
|
|
154
|
-
if ("writable" in raw) {
|
|
155
|
-
ctx.addIssue({
|
|
156
|
-
code: z.ZodIssueCode.custom,
|
|
157
|
-
path: ["writable"],
|
|
158
|
-
message: "top-level writable is not supported; configure bundles.<id>.writable instead",
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
141
|
// `defaultBundle`, when present, must name a configured bundle.
|
|
162
142
|
if (config.defaultBundle !== undefined) {
|
|
163
143
|
if (config.bundles === undefined) {
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
import { z } from "zod";
|
|
24
24
|
import { isRecord } from "../common.js";
|
|
25
25
|
import { UsageError } from "../errors.js";
|
|
26
|
+
import { hasRegistryUrlCredentials, REGISTRY_CREDENTIALS_UNSUPPORTED } from "../registry-url.js";
|
|
27
|
+
import { warnOnce } from "../warn.js";
|
|
26
28
|
import { AkmConfigBaseSchema, EngineConfigSchema, listTopLevelConfigKeys } from "./config-schema.js";
|
|
27
29
|
import { deepMergeConfig } from "./deep-merge.js";
|
|
28
30
|
/**
|
|
@@ -175,11 +177,13 @@ export function configSet(config, dotted, raw) {
|
|
|
175
177
|
}
|
|
176
178
|
// #454: apiKey paths are not persistable. Throw at set time.
|
|
177
179
|
rejectApiKeyPath(path, dotted);
|
|
180
|
+
rejectLiteralApiKeyInWholeObjectSet(path, raw, dotted);
|
|
178
181
|
const schema = resolveSchemaAt(path, config, raw);
|
|
179
182
|
const symbolicApiKey = (path[0] === "engines" && path[2] === "apiKey") ||
|
|
180
183
|
(path[0] === "embedding" && path.length === 2 && path[1] === "apiKey");
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
const isUnknownKey = !schema && !symbolicApiKey;
|
|
185
|
+
if (isUnknownKey) {
|
|
186
|
+
warnOnce(`config-set:unknown-key:${dotted}`, `"${dotted}" is not a known config key; storing it anyway. Run \`akm config get ${dotted}\` to confirm it round-trips as expected.`);
|
|
183
187
|
}
|
|
184
188
|
const judgmentObjectPath = isTriageJudgmentPath(path);
|
|
185
189
|
const value = judgmentObjectPath && raw === "null"
|
|
@@ -188,7 +192,11 @@ export function configSet(config, dotted, raw) {
|
|
|
188
192
|
? parseObjectPatch(raw, dotted)
|
|
189
193
|
: symbolicApiKey
|
|
190
194
|
? raw
|
|
191
|
-
:
|
|
195
|
+
: isUnknownKey
|
|
196
|
+
? bestEffortJsonValue(raw)
|
|
197
|
+
: coerceForSchema(schema, raw, dotted);
|
|
198
|
+
if (path[0] === "registries")
|
|
199
|
+
rejectRegistryCredentialValue(value, dotted);
|
|
192
200
|
const existing = path.reduce((value, key) => {
|
|
193
201
|
if (value && typeof value === "object")
|
|
194
202
|
return value[key];
|
|
@@ -209,7 +217,9 @@ export function configSet(config, dotted, raw) {
|
|
|
209
217
|
? /^\$[A-Za-z_][A-Za-z0-9_]*$|^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(raw)
|
|
210
218
|
? { success: true, data: value }
|
|
211
219
|
: { success: false, error: { issues: [{ path: [], message: `apiKey must be $VAR or \${VAR}` }] } }
|
|
212
|
-
:
|
|
220
|
+
: isUnknownKey
|
|
221
|
+
? { success: true, data: value }
|
|
222
|
+
: schema.safeParse(candidate);
|
|
213
223
|
if (!parsed.success) {
|
|
214
224
|
const lines = parsed.error.issues
|
|
215
225
|
.map((i) => {
|
|
@@ -273,6 +283,42 @@ function rejectApiKeyPath(path, dotted) {
|
|
|
273
283
|
throw new UsageError(`apiKey cannot be persisted in config; export ${recipe} instead. (key: ${dotted})`, "INVALID_FLAG_VALUE", "Storing API keys in config.json leaks them through backups, logs, and version control. " +
|
|
274
284
|
"Use the corresponding environment variable. AKM reads it at request time.");
|
|
275
285
|
}
|
|
286
|
+
function rejectLiteralApiKeyInWholeObjectSet(path, raw, dotted) {
|
|
287
|
+
const isWholeEngineSet = path[0] === "engines" && path.length === 2;
|
|
288
|
+
const isWholeEmbeddingSet = path.length === 1 && path[0] === "embedding";
|
|
289
|
+
if (!isWholeEngineSet && !isWholeEmbeddingSet)
|
|
290
|
+
return;
|
|
291
|
+
let parsed;
|
|
292
|
+
try {
|
|
293
|
+
parsed = JSON.parse(raw);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return; // Malformed JSON — the caller's own parse/coercion reports this.
|
|
297
|
+
}
|
|
298
|
+
if (!isRecord(parsed) || typeof parsed.apiKey !== "string")
|
|
299
|
+
return;
|
|
300
|
+
if (/^\$[A-Za-z_][A-Za-z0-9_]*$|^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(parsed.apiKey))
|
|
301
|
+
return;
|
|
302
|
+
throw new UsageError(`apiKey cannot be persisted in config; export ${recipeForApiKey([...path, "apiKey"], `${dotted}.apiKey`)} instead. (key: ${dotted}.apiKey)`, "INVALID_FLAG_VALUE", "Storing API keys in config.json leaks them through backups, logs, and version control. " +
|
|
303
|
+
"Use the corresponding environment variable. AKM reads it at request time.");
|
|
304
|
+
}
|
|
305
|
+
function rejectRegistryCredentialValue(value, dotted) {
|
|
306
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
307
|
+
for (const entry of entries) {
|
|
308
|
+
const url = isRecord(entry) && typeof entry.url === "string" ? entry.url : undefined;
|
|
309
|
+
if (url && hasRegistryUrlCredentials(url)) {
|
|
310
|
+
throw new UsageError(`${REGISTRY_CREDENTIALS_UNSUPPORTED} (key: ${dotted})`, "INVALID_FLAG_VALUE", REGISTRY_CREDENTIALS_UNSUPPORTED);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function bestEffortJsonValue(raw) {
|
|
315
|
+
try {
|
|
316
|
+
return JSON.parse(raw);
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return raw;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
276
322
|
function parseObjectPatch(raw, key) {
|
|
277
323
|
try {
|
|
278
324
|
const value = JSON.parse(raw);
|
|
@@ -5,14 +5,16 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { ConfigError } from "../errors.js";
|
|
7
7
|
import { liftLegacyEngineExtraParams } from "../extra-params.js";
|
|
8
|
+
import { formatRegistryLabel, hasRegistryUrlCredentials } from "../registry-url.js";
|
|
8
9
|
import { acquireConfigLock, backupExistingConfig, parseConfigText, readConfigText, withConfigLock, writeConfigAtomic, } from "./config-io.js";
|
|
9
10
|
import { AkmConfigSchema, CURRENT_CONFIG_VERSION } from "./config-schema.js";
|
|
10
11
|
import { bundlesToSourceEntries } from "./config-sources.js";
|
|
11
12
|
import { upgradeConfigVersion } from "./config-version-shim.js";
|
|
12
13
|
import { deepMergeConfig } from "./deep-merge.js";
|
|
14
|
+
import { migrateLegacySourceShape } from "./legacy-source-shape-shim.js";
|
|
13
15
|
export { stripJsonComments } from "./config-io.js";
|
|
14
16
|
import { getConfigPath } from "../paths.js";
|
|
15
|
-
import { warn } from "../warn.js";
|
|
17
|
+
import { warn, warnOnce } from "../warn.js";
|
|
16
18
|
// Canonical harness-id source of truth (#565) — runtime value re-export.
|
|
17
19
|
export { VALID_HARNESS_IDS } from "./config-types.js";
|
|
18
20
|
// ── Feedback failure-mode constants (F-3 / #384) ────────────────────────────
|
|
@@ -145,7 +147,8 @@ export function acquireConfigReadFence() {
|
|
|
145
147
|
* is validated.
|
|
146
148
|
*/
|
|
147
149
|
export function parseAndValidateConfigText(text, sourcePath) {
|
|
148
|
-
const
|
|
150
|
+
const versioned = upgradeConfigVersion(parseConfigText(text, sourcePath), sourcePath);
|
|
151
|
+
const parsedRaw = migrateLegacySourceShape(versioned, sourcePath);
|
|
149
152
|
// #852 (following #815): a config still using legacy `extraParams` keys —
|
|
150
153
|
// e.g. `reasoning_effort`, a documented 0.9.1 workaround — needs to be
|
|
151
154
|
// rewritten onto the first-class engine field they now shadow. This used
|
|
@@ -155,7 +158,7 @@ export function parseAndValidateConfigText(text, sourcePath) {
|
|
|
155
158
|
// config that has not been migrated yet fails closed here instead of
|
|
156
159
|
// silently drifting from what's on disk.
|
|
157
160
|
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
158
|
-
const { lifted, conflicts } = liftLegacyEngineExtraParams(parsedRaw);
|
|
161
|
+
const { config: liftedConfig, lifted, conflicts } = liftLegacyEngineExtraParams(parsedRaw);
|
|
159
162
|
if (conflicts.length > 0) {
|
|
160
163
|
const lines = conflicts
|
|
161
164
|
.map((c) => ` - engines.${c.engine}.extraParams.${c.key} (${JSON.stringify(c.extraParamsValue)}) conflicts with engines.${c.engine}.${c.field} (${JSON.stringify(c.fieldValue)})`)
|
|
@@ -163,9 +166,9 @@ export function parseAndValidateConfigText(text, sourcePath) {
|
|
|
163
166
|
throw new ConfigError(`Invalid config${where}: extraParams and the first-class field disagree:\n${lines}\n\nEach extraParams key above has a first-class equivalent and akm will not guess which value you meant — remove the extraParams entry once the field carries the value you want.`, "INVALID_CONFIG_FILE");
|
|
164
167
|
}
|
|
165
168
|
if (lifted.length > 0) {
|
|
166
|
-
|
|
169
|
+
warnOnce(`config:extra-params-lift${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses deprecated extraParams keys with first-class equivalents — auto-lifted in memory:\n - ${lifted.join("\n - ")}\n\nRun \`akm migrate apply\` to rewrite the config file and silence this warning.`);
|
|
167
170
|
}
|
|
168
|
-
const parsed = AkmConfigSchema.safeParse(
|
|
171
|
+
const parsed = AkmConfigSchema.safeParse(liftedConfig);
|
|
169
172
|
if (!parsed.success) {
|
|
170
173
|
const lines = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
171
174
|
throw new ConfigError(`Invalid config${where}:\n${lines}`, "INVALID_CONFIG_FILE");
|
|
@@ -327,6 +330,19 @@ export function sanitizeConfigForWrite(config) {
|
|
|
327
330
|
if (stripped.length > 0) {
|
|
328
331
|
warn(`Config sanitizer dropped API key(s) before writing to disk:\n - ${stripped.join("\n - ")}\n\nakm does not persist API keys to config.json. Set the listed environment variables to provide them at runtime, or use \`\${VAR}\` references in your config to defer lookup. See docs/reference/data-and-telemetry.md.`);
|
|
329
332
|
}
|
|
333
|
+
if (config.registries) {
|
|
334
|
+
const droppedRegistries = [];
|
|
335
|
+
const registries = config.registries.filter((entry) => {
|
|
336
|
+
if (!hasRegistryUrlCredentials(entry.url))
|
|
337
|
+
return true;
|
|
338
|
+
droppedRegistries.push(formatRegistryLabel(entry));
|
|
339
|
+
return false;
|
|
340
|
+
});
|
|
341
|
+
if (droppedRegistries.length > 0) {
|
|
342
|
+
sanitized.registries = registries;
|
|
343
|
+
warn(`Config sanitizer dropped registry entr${droppedRegistries.length === 1 ? "y" : "ies"} with URL credentials before writing to disk:\n - ${droppedRegistries.join("\n - ")}\n\nRegistry URLs must be credential-free; configure a credential-free HTTPS endpoint.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
330
346
|
return sanitized;
|
|
331
347
|
}
|
|
332
348
|
/** Matches the only 0.9 symbolic secret forms: `${VAR}` or `$VAR`. */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isBundleSlug } from "../asset/asset-ref.js";
|
|
2
|
+
import { warnOnce } from "../warn.js";
|
|
3
|
+
function isPlainRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
const DEFAULT_WRITABLE_BY_TYPE = {
|
|
7
|
+
filesystem: true,
|
|
8
|
+
git: false,
|
|
9
|
+
website: false,
|
|
10
|
+
npm: false,
|
|
11
|
+
};
|
|
12
|
+
function bundleFromLegacySource(entry, index) {
|
|
13
|
+
if (!isPlainRecord(entry))
|
|
14
|
+
return undefined;
|
|
15
|
+
const type = typeof entry.type === "string" ? entry.type : undefined;
|
|
16
|
+
const bundle = {};
|
|
17
|
+
switch (type) {
|
|
18
|
+
case "filesystem":
|
|
19
|
+
if (typeof entry.path !== "string" || !entry.path)
|
|
20
|
+
return undefined;
|
|
21
|
+
bundle.path = entry.path;
|
|
22
|
+
break;
|
|
23
|
+
case "git":
|
|
24
|
+
if (typeof entry.url !== "string" || !entry.url)
|
|
25
|
+
return undefined;
|
|
26
|
+
bundle.git = entry.url;
|
|
27
|
+
break;
|
|
28
|
+
case "website":
|
|
29
|
+
if (typeof entry.url !== "string" || !entry.url)
|
|
30
|
+
return undefined;
|
|
31
|
+
bundle.website = { url: entry.url };
|
|
32
|
+
break;
|
|
33
|
+
case "npm": {
|
|
34
|
+
const spec = typeof entry.url === "string" && entry.url ? entry.url : entry.path;
|
|
35
|
+
if (typeof spec !== "string" || !spec)
|
|
36
|
+
return undefined;
|
|
37
|
+
bundle.npm = spec;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const writable = typeof entry.writable === "boolean" ? entry.writable : DEFAULT_WRITABLE_BY_TYPE[type ?? ""];
|
|
44
|
+
if (writable !== undefined)
|
|
45
|
+
bundle.writable = writable;
|
|
46
|
+
if (typeof entry.enabled === "boolean")
|
|
47
|
+
bundle.enabled = entry.enabled;
|
|
48
|
+
const name = typeof entry.name === "string" ? entry.name : undefined;
|
|
49
|
+
const key = name && isBundleSlug(name) ? name : `source-${index + 1}`;
|
|
50
|
+
return [key, bundle];
|
|
51
|
+
}
|
|
52
|
+
export function migrateLegacySourceShape(raw, sourcePath) {
|
|
53
|
+
const hasStashDir = typeof raw.stashDir === "string" && raw.stashDir.trim().length > 0;
|
|
54
|
+
const hasSources = Array.isArray(raw.sources) && raw.sources.length > 0;
|
|
55
|
+
const hasInstalled = "installed" in raw && raw.installed !== undefined;
|
|
56
|
+
if (!hasStashDir && !hasSources && !hasInstalled)
|
|
57
|
+
return raw;
|
|
58
|
+
const { stashDir: _stashDir, sources: _sources, installed: _installed, ...rest } = raw;
|
|
59
|
+
const bundles = isPlainRecord(rest.bundles) ? { ...rest.bundles } : {};
|
|
60
|
+
let defaultBundle = typeof rest.defaultBundle === "string" ? rest.defaultBundle : undefined;
|
|
61
|
+
if (hasStashDir) {
|
|
62
|
+
bundles.stash = { path: raw.stashDir, writable: true };
|
|
63
|
+
defaultBundle ??= "stash";
|
|
64
|
+
}
|
|
65
|
+
if (hasSources) {
|
|
66
|
+
raw.sources.forEach((entry, index) => {
|
|
67
|
+
const converted = bundleFromLegacySource(entry, index);
|
|
68
|
+
if (!converted)
|
|
69
|
+
return;
|
|
70
|
+
const [key, bundle] = converted;
|
|
71
|
+
bundles[key] = bundle;
|
|
72
|
+
defaultBundle ??= key;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const droppedKeys = [hasStashDir && "stashDir", hasSources && "sources", hasInstalled && "installed"].filter(Boolean);
|
|
76
|
+
const where = sourcePath ? ` at ${sourcePath}` : "";
|
|
77
|
+
warnOnce(`legacy-source-shape${sourcePath ? `:${sourcePath}` : ""}`, `Config${where} uses the retired ${droppedKeys.join("/")} shape — auto-migrated in memory to \`bundles\`/\`defaultBundle\`. Run \`akm migrate apply\` to rewrite the config file and silence this warning.`);
|
|
78
|
+
return { ...rest, bundles, ...(defaultBundle !== undefined ? { defaultBundle } : {}) };
|
|
79
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `config-schema.ts` monolith — no behavior change.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import {
|
|
9
|
+
import { positiveInt, symbolicOrWarnApiKey } from "./primitives.js";
|
|
10
10
|
const EmbeddingOllamaOptionsSchema = z
|
|
11
11
|
.object({
|
|
12
12
|
num_ctx: positiveInt.optional(),
|
|
@@ -26,7 +26,7 @@ export const EmbeddingConnectionConfigSchema = z
|
|
|
26
26
|
provider: z.string().optional(),
|
|
27
27
|
endpoint: z.string().optional(),
|
|
28
28
|
model: z.string().optional(),
|
|
29
|
-
apiKey:
|
|
29
|
+
apiKey: symbolicOrWarnApiKey("embedding.apiKey").optional(),
|
|
30
30
|
// Bounded to the index schema's own vec-table guard (1–4096,
|
|
31
31
|
// storage/repositories/index-schema.ts) so an out-of-range dimension
|
|
32
32
|
// fails at config validation with a clear message instead of crashing
|
|
@@ -15,7 +15,7 @@ import { z } from "zod";
|
|
|
15
15
|
// a config-schema ↔ config-types type cycle that collapses inference.
|
|
16
16
|
import { HARNESS_AGENT_DISPATCH_IDS, VALID_HARNESS_IDS } from "../../../integrations/harnesses/ids.js";
|
|
17
17
|
import { WORKFLOW_MAX_TIMEOUT_MS } from "../../../workflows/resource-limits.js";
|
|
18
|
-
import { chatCompletionsEndpoint,
|
|
18
|
+
import { chatCompletionsEndpoint, ExtraParamsSchema, engineName, nonEmptyString, positiveInt, symbolicOrWarnApiKey, } from "./primitives.js";
|
|
19
19
|
/**
|
|
20
20
|
* Engine-config timeouts share the workflow ceiling.
|
|
21
21
|
*
|
|
@@ -67,7 +67,12 @@ const LlmEngineSchema = z
|
|
|
67
67
|
provider: z.string().optional(),
|
|
68
68
|
endpoint: chatCompletionsEndpoint,
|
|
69
69
|
model: nonEmptyString,
|
|
70
|
-
apiKey:
|
|
70
|
+
apiKey: symbolicOrWarnApiKey("engines.<name>.apiKey").optional(),
|
|
71
|
+
// #905: file-backed alternative to `apiKey` for hosts that refuse
|
|
72
|
+
// secrets in the process environment. A plain filesystem path (`~`
|
|
73
|
+
// expanded, read at dispatch) — see resolveLlmEngineUse/
|
|
74
|
+
// materializeLlmConnectionWithCredential in integrations/agent/engine-resolution.ts.
|
|
75
|
+
apiKeyFile: nonEmptyString.optional(),
|
|
71
76
|
temperature: z.number().finite().optional(),
|
|
72
77
|
maxTokens: positiveInt.optional(),
|
|
73
78
|
timeoutMs: timeoutMsField,
|
|
@@ -83,6 +88,17 @@ const LlmEngineSchema = z
|
|
|
83
88
|
if (key in value)
|
|
84
89
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [key], message: `${key} is not valid on an LLM engine` });
|
|
85
90
|
}
|
|
91
|
+
// #905: apiKey and apiKeyFile are two alternative ways to supply the same
|
|
92
|
+
// credential; a third (the implicit AKM_ENGINE_<NAME>_API_KEY env var) is
|
|
93
|
+
// still available when neither is set, so only the both-set case is
|
|
94
|
+
// rejected here.
|
|
95
|
+
if (value.apiKey !== undefined && value.apiKeyFile !== undefined) {
|
|
96
|
+
ctx.addIssue({
|
|
97
|
+
code: z.ZodIssueCode.custom,
|
|
98
|
+
path: ["apiKeyFile"],
|
|
99
|
+
message: "apiKey and apiKeyFile cannot both be set",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
86
102
|
});
|
|
87
103
|
const AgentEngineSchema = z
|
|
88
104
|
.object({
|
|
@@ -103,6 +119,7 @@ const AgentEngineSchema = z
|
|
|
103
119
|
"provider",
|
|
104
120
|
"endpoint",
|
|
105
121
|
"apiKey",
|
|
122
|
+
"apiKeyFile",
|
|
106
123
|
"temperature",
|
|
107
124
|
"maxTokens",
|
|
108
125
|
"concurrency",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* change.
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
import { warnOnce } from "../../warn.js";
|
|
10
11
|
import { engineName, LlmInvocationOverridesSchema, nonEmptyString, positiveInt } from "./primitives.js";
|
|
11
12
|
// ── Index / per-pass ────────────────────────────────────────────────────────
|
|
12
13
|
//
|
|
@@ -53,26 +54,23 @@ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
|
|
|
53
54
|
return raw; // let z.object below produce the type error
|
|
54
55
|
}
|
|
55
56
|
const obj = raw;
|
|
57
|
+
let cleaned;
|
|
56
58
|
for (const key of Object.keys(obj)) {
|
|
59
|
+
const dotted = [...(ctx.path ?? []), key].join(".");
|
|
57
60
|
if (INDEX_PASS_RETIRED_KEYS.has(key)) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"Select a named engine and use typed invocation fields instead.",
|
|
62
|
-
});
|
|
63
|
-
return raw;
|
|
61
|
+
warnOnce(`index-pass:retired:${dotted}`, `\`${dotted}\` is a retired engine setting and is ignored; select a named engine and use typed invocation fields instead.`);
|
|
62
|
+
cleaned ??= { ...obj };
|
|
63
|
+
delete cleaned[key];
|
|
64
64
|
}
|
|
65
|
-
if (!INDEX_PASS_KNOWN_KEYS.has(key)) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
return raw;
|
|
65
|
+
else if (!INDEX_PASS_KNOWN_KEYS.has(key)) {
|
|
66
|
+
warnOnce(`index-pass:unknown:${dotted}`, `Unknown key \`${dotted}\` ignored. Per-pass entries support ` +
|
|
67
|
+
"`engine`, `model`, `timeoutMs`, `enabled`, `llm`, `graphExtractionBatchSize`, " +
|
|
68
|
+
"`graphExtractionIncludeTypes`, and `lazyGraphExtraction`.");
|
|
69
|
+
cleaned ??= { ...obj };
|
|
70
|
+
delete cleaned[key];
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
|
-
return raw;
|
|
73
|
+
return cleaned ?? raw;
|
|
76
74
|
}, z
|
|
77
75
|
.object({
|
|
78
76
|
engine: engineName.optional(),
|
|
@@ -120,13 +118,13 @@ const IndexConfigRuntimeSchema = z.preprocess((raw, ctx) => {
|
|
|
120
118
|
}
|
|
121
119
|
if (typeof raw !== "object")
|
|
122
120
|
return raw;
|
|
121
|
+
let cleaned;
|
|
123
122
|
for (const [passName, value] of Object.entries(raw)) {
|
|
124
123
|
if (passName === "stalenessDetection") {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return raw;
|
|
124
|
+
warnOnce("index:stalenessDetection", "`index.stalenessDetection` is a retired pass and is ignored.");
|
|
125
|
+
cleaned ??= { ...raw };
|
|
126
|
+
delete cleaned.stalenessDetection;
|
|
127
|
+
continue;
|
|
130
128
|
}
|
|
131
129
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
132
130
|
ctx.addIssue({
|
|
@@ -136,7 +134,7 @@ const IndexConfigRuntimeSchema = z.preprocess((raw, ctx) => {
|
|
|
136
134
|
return raw;
|
|
137
135
|
}
|
|
138
136
|
}
|
|
139
|
-
return raw;
|
|
137
|
+
return cleaned ?? raw;
|
|
140
138
|
}, z
|
|
141
139
|
.object({
|
|
142
140
|
defaults: IndexDefaultsSchema.optional(),
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { validateExtraParams } from "../../extra-params.js";
|
|
11
|
+
import { warnOnce } from "../../warn.js";
|
|
11
12
|
import { ENGINE_NAME_PATTERN_SOURCE } from "../engine-semantics.js";
|
|
12
13
|
/** Persisted config schema version. Package prerelease/patch versions do not change this value. */
|
|
13
14
|
export const CURRENT_CONFIG_VERSION = "0.9.0";
|
|
@@ -31,21 +32,31 @@ export const engineName = z
|
|
|
31
32
|
.string()
|
|
32
33
|
.max(63)
|
|
33
34
|
.regex(ENGINE_NAME_PATTERN, "names must be lowercase kebab-case and must not begin with reserved akm-");
|
|
35
|
+
export function symbolicOrWarnApiKey(label) {
|
|
36
|
+
return z.string().superRefine((value) => {
|
|
37
|
+
if (ENV_REFERENCE_PATTERN.test(value))
|
|
38
|
+
return;
|
|
39
|
+
warnOnce(`config:literal-api-key:${label}`, `A ${label} in config.json is a literal API key, not a $VAR/\${VAR} reference; using it as configured. Prefer \`akm config set ...apiKey '$VAR'\` (with the corresponding env var set) — see docs/reference/data-and-telemetry.md.`);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
34
42
|
export const chatCompletionsEndpoint = z.string().superRefine((value, ctx) => {
|
|
43
|
+
let url;
|
|
35
44
|
try {
|
|
36
|
-
|
|
37
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
38
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "endpoint must use http:// or https://" });
|
|
39
|
-
}
|
|
40
|
-
if (url.username || url.password || url.search || url.hash || !url.pathname.endsWith("/chat/completions")) {
|
|
41
|
-
ctx.addIssue({
|
|
42
|
-
code: z.ZodIssueCode.custom,
|
|
43
|
-
message: "endpoint must be a credential-free OpenAI chat-completions URL without query or fragment",
|
|
44
|
-
});
|
|
45
|
-
}
|
|
45
|
+
url = new URL(value);
|
|
46
46
|
}
|
|
47
47
|
catch {
|
|
48
48
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "endpoint must be a complete URL" });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
52
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "endpoint must use http:// or https://" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (url.username || url.password) {
|
|
56
|
+
warnOnce(`chatCompletionsEndpoint:userinfo:${value}`, `Config endpoint "${value}" embeds a username/password; consider moving the credential to the engine's apiKey field instead.`);
|
|
57
|
+
}
|
|
58
|
+
if (!url.pathname.endsWith("/chat/completions")) {
|
|
59
|
+
warnOnce(`chatCompletionsEndpoint:path:${value}`, `Config endpoint "${value}" does not end in /chat/completions; using it as configured.`);
|
|
49
60
|
}
|
|
50
61
|
});
|
|
51
62
|
export const ExtraParamsSchema = z.record(z.unknown()).superRefine((value, ctx) => {
|
|
@@ -13,7 +13,6 @@ import { z } from "zod";
|
|
|
13
13
|
// and, transitively, the indexer modules they delegate to).
|
|
14
14
|
import { VALID_ADAPTER_IDS } from "../../adapter/adapter-ids.js";
|
|
15
15
|
import { isBundleSlug } from "../../asset/asset-ref.js";
|
|
16
|
-
import { hasRegistryUrlCredentials, REGISTRY_CREDENTIALS_UNSUPPORTED } from "../../registry-url.js";
|
|
17
16
|
import { httpUrl, nonEmptyString, positiveInt } from "./primitives.js";
|
|
18
17
|
const VALID_ADAPTER_IDS_SET = new Set(VALID_ADAPTER_IDS);
|
|
19
18
|
// ── Sources / registries / installed ────────────────────────────────────────
|
|
@@ -56,11 +55,7 @@ export const SourceConfigEntrySchema = z
|
|
|
56
55
|
});
|
|
57
56
|
export const RegistryConfigEntrySchema = z
|
|
58
57
|
.object({
|
|
59
|
-
url: httpUrl
|
|
60
|
-
if (hasRegistryUrlCredentials(value)) {
|
|
61
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: REGISTRY_CREDENTIALS_UNSUPPORTED });
|
|
62
|
-
}
|
|
63
|
-
}),
|
|
58
|
+
url: httpUrl,
|
|
64
59
|
name: z.string().min(1).optional(),
|
|
65
60
|
enabled: z.boolean().optional(),
|
|
66
61
|
provider: z.string().min(1).optional(),
|
package/dist/core/errors.js
CHANGED
|
@@ -14,7 +14,6 @@ const CONFIG_HINTS = {
|
|
|
14
14
|
EMBEDDING_NOT_CONFIGURED: 'Run `akm config set embedding \'{"endpoint":"...","model":"..."}\'` to enable embeddings.',
|
|
15
15
|
LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
|
|
16
16
|
TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
|
|
17
|
-
SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_BUNDLE_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
|
|
18
17
|
UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The bundle directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
|
|
19
18
|
UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive.",
|
|
20
19
|
EXECUTION_NOT_AUTHORIZED: "Change the selected tools or update the machine/user execution policy, then retry.",
|
|
@@ -39,7 +38,7 @@ const USAGE_HINTS = {
|
|
|
39
38
|
INVALID_SOURCE_VALUE: "Pick one of: local, registry, all, or a configured source name.",
|
|
40
39
|
INVALID_FORMAT_VALUE: "Pick one of: json, jsonl, yaml, text, md, html.",
|
|
41
40
|
INVALID_DETAIL_VALUE: "Pick one of: brief, normal, full. For agent/summary projections use --shape.",
|
|
42
|
-
INVALID_SHAPE_VALUE: "Pick one of: human, agent, summary (summary
|
|
41
|
+
INVALID_SHAPE_VALUE: "Pick one of: human, agent, summary (summary falls back to agent, with a warning, on commands with no summary projection).",
|
|
43
42
|
INVALID_JSON_CONFIG_VALUE: 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.',
|
|
44
43
|
MISSING_OR_AMBIGUOUS_TARGET: "Use `akm bundle update --all` or pass a target like `akm bundle update npm:@scope/pkg` (not both).",
|
|
45
44
|
TARGET_NOT_UPDATABLE: "Run `akm bundle list` to view your sources, then retry with one of those values.",
|
|
@@ -74,7 +73,7 @@ const USAGE_HINTS = {
|
|
|
74
73
|
TASK_TARGET_UNSUPPORTED: "Task definitions support command, script, workflow, and shell (run:) targets; akm/command is layered by callers.",
|
|
75
74
|
// P3a (docs/plans/specs/p3a-plan-v5-child-freeze.md §3.2, A-N2): the
|
|
76
75
|
// complete-or-abandon policy for a stored pre-irVersion-5 run.
|
|
77
|
-
WORKFLOW_IR_VERSION_UNSUPPORTED: "Abandon the run with `akm workflow abandon <id>`, then start it again from the workflow source —
|
|
76
|
+
WORKFLOW_IR_VERSION_UNSUPPORTED: "Abandon the run with `akm workflow abandon <id>`, then start it again from the workflow source — a frozen plan this akm cannot execute is not re-executable in place.",
|
|
78
77
|
// P3b (docs/plans/specs/p3b-child-executor.md §4.3).
|
|
79
78
|
WORKFLOW_OUTPUT_INVALID: "Check each `outputs:` entry's `from:` against the step artifact it names, and its `schema:` against the value that step actually promotes.",
|
|
80
79
|
};
|
|
@@ -27,6 +27,7 @@ const COMMON_FIELDS = [
|
|
|
27
27
|
"coverageGaps",
|
|
28
28
|
"evalCasesWritten",
|
|
29
29
|
"deadUrls",
|
|
30
|
+
"deadUrlCoverage",
|
|
30
31
|
"reflectsWithErrorContext",
|
|
31
32
|
"memoryInference",
|
|
32
33
|
"graphExtraction",
|
|
@@ -440,6 +441,7 @@ function validateCommon(value) {
|
|
|
440
441
|
"sync",
|
|
441
442
|
"terminated",
|
|
442
443
|
"plan",
|
|
444
|
+
"deadUrlCoverage",
|
|
443
445
|
]) {
|
|
444
446
|
if (value[field] !== undefined && !isRecord(value[field]))
|
|
445
447
|
fail(`${field} must be an object`);
|
|
@@ -453,6 +455,12 @@ function validateCommon(value) {
|
|
|
453
455
|
fail("terminated.errorMessage must be a string when present");
|
|
454
456
|
}
|
|
455
457
|
}
|
|
458
|
+
if (isRecord(value.deadUrlCoverage)) {
|
|
459
|
+
requireExactFields(value.deadUrlCoverage, new Set(["checked", "total", "skipped"]));
|
|
460
|
+
for (const field of ["checked", "total", "skipped"]) {
|
|
461
|
+
requireCount(value.deadUrlCoverage, field, "deadUrlCoverage");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
456
464
|
}
|
|
457
465
|
/**
|
|
458
466
|
* Per-`schemaVersion` decoders for the persisted `improve_runs.result_json`
|
|
@@ -49,6 +49,23 @@ export function classifyImproveAction(mode) {
|
|
|
49
49
|
return assertNever(mode);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* #912 — skip reasons that mean infrastructure failed to run the extraction,
|
|
54
|
+
* not that a session was legitimately uninteresting. `already_extracted`,
|
|
55
|
+
* `too_short`, and `triaged_out` are the ledger and pre-filter doing their
|
|
56
|
+
* job and are deliberately excluded; `malformed_model_output` already gets
|
|
57
|
+
* its own per-session forwarding into the envelope's `warnings[]` and is
|
|
58
|
+
* excluded here to avoid double-reporting the same failure two ways.
|
|
59
|
+
*
|
|
60
|
+
* Typed against `ExtractedSessionResult["skipReason"]` so a future reason
|
|
61
|
+
* cannot be added to one union without a compiler error surfacing here.
|
|
62
|
+
*/
|
|
63
|
+
export const EXTRACT_INFRASTRUCTURE_SKIP_REASONS = [
|
|
64
|
+
"llm_unavailable",
|
|
65
|
+
"read_failed",
|
|
66
|
+
"exception",
|
|
67
|
+
"locked_concurrent",
|
|
68
|
+
];
|
|
52
69
|
/** Upper bound on retained sample refs PER reason in {@link DistillSkippedAggregate}. */
|
|
53
70
|
export const DISTILL_SKIPPED_SAMPLE_CAP_PER_REASON = 3;
|
|
54
71
|
/**
|