akm-cli 0.9.0-beta.11 → 0.9.0-beta.26
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 +163 -0
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +5 -1
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/templates/html/health.html +25 -27
- package/dist/cli.js +2 -2
- package/dist/commands/agent/contribute-cli.js +16 -3
- package/dist/commands/feedback-cli.js +48 -44
- package/dist/commands/health/html-report.js +140 -16
- package/dist/commands/health.js +277 -1
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate.js +595 -105
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +119 -64
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +115 -1
- package/dist/commands/improve/extract-prompt.js +32 -1
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +210 -30
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +80 -7
- package/dist/commands/improve/improve-profiles.js +8 -0
- package/dist/commands/improve/improve.js +991 -61
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/proactive-maintenance.js +9 -35
- package/dist/commands/improve/procedural.js +409 -0
- package/dist/commands/improve/recombine.js +488 -0
- package/dist/commands/improve/reflect.js +20 -1
- package/dist/commands/improve/related-sessions.js +120 -0
- package/dist/commands/improve/salience.js +386 -0
- package/dist/commands/improve/triage.js +95 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +34 -1
- package/dist/commands/lint/index.js +30 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +5 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/validators/proposals.js +4 -0
- package/dist/commands/read/curate.js +284 -86
- package/dist/commands/read/search-cli.js +7 -0
- package/dist/commands/read/search.js +1 -0
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/config/config-schema.js +211 -3
- package/dist/core/config/config.js +2 -2
- package/dist/core/logs-db.js +4 -3
- package/dist/core/state-db.js +555 -29
- package/dist/indexer/db/db.js +250 -27
- package/dist/indexer/db/graph-db.js +81 -86
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/passes/memory-inference.js +10 -3
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +15 -4
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/opencode/session-log.js +9 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/embedder.js +27 -3
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/graph-extract.js +2 -1
- package/dist/llm/memory-infer.js +4 -8
- package/dist/llm/metadata-enhance.js +9 -1
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/text/helpers.js +9 -0
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +1025 -567
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +435 -269
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +1 -0
- package/package.json +5 -4
|
@@ -11,11 +11,9 @@ import { writeFileAtomic } from "../core/common.js";
|
|
|
11
11
|
import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config/config.js";
|
|
12
12
|
import { UsageError } from "../core/errors.js";
|
|
13
13
|
import { appendEvent } from "../core/events.js";
|
|
14
|
+
import { getDbPath } from "../core/paths.js";
|
|
14
15
|
import { warn } from "../core/warn.js";
|
|
15
16
|
import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, getEntryFilePathById, openExistingDatabase, } from "../indexer/db/db.js";
|
|
16
|
-
import { ensureIndex } from "../indexer/ensure-index.js";
|
|
17
|
-
import { withIndexWriterLease } from "../indexer/index-writer-lock.js";
|
|
18
|
-
import { resolveSourceEntries } from "../indexer/search/search-source.js";
|
|
19
17
|
import { countFeedbackSignals, insertUsageEvent } from "../indexer/usage/usage-events.js";
|
|
20
18
|
// ── Tag validation ────────────────────────────────────────────────────────────
|
|
21
19
|
const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
|
|
@@ -204,51 +202,57 @@ export const feedbackCommand = defineCommand({
|
|
|
204
202
|
...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
|
|
205
203
|
};
|
|
206
204
|
const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
205
|
+
// Feedback only needs the index to exist, not to be current. A stale index
|
|
206
|
+
// is fine — the ref lookup works against any populated DB. We do NOT call
|
|
207
|
+
// ensureIndex here: it either blocks (3+ min inline reindex) or spawns a
|
|
208
|
+
// background process that holds the writer lock, causing the feedback write
|
|
209
|
+
// to spin-wait for the full reindex duration. If the DB is absent we give a
|
|
210
|
+
// clear error below rather than silently triggering a rebuild.
|
|
211
|
+
if (!fs.existsSync(getDbPath())) {
|
|
212
|
+
throw new UsageError("Index not found. Run 'akm index' first to build the index before recording feedback.", "MISSING_REQUIRED_ARGUMENT", "akm index");
|
|
213
|
+
}
|
|
214
|
+
// Feedback writes exactly 2 rows (usage_events + utility_score). SQLite
|
|
215
|
+
// WAL mode + busy_timeout=30s handles concurrent access with an ongoing
|
|
216
|
+
// `akm improve` run without needing the application-level writer lock.
|
|
217
|
+
// The lock was originally needed to prevent feedback from racing a
|
|
218
|
+
// background reindex it spawned — now that ensureIndex is removed, holding
|
|
219
|
+
// the lock only causes feedback to block for the full improve run duration.
|
|
220
|
+
let utilityResult;
|
|
221
|
+
const db = openExistingDatabase();
|
|
222
|
+
try {
|
|
223
|
+
const entryId = findEntryIdByRef(db, ref);
|
|
224
|
+
if (entryId === undefined) {
|
|
225
|
+
throw new UsageError(`Ref "${ref}" is not in the index. ` +
|
|
226
|
+
"Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
|
|
213
227
|
}
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
// Persist the feedback signal into usage_events. For positive signals,
|
|
229
|
+
// the EMA utility score is updated immediately on the next read path.
|
|
230
|
+
// For negative signals, the score is adjusted the next time `akm index`
|
|
231
|
+
// runs — the signal is durable in the DB but does NOT suppress ranking
|
|
232
|
+
// in search results until after reindexing.
|
|
233
|
+
insertUsageEvent(db, {
|
|
234
|
+
event_type: "feedback",
|
|
235
|
+
entry_ref: ref,
|
|
236
|
+
entry_id: entryId,
|
|
237
|
+
signal,
|
|
238
|
+
metadata: metadataStr,
|
|
239
|
+
});
|
|
240
|
+
// Apply feedback-derived utility score adjustment immediately so that
|
|
241
|
+
// positive/negative signals influence search ranking without requiring
|
|
242
|
+
// a full reindex. We query the total accumulated feedback counts from
|
|
243
|
+
// usage_events so the delta reflects the entire signal history.
|
|
244
|
+
// Uses MemRL bounded-step EMA (F-5 / #386, arXiv:2601.03192).
|
|
216
245
|
try {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
throw new UsageError(`Ref "${ref}" is not in the index. ` +
|
|
220
|
-
"Run 'akm search' to verify the asset exists, then 'akm index' if it was recently added.");
|
|
221
|
-
}
|
|
222
|
-
// Persist the feedback signal into usage_events. For positive signals,
|
|
223
|
-
// the EMA utility score is updated immediately on the next read path.
|
|
224
|
-
// For negative signals, the score is adjusted the next time `akm index`
|
|
225
|
-
// runs — the signal is durable in the DB but does NOT suppress ranking
|
|
226
|
-
// in search results until after reindexing.
|
|
227
|
-
insertUsageEvent(db, {
|
|
228
|
-
event_type: "feedback",
|
|
229
|
-
entry_ref: ref,
|
|
230
|
-
entry_id: entryId,
|
|
231
|
-
signal,
|
|
232
|
-
metadata: metadataStr,
|
|
233
|
-
});
|
|
234
|
-
// Apply feedback-derived utility score adjustment immediately so that
|
|
235
|
-
// positive/negative signals influence search ranking without requiring
|
|
236
|
-
// a full reindex. We query the total accumulated feedback counts from
|
|
237
|
-
// usage_events so the delta reflects the entire signal history.
|
|
238
|
-
// Uses MemRL bounded-step EMA (F-5 / #386, arXiv:2601.03192).
|
|
239
|
-
try {
|
|
240
|
-
const { pos, neg } = countFeedbackSignals(db, entryId);
|
|
241
|
-
scopedUtilityResult = applyFeedbackToUtilityScore(db, entryId, pos, neg);
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
// best-effort — feedback recording succeeds even if utility update fails
|
|
245
|
-
}
|
|
246
|
+
const { pos, neg } = countFeedbackSignals(db, entryId);
|
|
247
|
+
utilityResult = applyFeedbackToUtilityScore(db, entryId, pos, neg);
|
|
246
248
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
catch {
|
|
250
|
+
// best-effort — feedback recording succeeds even if utility update fails
|
|
249
251
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
closeDatabase(db);
|
|
255
|
+
}
|
|
252
256
|
appendEvent({
|
|
253
257
|
eventType: "feedback",
|
|
254
258
|
ref,
|
|
@@ -29,6 +29,11 @@ const ECHARTS_CDN = "https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"
|
|
|
29
29
|
const ECHARTS_VENDOR_PATH = path.join(getDirname(import.meta.url), "../../assets/templates/html/vendor/echarts.min.js");
|
|
30
30
|
// ── Small formatters (ports of render.py helpers) ───────────────────────────
|
|
31
31
|
const esc = escapeHtml;
|
|
32
|
+
/** Emit a <time> element that the browser's JS will reformat to the viewer's local timezone. */
|
|
33
|
+
function isoTimeTag(iso) {
|
|
34
|
+
const fallback = iso.slice(0, 16).replace("T", " ");
|
|
35
|
+
return `<time data-iso="${esc(iso)}">${esc(fallback)}</time>`;
|
|
36
|
+
}
|
|
32
37
|
function num(value) {
|
|
33
38
|
return Math.round(value).toLocaleString("en-US");
|
|
34
39
|
}
|
|
@@ -264,6 +269,19 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
264
269
|
const mi = improve.memoryInference;
|
|
265
270
|
const ge = improve.graphExtraction;
|
|
266
271
|
const wallTime = improve.wallTime;
|
|
272
|
+
// WS-5: perf telemetry, coverage, and degradation metrics.
|
|
273
|
+
const perf = improve.perfTelemetry ?? {
|
|
274
|
+
dedupPoolSize: 0,
|
|
275
|
+
llmPoolSize: 0,
|
|
276
|
+
judgedCacheSkipped: 0,
|
|
277
|
+
embedMs: 0,
|
|
278
|
+
embedCacheHits: 0,
|
|
279
|
+
embedCacheMisses: 0,
|
|
280
|
+
overBudgetRuns: 0,
|
|
281
|
+
runsWithTelemetry: 0,
|
|
282
|
+
};
|
|
283
|
+
const coverage = improve.coverage;
|
|
284
|
+
const degradation = improve.degradation;
|
|
267
285
|
// #576: real per-stage LLM token/time accounting (replaces the GPU-time
|
|
268
286
|
// proxy). Optional-guarded so reports built from older health JSON without
|
|
269
287
|
// the aggregate still render.
|
|
@@ -292,7 +310,7 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
292
310
|
// ── Meta (collect.py steps 10-11) ──────────────────────────────────────────
|
|
293
311
|
const sinceIso = result.since;
|
|
294
312
|
const reportDate = sinceIso.slice(0, 10);
|
|
295
|
-
const sinceHuman = sinceIso ? `${sinceIso
|
|
313
|
+
const sinceHuman = sinceIso ? `${isoTimeTag(sinceIso)} → now` : `last ${esc(opts.window)}`;
|
|
296
314
|
const reportTitle = reportDate ? `AKM Health Report — ${reportDate}` : "AKM Health Report";
|
|
297
315
|
const lastRun = runs[runs.length - 1];
|
|
298
316
|
const generatedAt = lastRun ? lastRun.completedAt || lastRun.startedAt || sinceIso : sinceIso;
|
|
@@ -308,9 +326,7 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
308
326
|
const generatedMs = Date.parse(generatedAt);
|
|
309
327
|
const idleTailMs = Number.isFinite(latestRunMs) && Number.isFinite(generatedMs) ? Math.max(0, generatedMs - latestRunMs) : 0;
|
|
310
328
|
const isStale = totalRuns === 0 || (Number.isFinite(latestRunMs) && Number.isFinite(sinceMs) && idleTailMs > STALE_MS);
|
|
311
|
-
const latestRunHuman = lastRun
|
|
312
|
-
? `${(lastRun.completedAt || lastRun.startedAt).slice(0, 16).replace("T", " ")} UTC`
|
|
313
|
-
: "—";
|
|
329
|
+
const latestRunHuman = lastRun ? isoTimeTag(lastRun.completedAt || lastRun.startedAt) : "—";
|
|
314
330
|
// ── Status badges ──────────────────────────────────────────────────────────
|
|
315
331
|
const badgeByStatus = {
|
|
316
332
|
pass: { badge: "badge-pass", dot: "dot-pass", label: "PASS" },
|
|
@@ -353,7 +369,7 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
353
369
|
const snapRows = latest
|
|
354
370
|
? [
|
|
355
371
|
li("Run id", `<code>${esc(latest.id.slice(0, 28))}</code>`),
|
|
356
|
-
li("Completed",
|
|
372
|
+
li("Completed", isoTimeTag(latest.completedAt || latest.startedAt)),
|
|
357
373
|
li("Status", latest.ok ? "✅ ok" : "❌ failed"),
|
|
358
374
|
li("Wall time", fmtMs(latest.wallTimeMs)),
|
|
359
375
|
li("Reflect ok/fail", `${latest.reflectOk} / ${latest.reflectFailed}`),
|
|
@@ -404,7 +420,7 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
404
420
|
const verdictSentence = `${verdictWord} — ${esc(verdictRest)}.`;
|
|
405
421
|
const verdictHtml = `<div class="verdict ${result.status}"><b>Verdict:</b> ${verdictSentence}</div>`;
|
|
406
422
|
// ── Freshness line ─────────────────────────────────────────────────────────
|
|
407
|
-
const freshnessHtml = `<div class="freshness${isStale ? " stale" : ""}">${isStale ? "⚠️ Stale: " : ""}Latest run ${
|
|
423
|
+
const freshnessHtml = `<div class="freshness${isStale ? " stale" : ""}">${isStale ? "⚠️ Stale: " : ""}Latest run ${latestRunHuman} · generated ${isoTimeTag(generatedAt)}${isStale ? " — no recent activity (newest run older than the 6h freshness threshold)." : "."}</div>`;
|
|
408
424
|
const execSummary = `
|
|
409
425
|
<h2>${overallEmoji} Executive Summary
|
|
410
426
|
<span class="badge-pill ${badge.badge}" style="font-size:11px;">
|
|
@@ -513,6 +529,97 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
513
529
|
],
|
|
514
530
|
["LLM wall time", fmtMs(llm.totalDurationMs), trend.latency],
|
|
515
531
|
];
|
|
532
|
+
// #612 — auto-accept gate calibration. Only surface when the gate actually
|
|
533
|
+
// acted on proposals in the window (samples > 0); a default ungated install
|
|
534
|
+
// reports an empty summary and we omit the rows to keep the table parity-clean.
|
|
535
|
+
const calibration = improve.calibration;
|
|
536
|
+
if (calibration && calibration.samples > 0) {
|
|
537
|
+
summaryRows.push([
|
|
538
|
+
"Calibration samples",
|
|
539
|
+
num(calibration.samples),
|
|
540
|
+
"flat",
|
|
541
|
+
"Auto-accept gate decisions (auto-accepted + auto-rejected) the calibration join measured this window.",
|
|
542
|
+
], [
|
|
543
|
+
"Calibration accept rate",
|
|
544
|
+
String(calibration.overallAcceptRate),
|
|
545
|
+
"flat",
|
|
546
|
+
"Realized accept rate of acted-on gate decisions (auto-accepted / total acted-on).",
|
|
547
|
+
], [
|
|
548
|
+
"Calibration gap",
|
|
549
|
+
String(calibration.calibrationGap),
|
|
550
|
+
"flat",
|
|
551
|
+
"Mean predicted confidence minus realized accept rate. Positive = the gate is over-confident.",
|
|
552
|
+
]);
|
|
553
|
+
}
|
|
554
|
+
// WS-5: denominator-fixed coverage rows (only when we have real data).
|
|
555
|
+
if (coverage && !Number.isNaN(coverage.rate)) {
|
|
556
|
+
summaryRows.push([
|
|
557
|
+
"Coverage rate",
|
|
558
|
+
pct(coverage.rate, 1),
|
|
559
|
+
"flat",
|
|
560
|
+
"Accepted proposals / total stash assets (denominator-fixed). Shows what fraction of the corpus has been touched.",
|
|
561
|
+
], [
|
|
562
|
+
"Eligible fraction",
|
|
563
|
+
pct(coverage.eligibleFraction, 1),
|
|
564
|
+
"flat",
|
|
565
|
+
"Eligible assets / total stash assets. Fraction the improve pipeline actively considers.",
|
|
566
|
+
], [
|
|
567
|
+
"Coverage accepted",
|
|
568
|
+
num(coverage.acceptedProposals),
|
|
569
|
+
"up",
|
|
570
|
+
"Total accepted proposals used for the denominator-fixed coverage rate.",
|
|
571
|
+
]);
|
|
572
|
+
}
|
|
573
|
+
// WS-5: perf telemetry rows (only when at least one run reported telemetry).
|
|
574
|
+
if (perf.runsWithTelemetry > 0) {
|
|
575
|
+
const embedCacheTotal = perf.embedCacheHits + perf.embedCacheMisses;
|
|
576
|
+
const embedCacheHitRate = embedCacheTotal > 0 ? pct(perf.embedCacheHits / embedCacheTotal, 1) : "—";
|
|
577
|
+
summaryRows.push([
|
|
578
|
+
"Embed cache hit rate",
|
|
579
|
+
embedCacheHitRate,
|
|
580
|
+
"flat",
|
|
581
|
+
"Fraction of embedding lookups served from cache (>95% is healthy). Aggregated across WS-5 runs.",
|
|
582
|
+
], [
|
|
583
|
+
"Embed wall time",
|
|
584
|
+
fmtMs(perf.embedMs),
|
|
585
|
+
"flat",
|
|
586
|
+
"Cumulative embedding wall-clock time across consolidation runs in the window.",
|
|
587
|
+
], [
|
|
588
|
+
"Judged-cache skipped",
|
|
589
|
+
num(perf.judgedCacheSkipped),
|
|
590
|
+
"flat",
|
|
591
|
+
"Candidates skipped by the judged-cache (not sent to LLM). Higher = more efficient reuse of prior judgments.",
|
|
592
|
+
], [
|
|
593
|
+
"Dedup pool size",
|
|
594
|
+
num(perf.dedupPoolSize),
|
|
595
|
+
"flat",
|
|
596
|
+
"Average memory pool size after deduplication (before judged-cache narrowing). WS-5 perf telemetry.",
|
|
597
|
+
], [
|
|
598
|
+
"Over-budget consolidation runs",
|
|
599
|
+
String(perf.overBudgetRuns),
|
|
600
|
+
perf.overBudgetRuns > 0 ? "down" : "flat",
|
|
601
|
+
"Runs where consolidation alone exceeded the total run budget (estimatedBudgetFractionUsed > 1.0).",
|
|
602
|
+
]);
|
|
603
|
+
}
|
|
604
|
+
// WS-5: degradation metrics rows.
|
|
605
|
+
if (degradation) {
|
|
606
|
+
summaryRows.push([
|
|
607
|
+
"Corpus diversity (Gini)",
|
|
608
|
+
num(degradation.corpusCentroidDistance),
|
|
609
|
+
degradation.entrenchmentFlagged ? "down" : "flat",
|
|
610
|
+
"Gini coefficient of retrieval_salience for top-100 ranked assets. High Gini (>0.35) = entrenchment risk.",
|
|
611
|
+
], [
|
|
612
|
+
"Merge fidelity contradiction rate",
|
|
613
|
+
pct(degradation.mergeFidelityContradictionRate, 1),
|
|
614
|
+
"flat",
|
|
615
|
+
"Fraction of consolidated proposals that involved a contradiction, from consolidation result envelopes.",
|
|
616
|
+
], [
|
|
617
|
+
"High-generation fraction",
|
|
618
|
+
pct(degradation.highGenerationFraction, 1),
|
|
619
|
+
"flat",
|
|
620
|
+
"Fraction of assets with consecutive_no_ops >= 2 (proxy for high-generation assets in the salience table).",
|
|
621
|
+
]);
|
|
622
|
+
}
|
|
516
623
|
const summaryRowsHtml = summaryRows
|
|
517
624
|
.map(([label, value, t, tip]) => {
|
|
518
625
|
const labelHtml = tip ? `<abbr title="${esc(tip)}">${esc(label)}</abbr>` : esc(label);
|
|
@@ -606,6 +713,31 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
606
713
|
descHtml: `Newest run is ${esc(latestRunHuman)} — older than the 6h freshness threshold. Check the improve scheduler/cron.`,
|
|
607
714
|
});
|
|
608
715
|
}
|
|
716
|
+
// WS-5: corpus entrenchment flag.
|
|
717
|
+
if (degradation?.entrenchmentFlagged) {
|
|
718
|
+
pushItem({
|
|
719
|
+
key: "corpus-entrenchment",
|
|
720
|
+
prio: "P2",
|
|
721
|
+
cls: "warn",
|
|
722
|
+
title: "Corpus entrenchment risk: retrieval_salience Gini > 0.35",
|
|
723
|
+
descHtml: "A small set of assets dominates retrieval — retrieval diversity is low. " +
|
|
724
|
+
"Review top-ranked assets for stale or over-represented content. " +
|
|
725
|
+
`Corpus diversity proxy: ${esc(String(degradation.corpusCentroidDistance))}.`,
|
|
726
|
+
remedy: "akm health --format json | jq '.improve.degradation'",
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
// WS-5: over-budget consolidation advisory.
|
|
730
|
+
if (perf.overBudgetRuns > 0) {
|
|
731
|
+
pushItem({
|
|
732
|
+
key: "over-budget-consolidation",
|
|
733
|
+
prio: "P2",
|
|
734
|
+
cls: "warn",
|
|
735
|
+
title: `${perf.overBudgetRuns} consolidation run${perf.overBudgetRuns === 1 ? "" : "s"} exceeded budget`,
|
|
736
|
+
descHtml: "Consolidation phase wall time exceeded the total run budget on these runs. " +
|
|
737
|
+
"Consider increasing the timeout or reducing the consolidation pool via profile config.",
|
|
738
|
+
remedy: "akm config show",
|
|
739
|
+
});
|
|
740
|
+
}
|
|
609
741
|
// Session-log notes (informational, lowest priority).
|
|
610
742
|
if (result.sessionLogAdvisories.length > 0) {
|
|
611
743
|
const patterns = result.sessionLogAdvisories
|
|
@@ -629,24 +761,17 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
629
761
|
? proposals
|
|
630
762
|
.map((p, i) => {
|
|
631
763
|
const tagCls = p.source === "extract" ? "tag-extract" : "tag-consolidate";
|
|
632
|
-
const ts = p.createdAt.slice(0, 16).replace("T", " ");
|
|
633
764
|
return (`<tr><td>${i + 1}</td><td><code>${esc(p.ref)}</code></td>` +
|
|
634
765
|
`<td><span class="tag ${tagCls}">${esc(p.source)}</span></td>` +
|
|
635
|
-
`<td>${
|
|
766
|
+
`<td>${isoTimeTag(p.createdAt)}</td></tr>`);
|
|
636
767
|
})
|
|
637
768
|
.join("\n")
|
|
638
769
|
: '<tr><td colspan="4" style="text-align:center;color:var(--muted);">No pending proposals</td></tr>';
|
|
639
|
-
// ── Commands used ──────────────────────────────────────────────────────────
|
|
640
|
-
const commandsHtml = [
|
|
641
|
-
` <div><span>akm health --since=${esc(opts.window)} --group-by run --format json</span></div>`,
|
|
642
|
-
` <div><span>akm health --since=${esc(opts.window)} --window-compare=${esc(opts.compare)} --format json</span></div>`,
|
|
643
|
-
" <div><span>akm proposal list</span></div>",
|
|
644
|
-
].join("\n");
|
|
645
770
|
return {
|
|
646
771
|
"%%ECHARTS_TAG%%": buildEchartsTag(opts),
|
|
647
772
|
"%%REPORT_TITLE%%": esc(reportTitle),
|
|
648
773
|
"%%WINDOW%%": esc(opts.window),
|
|
649
|
-
"%%SINCE_HUMAN%%":
|
|
774
|
+
"%%SINCE_HUMAN%%": sinceHuman,
|
|
650
775
|
"%%RUN_COUNT%%": num(totalRuns),
|
|
651
776
|
"%%STATUS_BADGE_HTML%%": `${statusBadge}\n ${failBadge}`,
|
|
652
777
|
"%%EXEC_SUMMARY_HTML%%": execSummary,
|
|
@@ -659,7 +784,6 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
659
784
|
"%%ACTION_ITEMS_HTML%%": actionItemsHtml,
|
|
660
785
|
"%%PROPOSAL_ROWS_HTML%%": proposalRowsHtml,
|
|
661
786
|
"%%PROPOSAL_COUNT%%": String(proposals.length),
|
|
662
|
-
"%%COMMANDS_HTML%%": commandsHtml,
|
|
663
787
|
"%%GENERATED_AT%%": esc(generatedAt),
|
|
664
788
|
"%%AKM_VERSION%%": esc(pkgVersion),
|
|
665
789
|
};
|