akm-cli 0.9.0-beta.52 → 0.9.0-beta.54

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.
Files changed (66) hide show
  1. package/dist/assets/hints/cli-hints-full.md +6 -5
  2. package/dist/cli/clack.js +56 -0
  3. package/dist/cli/confirm.js +1 -1
  4. package/dist/cli.js +0 -7
  5. package/dist/commands/env/env-cli.js +3 -2
  6. package/dist/commands/env/env.js +14 -67
  7. package/dist/commands/health/checks.js +28 -15
  8. package/dist/commands/health/html-report.js +33 -10
  9. package/dist/commands/health.js +222 -22
  10. package/dist/commands/improve/collapse-detector.js +419 -0
  11. package/dist/commands/improve/consolidate.js +72 -54
  12. package/dist/commands/improve/distill.js +79 -13
  13. package/dist/commands/improve/extract.js +13 -6
  14. package/dist/commands/improve/homeostatic.js +109 -79
  15. package/dist/commands/improve/improve-cli.js +67 -1
  16. package/dist/commands/improve/improve.js +10 -0
  17. package/dist/commands/improve/loop-stages.js +39 -1
  18. package/dist/commands/improve/outcome-loop.js +33 -19
  19. package/dist/commands/improve/preparation.js +36 -11
  20. package/dist/commands/improve/salience.js +49 -32
  21. package/dist/commands/read/curate.js +9 -13
  22. package/dist/commands/read/knowledge.js +4 -0
  23. package/dist/commands/read/search-cli.js +6 -4
  24. package/dist/commands/read/search.js +12 -5
  25. package/dist/commands/read/show.js +6 -8
  26. package/dist/commands/sources/add-cli.js +1 -1
  27. package/dist/commands/sources/init.js +12 -0
  28. package/dist/commands/sources/stash-cli.js +1 -1
  29. package/dist/commands/tasks/default-tasks.js +12 -0
  30. package/dist/core/asset/asset-spec.js +3 -2
  31. package/dist/core/config/config-schema.js +39 -17
  32. package/dist/core/config/config.js +12 -0
  33. package/dist/core/eval/rank-metrics.js +113 -0
  34. package/dist/core/state/migrations.js +56 -0
  35. package/dist/core/state-db.js +146 -19
  36. package/dist/core/warn.js +21 -0
  37. package/dist/indexer/db/db.js +6 -0
  38. package/dist/indexer/ensure-index.js +36 -92
  39. package/dist/indexer/index-writer-lock.js +9 -11
  40. package/dist/indexer/index-written-assets.js +105 -0
  41. package/dist/indexer/indexer.js +16 -4
  42. package/dist/indexer/passes/metadata.js +20 -0
  43. package/dist/indexer/read-preflight.js +23 -0
  44. package/dist/indexer/search/db-search.js +29 -1
  45. package/dist/indexer/search/ranking-contributors.js +33 -1
  46. package/dist/indexer/search/ranking.js +66 -0
  47. package/dist/indexer/search/search-fields.js +6 -0
  48. package/dist/indexer/walk/walker.js +21 -13
  49. package/dist/integrations/agent/detect.js +9 -0
  50. package/dist/integrations/agent/index.js +1 -1
  51. package/dist/llm/client.js +12 -0
  52. package/dist/llm/embedder.js +26 -2
  53. package/dist/llm/embedders/local.js +7 -1
  54. package/dist/llm/feature-gate.js +6 -2
  55. package/dist/output/renderers.js +8 -13
  56. package/dist/output/shapes/helpers.js +0 -3
  57. package/dist/output/shapes/passthrough.js +1 -0
  58. package/dist/scripts/migrate-storage.js +178 -35
  59. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +46 -19
  60. package/dist/setup/detect.js +9 -0
  61. package/dist/setup/registry-stash-loader.js +12 -0
  62. package/dist/setup/setup.js +1 -1
  63. package/dist/storage/repositories/index-db.js +10 -1
  64. package/dist/tasks/backends/index.js +9 -0
  65. package/dist/tasks/runner.js +9 -0
  66. package/package.json +2 -4
