akm-cli 0.9.0-beta.5 → 0.9.0-beta.51

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 (221) hide show
  1. package/CHANGELOG.md +711 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/profiles/default.json +9 -4
  6. package/dist/assets/profiles/frequent.json +1 -1
  7. package/dist/assets/profiles/memory-focus.json +1 -1
  8. package/dist/assets/profiles/quick.json +1 -1
  9. package/dist/assets/profiles/synthesize.json +15 -0
  10. package/dist/assets/profiles/thorough.json +1 -1
  11. package/dist/assets/prompts/consolidate-system.md +23 -0
  12. package/dist/assets/prompts/contradiction-judge.md +33 -0
  13. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  14. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  15. package/dist/assets/prompts/extract-session.md +6 -2
  16. package/dist/assets/prompts/graph-extract-system.md +1 -0
  17. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  18. package/dist/assets/prompts/memory-infer-system.md +1 -0
  19. package/dist/assets/prompts/memory-infer-user.md +5 -0
  20. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  21. package/dist/assets/prompts/procedural-system.md +44 -0
  22. package/dist/assets/prompts/recombine-system.md +40 -0
  23. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  24. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  25. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  34. package/dist/assets/templates/html/health.html +281 -111
  35. package/dist/assets/wiki/ingest-workflow-template.md +38 -10
  36. package/dist/cli/parse-args.js +46 -1
  37. package/dist/cli/shared.js +28 -0
  38. package/dist/cli.js +27 -11
  39. package/dist/commands/agent/agent-dispatch.js +2 -2
  40. package/dist/commands/agent/agent-support.js +0 -7
  41. package/dist/commands/agent/contribute-cli.js +17 -4
  42. package/dist/commands/config-cli.js +18 -2
  43. package/dist/commands/env/child-env.js +47 -0
  44. package/dist/commands/env/env-cli.js +33 -26
  45. package/dist/commands/env/secret-cli.js +36 -22
  46. package/dist/commands/feedback-cli.js +15 -6
  47. package/dist/commands/graph/graph-cli.js +5 -13
  48. package/dist/commands/graph/graph.js +76 -72
  49. package/dist/commands/health/checks.js +49 -1
  50. package/dist/commands/health/html-report.js +422 -80
  51. package/dist/commands/health.js +386 -9
  52. package/dist/commands/improve/calibration.js +161 -0
  53. package/dist/commands/improve/consolidate/chunking.js +141 -0
  54. package/dist/commands/improve/consolidate/eligibility.js +81 -0
  55. package/dist/commands/improve/consolidate/merge.js +145 -0
  56. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  57. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  58. package/dist/commands/improve/consolidate.js +635 -660
  59. package/dist/commands/improve/dedup.js +482 -0
  60. package/dist/commands/improve/distill.js +159 -69
  61. package/dist/commands/improve/eligibility.js +434 -0
  62. package/dist/commands/improve/encoding-salience.js +205 -0
  63. package/dist/commands/improve/extract-cli.js +124 -2
  64. package/dist/commands/improve/extract-prompt.js +39 -2
  65. package/dist/commands/improve/extract-watch.js +140 -0
  66. package/dist/commands/improve/extract.js +389 -40
  67. package/dist/commands/improve/feedback-valence.js +54 -0
  68. package/dist/commands/improve/homeostatic.js +467 -0
  69. package/dist/commands/improve/improve-auto-accept.js +138 -7
  70. package/dist/commands/improve/improve-cli.js +36 -61
  71. package/dist/commands/improve/improve-profiles.js +14 -0
  72. package/dist/commands/improve/improve-result-file.js +14 -25
  73. package/dist/commands/improve/improve-session.js +58 -0
  74. package/dist/commands/improve/improve.js +485 -2498
  75. package/dist/commands/improve/locks.js +154 -0
  76. package/dist/commands/improve/loop-stages.js +1083 -0
  77. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  78. package/dist/commands/improve/outcome-loop.js +256 -0
  79. package/dist/commands/improve/preparation.js +1966 -0
  80. package/dist/commands/improve/proactive-maintenance.js +115 -0
  81. package/dist/commands/improve/procedural.js +418 -0
  82. package/dist/commands/improve/recombine.js +850 -0
  83. package/dist/commands/improve/reflect-noise.js +0 -0
  84. package/dist/commands/improve/reflect.js +183 -40
  85. package/dist/commands/improve/salience.js +438 -0
  86. package/dist/commands/improve/triage.js +93 -0
  87. package/dist/commands/lint/agent-linter.js +19 -24
  88. package/dist/commands/lint/base-linter.js +173 -60
  89. package/dist/commands/lint/command-linter.js +19 -24
  90. package/dist/commands/lint/env-key-rules.js +38 -1
  91. package/dist/commands/lint/fact-linter.js +39 -0
  92. package/dist/commands/lint/index.js +31 -13
  93. package/dist/commands/lint/memory-linter.js +1 -1
  94. package/dist/commands/lint/registry.js +7 -2
  95. package/dist/commands/lint/task-linter.js +3 -3
  96. package/dist/commands/lint/workflow-linter.js +26 -1
  97. package/dist/commands/proposal/drain-policies.js +5 -0
  98. package/dist/commands/proposal/drain.js +43 -50
  99. package/dist/commands/proposal/proposal-cli.js +21 -31
  100. package/dist/commands/proposal/proposal.js +5 -0
  101. package/dist/commands/proposal/propose.js +7 -2
  102. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  103. package/dist/commands/proposal/validators/proposals.js +189 -63
  104. package/dist/commands/read/curate.js +414 -94
  105. package/dist/commands/read/knowledge.js +6 -3
  106. package/dist/commands/read/search-cli.js +9 -4
  107. package/dist/commands/read/search.js +10 -6
  108. package/dist/commands/read/show.js +86 -7
  109. package/dist/commands/sources/init.js +49 -17
  110. package/dist/commands/sources/installed-stashes.js +11 -3
  111. package/dist/commands/sources/schema-repair.js +43 -45
  112. package/dist/commands/sources/self-update.js +2 -2
  113. package/dist/commands/sources/source-add.js +7 -3
  114. package/dist/commands/sources/stash-cli.js +28 -40
  115. package/dist/commands/sources/stash-skeleton.js +23 -8
  116. package/dist/commands/tasks/tasks-cli.js +19 -27
  117. package/dist/commands/tasks/tasks.js +39 -11
  118. package/dist/commands/wiki-cli.js +21 -35
  119. package/dist/core/asset/asset-registry.js +3 -1
  120. package/dist/core/asset/asset-spec.js +18 -2
  121. package/dist/core/asset/frontmatter.js +166 -167
  122. package/dist/core/asset/markdown.js +8 -0
  123. package/dist/core/authoring-rules.js +92 -0
  124. package/dist/core/common.js +0 -5
  125. package/dist/core/config/config-migration.js +12 -11
  126. package/dist/core/config/config-schema.js +340 -56
  127. package/dist/core/config/config-types.js +3 -3
  128. package/dist/core/config/config.js +28 -7
  129. package/dist/core/events.js +3 -7
  130. package/dist/core/improve-types.js +11 -8
  131. package/dist/core/logs-db.js +10 -66
  132. package/dist/core/parse.js +36 -16
  133. package/dist/core/paths.js +3 -0
  134. package/dist/core/standards/resolve-standards-context.js +87 -0
  135. package/dist/core/standards/resolve-stash-standards.js +99 -0
  136. package/dist/core/standards/resolve-type-conventions.js +66 -0
  137. package/dist/core/state/migrations.js +714 -0
  138. package/dist/core/state-db.js +525 -474
  139. package/dist/indexer/db/db.js +439 -247
  140. package/dist/indexer/db/graph-db.js +129 -86
  141. package/dist/indexer/ensure-index.js +152 -17
  142. package/dist/indexer/graph/graph-boost.js +51 -41
  143. package/dist/indexer/graph/graph-extraction.js +218 -4
  144. package/dist/indexer/index-writer-lock.js +99 -0
  145. package/dist/indexer/indexer.js +123 -221
  146. package/dist/indexer/passes/dir-staleness.js +114 -0
  147. package/dist/indexer/passes/memory-inference.js +13 -5
  148. package/dist/indexer/passes/staleness-detect.js +2 -5
  149. package/dist/indexer/search/db-search.js +19 -6
  150. package/dist/indexer/search/ranking-contributors.js +22 -0
  151. package/dist/indexer/search/ranking.js +4 -0
  152. package/dist/indexer/search/search-source.js +17 -18
  153. package/dist/indexer/search/semantic-status.js +4 -0
  154. package/dist/indexer/walk/matchers.js +9 -0
  155. package/dist/integrations/agent/config.js +6 -53
  156. package/dist/integrations/agent/index.js +2 -18
  157. package/dist/integrations/agent/prompts.js +75 -9
  158. package/dist/integrations/agent/runner-dispatch.js +59 -0
  159. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  160. package/dist/integrations/harnesses/index.js +2 -3
  161. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  162. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  163. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
  164. package/dist/integrations/session-logs/index.js +16 -0
  165. package/dist/llm/client.js +45 -15
  166. package/dist/llm/embedder.js +42 -3
  167. package/dist/llm/embedders/deterministic.js +66 -0
  168. package/dist/llm/embedders/local.js +66 -2
  169. package/dist/llm/feature-gate.js +8 -4
  170. package/dist/llm/graph-extract.js +67 -44
  171. package/dist/llm/memory-infer-impl.js +138 -0
  172. package/dist/llm/memory-infer.js +1 -127
  173. package/dist/llm/metadata-enhance.js +44 -31
  174. package/dist/llm/structured-call.js +49 -0
  175. package/dist/migrate-storage-node.mjs +8 -0
  176. package/dist/output/context.js +5 -5
  177. package/dist/output/renderers.js +74 -2
  178. package/dist/output/shapes/curate.js +14 -2
  179. package/dist/output/shapes/passthrough.js +0 -1
  180. package/dist/output/text/helpers.js +16 -1
  181. package/dist/registry/providers/skills-sh.js +21 -147
  182. package/dist/registry/providers/static-index.js +15 -157
  183. package/dist/registry/resolve.js +22 -9
  184. package/dist/runtime.js +25 -1
  185. package/dist/scripts/migrate-storage.js +2617 -1961
  186. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
  187. package/dist/setup/setup.js +29 -8
  188. package/dist/sources/include.js +6 -2
  189. package/dist/sources/providers/filesystem.js +0 -1
  190. package/dist/sources/providers/git-install.js +210 -0
  191. package/dist/sources/providers/git-provider.js +234 -0
  192. package/dist/sources/providers/git-stash.js +248 -0
  193. package/dist/sources/providers/git.js +10 -661
  194. package/dist/sources/providers/npm.js +2 -6
  195. package/dist/sources/providers/provider-utils.js +13 -7
  196. package/dist/sources/providers/sync-from-ref.js +9 -1
  197. package/dist/sources/providers/tar-utils.js +16 -8
  198. package/dist/sources/providers/website.js +9 -5
  199. package/dist/sources/website-ingest.js +187 -29
  200. package/dist/sources/wiki-fetchers/registry.js +53 -0
  201. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  202. package/dist/storage/database.js +45 -10
  203. package/dist/storage/managed-db.js +82 -0
  204. package/dist/storage/repositories/registry-cache.js +92 -0
  205. package/dist/storage/sqlite-pragmas.js +146 -0
  206. package/dist/tasks/backends/cron.js +1 -1
  207. package/dist/tasks/backends/launchd.js +1 -1
  208. package/dist/tasks/backends/schtasks.js +1 -1
  209. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  210. package/dist/tasks/runner.js +5 -13
  211. package/dist/text-import-hook.mjs +0 -0
  212. package/dist/wiki/wiki.js +37 -0
  213. package/dist/workflows/db.js +3 -4
  214. package/dist/workflows/runtime/runs.js +1 -117
  215. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  216. package/dist/workflows/validate-summary.js +2 -7
  217. package/docs/data-and-telemetry.md +3 -2
  218. package/docs/migration/release-notes/0.9.0.md +39 -0
  219. package/package.json +13 -11
  220. package/dist/commands/db-cli.js +0 -23
  221. package/dist/indexer/db/db-backup.js +0 -376
