akm-cli 0.9.0-beta.10 → 0.9.0-beta.11
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
CHANGED
|
@@ -6,6 +6,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0-beta.11] - 2026-06-15
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`extract.maxSessionsPerRun`** (default 25) — caps the NEW sessions the
|
|
14
|
+
extract pass LLM-processes in a single run so a backlog (e.g. after downtime)
|
|
15
|
+
can't push one run past its scheduled-task timeout. Overflow sessions stay
|
|
16
|
+
unseen and are picked up by later runs, so coverage is preserved. `0` disables.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **Auto-accept validation failures are no longer a blind leak.** When a
|
|
21
|
+
confidence-passing proposal fails promotion validation, the gate now captures
|
|
22
|
+
the reason (the `validateProposal` finding kind, e.g. `validation:description-quality`),
|
|
23
|
+
records it on the proposal (`akm proposal show` explains the rejection), logs
|
|
24
|
+
it, and exposes `failedByReason` on the gate result — so the ~5% leak is
|
|
25
|
+
diagnosable instead of silently warned-and-dropped.
|
|
26
|
+
- **Inflated skip-reason aggregates in `akm health`.** `no_new_signal` /
|
|
27
|
+
`profile_filtered_all_passes` are per-run snapshots of a stable set; the
|
|
28
|
+
window aggregator summed their per-run counts (≈2.7M / 3M). It now uses the
|
|
29
|
+
most recent run's count for these aggregated-snapshot reasons while still
|
|
30
|
+
summing genuine per-occurrence skips.
|
|
31
|
+
|
|
9
32
|
## [0.9.0-beta.10] - 2026-06-15
|
|
10
33
|
|
|
11
34
|
### Added
|
package/dist/commands/health.js
CHANGED
|
@@ -833,19 +833,33 @@ function computeWallTimeStats(durationsMs, byPhase) {
|
|
|
833
833
|
};
|
|
834
834
|
}
|
|
835
835
|
function buildImproveSkipSummary(events) {
|
|
836
|
-
|
|
837
|
-
|
|
836
|
+
// Two kinds of skip events:
|
|
837
|
+
// - Per-occurrence (no `count`): one event per skipped ref → SUM is correct.
|
|
838
|
+
// - Aggregated snapshot (carries `count`): a single per-run event whose count
|
|
839
|
+
// is the number of refs that hit a STABLE, whole-stash condition that run
|
|
840
|
+
// (`no_new_signal`, `profile_filtered_all_passes`). Each run re-counts the
|
|
841
|
+
// same stable set, so summing across the window re-counts it N times (the
|
|
842
|
+
// 2.7M / 3M inflation). For these we keep the MOST RECENT run's count — the
|
|
843
|
+
// current snapshot — matching how memorySummary/profileFilteredRefs are
|
|
844
|
+
// handled. Events arrive in chronological (offset) order, so the last
|
|
845
|
+
// count-bearing event per reason is the latest run's value.
|
|
846
|
+
const summed = {};
|
|
847
|
+
const latestSnapshot = {};
|
|
838
848
|
for (const event of events) {
|
|
839
849
|
const reason = typeof event.metadata?.reason === "string" && event.metadata.reason.trim() ? event.metadata.reason : "unknown";
|
|
840
|
-
// Aggregated skip events (e.g. `no_new_signal`, `profile_filtered_all_passes`)
|
|
841
|
-
// carry a `count` of the refs they represent in a single row instead of one
|
|
842
|
-
// event per ref. Honor that count so the skip histogram reflects the true
|
|
843
|
-
// number of skipped refs; per-ref events without a count contribute 1.
|
|
844
850
|
const rawCount = event.metadata?.count;
|
|
845
|
-
|
|
851
|
+
if (typeof rawCount === "number" && Number.isFinite(rawCount) && rawCount > 0) {
|
|
852
|
+
latestSnapshot[reason] = rawCount; // overwrite → keeps the latest run's snapshot
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
summed[reason] = (summed[reason] ?? 0) + 1;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const skipReasons = { ...summed };
|
|
859
|
+
for (const [reason, count] of Object.entries(latestSnapshot)) {
|
|
846
860
|
skipReasons[reason] = (skipReasons[reason] ?? 0) + count;
|
|
847
|
-
skipped += count;
|
|
848
861
|
}
|
|
862
|
+
const skipped = Object.values(skipReasons).reduce((a, b) => a + b, 0);
|
|
849
863
|
return { skipped, skipReasons };
|
|
850
864
|
}
|
|
851
865
|
function probeStateDbRoundTrip(stateDbPath) {
|
|
@@ -51,6 +51,14 @@ const DEFAULT_MIN_SESSION_DURATION_MINUTES = 5;
|
|
|
51
51
|
* (0 chars, journal files) are safe to skip.
|
|
52
52
|
*/
|
|
53
53
|
const DEFAULT_MIN_CONTENT_CHARS = 10;
|
|
54
|
+
/**
|
|
55
|
+
* Default cap on NEW sessions the extract pass will LLM-process in a single run
|
|
56
|
+
* (`processes.extract.maxSessionsPerRun` overrides; `0` disables). Bounds per-run
|
|
57
|
+
* wall time + token spend so a backlog of accumulated sessions can't run a single
|
|
58
|
+
* pass past its scheduled-task timeout. Overflow sessions stay unseen and are
|
|
59
|
+
* processed by subsequent runs, so coverage is preserved — just spread out.
|
|
60
|
+
*/
|
|
61
|
+
const DEFAULT_MAX_SESSIONS_PER_RUN = 25;
|
|
54
62
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
55
63
|
/**
|
|
56
64
|
* Parse a since-string into an absolute ms-epoch cutoff. Accepts:
|
|
@@ -383,6 +391,12 @@ export async function akmExtract(options) {
|
|
|
383
391
|
// #595/#596 — minimum raw session size; sessions below it skip the LLM call
|
|
384
392
|
// entirely. Set `processes.extract.minContentChars: 0` to disable the gate.
|
|
385
393
|
const minContentChars = typeof extractProcess?.minContentChars === "number" ? extractProcess.minContentChars : DEFAULT_MIN_CONTENT_CHARS;
|
|
394
|
+
// Cap on NEW sessions LLM-processed per run; 0 disables. Absent = default.
|
|
395
|
+
// Bounds per-run wall time / LLM cost so a backlog can't push a run past its
|
|
396
|
+
// task timeout — the overflow stays unseen and is picked up by later runs.
|
|
397
|
+
const maxSessionsPerRun = typeof extractProcess?.maxSessionsPerRun === "number"
|
|
398
|
+
? extractProcess.maxSessionsPerRun
|
|
399
|
+
: DEFAULT_MAX_SESSIONS_PER_RUN;
|
|
386
400
|
// Default discovery window — process config can override the built-in 24h.
|
|
387
401
|
const effectiveSince = options.since ?? extractProcess?.defaultSince;
|
|
388
402
|
// #561 — resolve session-indexing config. Default ON: we only reach this code
|
|
@@ -526,6 +540,13 @@ export async function akmExtract(options) {
|
|
|
526
540
|
skippedCount += 1;
|
|
527
541
|
continue;
|
|
528
542
|
}
|
|
543
|
+
// Per-run cap on LLM-processed sessions (skip-tracked seen sessions above
|
|
544
|
+
// don't count). Single-session / --force modes bypass the cap (explicit
|
|
545
|
+
// intent). Overflow sessions are left unseen for the next run.
|
|
546
|
+
if (!options.sessionId && !options.force && maxSessionsPerRun > 0 && processedCount >= maxSessionsPerRun) {
|
|
547
|
+
topLevelWarnings.push(`Reached maxSessionsPerRun=${maxSessionsPerRun}; ${candidates.length - processedCount - skippedCount} session(s) deferred to a later run.`);
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
529
550
|
try {
|
|
530
551
|
const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, sessionIndexing);
|
|
531
552
|
sessions.push(result);
|
|
@@ -5,6 +5,23 @@ import { loadConfig } from "../../core/config/config.js";
|
|
|
5
5
|
import { appendEvent } from "../../core/events.js";
|
|
6
6
|
import { info, warn } from "../../core/warn.js";
|
|
7
7
|
import { promoteProposal, recordGateDecision } from "../proposal/validators/proposals.js";
|
|
8
|
+
/**
|
|
9
|
+
* Derive a stable, low-cardinality reason bucket from an auto-accept promotion
|
|
10
|
+
* error. `promoteProposal` throws a `validateProposal` report formatted as
|
|
11
|
+
* `[kind] message` lines; we extract the first finding kind. Non-validation
|
|
12
|
+
* throws collapse to `promote-error`.
|
|
13
|
+
*/
|
|
14
|
+
function classifyPromoteFailure(err) {
|
|
15
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16
|
+
const finding = /\[([a-z][a-z0-9-]*)\]/i.exec(message);
|
|
17
|
+
if (finding)
|
|
18
|
+
return `validation:${finding[1]}`;
|
|
19
|
+
if (/not pending/i.test(message))
|
|
20
|
+
return "not-pending";
|
|
21
|
+
if (/unknown asset type/i.test(message))
|
|
22
|
+
return "unknown-type";
|
|
23
|
+
return "promote-error";
|
|
24
|
+
}
|
|
8
25
|
// ---------------------------------------------------------------------------
|
|
9
26
|
// Gate implementation
|
|
10
27
|
// ---------------------------------------------------------------------------
|
|
@@ -18,7 +35,7 @@ import { promoteProposal, recordGateDecision } from "../proposal/validators/prop
|
|
|
18
35
|
* @param promoteFn Injectable override for `promoteProposal` (test seam).
|
|
19
36
|
*/
|
|
20
37
|
export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProposal) {
|
|
21
|
-
const result = { promoted: [], skipped: [], failed: [] };
|
|
38
|
+
const result = { promoted: [], skipped: [], failed: [], failedByReason: {} };
|
|
22
39
|
// --- Guard: gate is disabled or context is incomplete ---
|
|
23
40
|
if (cfg.dryRun || cfg.globalThreshold === undefined || !cfg.stashDir) {
|
|
24
41
|
result.skipped = candidates.map((c) => c.proposalId);
|
|
@@ -84,8 +101,19 @@ export async function runAutoAcceptGate(candidates, cfg, promoteFn = promoteProp
|
|
|
84
101
|
result.promoted.push(proposalId);
|
|
85
102
|
}
|
|
86
103
|
catch (err) {
|
|
87
|
-
|
|
104
|
+
const reason = classifyPromoteFailure(err);
|
|
105
|
+
warn(`[improve] ${cfg.phase} auto-accept failed for ${proposalId} (${reason}): ${err instanceof Error ? err.message : String(err)}`);
|
|
88
106
|
result.failed.push(proposalId);
|
|
107
|
+
result.failedByReason[reason] = (result.failedByReason[reason] ?? 0) + 1;
|
|
108
|
+
// Record WHY on the proposal so `akm proposal show` explains the rejection
|
|
109
|
+
// and the leak is no longer blind. Best-effort.
|
|
110
|
+
stamp(proposalId, {
|
|
111
|
+
outcome: "auto-rejected",
|
|
112
|
+
reason,
|
|
113
|
+
confidence,
|
|
114
|
+
thresholds: { autoAccept: effectiveThreshold },
|
|
115
|
+
gate: gateLabel,
|
|
116
|
+
});
|
|
89
117
|
}
|
|
90
118
|
}
|
|
91
119
|
return result;
|
|
@@ -173,6 +173,9 @@ export const ImproveProcessConfigSchema = z
|
|
|
173
173
|
// disables the guard. Only meaningful on the `extract` process. Default 0
|
|
174
174
|
// (disabled) so existing behaviour is preserved; only opted-in profiles set it.
|
|
175
175
|
minNewSessions: z.number().int().min(0).optional(),
|
|
176
|
+
// Extract process: cap on NEW sessions processed (LLM-called) per run; the
|
|
177
|
+
// rest roll to the next run (still unseen). 0 disables. Absent = default 25.
|
|
178
|
+
maxSessionsPerRun: z.number().int().min(0).optional(),
|
|
176
179
|
// #561 — index agent sessions as a searchable `session` asset (extract
|
|
177
180
|
// process). Absent = on-when-an-LLM-is-available (fail-open when offline).
|
|
178
181
|
indexSessions: z.boolean().optional(),
|
|
@@ -15543,6 +15543,7 @@ var init_config_schema = __esm(() => {
|
|
|
15543
15543
|
importanceWeights: exports_external.record(exports_external.string().min(1), exports_external.number()).optional(),
|
|
15544
15544
|
minPendingCount: exports_external.number().int().min(0).optional(),
|
|
15545
15545
|
minNewSessions: exports_external.number().int().min(0).optional(),
|
|
15546
|
+
maxSessionsPerRun: exports_external.number().int().min(0).optional(),
|
|
15546
15547
|
indexSessions: exports_external.boolean().optional(),
|
|
15547
15548
|
minSessionDuration: exports_external.number().min(0).optional(),
|
|
15548
15549
|
applyMode: exports_external.enum(["queue", "promote"]).optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|