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
@@ -11,10 +11,9 @@ import { writeFileAtomic } from "../core/common.js";
11
11
  import { FEEDBACK_FAILURE_MODES, loadConfig } from "../core/config/config.js";
12
12
  import { UsageError } from "../core/errors.js";
13
13
  import { appendEvent } from "../core/events.js";
14
+ import { getDbPath } from "../core/paths.js";
14
15
  import { warn } from "../core/warn.js";
15
16
  import { applyFeedbackToUtilityScore, closeDatabase, findEntryIdByRef, getEntryFilePathById, openExistingDatabase, } from "../indexer/db/db.js";
16
- import { ensureIndex } from "../indexer/ensure-index.js";
17
- import { resolveSourceEntries } from "../indexer/search/search-source.js";
18
17
  import { countFeedbackSignals, insertUsageEvent } from "../indexer/usage/usage-events.js";
19
18
  // ── Tag validation ────────────────────────────────────────────────────────────
20
19
  const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
@@ -203,11 +202,21 @@ export const feedbackCommand = defineCommand({
203
202
  ...(validatedTags.length > 0 ? { tags: validatedTags } : {}),
204
203
  };
205
204
  const metadataStr = Object.keys(metadataObj).length > 1 ? JSON.stringify(metadataObj) : undefined;
206
- // Auto-index when stale so the index is current before recording feedback.
207
- const sources = resolveSourceEntries();
208
- if (sources.length > 0) {
209
- await ensureIndex(sources[0].path);
205
+ // Feedback only needs the index to exist, not to be current. A stale index
206
+ // is fine — the ref lookup works against any populated DB. We do NOT call
207
+ // ensureIndex here: it either blocks (3+ min inline reindex) or spawns a
208
+ // background process that holds the writer lock, causing the feedback write
209
+ // to spin-wait for the full reindex duration. If the DB is absent we give a
210
+ // clear error below rather than silently triggering a rebuild.
211
+ if (!fs.existsSync(getDbPath())) {
212
+ throw new UsageError("Index not found. Run 'akm index' first to build the index before recording feedback.", "MISSING_REQUIRED_ARGUMENT", "akm index");
210
213
  }
214
+ // Feedback writes exactly 2 rows (usage_events + utility_score). SQLite
215
+ // WAL mode + busy_timeout=30s handles concurrent access with an ongoing
216
+ // `akm improve` run without needing the application-level writer lock.
217
+ // The lock was originally needed to prevent feedback from racing a
218
+ // background reindex it spawned — now that ensureIndex is removed, holding
219
+ // the lock only causes feedback to block for the full improve run duration.
211
220
  let utilityResult;
212
221
  const db = openExistingDatabase();
213
222
  try {
@@ -9,12 +9,9 @@
9
9
  * same JSON envelope (stdout/stderr/exit-code) as the inline `runWithJsonErrors`
10
10
  * form it replaces.
11
11
  */
12
- import { defineCommand } from "citty";
13
- import { hasSubcommand, parsePositiveIntFlag } from "../../cli/parse-args.js";
14
- import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
12
+ import { parsePositiveIntFlag } from "../../cli/parse-args.js";
13
+ import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
15
14
  import { akmGraphEntities, akmGraphEntity, akmGraphExport, akmGraphOrphans, akmGraphRelated, akmGraphRelations, akmGraphSummary, akmGraphUpdate, } from "./graph.js";
16
- // Single source of truth: the routing set is derived from the subCommands keys
17
- // (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
18
15
  const graphSubCommands = {
19
16
  summary: defineJsonCommand({
20
17
  meta: { name: "summary", description: "Show entity-graph counts and quality telemetry" },
@@ -118,15 +115,10 @@ const graphSubCommands = {
118
115
  },
119
116
  }),
120
117
  };
121
- const GRAPH_SUBCOMMAND_SET = new Set(Object.keys(graphSubCommands));
122
- export const graphCommand = defineCommand({
118
+ export const graphCommand = defineGroupCommand({
123
119
  meta: { name: "graph", description: "Inspect the indexed entity graph stored in SQLite" },
124
120
  subCommands: graphSubCommands,
125
- run({ args }) {
126
- return runWithJsonErrors(() => {
127
- if (hasSubcommand(args, GRAPH_SUBCOMMAND_SET))
128
- return;
129
- output("graph-summary", akmGraphSummary());
130
- });
121
+ defaultRun() {
122
+ output("graph-summary", akmGraphSummary());
131
123
  },
132
124
  });
@@ -8,10 +8,11 @@ import { loadConfig } from "../../core/config/config.js";
8
8
  import { NotFoundError, UsageError } from "../../core/errors.js";
9
9
  import { getDbPath } from "../../core/paths.js";
10
10
  import { warn } from "../../core/warn.js";
11
- import { closeDatabase, findEntryIdByRef, getEntryById, getEntryRefRowsForStashRoot, openDatabase, openExistingDatabase, } from "../../indexer/db/db.js";
11
+ import { closeDatabase, findEntryIdByRef, getEntryById, getEntryRefRowsForStashRoot, openExistingDatabase, openIndexDatabase, } from "../../indexer/db/db.js";
12
12
  import { loadStoredGraphSnapshot } from "../../indexer/db/graph-db.js";
13
13
  import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
14
14
  import { runGraphExtractionPass } from "../../indexer/graph/graph-extraction.js";
15
+ import { withIndexWriterLease } from "../../indexer/index-writer-lock.js";
15
16
  import { lookup } from "../../indexer/indexer.js";
16
17
  import { findSourceForPath, resolveSourceEntries } from "../../indexer/search/search-source.js";
17
18
  import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
@@ -375,85 +376,88 @@ export async function akmGraphUpdate(options) {
375
376
  }
376
377
  }
377
378
  const scoped = Array.isArray(options.refs) && options.refs.length > 0;
378
- let candidatePaths;
379
- if (scoped && options.refs) {
380
- // Resolve each ref to an absolute file path via the index DB.
381
- const dbPath = getDbPath();
382
- let db;
383
- const resolvedPaths = new Set();
384
- try {
385
- db = openDatabase(dbPath);
386
- for (const ref of options.refs) {
387
- const trimmed = ref.trim();
388
- if (!trimmed)
389
- continue;
390
- const entryId = findEntryIdByRef(db, trimmed);
391
- if (entryId === undefined) {
392
- warn(`[graph] ref not found in index, skipping: ${trimmed}`);
393
- continue;
379
+ return withIndexWriterLease({ purpose: "graph-update" }, async () => {
380
+ let candidatePaths;
381
+ if (scoped && options.refs) {
382
+ // Resolve each ref to an absolute file path while the writer lease is held
383
+ // so the scoped graph write sees the same index snapshot it resolved from.
384
+ const dbPath = getDbPath();
385
+ let db;
386
+ const resolvedPaths = new Set();
387
+ try {
388
+ db = openIndexDatabase(dbPath);
389
+ for (const ref of options.refs) {
390
+ const trimmed = ref.trim();
391
+ if (!trimmed)
392
+ continue;
393
+ const entryId = findEntryIdByRef(db, trimmed);
394
+ if (entryId === undefined) {
395
+ warn(`[graph] ref not found in index, skipping: ${trimmed}`);
396
+ continue;
397
+ }
398
+ const row = getEntryById(db, entryId);
399
+ if (!row?.filePath) {
400
+ warn(`[graph] could not resolve path for ref, skipping: ${trimmed}`);
401
+ continue;
402
+ }
403
+ resolvedPaths.add(row.filePath);
394
404
  }
395
- const row = getEntryById(db, entryId);
396
- if (!row?.filePath) {
397
- warn(`[graph] could not resolve path for ref, skipping: ${trimmed}`);
398
- continue;
399
- }
400
- resolvedPaths.add(row.filePath);
401
405
  }
406
+ finally {
407
+ if (db)
408
+ closeDatabase(db);
409
+ }
410
+ if (resolvedPaths.size === 0) {
411
+ warn("[graph] none of the provided refs resolved to indexed paths — no extraction performed.");
412
+ return {
413
+ shape: "graph-update",
414
+ ok: true,
415
+ filesExtracted: 0,
416
+ entitiesUpserted: 0,
417
+ relationsUpserted: 0,
418
+ durationMs: 0,
419
+ scoped: true,
420
+ };
421
+ }
422
+ candidatePaths = resolvedPaths;
402
423
  }
403
- finally {
404
- if (db)
405
- closeDatabase(db);
406
- }
407
- if (resolvedPaths.size === 0) {
408
- warn("[graph] none of the provided refs resolved to indexed paths — no extraction performed.");
424
+ const extractionFn = options.graphExtractionFn ?? runGraphExtractionPass;
425
+ const passOptions = candidatePaths ? { candidatePaths } : {};
426
+ let db;
427
+ const startMs = Date.now();
428
+ try {
429
+ db = openIndexDatabase(getDbPath());
430
+ const onProgress = (event) => {
431
+ if (!event.currentPath)
432
+ return;
433
+ const file = path.basename(event.currentPath);
434
+ warn(`[graph] extracting ${event.processed}/${event.total} ${file}`);
435
+ };
436
+ const result = await extractionFn({
437
+ config,
438
+ sources,
439
+ signal: undefined,
440
+ db,
441
+ reEnrich: false,
442
+ onProgress,
443
+ options: passOptions,
444
+ });
445
+ const durationMs = Date.now() - startMs;
409
446
  return {
410
447
  shape: "graph-update",
411
448
  ok: true,
412
- filesExtracted: 0,
413
- entitiesUpserted: 0,
414
- relationsUpserted: 0,
415
- durationMs: 0,
416
- scoped: true,
449
+ filesExtracted: result.quality.extractedFiles,
450
+ entitiesUpserted: result.quality.entityCount,
451
+ relationsUpserted: result.quality.relationCount,
452
+ durationMs,
453
+ scoped,
417
454
  };
418
455
  }
419
- candidatePaths = resolvedPaths;
420
- }
421
- const extractionFn = options.graphExtractionFn ?? runGraphExtractionPass;
422
- const passOptions = candidatePaths ? { candidatePaths } : {};
423
- let db;
424
- const startMs = Date.now();
425
- try {
426
- db = openDatabase(getDbPath());
427
- const onProgress = (event) => {
428
- if (!event.currentPath)
429
- return;
430
- const file = path.basename(event.currentPath);
431
- warn(`[graph] extracting ${event.processed}/${event.total} ${file}`);
432
- };
433
- const result = await extractionFn({
434
- config,
435
- sources,
436
- signal: undefined,
437
- db,
438
- reEnrich: false,
439
- onProgress,
440
- options: passOptions,
441
- });
442
- const durationMs = Date.now() - startMs;
443
- return {
444
- shape: "graph-update",
445
- ok: true,
446
- filesExtracted: result.quality.extractedFiles,
447
- entitiesUpserted: result.quality.entityCount,
448
- relationsUpserted: result.quality.relationCount,
449
- durationMs,
450
- scoped,
451
- };
452
- }
453
- finally {
454
- if (db)
455
- closeDatabase(db);
456
- }
456
+ finally {
457
+ if (db)
458
+ closeDatabase(db);
459
+ }
460
+ });
457
461
  }
458
462
  async function resolveGraphTarget(ref, source) {
459
463
  const parsedRef = parseAssetRef(ref);
@@ -257,6 +257,54 @@ export const HEALTH_CHECKS = [
257
257
  };
258
258
  },
259
259
  },
260
+ {
261
+ // #603: pool-saturation advisory. The raw `sessionsScanned` count fired on
262
+ // normal cadence changes (the Jun 12 false alarm). Instead track the ratio
263
+ // of NEW (unseen) sessions to the total session pool extract evaluated in
264
+ // the window: a low ratio is the *expected* steady state, only a near-zero
265
+ // ratio signals a possible discovery/dedup bug.
266
+ //
267
+ // unseen ≈ `sessionsScanned` (extract only processes new sessions; already-
268
+ // seen ones are deduped into `sessionsSkipped`). total = scanned + skipped.
269
+ // This is a heuristic approximation — `sessionsSkipped` also folds in
270
+ // too-short skips — so the check is informational and never gates status.
271
+ name: "pool-saturation",
272
+ channel: "advisory",
273
+ run: (ctx) => {
274
+ const sx = ctx.sessionExtraction;
275
+ const total = sx.sessionsScanned + sx.sessionsSkipped;
276
+ const unseen = sx.sessionsScanned;
277
+ const ratio = total > 0 ? unseen / total : null;
278
+ const pct = ratio === null ? null : Math.round(ratio * 1000) / 10;
279
+ let status = "pass";
280
+ let confidence = "low";
281
+ let message;
282
+ if (!sx.ran || ratio === null) {
283
+ message = "Pool saturation: no extract activity in the window — no signal.";
284
+ }
285
+ else if (ratio < 0.02) {
286
+ status = "warn";
287
+ confidence = "medium";
288
+ message = `Session pool near-exhausted: only ${pct}% of the ${total}-session pool was new (<2%). Possible discovery/dedup bug — verify extract is still finding new sessions.`;
289
+ }
290
+ else if (ratio < 0.1) {
291
+ confidence = "medium";
292
+ message = `Session pool saturation: ${pct}% of ${total} sessions were new (<10%, steady-state expected — informational).`;
293
+ }
294
+ else {
295
+ confidence = "medium";
296
+ message = `Session pool healthy: ${pct}% of ${total} sessions were new.`;
297
+ }
298
+ return {
299
+ name: "pool-saturation",
300
+ kind: "heuristic",
301
+ status,
302
+ confidence,
303
+ message,
304
+ evidence: { totalSessions: total, unseenSessions: unseen, saturationRatio: ratio },
305
+ };
306
+ },
307
+ },
260
308
  {
261
309
  name: "auto-accept-validation",
262
310
  channel: "advisory",
@@ -268,7 +316,7 @@ export const HEALTH_CHECKS = [
268
316
  status: aa.validationFailed > 0 ? "warn" : "pass",
269
317
  confidence: aa.promoted + aa.validationFailed > 0 ? "high" : "low",
270
318
  message: aa.validationFailed > 0
271
- ? `${aa.validationFailed} proposal(s) passed confidence threshold but failed auto-accept validation (truncated description, invalid frontmatter, etc.) — they remain in the queue for manual review.`
319
+ ? `${aa.validationFailed} auto-accept validation attempt(s) failed after passing the confidence threshold (truncated description, invalid frontmatter, etc.) — the affected proposals remain pending for manual review.`
272
320
  : aa.promoted > 0
273
321
  ? `Auto-accept healthy: ${aa.promoted} proposal(s) promoted, 0 validation failures.`
274
322
  : "Auto-accept gate did not run (disabled or no proposals above threshold).",