akm-cli 0.8.0-rc.11 → 0.8.0-rc.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +121 -1
- package/dist/commands/consolidate.js +22 -0
- package/dist/commands/improve-cli.js +19 -0
- package/dist/commands/improve-profiles.js +9 -0
- package/dist/commands/improve.js +265 -98
- package/dist/commands/proposal-drain-policies.js +117 -0
- package/dist/commands/proposal-drain.js +449 -0
- package/dist/commands/tasks.js +14 -0
- package/dist/core/config-schema.js +24 -0
- package/dist/core/write-source.js +9 -0
- package/dist/integrations/agent/runner.js +35 -0
- package/dist/output/shapes/passthrough.js +3 -0
- package/dist/output/text/helpers.js +20 -0
- package/dist/output/text/proposal.js +2 -1
- package/dist/scripts/migrate-storage.js +21 -4
- package/dist/sources/providers/git.js +14 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -90,7 +90,10 @@ function resolveEventSource() {
|
|
|
90
90
|
return "user";
|
|
91
91
|
return undefined;
|
|
92
92
|
}
|
|
93
|
+
import { resolveImproveProfile } from "./commands/improve-profiles";
|
|
93
94
|
import { akmProposalAccept, akmProposalDiff, akmProposalList, akmProposalReject, akmProposalRevert, akmProposalShow, } from "./commands/proposal";
|
|
95
|
+
import { drainProposals } from "./commands/proposal-drain";
|
|
96
|
+
import { resolveDrainPolicy } from "./commands/proposal-drain-policies";
|
|
94
97
|
import { akmPropose } from "./commands/propose";
|
|
95
98
|
import { akmSearch, parseBeliefFilterMode, parseScopeFilterFlags, parseSearchSource } from "./commands/search";
|
|
96
99
|
import { checkForUpdate, performUpgrade } from "./commands/self-update";
|
|
@@ -109,6 +112,7 @@ import { clearLogFile, info, isQuiet, isVerbose, setLogFile, setQuiet, setVerbos
|
|
|
109
112
|
import { closeDatabase, openExistingDatabase } from "./indexer/db";
|
|
110
113
|
import { akmIndex } from "./indexer/indexer";
|
|
111
114
|
import { resolveSourceEntries } from "./indexer/search-source";
|
|
115
|
+
import { resolveTriageJudgmentRunner } from "./integrations/agent/runner";
|
|
112
116
|
import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "./output/cli-hints";
|
|
113
117
|
import { getHyphenatedArg, getHyphenatedBoolean, getOutputMode, hasBooleanFlag, initOutputMode, parseDetailLevel, parseFlagValue, } from "./output/context";
|
|
114
118
|
import { formatEventLine } from "./output/text";
|
|
@@ -3460,13 +3464,128 @@ const proposalShowCommand = defineCommand({
|
|
|
3460
3464
|
});
|
|
3461
3465
|
},
|
|
3462
3466
|
});
|
|
3467
|
+
const proposalDrainCommand = defineCommand({
|
|
3468
|
+
meta: {
|
|
3469
|
+
name: "drain",
|
|
3470
|
+
description: "Drain the standing pending proposal backlog using a deterministic triage policy",
|
|
3471
|
+
},
|
|
3472
|
+
args: {
|
|
3473
|
+
policy: {
|
|
3474
|
+
type: "string",
|
|
3475
|
+
description: "Built-in preset (personal-stash|conservative|manual) or path to a policy file",
|
|
3476
|
+
},
|
|
3477
|
+
"dry-run": {
|
|
3478
|
+
type: "boolean",
|
|
3479
|
+
description: "List what would be accepted/rejected/deferred without writing.",
|
|
3480
|
+
default: false,
|
|
3481
|
+
},
|
|
3482
|
+
yes: {
|
|
3483
|
+
type: "boolean",
|
|
3484
|
+
alias: "y",
|
|
3485
|
+
description: "Skip confirmation prompt (required in non-interactive mode for promotion).",
|
|
3486
|
+
default: false,
|
|
3487
|
+
},
|
|
3488
|
+
"max-accepts": {
|
|
3489
|
+
type: "string",
|
|
3490
|
+
description: "Hard per-run accept ceiling. Accepts beyond this are reported as skippedByCap.",
|
|
3491
|
+
},
|
|
3492
|
+
"max-diff-lines": {
|
|
3493
|
+
type: "string",
|
|
3494
|
+
description: "Defer (never promote) accepts whose proposed content exceeds this many lines.",
|
|
3495
|
+
},
|
|
3496
|
+
"older-than": {
|
|
3497
|
+
type: "string",
|
|
3498
|
+
description: "Only consider proposals created more than this many days ago.",
|
|
3499
|
+
},
|
|
3500
|
+
promote: {
|
|
3501
|
+
type: "boolean",
|
|
3502
|
+
description: "Promote (accept) matching proposals. Default is queue mode (stage only, no writes to assets).",
|
|
3503
|
+
default: false,
|
|
3504
|
+
},
|
|
3505
|
+
judgment: {
|
|
3506
|
+
type: "boolean",
|
|
3507
|
+
description: "Opt into the judgment tier (llm by default; agent/sdk per config) for deferred items. No-op with a logged triage_deferred summary when no runner is configured.",
|
|
3508
|
+
default: false,
|
|
3509
|
+
},
|
|
3510
|
+
profile: {
|
|
3511
|
+
type: "string",
|
|
3512
|
+
description: "Read the triage block (policy, applyMode, ceilings, judgment) from this improve profile.",
|
|
3513
|
+
},
|
|
3514
|
+
},
|
|
3515
|
+
async run({ args }) {
|
|
3516
|
+
await runWithJsonErrors(async () => {
|
|
3517
|
+
const stashDir = resolveStashDir();
|
|
3518
|
+
// Phase 2: read the triage block from the named improve profile. CLI flags
|
|
3519
|
+
// always override config; config supplies defaults for any flag omitted.
|
|
3520
|
+
const triageConfig = args.profile !== undefined
|
|
3521
|
+
? resolveImproveProfile(args.profile, loadConfig()).processes?.triage
|
|
3522
|
+
: undefined;
|
|
3523
|
+
const policy = resolveDrainPolicy(args.policy ?? triageConfig?.policy);
|
|
3524
|
+
const dryRun = args["dry-run"] === true;
|
|
3525
|
+
const applyMode = args.promote === true ? "promote" : (triageConfig?.applyMode ?? "queue");
|
|
3526
|
+
const maxAccepts = parsePositiveIntFlag(args["max-accepts"], "--max-accepts") ??
|
|
3527
|
+
triageConfig?.maxAcceptsPerRun ??
|
|
3528
|
+
25;
|
|
3529
|
+
const maxDiffLines = parsePositiveIntFlag(args["max-diff-lines"], "--max-diff-lines") ??
|
|
3530
|
+
triageConfig?.maxDiffLines;
|
|
3531
|
+
const rawOlderThan = parsePositiveIntFlag(args["older-than"], "--older-than");
|
|
3532
|
+
const olderThanMs = rawOlderThan !== undefined ? rawOlderThan * 86_400_000 : undefined;
|
|
3533
|
+
// Promotion in promote mode is destructive (commits to git, no batch revert).
|
|
3534
|
+
if (applyMode === "promote" && !dryRun) {
|
|
3535
|
+
const { confirmDestructive } = await import("./cli/confirm.js");
|
|
3536
|
+
const confirmed = await confirmDestructive(`Drain and promote matching pending proposals under policy "${policy.name}"? Promotions commit to git and cannot be batch-reverted.`, { yes: args.yes === true });
|
|
3537
|
+
if (!confirmed) {
|
|
3538
|
+
process.stderr.write("Aborted.\n");
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
// `--older-than` is applied here as a pre-filter on excludeIds: ids that
|
|
3543
|
+
// are too fresh are excluded so the engine never touches them.
|
|
3544
|
+
let excludeIds;
|
|
3545
|
+
if (olderThanMs !== undefined) {
|
|
3546
|
+
const { listProposals } = await import("./core/proposals");
|
|
3547
|
+
const now = Date.now();
|
|
3548
|
+
excludeIds = new Set(listProposals(stashDir, { status: "pending" })
|
|
3549
|
+
.filter((proposal) => now - new Date(proposal.createdAt).getTime() < olderThanMs)
|
|
3550
|
+
.map((proposal) => proposal.id));
|
|
3551
|
+
}
|
|
3552
|
+
// Phase 3: resolve the judgment runner when --judgment is set. Default
|
|
3553
|
+
// mode is llm; falls back to defaults.llm when the triage block sets
|
|
3554
|
+
// neither mode nor profile (mirrors resolveValidationRunner). null when
|
|
3555
|
+
// nothing is configured → the engine leaves deferred items unresolved and
|
|
3556
|
+
// emits triage_deferred.
|
|
3557
|
+
const judgment = args.judgment === true ? resolveTriageJudgmentRunner(triageConfig?.judgment, loadConfig()) : null;
|
|
3558
|
+
const result = await drainProposals({
|
|
3559
|
+
stashDir,
|
|
3560
|
+
policy,
|
|
3561
|
+
applyMode,
|
|
3562
|
+
maxAccepts,
|
|
3563
|
+
dryRun,
|
|
3564
|
+
...(maxDiffLines !== undefined ? { maxDiffLines } : {}),
|
|
3565
|
+
...(excludeIds ? { excludeIds } : {}),
|
|
3566
|
+
judgment,
|
|
3567
|
+
});
|
|
3568
|
+
output("proposal-drain", {
|
|
3569
|
+
schemaVersion: 1,
|
|
3570
|
+
ok: true,
|
|
3571
|
+
policy: policy.name,
|
|
3572
|
+
applyMode,
|
|
3573
|
+
dryRun,
|
|
3574
|
+
promoted: result.promoted,
|
|
3575
|
+
rejected: result.rejected,
|
|
3576
|
+
deferred: result.deferred,
|
|
3577
|
+
skippedByCap: result.skippedByCap,
|
|
3578
|
+
});
|
|
3579
|
+
});
|
|
3580
|
+
},
|
|
3581
|
+
});
|
|
3463
3582
|
// ── proposal noun group (#225 / 0.8 CLI stabilization) ────────────────────────
|
|
3464
3583
|
//
|
|
3465
3584
|
// `akm proposal <verb>` is the canonical grammar in 0.8. The flat verbs
|
|
3466
3585
|
// (`proposals`/`accept`/`reject`/`diff`/`revert`) remain as deprecated aliases
|
|
3467
3586
|
// that warn to stderr and delegate to the same command bodies; they are removed
|
|
3468
3587
|
// in 0.9.0. Bare `akm proposal` behaves as `proposal list` (mirrors `akm env`).
|
|
3469
|
-
const PROPOSAL_SUBCOMMAND_SET = new Set(["list", "show", "diff", "accept", "reject", "revert"]);
|
|
3588
|
+
const PROPOSAL_SUBCOMMAND_SET = new Set(["list", "show", "diff", "accept", "reject", "revert", "drain"]);
|
|
3470
3589
|
function emitProposalVerbDeprecation(oldVerb, canonical) {
|
|
3471
3590
|
if (isQuiet())
|
|
3472
3591
|
return;
|
|
@@ -3489,6 +3608,7 @@ const proposalCommand = defineCommand({
|
|
|
3489
3608
|
accept: proposalAcceptCommand,
|
|
3490
3609
|
reject: proposalRejectCommand,
|
|
3491
3610
|
revert: proposalRevertCommand,
|
|
3611
|
+
drain: proposalDrainCommand,
|
|
3492
3612
|
},
|
|
3493
3613
|
run({ args }) {
|
|
3494
3614
|
return runWithJsonErrors(() => {
|
|
@@ -1148,6 +1148,27 @@ export async function akmConsolidate(opts = {}) {
|
|
|
1148
1148
|
emitMergeFailureSkips("merge_no_valid_secondaries");
|
|
1149
1149
|
continue;
|
|
1150
1150
|
}
|
|
1151
|
+
// Pre-flight hot guard — skip the LLM call entirely if any participant
|
|
1152
|
+
// is hot or unparseable. Without this, mixed chunks still send hot merges
|
|
1153
|
+
// to the planner which proposes them; generateMergedContent() is then
|
|
1154
|
+
// called, produces output without `description`, and the skip is
|
|
1155
|
+
// misattributed to merge_missing_description instead of the real cause.
|
|
1156
|
+
const preflightParticipants = [op.primary, ...op.secondaries];
|
|
1157
|
+
const preflightBlocked = preflightParticipants.flatMap((ref) => {
|
|
1158
|
+
const e = memoryByRef.get(ref);
|
|
1159
|
+
if (!e)
|
|
1160
|
+
return [];
|
|
1161
|
+
const verdict = consolidateGuardStatus(e.filePath);
|
|
1162
|
+
if (verdict === "hot" || verdict === "unparseable")
|
|
1163
|
+
return [{ ref, verdict }];
|
|
1164
|
+
return [];
|
|
1165
|
+
});
|
|
1166
|
+
if (preflightBlocked.length > 0) {
|
|
1167
|
+
const detail = preflightBlocked.map((p) => `${p.ref} (${p.verdict})`).join(", ");
|
|
1168
|
+
warnings.push(`Merge: refused for ${op.primary} — ${preflightBlocked.length} participant(s) blocked by hot/unparseable frontmatter guard (pre-flight): ${detail}`);
|
|
1169
|
+
emitMergeFailureSkips("merge_participant_blocked");
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1151
1172
|
let primaryBody = "";
|
|
1152
1173
|
try {
|
|
1153
1174
|
primaryBody = fs.readFileSync(primaryEntry.filePath, "utf8");
|
|
@@ -2003,6 +2024,7 @@ async function generateMergedContent(config, primaryRef, primaryBody, secondaryR
|
|
|
2003
2024
|
"",
|
|
2004
2025
|
"## FRONTMATTER RULES (MANDATORY)",
|
|
2005
2026
|
"- The `updated:` field, if present, MUST be a real ISO date (e.g. `updated: 2026-05-20`). NEVER emit `updated: today`, `updated: now`, or `updated: {today: null}`. If you don't have a real date, OMIT the field — the post-processor will not invent one.",
|
|
2027
|
+
"- REQUIRED: The merged frontmatter MUST include a `description` field with a concise one-sentence summary of the merged asset's content. If neither source has a `description` field, synthesize one from the content.",
|
|
2006
2028
|
requiredFmKeys.length > 0
|
|
2007
2029
|
? `- CRITICAL: The merged frontmatter MUST include ALL of these keys from both source memories: ${requiredFmKeys.join(", ")}. Do NOT drop any of them.`
|
|
2008
2030
|
: null,
|
|
@@ -58,6 +58,14 @@ export const improveCommand = defineCommand({
|
|
|
58
58
|
type: "string",
|
|
59
59
|
description: "Named improve profile from profiles.improve or built-in profiles (default, quick, thorough, memory-focus). Controls which sub-processes run and which asset types are processed.",
|
|
60
60
|
},
|
|
61
|
+
sync: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
description: "Commit (and optionally push) the git-backed primary stash when the run finishes. Use --no-sync to disable. Default: on for git-backed stashes (per profile config).",
|
|
64
|
+
},
|
|
65
|
+
push: {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
description: "Push after the end-of-run sync commit when writable + remote configured. Use --no-push to commit only. Default: per profile config (true).",
|
|
68
|
+
},
|
|
61
69
|
},
|
|
62
70
|
async run({ args }) {
|
|
63
71
|
await runWithJsonErrors(async () => {
|
|
@@ -85,6 +93,16 @@ export const improveCommand = defineCommand({
|
|
|
85
93
|
const minRetrievalCount = parseNonNegativeIntFlag(minRetrievalCountRaw, "--min-retrieval-count");
|
|
86
94
|
const requireFeedbackSignal = getHyphenatedBoolean(args, "require-feedback-signal");
|
|
87
95
|
const profileArg = getStringArg(args, "profile");
|
|
96
|
+
// Only set the keys the user actually passed (citty leaves the flag
|
|
97
|
+
// undefined unless `--sync`/`--no-sync` / `--push`/`--no-push` appears),
|
|
98
|
+
// so the resolved profile `sync` block wins by default.
|
|
99
|
+
const syncFlag = getHyphenatedArg(args, "sync");
|
|
100
|
+
const pushFlag = getHyphenatedArg(args, "push");
|
|
101
|
+
const syncOverride = {};
|
|
102
|
+
if (syncFlag !== undefined)
|
|
103
|
+
syncOverride.enabled = syncFlag;
|
|
104
|
+
if (pushFlag !== undefined)
|
|
105
|
+
syncOverride.push = pushFlag;
|
|
88
106
|
const improveLogFile = path.join(getCacheDir(), "logs", "improve", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
89
107
|
setLogFile(improveLogFile);
|
|
90
108
|
const startedAtMs = Date.now();
|
|
@@ -150,6 +168,7 @@ export const improveCommand = defineCommand({
|
|
|
150
168
|
...(minRetrievalCount !== undefined ? { minRetrievalCount } : {}),
|
|
151
169
|
...(requireFeedbackSignal ? { requireFeedbackSignal } : {}),
|
|
152
170
|
...(profileArg !== undefined ? { profile: profileArg } : {}),
|
|
171
|
+
...(Object.keys(syncOverride).length > 0 ? { sync: syncOverride } : {}),
|
|
153
172
|
consolidateOptions: {
|
|
154
173
|
target: targetArg,
|
|
155
174
|
dryRun,
|
|
@@ -21,7 +21,9 @@ const BUILTIN_PROFILES = {
|
|
|
21
21
|
memoryInference: { enabled: true },
|
|
22
22
|
graphExtraction: { enabled: true },
|
|
23
23
|
// validation: deliberately undefined — third-tier classifier is opt-in.
|
|
24
|
+
triage: { enabled: false, applyMode: "queue", policy: "personal-stash" },
|
|
24
25
|
},
|
|
26
|
+
sync: { enabled: true, push: true },
|
|
25
27
|
},
|
|
26
28
|
quick: {
|
|
27
29
|
description: "Reflect-only pass — no distill, consolidate, memoryInference, or graphExtraction.",
|
|
@@ -31,6 +33,7 @@ const BUILTIN_PROFILES = {
|
|
|
31
33
|
consolidate: { enabled: false },
|
|
32
34
|
memoryInference: { enabled: false },
|
|
33
35
|
graphExtraction: { enabled: false },
|
|
36
|
+
triage: { enabled: false },
|
|
34
37
|
},
|
|
35
38
|
},
|
|
36
39
|
thorough: {
|
|
@@ -44,7 +47,9 @@ const BUILTIN_PROFILES = {
|
|
|
44
47
|
consolidate: { enabled: true, allowedTypes: DEFAULT_ALLOWED_TYPES.consolidate },
|
|
45
48
|
memoryInference: { enabled: true },
|
|
46
49
|
graphExtraction: { enabled: true },
|
|
50
|
+
triage: { enabled: true, applyMode: "queue" },
|
|
47
51
|
},
|
|
52
|
+
sync: { enabled: true, push: true },
|
|
48
53
|
},
|
|
49
54
|
"memory-focus": {
|
|
50
55
|
description: "Memory and lesson improvement only — no distill or consolidate.",
|
|
@@ -54,6 +59,7 @@ const BUILTIN_PROFILES = {
|
|
|
54
59
|
consolidate: { enabled: false },
|
|
55
60
|
memoryInference: { enabled: true },
|
|
56
61
|
graphExtraction: { enabled: false },
|
|
62
|
+
triage: { enabled: false },
|
|
57
63
|
},
|
|
58
64
|
},
|
|
59
65
|
};
|
|
@@ -76,6 +82,9 @@ const IMPROVE_PROCESS_DEFAULTS = {
|
|
|
76
82
|
// and queues durable-insight proposals. Default on — opt out via
|
|
77
83
|
// profiles.improve.default.processes.extract.enabled: false.
|
|
78
84
|
extract: true,
|
|
85
|
+
// proposal-queue triage drains the standing backlog. Opt-in (default off),
|
|
86
|
+
// like `validation` — needs an explicit `enabled: true`.
|
|
87
|
+
triage: false,
|
|
79
88
|
};
|
|
80
89
|
/**
|
|
81
90
|
* Compute the effective enabled-state for a named improve process.
|