@@ -2,17 +2,29 @@
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";
13
14
  import { LLM_USAGE_EVENT } from "../llm/usage-persist.js";
14
15
  import { HEALTH_CHECKS } from "./health/checks.js";
15
16
  import { gateDecisionsToSamples, summarizeCalibration } from "./improve/calibration.js";
17
+ /**
18
+ * Lanes ratified as ENRICHMENT-ONLY: they may propose edits to existing
19
+ * assets (metadata, relations, content refresh) but must not mint new ones.
20
+ * New-asset generation belongs to the signal-gated minting lanes
21
+ * (extract/distill/memory-inference/recombine).
22
+ */
23
+ export const ENRICHMENT_LANES = ["proactive", "high-salience", "high-retrieval", "signal-delta"];
24
+ /** Minted share of enrichment-lane accepts that triggers a WARN advisory. */
25
+ export const ENRICHMENT_MINTED_WARN_SHARE = 0.05;
26
+ /** Minted share of enrichment-lane accepts that triggers a FAIL advisory. */
27
+ export const ENRICHMENT_MINTED_FAIL_SHARE = 0.15;
16
28
  const DEFAULT_SINCE_MS = 24 * 60 * 60 * 1000;
17
29
  const IMPROVE_COMPLETED_EVENT = "improve_completed";
18
30
  const HEALTH_PROBE_EVENT = "health_probe";
@@ -172,6 +184,8 @@ function createUnknownImproveMetrics() {
172
184
  rate: Number.NaN,
173
185
  eligibleFraction: Number.NaN,
174
186
  acceptedProposals: 0,
187
+ distinctRefs: 0,
188
+ churnRatio: Number.NaN,
175
189
  totalAssets: 0,
176
190
  },
177
191
  };
