akm-cli 0.9.0-rc.3 → 0.9.0-rc.5
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 +12 -4
- 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/help/help-improve.md +3 -0
- package/dist/cli.js +25 -10
- 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/windows.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/distill/promote-memory.js +6 -2
- package/dist/commands/improve/distill/quality-gate.js +94 -31
- package/dist/commands/improve/distill.js +7 -2
- package/dist/commands/improve/extract.js +5 -2
- package/dist/commands/improve/improve-auto-accept.js +2 -2
- package/dist/commands/improve/improve.js +158 -176
- package/dist/commands/improve/locks.js +22 -94
- package/dist/commands/improve/loop-stages.js +15 -10
- package/dist/commands/improve/preparation.js +21 -17
- package/dist/commands/improve/proactive-maintenance.js +4 -6
- package/dist/commands/improve/reflect.js +40 -67
- 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/extra-params.js +1 -0
- package/dist/core/improve-result.js +87 -7
- package/dist/core/maintenance-barrier.js +16 -0
- package/dist/core/state-db.js +1 -0
- package/dist/core/write-source.js +1 -0
- package/dist/indexer/ensure-index.js +6 -4
- package/dist/llm/client.js +7 -5
- package/dist/schemas/akm-task.json +2 -2
- package/dist/schemas/akm-workflow.json +2 -2
- package/dist/scripts/migrate-storage.js +23 -4
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +91 -9
- 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/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/docs/migration/release-notes/0.9.0.md +4 -2
- package/docs/migration/v0.8-to-v0.9.md +64 -9
- package/package.json +2 -3
- package/schemas/akm-task.json +2 -2
- package/schemas/akm-workflow.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -350,6 +350,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
350
350
|
|
|
351
351
|
### Fixed
|
|
352
352
|
|
|
353
|
+
- **Improve RC stabilization.** Restored one ownership-safe whole-run lock from
|
|
354
|
+
triage through final sync; `--skip-if-locked` is a true no-op; the run deadline
|
|
355
|
+
now starts before indexing and reaches index waits, generation, reindexing, and
|
|
356
|
+
quality judges; reflect judges the sanitized final candidate with bounded
|
|
357
|
+
changed-region context; write-target selectors no longer replace durable source
|
|
358
|
+
identity; and vLLM thinking controls cannot be overridden through `extraParams`.
|
|
353
359
|
- **Proposal promotion, reversion, and rejection are durable and recoverable.**
|
|
354
360
|
Acceptance and reversion persist target ownership and content fingerprints,
|
|
355
361
|
publish atomically across filesystem layouts, index immediately, commit exact
|
|
@@ -1234,14 +1240,16 @@ proposal and log storage, `--format html` output, and per-stage LLM telemetry.
|
|
|
1234
1240
|
|
|
1235
1241
|
### Added
|
|
1236
1242
|
|
|
1237
|
-
- **Cross-runtime: akm now runs on Node.js
|
|
1243
|
+
- **Cross-runtime: akm now runs on Node.js >= 20.12 in addition to Bun** (#560,
|
|
1238
1244
|
#465). A two-file runtime boundary (`src/storage/database.ts` owns SQLite via
|
|
1239
1245
|
`bun:sqlite` on Bun / `better-sqlite3` on Node; `src/runtime.ts` owns every
|
|
1240
1246
|
`Bun.*` API) contains all runtime-specific code, enforced by a lint guard so it
|
|
1241
1247
|
cannot leak back out. A CI `node-smoke` matrix runs the built CLI under Node
|
|
1242
|
-
20 and 22.
|
|
1243
|
-
|
|
1244
|
-
Bun
|
|
1248
|
+
20 and 22. The prompts dependency (`@clack/core`) uses `node:util.styleText`,
|
|
1249
|
+
added in Node 20.12; Node 18 is EOL and unsupported. The npm package uses Node
|
|
1250
|
+
as its bootstrap and prefers a working Bun >= 1.0 for execution when both are
|
|
1251
|
+
available. Old, unusable, or absent Bun installations fall back to Node.js;
|
|
1252
|
+
standalone binaries remain runtime-free.
|
|
1245
1253
|
- **`session` asset type — agent sessions are now searchable** (#561). The
|
|
1246
1254
|
`extract` pass, after distilling memory proposals from a session, additionally
|
|
1247
1255
|
writes the session itself as a first-class `session` asset
|
package/README.md
CHANGED
|
@@ -24,13 +24,7 @@ curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh |
|
|
|
24
24
|
irm https://github.com/itlackey/akm/releases/latest/download/install.ps1 | iex
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
**Option 2 —
|
|
28
|
-
|
|
29
|
-
```sh
|
|
30
|
-
bun install -g akm-cli
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Option 3 — Node.js (requires Node.js >= 20.12):**
|
|
27
|
+
**Option 2 — npm package (requires [Node.js](https://nodejs.org) >= 20.12):**
|
|
34
28
|
|
|
35
29
|
```sh
|
|
36
30
|
npm install -g akm-cli
|
|
@@ -38,7 +32,12 @@ npm install -g akm-cli
|
|
|
38
32
|
|
|
39
33
|
Upgrade in place with `akm upgrade`.
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
The npm package always uses Node.js to bootstrap its cross-platform command.
|
|
36
|
+
If a working [Bun](https://bun.sh) >= 1.0 is also on `PATH`, the launcher
|
|
37
|
+
prefers Bun for execution; old, unusable, or absent Bun installations fall back
|
|
38
|
+
to Node.js. Node.js remains required for the npm package. The standalone
|
|
39
|
+
binaries are runtime-free.
|
|
40
|
+
|
|
42
41
|
> The old `vault` asset type was removed in 0.9.0; use `env` for whole `.env`
|
|
43
42
|
> groups and `secret` for standalone sensitive values.
|
|
44
43
|
|
package/SECURITY.md
CHANGED
|
@@ -82,9 +82,11 @@ containing secrets or private notes.
|
|
|
82
82
|
|
|
83
83
|
## Known non-issues
|
|
84
84
|
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
- **The `akm-cli` npm package requires Node.js >= 20.12 as its bootstrap.** A
|
|
86
|
+
working Bun >= 1.0 is preferred for execution when it is also on `PATH`; old,
|
|
87
|
+
unusable, or absent Bun installations fall back to Node.js. Bun does not
|
|
88
|
+
remove the package's Node.js requirement. Standalone binaries are
|
|
89
|
+
runtime-free. This is a compatibility limitation, not a security risk.
|
|
88
90
|
- **Workflows can read any file the akm process can read.** This is not a
|
|
89
91
|
bug — see "Threat model" above.
|
|
90
92
|
- **Installing `akm-cli` runs the preinstall hook.** The hook only validates
|
package/dist/akm
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
1
|
-
#!/bin/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*)
|
|
9
|
-
SCRIPT_PATH=$(command -v -- "$0" 2>/dev/null || true)
|
|
10
|
-
if [ -z "$SCRIPT_PATH" ]; then
|
|
11
|
-
echo "akm launcher could not resolve its own path." >&2
|
|
12
|
-
exit 127
|
|
13
|
-
fi
|
|
14
|
-
;;
|
|
15
|
-
esac
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
16
8
|
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
done
|
|
25
|
-
fi
|
|
9
|
+
if (!process.versions.bun) {
|
|
10
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
11
|
+
if (major < 20 || (major === 20 && minor < 12)) {
|
|
12
|
+
console.error("The akm-cli npm package requires Node.js >= 20.12 to bootstrap.");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
26
16
|
|
|
27
|
-
|
|
17
|
+
const bunProbe = process.versions.bun
|
|
18
|
+
? { status: 0, stdout: process.versions.bun }
|
|
19
|
+
: spawnSync("bun", ["--version"], {
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
22
|
+
timeout: 5_000,
|
|
23
|
+
});
|
|
24
|
+
const [bunMajor = 0] = (bunProbe.status === 0 ? bunProbe.stdout.trim() : "").split(".").map(Number);
|
|
25
|
+
const useBun = bunMajor >= 1;
|
|
26
|
+
const bunEntry = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
27
|
+
const nodeEntry = fileURLToPath(new URL("./cli-node.mjs", import.meta.url));
|
|
28
28
|
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (!useBun && !process.versions.bun) {
|
|
30
|
+
await import("./cli-node.mjs");
|
|
31
|
+
} else {
|
|
32
|
+
const command = useBun ? (process.versions.bun ? process.execPath : "bun") : "node";
|
|
33
|
+
const entry = useBun ? bunEntry : nodeEntry;
|
|
34
|
+
const runtime = useBun ? "Bun" : "Node.js";
|
|
35
|
+
const result = await new Promise((resolve) => {
|
|
36
|
+
const child = spawn(command, [entry, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
37
|
+
child.once("error", (error) => resolve({ error }));
|
|
38
|
+
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
39
|
+
});
|
|
32
40
|
|
|
33
|
-
if
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
if (result.error) {
|
|
42
|
+
console.error(`akm launcher could not start ${runtime}: ${result.error.message}`);
|
|
43
|
+
process.exitCode = 127;
|
|
44
|
+
} else if (result.signal) {
|
|
45
|
+
process.kill(process.pid, result.signal);
|
|
46
|
+
} else {
|
|
47
|
+
process.exitCode = result.code ?? 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/akm-migrate-storage
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
1
|
-
#!/bin/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*)
|
|
9
|
-
SCRIPT_PATH=$(command -v -- "$0" 2>/dev/null || true)
|
|
10
|
-
if [ -z "$SCRIPT_PATH" ]; then
|
|
11
|
-
echo "akm-migrate-storage launcher could not resolve its own path." >&2
|
|
12
|
-
exit 127
|
|
13
|
-
fi
|
|
14
|
-
;;
|
|
15
|
-
esac
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
16
8
|
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
done
|
|
25
|
-
fi
|
|
9
|
+
if (!process.versions.bun) {
|
|
10
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
11
|
+
if (major < 20 || (major === 20 && minor < 12)) {
|
|
12
|
+
console.error("The akm-cli npm package requires Node.js >= 20.12 to bootstrap.");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
26
16
|
|
|
27
|
-
|
|
17
|
+
const bunProbe = process.versions.bun
|
|
18
|
+
? { status: 0, stdout: process.versions.bun }
|
|
19
|
+
: spawnSync("bun", ["--version"], {
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
22
|
+
timeout: 5_000,
|
|
23
|
+
});
|
|
24
|
+
const [bunMajor = 0] = (bunProbe.status === 0 ? bunProbe.stdout.trim() : "").split(".").map(Number);
|
|
25
|
+
const useBun = bunMajor >= 1;
|
|
26
|
+
const bunEntry = fileURLToPath(new URL("./scripts/migrate-storage.js", import.meta.url));
|
|
27
|
+
const nodeEntry = fileURLToPath(new URL("./migrate-storage-node.mjs", import.meta.url));
|
|
28
28
|
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (!useBun && !process.versions.bun) {
|
|
30
|
+
await import("./migrate-storage-node.mjs");
|
|
31
|
+
} else {
|
|
32
|
+
const command = useBun ? (process.versions.bun ? process.execPath : "bun") : "node";
|
|
33
|
+
const entry = useBun ? bunEntry : nodeEntry;
|
|
34
|
+
const runtime = useBun ? "Bun" : "Node.js";
|
|
35
|
+
const result = await new Promise((resolve) => {
|
|
36
|
+
const child = spawn(command, [entry, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
37
|
+
child.once("error", (error) => resolve({ error }));
|
|
38
|
+
child.once("exit", (code, signal) => resolve({ code, signal }));
|
|
39
|
+
});
|
|
32
40
|
|
|
33
|
-
if
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
if (result.error) {
|
|
42
|
+
console.error(`akm-migrate-storage launcher could not start ${runtime}: ${result.error.message}`);
|
|
43
|
+
process.exitCode = 127;
|
|
44
|
+
} else if (result.signal) {
|
|
45
|
+
process.kill(process.pid, result.signal);
|
|
46
|
+
} else {
|
|
47
|
+
process.exitCode = result.code ?? 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
<RegistrationInfo>
|
|
4
4
|
<Description>akm scheduled task: {{TASK_ID}}</Description>
|
|
5
5
|
<URI>{{FOLDER}}{{TASK_ID}}</URI>
|
|
6
|
+
<Source>{{SIGNATURE}}</Source>
|
|
7
|
+
<Documentation>Log target (informational only; schtasks doesn't redirect): {{LOG_PATH}}</Documentation>
|
|
6
8
|
</RegistrationInfo>
|
|
7
9
|
<Triggers>
|
|
8
10
|
{{TRIGGER_XML}}
|
|
@@ -25,5 +27,4 @@
|
|
|
25
27
|
<Arguments>{{ARGS}}</Arguments>
|
|
26
28
|
</Exec>
|
|
27
29
|
</Actions>
|
|
28
|
-
<!-- Log target (informational only; schtasks doesn't redirect): {{LOG_PATH}} -->
|
|
29
30
|
</Task>
|
|
@@ -67,6 +67,9 @@ Options:
|
|
|
67
67
|
Only process refs with recent feedback signal events
|
|
68
68
|
--min-retrieval-count <n>
|
|
69
69
|
Retrieval fallback threshold when no recent feedback exists (default: 5)
|
|
70
|
+
--timeout-ms <n> Wall-clock budget for the entire live run (default: 7200000)
|
|
71
|
+
--skip-if-locked Exit 0 without doing work when another improve run owns
|
|
72
|
+
the whole-run lock; no triage, indexing, events, or sync
|
|
70
73
|
--json-to-stdout Emit the full JSON result on stdout (legacy behaviour).
|
|
71
74
|
(0.8.0+: full result is recorded in the improve_runs table of
|
|
72
75
|
state.db and stdout is empty; use --json-to-stdout for the prior
|
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";
|
|
@@ -93,6 +93,7 @@ import { workflowCommand } from "./commands/workflow-cli.js";
|
|
|
93
93
|
import { bestEffort } from "./core/best-effort.js";
|
|
94
94
|
import { DEFAULT_CONFIG, loadConfig } from "./core/config/config.js";
|
|
95
95
|
import { UsageError } from "./core/errors.js";
|
|
96
|
+
import { assertNoPendingMigrationOperation } from "./core/migration-operation.js";
|
|
96
97
|
import { getCacheDir, getConfigPath, getDbPath } from "./core/paths.js";
|
|
97
98
|
import { plainize } from "./core/tty.js";
|
|
98
99
|
import { info, isQuiet, setQuiet, setVerbose, warn } from "./core/warn.js";
|
|
@@ -541,14 +542,26 @@ export const main = defineCommand({
|
|
|
541
542
|
},
|
|
542
543
|
});
|
|
543
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
|
+
}
|
|
544
554
|
/** Recovery/setup surfaces must remain reachable when config.json is invalid. */
|
|
545
555
|
export function shouldBypassConfigStartup(argv) {
|
|
546
556
|
const args = argv.slice(2);
|
|
547
557
|
if (args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-v"))
|
|
548
558
|
return true;
|
|
549
|
-
const
|
|
559
|
+
const commandIndex = findCittyTopLevelCommandIndex(args, MAIN_TOP_LEVEL_ARGS);
|
|
560
|
+
const command = commandIndex >= 0 ? args[commandIndex] : undefined;
|
|
550
561
|
if (command === "setup" || command === "backup" || command === "migrate")
|
|
551
562
|
return true;
|
|
563
|
+
if (isTaskRunWithId(argv))
|
|
564
|
+
return true;
|
|
552
565
|
if (command !== "config")
|
|
553
566
|
return false;
|
|
554
567
|
const configIndex = args.indexOf("config");
|
|
@@ -584,6 +597,8 @@ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
|
|
|
584
597
|
// rather than letting the raw exception escape with a stack trace.
|
|
585
598
|
try {
|
|
586
599
|
applyEarlyStderrFlags(process.argv);
|
|
600
|
+
if (isTaskRunWithId(process.argv))
|
|
601
|
+
assertNoPendingMigrationOperation();
|
|
587
602
|
const bypassConfig = shouldBypassConfigStartup(process.argv);
|
|
588
603
|
initOutputMode(process.argv, bypassConfig ? (DEFAULT_CONFIG.output ?? {}) : (loadConfig().output ?? {}));
|
|
589
604
|
}
|
|
@@ -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 = "—";
|
|
@@ -146,7 +146,7 @@ export function buildWindowMetrics(db, stateDbPath, since, until, now = () => Da
|
|
|
146
146
|
});
|
|
147
147
|
const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
|
|
148
148
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
149
|
-
const activeRows = taskRows.filter((row) => row.status === "active");
|
|
149
|
+
const activeRows = taskRows.filter((row) => row.status === "active" && row.completed_at === null);
|
|
150
150
|
const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
|
|
151
151
|
const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
|
|
152
152
|
const promptFailures = promptRows.filter((row) => {
|
package/dist/commands/health.js
CHANGED
|
@@ -94,7 +94,7 @@ export function akmHealth(options = {}) {
|
|
|
94
94
|
const taskRows = queryTaskHistory(db, { since });
|
|
95
95
|
const { withLogs: taskRowsWithLogs, backed: existingLogRows } = partitionLogBackedRows(taskRows, logsDb);
|
|
96
96
|
const failedTaskRows = taskRows.filter((row) => row.status === "failed");
|
|
97
|
-
const activeRows = taskRows.filter((row) => row.status === "active");
|
|
97
|
+
const activeRows = taskRows.filter((row) => row.status === "active" && row.completed_at === null);
|
|
98
98
|
const stuckActiveRuns = activeRows.filter((row) => now() - new Date(row.started_at).getTime() > ACTIVE_RUN_WARN_MS).length;
|
|
99
99
|
const promptRows = taskRows.filter((row) => row.target_kind === "prompt");
|
|
100
100
|
const promptFailures = promptRows.filter((row) => {
|
|
@@ -83,7 +83,7 @@ export async function promoteMemoryToKnowledge(ctx) {
|
|
|
83
83
|
const mergeResponse = await chat(ctx.llmConfig, [
|
|
84
84
|
{ role: "system", content: "Return only valid JSON. No prose." },
|
|
85
85
|
{ role: "user", content: mergePrompt },
|
|
86
|
-
]);
|
|
86
|
+
], ctx.signal ? { signal: ctx.signal } : undefined);
|
|
87
87
|
const mergeResult = parseEmbeddedJsonResponse(mergeResponse);
|
|
88
88
|
if (mergeResult?.action === "NOOP") {
|
|
89
89
|
// Existing content is authoritative — no update needed.
|
|
@@ -139,7 +139,11 @@ export async function promoteMemoryToKnowledge(ctx) {
|
|
|
139
139
|
if (ctx.strategy?.processes?.distill?.qualityGate?.enabled ?? true) {
|
|
140
140
|
// D-4 / #390: retrieve top-3 similar lessons for dedup check in judge.
|
|
141
141
|
const similarLessons = await fetchSimilarLessonsFn(resolvedPromotionContent.slice(0, 500), 3);
|
|
142
|
-
const judgeResult = await runLessonQualityJudge(config, resolvedPromotionContent, assetContent ?? "", chat,
|
|
142
|
+
const judgeResult = await runLessonQualityJudge(config, resolvedPromotionContent, assetContent ?? "", chat, {
|
|
143
|
+
...(similarLessons.length > 0 ? { similarLessons } : {}),
|
|
144
|
+
...(ctx.llmConfig ? { llmConfig: ctx.llmConfig } : {}),
|
|
145
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
146
|
+
});
|
|
143
147
|
if (!judgeResult.pass) {
|
|
144
148
|
if (judgeResult.reviewNeeded) {
|
|
145
149
|
// Uncertainty band (2.5–3.5): queue as review_needed instead of rejecting.
|