@@ -6,12 +6,13 @@ import { ConfigError, UsageError } from "../core/errors.js";
6
6
  import { appendEvent, readEvents } from "../core/events.js";
7
7
  import { buildTaskRunId, getLoggedRunIds, openLogsDatabase } from "../core/logs-db.js";
8
8
  import { getStateDbPathInDataDir } from "../core/paths.js";
9
- import { listExistingTableNames, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
9
+ import { listExistingTableNames, listProposalGateDecisions, listStateProposals, openStateDatabase, queryCompletedTaskIntervals, queryImproveRuns, queryTaskHistory, } from "../core/state-db.js";
10
10
  import { parseSinceToIso } from "../core/time.js";
11
11
  import { readSemanticStatus } from "../indexer/search/semantic-status.js";
12
12
  import { getExecutionLogCandidates } from "../integrations/session-logs/index.js";
13
13
  import { LLM_USAGE_EVENT } from "../llm/usage-persist.js";
14
14
  import { HEALTH_CHECKS } from "./health/checks.js";
15
+ import { gateDecisionsToSamples, summarizeCalibration } from "./improve/calibration.js";
15
16
  const DEFAULT_SINCE_MS = 24 * 60 * 60 * 1000;
16
17
  const IMPROVE_COMPLETED_EVENT = "improve_completed";
17
18
  const HEALTH_PROBE_EVENT = "health_probe";
@@ -72,6 +73,7 @@ function createUnknownImproveMetrics() {
72
73
  error: 0,
73
74
  },
74
75
  autoAccept: { promoted: 0, validationFailed: 0 },
76
+ calibration: summarizeCalibration([]),
75
77
  reflectsWithErrorContext: 0,
76
78
  coverageGapCount: 0,
77
79
  evalCasesWritten: 0,
@@ -132,6 +134,7 @@ function createUnknownImproveMetrics() {
132
134
  failures: 0,
133
135
  htmlErrors: 0,
134
136
  retryAttempts: 0,
137
+ nonArrayBatchFailures: 0,
135
138
  durationMs: 0,
136
139
  },
137
140
  sessionExtraction: {
@@ -155,6 +158,22 @@ function createUnknownImproveMetrics() {
155
158
  graphExtraction: { count: 0, totalMs: 0, medianMs: 0, p95Ms: 0 },
156
159
  },
157
160
  },
161
+ perfTelemetry: {
162
+ dedupPoolSize: 0,
163
+ llmPoolSize: 0,
164
+ judgedCacheSkipped: 0,
165
+ embedMs: 0,
166
+ embedCacheHits: 0,
167
+ embedCacheMisses: 0,
168
+ overBudgetRuns: 0,
169
+ runsWithTelemetry: 0,
170
+ },
171
+ coverage: {
172
+ rate: Number.NaN,
173
+ eligibleFraction: Number.NaN,
174
+ acceptedProposals: 0,
175
+ totalAssets: 0,
176
+ },
158
177
  };
