akm-cli 0.9.0-beta.52 → 0.9.0-beta.53
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/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/cli.js +0 -7
- package/dist/commands/env/env-cli.js +3 -2
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/health/checks.js +28 -15
- package/dist/commands/health.js +68 -1
- package/dist/commands/improve/collapse-detector.js +419 -0
- package/dist/commands/improve/consolidate.js +72 -54
- package/dist/commands/improve/distill.js +79 -13
- package/dist/commands/improve/extract.js +13 -6
- package/dist/commands/improve/homeostatic.js +109 -79
- package/dist/commands/improve/improve-cli.js +67 -1
- package/dist/commands/improve/improve.js +10 -0
- package/dist/commands/improve/loop-stages.js +39 -1
- package/dist/commands/improve/outcome-loop.js +15 -3
- package/dist/commands/improve/preparation.js +17 -8
- package/dist/commands/improve/salience.js +49 -32
- package/dist/commands/read/curate.js +5 -9
- package/dist/commands/read/knowledge.js +4 -0
- package/dist/commands/read/search.js +5 -2
- package/dist/commands/read/show.js +3 -3
- package/dist/core/asset/asset-spec.js +3 -2
- package/dist/core/config/config-schema.js +39 -17
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/state/migrations.js +56 -0
- package/dist/core/state-db.js +146 -19
- package/dist/indexer/ensure-index.js +33 -90
- package/dist/indexer/index-writer-lock.js +0 -11
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/search/db-search.js +29 -1
- package/dist/indexer/search/ranking-contributors.js +33 -1
- package/dist/indexer/search/ranking.js +66 -0
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/llm/feature-gate.js +6 -2
- package/dist/output/renderers.js +8 -13
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/scripts/migrate-storage.js +152 -33
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +41 -18
- package/dist/storage/repositories/index-db.js +10 -1
- package/package.json +2 -4
|
@@ -58,7 +58,7 @@ akm show knowledge:my-doc # Show content (local or remote)
|
|
|
58
58
|
| knowledge | `content` (with view modes: `full`, `toc`, `frontmatter`, `section`, `lines`) |
|
|
59
59
|
| workflow | `workflowTitle`, `workflowParameters`, `steps` |
|
|
60
60
|
| memory | `content` (recalled context) |
|
|
61
|
-
| env | `keys
|
|
61
|
+
| env | `keys` (key names only — values and comment text never returned) |
|
|
62
62
|
| secret | `name` only (the whole file is the value — never returned) |
|
|
63
63
|
| wiki | `content` (same view modes as knowledge). For any wiki task, run `akm wiki list`. To ingest sources, `akm wiki ingest <name>` dispatches the configured agent (defaults.agent or `--profile`) to execute the ingest workflow. |
|
|
64
64
|
|
|
@@ -122,15 +122,16 @@ search results. No `--llm` anywhere — akm never reasons about page content.
|
|
|
122
122
|
## Env files
|
|
123
123
|
|
|
124
124
|
A group of related CONFIGURATION for an app/service in one `.env` file at
|
|
125
|
-
`<stashDir>/env/<name>.env`, sourced/injected wholesale. Key names
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
`<stashDir>/env/<name>.env`, sourced/injected wholesale. Key names are
|
|
126
|
+
discoverable; values and comment text stay on disk and never reach stdout or
|
|
127
|
+
the index (comments can contain commented-out credentials). akm does not edit
|
|
128
|
+
entries — you edit the file with your own editor and akm loads it.
|
|
128
129
|
|
|
129
130
|
```sh
|
|
130
131
|
akm env create prod # Create an empty env file
|
|
131
132
|
akm env create prod --from-file ./.env # Ingest an existing .env
|
|
132
133
|
akm env list # List all env files across stashes with key names
|
|
133
|
-
akm show env:prod # Inspect key names
|
|
134
|
+
akm show env:prod # Inspect key names (never values or comments)
|
|
134
135
|
akm env run env:prod -- ./deploy.sh # Run a command with the whole .env injected (the safe path)
|
|
135
136
|
akm env run env:prod -- $SHELL # Open an interactive shell with values injected
|
|
136
137
|
akm env export env:prod --out ./env.sh # Write a sourceable script to a file (mode 0600)
|
package/dist/cli.js
CHANGED
|
@@ -544,13 +544,6 @@ const EXIT_HEALTH_WARN = EXIT_CODES.HEALTH_WARN;
|
|
|
544
544
|
// The wrapper sets `AKM_NODE_ENTRY=1` to opt into the startup block. The test
|
|
545
545
|
// harness never sets it, so importing cli.ts under Bun stays inert as before.
|
|
546
546
|
if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
|
|
547
|
-
// Mark that this process is the real akm CLI: its `process.argv[1]` is the
|
|
548
|
-
// akm entrypoint, so the background auto-reindex may safely re-invoke it as a
|
|
549
|
-
// detached child. Hosts that merely import this module (the in-process test
|
|
550
|
-
// harness, library embeddings) never reach this block, so they fall back to
|
|
551
|
-
// an inline reindex instead of spawning the wrong program. See
|
|
552
|
-
// `ensureIndex` in src/indexer/ensure-index.ts.
|
|
553
|
-
process.env.AKM_CLI_ENTRY = "1";
|
|
554
547
|
// citty reads process.argv directly and does not accept a custom argv array,
|
|
555
548
|
// so we must replace process.argv with the normalized version before runMain.
|
|
556
549
|
process.argv = normalizeShowArgv(process.argv);
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* copy.
|
|
11
11
|
*
|
|
12
12
|
* `akm env` manages whole `.env` files under each stash's env/ directory.
|
|
13
|
-
* Values are NEVER written to stdout or structured output —
|
|
14
|
-
*
|
|
13
|
+
* Values and comment text are NEVER written to stdout or structured output —
|
|
14
|
+
* only key NAMES are surfaced (comments routinely contain commented-out
|
|
15
|
+
* credentials). akm does not manage individual entries;
|
|
15
16
|
* you edit the `.env` file yourself and akm loads it. Replaced the deprecated
|
|
16
17
|
* `vault` type (removed in 0.9.0).
|
|
17
18
|
*/
|
package/dist/commands/env/env.js
CHANGED
|
@@ -18,10 +18,13 @@
|
|
|
18
18
|
* round-trip through `dotenv`; the shell-load safety guarantee still lives on
|
|
19
19
|
* the READ path (see `buildShellExportScript` + `akm env export`).
|
|
20
20
|
*
|
|
21
|
-
* Invariant: env
|
|
22
|
-
* indexer, the `akm show` renderer, or any
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* Invariant: nothing from an env file except key NAMES may be written to
|
|
22
|
+
* stdout, returned through the indexer, the `akm show` renderer, or any
|
|
23
|
+
* structured output channel. Key NAMES are surfaced for discoverability;
|
|
24
|
+
* comment text is NOT — real .env files routinely carry commented-out
|
|
25
|
+
* `KEY=value` lines and free-text notes containing live credentials, so
|
|
26
|
+
* comments are treated exactly like values. The supported value-load paths
|
|
27
|
+
* are:
|
|
25
28
|
*
|
|
26
29
|
* - `akm env run <ref> -- <command>` — values injected into the child
|
|
27
30
|
* process env (never via a shell), see `injectIntoEnv` / `loadEnv`. This is
|
|
@@ -71,75 +74,19 @@ function scanKeys(text) {
|
|
|
71
74
|
return keys;
|
|
72
75
|
}
|
|
73
76
|
/**
|
|
74
|
-
*
|
|
75
|
-
* any leading whitespace stripped). Inline/trailing `#` after an assignment is
|
|
76
|
-
* never extracted.
|
|
77
|
-
*/
|
|
78
|
-
function scanComments(text) {
|
|
79
|
-
const comments = [];
|
|
80
|
-
for (const line of text.split(/\r?\n/)) {
|
|
81
|
-
const trimmed = line.trimStart();
|
|
82
|
-
if (trimmed.startsWith("#")) {
|
|
83
|
-
comments.push(trimmed.slice(1).trimStart());
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return comments;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Read and return ONLY non-secret metadata (keys + start-of-line comments).
|
|
77
|
+
* Read and return ONLY non-secret metadata: key names.
|
|
90
78
|
*
|
|
91
79
|
* The function reads the whole file into memory (same as any dotenv parser)
|
|
92
|
-
* but deliberately does not parse values — the LHS-only regex
|
|
93
|
-
*
|
|
94
|
-
*
|
|
80
|
+
* but deliberately does not parse values — the LHS-only regex scanner above
|
|
81
|
+
* ensures no value content is retained or returned. Comment text is never
|
|
82
|
+
* returned either: comments routinely contain commented-out `KEY=value`
|
|
83
|
+
* credentials and free-text secrets, so they never leave this function.
|
|
95
84
|
*/
|
|
96
85
|
export function listKeys(envPath) {
|
|
97
86
|
if (!fs.existsSync(envPath))
|
|
98
|
-
return { keys: []
|
|
99
|
-
const text = fs.readFileSync(envPath, "utf8");
|
|
100
|
-
return { keys: scanKeys(text), comments: scanComments(text) };
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Return structured `entries` pairing each key with the nearest preceding
|
|
104
|
-
* comment line (if any). This is an easier-to-consume shape than the parallel
|
|
105
|
-
* `keys[]` + `comments[]` of `listKeys` (QA #35).
|
|
106
|
-
*
|
|
107
|
-
* Values are never included — the same privacy guarantee as `listKeys`.
|
|
108
|
-
*/
|
|
109
|
-
export function listEntries(envPath) {
|
|
110
|
-
if (!fs.existsSync(envPath))
|
|
111
|
-
return [];
|
|
87
|
+
return { keys: [] };
|
|
112
88
|
const text = fs.readFileSync(envPath, "utf8");
|
|
113
|
-
|
|
114
|
-
const seen = new Set();
|
|
115
|
-
const entries = [];
|
|
116
|
-
let pendingComment;
|
|
117
|
-
for (const line of lines) {
|
|
118
|
-
const trimmed = line.trimStart();
|
|
119
|
-
if (trimmed.startsWith("#")) {
|
|
120
|
-
// Capture the most recent comment before a key
|
|
121
|
-
pendingComment = trimmed.slice(1).trimStart() || undefined;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
const m = line.match(ASSIGN_RE);
|
|
125
|
-
if (m) {
|
|
126
|
-
const key = m[1];
|
|
127
|
-
if (!seen.has(key)) {
|
|
128
|
-
seen.add(key);
|
|
129
|
-
const entry = { key };
|
|
130
|
-
if (pendingComment)
|
|
131
|
-
entry.comment = pendingComment;
|
|
132
|
-
entries.push(entry);
|
|
133
|
-
}
|
|
134
|
-
pendingComment = undefined;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// Any non-comment, non-assignment line (including blank lines)
|
|
138
|
-
// breaks "nearest preceding comment line" association.
|
|
139
|
-
pendingComment = undefined;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return entries;
|
|
89
|
+
return { keys: scanKeys(text) };
|
|
143
90
|
}
|
|
144
91
|
/**
|
|
145
92
|
* Read all KEY=value pairs from an env file. Intended for programmatic callers
|
|
@@ -191,21 +191,34 @@ export const HEALTH_CHECKS = [
|
|
|
191
191
|
{
|
|
192
192
|
name: "semantic-search-runtime",
|
|
193
193
|
channel: "advisory",
|
|
194
|
-
run: (ctx) =>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
194
|
+
run: (ctx) => {
|
|
195
|
+
const blocked = ctx.semanticStatus?.status === "blocked";
|
|
196
|
+
// The generic "status: blocked" line is not actionable when the real
|
|
197
|
+
// problem is a configured remote embedding endpoint that is down while
|
|
198
|
+
// semanticSearchMode leaves semantic search enabled — every index run
|
|
199
|
+
// burns time failing against it and searches silently degrade to
|
|
200
|
+
// keyword-only. Name the endpoint and the two ways out.
|
|
201
|
+
const remoteReason = ctx.semanticStatus?.reason?.startsWith("remote-") === true;
|
|
202
|
+
const endpointAdvisory = blocked && remoteReason && ctx.embeddingEndpoint
|
|
203
|
+
? `Configured embedding endpoint ${ctx.embeddingEndpoint} is failing ` +
|
|
204
|
+
`(${ctx.semanticStatus?.reason}${ctx.semanticStatus?.message ? `: ${ctx.semanticStatus.message}` : ""}) ` +
|
|
205
|
+
`while semanticSearchMode is "${ctx.semanticSearchMode ?? "auto"}". Searches fall back to keyword-only. ` +
|
|
206
|
+
`Restore the endpoint, or set semanticSearchMode to "off" (or remove embedding.endpoint to use the local model).`
|
|
207
|
+
: undefined;
|
|
208
|
+
return {
|
|
209
|
+
name: "semantic-search-runtime",
|
|
210
|
+
kind: "deterministic",
|
|
211
|
+
status: !ctx.semanticStatus || !blocked ? "pass" : "warn",
|
|
212
|
+
confidence: "medium",
|
|
213
|
+
message: endpointAdvisory ??
|
|
214
|
+
(ctx.semanticStatus
|
|
215
|
+
? `Semantic search status: ${ctx.semanticStatus.status}`
|
|
216
|
+
: "No semantic-search runtime status recorded yet."),
|
|
217
|
+
evidence: ctx.semanticStatus
|
|
218
|
+
? { ...ctx.semanticStatus, ...(ctx.embeddingEndpoint ? { embeddingEndpoint: ctx.embeddingEndpoint } : {}) }
|
|
219
|
+
: undefined,
|
|
220
|
+
};
|
|
221
|
+
},
|
|
209
222
|
},
|
|
210
223
|
{
|
|
211
224
|
// session-log-failures: demoted to informational — the ERROR_PATTERNS regex
|
package/dist/commands/health.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import fs from "node:fs";
|
|
5
|
+
import { loadConfig } from "../core/config/config.js";
|
|
5
6
|
import { ConfigError, UsageError } from "../core/errors.js";
|
|
6
7
|
import { appendEvent, readEvents } from "../core/events.js";
|
|
7
8
|
import { buildTaskRunId, getLoggedRunIds, openLogsDatabase } from "../core/logs-db.js";
|
|
8
9
|
import { getStateDbPathInDataDir } from "../core/paths.js";
|
|
9
|
-
import { listExistingTableNames, listProposalGateDecisions, listStateProposals, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
|
|
10
|
+
import { getLatestCycleMetrics, listExistingTableNames, listProposalGateDecisions, listStateProposals, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
|
|
10
11
|
import { parseSinceToIso } from "../core/time.js";
|
|
11
12
|
import { readSemanticStatus } from "../indexer/search/semantic-status.js";
|
|
12
13
|
import { getExecutionLogCandidates } from "../integrations/session-logs/index.js";
|
|
@@ -1411,6 +1412,18 @@ export function akmHealth(options = {}) {
|
|
|
1411
1412
|
const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
|
|
1412
1413
|
const agentFailureRate = promptRows.length === 0 ? 0 : promptFailures.length / promptRows.length;
|
|
1413
1414
|
const semanticStatus = readSemanticStatus();
|
|
1415
|
+
// For the embedding-endpoint advisory. Best-effort: an unloadable config
|
|
1416
|
+
// leaves both undefined and the check falls back to its generic message.
|
|
1417
|
+
let semanticSearchMode;
|
|
1418
|
+
let embeddingEndpoint;
|
|
1419
|
+
try {
|
|
1420
|
+
const config = loadConfig();
|
|
1421
|
+
semanticSearchMode = config.semanticSearchMode;
|
|
1422
|
+
embeddingEndpoint = config.embedding?.endpoint;
|
|
1423
|
+
}
|
|
1424
|
+
catch {
|
|
1425
|
+
// fall through with undefined
|
|
1426
|
+
}
|
|
1414
1427
|
const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
|
|
1415
1428
|
const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
|
|
1416
1429
|
const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
|
|
@@ -1453,6 +1466,58 @@ export function akmHealth(options = {}) {
|
|
|
1453
1466
|
"The 0.10+ rich in-session outcome signal is no longer deferrable. See plan §WS-2.",
|
|
1454
1467
|
});
|
|
1455
1468
|
}
|
|
1469
|
+
// R5 collapse/churn detector: surface any collapse_detector_alert events
|
|
1470
|
+
// in the health window, plus the latest cycle row's headline numbers so
|
|
1471
|
+
// the operator can act without opening the DB. `unknown` when the detector
|
|
1472
|
+
// has never produced a cycle row (no consolidate/recombine work yet).
|
|
1473
|
+
try {
|
|
1474
|
+
// Reuse the already-open state.db handle (readEvents supports a
|
|
1475
|
+
// borrowed connection) — no extra open/migrate/close per health call.
|
|
1476
|
+
const collapseAlertEvents = readEvents({ since, type: "collapse_detector_alert" }, { dbPath: stateDbPath, db }).events;
|
|
1477
|
+
const latestCycle = getLatestCycleMetrics(db);
|
|
1478
|
+
const cycleSummary = latestCycle
|
|
1479
|
+
? `Latest cycle (${latestCycle.ts}, ${latestCycle.pass}): mean canary recall ${latestCycle.mean_recall.toFixed(3)}, ` +
|
|
1480
|
+
`distinct-content ratio ${latestCycle.distinct_content_ratio.toFixed(3)}, ` +
|
|
1481
|
+
`${latestCycle.accepted_actions} accepted action(s).`
|
|
1482
|
+
: "";
|
|
1483
|
+
if (collapseAlertEvents.length > 0) {
|
|
1484
|
+
const kinds = [...new Set(collapseAlertEvents.map((e) => String(e.metadata?.kind ?? "unknown")))];
|
|
1485
|
+
const collapseKinds = kinds.filter((k) => k.startsWith("collapse"));
|
|
1486
|
+
advisories.push({
|
|
1487
|
+
name: "collapse-churn-detector",
|
|
1488
|
+
status: "warn",
|
|
1489
|
+
kind: "deterministic",
|
|
1490
|
+
// Collapse kinds are measured, not inferred; churn/merge-floor
|
|
1491
|
+
// volume thresholds are still being tuned (design doc §7).
|
|
1492
|
+
confidence: collapseKinds.length > 0 ? "high" : "medium",
|
|
1493
|
+
message: `R5 detector fired ${collapseAlertEvents.length} alert(s) in window (kinds: ${kinds.join(", ")}). ` +
|
|
1494
|
+
`${cycleSummary} See docs/design/improve-collapse-churn-detector-design.md §6.3 runbook queries.`,
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
else if (latestCycle) {
|
|
1498
|
+
advisories.push({
|
|
1499
|
+
name: "collapse-churn-detector",
|
|
1500
|
+
status: "pass",
|
|
1501
|
+
kind: "deterministic",
|
|
1502
|
+
confidence: "high",
|
|
1503
|
+
message: `No collapse/churn alerts in window. ${cycleSummary}`,
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
advisories.push({
|
|
1508
|
+
name: "collapse-churn-detector",
|
|
1509
|
+
status: "unknown",
|
|
1510
|
+
kind: "deterministic",
|
|
1511
|
+
confidence: "high",
|
|
1512
|
+
message: "No detector cycle rows yet — the collapse/churn detector runs only on improve cycles " +
|
|
1513
|
+
"where consolidate/recombine did work (synthesis lanes may be idle).",
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
catch {
|
|
1518
|
+
// Table may predate migration 016 in odd mixed-version setups — advisory
|
|
1519
|
+
// is best-effort and must never fail the health command.
|
|
1520
|
+
}
|
|
1456
1521
|
let sessionLogEntries = [];
|
|
1457
1522
|
try {
|
|
1458
1523
|
const sinceDays = Math.max(0, Math.ceil((now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
|
|
@@ -1482,6 +1547,8 @@ export function akmHealth(options = {}) {
|
|
|
1482
1547
|
logBackingRate,
|
|
1483
1548
|
stuckActiveRuns,
|
|
1484
1549
|
semanticStatus,
|
|
1550
|
+
semanticSearchMode,
|
|
1551
|
+
embeddingEndpoint,
|
|
1485
1552
|
sessionLogEntries,
|
|
1486
1553
|
sessionExtraction: improveSummary.sessionExtraction,
|
|
1487
1554
|
autoAccept: improveSummary.autoAccept,
|