akm-cli 0.9.0-beta.6 → 0.9.0-rc.0
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 +663 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- 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 +11 -3
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- 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/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -14
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +220 -227
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +153 -147
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1083
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +1313 -1278
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +243 -599
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +55 -4
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +160 -7
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +32 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +510 -2537
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +212 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -0
- package/dist/commands/improve/triage.js +93 -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 +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +7 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -882
- package/dist/commands/read/curate.js +419 -103
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +29 -41
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +15 -1
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +21 -4
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +389 -58
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +21 -18
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +770 -0
- package/dist/core/state-db.js +142 -1091
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +356 -780
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +103 -24
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +106 -0
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +291 -310
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +39 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +7 -1
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +69 -67
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
- package/dist/integrations/harnesses/claude/index.js +2 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +2 -0
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
- package/dist/integrations/harnesses/types.js +1 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +57 -15
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +85 -14
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/helpers.js +29 -1
- package/dist/output/text/workflow.js +1 -0
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2718 -2354
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +15 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/cli.js +1 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +43 -118
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +5 -4
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* (#582). Ports the external akm-health-report skill's collect.py + render.py
|
|
7
7
|
* to TypeScript so the report is generated in-process (no python, no
|
|
8
8
|
* shell-out). The template (`src/assets/templates/html/health.html`) is a
|
|
9
|
-
*
|
|
9
|
+
* strict superset of the skill's report.html; this module computes the 17
|
|
10
10
|
* `%%TOKEN%%` replacements it consumes.
|
|
11
11
|
*
|
|
12
12
|
* Determinism: nothing here depends on Date.now()/Math.random(). Runs are
|
|
@@ -17,13 +17,45 @@ import fs from "node:fs";
|
|
|
17
17
|
import path from "node:path";
|
|
18
18
|
import { escapeHtml } from "../../output/html-render.js";
|
|
19
19
|
import { getDirname } from "../../runtime.js";
|
|
20
|
+
import { pkgVersion } from "../../version.js";
|
|
21
|
+
import { TASK_FAIL_RATE_WARN, } from "./types.js";
|
|
22
|
+
/**
|
|
23
|
+
* Distill skip-reasons hidden from the breakdown chart. `no new signal since
|
|
24
|
+
* last proposal` is the steady-state "nothing changed, nothing to do" outcome —
|
|
25
|
+
* it dominates the histogram and drowns out the actionable reasons, so it is
|
|
26
|
+
* intentionally excluded from the chart (the count still lives in the data).
|
|
27
|
+
*/
|
|
28
|
+
const DISTILL_REASONS_HIDDEN = new Set(["no new signal since last proposal"]);
|
|
20
29
|
const ECHARTS_CDN = "https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js";
|
|
21
30
|
const ECHARTS_VENDOR_PATH = path.join(getDirname(import.meta.url), "../../assets/templates/html/vendor/echarts.min.js");
|
|
22
31
|
// ── Small formatters (ports of render.py helpers) ───────────────────────────
|
|
23
32
|
const esc = escapeHtml;
|
|
33
|
+
/** Emit a <time> element that the browser's JS will reformat to the viewer's local timezone. */
|
|
34
|
+
function isoTimeTag(iso) {
|
|
35
|
+
const fallback = iso.slice(0, 16).replace("T", " ");
|
|
36
|
+
return `<time data-iso="${esc(iso)}">${esc(fallback)}</time>`;
|
|
37
|
+
}
|
|
24
38
|
function num(value) {
|
|
25
39
|
return Math.round(value).toLocaleString("en-US");
|
|
26
40
|
}
|
|
41
|
+
/** Compact token/count formatter, e.g. 127345 → "127K", 1_500_000 → "1.5M". */
|
|
42
|
+
function compact(value) {
|
|
43
|
+
const v = Math.round(value);
|
|
44
|
+
if (Math.abs(v) >= 1_000_000)
|
|
45
|
+
return `${(v / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`;
|
|
46
|
+
if (Math.abs(v) >= 1_000)
|
|
47
|
+
return `${(v / 1_000).toFixed(1).replace(/\.0$/, "")}K`;
|
|
48
|
+
return String(v);
|
|
49
|
+
}
|
|
50
|
+
/** Humanize a camelCase / kebab / snake enum into reader-facing text. */
|
|
51
|
+
function humanize(raw) {
|
|
52
|
+
return raw
|
|
53
|
+
.replace(/[-_]/g, " ")
|
|
54
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
55
|
+
.replace(/\s+/g, " ")
|
|
56
|
+
.trim()
|
|
57
|
+
.replace(/^./, (c) => c.toUpperCase());
|
|
58
|
+
}
|
|
27
59
|
function fmtMs(ms) {
|
|
28
60
|
return ms ? `${(ms / 60000).toFixed(1)}m` : "—";
|
|
29
61
|
}
|
|
@@ -60,6 +92,7 @@ function reshapeRun(r) {
|
|
|
60
92
|
const geMs = ge.durationMs || 0;
|
|
61
93
|
return {
|
|
62
94
|
id: r.id,
|
|
95
|
+
taskId: r.taskId ?? "manual",
|
|
63
96
|
startedAt: r.startedAt,
|
|
64
97
|
completedAt: r.completedAt,
|
|
65
98
|
wallTimeMs: wall,
|
|
@@ -144,11 +177,14 @@ function deltaPill(deltas, key, lowerIsBetter = false) {
|
|
|
144
177
|
const signed = `${raw > 0 ? "+" : ""}${Math.round(raw)}%`;
|
|
145
178
|
return `<span class="trend-pill ${cls}">${arrow} ${signed}</span>`;
|
|
146
179
|
}
|
|
147
|
-
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
180
|
+
const PRIO_RANK = { P1: 0, P2: 1, P3: 2 };
|
|
181
|
+
function actionItemCard(item) {
|
|
182
|
+
const icon = item.cls === "fail" ? "🔴" : item.prio === "P3" ? "🟡" : "⚠️";
|
|
183
|
+
const remedy = item.remedy ? `<div class="remedy">Fix: <code>${esc(item.remedy)}</code></div>` : "";
|
|
184
|
+
return (`<div class="advisory ${item.cls}"><div class="advisory-icon">${icon}</div>` +
|
|
185
|
+
`<div class="advisory-body">` +
|
|
186
|
+
`<div class="title"><span class="prio ${item.prio.toLowerCase()}">${item.prio}</span>${item.title}</div>` +
|
|
187
|
+
`<div class="desc">${item.descHtml}</div>${remedy}</div></div>`);
|
|
152
188
|
}
|
|
153
189
|
function passCard(title, desc) {
|
|
154
190
|
return ('<div class="advisory" style="border-left:3px solid var(--green);">' +
|
|
@@ -168,6 +204,43 @@ function readSemSearch(advisories) {
|
|
|
168
204
|
return { blocked, detail: `${num(entries)} entries, ${num(embeddings)} embeddings` };
|
|
169
205
|
}
|
|
170
206
|
// ── ECharts delivery ─────────────────────────────────────────────────────────
|
|
207
|
+
/** Parse an akm window string (`24h`, `7d`, `30m`, `2w`) to milliseconds; 0 if unparseable. */
|
|
208
|
+
function windowToMs(window) {
|
|
209
|
+
const m = /^(\d+)\s*([mhdw])$/i.exec(window.trim());
|
|
210
|
+
if (!m)
|
|
211
|
+
return 0;
|
|
212
|
+
const n = Number(m[1]);
|
|
213
|
+
const mult = { m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 }[m[2].toLowerCase()] ?? 0;
|
|
214
|
+
return n * mult;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Build the time-slice `<option>`s for the report's filter bar, DERIVED from the
|
|
218
|
+
* actual report window so the choices always make sense (the old hard-coded
|
|
219
|
+
* 1d–21d list was useless on a 24h or 7d report). "All" is the default; the
|
|
220
|
+
* sub-window options carry their cutoff in milliseconds (consumed by
|
|
221
|
+
* filteredRuns), largest first, only those strictly shorter than the window.
|
|
222
|
+
*/
|
|
223
|
+
function buildSliceOptions(window) {
|
|
224
|
+
const windowMs = windowToMs(window);
|
|
225
|
+
const HOUR = 3_600_000;
|
|
226
|
+
const DAY = 86_400_000;
|
|
227
|
+
const candidates = [
|
|
228
|
+
[6 * HOUR, "6h"],
|
|
229
|
+
[12 * HOUR, "12h"],
|
|
230
|
+
[DAY, "1d"],
|
|
231
|
+
[3 * DAY, "3d"],
|
|
232
|
+
[7 * DAY, "7d"],
|
|
233
|
+
[14 * DAY, "14d"],
|
|
234
|
+
];
|
|
235
|
+
const subs = candidates
|
|
236
|
+
.filter(([ms]) => windowMs > 0 && ms < windowMs)
|
|
237
|
+
.sort((a, b) => b[0] - a[0])
|
|
238
|
+
.slice(0, 4);
|
|
239
|
+
const opts = [`<option value="all" selected>All (${esc(window)})</option>`];
|
|
240
|
+
for (const [ms, label] of subs)
|
|
241
|
+
opts.push(`<option value="${ms}">Last ${label}</option>`);
|
|
242
|
+
return opts.join("\n ");
|
|
243
|
+
}
|
|
171
244
|
function buildEchartsTag(opts) {
|
|
172
245
|
const mode = opts.echarts ?? (process.env.AKM_ECHARTS === "cdn" ? "cdn" : "inline");
|
|
173
246
|
if (mode === "cdn")
|
|
@@ -182,7 +255,8 @@ function buildEchartsTag(opts) {
|
|
|
182
255
|
* Compute all 17 `%%TOKEN%%` replacements for the health HTML template.
|
|
183
256
|
* There is deliberately NO standalone `%%OVERALL_STATUS%%` token — the
|
|
184
257
|
* overall status is embedded in the pre-rendered badge / exec-summary
|
|
185
|
-
* fragments, matching the skill template.
|
|
258
|
+
* fragments, matching the skill template. Advisories and "what to watch" are
|
|
259
|
+
* merged + de-duplicated into a single prioritized `%%ACTION_ITEMS_HTML%%`.
|
|
186
260
|
*/
|
|
187
261
|
export function buildHealthHtmlReplacements(result, opts) {
|
|
188
262
|
const deltas = opts.deltas ?? {};
|
|
@@ -196,6 +270,32 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
196
270
|
const mi = improve.memoryInference;
|
|
197
271
|
const ge = improve.graphExtraction;
|
|
198
272
|
const wallTime = improve.wallTime;
|
|
273
|
+
// WS-5: perf telemetry, coverage, and degradation metrics.
|
|
274
|
+
const perf = improve.perfTelemetry ?? {
|
|
275
|
+
dedupPoolSize: 0,
|
|
276
|
+
llmPoolSize: 0,
|
|
277
|
+
judgedCacheSkipped: 0,
|
|
278
|
+
embedMs: 0,
|
|
279
|
+
embedCacheHits: 0,
|
|
280
|
+
embedCacheMisses: 0,
|
|
281
|
+
overBudgetRuns: 0,
|
|
282
|
+
runsWithTelemetry: 0,
|
|
283
|
+
};
|
|
284
|
+
const coverage = improve.coverage;
|
|
285
|
+
const degradation = improve.degradation;
|
|
286
|
+
const minting = improve.enrichmentMinting;
|
|
287
|
+
// #576: real per-stage LLM token/time accounting (replaces the GPU-time
|
|
288
|
+
// proxy). Optional-guarded so reports built from older health JSON without
|
|
289
|
+
// the aggregate still render.
|
|
290
|
+
const llm = result.metrics.llmUsage ?? {
|
|
291
|
+
calls: 0,
|
|
292
|
+
totalDurationMs: 0,
|
|
293
|
+
promptTokens: 0,
|
|
294
|
+
completionTokens: 0,
|
|
295
|
+
totalTokens: 0,
|
|
296
|
+
reasoningTokens: 0,
|
|
297
|
+
byStage: {},
|
|
298
|
+
};
|
|
199
299
|
const totalRuns = runs.length;
|
|
200
300
|
const failedRuns = runs.filter((r) => !r.ok).length;
|
|
201
301
|
const invoked = improve.invoked || totalRuns;
|
|
@@ -212,11 +312,23 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
212
312
|
// ── Meta (collect.py steps 10-11) ──────────────────────────────────────────
|
|
213
313
|
const sinceIso = result.since;
|
|
214
314
|
const reportDate = sinceIso.slice(0, 10);
|
|
215
|
-
const sinceHuman = sinceIso ? `${sinceIso
|
|
315
|
+
const sinceHuman = sinceIso ? `${isoTimeTag(sinceIso)} → now` : `last ${esc(opts.window)}`;
|
|
216
316
|
const reportTitle = reportDate ? `AKM Health Report — ${reportDate}` : "AKM Health Report";
|
|
217
317
|
const lastRun = runs[runs.length - 1];
|
|
218
318
|
const generatedAt = lastRun ? lastRun.completedAt || lastRun.startedAt || sinceIso : sinceIso;
|
|
219
319
|
const latest = [...runs].reverse().find((r) => r.ok) ?? lastRun;
|
|
320
|
+
// Freshness: surface the newest run's timestamp + the generated-at anchor in
|
|
321
|
+
// the exec card. Staleness is computed deterministically (no Date.now()) from
|
|
322
|
+
// the gap between the window start (`since`) and the newest run we have: if no
|
|
323
|
+
// run landed in the window, or the newest run sits in the first 25% of a
|
|
324
|
+
// window wider than the 6h threshold (i.e. a long idle tail), we flag stale.
|
|
325
|
+
const STALE_MS = 6 * 60 * 60 * 1000;
|
|
326
|
+
const latestRunMs = lastRun ? Date.parse(lastRun.completedAt || lastRun.startedAt) : NaN;
|
|
327
|
+
const sinceMs = Date.parse(sinceIso);
|
|
328
|
+
const generatedMs = Date.parse(generatedAt);
|
|
329
|
+
const idleTailMs = Number.isFinite(latestRunMs) && Number.isFinite(generatedMs) ? Math.max(0, generatedMs - latestRunMs) : 0;
|
|
330
|
+
const isStale = totalRuns === 0 || (Number.isFinite(latestRunMs) && Number.isFinite(sinceMs) && idleTailMs > STALE_MS);
|
|
331
|
+
const latestRunHuman = lastRun ? isoTimeTag(lastRun.completedAt || lastRun.startedAt) : "—";
|
|
220
332
|
// ── Status badges ──────────────────────────────────────────────────────────
|
|
221
333
|
const badgeByStatus = {
|
|
222
334
|
pass: { badge: "badge-pass", dot: "dot-pass", label: "PASS" },
|
|
@@ -225,7 +337,7 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
225
337
|
};
|
|
226
338
|
const badge = badgeByStatus[result.status];
|
|
227
339
|
const statusBadge = `<span class="badge-pill ${badge.badge}"><span class="dot ${badge.dot}"></span>${badge.label}</span>`;
|
|
228
|
-
const failOk = result.metrics.taskFailRate <
|
|
340
|
+
const failOk = result.metrics.taskFailRate < TASK_FAIL_RATE_WARN;
|
|
229
341
|
const failBadge = `<span class="badge-pill ${failOk ? "badge-pass" : "badge-warn"}">` +
|
|
230
342
|
`<span class="dot ${failOk ? "dot-pass" : "dot-warn"}"></span>${taskFailRate} Fail Rate</span>`;
|
|
231
343
|
// ── Executive summary ──────────────────────────────────────────────────────
|
|
@@ -238,7 +350,7 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
238
350
|
li("MI yield rate", miYieldRate),
|
|
239
351
|
li("MI written", num(miWritten)),
|
|
240
352
|
li("Consolidation promoted", num(cons.promoted)),
|
|
241
|
-
li("Consolidation
|
|
353
|
+
li("Consolidation judged: no action", `<abbr title="Candidates the consolidator reviewed but intentionally left unchanged (the 'judgedNoAction' field).">${num(cons.judgedNoAction)}</abbr>`),
|
|
242
354
|
li("Chunk failure", chunkFail),
|
|
243
355
|
li("Median wall time", fmtMs(wallTime.medianMs)),
|
|
244
356
|
li("P95 wall time", fmtMs(wallTime.p95Ms)),
|
|
@@ -259,12 +371,12 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
259
371
|
const snapRows = latest
|
|
260
372
|
? [
|
|
261
373
|
li("Run id", `<code>${esc(latest.id.slice(0, 28))}</code>`),
|
|
262
|
-
li("Completed",
|
|
374
|
+
li("Completed", isoTimeTag(latest.completedAt || latest.startedAt)),
|
|
263
375
|
li("Status", latest.ok ? "✅ ok" : "❌ failed"),
|
|
264
376
|
li("Wall time", fmtMs(latest.wallTimeMs)),
|
|
265
377
|
li("Reflect ok/fail", `${latest.reflectOk} / ${latest.reflectFailed}`),
|
|
266
378
|
li("Promoted", String(latest.promoted)),
|
|
267
|
-
li("
|
|
379
|
+
li("Judged: no action", `<abbr title="Candidates reviewed but intentionally left unchanged on this run.">${latest.judgedNoAction}</abbr>`),
|
|
268
380
|
li("MI written", String(latest.miWritten)),
|
|
269
381
|
li("Graph entities/relations", `${latest.geEntities} / ${latest.geRelations}`),
|
|
270
382
|
].join("")
|
|
@@ -273,16 +385,50 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
273
385
|
li("Report window", esc(opts.window)),
|
|
274
386
|
li("Compare window", esc(opts.compare)),
|
|
275
387
|
li("Runs", `${num(totalRuns)} (${failedRuns} failed)`),
|
|
276
|
-
li("Stash derived", num(improve.memorySummary.derived)),
|
|
277
|
-
li("Stash eligible", num(improve.memorySummary.eligible)),
|
|
388
|
+
li("Stash derived", `<abbr title="Whole-stash recount of derived assets at report time — not a per-run sum.">${num(improve.memorySummary.derived)}</abbr>`),
|
|
389
|
+
li("Stash eligible", `<abbr title="Whole-stash recount of eligible assets at report time — not a per-run sum.">${num(improve.memorySummary.eligible)}</abbr>`),
|
|
278
390
|
li("Pending proposals", String(proposals.length)),
|
|
279
391
|
li("Semantic search", sem.blocked ? "BLOCKED" : "OK"),
|
|
280
392
|
].join("");
|
|
281
393
|
const overallEmoji = trend.overall === "improving" ? "📈" : trend.overall === "degrading" ? "📉" : "↔️";
|
|
394
|
+
// ── Synthesized verdict (one sentence + 2-3 drivers) ───────────────────────
|
|
395
|
+
// The verdict WORD is the authoritative health status (hard checks). Concerns
|
|
396
|
+
// (failed improve runs, blocked search, pending proposals) are advisory and do
|
|
397
|
+
// NOT gate that word, so we must not pair a green PASS with red "drivers" as
|
|
398
|
+
// if they were failures. When the status is PASS we frame concerns as "watch:"
|
|
399
|
+
// alongside the trend; only a degraded status (WARN/FAIL) leads with them.
|
|
400
|
+
const concerns = [];
|
|
401
|
+
if (failedRuns > 0)
|
|
402
|
+
concerns.push(`${failedRuns} failed run${failedRuns === 1 ? "" : "s"}`);
|
|
403
|
+
if (sem.blocked)
|
|
404
|
+
concerns.push("semantic search blocked");
|
|
405
|
+
if (proposals.length > 0)
|
|
406
|
+
concerns.push(`${proposals.length} pending proposal${proposals.length === 1 ? "" : "s"}`);
|
|
407
|
+
const trendClause = trend.overall === "improving"
|
|
408
|
+
? "throughput and latency improving"
|
|
409
|
+
: trend.overall === "degrading"
|
|
410
|
+
? "throughput or latency degrading"
|
|
411
|
+
: "throughput and latency steady";
|
|
412
|
+
const verdictWord = badge.label;
|
|
413
|
+
let verdictRest;
|
|
414
|
+
if (result.status === "pass") {
|
|
415
|
+
const watch = concerns.length > 0 ? `; watch: ${concerns.slice(0, 3).join(", ")}` : "";
|
|
416
|
+
verdictRest = `healthy, ${trendClause}${watch}`;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const lead = concerns.length > 0 ? concerns.slice(0, 3).join("; ") : trendClause;
|
|
420
|
+
verdictRest = trend.overall === "degrading" && concerns.length > 0 ? `${lead}; ${trendClause}` : lead;
|
|
421
|
+
}
|
|
422
|
+
const verdictSentence = `${verdictWord} — ${esc(verdictRest)}.`;
|
|
423
|
+
const verdictHtml = `<div class="verdict ${result.status}"><b>Verdict:</b> ${verdictSentence}</div>`;
|
|
424
|
+
// ── Freshness line ─────────────────────────────────────────────────────────
|
|
425
|
+
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>`;
|
|
282
426
|
const execSummary = `
|
|
283
427
|
<h2>${overallEmoji} Executive Summary
|
|
284
428
|
<span class="badge-pill ${badge.badge}" style="font-size:11px;">
|
|
285
429
|
<span class="dot ${badge.dot}"></span>${badge.label}</span></h2>
|
|
430
|
+
${verdictHtml}
|
|
431
|
+
${freshnessHtml}
|
|
286
432
|
<div class="exec-grid">
|
|
287
433
|
<div>
|
|
288
434
|
<h4>Quick Numbers</h4>
|
|
@@ -306,143 +452,363 @@ export function buildHealthHtmlReplacements(result, opts) {
|
|
|
306
452
|
<div class="overall">Overall trend: <b>${esc(trend.overall)}</b> ${overallEmoji}
|
|
307
453
|
· based on decision quality, output volume, failures, and latency vs the prior window.</div>`.trim();
|
|
308
454
|
// ── KPI cards ──────────────────────────────────────────────────────────────
|
|
455
|
+
// Color is a health SIGNAL, not decoration: green/yellow/red where a card has
|
|
456
|
+
// a meaningful threshold; "neutral" for purely-informational counts. Cards are
|
|
457
|
+
// ordered by operator priority (failures first, informational counts last).
|
|
309
458
|
const semValue = sem.blocked ? "BLOCKED" : "OK";
|
|
310
|
-
const semColor = sem.blocked ? "
|
|
459
|
+
const semColor = sem.blocked ? "red" : "green";
|
|
311
460
|
const semStyle = sem.blocked ? "font-size:18px;" : "";
|
|
461
|
+
const completionPct = invoked ? (100 * completed) / invoked : 100;
|
|
462
|
+
const completionColor = completionPct >= 99 ? "green" : completionPct >= 90 ? "yellow" : "red";
|
|
463
|
+
const llmTokensCompact = compact(llm.totalTokens);
|
|
312
464
|
const kpiCard = (color, label, value, sub, valueStyle = "") => `<div class="kpi-card ${color}">
|
|
313
465
|
<div class="label">${label}</div>
|
|
314
466
|
<div class="value"${valueStyle ? ` style="${valueStyle}"` : ""}>${value}</div>
|
|
315
467
|
<div class="sub">${sub}</div>
|
|
316
468
|
</div>`;
|
|
317
469
|
const kpiCards = [
|
|
318
|
-
kpiCard(failedRuns === 0 ? "green" : "yellow", "Completion Rate", completionRate, `${num(completed)} / ${num(invoked)} invoked`),
|
|
319
470
|
kpiCard(failedRuns === 0 ? "green" : "red", "Failed Runs", String(failedRuns), `of ${num(totalRuns)} runs · ${taskFailRate} task fail`),
|
|
471
|
+
kpiCard(completionColor, "Completion Rate", completionRate, `${num(completed)} / ${num(invoked)} invoked`),
|
|
472
|
+
kpiCard("neutral", "Median Duration", `${medianDurMin}m`, `p95 = ${p95DurMin}m`),
|
|
320
473
|
kpiCard("blue", "Total Promoted", num(cons.promoted), `avg ${avgPromoted} / run`),
|
|
321
474
|
kpiCard("blue", "MI Written", num(miWritten), `${miYieldRate} yield rate`),
|
|
322
475
|
kpiCard("purple", "Graph Entities", num(ge.entities), `+${num(ge.relations)} relations`),
|
|
323
|
-
kpiCard("
|
|
324
|
-
|
|
476
|
+
kpiCard("neutral", "Stash Derived", num(improve.memorySummary.derived), `of ${num(improve.memorySummary.eligible)} eligible (whole-stash)`),
|
|
477
|
+
// #576: real LLM work — duration leads, tokens compact, not a GPU proxy.
|
|
478
|
+
kpiCard(llm.calls > 0 ? "purple" : "neutral", "🧠 LLM Work", `${llmTokensCompact} tok`, `${fmtMs(llm.totalDurationMs)} · ${num(llm.calls)} calls · ${compact(llm.reasoningTokens)} reasoning`),
|
|
325
479
|
kpiCard(semColor, "Semantic Search", semValue, esc(sem.detail), semStyle),
|
|
326
|
-
kpiCard("yellow", "Pending Proposals", String(proposals.length), `from ${esc(opts.window)} batch`),
|
|
480
|
+
kpiCard(proposals.length > 0 ? "yellow" : "neutral", "Pending Proposals", String(proposals.length), `from ${esc(opts.window)} batch`),
|
|
327
481
|
].join("\n");
|
|
328
482
|
// ── Chart payload ──────────────────────────────────────────────────────────
|
|
329
|
-
const distillReasons = [...new Set(runs.flatMap((r) => Object.keys(r.distillByReason)))]
|
|
483
|
+
const distillReasons = [...new Set(runs.flatMap((r) => Object.keys(r.distillByReason)))]
|
|
484
|
+
.filter((reason) => !DISTILL_REASONS_HIDDEN.has(reason))
|
|
485
|
+
.sort();
|
|
330
486
|
const runsJsConst = `const RUNS = ${JSON.stringify(runs)};`;
|
|
331
487
|
// ── Summary table rows ─────────────────────────────────────────────────────
|
|
488
|
+
// Optional 4th element = a glossary tooltip rendered as <abbr title>.
|
|
332
489
|
const summaryRows = [
|
|
333
490
|
["Task fail rate", taskFailRate, "flat"],
|
|
334
491
|
["Agent fail rate", agentFailRate, "flat"],
|
|
335
492
|
["Improve completion", `${num(completed)} / ${num(invoked)}`, "flat"],
|
|
336
|
-
[
|
|
337
|
-
|
|
493
|
+
[
|
|
494
|
+
"MI yield rate",
|
|
495
|
+
miYieldRate,
|
|
496
|
+
trend.decisionQuality,
|
|
497
|
+
"Memory-inference yield: share of considered candidates that produced a written fact.",
|
|
498
|
+
],
|
|
499
|
+
["MI written", num(miWritten), trend.outputVolume, "Memory-inference: facts written this window."],
|
|
338
500
|
["Consolidation promoted", num(cons.promoted), trend.outputVolume],
|
|
339
501
|
["Consolidation merged", num(cons.merged), "flat"],
|
|
340
502
|
["Consolidation deleted", num(cons.deleted), "flat"],
|
|
341
503
|
["Consolidation contradicted", num(cons.contradicted), "flat"],
|
|
342
|
-
[
|
|
504
|
+
[
|
|
505
|
+
"Consolidation judged: no action",
|
|
506
|
+
num(cons.judgedNoAction),
|
|
507
|
+
"flat",
|
|
508
|
+
"Candidates reviewed but intentionally left unchanged (the 'judgedNoAction' field).",
|
|
509
|
+
],
|
|
343
510
|
["Chunk failure", chunkFail, "flat"],
|
|
344
511
|
["Graph entities", num(ge.entities), "up"],
|
|
345
512
|
["Graph relations", num(ge.relations), "up"],
|
|
346
|
-
[
|
|
513
|
+
[
|
|
514
|
+
"Stash derived",
|
|
515
|
+
num(improve.memorySummary.derived),
|
|
516
|
+
"up",
|
|
517
|
+
"Whole-stash recount of derived assets at report time — not a per-run sum.",
|
|
518
|
+
],
|
|
347
519
|
["Median wall time", fmtMs(wallTime.medianMs), trend.latency],
|
|
348
520
|
["P95 wall time", fmtMs(wallTime.p95Ms), trend.latency],
|
|
521
|
+
// #576: real LLM accounting (replaces the GPU-time proxy).
|
|
522
|
+
["LLM calls", num(llm.calls), "flat"],
|
|
523
|
+
["LLM total tokens", num(llm.totalTokens), "flat"],
|
|
524
|
+
["LLM prompt tokens", num(llm.promptTokens), "flat"],
|
|
525
|
+
["LLM completion tokens", num(llm.completionTokens), "flat"],
|
|
526
|
+
[
|
|
527
|
+
"LLM reasoning tokens",
|
|
528
|
+
num(llm.reasoningTokens),
|
|
529
|
+
"flat",
|
|
530
|
+
"Tokens spent on model reasoning/thinking, billed separately from prompt and completion.",
|
|
531
|
+
],
|
|
532
|
+
["LLM wall time", fmtMs(llm.totalDurationMs), trend.latency],
|
|
349
533
|
];
|
|
534
|
+
// #612 — auto-accept gate calibration. Only surface when the gate actually
|
|
535
|
+
// acted on proposals in the window (samples > 0); a default ungated install
|
|
536
|
+
// reports an empty summary and we omit the rows to keep the table parity-clean.
|
|
537
|
+
const calibration = improve.calibration;
|
|
538
|
+
if (calibration && calibration.samples > 0) {
|
|
539
|
+
summaryRows.push([
|
|
540
|
+
"Calibration samples",
|
|
541
|
+
num(calibration.samples),
|
|
542
|
+
"flat",
|
|
543
|
+
"Auto-accept gate decisions (auto-accepted + auto-rejected) the calibration join measured this window.",
|
|
544
|
+
], [
|
|
545
|
+
"Calibration accept rate",
|
|
546
|
+
String(calibration.overallAcceptRate),
|
|
547
|
+
"flat",
|
|
548
|
+
"Realized accept rate of acted-on gate decisions (auto-accepted / total acted-on).",
|
|
549
|
+
], [
|
|
550
|
+
"Calibration gap",
|
|
551
|
+
String(calibration.calibrationGap),
|
|
552
|
+
"flat",
|
|
553
|
+
"Mean predicted confidence minus realized accept rate. Positive = the gate is over-confident.",
|
|
554
|
+
]);
|
|
555
|
+
}
|
|
556
|
+
// WS-5: denominator-fixed coverage rows (only when we have real data).
|
|
557
|
+
if (coverage && !Number.isNaN(coverage.rate)) {
|
|
558
|
+
summaryRows.push([
|
|
559
|
+
"Coverage rate",
|
|
560
|
+
pct(coverage.rate, 1),
|
|
561
|
+
"flat",
|
|
562
|
+
"Distinct accepted refs / total stash assets (denominator-fixed). Shows what fraction of the corpus has been touched.",
|
|
563
|
+
], [
|
|
564
|
+
"Eligible fraction",
|
|
565
|
+
pct(coverage.eligibleFraction, 1),
|
|
566
|
+
"flat",
|
|
567
|
+
"Eligible assets / total stash assets. Fraction the improve pipeline actively considers.",
|
|
568
|
+
], [
|
|
569
|
+
"Coverage accepted",
|
|
570
|
+
num(coverage.acceptedProposals),
|
|
571
|
+
"flat",
|
|
572
|
+
"Total accepted proposals in the window (raw volume — includes repeated rewrites of the same asset).",
|
|
573
|
+
], [
|
|
574
|
+
"Churn ratio",
|
|
575
|
+
Number.isFinite(coverage.churnRatio) ? num(coverage.churnRatio) : "—",
|
|
576
|
+
Number.isFinite(coverage.churnRatio) && coverage.churnRatio > 1.5 ? "down" : "flat",
|
|
577
|
+
"Accepted proposals / distinct refs touched. >1.5 = the loop is repeatedly rewriting the same assets (churn, not coverage).",
|
|
578
|
+
]);
|
|
579
|
+
}
|
|
580
|
+
// Enrichment-vs-minting policy rollup (reporting-only).
|
|
581
|
+
if (minting && Number.isFinite(minting.share)) {
|
|
582
|
+
summaryRows.push([
|
|
583
|
+
"Enrichment-lane minted share",
|
|
584
|
+
pct(minting.share, 1),
|
|
585
|
+
minting.share > 0.05 ? "down" : "flat",
|
|
586
|
+
`New assets minted by enrichment lanes / their accepted total (${minting.minted} minted vs ${minting.updated} updated). Enrichment lanes are ratified to edit existing assets only; WARN >5%, FAIL >15%.`,
|
|
587
|
+
]);
|
|
588
|
+
}
|
|
589
|
+
// WS-5: perf telemetry rows (only when at least one run reported telemetry).
|
|
590
|
+
if (perf.runsWithTelemetry > 0) {
|
|
591
|
+
const embedCacheTotal = perf.embedCacheHits + perf.embedCacheMisses;
|
|
592
|
+
const embedCacheHitRate = embedCacheTotal > 0 ? pct(perf.embedCacheHits / embedCacheTotal, 1) : "—";
|
|
593
|
+
summaryRows.push([
|
|
594
|
+
"Embed cache hit rate",
|
|
595
|
+
embedCacheHitRate,
|
|
596
|
+
"flat",
|
|
597
|
+
"Fraction of embedding lookups served from cache (>95% is healthy). Aggregated across WS-5 runs.",
|
|
598
|
+
], [
|
|
599
|
+
"Embed wall time",
|
|
600
|
+
fmtMs(perf.embedMs),
|
|
601
|
+
"flat",
|
|
602
|
+
"Cumulative embedding wall-clock time across consolidation runs in the window.",
|
|
603
|
+
], [
|
|
604
|
+
"Judged-cache skipped",
|
|
605
|
+
num(perf.judgedCacheSkipped),
|
|
606
|
+
"flat",
|
|
607
|
+
"Candidates skipped by the judged-cache (not sent to LLM). Higher = more efficient reuse of prior judgments.",
|
|
608
|
+
], [
|
|
609
|
+
"Dedup pool size",
|
|
610
|
+
num(perf.dedupPoolSize),
|
|
611
|
+
"flat",
|
|
612
|
+
"Average memory pool size after deduplication (before judged-cache narrowing). WS-5 perf telemetry.",
|
|
613
|
+
], [
|
|
614
|
+
"Over-budget consolidation runs",
|
|
615
|
+
String(perf.overBudgetRuns),
|
|
616
|
+
perf.overBudgetRuns > 0 ? "down" : "flat",
|
|
617
|
+
"Runs where consolidation alone exceeded the total run budget (estimatedBudgetFractionUsed > 1.0).",
|
|
618
|
+
]);
|
|
619
|
+
}
|
|
620
|
+
// WS-5: degradation metrics rows.
|
|
621
|
+
if (degradation) {
|
|
622
|
+
summaryRows.push([
|
|
623
|
+
"Corpus diversity (Gini)",
|
|
624
|
+
num(degradation.corpusCentroidDistance),
|
|
625
|
+
degradation.entrenchmentFlagged || degradation.salienceUniformityFlagged ? "down" : "flat",
|
|
626
|
+
"Gini coefficient of retrieval_salience for top-100 ranked assets. Two-tailed: >0.35 = entrenchment risk; <0.08 = collapsed toward uniform (ranking no longer discriminates).",
|
|
627
|
+
], [
|
|
628
|
+
"Merge fidelity contradiction rate",
|
|
629
|
+
pct(degradation.mergeFidelityContradictionRate, 1),
|
|
630
|
+
"flat",
|
|
631
|
+
"Fraction of consolidated proposals that involved a contradiction, from consolidation result envelopes.",
|
|
632
|
+
]);
|
|
633
|
+
}
|
|
350
634
|
const summaryRowsHtml = summaryRows
|
|
351
|
-
.map(([label, value, t]) =>
|
|
352
|
-
`<
|
|
635
|
+
.map(([label, value, t, tip]) => {
|
|
636
|
+
const labelHtml = tip ? `<abbr title="${esc(tip)}">${esc(label)}</abbr>` : esc(label);
|
|
637
|
+
return (` <tr><td>${labelHtml}</td><td>${esc(value)}</td>` +
|
|
638
|
+
`<td class="trend ${trendClass(t)}">${trendLabel(t)}</td></tr>`);
|
|
639
|
+
})
|
|
353
640
|
.join("\n");
|
|
354
|
-
// ──
|
|
355
|
-
|
|
641
|
+
// ── Action Items (merged + de-duplicated advisories + what-to-watch) ────────
|
|
642
|
+
// Advisories and the old "what to watch" cards were built from the same data
|
|
643
|
+
// (result.advisories, sem-blocked, proposals, tail-latency, failed-runs). We
|
|
644
|
+
// collapse them into ONE prioritized, de-duplicated list (P1/P2/P3 + a
|
|
645
|
+
// remediation command per item), keyed so each concern appears exactly once.
|
|
646
|
+
const items = [];
|
|
647
|
+
const seen = new Set();
|
|
648
|
+
const pushItem = (item) => {
|
|
649
|
+
if (seen.has(item.key))
|
|
650
|
+
return;
|
|
651
|
+
seen.add(item.key);
|
|
652
|
+
items.push(item);
|
|
653
|
+
};
|
|
654
|
+
// Hard advisories from the health check engine (own remediation in message).
|
|
356
655
|
for (const a of result.advisories) {
|
|
357
656
|
if (a.status !== "warn" && a.status !== "fail")
|
|
358
657
|
continue;
|
|
359
|
-
|
|
658
|
+
pushItem({
|
|
659
|
+
key: `advisory:${a.name}`,
|
|
660
|
+
prio: a.status === "fail" ? "P1" : "P2",
|
|
661
|
+
cls: a.status === "fail" ? "fail" : "warn",
|
|
662
|
+
title: esc(humanize(a.name)),
|
|
663
|
+
descHtml: esc(a.message),
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
// Failed runs in window.
|
|
667
|
+
if (failedRuns > 0) {
|
|
668
|
+
pushItem({
|
|
669
|
+
key: "failed-runs",
|
|
670
|
+
prio: "P1",
|
|
671
|
+
cls: "fail",
|
|
672
|
+
title: `${failedRuns} failed run${failedRuns === 1 ? "" : "s"} in window`,
|
|
673
|
+
descHtml: `Task fail rate ${esc(taskFailRate)}. Inspect failed runs (ok=false) for early-exit or harness errors.`,
|
|
674
|
+
remedy: `akm health --since=${opts.window} --group-by run`,
|
|
675
|
+
});
|
|
360
676
|
}
|
|
677
|
+
// Semantic search blocked.
|
|
361
678
|
if (sem.blocked) {
|
|
362
|
-
|
|
679
|
+
pushItem({
|
|
680
|
+
key: "semantic-search-blocked",
|
|
681
|
+
prio: "P2",
|
|
682
|
+
cls: "warn",
|
|
683
|
+
title: "Semantic search blocked",
|
|
684
|
+
descHtml: `Embedding provider unreachable. ${esc(sem.detail)}. Curate falls back to keyword search — relevance scoring degraded.`,
|
|
685
|
+
remedy: "akm config show",
|
|
686
|
+
});
|
|
363
687
|
}
|
|
688
|
+
// Pending proposals to drain.
|
|
364
689
|
if (proposals.length > 0) {
|
|
365
|
-
|
|
690
|
+
const bySource = new Map();
|
|
691
|
+
for (const p of proposals)
|
|
692
|
+
bySource.set(p.source, (bySource.get(p.source) ?? 0) + 1);
|
|
693
|
+
const srcSummary = [...bySource.entries()]
|
|
694
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
695
|
+
.map(([source, count]) => `${count} via ${esc(source)}`)
|
|
696
|
+
.join(", ");
|
|
697
|
+
pushItem({
|
|
698
|
+
key: "drain-proposals",
|
|
699
|
+
prio: "P2",
|
|
700
|
+
cls: "warn",
|
|
701
|
+
title: `Drain ${proposals.length} pending proposal${proposals.length === 1 ? "" : "s"}`,
|
|
702
|
+
descHtml: `Proposals generated this batch (${srcSummary}). Review before the queue grows further.`,
|
|
703
|
+
remedy: "akm proposal list",
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
// High tail latency.
|
|
707
|
+
if (wallTime.p95Ms && wallTime.medianMs && wallTime.p95Ms / wallTime.medianMs > 2.5) {
|
|
708
|
+
pushItem({
|
|
709
|
+
key: "tail-latency",
|
|
710
|
+
prio: "P3",
|
|
711
|
+
cls: "warn",
|
|
712
|
+
title: `High tail latency: p95=${fmtMs(wallTime.p95Ms)}, median=${fmtMs(wallTime.medianMs)}`,
|
|
713
|
+
descHtml: "P95 is well above median. Consolidation/LLM phase dominates wall time on slow runs. " +
|
|
714
|
+
"Check for slow chunks or LLM rate limiting.",
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
// Stale freshness.
|
|
718
|
+
if (isStale && totalRuns > 0) {
|
|
719
|
+
pushItem({
|
|
720
|
+
key: "stale",
|
|
721
|
+
prio: "P3",
|
|
722
|
+
cls: "warn",
|
|
723
|
+
title: "No recent improve runs",
|
|
724
|
+
descHtml: `Newest run is ${esc(latestRunHuman)} — older than the 6h freshness threshold. Check the improve scheduler/cron.`,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
// WS-5: corpus entrenchment flag.
|
|
728
|
+
if (degradation?.entrenchmentFlagged) {
|
|
729
|
+
pushItem({
|
|
730
|
+
key: "corpus-entrenchment",
|
|
731
|
+
prio: "P2",
|
|
732
|
+
cls: "warn",
|
|
733
|
+
title: "Corpus entrenchment risk: retrieval_salience Gini > 0.35",
|
|
734
|
+
descHtml: "A small set of assets dominates retrieval — retrieval diversity is low. " +
|
|
735
|
+
"Review top-ranked assets for stale or over-represented content. " +
|
|
736
|
+
`Corpus diversity proxy: ${esc(String(degradation.corpusCentroidDistance))}.`,
|
|
737
|
+
remedy: "akm health --format json | jq '.improve.degradation'",
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
// Low-tail companion: salience distribution collapsed toward uniform.
|
|
741
|
+
if (degradation?.salienceUniformityFlagged) {
|
|
742
|
+
pushItem({
|
|
743
|
+
key: "salience-uniformity-collapse",
|
|
744
|
+
prio: "P2",
|
|
745
|
+
cls: "warn",
|
|
746
|
+
title: "Salience distribution collapsed: retrieval_salience Gini < 0.08",
|
|
747
|
+
descHtml: "The top-100 salience scores are near-uniform (uniform baseline ≈ 0.1) — " +
|
|
748
|
+
"ranking currently carries little to no discrimination between assets. " +
|
|
749
|
+
`Corpus diversity proxy: ${esc(String(degradation.corpusCentroidDistance))}.`,
|
|
750
|
+
remedy: "akm health --format json | jq '.improve.degradation'",
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
// WS-5: over-budget consolidation advisory.
|
|
754
|
+
if (perf.overBudgetRuns > 0) {
|
|
755
|
+
pushItem({
|
|
756
|
+
key: "over-budget-consolidation",
|
|
757
|
+
prio: "P2",
|
|
758
|
+
cls: "warn",
|
|
759
|
+
title: `${perf.overBudgetRuns} consolidation run${perf.overBudgetRuns === 1 ? "" : "s"} exceeded budget`,
|
|
760
|
+
descHtml: "Consolidation phase wall time exceeded the total run budget on these runs. " +
|
|
761
|
+
"Consider increasing the timeout or reducing the consolidation pool via profile config.",
|
|
762
|
+
remedy: "akm config show",
|
|
763
|
+
});
|
|
366
764
|
}
|
|
765
|
+
// Session-log notes (informational, lowest priority).
|
|
367
766
|
if (result.sessionLogAdvisories.length > 0) {
|
|
368
767
|
const patterns = result.sessionLogAdvisories
|
|
369
768
|
.slice(0, 6)
|
|
370
769
|
.map((p) => `<li>${esc(p.topic)}</li>`)
|
|
371
770
|
.join("");
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
771
|
+
pushItem({
|
|
772
|
+
key: "session-log-notes",
|
|
773
|
+
prio: "P3",
|
|
774
|
+
cls: "warn",
|
|
775
|
+
title: `${result.sessionLogAdvisories.length} session-log note(s) (informational)`,
|
|
776
|
+
descHtml: `<ul style="margin:4px 0 0 16px;padding:0;">${patterns}</ul>`,
|
|
777
|
+
});
|
|
376
778
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
779
|
+
items.sort((a, b) => PRIO_RANK[a.prio] - PRIO_RANK[b.prio]);
|
|
780
|
+
const actionItemsHtml = items.length > 0
|
|
781
|
+
? items.map(actionItemCard).join("\n")
|
|
782
|
+
: passCard("No action items", "All checks passed and nothing needs attention for this window.");
|
|
380
783
|
// ── Proposal rows ──────────────────────────────────────────────────────────
|
|
381
784
|
const proposalRowsHtml = proposals.length > 0
|
|
382
785
|
? proposals
|
|
383
786
|
.map((p, i) => {
|
|
384
787
|
const tagCls = p.source === "extract" ? "tag-extract" : "tag-consolidate";
|
|
385
|
-
const ts = p.createdAt.slice(0, 16).replace("T", " ");
|
|
386
788
|
return (`<tr><td>${i + 1}</td><td><code>${esc(p.ref)}</code></td>` +
|
|
387
789
|
`<td><span class="tag ${tagCls}">${esc(p.source)}</span></td>` +
|
|
388
|
-
`<td>${
|
|
790
|
+
`<td>${isoTimeTag(p.createdAt)}</td></tr>`);
|
|
389
791
|
})
|
|
390
792
|
.join("\n")
|
|
391
793
|
: '<tr><td colspan="4" style="text-align:center;color:var(--muted);">No pending proposals</td></tr>';
|
|
392
|
-
// ── What to watch ──────────────────────────────────────────────────────────
|
|
393
|
-
const watchParts = [];
|
|
394
|
-
for (const a of result.advisories) {
|
|
395
|
-
if (a.status !== "warn" && a.status !== "fail")
|
|
396
|
-
continue;
|
|
397
|
-
const prio = a.status === "fail" ? "P1" : "P2";
|
|
398
|
-
watchParts.push(advisoryCard(a.status === "fail" ? "fail" : "warn", a.status === "fail" ? "🔴" : "🟡", `${esc(a.name)} (${prio})`, esc(a.message)));
|
|
399
|
-
}
|
|
400
|
-
if (sem.blocked) {
|
|
401
|
-
watchParts.push(advisoryCard("warn", "🟡", "Embedding server unreachable (P2)", "Curate quality and semantic ranking are degraded. Check the embedding endpoint configured in config.json."));
|
|
402
|
-
}
|
|
403
|
-
if (proposals.length > 0) {
|
|
404
|
-
const bySource = new Map();
|
|
405
|
-
for (const p of proposals)
|
|
406
|
-
bySource.set(p.source, (bySource.get(p.source) ?? 0) + 1);
|
|
407
|
-
const srcSummary = [...bySource.entries()]
|
|
408
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
409
|
-
.map(([source, count]) => `${count} via ${esc(source)}`)
|
|
410
|
-
.join(", ");
|
|
411
|
-
watchParts.push(advisoryCard("warn", "🟡", `Drain ${proposals.length} pending proposals (P2)`, `Proposals generated this batch (${srcSummary}). Run <code>akm proposal list</code> before the queue grows further.`));
|
|
412
|
-
}
|
|
413
|
-
if (wallTime.p95Ms && wallTime.medianMs && wallTime.p95Ms / wallTime.medianMs > 2.5) {
|
|
414
|
-
watchParts.push(advisoryCard("warn", "🟡", `High tail latency (P3): p95=${fmtMs(wallTime.p95Ms)}, median=${fmtMs(wallTime.medianMs)}`, "P95 is well above median. Consolidation/LLM phase dominates wall time on slow runs. " +
|
|
415
|
-
"Check for slow chunks or LLM rate limiting."));
|
|
416
|
-
}
|
|
417
|
-
if (failedRuns > 0) {
|
|
418
|
-
watchParts.push(advisoryCard("warn", "🟡", `${failedRuns} failed run(s) in window (P2)`, `Task fail rate ${taskFailRate}. Inspect failed runs (ok=false) for early-exit or harness errors.`));
|
|
419
|
-
}
|
|
420
|
-
const watchItemsHtml = watchParts.length > 0
|
|
421
|
-
? watchParts.join("\n")
|
|
422
|
-
: passCard("Nothing critical to watch", "All indicators are within normal range.");
|
|
423
|
-
// ── Commands used ──────────────────────────────────────────────────────────
|
|
424
|
-
const commandsHtml = [
|
|
425
|
-
` <div><span>akm health --since=${esc(opts.window)} --group-by run --format json</span></div>`,
|
|
426
|
-
` <div><span>akm health --since=${esc(opts.window)} --window-compare=${esc(opts.compare)} --format json</span></div>`,
|
|
427
|
-
" <div><span>akm proposal list</span></div>",
|
|
428
|
-
].join("\n");
|
|
429
794
|
return {
|
|
430
795
|
"%%ECHARTS_TAG%%": buildEchartsTag(opts),
|
|
431
796
|
"%%REPORT_TITLE%%": esc(reportTitle),
|
|
432
797
|
"%%WINDOW%%": esc(opts.window),
|
|
433
|
-
"%%SINCE_HUMAN%%":
|
|
798
|
+
"%%SINCE_HUMAN%%": sinceHuman,
|
|
434
799
|
"%%RUN_COUNT%%": num(totalRuns),
|
|
435
800
|
"%%STATUS_BADGE_HTML%%": `${statusBadge}\n ${failBadge}`,
|
|
436
801
|
"%%EXEC_SUMMARY_HTML%%": execSummary,
|
|
437
802
|
"%%KPI_CARDS_HTML%%": kpiCards,
|
|
438
803
|
"%%RUNS_JS_CONST%%": runsJsConst,
|
|
439
804
|
"%%DISTILL_REASONS_JSON%%": JSON.stringify(distillReasons),
|
|
805
|
+
"%%SLICE_OPTIONS_HTML%%": buildSliceOptions(opts.window),
|
|
806
|
+
"%%LLM_BY_STAGE_JSON%%": JSON.stringify(llm.byStage ?? {}),
|
|
440
807
|
"%%SUMMARY_ROWS_HTML%%": summaryRowsHtml,
|
|
441
|
-
"%%
|
|
808
|
+
"%%ACTION_ITEMS_HTML%%": actionItemsHtml,
|
|
442
809
|
"%%PROPOSAL_ROWS_HTML%%": proposalRowsHtml,
|
|
443
810
|
"%%PROPOSAL_COUNT%%": String(proposals.length),
|
|
444
|
-
"%%WATCH_ITEMS_HTML%%": watchItemsHtml,
|
|
445
|
-
"%%COMMANDS_HTML%%": commandsHtml,
|
|
446
811
|
"%%GENERATED_AT%%": esc(generatedAt),
|
|
812
|
+
"%%AKM_VERSION%%": esc(pkgVersion),
|
|
447
813
|
};
|
|
448
814
|
}
|