@@ -1133,6 +1147,7 @@ function readCalibration(db, since, until) {
1133
1147
  */
1134
1148
  function computeDenominatorFixedCoverage(db, totalAssets, eligibleAssets, since, until, stashDir) {
1135
1149
  let acceptedProposals = 0;
1150
+ let distinctRefs = 0;
1136
1151
  try {
1137
1152
  const proposals = listStateProposals(db, {
1138
1153
  status: "accepted",
@@ -1146,25 +1161,91 @@ function computeDenominatorFixedCoverage(db, totalAssets, eligibleAssets, since,
1146
1161
  return true;
1147
1162
  });
1148
1163
  acceptedProposals = proposals.length;
1164
+ // Coverage counts DISTINCT refs: N accepted rewrites of one asset are
1165
+ // churn, not coverage. The raw proposal count is kept alongside so the
1166
+ // churn ratio (proposals ÷ distinct refs) stays visible.
1167
+ distinctRefs = new Set(proposals.map((p) => p.ref)).size;
1149
1168
  }
1150
1169
  catch {
1151
1170
  // Fail open: table may not exist on older installs.
1152
1171
  }
1172
+ const churnRatio = distinctRefs > 0 ? roundRate(acceptedProposals / distinctRefs) : Number.NaN;
1153
1173
  if (totalAssets === 0) {
1154
1174
  return {
1155
1175
  rate: Number.NaN,
1156
1176
  eligibleFraction: Number.NaN,
1157
1177
  acceptedProposals,
1178
+ distinctRefs,
1179
+ churnRatio,
1158
1180
  totalAssets: 0,
1159
1181
  };
1160
1182
  }
1161
1183
  return {
1162
- rate: roundRate(acceptedProposals / totalAssets),
1184
+ rate: roundRate(distinctRefs / totalAssets),
1163
1185
  eligibleFraction: roundRate(eligibleAssets / totalAssets),
1164
1186
  acceptedProposals,
1187
+ distinctRefs,
1188
+ churnRatio,
1165
1189
  totalAssets,
1166
1190
  };
1167
1191
  }
1192
+ /**
1193
+ * Compute the enrichment-vs-minting rollup over the window's accepted,
1194
+ * lane-attributed proposals (reporting-only; see {@link EnrichmentMintingRollup}).
1195
+ *
1196
+ * SQL-side `json_extract` keeps the (potentially large) `backupContent` blobs
1197
+ * out of process memory. Pre-Phase-6C rows without an `eligibilitySource`
1198
+ * cannot be lane-classified and are excluded. Fails open (undefined) when the
1199
+ * proposals table is absent.
1200
+ */
1201
+ export function computeEnrichmentMintingRollup(db, since, until) {
1202
+ try {
1203
+ const rows = db
1204
+ .prepare(`SELECT
1205
+ json_extract(metadata_json, '$.eligibilitySource') AS lane,
1206
+ CASE WHEN json_extract(metadata_json, '$.backupContent') IS NULL THEN 1 ELSE 0 END AS is_minted,
1207
+ COUNT(*) AS cnt
1208
+ FROM proposals
1209
+ WHERE status = 'accepted'
1210
+ AND updated_at >= ?
1211
+ AND (? IS NULL OR updated_at < ?)
1212
+ AND json_extract(metadata_json, '$.eligibilitySource') IS NOT NULL
1213
+ AND json_extract(metadata_json, '$.eligibilitySource') != ''
1214
+ GROUP BY lane, is_minted`)
1215
+ .all(since, until ?? null, until ?? null);
1216
+ if (rows.length === 0)
1217
+ return undefined;
1218
+ const byLane = {};
1219
+ for (const row of rows) {
1220
+ byLane[row.lane] ??= { minted: 0, updated: 0 };
1221
+ const entry = byLane[row.lane];
1222
+ if (row.is_minted === 1)
1223
+ entry.minted += row.cnt;
1224
+ else
1225
+ entry.updated += row.cnt;
1226
+ }
1227
+ let minted = 0;
1228
+ let updated = 0;
1229
+ for (const lane of ENRICHMENT_LANES) {
1230
+ const entry = byLane[lane];
1231
+ if (!entry)
1232
+ continue;
1233
+ minted += entry.minted;
1234
+ updated += entry.updated;
1235
+ }
1236
+ const decided = minted + updated;
1237
+ return {
1238
+ minted,
1239
+ updated,
1240
+ share: decided > 0 ? roundRate(minted / decided) : Number.NaN,
1241
+ byLane,
1242
+ };
1243
+ }
1244
+ catch {
1245
+ // Fail open: proposals table may not exist on older installs.
1246
+ return undefined;
1247
+ }
1248
+ }
1168
1249
  /**
1169
1250
  * Compute WS-5 per-run degradation metrics (Part V §4).
1170
1251
  *
@@ -1175,7 +1256,7 @@ function computeDenominatorFixedCoverage(db, totalAssets, eligibleAssets, since,
1175
1256
  * @param since - Window start (ISO-8601).
1176
1257
  * @param until - Window end (ISO-8601).
1177
1258
  */
1178
- function computeDegradationMetrics(db, since, until) {
1259
+ export function computeDegradationMetrics(db, since, until) {
1179
1260
  // (a) Corpus diversity — salience rank distribution of the top-100 assets.
1180
1261
  // We use the Gini coefficient of retrieval_salience scores as an intra-corpus
1181
1262
  // diversity proxy. A Gini close to 1 = highly concentrated (entrenched top
@@ -1183,6 +1264,7 @@ function computeDegradationMetrics(db, since, until) {
1183
1264
  // consecutive-run centroid distance requires cross-run history not yet stored.
1184
1265
  let corpusCentroidDistance = Number.NaN;
1185
1266
  let entrenchmentFlagged;
1267
+ let salienceUniformityFlagged;
1186
1268
  try {
1187
1269
  const rows = db
1188
1270
  .prepare(`SELECT retrieval_salience FROM asset_salience
@@ -1201,9 +1283,13 @@ function computeDegradationMetrics(db, since, until) {
1201
1283
  // corpusCentroidDistance approximation: gini is "distance from uniform".
1202
1284
  // Note: retrieval_salience values are in [0,1], so the max achievable Gini
1203
1285
  // with this formula is ~0.5 (when one asset dominates and others are near 0).
1204
- // Threshold: >0.35 flags entrenchment (robustly above the ~0.1 uniform baseline).
1286
+ // Two-tailed: >0.35 flags entrenchment (robustly above the ~0.1 uniform
1287
+ // baseline); <0.08 flags uniformity collapse — the distribution no longer
1288
+ // discriminates between assets (live 2026-07 value 0.040 sat unflagged
1289
+ // in this tail under the old one-tailed check).
1205
1290
  corpusCentroidDistance = roundRate(gini);
1206
1291
  entrenchmentFlagged = gini > 0.35;
1292
+ salienceUniformityFlagged = gini < 0.08;
1207
1293
  }
1208
1294
  }
1209
1295
  catch {
@@ -1242,23 +1328,11 @@ function computeDegradationMetrics(db, since, until) {
1242
1328
  catch {
1243
1329
  // Fail open.
1244
1330
  }
1245
- // (c) Generation distribution fraction of asset_salience rows with
1246
- // generation >= 2. Generation is NOT currently stored in asset_salience
1247
- // (it's in frontmatter). We approximate using consecutive_no_ops as a
1248
- // maturity proxy: assets that have never been no-op'd are "fresh".
1249
- // TODO(0.10+): store generation in asset_salience for proper tracking.
1250
- let highGenerationFraction = Number.NaN;
1251
- try {
1252
- const genRows = db.prepare("SELECT consecutive_no_ops FROM asset_salience").all();
1253
- if (genRows.length > 0) {
1254
- // Use consecutive_no_ops >= 2 as a proxy for "has been through merge cycles".
1255
- const highGen = genRows.filter((r) => r.consecutive_no_ops >= 2).length;
1256
- highGenerationFraction = roundRate(highGen / genRows.length);
1257
- }
1258
- }
1259
- catch {
1260
- // Table not present.
1261
- }
1331
+ // (c) highGenerationFraction was DELETED (meta-review 05 DRIFT-3): it
1332
+ // approximated "LLM-merge generations" from consecutive_no_ops which counts
1333
+ // the opposite condition (cycles where nothing was changed) and its own
1334
+ // in-code TODO admitted the proxy. Display-only, never actionable; removed
1335
+ // rather than instrumented.
1262
1336
  // (d) Oracle spot-check — up to 5 recently accepted proposals in the window.
1263
1337
  const oracleSpotCheck = [];
1264
1338
  try {
@@ -1286,8 +1360,8 @@ function computeDegradationMetrics(db, since, until) {
1286
1360
  return {
1287
1361
  corpusCentroidDistance,
1288
1362
  entrenchmentFlagged,
1363
+ salienceUniformityFlagged,
1289
1364
  mergeFidelityContradictionRate,
1290
- highGenerationFraction,
1291
1365
  oracleSpotCheck,
1292
1366
  };
1293
1367
  }
@@ -1411,6 +1485,18 @@ export function akmHealth(options = {}) {
1411
1485
  const taskFailRate = taskRows.length === 0 ? 0 : failedTaskRows.length / taskRows.length;
1412
1486
  const agentFailureRate = promptRows.length === 0 ? 0 : promptFailures.length / promptRows.length;
1413
1487
  const semanticStatus = readSemanticStatus();
1488
+ // For the embedding-endpoint advisory. Best-effort: an unloadable config
1489
+ // leaves both undefined and the check falls back to its generic message.
1490
+ let semanticSearchMode;
1491
+ let embeddingEndpoint;
1492
+ try {
1493
+ const config = loadConfig();
1494
+ semanticSearchMode = config.semanticSearchMode;
1495
+ embeddingEndpoint = config.embedding?.endpoint;
1496
+ }
1497
+ catch {
1498
+ // fall through with undefined
1499
+ }
1414
1500
  const improveInvoked = readEvents({ since, type: "improve_invoked" }, { dbPath: stateDbPath }).events.length;
1415
1501
  const improveCompletedEvents = readEvents({ since, type: IMPROVE_COMPLETED_EVENT }, { dbPath: stateDbPath }).events;
1416
1502
  const improveSkippedEvents = readEvents({ since, type: "improve_skipped" }, { dbPath: stateDbPath }).events;
@@ -1434,6 +1520,7 @@ export function akmHealth(options = {}) {
1434
1520
  if (degradationMain) {
1435
1521
  improveSummary.degradation = degradationMain;
1436
1522
  }
1523
+ improveSummary.enrichmentMinting = computeEnrichmentMintingRollup(db, since, until);
1437
1524
  // WS-2 proxy-adequacy tripwire: surface any outcome_proxy_inverted events
1438
1525
  // in the health window as an advisory so operators know when the 0.10+
1439
1526
  // rich in-session signal is no longer deferrable.
@@ -1453,6 +1540,117 @@ export function akmHealth(options = {}) {
1453
1540
  "The 0.10+ rich in-session outcome signal is no longer deferrable. See plan §WS-2.",
1454
1541
  });
1455
1542
  }
1543
+ // Two-tailed companion: a proxy that decays to noise (|corr| < 0.1 at scale)
1544
+ // is as much a failure as an inverted one — it just fails silently.
1545
+ const proxyDeadEvents = readEvents({ since, type: "outcome_proxy_dead" }, { dbPath: stateDbPath, db }).events;
1546
+ if (proxyDeadEvents.length > 0) {
1547
+ const lastEvent = proxyDeadEvents[proxyDeadEvents.length - 1];
1548
+ const correlation = typeof lastEvent.metadata?.correlation === "number" ? lastEvent.metadata.correlation.toFixed(3) : "unknown";
1549
+ advisories.push({
1550
+ name: "outcome-proxy-dead",
1551
+ status: "warn",
1552
+ kind: "deterministic",
1553
+ confidence: "high",
1554
+ message: `WS-2 outcome proxy is DEAD (${proxyDeadEvents.length} event(s) in window). ` +
1555
+ `|corr(outcome_score, accepted_change_rate)| = ${correlation} < 0.1 at n ≥ 500. ` +
1556
+ "outcome_score is statistically unrelated to improvement outcomes — " +
1557
+ "treat outcome-derived rank contributions as noise until a real usage/outcome signal lands.",
1558
+ });
1559
+ }
1560
+ // Salience-distribution collapse: Gini below the uniform baseline means
1561
+ // ranking no longer discriminates between assets.
1562
+ if (improveSummary.degradation?.salienceUniformityFlagged) {
1563
+ advisories.push({
1564
+ name: "salience-uniformity-collapse",
1565
+ status: "warn",
1566
+ kind: "deterministic",
1567
+ confidence: "high",
1568
+ message: `Salience distribution collapsed toward uniform: top-100 retrieval_salience Gini = ` +
1569
+ `${improveSummary.degradation.corpusCentroidDistance} < 0.08 (uniform baseline ≈ 0.1). ` +
1570
+ "Ranking currently carries little to no discrimination between assets.",
1571
+ });
1572
+ }
1573
+ // Enrichment-vs-minting policy: enrichment lanes edit existing assets;
1574
+ // a rising minted share means a lane is generating new content instead.
1575
+ const minting = improveSummary.enrichmentMinting;
1576
+ if (minting && Number.isFinite(minting.share) && minting.share > ENRICHMENT_MINTED_WARN_SHARE) {
1577
+ advisories.push({
1578
+ name: "enrichment-lane-minting",
1579
+ status: minting.share > ENRICHMENT_MINTED_FAIL_SHARE ? "fail" : "warn",
1580
+ kind: "deterministic",
1581
+ confidence: "high",
1582
+ message: `Enrichment lanes minted ${minting.minted} NEW asset(s) vs ${minting.updated} update(s) ` +
1583
+ `(${Math.round(minting.share * 100)}% minted, threshold ${Math.round(ENRICHMENT_MINTED_WARN_SHARE * 100)}%). ` +
1584
+ "Enrichment-classed lanes (proactive/high-salience/high-retrieval/signal-delta) are ratified to edit " +
1585
+ "existing assets only — new-asset generation belongs to the signal-gated minting lanes.",
1586
+ });
1587
+ }
1588
+ // Churn: accepted proposals far exceeding distinct touched refs means the
1589
+ // loop is repeatedly rewriting the same assets, not covering the corpus.
1590
+ if (Number.isFinite(improveSummary.coverage.churnRatio) && improveSummary.coverage.churnRatio > 1.5) {
1591
+ advisories.push({
1592
+ name: "improve-churn-ratio",
1593
+ status: "warn",
1594
+ kind: "deterministic",
1595
+ confidence: "high",
1596
+ message: `Improve churn ratio ${improveSummary.coverage.churnRatio} > 1.5: ` +
1597
+ `${improveSummary.coverage.acceptedProposals} accepted proposals touched only ` +
1598
+ `${improveSummary.coverage.distinctRefs} distinct assets in the window — ` +
1599
+ "repeated rewrites of the same refs count as churn, not coverage.",
1600
+ });
1601
+ }
1602
+ // R5 collapse/churn detector: surface any collapse_detector_alert events
1603
+ // in the health window, plus the latest cycle row's headline numbers so
1604
+ // the operator can act without opening the DB. `unknown` when the detector
1605
+ // has never produced a cycle row (no consolidate/recombine work yet).
1606
+ try {
1607
+ // Reuse the already-open state.db handle (readEvents supports a
1608
+ // borrowed connection) — no extra open/migrate/close per health call.
1609
+ const collapseAlertEvents = readEvents({ since, type: "collapse_detector_alert" }, { dbPath: stateDbPath, db }).events;
1610
+ const latestCycle = getLatestCycleMetrics(db);
1611
+ const cycleSummary = latestCycle
1612
+ ? `Latest cycle (${latestCycle.ts}, ${latestCycle.pass}): mean canary recall ${latestCycle.mean_recall.toFixed(3)}, ` +
1613
+ `distinct-content ratio ${latestCycle.distinct_content_ratio.toFixed(3)}, ` +
1614
+ `${latestCycle.accepted_actions} accepted action(s).`
1615
+ : "";
1616
+ if (collapseAlertEvents.length > 0) {
1617
+ const kinds = [...new Set(collapseAlertEvents.map((e) => String(e.metadata?.kind ?? "unknown")))];
1618
+ const collapseKinds = kinds.filter((k) => k.startsWith("collapse"));
1619
+ advisories.push({
1620
+ name: "collapse-churn-detector",
1621
+ status: "warn",
1622
+ kind: "deterministic",
1623
+ // Collapse kinds are measured, not inferred; churn/merge-floor
1624
+ // volume thresholds are still being tuned (design doc §7).
1625
+ confidence: collapseKinds.length > 0 ? "high" : "medium",
1626
+ message: `R5 detector fired ${collapseAlertEvents.length} alert(s) in window (kinds: ${kinds.join(", ")}). ` +
1627
+ `${cycleSummary} See docs/design/improve-collapse-churn-detector-design.md §6.3 runbook queries.`,
1628
+ });
1629
+ }
1630
+ else if (latestCycle) {
1631
+ advisories.push({
1632
+ name: "collapse-churn-detector",
1633
+ status: "pass",
1634
+ kind: "deterministic",
1635
+ confidence: "high",
1636
+ message: `No collapse/churn alerts in window. ${cycleSummary}`,
1637
+ });
1638
+ }
1639
+ else {
1640
+ advisories.push({
1641
+ name: "collapse-churn-detector",
1642
+ status: "unknown",
1643
+ kind: "deterministic",
1644
+ confidence: "high",
1645
+ message: "No detector cycle rows yet — the collapse/churn detector runs only on improve cycles " +
1646
+ "where consolidate/recombine did work (synthesis lanes may be idle).",
1647
+ });
1648
+ }
1649
+ }
1650
+ catch {
1651
+ // Table may predate migration 016 in odd mixed-version setups — advisory
1652
+ // is best-effort and must never fail the health command.
1653
+ }
1456
1654
  let sessionLogEntries = [];
1457
1655
  try {
1458
1656
  const sinceDays = Math.max(0, Math.ceil((now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
@@ -1482,6 +1680,8 @@ export function akmHealth(options = {}) {
1482
1680
  logBackingRate,
1483
1681
  stuckActiveRuns,
1484
1682
  semanticStatus,
1683
+ semanticSearchMode,
1684
+ embeddingEndpoint,
1485
1685
  sessionLogEntries,
1486
1686
  sessionExtraction: improveSummary.sessionExtraction,
1487
1687
  autoAccept: improveSummary.autoAccept,