akm-cli 0.9.0-rc.2 → 0.9.0-rc.4
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 +40 -12
- package/README.md +7 -8
- package/SECURITY.md +5 -3
- package/dist/akm +44 -33
- package/dist/akm-migrate-storage +44 -33
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +28 -11
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/html-report.js +23 -2
- package/dist/commands/health/improve-metrics.js +45 -6
- package/dist/commands/health/md-report.js +10 -1
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/health/windows.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/commands/tasks/default-tasks.js +5 -5
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +261 -68
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/improve-result.js +87 -7
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +23 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +887 -992
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/storage/repositories/task-history-repository.js +30 -3
- package/dist/tasks/backends/cron.js +71 -38
- package/dist/tasks/backends/exec-utils.js +55 -3
- package/dist/tasks/backends/launchd.js +241 -50
- package/dist/tasks/backends/schtasks.js +434 -59
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/tasks/parser.js +142 -6
- package/dist/tasks/resolve-akm-bin.js +19 -4
- package/dist/tasks/runner.js +258 -93
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +86 -0
- package/dist/tasks/task-id.js +37 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/README.md +103 -0
- package/docs/migration/release-notes/0.9.0.md +28 -9
- package/docs/migration/v0.8-to-v0.9.md +186 -26
- package/package.json +2 -3
package/dist/cli.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
3
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
4
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
-
// Runtime guard: akm-cli
|
|
6
|
-
// #560)
|
|
7
|
-
//
|
|
5
|
+
// Runtime guard: the akm-cli npm package bootstraps with Node.js >= 20.12
|
|
6
|
+
// (#465, #560), then its launcher prefers a working Bun >= 1.0 when available.
|
|
7
|
+
// The runtime boundary (src/runtime.ts, src/storage/database.ts) supports both.
|
|
8
|
+
// Under Node the CLI must be launched via the
|
|
8
9
|
// `dist/cli-node.mjs` wrapper, which registers the text-import loader hook
|
|
9
10
|
// before this module graph loads; running `node dist/cli.js` directly still
|
|
10
11
|
// works for code paths that touch no embedded text asset, but the wrapper is
|
|
@@ -18,12 +19,11 @@
|
|
|
18
19
|
.map((part) => Number.parseInt(part, 10) || 0);
|
|
19
20
|
const nodeOk = major > 20 || (major === 20 && (minor > 12 || (minor === 12 && patch >= 0)));
|
|
20
21
|
if (!nodeOk) {
|
|
21
|
-
console.error("\n ERROR: akm-cli
|
|
22
|
+
console.error("\n ERROR: the akm-cli npm package requires Node.js >= 20.12.\n" +
|
|
22
23
|
` Detected Node.js ${process.versions.node ?? "unknown"}.\n` +
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
" 3. Binary: curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\n");
|
|
24
|
+
" Bun >= 1.0 is optional for execution; it does not replace the Node.js bootstrap.\n" +
|
|
25
|
+
" Upgrade Node.js (https://nodejs.org), or install the runtime-free standalone binary:\n" +
|
|
26
|
+
" curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\n");
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -60,7 +60,7 @@ process.on("uncaughtException", (err) => {
|
|
|
60
60
|
import fs from "node:fs";
|
|
61
61
|
import path from "node:path";
|
|
62
62
|
import { defineCommand, runMain } from "citty";
|
|
63
|
-
import { findCittyTopLevelCommand } from "./cli/parse-args.js";
|
|
63
|
+
import { findCittyTopLevelCommand, findCittyTopLevelCommandIndex } from "./cli/parse-args.js";
|
|
64
64
|
import { EXIT_CODES, emitJsonError, output, parseAllFlagValues, runWithJsonErrors } from "./cli/shared.js";
|
|
65
65
|
import { agentCommand, lintCommand, proposeCommand } from "./commands/agent/contribute-cli.js";
|
|
66
66
|
import { backupCommand } from "./commands/backup-cli.js";
|
|
@@ -75,6 +75,7 @@ import { renderRunsDetailMd, renderWindowCompareMd } from "./commands/health/md-
|
|
|
75
75
|
import { parseWindowSpec } from "./commands/health/windows.js";
|
|
76
76
|
import { extractCommand } from "./commands/improve/extract-cli.js";
|
|
77
77
|
import { improveCommand } from "./commands/improve/improve-cli.js";
|
|
78
|
+
import { migrateCommand } from "./commands/migrate-cli.js";
|
|
78
79
|
import { mvCommand } from "./commands/mv-cli.js";
|
|
79
80
|
import { hintsCommand, lessonsCommand, logCommand } from "./commands/observability-cli.js";
|
|
80
81
|
import { proposalCommand } from "./commands/proposal/proposal-cli.js";
|
|
@@ -92,6 +93,7 @@ import { workflowCommand } from "./commands/workflow-cli.js";
|
|
|
92
93
|
import { bestEffort } from "./core/best-effort.js";
|
|
93
94
|
import { DEFAULT_CONFIG, loadConfig } from "./core/config/config.js";
|
|
94
95
|
import { UsageError } from "./core/errors.js";
|
|
96
|
+
import { assertNoPendingMigrationOperation } from "./core/migration-operation.js";
|
|
95
97
|
import { getCacheDir, getConfigPath, getDbPath } from "./core/paths.js";
|
|
96
98
|
import { plainize } from "./core/tty.js";
|
|
97
99
|
import { info, isQuiet, setQuiet, setVerbose, warn } from "./core/warn.js";
|
|
@@ -517,6 +519,7 @@ export const main = defineCommand({
|
|
|
517
519
|
clone: cloneCommand,
|
|
518
520
|
mv: mvCommand,
|
|
519
521
|
registry: registryCommand,
|
|
522
|
+
migrate: migrateCommand,
|
|
520
523
|
backup: backupCommand,
|
|
521
524
|
config: configCommand,
|
|
522
525
|
feedback: feedbackCommand,
|
|
@@ -539,13 +542,25 @@ export const main = defineCommand({
|
|
|
539
542
|
},
|
|
540
543
|
});
|
|
541
544
|
const MAIN_TOP_LEVEL_ARGS = main.args;
|
|
545
|
+
function isTaskRunWithId(argv) {
|
|
546
|
+
const args = argv.slice(2);
|
|
547
|
+
const commandIndex = findCittyTopLevelCommandIndex(args, MAIN_TOP_LEVEL_ARGS);
|
|
548
|
+
const command = commandIndex >= 0 ? args[commandIndex] : undefined;
|
|
549
|
+
if (command !== "tasks" && command !== "task")
|
|
550
|
+
return false;
|
|
551
|
+
const taskArgs = args.slice(commandIndex + 1);
|
|
552
|
+
return taskArgs[0] === "run" && typeof taskArgs[1] === "string" && !taskArgs[1].startsWith("-");
|
|
553
|
+
}
|
|
542
554
|
/** Recovery/setup surfaces must remain reachable when config.json is invalid. */
|
|
543
555
|
export function shouldBypassConfigStartup(argv) {
|
|
544
556
|
const args = argv.slice(2);
|
|
545
557
|
if (args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-v"))
|
|
546
558
|
return true;
|
|
547
|
-
const
|
|
548
|
-
|
|
559
|
+
const commandIndex = findCittyTopLevelCommandIndex(args, MAIN_TOP_LEVEL_ARGS);
|
|
560
|
+
const command = commandIndex >= 0 ? args[commandIndex] : undefined;
|
|
561
|
+
if (command === "setup" || command === "backup" || command === "migrate")
|
|
562
|
+
return true;
|
|
563
|
+
if (isTaskRunWithId(argv))
|
|
549
564
|
return true;
|
|
550
565
|
if (command !== "config")
|
|
551
566
|
return false;
|
|
@@ -582,6 +597,8 @@ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
|
|
|
582
597
|
// rather than letting the raw exception escape with a stack trace.
|
|
583
598
|
try {
|
|
584
599
|
applyEarlyStderrFlags(process.argv);
|
|
600
|
+
if (isTaskRunWithId(process.argv))
|
|
601
|
+
assertNoPendingMigrationOperation();
|
|
585
602
|
const bypassConfig = shouldBypassConfigStartup(process.argv);
|
|
586
603
|
initOutputMode(process.argv, bypassConfig ? (DEFAULT_CONFIG.output ?? {}) : (loadConfig().output ?? {}));
|
|
587
604
|
}
|
|
@@ -10,10 +10,10 @@ function requireVersion(value) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
export const backupCommand = defineGroupCommand({
|
|
13
|
-
meta: { name: "backup", description: "Create or restore a
|
|
13
|
+
meta: { name: "backup", description: "Create or restore a verified migration recovery run" },
|
|
14
14
|
subCommands: {
|
|
15
15
|
create: defineJsonCommand({
|
|
16
|
-
meta: { name: "create", description: "Create
|
|
16
|
+
meta: { name: "create", description: "Create a unique installation-scoped migration recovery run" },
|
|
17
17
|
args: {
|
|
18
18
|
for: { type: "string", required: true, description: "Migration target version (0.9.0)" },
|
|
19
19
|
},
|
|
@@ -30,19 +30,21 @@ export const backupCommand = defineGroupCommand({
|
|
|
30
30
|
},
|
|
31
31
|
}),
|
|
32
32
|
restore: defineJsonCommand({
|
|
33
|
-
meta: { name: "restore", description: "Restore
|
|
33
|
+
meta: { name: "restore", description: "Restore a recovery run after preserving a rescue snapshot" },
|
|
34
34
|
args: {
|
|
35
35
|
for: { type: "string", required: true, description: "Migration target version (0.9.0)" },
|
|
36
|
+
run: { type: "string", description: "Backup run ID (defaults to the newest applicable run)" },
|
|
36
37
|
confirm: { type: "boolean", default: false, description: "Confirm destructive restoration" },
|
|
37
38
|
},
|
|
38
39
|
run({ args }) {
|
|
39
40
|
requireVersion(args.for);
|
|
40
|
-
const result = restoreMigrationBackup(args.confirm);
|
|
41
|
+
const result = restoreMigrationBackup(args.confirm, args.run);
|
|
41
42
|
output("backup", {
|
|
42
43
|
action: "restore",
|
|
43
44
|
for: MIGRATION_BACKUP_VERSION,
|
|
44
45
|
path: result.path,
|
|
45
46
|
restored: true,
|
|
47
|
+
rescuePath: result.rescuePath,
|
|
46
48
|
manifest: result.manifest,
|
|
47
49
|
});
|
|
48
50
|
},
|
|
@@ -242,11 +242,18 @@ export const configCommand = defineGroupCommand({
|
|
|
242
242
|
migrate: defineJsonCommand({
|
|
243
243
|
meta: {
|
|
244
244
|
name: "migrate",
|
|
245
|
-
description: "
|
|
245
|
+
description: "Compatibility alias for the canonical top-level migrate status/apply coordinator.",
|
|
246
246
|
},
|
|
247
|
-
|
|
247
|
+
args: {
|
|
248
|
+
config: {
|
|
249
|
+
type: "string",
|
|
250
|
+
description: "Path to an operator-prepared current-version config to validate and install",
|
|
251
|
+
},
|
|
252
|
+
dryRun: { type: "boolean", default: false, description: "Validate and report without changing files" },
|
|
253
|
+
},
|
|
254
|
+
async run({ args }) {
|
|
248
255
|
const { runConfigMigrate } = await import("../cli/config-migrate.js");
|
|
249
|
-
await runConfigMigrate();
|
|
256
|
+
await runConfigMigrate({ preparedConfigPath: args.config, dryRun: args.dryRun });
|
|
250
257
|
},
|
|
251
258
|
}),
|
|
252
259
|
enable: defineJsonCommand({
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { defineJsonCommand, output, parseAllFlagValues } from "../cli/shared.js";
|
|
6
|
-
import { parseAssetRef } from "../core/asset/asset-ref.js";
|
|
6
|
+
import { parseAssetRef, refToString } from "../core/asset/asset-ref.js";
|
|
7
7
|
import { assembleAsset } from "../core/asset/asset-serialize.js";
|
|
8
8
|
import { parseFrontmatter, parseFrontmatterBlock } from "../core/asset/frontmatter.js";
|
|
9
9
|
import { writeFileAtomic } from "../core/common.js";
|
|
@@ -12,7 +12,8 @@ import { UsageError } from "../core/errors.js";
|
|
|
12
12
|
import { appendEvent } from "../core/events.js";
|
|
13
13
|
import { getDbPath } from "../core/paths.js";
|
|
14
14
|
import { warn } from "../core/warn.js";
|
|
15
|
-
import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, getEntryFilePathById, openExistingDatabase, } from "../indexer/db/db.js";
|
|
15
|
+
import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, getEntryById, getEntryFilePathById, openExistingDatabase, } from "../indexer/db/db.js";
|
|
16
|
+
import { resolveSourceEntries } from "../indexer/search/search-source.js";
|
|
16
17
|
import { countFeedbackSignals, insertUsageEvent } from "../indexer/usage/usage-events.js";
|
|
17
18
|
// ── Tag validation ────────────────────────────────────────────────────────────
|
|
18
19
|
const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
|
|
@@ -156,7 +157,7 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
156
157
|
if (!ref) {
|
|
157
158
|
throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skill:deploy` and either --positive or --negative.");
|
|
158
159
|
}
|
|
159
|
-
parseAssetRef(ref);
|
|
160
|
+
const parsedRef = parseAssetRef(ref);
|
|
160
161
|
if (args.positive && args.negative) {
|
|
161
162
|
throw new UsageError("Specify either --positive or --negative, not both.");
|
|
162
163
|
}
|
|
@@ -216,9 +217,19 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
216
217
|
// background reindex it spawned — now that ensureIndex is removed, holding
|
|
217
218
|
// the lock only causes feedback to block for the full improve run duration.
|
|
218
219
|
let utilityResult;
|
|
220
|
+
let durableRef = ref;
|
|
219
221
|
const db = openExistingDatabase();
|
|
220
222
|
try {
|
|
221
|
-
const
|
|
223
|
+
const config = loadConfig();
|
|
224
|
+
const sources = resolveSourceEntries(undefined, config);
|
|
225
|
+
const requestedSource = parsedRef.origin
|
|
226
|
+
? sources.find((source) => source.registryId === parsedRef.origin ||
|
|
227
|
+
((parsedRef.origin === "local" || parsedRef.origin === "stash") && source === sources[0]))
|
|
228
|
+
: undefined;
|
|
229
|
+
if (parsedRef.origin && !requestedSource) {
|
|
230
|
+
throw new UsageError(`Source "${parsedRef.origin}" is not configured.`, "INVALID_FLAG_VALUE");
|
|
231
|
+
}
|
|
232
|
+
const entryId = findEntryIdByRef(db, ref, requestedSource?.path);
|
|
222
233
|
if (entryId === undefined) {
|
|
223
234
|
throw new UsageError(`Ref "${ref}" is not in the index. ` +
|
|
224
235
|
"Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
|
|
@@ -228,9 +239,15 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
228
239
|
// For negative signals, the score is adjusted the next time `akm index`
|
|
229
240
|
// runs — the signal is durable in the DB but does NOT suppress ranking
|
|
230
241
|
// in search results until after reindexing.
|
|
242
|
+
const indexedEntry = getEntryById(db, entryId);
|
|
243
|
+
const source = sources.find((candidate) => candidate.path === indexedEntry?.stashDir);
|
|
244
|
+
durableRef = refToString({
|
|
245
|
+
...parsedRef,
|
|
246
|
+
origin: parsedRef.origin ?? source?.registryId ?? "stash",
|
|
247
|
+
});
|
|
231
248
|
insertUsageEvent(db, {
|
|
232
249
|
event_type: "feedback",
|
|
233
|
-
entry_ref:
|
|
250
|
+
entry_ref: durableRef,
|
|
234
251
|
entry_id: entryId,
|
|
235
252
|
signal,
|
|
236
253
|
metadata: metadataStr,
|
|
@@ -253,7 +270,7 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
253
270
|
}
|
|
254
271
|
appendEvent({
|
|
255
272
|
eventType: "feedback",
|
|
256
|
-
ref,
|
|
273
|
+
ref: durableRef,
|
|
257
274
|
metadata: metadataObj,
|
|
258
275
|
});
|
|
259
276
|
// F-5 / #386: When a high-utility asset crosses below the review threshold,
|
|
@@ -268,7 +285,7 @@ export const feedbackCommand = defineJsonCommand({
|
|
|
268
285
|
try {
|
|
269
286
|
appendEvent({
|
|
270
287
|
eventType: "improve_review_needed",
|
|
271
|
-
ref,
|
|
288
|
+
ref: durableRef,
|
|
272
289
|
metadata: {
|
|
273
290
|
previousUtility: utilityResult.previousUtility,
|
|
274
291
|
nextUtility: utilityResult.nextUtility,
|
|
@@ -6,20 +6,25 @@ import { createRequire } from "node:module";
|
|
|
6
6
|
import { loadConfig } from "../../core/config/config.js";
|
|
7
7
|
import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
|
|
8
8
|
import { resolveModel } from "../../integrations/agent/model-aliases.js";
|
|
9
|
+
import { resolveImprovePlan } from "../improve/improve-strategies.js";
|
|
9
10
|
import { TASK_FAIL_RATE_WARN, } from "./types.js";
|
|
10
11
|
const ACTIVE_RUN_WARN_MS = 15 * 60 * 1000;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
function credentialAvailable(credential, env) {
|
|
13
|
+
return !credential?.required || credential.names.some((name) => Boolean(env[name]?.trim()));
|
|
14
|
+
}
|
|
15
|
+
function runConfiguredEngineProbe(checkName, engineName, config, deps) {
|
|
14
16
|
if (!engineName) {
|
|
15
17
|
return {
|
|
16
|
-
name:
|
|
18
|
+
name: checkName,
|
|
17
19
|
kind: "deterministic",
|
|
18
20
|
status: "unknown",
|
|
19
21
|
confidence: "high",
|
|
20
|
-
message: "
|
|
22
|
+
message: checkName === "default-llm-engine"
|
|
23
|
+
? "No default LLM engine is configured."
|
|
24
|
+
: "No default engine is configured.",
|
|
21
25
|
};
|
|
22
26
|
}
|
|
27
|
+
const env = deps.env ?? process.env;
|
|
23
28
|
const configuredEngine = config.engines?.[engineName];
|
|
24
29
|
if (configuredEngine?.kind === "agent" && configuredEngine.platform === "opencode-sdk") {
|
|
25
30
|
let packageAvailable = false;
|
|
@@ -36,6 +41,7 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
36
41
|
const binaryAvailable = (version.status ?? 1) === 0;
|
|
37
42
|
const fallbackEngine = configuredEngine.llmEngine ?? config.defaults?.llmEngine;
|
|
38
43
|
let fallback;
|
|
44
|
+
let fallbackCredential;
|
|
39
45
|
let sdkRunner;
|
|
40
46
|
const resolve = deps.resolveEngine ?? resolveEngine;
|
|
41
47
|
try {
|
|
@@ -48,12 +54,15 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
48
54
|
}
|
|
49
55
|
if (sdkRunner?.fallbackConnection) {
|
|
50
56
|
fallback = { kind: "llm", connection: sdkRunner.fallbackConnection };
|
|
57
|
+
fallbackCredential = sdkRunner.fallbackCredential;
|
|
51
58
|
}
|
|
52
59
|
else if (fallbackEngine) {
|
|
53
60
|
try {
|
|
54
61
|
const resolved = resolve(fallbackEngine, config);
|
|
55
|
-
if (resolved.kind === "llm")
|
|
62
|
+
if (resolved.kind === "llm") {
|
|
56
63
|
fallback = resolved;
|
|
64
|
+
fallbackCredential = resolved.credential;
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
67
|
catch {
|
|
59
68
|
fallback = undefined;
|
|
@@ -63,14 +72,15 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
63
72
|
? resolveModel(configuredEngine.model, "opencode-sdk", configuredEngine.modelAliases, config.modelAliases)
|
|
64
73
|
: undefined;
|
|
65
74
|
const effectiveModel = sdkRunner?.profile.model ?? configuredModel ?? fallback?.connection.model;
|
|
75
|
+
const fallbackCredentialAvailable = credentialAvailable(fallbackCredential, env);
|
|
66
76
|
const missing = [
|
|
67
77
|
!packageAvailable ? "@opencode-ai/sdk package" : undefined,
|
|
68
78
|
!binaryAvailable ? `${binary} binary` : undefined,
|
|
69
|
-
!fallback ? "fallback LLM connection" : undefined,
|
|
70
|
-
!
|
|
79
|
+
fallbackEngine && !fallback ? "configured fallback LLM connection" : undefined,
|
|
80
|
+
!fallbackCredentialAvailable ? "required fallback credential" : undefined,
|
|
71
81
|
].filter((value) => value !== undefined);
|
|
72
82
|
return {
|
|
73
|
-
name:
|
|
83
|
+
name: checkName,
|
|
74
84
|
kind: "deterministic",
|
|
75
85
|
status: missing.length === 0 ? "pass" : "warn",
|
|
76
86
|
confidence: "high",
|
|
@@ -91,24 +101,29 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
91
101
|
fallbackEngine: fallbackEngine ?? null,
|
|
92
102
|
fallbackEndpoint: fallback?.connection.endpoint ?? null,
|
|
93
103
|
fallbackModel: fallback?.connection.model ?? null,
|
|
104
|
+
requiredCredentialAvailable: fallbackCredentialAvailable,
|
|
94
105
|
},
|
|
95
106
|
};
|
|
96
107
|
}
|
|
97
108
|
try {
|
|
98
109
|
const runner = (deps.resolveEngine ?? resolveEngine)(engineName, config);
|
|
99
110
|
if (runner.kind === "llm") {
|
|
111
|
+
const requiredCredentialAvailable = credentialAvailable(runner.credential, env);
|
|
100
112
|
return {
|
|
101
|
-
name:
|
|
113
|
+
name: checkName,
|
|
102
114
|
kind: "deterministic",
|
|
103
|
-
status: "pass",
|
|
115
|
+
status: requiredCredentialAvailable ? "pass" : "warn",
|
|
104
116
|
confidence: "high",
|
|
105
|
-
message:
|
|
117
|
+
message: requiredCredentialAvailable
|
|
118
|
+
? `LLM engine "${engineName}" is configured.`
|
|
119
|
+
: `LLM engine "${engineName}" is configured, but its required credential is unavailable.`,
|
|
106
120
|
evidence: {
|
|
107
121
|
engine: engineName,
|
|
108
122
|
platform: null,
|
|
109
123
|
runtimeKind: "llm",
|
|
110
124
|
model: runner.connection.model,
|
|
111
125
|
endpoint: runner.connection.endpoint,
|
|
126
|
+
requiredCredentialAvailable,
|
|
112
127
|
},
|
|
113
128
|
};
|
|
114
129
|
}
|
|
@@ -118,7 +133,7 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
118
133
|
const version = (deps.spawnSync ?? spawnSync)(profile.bin, ["--version"], { encoding: "utf8", timeout: 5_000 });
|
|
119
134
|
if ((version.status ?? 1) !== 0) {
|
|
120
135
|
return {
|
|
121
|
-
name:
|
|
136
|
+
name: checkName,
|
|
122
137
|
kind: "deterministic",
|
|
123
138
|
status: "warn",
|
|
124
139
|
confidence: "medium",
|
|
@@ -132,7 +147,7 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
132
147
|
};
|
|
133
148
|
}
|
|
134
149
|
return {
|
|
135
|
-
name:
|
|
150
|
+
name: checkName,
|
|
136
151
|
kind: "deterministic",
|
|
137
152
|
status: "pass",
|
|
138
153
|
confidence: "high",
|
|
@@ -147,7 +162,7 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
147
162
|
}
|
|
148
163
|
catch (error) {
|
|
149
164
|
return {
|
|
150
|
-
name:
|
|
165
|
+
name: checkName,
|
|
151
166
|
kind: "deterministic",
|
|
152
167
|
status: "warn",
|
|
153
168
|
confidence: "high",
|
|
@@ -155,6 +170,60 @@ export function runDefaultEngineProbe(deps = {}) {
|
|
|
155
170
|
};
|
|
156
171
|
}
|
|
157
172
|
}
|
|
173
|
+
export function runDefaultEngineProbe(deps = {}) {
|
|
174
|
+
const config = deps.loadConfig?.() ?? loadConfig();
|
|
175
|
+
return runConfiguredEngineProbe("default-engine", config.defaults?.engine, config, deps);
|
|
176
|
+
}
|
|
177
|
+
export function runDefaultLlmEngineProbe(deps = {}) {
|
|
178
|
+
const config = deps.loadConfig?.() ?? loadConfig();
|
|
179
|
+
return runConfiguredEngineProbe("default-llm-engine", config.defaults?.llmEngine, config, deps);
|
|
180
|
+
}
|
|
181
|
+
export function runActiveImproveStrategyProbe(deps = {}) {
|
|
182
|
+
const config = deps.loadConfig?.() ?? loadConfig();
|
|
183
|
+
const strategyName = config.defaults?.improveStrategy ?? "default";
|
|
184
|
+
try {
|
|
185
|
+
const plan = resolveImprovePlan(strategyName, config);
|
|
186
|
+
const env = deps.env ?? process.env;
|
|
187
|
+
const unavailableProcesses = Object.entries(plan.processes).flatMap(([name, process]) => {
|
|
188
|
+
if (!process.enabled || !process.runner)
|
|
189
|
+
return [];
|
|
190
|
+
return credentialAvailable(process.runner.credential, env) ? [] : [name];
|
|
191
|
+
});
|
|
192
|
+
if (plan.triageJudgment) {
|
|
193
|
+
const judgmentCredential = plan.triageJudgment.kind === "llm"
|
|
194
|
+
? plan.triageJudgment.credential
|
|
195
|
+
: plan.triageJudgment.kind === "sdk"
|
|
196
|
+
? plan.triageJudgment.fallbackCredential
|
|
197
|
+
: undefined;
|
|
198
|
+
if (!credentialAvailable(judgmentCredential, env))
|
|
199
|
+
unavailableProcesses.push("triage.judgment");
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
name: "active-improve-strategy",
|
|
203
|
+
kind: "deterministic",
|
|
204
|
+
status: unavailableProcesses.length === 0 ? "pass" : "warn",
|
|
205
|
+
confidence: "high",
|
|
206
|
+
message: unavailableProcesses.length === 0
|
|
207
|
+
? `Active improve strategy "${plan.strategy.name}" has available process engines.`
|
|
208
|
+
: `Active improve strategy "${plan.strategy.name}" has unavailable required credentials for: ${unavailableProcesses.join(", ")}.`,
|
|
209
|
+
evidence: {
|
|
210
|
+
strategy: plan.strategy.name,
|
|
211
|
+
unavailableProcesses,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const explicitlyConfigured = config.defaults?.improveStrategy !== undefined || Object.keys(config.improve?.strategies ?? {}).length > 0;
|
|
217
|
+
return {
|
|
218
|
+
name: "active-improve-strategy",
|
|
219
|
+
kind: "deterministic",
|
|
220
|
+
status: explicitlyConfigured ? "warn" : "unknown",
|
|
221
|
+
confidence: "high",
|
|
222
|
+
message: `Active improve strategy "${strategyName}" is unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
223
|
+
evidence: { strategy: strategyName, unavailableProcesses: [] },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
158
227
|
/**
|
|
159
228
|
* The ordered health-check registry. ORDER IS LOAD-BEARING: `akmHealth`
|
|
160
229
|
* iterates this array and appends to hardChecks/advisories in sequence, so the
|
|
@@ -236,6 +305,16 @@ export const HEALTH_CHECKS = [
|
|
|
236
305
|
channel: "hard",
|
|
237
306
|
run: () => runDefaultEngineProbe(),
|
|
238
307
|
},
|
|
308
|
+
{
|
|
309
|
+
name: "default-llm-engine",
|
|
310
|
+
channel: "hard",
|
|
311
|
+
run: () => runDefaultLlmEngineProbe(),
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "active-improve-strategy",
|
|
315
|
+
channel: "hard",
|
|
316
|
+
run: () => runActiveImproveStrategyProbe(),
|
|
317
|
+
},
|
|
239
318
|
{
|
|
240
319
|
// C2 (13-bus-factor): the cron task-failure rate was computed and rendered
|
|
241
320
|
// in the HTML report but never surfaced as an advisory, so a sustained
|
|
@@ -92,6 +92,8 @@ function reshapeRun(r) {
|
|
|
92
92
|
const geMs = ge.durationMs || 0;
|
|
93
93
|
return {
|
|
94
94
|
id: r.id,
|
|
95
|
+
resultStatus: r.resultStatus ?? "valid",
|
|
96
|
+
resultComplete: r.resultComplete ?? (r.resultStatus === undefined || r.resultStatus === "valid"),
|
|
95
97
|
taskId: r.taskId ?? "manual",
|
|
96
98
|
strategy: r.strategy,
|
|
97
99
|
legacyProfile: r.legacyProfile,
|
|
@@ -262,7 +264,10 @@ function buildEchartsTag(opts) {
|
|
|
262
264
|
*/
|
|
263
265
|
export function buildHealthHtmlReplacements(result, opts) {
|
|
264
266
|
const deltas = opts.deltas ?? {};
|
|
265
|
-
const
|
|
267
|
+
const compareRuns = (a, b) => a.startedAt.localeCompare(b.startedAt) || a.completedAt.localeCompare(b.completedAt) || a.id.localeCompare(b.id);
|
|
268
|
+
const allRuns = (result.runs ?? []).map(reshapeRun).sort(compareRuns);
|
|
269
|
+
const invalidRuns = allRuns.filter((run) => run.resultStatus === "invalid");
|
|
270
|
+
const runs = allRuns.filter((run) => run.resultStatus !== "invalid");
|
|
266
271
|
const improve = result.improve;
|
|
267
272
|
const proposals = opts.proposals;
|
|
268
273
|
const sem = readSemSearch(result.advisories);
|
|
@@ -320,7 +325,10 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
320
325
|
const reportTitle = reportDate ? `AKM Health Report — ${reportDate}` : "AKM Health Report";
|
|
321
326
|
const lastRun = runs[runs.length - 1];
|
|
322
327
|
const generatedAt = lastRun ? lastRun.completedAt || lastRun.startedAt || sinceIso : sinceIso;
|
|
323
|
-
const latest = [...runs]
|
|
328
|
+
const latest = [...runs]
|
|
329
|
+
.filter((run) => run.resultComplete)
|
|
330
|
+
.sort(compareRuns)
|
|
331
|
+
.at(-1);
|
|
324
332
|
// Freshness: surface the newest run's timestamp + the generated-at anchor in
|
|
325
333
|
// the exec card. Staleness is computed deterministically (no Date.now()) from
|
|
326
334
|
// the gap between the window start (`since`) and the newest run we have: if no
|
|
@@ -389,6 +397,9 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
389
397
|
li("Report window", esc(opts.window)),
|
|
390
398
|
li("Compare window", esc(opts.compare)),
|
|
391
399
|
li("Runs", `${num(totalRuns)} (${failedRuns} failed)`),
|
|
400
|
+
li("Included result rows", num(improve.resultRows?.included ?? totalRuns)),
|
|
401
|
+
li("Normalized result rows", num(improve.resultRows?.normalized ?? 0)),
|
|
402
|
+
li("Invalid result rows skipped", num(improve.resultRows?.skipped.invalid ?? invalidRuns.length)),
|
|
392
403
|
li("Stash derived", `<abbr title="Whole-stash recount of derived assets at report time — not a per-run sum.">${num(improve.memorySummary.derived)}</abbr>`),
|
|
393
404
|
li("Stash eligible", `<abbr title="Whole-stash recount of eligible assets at report time — not a per-run sum.">${num(improve.memorySummary.eligible)}</abbr>`),
|
|
394
405
|
li("Pending proposals", String(proposals.length)),
|
|
@@ -655,6 +666,16 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
655
666
|
seen.add(item.key);
|
|
656
667
|
items.push(item);
|
|
657
668
|
};
|
|
669
|
+
if (invalidRuns.length > 0) {
|
|
670
|
+
pushItem({
|
|
671
|
+
key: "invalid-improve-result-rows",
|
|
672
|
+
prio: "P2",
|
|
673
|
+
cls: "warn",
|
|
674
|
+
title: `${invalidRuns.length} invalid improve result row${invalidRuns.length === 1 ? "" : "s"} skipped`,
|
|
675
|
+
descHtml: `Excluded from result-derived metrics: ${invalidRuns.map((run) => `<code>${esc(run.id)}</code>`).join(", ")}.`,
|
|
676
|
+
remedy: `akm health --since=${opts.window} --group-by run`,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
658
679
|
// Hard advisories from the health check engine (own remediation in message).
|
|
659
680
|
for (const a of result.advisories) {
|
|
660
681
|
if (a.status !== "warn" && a.status !== "fail")
|
|
@@ -25,6 +25,7 @@ function createUnknownImproveMetrics() {
|
|
|
25
25
|
completed: 0,
|
|
26
26
|
skipped: 0,
|
|
27
27
|
skipReasons: {},
|
|
28
|
+
resultRows: { total: 0, included: 0, normalized: 0, skipped: { invalid: 0 } },
|
|
28
29
|
plannedRefs: 0,
|
|
29
30
|
strategyFilteredRefs: 0,
|
|
30
31
|
actions: {
|
|
@@ -605,6 +606,15 @@ function mergeImproveMetrics(dst, src) {
|
|
|
605
606
|
// totalAssets is intentionally NOT merged here — set from the most recent run in summarizeImproveRuns.
|
|
606
607
|
dst.coverage.acceptedProposals += src.coverage.acceptedProposals;
|
|
607
608
|
}
|
|
609
|
+
function compareImproveRunRecency(a, b) {
|
|
610
|
+
const started = a.started_at.localeCompare(b.started_at);
|
|
611
|
+
if (started !== 0)
|
|
612
|
+
return started;
|
|
613
|
+
const completed = (a.completed_at ?? "").localeCompare(b.completed_at ?? "");
|
|
614
|
+
if (completed !== 0)
|
|
615
|
+
return completed;
|
|
616
|
+
return a.id.localeCompare(b.id);
|
|
617
|
+
}
|
|
608
618
|
export function summarizeImproveRuns(db, since, until) {
|
|
609
619
|
const accum = createUnknownImproveMetrics();
|
|
610
620
|
const rows = queryImproveRuns(db, since, until);
|
|
@@ -618,16 +628,33 @@ export function summarizeImproveRuns(db, since, until) {
|
|
|
618
628
|
};
|
|
619
629
|
// memorySummary is a whole-stash snapshot per run, so the window value is the
|
|
620
630
|
// MOST RECENT run's snapshot (current state) — not a sum across runs.
|
|
621
|
-
let
|
|
631
|
+
let latestCompleteRow;
|
|
622
632
|
let latestMemorySummary;
|
|
623
633
|
let latestStrategyFilteredRefs = 0;
|
|
634
|
+
if (!accum.resultRows)
|
|
635
|
+
throw new Error("invariant: improve result-row accounting was not initialized");
|
|
636
|
+
accum.resultRows.total = rows.length;
|
|
624
637
|
for (const row of rows) {
|
|
625
|
-
|
|
638
|
+
let decoded;
|
|
639
|
+
try {
|
|
640
|
+
decoded = decodeImproveResult(row.result_json);
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
accum.resultRows.skipped.invalid += 1;
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
accum.resultRows.included += 1;
|
|
647
|
+
if (decoded.normalizedLegacyPartial)
|
|
648
|
+
accum.resultRows.normalized += 1;
|
|
649
|
+
const result = decoded.envelope;
|
|
626
650
|
const perRow = projectRunMetrics(result);
|
|
627
651
|
mergeImproveMetrics(accum, perRow);
|
|
628
652
|
const startMs = new Date(row.started_at).getTime();
|
|
629
|
-
if (
|
|
630
|
-
|
|
653
|
+
if (!decoded.normalizedLegacyPartial &&
|
|
654
|
+
result.terminated === undefined &&
|
|
655
|
+
Number.isFinite(startMs) &&
|
|
656
|
+
(latestCompleteRow === undefined || compareImproveRunRecency(row, latestCompleteRow) > 0)) {
|
|
657
|
+
latestCompleteRow = row;
|
|
631
658
|
latestMemorySummary = perRow.memorySummary;
|
|
632
659
|
latestStrategyFilteredRefs = perRow.strategyFilteredRefs;
|
|
633
660
|
}
|
|
@@ -681,8 +708,18 @@ export function summarizePhaseDurations(samples) {
|
|
|
681
708
|
* Used by `akm health --detail per-run`.
|
|
682
709
|
*/
|
|
683
710
|
export function projectImproveRunSummary(row, wallTimeMs, taskId) {
|
|
684
|
-
|
|
685
|
-
|
|
711
|
+
let result = {};
|
|
712
|
+
let resultStatus = "invalid";
|
|
713
|
+
try {
|
|
714
|
+
const decoded = decodeImproveResult(row.result_json);
|
|
715
|
+
result = decoded.envelope;
|
|
716
|
+
resultStatus = decoded.normalizedLegacyPartial ? "normalized" : "valid";
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
// Keep the persisted row visible in per-run output, but do not project its
|
|
720
|
+
// unknown payload or admit its duration to result-derived denominators.
|
|
721
|
+
wallTimeMs = 0;
|
|
722
|
+
}
|
|
686
723
|
const perRow = projectRunMetrics(result);
|
|
687
724
|
finalizeImproveMetrics(perRow);
|
|
688
725
|
const orphansPurged = toFiniteNumber(result.orphansPurged);
|
|
@@ -695,6 +732,8 @@ export function projectImproveRunSummary(row, wallTimeMs, taskId) {
|
|
|
695
732
|
completedAt: row.completed_at,
|
|
696
733
|
wallTimeMs,
|
|
697
734
|
ok: row.ok === 1,
|
|
735
|
+
resultStatus,
|
|
736
|
+
resultComplete: resultStatus === "valid" && result.terminated === undefined,
|
|
698
737
|
strategy: row.strategy,
|
|
699
738
|
legacyProfile: row.legacyProfile,
|
|
700
739
|
scope: {
|
|
@@ -38,6 +38,7 @@ export function renderRunsDetailMd(runs) {
|
|
|
38
38
|
"graph_f/e/r",
|
|
39
39
|
"orphans",
|
|
40
40
|
"lint_f/fl",
|
|
41
|
+
"result_status",
|
|
41
42
|
];
|
|
42
43
|
const rows = runs.map((r) => {
|
|
43
44
|
const totalActions = r.actions.reflect.ok +
|
|
@@ -66,6 +67,7 @@ export function renderRunsDetailMd(runs) {
|
|
|
66
67
|
`${r.graphExtraction.extractedFiles}/${r.graphExtraction.entities}/${r.graphExtraction.relations}`,
|
|
67
68
|
String(r.orphansPurged),
|
|
68
69
|
`${r.lintFixed}/${r.lintFlagged}`,
|
|
70
|
+
r.resultStatus ?? "valid",
|
|
69
71
|
];
|
|
70
72
|
});
|
|
71
73
|
return renderTable(headers, rows);
|
|
@@ -89,7 +91,14 @@ export function renderWindowCompareMd(windows, deltas) {
|
|
|
89
91
|
"improve.memoryInference.skippedNoFacts",
|
|
90
92
|
]);
|
|
91
93
|
const rows = [];
|
|
92
|
-
|
|
94
|
+
const paths = [
|
|
95
|
+
"runs",
|
|
96
|
+
"improve.resultRows.included",
|
|
97
|
+
"improve.resultRows.normalized",
|
|
98
|
+
"improve.resultRows.skipped.invalid",
|
|
99
|
+
...INTERESTING_DELTA_PATHS,
|
|
100
|
+
];
|
|
101
|
+
for (const path of paths) {
|
|
93
102
|
const values = windows.map((w) => String(readNumericPath(w, path)));
|
|
94
103
|
const delta = deltas?.[path];
|
|
95
104
|
let deltaStr = "—";
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
|
+
import { MAX_CONFIG_FILE_BYTES, readTextFileWithLimit } from "../../core/common.js";
|
|
21
22
|
import { CURRENT_CONFIG_VERSION } from "../../core/config/config-schema.js";
|
|
22
23
|
import { compareConfigVersion } from "../../core/config/config-version.js";
|
|
23
24
|
const GROUP_OTHER_BITS = 0o077;
|
|
@@ -98,7 +99,7 @@ export function collectSecretPermsAdvisory(input, platform = process.platform) {
|
|
|
98
99
|
export function collectConfigSkewAdvisory(configPath) {
|
|
99
100
|
let raw;
|
|
100
101
|
try {
|
|
101
|
-
raw = JSON.parse(
|
|
102
|
+
raw = JSON.parse(readTextFileWithLimit(configPath, MAX_CONFIG_FILE_BYTES, "Config file"));
|
|
102
103
|
}
|
|
103
104
|
catch {
|
|
104
105
|
return undefined;
|