159
178
  }
160
179
  function toFiniteNumber(value) {
@@ -363,6 +382,21 @@ function projectRunMetrics(result) {
363
382
  }
364
383
  }
365
384
  }
385
+ // WS-5: extract perf telemetry from the consolidation envelope.
386
+ // Pre-WS-5 envelopes lack `perfTelemetry`; be defensive.
387
+ const perf = consolidation.perfTelemetry;
388
+ if (perf) {
389
+ metrics.perfTelemetry.runsWithTelemetry += 1;
390
+ metrics.perfTelemetry.dedupPoolSize += toFiniteNumber(perf.dedupPoolSize);
391
+ metrics.perfTelemetry.llmPoolSize += toFiniteNumber(perf.llmPoolSize);
392
+ metrics.perfTelemetry.judgedCacheSkipped += toFiniteNumber(perf.judgedCacheSkipped);
393
+ metrics.perfTelemetry.embedMs += toFiniteNumber(perf.embedMs);
394
+ metrics.perfTelemetry.embedCacheHits += toFiniteNumber(perf.embedCacheHits);
395
+ metrics.perfTelemetry.embedCacheMisses += toFiniteNumber(perf.embedCacheMisses);
396
+ const budgetFrac = toFiniteNumber(perf.estimatedBudgetFractionUsed);
397
+ if (budgetFrac > 1.0)
398
+ metrics.perfTelemetry.overBudgetRuns += 1;
399
+ }
366
400
  }
367
401
  const memoryInference = result.memoryInference;
