akm-cli 0.9.0-beta.31 → 0.9.0-beta.33

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,48 @@ 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.33] — 2026-06-21
10
+
11
+ ### Fixed
12
+
13
+ - **`akm extract` decoupled from the improve-stage toggle.** `processes.extract.enabled`
14
+ now gates extract only as a STAGE of `akm improve` (the active improve profile, per
15
+ #593/#594); an explicit `akm extract` command always runs. Previously dropping extract
16
+ from the daily improve profile silently disabled the standalone command (and its LLM
17
+ calls, via the shared `session_extraction` feature gate).
18
+ - **`extract --session-id` now respects the content-hash ledger; `--force` overrides.**
19
+ Explicit single-session extraction previously bypassed the #602 already-extracted skip
20
+ unconditionally — re-paying the LLM on every call and risking double-extraction against
21
+ the cron. Now a targeted `extract --session-id <id>` is idempotent (skips an unchanged,
22
+ already-extracted session with zero LLM calls) and only `--force` re-extracts. This
23
+ makes a session-end hook firing `extract --session-id <id>` precise AND idempotent.
24
+
25
+ ## [0.9.0-beta.32] — 2026-06-21
26
+
27
+ ### Added
28
+
29
+ - **Recombine acceptance path — confirmed lessons now auto-accept.** Recombine
30
+ hypotheses that reach the confirmation threshold (promoted to `type: lesson`,
31
+ #625/#633) now flow to ACCEPTED by reusing the existing drain mechanism instead
32
+ of piling up pending forever: the `personal-stash` drain policy gains a
33
+ `{ generator: "recombine", requireType: "lesson", maxDiffLines: 200 }` rule, via
34
+ a new optional `requireType` frontmatter filter on `DrainAcceptRule`. Only
35
+ confirmed `type: lesson` proposals auto-accept; unconfirmed `type: hypothesis`
36
+ proposals stay pending; the existing proposal quality gate still applies.
37
+ - **`processes.reflect.lowValueFilter` (opt-in, default OFF)** — deterministic
38
+ semantic value-floor that defers trivial reflect rewrites (#639A).
39
+ - **`processes.extract.triage.proceduralAwareFloor` (opt-in, default OFF)** —
40
+ triage floor requiring markers/edits so real lessons always pass (#641).
41
+
42
+ ### Fixed
43
+
44
+ - **Select-time proactive cooldown leak.** `selectProactiveMaintenanceRefs` plans
45
+ the due set BEFORE acquiring `reflect-distill.lock`, so overlapping/back-to-back
46
+ improve runs reused stale due-state and re-reflected the same asset repeatedly
47
+ (observed up to ~16× in a day). The orchestrator now re-applies the dueDays gate
48
+ with freshly-read timestamp maps INSIDE the lock (`filterProactiveDue`), dropping
49
+ refs a concurrent run already reflected.
50
+
9
51
  ## [0.9.0-beta.31] — 2026-06-20
10
52
 
11
53
  ### Changed
@@ -37,7 +37,7 @@ import { getAvailableHarnesses } from "../../integrations/session-logs/index.js"
37
37
  import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
38
38
  import { chatCompletion } from "../../llm/client.js";
39
39
  import { embed } from "../../llm/embedder.js";
40
- import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
40
+ import { tryLlmFeature } from "../../llm/feature-gate.js";
41
41
  import { sha256Hex } from "../../runtime.js";
42
42
  import { createProposal, isProposalSkipped } from "../proposal/validators/proposals.js";
43
43
  import { buildExtractPrompt, EXTRACT_JSON_SCHEMA, parseExtractPayload } from "./extract-prompt.js";
@@ -173,13 +173,14 @@ export function hashSessionContent(data) {
173
173
  async function processSession(harness, sessionRef, stashDir, config, llmConfig, chat, ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars,
174
174
  // #626 — pre-LLM heuristic triage gate. Default-off (enabled:false) takes the
175
175
  // exact pre-change path (no scorer call, no new skipReason).
176
+ // #641 — proceduralAwareFloor is opt-in, DEFAULT OFF.
176
177
  triage, sessionIndexing, schemaSimilarityCtx,
177
178
  // #602 — already-extracted skip moved INSIDE processSession: the content hash
178
179
  // can only be computed after readSession, so the skip decision lives here. The
179
180
  // prior row + bypass flags are threaded in from the caller. Skipping here still
180
181
  // costs ZERO LLM calls (the expensive resource #602 protects); only the cheap
181
182
  // file read is incurred.
182
- prior, force, singleSession) {
183
+ prior, force) {
183
184
  const warnings = [];
184
185
  let data;
185
186
  try {
@@ -200,10 +201,12 @@ prior, force, singleSession) {
200
201
  // #602 — content-hash skip. Computed on the RAW event stream immediately after
201
202
  // a successful read, BEFORE the pre-filter / minContentChars / triage gates, so
202
203
  // an unchanged session never reaches the LLM. Hash-based ⇒ clock-independent
203
- // (immune to the Jun 11-12 timestamp double-extract/over-throttle bug). --force
204
- // and single-session (explicit sessionId) modes bypass the skip entirely.
204
+ // (immune to the Jun 11-12 timestamp double-extract/over-throttle bug). The skip
205
+ // applies UNIFORMLY including explicit `--session-id` targeting (so a
206
+ // session-end hook firing `extract --session-id <id>` is idempotent). ONLY
207
+ // `--force` overrides it to re-extract a previously-extracted session.
205
208
  const contentHash = hashSessionContent(data);
206
- if (!force && !singleSession && shouldSkipAlreadyExtractedSession(prior, contentHash)) {
209
+ if (!force && shouldSkipAlreadyExtractedSession(prior, contentHash)) {
207
210
  return {
208
211
  sessionId: sessionRef.sessionId,
209
212
  harness: harness.name,
@@ -251,7 +254,9 @@ prior, force, singleSession) {
251
254
  // the configured threshold we triage it out: no chat() call, no session asset,
252
255
  // no proposals. Pure-heuristic — zero added LLM cost. Default-off → skipped.
253
256
  if (triage.enabled) {
254
- const t = scoreSessionTriage(data, triage.minScore);
257
+ const t = scoreSessionTriage(data, triage.minScore, {
258
+ proceduralAwareFloor: triage.proceduralAwareFloor,
259
+ });
255
260
  if (!t.pass) {
256
261
  return {
257
262
  sessionId: sessionRef.sessionId,
@@ -467,9 +472,13 @@ export async function akmExtract(options) {
467
472
  const extractProcess = activeProfile
468
473
  ? activeProfile.processes?.extract
469
474
  : config.profiles?.improve?.default?.processes?.extract;
470
- const extractEnabled = activeProfile
471
- ? resolveProcessEnabled("extract", activeProfile)
472
- : isLlmFeatureEnabled(config, "session_extraction");
475
+ // The `extract.enabled` process toggle gates extract as a STAGE of `akm improve`
476
+ // (the activeProfile path) — consistent with #593/#594 where the active profile,
477
+ // not `default`, is the source of truth. An EXPLICIT `akm extract` invocation
478
+ // (no activeProfile) is a direct user/cron action and always runs; gating it on
479
+ // the default improve profile's stage toggle was a footgun — dropping extract
480
+ // from the daily improve profile would silently disable the standalone command.
481
+ const extractEnabled = activeProfile ? resolveProcessEnabled("extract", activeProfile) : true;
473
482
  // Feature-gate early so we get a clean "skipped because disabled" envelope.
474
483
  if (!extractEnabled) {
475
484
  return {
@@ -682,7 +691,7 @@ export async function akmExtract(options) {
682
691
  break;
683
692
  }
684
693
  try {
685
- const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, schemaSimilarityCtx, prior, options.force === true, typeof options.sessionId === "string" && options.sessionId.length > 0);
694
+ const result = await processSession(harness, summary, stashDir, config, llmConfig, chat, options.ctx, sourceRun, dryRun, timeoutMs, maxTotalChars, minContentChars, triage, sessionIndexing, schemaSimilarityCtx, prior, options.force === true);
686
695
  sessions.push(result);
687
696
  // #626 — triage aggregation. A session reached the triage gate only when it
688
697
  // was NOT already preempted by an earlier skip (read_failed / too_short /
@@ -50,7 +50,7 @@ import { isProfileFilteredForAllPasses, resolveImproveProfile, resolveProcessEna
50
50
  import { detectAndWriteContradictions } from "./memory/memory-contradiction-detect.js";
51
51
  import { analyzeMemoryCleanup, applyMemoryCleanup } from "./memory/memory-improve.js";
52
52
  import { computeProxyAdequacy, getAllAssetOutcomes, getOutcomeScoresByRef, outcomeScoreToSalience, updateAssetOutcome, } from "./outcome-loop.js";
53
- import { DEFAULT_DUE_DAYS, DEFAULT_MAX_PER_RUN, selectProactiveMaintenanceRefs } from "./proactive-maintenance.js";
53
+ import { DEFAULT_DUE_DAYS, DEFAULT_MAX_PER_RUN, filterProactiveDue, selectProactiveMaintenanceRefs, } from "./proactive-maintenance.js";
54
54
  import { akmProcedural } from "./procedural.js";
55
55
  import { akmRecombine } from "./recombine.js";
56
56
  import { akmReflect } from "./reflect.js";
@@ -1105,13 +1105,32 @@ export async function akmImprove(options = {}) {
1105
1105
  // both write proposals to state.db). Released immediately after.
1106
1106
  const reflectDistillLPath = processLockPath(lockBaseDir, "reflectDistill");
1107
1107
  const reflectDistillAcquired = tryAcquireProcessLock(reflectDistillLPath, PROCESS_LOCK_DEFS.reflectDistill.staleAfterMs, options.skipIfLocked, "reflect-distill") === "acquired";
1108
+ // Post-lock cooldown re-filter for proactive refs (#SELECT-TIME-LEAK).
1109
+ // Planning built `lastReflectProposalTs` BEFORE acquiring this lock, so a
1110
+ // concurrent run's `reflect_invoked` writes are invisible to it. Now that
1111
+ // we hold the lock, re-read fresh timestamp maps for the proactive subset
1112
+ // and drop any ref whose cooldown has been consumed by the concurrent run.
1113
+ const proactiveLoopRefs = preparation.loopRefs.filter((r) => r.eligibilitySource === "proactive");
1114
+ let postLockLoopRefs = preparation.loopRefs;
1115
+ if (proactiveLoopRefs.length > 0) {
1116
+ const proactiveRefStrs = proactiveLoopRefs.map((r) => r.ref);
1117
+ const freshReflectTs = buildLatestProposalTsMap(proactiveRefStrs, "reflect");
1118
+ const freshDistillTs = buildLatestProposalTsMap(proactiveRefStrs, "distill");
1119
+ const pmDueDays = improveProfile.processes?.proactiveMaintenance?.dueDays ?? DEFAULT_DUE_DAYS;
1120
+ const stillDue = new Set(filterProactiveDue(proactiveLoopRefs, freshReflectTs, freshDistillTs, pmDueDays, Date.now()).map((r) => r.ref));
1121
+ const dropped = proactiveLoopRefs.filter((r) => !stillDue.has(r.ref));
1122
+ if (dropped.length > 0) {
1123
+ info(`[improve] post-lock cooldown re-filter: dropped ${dropped.length} proactive ref(s) claimed by concurrent run (${dropped.map((r) => r.ref).join(", ")})`);
1124
+ postLockLoopRefs = preparation.loopRefs.filter((r) => r.eligibilitySource !== "proactive" || stillDue.has(r.ref));
1125
+ }
1126
+ }
1108
1127
  const loopResult = await runImproveLoopStageImpl({
1109
1128
  scope,
1110
1129
  options,
1111
1130
  primaryStashDir,
1112
1131
  reflectFn,
1113
1132
  distillFn,
1114
- loopRefs: preparation.loopRefs,
1133
+ loopRefs: postLockLoopRefs,
1115
1134
  actions: preparation.actions,
1116
1135
  signalBearingSet: preparation.signalBearingSet,
1117
1136
  distillCooledRefs: preparation.distillCooledRefs,
@@ -85,3 +85,31 @@ export function selectProactiveMaintenanceRefs(params) {
85
85
  const selected = ranked.slice(0, Math.max(0, maxPerRun)).map((s) => s.ref);
86
86
  return { selected, dueTotal, neverReflected, scored };
87
87
  }
88
+ /**
89
+ * Post-lock re-filter for proactive refs.
90
+ *
91
+ * Called INSIDE the reflect-distill.lock window with freshly-read timestamp
92
+ * maps so that any `reflect_invoked` events committed by a concurrent run
93
+ * while this run was waiting for the lock are visible. Refs that became
94
+ * non-due (staleDays ≤ dueDays) since planning time are dropped before
95
+ * execution, closing the SELECT-time cooldown race.
96
+ *
97
+ * The logic mirrors the DUE gate in `selectProactiveMaintenanceRefs` — an
98
+ * asset is due only when never touched OR last touched more than `dueDays`
99
+ * ago. The selector and this filter must always agree on the gate predicate.
100
+ */
101
+ export function filterProactiveDue(selected, lastReflectTs, lastDistillTs, dueDays, now) {
102
+ return selected.filter((candidate) => {
103
+ const ref = candidate.ref;
104
+ const reflectIso = lastReflectTs.get(ref);
105
+ const distillIso = lastDistillTs.get(ref);
106
+ let lastTouchMs = 0;
107
+ if (reflectIso)
108
+ lastTouchMs = Math.max(lastTouchMs, Date.parse(reflectIso) || 0);
109
+ if (distillIso)
110
+ lastTouchMs = Math.max(lastTouchMs, Date.parse(distillIso) || 0);
111
+ const neverReflected = lastTouchMs === 0;
112
+ const staleDays = neverReflected ? Number.POSITIVE_INFINITY : (now - lastTouchMs) / DAY_MS;
113
+ return neverReflected || staleDays > dueDays;
114
+ });
115
+ }
@@ -1170,8 +1170,18 @@ export async function akmReflect(options = {}) {
1170
1170
  // (new-asset proposals have nothing to diff against).
1171
1171
  if (assetContent !== undefined) {
1172
1172
  const changeKind = classifyReflectChange(assetContent, payload.content);
1173
- if (changeKind !== "substantive") {
1174
- const subreason = changeKind === "noop" ? "reflect_skipped_noop" : "reflect_skipped_cosmetic";
1173
+ // 'low-value' is config-gated (#639): only defer when
1174
+ // profiles.improve.default.processes.reflect.lowValueFilter.enabled is
1175
+ // explicitly true. DEFAULT OFF — absent flag = byte-identical pre-#639
1176
+ // behaviour (low-value treated the same as substantive).
1177
+ const lowValueFilterEnabled = runtimeConfig?.profiles?.improve?.default?.processes?.reflect?.lowValueFilter?.enabled === true;
1178
+ const isDeferred = changeKind === "noop" || changeKind === "cosmetic" || (changeKind === "low-value" && lowValueFilterEnabled);
1179
+ if (isDeferred) {
1180
+ const subreason = changeKind === "noop"
1181
+ ? "reflect_skipped_noop"
1182
+ : changeKind === "low-value"
1183
+ ? "reflect_skipped_low_value"
1184
+ : "reflect_skipped_cosmetic";
1175
1185
  emitReflectFailed("no_change", subreason, options.ref, { changeKind });
1176
1186
  return {
1177
1187
  schemaVersion: 1,
@@ -1179,7 +1189,9 @@ export async function akmReflect(options = {}) {
1179
1189
  reason: "no_change",
1180
1190
  error: changeKind === "noop"
1181
1191
  ? `Reflect skipped: proposed content for ${payload.ref} is identical to the current asset (empty diff); no proposal created.`
1182
- : `Reflect skipped: proposed content for ${payload.ref} is a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes); no proposal created.`,
1192
+ : changeKind === "low-value"
1193
+ ? `Reflect skipped: proposed content for ${payload.ref} is a low-value prose micro-rewrite (few changed tokens, no structural changes); no proposal created.`
1194
+ : `Reflect skipped: proposed content for ${payload.ref} is a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes); no proposal created.`,
1183
1195
  ...(options.ref ? { ref: options.ref } : {}),
1184
1196
  exitCode: result.exitCode,
1185
1197
  };
@@ -14,23 +14,7 @@ const MARKER_RE = /\b(error|failed|fix(?:ed)?|root cause|turns out|because|decid
14
14
  const EDIT_COMMIT_RE = /\b(Edit|Write|MultiEdit|git commit|diff)\b/i;
15
15
  // A substantive turn is an assistant or tool turn whose text is non-trivial.
16
16
  const SUBSTANTIVE_MIN_CHARS = 40;
17
- /**
18
- * Pure heuristic scorer. Operates only on `data.events`. Each sub-signal is
19
- * normalized to a small bounded contribution; `score` is their sum and
20
- * `pass = score >= minScore`.
21
- *
22
- * Sub-signals:
23
- * - markers: presence of decision/outcome/error keywords across event text
24
- * (capped, narrative-lesson signal).
25
- * - toolDensity: bounded contribution from tool-use events (procedural).
26
- * - editCommit: bounded contribution from edit/write/commit events (procedural).
27
- * - substantiveRatio: scaled fraction of non-trivial assistant+tool turns,
28
- * filtering pure short Q&A.
29
- *
30
- * The procedural sub-signals (toolDensity + editCommit) alone can clear
31
- * DEFAULT_TRIAGE_MIN_SCORE so high-action / no-narrative sessions are KEPT (#615).
32
- */
33
- export function scoreSessionTriage(data, minScore) {
17
+ export function scoreSessionTriage(data, minScore, options) {
34
18
  const events = data.events;
35
19
  const total = events.length;
36
20
  // (a) markers — count word-bounded marker hits across all event text, capped.
@@ -72,7 +56,19 @@ export function scoreSessionTriage(data, minScore) {
72
56
  const substantiveRatio = Math.min(ratio, 1);
73
57
  const subscores = { markers, toolDensity, editCommit, substantiveRatio };
74
58
  const score = markers + toolDensity + editCommit + substantiveRatio;
75
- const pass = score >= minScore;
59
+ // Base gate: score must clear the floor.
60
+ let pass = score >= minScore;
61
+ // #641 procedural-aware floor (opt-in, DEFAULT OFF).
62
+ // When enabled, a session that clears the score gate must ALSO have at least
63
+ // one narrative marker (markers >= 1) OR meaningful edit/commit signal
64
+ // (editCommit >= 0.5). Pure read-only Q&A sessions that pass only via
65
+ // toolDensity + substantiveRatio are rejected as low-signal.
66
+ if (pass && options?.proceduralAwareFloor === true) {
67
+ const hasProceduralSignal = markers >= 1 || editCommit >= 0.5;
68
+ if (!hasProceduralSignal) {
69
+ pass = false;
70
+ }
71
+ }
76
72
  return {
77
73
  pass,
78
74
  score,
@@ -91,5 +87,7 @@ export function resolveTriageConfig(extractProcess) {
91
87
  const triage = extractProcess?.triage;
92
88
  const enabled = triage?.enabled === true;
93
89
  const minScore = typeof triage?.minScore === "number" ? triage.minScore : DEFAULT_TRIAGE_MIN_SCORE;
94
- return { enabled, minScore };
90
+ // #641: default-off only true when explicitly set to true.
91
+ const proceduralAwareFloor = triage?.proceduralAwareFloor === true;
92
+ return { enabled, minScore, proceduralAwareFloor };
95
93
  }
@@ -45,6 +45,10 @@ export const PERSONAL_STASH = {
45
45
  { generator: "reflect", maxDiffLines: 80 },
46
46
  // Consolidate within the diff band; mid-band lands in `defer` below.
47
47
  { generator: "consolidate", maxDiffLines: 200 },
48
+ // Recombine: accept only confirmed type:lesson proposals (promoted by the
49
+ // recombine confidence gate) within the diff band. type:hypothesis proposals
50
+ // have no matching rule here and stay pending for manual review.
51
+ { generator: "recombine", requireType: "lesson", maxDiffLines: 200 },
48
52
  ],
49
53
  rejectEmpty: true,
50
54
  // Mid-band consolidate, distill duplicates, and contradiction escalations are
@@ -83,6 +87,7 @@ const DrainAcceptRuleSchema = z
83
87
  generator: GeneratorSchema,
84
88
  maxDiffLines: z.number().int().positive().optional(),
85
89
  minContentLines: z.number().int().nonnegative().optional(),
90
+ requireType: z.string().optional(),
86
91
  })
87
92
  .strict();
88
93
  const DrainPolicySchema = z
@@ -80,7 +80,16 @@ export function classifyProposal(proposal, policy, maxDiffLines) {
80
80
  if (policy.rejectEmpty && isEmptyDiff(proposal)) {
81
81
  return { verdict: "reject", reason: "empty diff", gate: { reason: "empty-diff" } };
82
82
  }
83
- const rule = policy.accept.find((r) => r.generator === proposal.source);
83
+ const rule = policy.accept.find((r) => {
84
+ if (r.generator !== proposal.source)
85
+ return false;
86
+ if (r.requireType !== undefined) {
87
+ const fm = parseFrontmatter(proposal.payload.content ?? "").data;
88
+ if (typeof fm.type !== "string" || fm.type !== r.requireType)
89
+ return false;
90
+ }
91
+ return true;
92
+ });
84
93
  if (rule) {
85
94
  const lines = contentLineCount(content);
86
95
  const body = contentBodyLineCount(content);
@@ -329,6 +329,16 @@ export const ImproveProcessConfigSchema = z
329
329
  // #615 — procedural process: asset type a compiled sequence is emitted as.
330
330
  // Reserved; v1 always emits "workflow". Only meaningful on `procedural`.
331
331
  emitAs: z.enum(["workflow", "skill"]).optional(),
332
+ // #639 — semantic value-floor filter for the `reflect` process. When
333
+ // enabled, proposals classified as "low-value" by the deterministic noise
334
+ // gate are deferred. DEFAULT OFF (absent / { enabled: false } = pre-#639
335
+ // byte-identical behaviour). Only meaningful on the `reflect` process.
336
+ lowValueFilter: z.object({ enabled: z.boolean().optional() }).strict().optional(),
337
+ // #641 — procedural-aware floor for the `extract` process triage gate.
338
+ // When true, a session must have markers>=1 OR editCommit>=0.5 to pass, even
339
+ // if score>=minScore. DEFAULT OFF (absent/false = pre-#641 byte-identical).
340
+ // Only meaningful on the `extract` process when triage is also enabled.
341
+ proceduralAwareFloor: z.boolean().optional(),
332
342
  // Triage process config (only meaningful for the `triage` process)
333
343
  applyMode: z.enum(["queue", "promote"]).optional(),
334
344
  policy: z.string().min(1).optional(),
@@ -30,10 +30,14 @@ const FEATURE_LOCATION = {
30
30
  proposal_quality_gate: (cfg) => cfg.profiles?.improve?.default?.processes?.reflect?.qualityGate?.enabled ?? false,
31
31
  // Legacy default: false
32
32
  memory_contradiction_detection: (cfg) => cfg.profiles?.improve?.default?.processes?.consolidate?.contradictionDetection?.enabled ?? false,
33
- // Default: true. Session extraction replaces the akm-plugin checkpoint hook
34
- // and is the primary path for capturing durable signal from real sessions.
35
- // Opt out via `profiles.improve.default.processes.extract.enabled: false`.
36
- session_extraction: (cfg) => cfg.profiles?.improve?.default?.processes?.extract?.enabled ?? true,
33
+ // Always on at the LLM-wrapper level. Enablement is decided ONCE at the
34
+ // extract entry point (`akmExtract`): the `extract.enabled` process toggle
35
+ // gates extract as a STAGE of `akm improve` (the active improve profile, per
36
+ // #593/#594), while an explicit `akm extract` command always runs. Gating the
37
+ // inner LLM calls on `default.processes.extract.enabled` here was a footgun —
38
+ // dropping extract from the daily improve profile silently disabled the
39
+ // standalone `akm extract` command. (cfg unused — kept for resolver signature.)
40
+ session_extraction: (_cfg) => true,
37
41
  };
38
42
  /**
39
43
  * Pure predicate: is the named feature gate enabled in `config`?
@@ -15920,6 +15920,8 @@ var init_config_schema = __esm(() => {
15920
15920
  minRecurrence: exports_external.number().int().min(2).optional(),
15921
15921
  maxProposalsPerRun: positiveInt.optional(),
15922
15922
  emitAs: exports_external.enum(["workflow", "skill"]).optional(),
15923
+ lowValueFilter: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15924
+ proceduralAwareFloor: exports_external.boolean().optional(),
15923
15925
  applyMode: exports_external.enum(["queue", "promote"]).optional(),
15924
15926
  policy: exports_external.string().min(1).optional(),
15925
15927
  maxAcceptsPerRun: positiveInt.optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.0-beta.31",
3
+ "version": "0.9.0-beta.33",
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": [