akm-cli 0.8.0-rc.10 → 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 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.