368
402
  if (memoryInference) {
@@ -407,6 +441,7 @@ function projectRunMetrics(result) {
407
441
  metrics.graphExtraction.failures += toFiniteNumber(telemetry.failureCount);
408
442
  metrics.graphExtraction.htmlErrors += toFiniteNumber(telemetry.htmlErrorCount);
409
443
  metrics.graphExtraction.retryAttempts += toFiniteNumber(telemetry.retryAttempts);
444
+ metrics.graphExtraction.nonArrayBatchFailures += toFiniteNumber(telemetry.nonArrayBatchFailures);
410
445
  }
411
446
  }
412
447
  metrics.graphExtraction.durationMs += toFiniteNumber(result.graphExtractionDurationMs);
@@ -473,7 +508,10 @@ function finalizeImproveMetrics(metrics) {
473
508
  */
474
509
  function mergeImproveMetrics(dst, src) {
475
510
  dst.plannedRefs += src.plannedRefs;
476
- dst.profileFilteredRefs += src.profileFilteredRefs;
511
+ // profileFilteredRefs is the count of refs the planner drops up-front for the
512
+ // active profile — recomputed against the (stable) stash every run, so it is a
513
+ // snapshot, NOT a per-run increment. Summing it re-counts the same refs each
514
+ // run (the ~2.4M bug). Set from the most recent run in summarizeImproveRuns.
477
515
  dst.actions.reflect.ok += src.actions.reflect.ok;
478
516
  dst.actions.reflect.failed += src.actions.reflect.failed;
479
517
  dst.actions.reflect.cooldown += src.actions.reflect.cooldown;
@@ -506,8 +544,10 @@ function mergeImproveMetrics(dst, src) {
506
544
  dst.coverageGapCount += src.coverageGapCount;
507
545
  dst.evalCasesWritten += src.evalCasesWritten;
508
546
  dst.deadUrlCount += src.deadUrlCount;
509
- dst.memorySummary.eligible += src.memorySummary.eligible;
510
- dst.memorySummary.derived += src.memorySummary.derived;
547
+ // NOTE: memorySummary (derived/eligible) is a WHOLE-STASH snapshot recorded on
548
+ // every run, NOT a per-run increment — summing it across the window inflates
549
+ // it ~N× (the 1.2M-eligible bug). It is set from the most recent run in
550
+ // summarizeImproveRuns instead, so it is intentionally not merged here.
511
551
  dst.memoryCleanup.pruneCandidates += src.memoryCleanup.pruneCandidates;
512
552
  dst.memoryCleanup.contradictionCandidates += src.memoryCleanup.contradictionCandidates;
513
553
  dst.memoryCleanup.beliefStateTransitions += src.memoryCleanup.beliefStateTransitions;
@@ -549,6 +589,7 @@ function mergeImproveMetrics(dst, src) {
549
589
  dst.graphExtraction.truncations += src.graphExtraction.truncations;
550
590
  dst.graphExtraction.failures += src.graphExtraction.failures;
551
591
  dst.graphExtraction.htmlErrors += src.graphExtraction.htmlErrors;
592
+ dst.graphExtraction.nonArrayBatchFailures += src.graphExtraction.nonArrayBatchFailures;
552
593
  dst.graphExtraction.durationMs += src.graphExtraction.durationMs;
553
594
  dst.sessionExtraction.sessionsScanned += src.sessionExtraction.sessionsScanned;
554
595
  dst.sessionExtraction.sessionsExtracted += src.sessionExtraction.sessionsExtracted;
@@ -556,6 +597,18 @@ function mergeImproveMetrics(dst, src) {
556
597
  dst.sessionExtraction.proposalsCreated += src.sessionExtraction.proposalsCreated;
557
598
  dst.sessionExtraction.warnings += src.sessionExtraction.warnings;
558
599
  dst.sessionExtraction.durationMs += src.sessionExtraction.durationMs;
600
+ // WS-5: merge perf telemetry (additive sums).
601
+ dst.perfTelemetry.dedupPoolSize += src.perfTelemetry.dedupPoolSize;
602
+ dst.perfTelemetry.llmPoolSize += src.perfTelemetry.llmPoolSize;
603
+ dst.perfTelemetry.judgedCacheSkipped += src.perfTelemetry.judgedCacheSkipped;
604
+ dst.perfTelemetry.embedMs += src.perfTelemetry.embedMs;
605
+ dst.perfTelemetry.embedCacheHits += src.perfTelemetry.embedCacheHits;
606
+ dst.perfTelemetry.embedCacheMisses += src.perfTelemetry.embedCacheMisses;
607
+ dst.perfTelemetry.overBudgetRuns += src.perfTelemetry.overBudgetRuns;
608
+ dst.perfTelemetry.runsWithTelemetry += src.perfTelemetry.runsWithTelemetry;
609
+ // coverage: acceptedProposals is additive; totalAssets is a snapshot (like memorySummary).
610
+ // totalAssets is intentionally NOT merged here — set from the most recent run in summarizeImproveRuns.
611
+ dst.coverage.acceptedProposals += src.coverage.acceptedProposals;
559
612
  }
560
613
  function summarizeImproveRuns(db, since, until) {
561
614
  const accum = createUnknownImproveMetrics();
@@ -568,6 +621,11 @@ function summarizeImproveRuns(db, since, until) {
568
621
  memoryInference: [],
569
622
  graphExtraction: [],
570
623
  };
624
+ // memorySummary is a whole-stash snapshot per run, so the window value is the
625
+ // MOST RECENT run's snapshot (current state) — not a sum across runs.
626
+ let latestStartMs = Number.NEGATIVE_INFINITY;
627
+ let latestMemorySummary;
628
+ let latestProfileFilteredRefs = 0;
571
629
  for (const row of rows) {
572
630
  let result;
573
631
  try {
@@ -578,6 +636,12 @@ function summarizeImproveRuns(db, since, until) {
578
636
  }
579
637
  const perRow = projectRunMetrics(result);
580
638
  mergeImproveMetrics(accum, perRow);
639
+ const startMs = new Date(row.started_at).getTime();
640
+ if (Number.isFinite(startMs) && startMs >= latestStartMs) {
641
+ latestStartMs = startMs;
642
+ latestMemorySummary = perRow.memorySummary;
643
+ latestProfileFilteredRefs = perRow.profileFilteredRefs;
644
+ }
581
645
  // Collect per-phase durations directly off the envelope. consolidation's
582
646
  // duration lives inside the sub-object; memoryInference and graphExtraction
583
647
  // expose top-level *DurationMs keys (`memoryInferenceDurationMs`,
@@ -594,6 +658,9 @@ function summarizeImproveRuns(db, since, until) {
594
658
  phaseDurations.graphExtraction.push(graphMs);
595
659
  }
596
660
  finalizeImproveMetrics(accum);
661
+ if (latestMemorySummary)
662
+ accum.memorySummary = latestMemorySummary;
663
+ accum.profileFilteredRefs = latestProfileFilteredRefs;
597
664
  accum.wallTime.byPhase = {
598
665
  consolidation: summarizePhaseDurations(phaseDurations.consolidation),
599
666
  memoryInference: summarizePhaseDurations(phaseDurations.memoryInference),
@@ -624,7 +691,7 @@ function summarizePhaseDurations(samples) {
624
691
  * Project an improve_runs row + wall-time lookup into a single ImproveRunSummary.
625
692
  * Used by `akm health --detail per-run`.
626
693
  */
627
- function projectImproveRunSummary(row, wallTimeMs) {
694
+ function projectImproveRunSummary(row, wallTimeMs, taskId) {
628
695
  let result = {};
629
696
  try {
630
697
  result = JSON.parse(row.result_json);
@@ -648,6 +715,7 @@ function projectImproveRunSummary(row, wallTimeMs) {
648
715
  mode: row.scope_mode,
649
716
  ...(row.scope_value ? { value: row.scope_value } : {}),
650
717
  },
718
+ taskId,
651
719
  actions: perRow.actions,
652
720
  memorySummary: perRow.memorySummary,
653
721
  memoryCleanup: perRow.memoryCleanup,
@@ -710,9 +778,62 @@ function findContainingTaskInterval(timestampMs, intervals) {
710
778
  }
711
779
  return undefined;
712
780
  }
781
+ /**
782
+ * Load `task_history` rows whose `task_id` begins `akm-improve` (the scheduled
783
+ * improve tasks: `akm-improve-frequent`, `akm-improve-proactive-weekly`, …) in
784
+ * the window, widened ±5 min so a task that fired just before the window opened
785
+ * still matches a run inside it. Used to attribute each improve run to the task
786
+ * that launched it.
787
+ */
788
+ function loadImproveTaskRuns(db, since, until) {
789
+ const sinceMs = new Date(since).getTime();
790
+ const untilMs = until ? new Date(until).getTime() : undefined;
791
+ const widenedSince = new Date(sinceMs - 5 * 60 * 1000).toISOString();
792
+ const widenedUntil = untilMs !== undefined ? new Date(untilMs + 5 * 60 * 1000).toISOString() : undefined;
793
+ const runs = [];
794
+ for (const row of queryTaskHistory(db, { since: widenedSince, until: widenedUntil })) {
795
+ if (!row.task_id.startsWith("akm-improve"))
796
+ continue;
797
+ const startMs = new Date(row.started_at).getTime();
798
+ if (!Number.isFinite(startMs))
799
+ continue;
800
+ const endIso = row.completed_at ?? row.failed_at;
801
+ const endMs = endIso ? new Date(endIso).getTime() : Number.NaN;
802
+ runs.push({ taskId: row.task_id, startMs, endMs });
803
+ }
804
+ return runs;
805
+ }
806
+ /**
807
+ * Attribute an improve run to the scheduled task that launched it by matching
808
+ * start times within ±5 min, scored by start delta (plus end delta when both
809
+ * ends are known). Port of the health-report skill's `match_task_id`. Returns
810
+ * `"manual"` when no scheduled improve task matches.
811
+ */
812
+ function matchImproveTaskId(startedAt, completedAt, taskRuns) {
813
+ const startMs = new Date(startedAt).getTime();
814
+ if (!Number.isFinite(startMs))
815
+ return "manual";
816
+ const endMs = completedAt ? new Date(completedAt).getTime() : Number.NaN;
817
+ let best;
818
+ let bestScore = Number.POSITIVE_INFINITY;
819
+ for (const task of taskRuns) {
820
+ const startDelta = Math.abs(task.startMs - startMs);
821
+ if (startDelta > 5 * 60 * 1000)
822
+ continue;
823
+ let score = startDelta;
824
+ if (Number.isFinite(endMs) && Number.isFinite(task.endMs))
825
+ score += Math.abs(task.endMs - endMs);
826
+ if (score < bestScore) {
827
+ bestScore = score;
828
+ best = task.taskId;
829
+ }
830
+ }
831
+ return best ?? "manual";
832
+ }
713
833
  function buildPerRunSummaries(db, since, until) {
714
834
  const rows = queryImproveRuns(db, since, until);
715
835
  const taskIntervals = loadTaskIntervals(db, since, until);
836
+ const improveTaskRuns = loadImproveTaskRuns(db, since, until);
716
837
  const summaries = [];
717
838
  for (const row of rows) {
718
839
  const startMs = new Date(row.started_at).getTime();
@@ -732,7 +853,8 @@ function buildPerRunSummaries(db, since, until) {
732
853
  const interval = Number.isFinite(startMs) ? findContainingTaskInterval(startMs, taskIntervals) : undefined;
733
854
  wallTimeMs = interval?.durationMs ?? 0;
734
855
  }
735
- summaries.push(projectImproveRunSummary(row, wallTimeMs));
856
+ const taskId = matchImproveTaskId(row.started_at, row.completed_at, improveTaskRuns);
857
+ summaries.push(projectImproveRunSummary(row, wallTimeMs, taskId));
736
858
  }
737
859
  return summaries;
738
860
  }
@@ -759,12 +881,34 @@ function computeWallTimeStats(durationsMs, byPhase) {
759
881
  };
760
882
  }
761
883
  function buildImproveSkipSummary(events) {
762
- const skipReasons = {};
884
+ // Two kinds of skip events:
885
+ // - Per-occurrence (no `count`): one event per skipped ref → SUM is correct.
886
+ // - Aggregated snapshot (carries `count`): a single per-run event whose count
887
+ // is the number of refs that hit a STABLE, whole-stash condition that run
888
+ // (`no_new_signal`, `profile_filtered_all_passes`). Each run re-counts the
889
+ // same stable set, so summing across the window re-counts it N times (the
890
+ // 2.7M / 3M inflation). For these we keep the MOST RECENT run's count — the
891
+ // current snapshot — matching how memorySummary/profileFilteredRefs are
892
+ // handled. Events arrive in chronological (offset) order, so the last
893
+ // count-bearing event per reason is the latest run's value.
894
+ const summed = {};
895
+ const latestSnapshot = {};
763
896
  for (const event of events) {
764
897
  const reason = typeof event.metadata?.reason === "string" && event.metadata.reason.trim() ? event.metadata.reason : "unknown";
765
- skipReasons[reason] = (skipReasons[reason] ?? 0) + 1;
898
+ const rawCount = event.metadata?.count;
899
+ if (typeof rawCount === "number" && Number.isFinite(rawCount) && rawCount > 0) {
900
+ latestSnapshot[reason] = rawCount; // overwrite → keeps the latest run's snapshot
901
+ }
902
+ else {
903
+ summed[reason] = (summed[reason] ?? 0) + 1;
904
+ }
905
+ }
906
+ const skipReasons = { ...summed };
907
+ for (const [reason, count] of Object.entries(latestSnapshot)) {
908
+ skipReasons[reason] = (skipReasons[reason] ?? 0) + count;
766
909
  }
767
- return { skipped: events.length, skipReasons };
910
+ const skipped = Object.values(skipReasons).reduce((a, b) => a + b, 0);
911
+ return { skipped, skipReasons };
768
912
  }
769
913
  function probeStateDbRoundTrip(stateDbPath) {
770
914
  const before = readEvents({}, { dbPath: stateDbPath }).nextOffset;
@@ -849,6 +993,7 @@ const INTERESTING_DELTA_PATHS = [
849
993
  "improve.graphExtraction.cacheHitRate",
850
994
  "improve.graphExtraction.failures",
851
995
  "improve.graphExtraction.htmlErrors",
996
+ "improve.graphExtraction.nonArrayBatchFailures",
852
997
  "improve.sessionExtraction.sessionsScanned",
853
998
  "improve.sessionExtraction.proposalsCreated",
854
999
  "improve.autoAccept.promoted",
@@ -955,6 +1100,197 @@ function readLlmUsageAggregate(stateDbPath, since, until) {
955
1100
  });
956
1101
  return summarizeLlmUsage(events);
957
1102
  }
1103
+ /**
1104
+ * Read the auto-accept gate calibration summary (#612) over `[since, until)`.
1105
+ * Reads every proposal's `gateDecision` from the open state.db, projects the
1106
+ * acted-on (auto-accepted / auto-rejected) decisions into calibration samples
1107
+ * within the window, and aggregates them deterministically.
1108
+ */
1109
+ function readCalibration(db, since, until) {
1110
+ const decisions = listProposalGateDecisions(db);
1111
+ const samples = gateDecisionsToSamples(decisions, { since, ...(until !== undefined ? { until } : {}) });
1112
+ return summarizeCalibration(samples);
1113
+ }
1114
+ // ── WS-5 Observability helpers ───────────────────────────────────────────────
1115
+ /**
1116
+ * Compute WS-5 denominator-fixed coverage metrics.
1117
+ *
1118
+ * `coverage = accepted_proposals / total_assets` (Part V §3).
1119
+ * The denominator is the TOTAL stash size (not the moving eligible set) so
1120
+ * more-inclusive WS-1 ranking cannot spuriously inflate coverage.
1121
+ * `eligibleFraction = eligible_assets / total_assets` is reported separately.
1122
+ *
1123
+ * Proposals are counted only when their `updatedAt` falls within `[since, until)`
1124
+ * so the rate is genuinely window-scoped (matching the JSDoc on the type).
1125
+ *
1126
+ * @param db - Open state.db connection.
1127
+ * @param totalAssets - Total stash asset count (eligible + derived) from the
1128
+ * most recent run's memorySummary. 0 = denominator unknown, returns NaN rates.
1129
+ * @param eligibleAssets - Eligible (non-derived) asset count from the most recent run.
1130
+ * @param since - Window start (ISO-8601). Proposals accepted before this are excluded.
1131
+ * @param until - Window end (ISO-8601, exclusive). Absent = open-ended (up to now).
1132
+ * @param stashDir - Optional: scope accepted proposals to one stash. Absent = all stashes.
1133
+ */
1134
+ function computeDenominatorFixedCoverage(db, totalAssets, eligibleAssets, since, until, stashDir) {
1135
+ let acceptedProposals = 0;
1136
+ try {
1137
+ const proposals = listStateProposals(db, {
1138
+ status: "accepted",
1139
+ ...(stashDir ? { stashDir } : {}),
1140
+ }).filter((p) => {
1141
+ const updatedAt = p.updatedAt ?? "";
1142
+ if (updatedAt < since)
1143
+ return false;
1144
+ if (until !== undefined && updatedAt >= until)
1145
+ return false;
1146
+ return true;
1147
+ });
1148
+ acceptedProposals = proposals.length;
1149
+ }
1150
+ catch {
1151
+ // Fail open: table may not exist on older installs.
1152
+ }
1153
+ if (totalAssets === 0) {
1154
+ return {
1155
+ rate: Number.NaN,
1156
+ eligibleFraction: Number.NaN,
1157
+ acceptedProposals,
1158
+ totalAssets: 0,
1159
+ };
1160
+ }
1161
+ return {
1162
+ rate: roundRate(acceptedProposals / totalAssets),
1163
+ eligibleFraction: roundRate(eligibleAssets / totalAssets),
1164
+ acceptedProposals,
1165
+ totalAssets,
1166
+ };
1167
+ }
1168
+ /**
1169
+ * Compute WS-5 per-run degradation metrics (Part V §4).
1170
+ *
1171
+ * Health VIEWS only — reads from state.db tables populated by prior improve
1172
+ * runs. Gracefully returns partial data when tables are absent (pre-WS-1/2).
1173
+ *
1174
+ * @param db - Open state.db connection.
1175
+ * @param since - Window start (ISO-8601).
1176
+ * @param until - Window end (ISO-8601).
1177
+ */
1178
+ function computeDegradationMetrics(db, since, until) {
1179
+ // (a) Corpus diversity — salience rank distribution of the top-100 assets.
1180
+ // We use the Gini coefficient of retrieval_salience scores as an intra-corpus
1181
+ // diversity proxy. A Gini close to 1 = highly concentrated (entrenched top
1182
+ // assets), Gini near 0 = flat/diverse. This is a single-snapshot metric;
1183
+ // consecutive-run centroid distance requires cross-run history not yet stored.
1184
+ let corpusCentroidDistance = Number.NaN;
1185
+ let entrenchmentFlagged;
1186
+ try {
1187
+ const rows = db
1188
+ .prepare(`SELECT retrieval_salience FROM asset_salience
1189
+ ORDER BY rank_score DESC LIMIT 100`)
1190
+ .all();
1191
+ if (rows.length >= 5) {
1192
+ const vals = rows.map((r) => r.retrieval_salience).sort((a, b) => a - b);
1193
+ const n = vals.length;
1194
+ const sumAbsDiff = vals.reduce((acc, xi, i) => {
1195
+ return acc + vals.slice(i + 1).reduce((a, xj) => a + Math.abs(xi - xj), 0);
1196
+ }, 0);
1197
+ const mean = vals.reduce((a, b) => a + b, 0) / n;
1198
+ // Gini = (sum |xi - xj|) / (2 n^2 mean); 0 = perfect equality, 1 = perfect inequality.
1199
+ const gini = mean > 0 ? sumAbsDiff / (2 * n * n * mean) : 0;
1200
+ // Re-express as a diversity proxy in [0,1]: high gini = low diversity.
1201
+ // corpusCentroidDistance approximation: gini is "distance from uniform".
1202
+ // Note: retrieval_salience values are in [0,1], so the max achievable Gini
1203
+ // 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).
1205
+ corpusCentroidDistance = roundRate(gini);
1206
+ entrenchmentFlagged = gini > 0.35;
1207
+ }
1208
+ }
1209
+ catch {
1210
+ // Table not present (pre-WS-1 install) — leave NaN.
1211
+ }
1212
+ // (b) Merge fidelity — fraction of consolidate accepted proposals in the window
1213
+ // whose ref also has a consolidate skip-reason of "contradict_target_missing"
1214
+ // or an event indicating contradiction. Uses the improve_runs result_json
1215
+ // consolidation.contradicted count as a proxy.
1216
+ // Simple implementation: contradictionRate = total_contradicted / max(1, total_processed)
1217
+ // sourced from the window's consolidation envelope.
1218
+ // (The full "merge proposal → later contradiction" correlation requires cross-run
1219
+ // history; this is the available proxy.)
1220
+ let mergeFidelityContradictionRate = 0;
1221
+ try {
1222
+ const runs = queryImproveRuns(db, since, until);
1223
+ let totalContradicted = 0;
1224
+ let totalProcessed = 0;
1225
+ for (const row of runs) {
1226
+ try {
1227
+ const result = JSON.parse(row.result_json);
1228
+ const cons = result.consolidation;
1229
+ if (cons) {
1230
+ totalContradicted += toFiniteNumber(cons.contradicted);
1231
+ totalProcessed += toFiniteNumber(cons.processed);
1232
+ }
1233
+ }
1234
+ catch {
1235
+ // Skip malformed rows.
1236
+ }
1237
+ }
1238
+ if (totalProcessed > 0) {
1239
+ mergeFidelityContradictionRate = roundRate(totalContradicted / totalProcessed);
1240
+ }
1241
+ }
1242
+ catch {
1243
+ // Fail open.
1244
+ }
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
+ }
1262
+ // (d) Oracle spot-check — up to 5 recently accepted proposals in the window.
1263
+ const oracleSpotCheck = [];
1264
+ try {
1265
+ const accepted = listStateProposals(db, { status: "accepted" }).filter((p) => {
1266
+ const updatedAt = p.updatedAt ?? "";
1267
+ return updatedAt >= since && updatedAt < until;
1268
+ });
1269
+ // Sample up to 5: pick evenly spaced (not just the first 5).
1270
+ const step = Math.max(1, Math.floor(accepted.length / 5));
1271
+ for (let i = 0; i < accepted.length && oracleSpotCheck.length < 5; i += step) {
1272
+ const p = accepted[i];
1273
+ if (p) {
1274
+ oracleSpotCheck.push({
1275
+ proposalId: p.id,
1276
+ ref: p.ref,
1277
+ source: p.source ?? "unknown",
1278
+ acceptedAt: p.updatedAt ?? p.createdAt ?? "",
1279
+ });
1280
+ }
1281
+ }
1282
+ }
1283
+ catch {
1284
+ // Fail open.
1285
+ }
1286
+ return {
1287
+ corpusCentroidDistance,
1288
+ entrenchmentFlagged,
1289
+ mergeFidelityContradictionRate,
1290
+ highGenerationFraction,
1291
+ oracleSpotCheck,
1292
+ };
1293
+ }
958
1294
  function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now(), logsDb) {
959
1295
  const taskRows = queryTaskHistory(db, { since }).filter((row) => {
960
1296
  const startMs = new Date(row.started_at).getTime();
@@ -989,6 +1325,17 @@ function buildWindowMetrics(db, stateDbPath, since, until, now = () => Date.now(
989
1325
  const perRunSummaries = buildPerRunSummaries(db, since, until);
990
1326
  const wallTimes = perRunSummaries.map((run) => run.wallTimeMs).filter((ms) => Number.isFinite(ms) && ms > 0);
991
1327
  improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
1328
+ improveSummary.calibration = readCalibration(db, since, until);
1329
+ // WS-5: Compute denominator-fixed coverage from the most recent run's
1330
+ // memorySummary (totalAssets = eligible + derived — the fixed denominator).
1331
+ const totalAssets = improveSummary.memorySummary.eligible + improveSummary.memorySummary.derived;
1332
+ improveSummary.coverage = computeDenominatorFixedCoverage(db, totalAssets, improveSummary.memorySummary.eligible, since, until);
1333
+ // WS-5: Compute per-run degradation metrics (corpus diversity, merge fidelity,
1334
+ // generation distribution, oracle spot-check). Health VIEWS only.
1335
+ const degradation = computeDegradationMetrics(db, since, until);
1336
+ if (degradation) {
1337
+ improveSummary.degradation = degradation;
1338
+ }
992
1339
  const metrics = {
993
1340
  taskFailRate: roundRate(taskFailRate),
994
1341
  agentFailureRate: roundRate(agentFailureRate),
@@ -1077,6 +1424,35 @@ export function akmHealth(options = {}) {
1077
1424
  const perRunSummaries = buildPerRunSummaries(db, since);
1078
1425
  const wallTimes = perRunSummaries.map((run) => run.wallTimeMs).filter((ms) => Number.isFinite(ms) && ms > 0);
1079
1426
  improveSummary.wallTime = computeWallTimeStats(wallTimes, improveSummary.wallTime.byPhase);
1427
+ improveSummary.calibration = readCalibration(db, since);
1428
+ // WS-5: Compute denominator-fixed coverage and per-run degradation metrics
1429
+ // for the main health path (not just window-compare mode).
1430
+ const until = new Date(now()).toISOString();
1431
+ const totalAssetsMain = improveSummary.memorySummary.eligible + improveSummary.memorySummary.derived;
1432
+ improveSummary.coverage = computeDenominatorFixedCoverage(db, totalAssetsMain, improveSummary.memorySummary.eligible, since, until);
1433
+ const degradationMain = computeDegradationMetrics(db, since, until);
1434
+ if (degradationMain) {
1435
+ improveSummary.degradation = degradationMain;
1436
+ }
1437
+ // WS-2 proxy-adequacy tripwire: surface any outcome_proxy_inverted events
1438
+ // in the health window as an advisory so operators know when the 0.10+
1439
+ // rich in-session signal is no longer deferrable.
1440
+ const proxyInvertedEvents = readEvents({ since, type: "outcome_proxy_inverted" }, { dbPath: stateDbPath }).events;
1441
+ if (proxyInvertedEvents.length > 0) {
1442
+ const lastEvent = proxyInvertedEvents[proxyInvertedEvents.length - 1];
1443
+ const correlation = typeof lastEvent.metadata?.correlation === "number" ? lastEvent.metadata.correlation.toFixed(3) : "unknown";
1444
+ advisories.push({
1445
+ name: "outcome-proxy-adequacy",
1446
+ status: "warn",
1447
+ kind: "deterministic",
1448
+ confidence: "high",
1449
+ message: `WS-2 outcome proxy inverted (${proxyInvertedEvents.length} event(s) in window). ` +
1450
+ `corr(outcome_score, accepted_change_rate) = ${correlation} < −0.3. ` +
1451
+ "Popular assets are also the most-needing-improvement assets — " +
1452
+ "the retrieval-based proxy is inverted. " +
1453
+ "The 0.10+ rich in-session outcome signal is no longer deferrable. See plan §WS-2.",
1454
+ });
1455
+ }
1080
1456
  let sessionLogEntries = [];
1081
1457
  try {
1082
1458
  const sinceDays = Math.max(0, Math.ceil((now() - new Date(since).getTime()) / (24 * 60 * 60 * 1000)));
@@ -1283,6 +1659,7 @@ export function renderWindowCompareMd(windows, deltas) {
1283
1659
  "improve.actions.reflect.failed",
1284
1660
  "improve.actions.distill.llmFailed",
1285
1661
  "improve.graphExtraction.failures",
1662
+ "improve.graphExtraction.nonArrayBatchFailures",
1286
1663
  "improve.wallTime.medianMs",
1287
1664
  "improve.wallTime.p95Ms",
1288
1665
  "improve.memoryInference.skippedNoFacts",