akm-cli 0.9.0-beta.2 → 0.9.0-beta.27

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 (120) hide show
  1. package/CHANGELOG.md +660 -0
  2. package/dist/assets/prompts/consolidate-system.md +23 -0
  3. package/dist/assets/prompts/contradiction-judge.md +33 -0
  4. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  5. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  6. package/dist/assets/prompts/extract-session.md +5 -1
  7. package/dist/assets/prompts/graph-extract-system.md +1 -0
  8. package/dist/assets/prompts/memory-infer-system.md +1 -0
  9. package/dist/assets/prompts/memory-infer-user.md +5 -0
  10. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  11. package/dist/assets/prompts/procedural-system.md +44 -0
  12. package/dist/assets/prompts/recombine-system.md +40 -0
  13. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  14. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +730 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/shared.js +21 -5
  19. package/dist/cli.js +47 -5
  20. package/dist/commands/agent/contribute-cli.js +16 -3
  21. package/dist/commands/feedback-cli.js +15 -6
  22. package/dist/commands/graph/graph.js +75 -71
  23. package/dist/commands/health/checks.js +48 -0
  24. package/dist/commands/health/html-report.js +790 -0
  25. package/dist/commands/health.js +478 -15
  26. package/dist/commands/improve/calibration.js +161 -0
  27. package/dist/commands/improve/consolidate.js +634 -111
  28. package/dist/commands/improve/dedup.js +482 -0
  29. package/dist/commands/improve/distill.js +145 -69
  30. package/dist/commands/improve/encoding-salience.js +205 -0
  31. package/dist/commands/improve/extract-cli.js +115 -1
  32. package/dist/commands/improve/extract-prompt.js +33 -2
  33. package/dist/commands/improve/extract-watch.js +140 -0
  34. package/dist/commands/improve/extract.js +280 -35
  35. package/dist/commands/improve/feedback-valence.js +54 -0
  36. package/dist/commands/improve/homeostatic.js +467 -0
  37. package/dist/commands/improve/improve-auto-accept.js +139 -6
  38. package/dist/commands/improve/improve-profiles.js +12 -0
  39. package/dist/commands/improve/improve.js +2079 -608
  40. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  41. package/dist/commands/improve/outcome-loop.js +256 -0
  42. package/dist/commands/improve/proactive-maintenance.js +87 -0
  43. package/dist/commands/improve/procedural.js +409 -0
  44. package/dist/commands/improve/recombine.js +488 -0
  45. package/dist/commands/improve/reflect-noise.js +0 -0
  46. package/dist/commands/improve/reflect.js +51 -1
  47. package/dist/commands/improve/related-sessions.js +120 -0
  48. package/dist/commands/improve/salience.js +386 -0
  49. package/dist/commands/improve/triage.js +95 -0
  50. package/dist/commands/lint/agent-linter.js +19 -24
  51. package/dist/commands/lint/base-linter.js +173 -60
  52. package/dist/commands/lint/command-linter.js +19 -24
  53. package/dist/commands/lint/env-key-rules.js +34 -1
  54. package/dist/commands/lint/fact-linter.js +39 -0
  55. package/dist/commands/lint/index.js +31 -13
  56. package/dist/commands/lint/memory-linter.js +1 -1
  57. package/dist/commands/lint/registry.js +7 -2
  58. package/dist/commands/lint/task-linter.js +3 -3
  59. package/dist/commands/lint/workflow-linter.js +26 -1
  60. package/dist/commands/proposal/drain.js +73 -6
  61. package/dist/commands/proposal/proposal-cli.js +22 -10
  62. package/dist/commands/proposal/proposal.js +17 -1
  63. package/dist/commands/proposal/validators/proposals.js +369 -329
  64. package/dist/commands/read/curate.js +344 -80
  65. package/dist/commands/read/search-cli.js +7 -0
  66. package/dist/commands/read/search.js +1 -0
  67. package/dist/commands/read/show.js +67 -2
  68. package/dist/commands/remember.js +6 -2
  69. package/dist/commands/sources/installed-stashes.js +5 -1
  70. package/dist/commands/sources/stash-cli.js +10 -2
  71. package/dist/core/asset/asset-registry.js +2 -0
  72. package/dist/core/asset/asset-spec.js +14 -0
  73. package/dist/core/asset/frontmatter.js +166 -167
  74. package/dist/core/asset/markdown.js +8 -0
  75. package/dist/core/config/config-schema.js +255 -2
  76. package/dist/core/config/config.js +2 -2
  77. package/dist/core/logs-db.js +305 -0
  78. package/dist/core/paths.js +3 -0
  79. package/dist/core/state-db.js +706 -42
  80. package/dist/indexer/db/db.js +364 -38
  81. package/dist/indexer/db/graph-db.js +129 -86
  82. package/dist/indexer/ensure-index.js +152 -17
  83. package/dist/indexer/graph/graph-boost.js +51 -41
  84. package/dist/indexer/graph/graph-extraction.js +203 -3
  85. package/dist/indexer/index-writer-lock.js +99 -0
  86. package/dist/indexer/indexer.js +114 -111
  87. package/dist/indexer/passes/memory-inference.js +71 -25
  88. package/dist/indexer/passes/staleness-detect.js +2 -5
  89. package/dist/indexer/search/db-search.js +15 -4
  90. package/dist/indexer/search/ranking-contributors.js +22 -0
  91. package/dist/indexer/search/ranking.js +4 -0
  92. package/dist/indexer/walk/matchers.js +9 -0
  93. package/dist/integrations/agent/prompts.js +1 -0
  94. package/dist/integrations/harnesses/claude/session-log.js +27 -5
  95. package/dist/integrations/harnesses/opencode/session-log.js +9 -0
  96. package/dist/integrations/session-logs/index.js +16 -0
  97. package/dist/llm/client.js +38 -4
  98. package/dist/llm/embedder.js +27 -3
  99. package/dist/llm/embedders/local.js +66 -2
  100. package/dist/llm/graph-extract.js +2 -1
  101. package/dist/llm/memory-infer.js +4 -8
  102. package/dist/llm/metadata-enhance.js +9 -1
  103. package/dist/llm/usage-persist.js +77 -0
  104. package/dist/llm/usage-telemetry.js +103 -0
  105. package/dist/output/context.js +3 -2
  106. package/dist/output/html-render.js +73 -0
  107. package/dist/output/renderers.js +73 -1
  108. package/dist/output/shapes/curate.js +14 -2
  109. package/dist/output/shapes/helpers.js +17 -1
  110. package/dist/output/text/helpers.js +78 -1
  111. package/dist/runtime.js +25 -1
  112. package/dist/scripts/migrate-storage.js +1262 -591
  113. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +485 -270
  114. package/dist/sources/providers/tar-utils.js +16 -8
  115. package/dist/storage/sqlite-pragmas.js +146 -0
  116. package/dist/tasks/runner.js +99 -16
  117. package/dist/workflows/db.js +5 -2
  118. package/dist/workflows/validate-summary.js +2 -7
  119. package/docs/data-and-telemetry.md +1 -0
  120. package/package.json +9 -6
@@ -491,349 +491,6 @@ var init_env = __esm(() => {
491
491
  ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
492
492
  });
493
493
 
494
- // src/core/asset/frontmatter.ts
495
- function parseFrontmatter(raw) {
496
- const parsedBlock = parseFrontmatterBlock(raw);
497
- if (!parsedBlock) {
498
- return { data: {}, content: raw, frontmatter: null, bodyStartLine: 1 };
499
- }
500
- const data = {};
501
- let currentKey = null;
502
- let mode = "scalar";
503
- let nested = null;
504
- let currentList = null;
505
- let blockLines = null;
506
- let blockChomping = "clip";
507
- const flushPending = () => {
508
- if (mode === "pending" && currentKey !== null) {
509
- data[currentKey] = "";
510
- }
511
- };
512
- const flushBlock = () => {
513
- if (mode !== "block" || currentKey === null || blockLines === null)
514
- return;
515
- const deindented = blockLines.map((l) => l.startsWith(" ") ? l.slice(2) : l);
516
- if (blockChomping === "keep") {
517
- data[currentKey] = deindented.join(`
518
- `);
519
- } else if (blockChomping === "strip") {
520
- data[currentKey] = deindented.join(`
521
- `).replace(/\n+$/, "");
522
- } else {
523
- data[currentKey] = `${deindented.join(`
524
- `).replace(/\n+$/, "")}
525
- `;
526
- }
527
- };
528
- for (const line of parsedBlock.frontmatter.split(/\r?\n/)) {
529
- if (mode === "block") {
530
- if (line.startsWith(" ") || line === "") {
531
- blockLines.push(line);
532
- continue;
533
- }
534
- flushBlock();
535
- mode = "scalar";
536
- blockLines = null;
537
- }
538
- const seqItem = line.match(/^(?: {2})?- (.*)$/);
539
- if (seqItem && currentKey !== null && (mode === "list" || mode === "pending")) {
540
- if (mode === "pending") {
541
- currentList = [];
542
- data[currentKey] = currentList;
543
- mode = "list";
544
- }
545
- currentList.push(parseYamlScalar(seqItem[1].trim()));
546
- continue;
547
- }
548
- if (mode === "scalar" && currentKey !== null && /^ {2}\S/.test(line)) {
549
- data[currentKey] = `${String(data[currentKey])} ${line.trim()}`;
550
- continue;
551
- }
552
- const indented = line.match(/^ {2}(\w[\w-]*):\s*(.+)$/);
553
- if (indented && currentKey !== null && (mode === "object" || mode === "pending")) {
554
- if (mode === "pending") {
555
- nested = {};
556
- data[currentKey] = nested;
557
- mode = "object";
558
- }
559
- nested[indented[1]] = parseYamlScalar(indented[2].trim());
560
- continue;
561
- }
562
- const top = line.match(/^(\w[\w-]*):\s*(.*)$/);
563
- if (!top) {
564
- continue;
565
- }
566
- flushPending();
567
- currentKey = top[1];
568
- const value = top[2].trim();
569
- if (value === "|" || value === "|-" || value === "|+") {
570
- mode = "block";
571
- blockLines = [];
572
- blockChomping = value === "|-" ? "strip" : value === "|+" ? "keep" : "clip";
573
- nested = null;
574
- currentList = null;
575
- } else if (value === "") {
576
- mode = "pending";
577
- nested = null;
578
- currentList = null;
579
- } else if (value.startsWith("[") && value.endsWith("]")) {
580
- mode = "list";
581
- nested = null;
582
- currentList = null;
583
- currentList = parseFlowArray(value);
584
- data[currentKey] = currentList;
585
- } else {
586
- mode = "scalar";
587
- nested = null;
588
- currentList = null;
589
- data[currentKey] = parseYamlScalar(value);
590
- }
591
- }
592
- if (mode === "block") {
593
- flushBlock();
594
- }
595
- flushPending();
596
- return {
597
- data,
598
- content: parsedBlock.content,
599
- frontmatter: parsedBlock.frontmatter,
600
- bodyStartLine: parsedBlock.bodyStartLine
601
- };
602
- }
603
- function parseFlowArray(value) {
604
- const inner = value.slice(1, -1).trim();
605
- if (!inner)
606
- return [];
607
- return inner.split(",").map((item) => parseYamlScalar(item.trim()));
608
- }
609
- function parseFrontmatterBlock(raw) {
610
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r\n|\r|\n|$)([\s\S]*)$/);
611
- if (!match)
612
- return null;
613
- const frontmatter = match[1].replace(/\r/g, "");
614
- const content = match[2];
615
- return {
616
- frontmatter,
617
- content,
618
- bodyStartLine: countLines(raw.slice(0, match[0].length - match[2].length)) + 1
619
- };
620
- }
621
- function countLines(text) {
622
- if (text.length === 0)
623
- return 0;
624
- return text.split(/\r?\n/).length - 1;
625
- }
626
- function parseYamlScalar(value) {
627
- if (value === "")
628
- return "";
629
- if (value === "true")
630
- return true;
631
- if (value === "false")
632
- return false;
633
- const asNumber = Number(value);
634
- if (!Number.isNaN(asNumber))
635
- return asNumber;
636
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
637
- return value.slice(1, -1);
638
- }
639
- return value;
640
- }
641
-
642
- // src/core/asset/markdown.ts
643
- function parseMarkdownToc(content) {
644
- const lines = content.split(/\r?\n/);
645
- const headings = [];
646
- const parsed = parseFrontmatter(content);
647
- const start = parsed.frontmatter ? parsed.bodyStartLine - 1 : 0;
648
- for (let i = start;i < lines.length; i++) {
649
- const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
650
- if (match) {
651
- headings.push({
652
- level: match[1].length,
653
- text: match[2].replace(/\s+#+\s*$/, "").trim(),
654
- line: i + 1
655
- });
656
- }
657
- }
658
- return { headings, totalLines: lines.length };
659
- }
660
- var init_markdown = () => {};
661
-
662
- // src/core/warn.ts
663
- import fs2 from "fs";
664
- function isVerbose() {
665
- const env = process.env.AKM_VERBOSE?.trim().toLowerCase();
666
- if (env === "1" || env === "true" || env === "yes" || env === "on")
667
- return true;
668
- if (env === "0" || env === "false" || env === "no" || env === "off")
669
- return false;
670
- return verbose;
671
- }
672
- function appendToLogFile(level, args) {
673
- if (!logFilePath)
674
- return;
675
- const ts = new Date().toISOString();
676
- const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
677
- try {
678
- fs2.appendFileSync(logFilePath, `[${ts}] [${level}] ${msg}
679
- `);
680
- } catch (e) {
681
- process.stderr.write(`[akm:warn] log-file write failed (${logFilePath}): ${e}
682
- `);
683
- process.stderr.write(`[${ts}] [${level}] ${msg}
684
- `);
685
- }
686
- }
687
- function warn(...args) {
688
- appendToLogFile("WARN", args);
689
- if (!quiet) {
690
- console.warn(...args);
691
- }
692
- }
693
- function error(...args) {
694
- appendToLogFile("ERROR", args);
695
- if (!quiet) {
696
- console.error(...args);
697
- }
698
- }
699
- function warnVerbose(...args) {
700
- if (isVerbose()) {
701
- warn(...args);
702
- }
703
- }
704
- var quiet = false, verbose = false, logFilePath;
705
- var init_warn = () => {};
706
-
707
- // src/indexer/walk/file-context.ts
708
- var renderers;
709
- var init_file_context = __esm(() => {
710
- init_common();
711
- renderers = new Map;
712
- });
713
-
714
- // src/indexer/passes/metadata-contributors.ts
715
- function registerMetadataContributor(contributor) {
716
- contributors.push(contributor);
717
- }
718
- var contributors;
719
- var init_metadata_contributors = __esm(() => {
720
- contributors = [];
721
- });
722
-
723
- // src/indexer/passes/metadata.ts
724
- import fs3 from "fs";
725
- function extractDescriptionFromComments(filePath) {
726
- let content;
727
- try {
728
- content = fs3.readFileSync(filePath, "utf8");
729
- } catch {
730
- return null;
731
- }
732
- const lines = content.split(/\r?\n/).slice(0, 50);
733
- const blockStart = lines.findIndex((l) => /^\s*\/\*\*/.test(l));
734
- if (blockStart >= 0) {
735
- const desc = [];
736
- for (let i = blockStart;i < lines.length; i++) {
737
- const line = lines[i];
738
- if (i > blockStart && /\*\//.test(line))
739
- break;
740
- const cleaned = line.replace(/^\s*\/?\*\*?\s?/, "").replace(/\*\/\s*$/, "").trim();
741
- if (cleaned)
742
- desc.push(cleaned);
743
- }
744
- if (desc.length > 0)
745
- return desc.join(" ");
746
- }
747
- let start = 0;
748
- if (lines[0]?.startsWith("#!"))
749
- start = 1;
750
- const hashLines = [];
751
- for (let i = start;i < lines.length; i++) {
752
- const line = lines[i].trim();
753
- if (line.startsWith("#") && !line.startsWith("#!")) {
754
- hashLines.push(line.replace(/^#+\s*/, "").trim());
755
- } else if (line === "") {} else {
756
- break;
757
- }
758
- }
759
- if (hashLines.length > 0)
760
- return hashLines.join(" ");
761
- return null;
762
- }
763
- var KNOWN_QUALITY_VALUES, warnedUnknownQualityValues, WIKI_INFRA_FILES;
764
- var init_metadata = __esm(() => {
765
- init_asset_spec();
766
- init_common();
767
- init_warn();
768
- init_file_context();
769
- init_metadata_contributors();
770
- KNOWN_QUALITY_VALUES = new Set(["generated", "curated", "enriched", "proposed"]);
771
- warnedUnknownQualityValues = new Set;
772
- WIKI_INFRA_FILES = new Set(["schema.md", "index.md", "log.md"]);
773
- });
774
-
775
- // src/core/asset/asset-ref.ts
776
- import path from "path";
777
- function parseAssetRef(ref) {
778
- const trimmed = ref.trim();
779
- if (!trimmed)
780
- throw new UsageError("Empty ref.", "MISSING_REQUIRED_ARGUMENT");
781
- let origin;
782
- let body = trimmed;
783
- const boundary = trimmed.indexOf("//");
784
- if (boundary >= 0) {
785
- origin = trimmed.slice(0, boundary);
786
- body = trimmed.slice(boundary + 2);
787
- if (!origin)
788
- throw new UsageError("Empty origin in ref.", "MISSING_REQUIRED_ARGUMENT");
789
- }
790
- const colon = body.indexOf(":");
791
- if (colon <= 0) {
792
- throw new UsageError(`Invalid ref "${trimmed}". Expected [origin//]type:name, e.g. skill:deploy or knowledge:guide.md`, "MISSING_REQUIRED_ARGUMENT");
793
- }
794
- const rawType = body.slice(0, colon);
795
- const rawName = body.slice(colon + 1);
796
- if (rawType === "vault") {
797
- throw new UsageError("The `vault` asset type was removed in 0.9.0 \u2014 use `env:` (whole .env config) or `secret:` (a single value).", "MISSING_REQUIRED_ARGUMENT");
798
- }
799
- const resolvedType = TYPE_ALIASES[rawType] ?? rawType;
800
- if (!isAssetType(resolvedType)) {
801
- throw new UsageError(`Invalid asset type: "${rawType}".`, "MISSING_REQUIRED_ARGUMENT");
802
- }
803
- validateName(rawName);
804
- const name = normalizeName(rawName);
805
- return { type: resolvedType, name, origin: origin || undefined };
806
- }
807
- function validateName(name) {
808
- if (!name)
809
- throw new UsageError("Empty asset name.", "MISSING_REQUIRED_ARGUMENT");
810
- if (name.includes("\x00"))
811
- throw new UsageError("Null byte in asset name.", "MISSING_REQUIRED_ARGUMENT");
812
- if (/^[A-Za-z]:/.test(name))
813
- throw new UsageError("Windows drive path in asset name.", "MISSING_REQUIRED_ARGUMENT");
814
- const normalized = path.posix.normalize(name.replace(/\\/g, "/"));
815
- if (path.posix.isAbsolute(normalized))
816
- throw new UsageError("Absolute path in asset name.", "MISSING_REQUIRED_ARGUMENT");
817
- if (normalized === ".." || normalized.startsWith("../")) {
818
- throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
819
- }
820
- const segments = normalized.split("/");
821
- if (segments.some((seg) => seg === "." || seg === "..")) {
822
- throw new UsageError("Asset name cannot contain relative path segments.", "MISSING_REQUIRED_ARGUMENT");
823
- }
824
- }
825
- function normalizeName(name) {
826
- return path.posix.normalize(name.replace(/\\/g, "/"));
827
- }
828
- var TYPE_ALIASES;
829
- var init_asset_ref = __esm(() => {
830
- init_common();
831
- init_errors();
832
- TYPE_ALIASES = {
833
- environment: "env"
834
- };
835
- });
836
-
837
494
  // node_modules/yaml/dist/nodes/identity.js
838
495
  var require_identity = __commonJS((exports) => {
839
496
  var ALIAS = Symbol.for("yaml.alias");
@@ -906,17 +563,17 @@ var require_visit = __commonJS((exports) => {
906
563
  visit.BREAK = BREAK;
907
564
  visit.SKIP = SKIP;
908
565
  visit.REMOVE = REMOVE;
909
- function visit_(key, node, visitor, path2) {
910
- const ctrl = callVisitor(key, node, visitor, path2);
566
+ function visit_(key, node, visitor, path) {
567
+ const ctrl = callVisitor(key, node, visitor, path);
911
568
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
912
- replaceNode(key, path2, ctrl);
913
- return visit_(key, ctrl, visitor, path2);
569
+ replaceNode(key, path, ctrl);
570
+ return visit_(key, ctrl, visitor, path);
914
571
  }
915
572
  if (typeof ctrl !== "symbol") {
916
573
  if (identity.isCollection(node)) {
917
- path2 = Object.freeze(path2.concat(node));
574
+ path = Object.freeze(path.concat(node));
918
575
  for (let i = 0;i < node.items.length; ++i) {
919
- const ci = visit_(i, node.items[i], visitor, path2);
576
+ const ci = visit_(i, node.items[i], visitor, path);
920
577
  if (typeof ci === "number")
921
578
  i = ci - 1;
922
579
  else if (ci === BREAK)
@@ -927,13 +584,13 @@ var require_visit = __commonJS((exports) => {
927
584
  }
928
585
  }
929
586
  } else if (identity.isPair(node)) {
930
- path2 = Object.freeze(path2.concat(node));
931
- const ck = visit_("key", node.key, visitor, path2);
587
+ path = Object.freeze(path.concat(node));
588
+ const ck = visit_("key", node.key, visitor, path);
932
589
  if (ck === BREAK)
933
590
  return BREAK;
934
591
  else if (ck === REMOVE)
935
592
  node.key = null;
936
- const cv = visit_("value", node.value, visitor, path2);
593
+ const cv = visit_("value", node.value, visitor, path);
937
594
  if (cv === BREAK)
938
595
  return BREAK;
939
596
  else if (cv === REMOVE)
@@ -954,17 +611,17 @@ var require_visit = __commonJS((exports) => {
954
611
  visitAsync.BREAK = BREAK;
955
612
  visitAsync.SKIP = SKIP;
956
613
  visitAsync.REMOVE = REMOVE;
957
- async function visitAsync_(key, node, visitor, path2) {
958
- const ctrl = await callVisitor(key, node, visitor, path2);
614
+ async function visitAsync_(key, node, visitor, path) {
615
+ const ctrl = await callVisitor(key, node, visitor, path);
959
616
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
960
- replaceNode(key, path2, ctrl);
961
- return visitAsync_(key, ctrl, visitor, path2);
617
+ replaceNode(key, path, ctrl);
618
+ return visitAsync_(key, ctrl, visitor, path);
962
619
  }
963
620
  if (typeof ctrl !== "symbol") {
964
621
  if (identity.isCollection(node)) {
965
- path2 = Object.freeze(path2.concat(node));
622
+ path = Object.freeze(path.concat(node));
966
623
  for (let i = 0;i < node.items.length; ++i) {
967
- const ci = await visitAsync_(i, node.items[i], visitor, path2);
624
+ const ci = await visitAsync_(i, node.items[i], visitor, path);
968
625
  if (typeof ci === "number")
969
626
  i = ci - 1;
970
627
  else if (ci === BREAK)
@@ -975,13 +632,13 @@ var require_visit = __commonJS((exports) => {
975
632
  }
976
633
  }
977
634
  } else if (identity.isPair(node)) {
978
- path2 = Object.freeze(path2.concat(node));
979
- const ck = await visitAsync_("key", node.key, visitor, path2);
635
+ path = Object.freeze(path.concat(node));
636
+ const ck = await visitAsync_("key", node.key, visitor, path);
980
637
  if (ck === BREAK)
981
638
  return BREAK;
982
639
  else if (ck === REMOVE)
983
640
  node.key = null;
984
- const cv = await visitAsync_("value", node.value, visitor, path2);
641
+ const cv = await visitAsync_("value", node.value, visitor, path);
985
642
  if (cv === BREAK)
986
643
  return BREAK;
987
644
  else if (cv === REMOVE)
@@ -1008,23 +665,23 @@ var require_visit = __commonJS((exports) => {
1008
665
  }
1009
666
  return visitor;
1010
667
  }
1011
- function callVisitor(key, node, visitor, path2) {
668
+ function callVisitor(key, node, visitor, path) {
1012
669
  if (typeof visitor === "function")
1013
- return visitor(key, node, path2);
670
+ return visitor(key, node, path);
1014
671
  if (identity.isMap(node))
1015
- return visitor.Map?.(key, node, path2);
672
+ return visitor.Map?.(key, node, path);
1016
673
  if (identity.isSeq(node))
1017
- return visitor.Seq?.(key, node, path2);
674
+ return visitor.Seq?.(key, node, path);
1018
675
  if (identity.isPair(node))
1019
- return visitor.Pair?.(key, node, path2);
676
+ return visitor.Pair?.(key, node, path);
1020
677
  if (identity.isScalar(node))
1021
- return visitor.Scalar?.(key, node, path2);
678
+ return visitor.Scalar?.(key, node, path);
1022
679
  if (identity.isAlias(node))
1023
- return visitor.Alias?.(key, node, path2);
680
+ return visitor.Alias?.(key, node, path);
1024
681
  return;
1025
682
  }
1026
- function replaceNode(key, path2, node) {
1027
- const parent = path2[path2.length - 1];
683
+ function replaceNode(key, path, node) {
684
+ const parent = path[path.length - 1];
1028
685
  if (identity.isCollection(parent)) {
1029
686
  parent.items[key] = node;
1030
687
  } else if (identity.isPair(parent)) {
@@ -1150,8 +807,8 @@ var require_directives = __commonJS((exports) => {
1150
807
  if (prefix) {
1151
808
  try {
1152
809
  return prefix + decodeURIComponent(suffix);
1153
- } catch (error2) {
1154
- onError(String(error2));
810
+ } catch (error) {
811
+ onError(String(error));
1155
812
  return null;
1156
813
  }
1157
814
  }
@@ -1242,9 +899,9 @@ var require_anchors = __commonJS((exports) => {
1242
899
  if (typeof ref === "object" && ref.anchor && (identity.isScalar(ref.node) || identity.isCollection(ref.node))) {
1243
900
  ref.node.anchor = ref.anchor;
1244
901
  } else {
1245
- const error2 = new Error("Failed to resolve repeated object (this should not happen)");
1246
- error2.source = source;
1247
- throw error2;
902
+ const error = new Error("Failed to resolve repeated object (this should not happen)");
903
+ error.source = source;
904
+ throw error;
1248
905
  }
1249
906
  }
1250
907
  },
@@ -1583,10 +1240,10 @@ var require_Collection = __commonJS((exports) => {
1583
1240
  var createNode = require_createNode();
1584
1241
  var identity = require_identity();
1585
1242
  var Node = require_Node();
1586
- function collectionFromPath(schema, path2, value) {
1243
+ function collectionFromPath(schema, path, value) {
1587
1244
  let v = value;
1588
- for (let i = path2.length - 1;i >= 0; --i) {
1589
- const k = path2[i];
1245
+ for (let i = path.length - 1;i >= 0; --i) {
1246
+ const k = path[i];
1590
1247
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
1591
1248
  const a = [];
1592
1249
  a[k] = v;
@@ -1605,7 +1262,7 @@ var require_Collection = __commonJS((exports) => {
1605
1262
  sourceObjects: new Map
1606
1263
  });
1607
1264
  }
1608
- var isEmptyPath = (path2) => path2 == null || typeof path2 === "object" && !!path2[Symbol.iterator]().next().done;
1265
+ var isEmptyPath = (path) => path == null || typeof path === "object" && !!path[Symbol.iterator]().next().done;
1609
1266
 
1610
1267
  class Collection extends Node.NodeBase {
1611
1268
  constructor(type, schema) {
@@ -1626,11 +1283,11 @@ var require_Collection = __commonJS((exports) => {
1626
1283
  copy.range = this.range.slice();
1627
1284
  return copy;
1628
1285
  }
1629
- addIn(path2, value) {
1630
- if (isEmptyPath(path2))
1286
+ addIn(path, value) {
1287
+ if (isEmptyPath(path))
1631
1288
  this.add(value);
1632
1289
  else {
1633
- const [key, ...rest] = path2;
1290
+ const [key, ...rest] = path;
1634
1291
  const node = this.get(key, true);
1635
1292
  if (identity.isCollection(node))
1636
1293
  node.addIn(rest, value);
@@ -1640,8 +1297,8 @@ var require_Collection = __commonJS((exports) => {
1640
1297
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1641
1298
  }
1642
1299
  }
1643
- deleteIn(path2) {
1644
- const [key, ...rest] = path2;
1300
+ deleteIn(path) {
1301
+ const [key, ...rest] = path;
1645
1302
  if (rest.length === 0)
1646
1303
  return this.delete(key);
1647
1304
  const node = this.get(key, true);
@@ -1650,8 +1307,8 @@ var require_Collection = __commonJS((exports) => {
1650
1307
  else
1651
1308
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1652
1309
  }
1653
- getIn(path2, keepScalar) {
1654
- const [key, ...rest] = path2;
1310
+ getIn(path, keepScalar) {
1311
+ const [key, ...rest] = path;
1655
1312
  const node = this.get(key, true);
1656
1313
  if (rest.length === 0)
1657
1314
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -1666,15 +1323,15 @@ var require_Collection = __commonJS((exports) => {
1666
1323
  return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
1667
1324
  });
1668
1325
  }
1669
- hasIn(path2) {
1670
- const [key, ...rest] = path2;
1326
+ hasIn(path) {
1327
+ const [key, ...rest] = path;
1671
1328
  if (rest.length === 0)
1672
1329
  return this.has(key);
1673
1330
  const node = this.get(key, true);
1674
1331
  return identity.isCollection(node) ? node.hasIn(rest) : false;
1675
1332
  }
1676
- setIn(path2, value) {
1677
- const [key, ...rest] = path2;
1333
+ setIn(path, value) {
1334
+ const [key, ...rest] = path;
1678
1335
  if (rest.length === 0) {
1679
1336
  this.set(key, value);
1680
1337
  } else {
@@ -2409,7 +2066,7 @@ var require_log = __commonJS((exports) => {
2409
2066
  if (logLevel === "debug")
2410
2067
  console.log(...messages);
2411
2068
  }
2412
- function warn2(logLevel, warning) {
2069
+ function warn(logLevel, warning) {
2413
2070
  if (logLevel === "debug" || logLevel === "warn") {
2414
2071
  if (typeof node_process.emitWarning === "function")
2415
2072
  node_process.emitWarning(warning);
@@ -2418,7 +2075,7 @@ var require_log = __commonJS((exports) => {
2418
2075
  }
2419
2076
  }
2420
2077
  exports.debug = debug;
2421
- exports.warn = warn2;
2078
+ exports.warn = warn;
2422
2079
  });
2423
2080
 
2424
2081
  // node_modules/yaml/dist/schema/yaml-1.1/merge.js
@@ -4067,9 +3724,9 @@ var require_Document = __commonJS((exports) => {
4067
3724
  if (assertCollection(this.contents))
4068
3725
  this.contents.add(value);
4069
3726
  }
4070
- addIn(path2, value) {
3727
+ addIn(path, value) {
4071
3728
  if (assertCollection(this.contents))
4072
- this.contents.addIn(path2, value);
3729
+ this.contents.addIn(path, value);
4073
3730
  }
4074
3731
  createAlias(node, name) {
4075
3732
  if (!node.anchor) {
@@ -4118,30 +3775,30 @@ var require_Document = __commonJS((exports) => {
4118
3775
  delete(key) {
4119
3776
  return assertCollection(this.contents) ? this.contents.delete(key) : false;
4120
3777
  }
4121
- deleteIn(path2) {
4122
- if (Collection.isEmptyPath(path2)) {
3778
+ deleteIn(path) {
3779
+ if (Collection.isEmptyPath(path)) {
4123
3780
  if (this.contents == null)
4124
3781
  return false;
4125
3782
  this.contents = null;
4126
3783
  return true;
4127
3784
  }
4128
- return assertCollection(this.contents) ? this.contents.deleteIn(path2) : false;
3785
+ return assertCollection(this.contents) ? this.contents.deleteIn(path) : false;
4129
3786
  }
4130
3787
  get(key, keepScalar) {
4131
3788
  return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined;
4132
3789
  }
4133
- getIn(path2, keepScalar) {
4134
- if (Collection.isEmptyPath(path2))
3790
+ getIn(path, keepScalar) {
3791
+ if (Collection.isEmptyPath(path))
4135
3792
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
4136
- return identity.isCollection(this.contents) ? this.contents.getIn(path2, keepScalar) : undefined;
3793
+ return identity.isCollection(this.contents) ? this.contents.getIn(path, keepScalar) : undefined;
4137
3794
  }
4138
3795
  has(key) {
4139
3796
  return identity.isCollection(this.contents) ? this.contents.has(key) : false;
4140
3797
  }
4141
- hasIn(path2) {
4142
- if (Collection.isEmptyPath(path2))
3798
+ hasIn(path) {
3799
+ if (Collection.isEmptyPath(path))
4143
3800
  return this.contents !== undefined;
4144
- return identity.isCollection(this.contents) ? this.contents.hasIn(path2) : false;
3801
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path) : false;
4145
3802
  }
4146
3803
  set(key, value) {
4147
3804
  if (this.contents == null) {
@@ -4150,13 +3807,13 @@ var require_Document = __commonJS((exports) => {
4150
3807
  this.contents.set(key, value);
4151
3808
  }
4152
3809
  }
4153
- setIn(path2, value) {
4154
- if (Collection.isEmptyPath(path2)) {
3810
+ setIn(path, value) {
3811
+ if (Collection.isEmptyPath(path)) {
4155
3812
  this.contents = value;
4156
3813
  } else if (this.contents == null) {
4157
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path2), value);
3814
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path), value);
4158
3815
  } else if (assertCollection(this.contents)) {
4159
- this.contents.setIn(path2, value);
3816
+ this.contents.setIn(path, value);
4160
3817
  }
4161
3818
  }
4162
3819
  setSchema(version, options = {}) {
@@ -4255,12 +3912,12 @@ var require_errors = __commonJS((exports) => {
4255
3912
  super("YAMLWarning", pos, code, message);
4256
3913
  }
4257
3914
  }
4258
- var prettifyError = (src, lc) => (error2) => {
4259
- if (error2.pos[0] === -1)
3915
+ var prettifyError = (src, lc) => (error) => {
3916
+ if (error.pos[0] === -1)
4260
3917
  return;
4261
- error2.linePos = error2.pos.map((pos) => lc.linePos(pos));
4262
- const { line, col } = error2.linePos[0];
4263
- error2.message += ` at line ${line}, column ${col}`;
3918
+ error.linePos = error.pos.map((pos) => lc.linePos(pos));
3919
+ const { line, col } = error.linePos[0];
3920
+ error.message += ` at line ${line}, column ${col}`;
4264
3921
  let ci = col - 1;
4265
3922
  let lineStr = src.substring(lc.lineStarts[line - 1], lc.lineStarts[line]).replace(/[\n\r]+$/, "");
4266
3923
  if (ci >= 60 && lineStr.length > 80) {
@@ -4279,12 +3936,12 @@ var require_errors = __commonJS((exports) => {
4279
3936
  }
4280
3937
  if (/[^ ]/.test(lineStr)) {
4281
3938
  let count = 1;
4282
- const end = error2.linePos[1];
3939
+ const end = error.linePos[1];
4283
3940
  if (end?.line === line && end.col > col) {
4284
3941
  count = Math.max(1, Math.min(end.col - col, 80 - ci));
4285
3942
  }
4286
3943
  const pointer = " ".repeat(ci) + "^".repeat(count);
4287
- error2.message += `:
3944
+ error.message += `:
4288
3945
 
4289
3946
  ${lineStr}
4290
3947
  ${pointer}
@@ -5074,7 +4731,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
5074
4731
  const mode = source[0];
5075
4732
  let indent = 0;
5076
4733
  let chomp = "";
5077
- let error2 = -1;
4734
+ let error = -1;
5078
4735
  for (let i = 1;i < source.length; ++i) {
5079
4736
  const ch = source[i];
5080
4737
  if (!chomp && (ch === "-" || ch === "+"))
@@ -5083,12 +4740,12 @@ var require_resolve_block_scalar = __commonJS((exports) => {
5083
4740
  const n = Number(ch);
5084
4741
  if (!indent && n)
5085
4742
  indent = n;
5086
- else if (error2 === -1)
5087
- error2 = offset + i;
4743
+ else if (error === -1)
4744
+ error = offset + i;
5088
4745
  }
5089
4746
  }
5090
- if (error2 !== -1)
5091
- onError(error2, "UNEXPECTED_TOKEN", `Block scalar header includes extra characters: ${source}`);
4747
+ if (error !== -1)
4748
+ onError(error, "UNEXPECTED_TOKEN", `Block scalar header includes extra characters: ${source}`);
5092
4749
  let hasSpace = false;
5093
4750
  let comment = "";
5094
4751
  let length = source.length;
@@ -5375,8 +5032,8 @@ var require_compose_scalar = __commonJS((exports) => {
5375
5032
  try {
5376
5033
  const res = tag.resolve(value, (msg) => onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg), ctx.options);
5377
5034
  scalar = identity.isScalar(res) ? res : new Scalar.Scalar(res);
5378
- } catch (error2) {
5379
- const msg = error2 instanceof Error ? error2.message : String(error2);
5035
+ } catch (error) {
5036
+ const msg = error instanceof Error ? error.message : String(error);
5380
5037
  onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg);
5381
5038
  scalar = new Scalar.Scalar(value);
5382
5039
  }
@@ -5493,8 +5150,8 @@ var require_compose_node = __commonJS((exports) => {
5493
5150
  node = composeCollection.composeCollection(CN, ctx, token, props, onError);
5494
5151
  if (anchor)
5495
5152
  node.anchor = anchor.source.substring(1);
5496
- } catch (error2) {
5497
- const message = error2 instanceof Error ? error2.message : String(error2);
5153
+ } catch (error) {
5154
+ const message = error instanceof Error ? error.message : String(error);
5498
5155
  onError(token, "RESOURCE_EXHAUSTION", message);
5499
5156
  }
5500
5157
  break;
@@ -5743,11 +5400,11 @@ ${cb}` : comment;
5743
5400
  break;
5744
5401
  case "error": {
5745
5402
  const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message;
5746
- const error2 = new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
5403
+ const error = new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg);
5747
5404
  if (this.atDirectives || !this.doc)
5748
- this.errors.push(error2);
5405
+ this.errors.push(error);
5749
5406
  else
5750
- this.doc.errors.push(error2);
5407
+ this.doc.errors.push(error);
5751
5408
  break;
5752
5409
  }
5753
5410
  case "doc-end": {
@@ -6051,9 +5708,9 @@ var require_cst_visit = __commonJS((exports) => {
6051
5708
  visit.BREAK = BREAK;
6052
5709
  visit.SKIP = SKIP;
6053
5710
  visit.REMOVE = REMOVE;
6054
- visit.itemAtPath = (cst, path2) => {
5711
+ visit.itemAtPath = (cst, path) => {
6055
5712
  let item = cst;
6056
- for (const [field, index] of path2) {
5713
+ for (const [field, index] of path) {
6057
5714
  const tok = item?.[field];
6058
5715
  if (tok && "items" in tok) {
6059
5716
  item = tok.items[index];
@@ -6062,23 +5719,23 @@ var require_cst_visit = __commonJS((exports) => {
6062
5719
  }
6063
5720
  return item;
6064
5721
  };
6065
- visit.parentCollection = (cst, path2) => {
6066
- const parent = visit.itemAtPath(cst, path2.slice(0, -1));
6067
- const field = path2[path2.length - 1][0];
5722
+ visit.parentCollection = (cst, path) => {
5723
+ const parent = visit.itemAtPath(cst, path.slice(0, -1));
5724
+ const field = path[path.length - 1][0];
6068
5725
  const coll = parent?.[field];
6069
5726
  if (coll && "items" in coll)
6070
5727
  return coll;
6071
5728
  throw new Error("Parent collection not found");
6072
5729
  };
6073
- function _visit(path2, item, visitor) {
6074
- let ctrl = visitor(item, path2);
5730
+ function _visit(path, item, visitor) {
5731
+ let ctrl = visitor(item, path);
6075
5732
  if (typeof ctrl === "symbol")
6076
5733
  return ctrl;
6077
5734
  for (const field of ["key", "value"]) {
6078
5735
  const token = item[field];
6079
5736
  if (token && "items" in token) {
6080
5737
  for (let i = 0;i < token.items.length; ++i) {
6081
- const ci = _visit(Object.freeze(path2.concat([[field, i]])), token.items[i], visitor);
5738
+ const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor);
6082
5739
  if (typeof ci === "number")
6083
5740
  i = ci - 1;
6084
5741
  else if (ci === BREAK)
@@ -6089,10 +5746,10 @@ var require_cst_visit = __commonJS((exports) => {
6089
5746
  }
6090
5747
  }
6091
5748
  if (typeof ctrl === "function" && field === "key")
6092
- ctrl = ctrl(item, path2);
5749
+ ctrl = ctrl(item, path);
6093
5750
  }
6094
5751
  }
6095
- return typeof ctrl === "function" ? ctrl(item, path2) : ctrl;
5752
+ return typeof ctrl === "function" ? ctrl(item, path) : ctrl;
6096
5753
  }
6097
5754
  exports.visit = visit;
6098
5755
  });
@@ -7020,8 +6677,8 @@ var require_parser = __commonJS((exports) => {
7020
6677
  peek(n) {
7021
6678
  return this.stack[this.stack.length - n];
7022
6679
  }
7023
- *pop(error2) {
7024
- const token = error2 ?? this.stack.pop();
6680
+ *pop(error) {
6681
+ const token = error ?? this.stack.pop();
7025
6682
  if (!token) {
7026
6683
  const message = "Tried to pop an empty stack";
7027
6684
  yield { type: "error", offset: this.offset, source: "", message };
@@ -7361,14 +7018,14 @@ var require_parser = __commonJS((exports) => {
7361
7018
  case "scalar":
7362
7019
  case "single-quoted-scalar":
7363
7020
  case "double-quoted-scalar": {
7364
- const fs4 = this.flowScalar(this.type);
7021
+ const fs2 = this.flowScalar(this.type);
7365
7022
  if (atNextItem || it.value) {
7366
- map.items.push({ start, key: fs4, sep: [] });
7023
+ map.items.push({ start, key: fs2, sep: [] });
7367
7024
  this.onKeyLine = true;
7368
7025
  } else if (it.sep) {
7369
- this.stack.push(fs4);
7026
+ this.stack.push(fs2);
7370
7027
  } else {
7371
- Object.assign(it, { key: fs4, sep: [] });
7028
+ Object.assign(it, { key: fs2, sep: [] });
7372
7029
  this.onKeyLine = true;
7373
7030
  }
7374
7031
  return;
@@ -7496,13 +7153,13 @@ var require_parser = __commonJS((exports) => {
7496
7153
  case "scalar":
7497
7154
  case "single-quoted-scalar":
7498
7155
  case "double-quoted-scalar": {
7499
- const fs4 = this.flowScalar(this.type);
7156
+ const fs2 = this.flowScalar(this.type);
7500
7157
  if (!it || it.value)
7501
- fc.items.push({ start: [], key: fs4, sep: [] });
7158
+ fc.items.push({ start: [], key: fs2, sep: [] });
7502
7159
  else if (it.sep)
7503
- this.stack.push(fs4);
7160
+ this.stack.push(fs2);
7504
7161
  else
7505
- Object.assign(it, { key: fs4, sep: [] });
7162
+ Object.assign(it, { key: fs2, sep: [] });
7506
7163
  return;
7507
7164
  }
7508
7165
  case "flow-map-end":
@@ -7804,7 +7461,301 @@ var init_dist = __esm(() => {
7804
7461
  $visitAsync = visit.visitAsync;
7805
7462
  });
7806
7463
 
7807
- // src/workflows/schema.ts
7464
+ // src/core/asset/frontmatter.ts
7465
+ function parseFrontmatter(raw) {
7466
+ const parsedBlock = parseFrontmatterBlock(raw);
7467
+ if (!parsedBlock) {
7468
+ return { data: {}, content: raw, frontmatter: null, bodyStartLine: 1 };
7469
+ }
7470
+ let data = {};
7471
+ if (parsedBlock.frontmatter.trim()) {
7472
+ try {
7473
+ const parsed = $parse(parsedBlock.frontmatter);
7474
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
7475
+ data = normalizeYamlValues(parsed);
7476
+ }
7477
+ } catch {
7478
+ data = parseFrontmatterLenient(parsedBlock.frontmatter);
7479
+ }
7480
+ }
7481
+ return {
7482
+ data,
7483
+ content: parsedBlock.content,
7484
+ frontmatter: parsedBlock.frontmatter,
7485
+ bodyStartLine: parsedBlock.bodyStartLine
7486
+ };
7487
+ }
7488
+ function normalizeYamlValues(value) {
7489
+ if (value instanceof Date) {
7490
+ const y = value.getUTCFullYear();
7491
+ const m = String(value.getUTCMonth() + 1).padStart(2, "0");
7492
+ const d = String(value.getUTCDate()).padStart(2, "0");
7493
+ return `${y}-${m}-${d}`;
7494
+ }
7495
+ if (value === null)
7496
+ return "";
7497
+ if (Array.isArray(value))
7498
+ return value.map(normalizeYamlValues);
7499
+ if (typeof value === "object") {
7500
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, normalizeYamlValues(v)]));
7501
+ }
7502
+ return value;
7503
+ }
7504
+ function parseFrontmatterLenient(frontmatter) {
7505
+ const data = {};
7506
+ for (const line of frontmatter.split(/\r?\n/)) {
7507
+ const m = line.match(/^([\w][\w-]*):\s*(.*)$/);
7508
+ if (!m)
7509
+ continue;
7510
+ const key = m[1];
7511
+ const rawValue = (m[2] ?? "").trim();
7512
+ try {
7513
+ const singleEntry = $parse(`k: ${rawValue}`);
7514
+ if (singleEntry !== null && typeof singleEntry === "object" && !Array.isArray(singleEntry)) {
7515
+ const v = singleEntry.k;
7516
+ data[key] = v === null || v === undefined ? "" : v;
7517
+ } else {
7518
+ data[key] = rawValue;
7519
+ }
7520
+ } catch {
7521
+ data[key] = rawValue;
7522
+ }
7523
+ }
7524
+ return data;
7525
+ }
7526
+ function parseFrontmatterBlock(raw) {
7527
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r\n|\r|\n|$)([\s\S]*)$/);
7528
+ if (match) {
7529
+ const frontmatter = match[1].replace(/\r/g, "");
7530
+ const content = match[2];
7531
+ return {
7532
+ frontmatter,
7533
+ content,
7534
+ bodyStartLine: countLines(raw.slice(0, match[0].length - match[2].length)) + 1
7535
+ };
7536
+ }
7537
+ const emptyMatch = raw.match(/^---\r?\n---(?:\r\n|\r|\n)([\s\S]*)$/);
7538
+ if (emptyMatch) {
7539
+ return { frontmatter: "", content: emptyMatch[1], bodyStartLine: 3 };
7540
+ }
7541
+ return null;
7542
+ }
7543
+ function countLines(text) {
7544
+ if (text.length === 0)
7545
+ return 0;
7546
+ return text.split(/\r?\n/).length - 1;
7547
+ }
7548
+ var init_frontmatter = __esm(() => {
7549
+ init_dist();
7550
+ });
7551
+
7552
+ // src/core/asset/markdown.ts
7553
+ function parseMarkdownToc(content) {
7554
+ const lines = content.split(/\r?\n/);
7555
+ const headings = [];
7556
+ const parsed = parseFrontmatter(content);
7557
+ const start = parsed.frontmatter ? parsed.bodyStartLine - 1 : 0;
7558
+ let inFence = false;
7559
+ for (let i = start;i < lines.length; i++) {
7560
+ if (/^\s*(`{3,}|~{3,})/.test(lines[i])) {
7561
+ inFence = !inFence;
7562
+ continue;
7563
+ }
7564
+ if (inFence)
7565
+ continue;
7566
+ const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
7567
+ if (match) {
7568
+ headings.push({
7569
+ level: match[1].length,
7570
+ text: match[2].replace(/\s+#+\s*$/, "").trim(),
7571
+ line: i + 1
7572
+ });
7573
+ }
7574
+ }
7575
+ return { headings, totalLines: lines.length };
7576
+ }
7577
+ var init_markdown = __esm(() => {
7578
+ init_frontmatter();
7579
+ });
7580
+
7581
+ // src/core/warn.ts
7582
+ import fs2 from "fs";
7583
+ function isVerbose() {
7584
+ const env = process.env.AKM_VERBOSE?.trim().toLowerCase();
7585
+ if (env === "1" || env === "true" || env === "yes" || env === "on")
7586
+ return true;
7587
+ if (env === "0" || env === "false" || env === "no" || env === "off")
7588
+ return false;
7589
+ return verbose;
7590
+ }
7591
+ function appendToLogFile(level, args) {
7592
+ if (!logFilePath)
7593
+ return;
7594
+ const ts = new Date().toISOString();
7595
+ const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
7596
+ try {
7597
+ fs2.appendFileSync(logFilePath, `[${ts}] [${level}] ${msg}
7598
+ `);
7599
+ } catch (e) {
7600
+ process.stderr.write(`[akm:warn] log-file write failed (${logFilePath}): ${e}
7601
+ `);
7602
+ process.stderr.write(`[${ts}] [${level}] ${msg}
7603
+ `);
7604
+ }
7605
+ }
7606
+ function warn(...args) {
7607
+ appendToLogFile("WARN", args);
7608
+ if (!quiet) {
7609
+ console.warn(...args);
7610
+ }
7611
+ }
7612
+ function error(...args) {
7613
+ appendToLogFile("ERROR", args);
7614
+ if (!quiet) {
7615
+ console.error(...args);
7616
+ }
7617
+ }
7618
+ function warnVerbose(...args) {
7619
+ if (isVerbose()) {
7620
+ warn(...args);
7621
+ }
7622
+ }
7623
+ var quiet = false, verbose = false, logFilePath;
7624
+ var init_warn = () => {};
7625
+
7626
+ // src/indexer/walk/file-context.ts
7627
+ var renderers;
7628
+ var init_file_context = __esm(() => {
7629
+ init_frontmatter();
7630
+ init_common();
7631
+ renderers = new Map;
7632
+ });
7633
+
7634
+ // src/indexer/passes/metadata-contributors.ts
7635
+ function registerMetadataContributor(contributor) {
7636
+ contributors.push(contributor);
7637
+ }
7638
+ var contributors;
7639
+ var init_metadata_contributors = __esm(() => {
7640
+ contributors = [];
7641
+ });
7642
+
7643
+ // src/indexer/passes/metadata.ts
7644
+ import fs3 from "fs";
7645
+ function extractDescriptionFromComments(filePath) {
7646
+ let content;
7647
+ try {
7648
+ content = fs3.readFileSync(filePath, "utf8");
7649
+ } catch {
7650
+ return null;
7651
+ }
7652
+ const lines = content.split(/\r?\n/).slice(0, 50);
7653
+ const blockStart = lines.findIndex((l) => /^\s*\/\*\*/.test(l));
7654
+ if (blockStart >= 0) {
7655
+ const desc = [];
7656
+ for (let i = blockStart;i < lines.length; i++) {
7657
+ const line = lines[i];
7658
+ if (i > blockStart && /\*\//.test(line))
7659
+ break;
7660
+ const cleaned = line.replace(/^\s*\/?\*\*?\s?/, "").replace(/\*\/\s*$/, "").trim();
7661
+ if (cleaned)
7662
+ desc.push(cleaned);
7663
+ }
7664
+ if (desc.length > 0)
7665
+ return desc.join(" ");
7666
+ }
7667
+ let start = 0;
7668
+ if (lines[0]?.startsWith("#!"))
7669
+ start = 1;
7670
+ const hashLines = [];
7671
+ for (let i = start;i < lines.length; i++) {
7672
+ const line = lines[i].trim();
7673
+ if (line.startsWith("#") && !line.startsWith("#!")) {
7674
+ hashLines.push(line.replace(/^#+\s*/, "").trim());
7675
+ } else if (line === "") {} else {
7676
+ break;
7677
+ }
7678
+ }
7679
+ if (hashLines.length > 0)
7680
+ return hashLines.join(" ");
7681
+ return null;
7682
+ }
7683
+ var KNOWN_QUALITY_VALUES, warnedUnknownQualityValues, WIKI_INFRA_FILES;
7684
+ var init_metadata = __esm(() => {
7685
+ init_asset_spec();
7686
+ init_frontmatter();
7687
+ init_common();
7688
+ init_warn();
7689
+ init_file_context();
7690
+ init_metadata_contributors();
7691
+ KNOWN_QUALITY_VALUES = new Set(["generated", "curated", "enriched", "proposed"]);
7692
+ warnedUnknownQualityValues = new Set;
7693
+ WIKI_INFRA_FILES = new Set(["schema.md", "index.md", "log.md"]);
7694
+ });
7695
+
7696
+ // src/core/asset/asset-ref.ts
7697
+ import path from "path";
7698
+ function parseAssetRef(ref) {
7699
+ const trimmed = ref.trim();
7700
+ if (!trimmed)
7701
+ throw new UsageError("Empty ref.", "MISSING_REQUIRED_ARGUMENT");
7702
+ let origin;
7703
+ let body = trimmed;
7704
+ const boundary = trimmed.indexOf("//");
7705
+ if (boundary >= 0) {
7706
+ origin = trimmed.slice(0, boundary);
7707
+ body = trimmed.slice(boundary + 2);
7708
+ if (!origin)
7709
+ throw new UsageError("Empty origin in ref.", "MISSING_REQUIRED_ARGUMENT");
7710
+ }
7711
+ const colon = body.indexOf(":");
7712
+ if (colon <= 0) {
7713
+ throw new UsageError(`Invalid ref "${trimmed}". Expected [origin//]type:name, e.g. skill:deploy or knowledge:guide.md`, "MISSING_REQUIRED_ARGUMENT");
7714
+ }
7715
+ const rawType = body.slice(0, colon);
7716
+ const rawName = body.slice(colon + 1);
7717
+ if (rawType === "vault") {
7718
+ throw new UsageError("The `vault` asset type was removed in 0.9.0 \u2014 use `env:` (whole .env config) or `secret:` (a single value).", "MISSING_REQUIRED_ARGUMENT");
7719
+ }
7720
+ const resolvedType = TYPE_ALIASES[rawType] ?? rawType;
7721
+ if (!isAssetType(resolvedType)) {
7722
+ throw new UsageError(`Invalid asset type: "${rawType}".`, "MISSING_REQUIRED_ARGUMENT");
7723
+ }
7724
+ validateName(rawName);
7725
+ const name = normalizeName(rawName);
7726
+ return { type: resolvedType, name, origin: origin || undefined };
7727
+ }
7728
+ function validateName(name) {
7729
+ if (!name)
7730
+ throw new UsageError("Empty asset name.", "MISSING_REQUIRED_ARGUMENT");
7731
+ if (name.includes("\x00"))
7732
+ throw new UsageError("Null byte in asset name.", "MISSING_REQUIRED_ARGUMENT");
7733
+ if (/^[A-Za-z]:/.test(name))
7734
+ throw new UsageError("Windows drive path in asset name.", "MISSING_REQUIRED_ARGUMENT");
7735
+ const normalized = path.posix.normalize(name.replace(/\\/g, "/"));
7736
+ if (path.posix.isAbsolute(normalized))
7737
+ throw new UsageError("Absolute path in asset name.", "MISSING_REQUIRED_ARGUMENT");
7738
+ if (normalized === ".." || normalized.startsWith("../")) {
7739
+ throw new UsageError("Path traversal in asset name.", "MISSING_REQUIRED_ARGUMENT");
7740
+ }
7741
+ const segments = normalized.split("/");
7742
+ if (segments.some((seg) => seg === "." || seg === "..")) {
7743
+ throw new UsageError("Asset name cannot contain relative path segments.", "MISSING_REQUIRED_ARGUMENT");
7744
+ }
7745
+ }
7746
+ function normalizeName(name) {
7747
+ return path.posix.normalize(name.replace(/\\/g, "/"));
7748
+ }
7749
+ var TYPE_ALIASES;
7750
+ var init_asset_ref = __esm(() => {
7751
+ init_common();
7752
+ init_errors();
7753
+ TYPE_ALIASES = {
7754
+ environment: "env"
7755
+ };
7756
+ });
7757
+
7758
+ // src/workflows/schema.ts
7808
7759
  var WORKFLOW_SCHEMA_VERSION = 1;
7809
7760
 
7810
7761
  // src/workflows/validator.ts
@@ -8198,6 +8149,7 @@ function sortErrors(errors2) {
8198
8149
  var WORKFLOW_TITLE_PREFIX = "Workflow:", STEP_PREFIX = "Step:", STEP_ID_LINE, BULLET_LINE, SUBSECTION_INSTRUCTIONS = "Instructions", SUBSECTION_COMPLETION_CRITERIA = "Completion Criteria";
8199
8150
  var init_parser = __esm(() => {
8200
8151
  init_dist();
8152
+ init_frontmatter();
8201
8153
  init_markdown();
8202
8154
  init_validator();
8203
8155
  STEP_ID_LINE = /^Step ID:\s+(.+?)\s*$/;
@@ -8288,6 +8240,25 @@ function applyTocMetadata(entry, ctx) {
8288
8240
  entry.toc = toc.headings;
8289
8241
  } catch {}
8290
8242
  }
8243
+ function applyFactMetadata(entry, ctx) {
8244
+ try {
8245
+ const fm = applyFrontmatterDescriptionAndTags(entry, ctx);
8246
+ const tags = new Set([...entry.tags ?? [], "fact"]);
8247
+ const hints = new Set(entry.searchHints ?? []);
8248
+ const category = asNonEmptyString(fm.category);
8249
+ if (category) {
8250
+ tags.add(category);
8251
+ hints.add(`category:${category}`);
8252
+ }
8253
+ if (fm.pinned === true) {
8254
+ tags.add("pinned");
8255
+ hints.add("pinned");
8256
+ }
8257
+ entry.tags = Array.from(tags).filter(Boolean);
8258
+ if (hints.size > 0)
8259
+ entry.searchHints = Array.from(hints).filter(Boolean);
8260
+ } catch {}
8261
+ }
8291
8262
  function applyFrontmatterDescriptionAndTags(entry, ctx) {
8292
8263
  const parsed = parseFrontmatter(ctx.content());
8293
8264
  const fm = parsed.data;
@@ -8387,6 +8358,7 @@ function applyTaskMetadata(entry, ctx) {
8387
8358
  }
8388
8359
  var init_renderers = __esm(() => {
8389
8360
  init_env();
8361
+ init_frontmatter();
8390
8362
  init_markdown();
8391
8363
  init_common();
8392
8364
  init_metadata();
@@ -8433,6 +8405,11 @@ var init_renderers = __esm(() => {
8433
8405
  appliesTo: ({ rendererName }) => rendererName === "session-md",
8434
8406
  contribute: (entry, ctx) => applySessionMetadata(entry, ctx.renderContext)
8435
8407
  });
8408
+ registerMetadataContributor({
8409
+ name: "fact-md-metadata",
8410
+ appliesTo: ({ rendererName }) => rendererName === "fact-md",
8411
+ contribute: (entry, ctx) => applyFactMetadata(entry, ctx.renderContext)
8412
+ });
8436
8413
  });
8437
8414
 
8438
8415
  // src/core/asset/asset-registry.ts
@@ -8567,6 +8544,12 @@ var init_asset_spec = __esm(() => {
8567
8544
  ...markdownSpec,
8568
8545
  rendererName: "session-md",
8569
8546
  actionBuilder: (ref) => `akm show ${ref} -> read the session summary; follow the \`access\` frontmatter to open the raw log at \`log_path\``
8547
+ },
8548
+ fact: {
8549
+ stashDir: "facts",
8550
+ ...markdownSpec,
8551
+ rendererName: "fact-md",
8552
+ actionBuilder: (ref) => `akm show ${ref} -> read the stash fact and apply it as durable context`
8570
8553
  }
8571
8554
  };
8572
8555
  TYPE_DIRS = Object.fromEntries(Object.entries(ASSET_SPECS_INTERNAL).map(([type, spec]) => [type, spec.stashDir]));
@@ -8840,6 +8823,103 @@ function runMigrations(db, migrations, opts) {
8840
8823
  }
8841
8824
  }
8842
8825
 
8826
+ // src/runtime.ts
8827
+ import { createHash } from "crypto";
8828
+ import { createWriteStream, statfsSync } from "fs";
8829
+ import { createRequire as createRequire2 } from "module";
8830
+ function sha256Hex(data) {
8831
+ return createHash("sha256").update(data).digest("hex");
8832
+ }
8833
+ function statfsType(path5) {
8834
+ try {
8835
+ return statfsSync(path5).type;
8836
+ } catch {
8837
+ return;
8838
+ }
8839
+ }
8840
+ function sleepSync(ms) {
8841
+ if (isBun2) {
8842
+ bunGlobal().sleepSync(ms);
8843
+ return;
8844
+ }
8845
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
8846
+ }
8847
+ function bunGlobal() {
8848
+ return globalThis.Bun;
8849
+ }
8850
+ var isBun2, nodeRequire2, mainPath;
8851
+ var init_runtime = __esm(() => {
8852
+ isBun2 = !!process.versions?.bun;
8853
+ nodeRequire2 = createRequire2(import.meta.url);
8854
+ mainPath = isBun2 ? bunGlobal().main : process.argv[1];
8855
+ });
8856
+
8857
+ // src/storage/sqlite-pragmas.ts
8858
+ function resolveJournalMode(raw) {
8859
+ if (raw === undefined)
8860
+ return "WAL";
8861
+ const normalized = raw.trim().toUpperCase();
8862
+ if (normalized === "")
8863
+ return "WAL";
8864
+ if (VALID_MODES.has(normalized)) {
8865
+ return normalized;
8866
+ }
8867
+ warnInvalidJournalModeOnce(raw);
8868
+ return "WAL";
8869
+ }
8870
+ function resolveConfiguredJournalMode() {
8871
+ return resolveJournalMode(process.env.AKM_SQLITE_JOURNAL_MODE);
8872
+ }
8873
+ function warnInvalidJournalModeOnce(raw) {
8874
+ if (warnedInvalid)
8875
+ return;
8876
+ warnedInvalid = true;
8877
+ warn(`[akm] invalid AKM_SQLITE_JOURNAL_MODE=${JSON.stringify(raw)} \u2014 using WAL (valid: WAL, DELETE, TRUNCATE)`);
8878
+ }
8879
+ function isNetworkFilesystem(fsType) {
8880
+ if (fsType === undefined)
8881
+ return false;
8882
+ return NETWORK_FS_MAGICS.has(fsType);
8883
+ }
8884
+ function applyStandardPragmas(db, opts = {}) {
8885
+ let mode = resolveConfiguredJournalMode();
8886
+ if (mode === "WAL" && opts.dataDir) {
8887
+ const probe = opts.fsTypeProbe ?? statfsType;
8888
+ if (isNetworkFilesystem(probe(opts.dataDir))) {
8889
+ mode = "DELETE";
8890
+ warnNetworkFallbackOnce(opts.dataDir);
8891
+ }
8892
+ }
8893
+ db.exec("PRAGMA busy_timeout = 30000");
8894
+ db.exec(`PRAGMA journal_mode = ${mode}`);
8895
+ if (opts.foreignKeys !== false) {
8896
+ db.exec("PRAGMA foreign_keys = ON");
8897
+ }
8898
+ if (mode !== "WAL") {
8899
+ db.exec("PRAGMA synchronous = FULL");
8900
+ }
8901
+ return mode;
8902
+ }
8903
+ function warnNetworkFallbackOnce(dataDir) {
8904
+ if (warnedNetworkFallback)
8905
+ return;
8906
+ warnedNetworkFallback = true;
8907
+ warn(`[akm] network filesystem detected at ${dataDir} \u2014 WAL unsupported, using DELETE journal mode`);
8908
+ }
8909
+ var VALID_MODES, warnedInvalid = false, warnedNetworkFallback = false, FS_MAGIC_NFS = 26985, FS_MAGIC_SMB = 20859, FS_MAGIC_CIFS = 4283649346, FS_MAGIC_SMB2 = 4266872130, FS_MAGIC_FUSE = 1702057286, NETWORK_FS_MAGICS;
8910
+ var init_sqlite_pragmas = __esm(() => {
8911
+ init_warn();
8912
+ init_runtime();
8913
+ VALID_MODES = new Set(["WAL", "DELETE", "TRUNCATE"]);
8914
+ NETWORK_FS_MAGICS = new Set([
8915
+ FS_MAGIC_NFS,
8916
+ FS_MAGIC_SMB,
8917
+ FS_MAGIC_CIFS,
8918
+ FS_MAGIC_SMB2,
8919
+ FS_MAGIC_FUSE
8920
+ ]);
8921
+ });
8922
+
8843
8923
  // src/core/assert.ts
8844
8924
  function assertNever(x, context) {
8845
8925
  let serialized;
@@ -8882,12 +8962,17 @@ var init_improve_types = () => {};
8882
8962
  // src/core/state-db.ts
8883
8963
  var exports_state_db = {};
8884
8964
  __export(exports_state_db, {
8965
+ withImmediateTransaction: () => withImmediateTransaction,
8885
8966
  upsertTaskHistory: () => upsertTaskHistory,
8886
8967
  upsertProposal: () => upsertProposal,
8887
8968
  upsertExtractedSession: () => upsertExtractedSession,
8969
+ upsertConsolidationJudged: () => upsertConsolidationJudged,
8970
+ upsertBodyEmbeddings: () => upsertBodyEmbeddings,
8888
8971
  shouldSkipAlreadyExtractedSession: () => shouldSkipAlreadyExtractedSession,
8889
8972
  runMigrations: () => runMigrations2,
8973
+ recordRecombineInduction: () => recordRecombineInduction,
8890
8974
  recordImproveRun: () => recordImproveRun,
8975
+ recordFsProposalsImport: () => recordFsProposalsImport,
8891
8976
  readStateEvents: () => readStateEvents,
8892
8977
  queryTaskHistory: () => queryTaskHistory,
8893
8978
  queryImproveRuns: () => queryImproveRuns,
@@ -8896,19 +8981,32 @@ __export(exports_state_db, {
8896
8981
  purgeOldEvents: () => purgeOldEvents,
8897
8982
  proposalToRowValues: () => proposalToRowValues,
8898
8983
  proposalRowToProposal: () => proposalRowToProposal,
8984
+ persistPhaseThreshold: () => persistPhaseThreshold,
8899
8985
  openStateDatabase: () => openStateDatabase,
8986
+ markRecombineHypothesisPromoted: () => markRecombineHypothesisPromoted,
8900
8987
  listStateProposals: () => listStateProposals,
8988
+ listStateProposalIdsByPrefix: () => listStateProposalIdsByPrefix,
8989
+ listProposalGateDecisions: () => listProposalGateDecisions,
8901
8990
  listExistingTableNames: () => listExistingTableNames,
8991
+ insertProposalIfAbsent: () => insertProposalIfAbsent,
8902
8992
  insertEvent: () => insertEvent,
8903
8993
  importEventsJsonl: () => importEventsJsonl,
8994
+ hasImportedFsProposals: () => hasImportedFsProposals,
8904
8995
  getTaskHistoryRuns: () => getTaskHistoryRuns,
8905
8996
  getTaskHistory: () => getTaskHistory,
8906
8997
  getStateProposal: () => getStateProposal,
8907
8998
  getStateDbPath: () => getStateDbPath,
8999
+ getRecombineHypothesis: () => getRecombineHypothesis,
9000
+ getPhaseThreshold: () => getPhaseThreshold,
8908
9001
  getExtractedSessionsMap: () => getExtractedSessionsMap,
8909
9002
  getExtractedSession: () => getExtractedSession,
9003
+ getConsolidationJudgedMap: () => getConsolidationJudgedMap,
9004
+ getBodyEmbeddings: () => getBodyEmbeddings,
8910
9005
  eventRowToEnvelope: () => eventRowToEnvelope,
9006
+ embeddingToBlob: () => embeddingToBlob,
9007
+ decayUnseenRecombineHypotheses: () => decayUnseenRecombineHypotheses,
8911
9008
  computeImproveRunMetrics: () => computeImproveRunMetrics,
9009
+ blobToEmbedding: () => blobToEmbedding,
8912
9010
  REGISTRY_INDEX_CACHE_DDL: () => REGISTRY_INDEX_CACHE_DDL
8913
9011
  });
8914
9012
  import fs5 from "fs";
@@ -8923,9 +9021,7 @@ function openStateDatabase(dbPath) {
8923
9021
  fs5.mkdirSync(dir, { recursive: true });
8924
9022
  }
8925
9023
  const db = openDatabase(resolvedPath);
8926
- db.exec("PRAGMA journal_mode = WAL");
8927
- db.exec("PRAGMA foreign_keys = ON");
8928
- db.exec("PRAGMA busy_timeout = 5000");
9024
+ applyStandardPragmas(db, { dataDir: dir });
8929
9025
  runMigrations2(db);
8930
9026
  return db;
8931
9027
  }
@@ -8972,7 +9068,11 @@ function proposalRowToProposal(row) {
8972
9068
  content: row.content,
8973
9069
  ...frontmatter !== undefined ? { frontmatter } : {}
8974
9070
  },
8975
- ...meta.review !== undefined ? { review: meta.review } : {}
9071
+ ...meta.review !== undefined ? { review: meta.review } : {},
9072
+ ...typeof meta.confidence === "number" ? { confidence: meta.confidence } : {},
9073
+ ...meta.gateDecision !== undefined ? { gateDecision: meta.gateDecision } : {},
9074
+ ...typeof meta.backupContent === "string" ? { backupContent: meta.backupContent } : {},
9075
+ ...typeof meta.eligibilitySource === "string" ? { eligibilitySource: meta.eligibilitySource } : {}
8976
9076
  };
8977
9077
  }
8978
9078
  function proposalToRowValues(proposal, stashDir) {
@@ -8981,6 +9081,14 @@ function proposalToRowValues(proposal, stashDir) {
8981
9081
  metaObj.sourceRun = proposal.sourceRun;
8982
9082
  if (proposal.review !== undefined)
8983
9083
  metaObj.review = proposal.review;
9084
+ if (proposal.confidence !== undefined)
9085
+ metaObj.confidence = proposal.confidence;
9086
+ if (proposal.gateDecision !== undefined)
9087
+ metaObj.gateDecision = proposal.gateDecision;
9088
+ if (proposal.backupContent !== undefined)
9089
+ metaObj.backupContent = proposal.backupContent;
9090
+ if (proposal.eligibilitySource !== undefined)
9091
+ metaObj.eligibilitySource = proposal.eligibilitySource;
8984
9092
  return {
8985
9093
  id: proposal.id,
8986
9094
  stash_dir: stashDir,
@@ -9074,15 +9182,78 @@ function listStateProposals(db, options = {}) {
9074
9182
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
9075
9183
  const rows = db.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
9076
9184
  content, frontmatter_json, metadata_json
9077
- FROM proposals ${where} ORDER BY created_at ASC`).all(...params);
9185
+ FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`).all(...params);
9078
9186
  return rows.map(proposalRowToProposal);
9079
9187
  }
9080
- function getStateProposal(db, id) {
9081
- const row = db.prepare(`SELECT id, stash_dir, ref, status, source, created_at, updated_at,
9188
+ function listProposalGateDecisions(db) {
9189
+ const rows = db.prepare("SELECT metadata_json FROM proposals ORDER BY created_at ASC, rowid ASC").all();
9190
+ const decisions = [];
9191
+ for (const row of rows) {
9192
+ let meta;
9193
+ try {
9194
+ meta = JSON.parse(row.metadata_json);
9195
+ } catch {
9196
+ continue;
9197
+ }
9198
+ const decision = meta.gateDecision;
9199
+ if (decision && typeof decision === "object" && typeof decision.outcome === "string") {
9200
+ decisions.push(decision);
9201
+ }
9202
+ }
9203
+ decisions.sort((a, b) => new Date(a.decidedAt).getTime() - new Date(b.decidedAt).getTime());
9204
+ return decisions;
9205
+ }
9206
+ function getPhaseThreshold(db, phase) {
9207
+ const row = db.prepare("SELECT threshold FROM improve_gate_thresholds WHERE phase = ?").get(phase);
9208
+ return row?.threshold;
9209
+ }
9210
+ function persistPhaseThreshold(db, phase, threshold) {
9211
+ db.prepare(`INSERT OR REPLACE INTO improve_gate_thresholds (phase, threshold, updated_at)
9212
+ VALUES (?, ?, ?)`).run(phase, Math.round(threshold), Date.now());
9213
+ }
9214
+ function getStateProposal(db, id, stashDir) {
9215
+ const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
9082
9216
  content, frontmatter_json, metadata_json
9083
- FROM proposals WHERE id = ?`).get(id);
9217
+ FROM proposals WHERE id = ?${stashDir ? " AND stash_dir = ?" : ""}`;
9218
+ const row = stashDir ? db.prepare(sql).get(id, stashDir) : db.prepare(sql).get(id);
9084
9219
  return row ? proposalRowToProposal(row) : undefined;
9085
9220
  }
9221
+ function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
9222
+ const escaped = idPrefix.replace(/[\\%_]/g, (ch) => `\\${ch}`);
9223
+ const rows = db.prepare(`SELECT id FROM proposals
9224
+ WHERE stash_dir = ? AND status = 'pending' AND id LIKE ? ESCAPE '\\'
9225
+ ORDER BY id ASC`).all(stashDir, `${escaped}%`);
9226
+ return rows.map((r) => r.id);
9227
+ }
9228
+ function hasImportedFsProposals(db, stashDir) {
9229
+ return Boolean(db.prepare("SELECT 1 FROM proposal_fs_imports WHERE stash_dir = ?").get(stashDir));
9230
+ }
9231
+ function recordFsProposalsImport(db, stashDir, importedCount) {
9232
+ db.prepare("INSERT OR REPLACE INTO proposal_fs_imports (stash_dir, imported_at, imported_count) VALUES (?, ?, ?)").run(stashDir, new Date().toISOString(), importedCount);
9233
+ }
9234
+ function insertProposalIfAbsent(db, proposal, stashDir) {
9235
+ const v = proposalToRowValues(proposal, stashDir);
9236
+ const result = db.prepare(`
9237
+ INSERT OR IGNORE INTO proposals
9238
+ (id, stash_dir, ref, status, source, created_at, updated_at, content, frontmatter_json, metadata_json)
9239
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9240
+ `).run(v.id, v.stash_dir, v.ref, v.status, v.source, v.created_at, v.updated_at, v.content, v.frontmatter_json, v.metadata_json);
9241
+ const changes = result.changes ?? 0;
9242
+ return Number(changes) > 0;
9243
+ }
9244
+ function withImmediateTransaction(db, fn) {
9245
+ db.exec("BEGIN IMMEDIATE");
9246
+ try {
9247
+ const result = fn();
9248
+ db.exec("COMMIT");
9249
+ return result;
9250
+ } catch (err) {
9251
+ try {
9252
+ db.exec("ROLLBACK");
9253
+ } catch {}
9254
+ throw err;
9255
+ }
9256
+ }
9086
9257
  function upsertTaskHistory(db, row) {
9087
9258
  db.prepare(`
9088
9259
  INSERT OR IGNORE INTO task_history
@@ -9241,9 +9412,10 @@ function upsertExtractedSession(db, input) {
9241
9412
  db.prepare(`
9242
9413
  INSERT OR REPLACE INTO extract_sessions_seen
9243
9414
  (harness, session_id, processed_at, session_ended_at, outcome,
9244
- candidate_count, proposal_count, rationale, source_run, metadata_json)
9245
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9246
- `).run(input.harness, input.sessionId, input.processedAt, endedAtIso, input.outcome, input.candidateCount, input.proposalCount, input.rationale ?? null, input.sourceRun ?? null, JSON.stringify(input.metadata ?? {}));
9415
+ candidate_count, proposal_count, rationale, source_run, metadata_json,
9416
+ content_hash)
9417
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9418
+ `).run(input.harness, input.sessionId, input.processedAt, endedAtIso, input.outcome, input.candidateCount, input.proposalCount, input.rationale ?? null, input.sourceRun ?? null, JSON.stringify(input.metadata ?? {}), input.contentHash);
9247
9419
  }
9248
9420
  function getExtractedSession(db, harness, sessionId) {
9249
9421
  const row = db.prepare("SELECT * FROM extract_sessions_seen WHERE harness = ? AND session_id = ?").get(harness, sessionId);
@@ -9264,16 +9436,114 @@ function getExtractedSessionsMap(db, harness, sessionIds) {
9264
9436
  }
9265
9437
  return out;
9266
9438
  }
9267
- function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
9439
+ function shouldSkipAlreadyExtractedSession(prior, currentContentHash) {
9268
9440
  if (!prior)
9269
9441
  return false;
9270
- if (typeof liveSessionEndedAtMs !== "number" || !Number.isFinite(liveSessionEndedAtMs)) {
9271
- return true;
9272
- }
9273
- const priorMs = prior.session_ended_at ? Date.parse(prior.session_ended_at) : Number.NaN;
9274
- if (!Number.isFinite(priorMs))
9442
+ if (prior.content_hash == null)
9275
9443
  return false;
9276
- return liveSessionEndedAtMs <= priorMs;
9444
+ return prior.content_hash === currentContentHash;
9445
+ }
9446
+ function getConsolidationJudgedMap(db, entryKeys) {
9447
+ const out = new Map;
9448
+ if (entryKeys.length === 0)
9449
+ return out;
9450
+ const CHUNK = 500;
9451
+ for (let i = 0;i < entryKeys.length; i += CHUNK) {
9452
+ const chunk = entryKeys.slice(i, i + CHUNK);
9453
+ const placeholders = chunk.map(() => "?").join(",");
9454
+ const rows = db.prepare(`SELECT * FROM consolidation_judged WHERE entry_key IN (${placeholders})`).all(...chunk);
9455
+ for (const row of rows)
9456
+ out.set(row.entry_key, row);
9457
+ }
9458
+ return out;
9459
+ }
9460
+ function upsertConsolidationJudged(db, input) {
9461
+ db.prepare(`
9462
+ INSERT OR REPLACE INTO consolidation_judged
9463
+ (entry_key, content_hash, judged_at, outcome)
9464
+ VALUES (?, ?, ?, ?)
9465
+ `).run(input.entryKey, input.contentHash, input.judgedAt, input.outcome);
9466
+ }
9467
+ function recordRecombineInduction(db, input) {
9468
+ const row = db.prepare(`
9469
+ INSERT INTO recombine_hypotheses
9470
+ (hypothesis_ref, signature, member_key, consecutive_count, first_seen_at, last_seen_at, last_run)
9471
+ VALUES (?, ?, ?, 1, ?, ?, ?)
9472
+ ON CONFLICT(hypothesis_ref) DO UPDATE SET
9473
+ consecutive_count = consecutive_count + (CASE WHEN last_run IS excluded.last_run THEN 0 ELSE 1 END),
9474
+ last_seen_at = excluded.last_seen_at,
9475
+ last_run = excluded.last_run,
9476
+ signature = excluded.signature,
9477
+ member_key = excluded.member_key
9478
+ RETURNING consecutive_count
9479
+ `).get(input.hypothesisRef, input.signature, input.memberKey, input.seenAt, input.seenAt, input.run);
9480
+ return row?.consecutive_count ?? 0;
9481
+ }
9482
+ function getRecombineHypothesis(db, hypothesisRef) {
9483
+ const row = db.prepare("SELECT * FROM recombine_hypotheses WHERE hypothesis_ref = ?").get(hypothesisRef);
9484
+ return row ?? undefined;
9485
+ }
9486
+ function markRecombineHypothesisPromoted(db, hypothesisRef, promotedAt) {
9487
+ db.prepare("UPDATE recombine_hypotheses SET promoted_at = ?, consecutive_count = 0 WHERE hypothesis_ref = ?").run(promotedAt, hypothesisRef);
9488
+ }
9489
+ function decayUnseenRecombineHypotheses(db, currentRun, seenRefs) {
9490
+ if (seenRefs.length === 0) {
9491
+ const res2 = db.prepare("UPDATE recombine_hypotheses SET consecutive_count = 0 WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").run(currentRun);
9492
+ return Number(res2.changes);
9493
+ }
9494
+ if (seenRefs.length > 900) {
9495
+ const res2 = db.prepare("UPDATE recombine_hypotheses SET consecutive_count = 0 WHERE promoted_at IS NULL AND (last_run IS NULL OR last_run != ?) AND consecutive_count != 0").run(currentRun);
9496
+ return Number(res2.changes);
9497
+ }
9498
+ const placeholders = seenRefs.map(() => "?").join(",");
9499
+ const res = db.prepare(`UPDATE recombine_hypotheses SET consecutive_count = 0
9500
+ WHERE promoted_at IS NULL
9501
+ AND (last_run IS NULL OR last_run != ?)
9502
+ AND consecutive_count != 0
9503
+ AND hypothesis_ref NOT IN (${placeholders})`).run(currentRun, ...seenRefs);
9504
+ return Number(res.changes);
9505
+ }
9506
+ function embeddingToBlob(vec) {
9507
+ const f32 = new Float32Array(vec);
9508
+ return new Uint8Array(f32.buffer);
9509
+ }
9510
+ function blobToEmbedding(blob) {
9511
+ const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
9512
+ return Array.from(f32);
9513
+ }
9514
+ function getBodyEmbeddings(db, contentHashes, expectedModelId) {
9515
+ const out = new Map;
9516
+ if (contentHashes.length === 0)
9517
+ return out;
9518
+ const firstRow = db.prepare("SELECT model_id FROM body_embeddings LIMIT 1").get();
9519
+ if (firstRow && firstRow.model_id !== expectedModelId) {
9520
+ db.exec("DELETE FROM body_embeddings");
9521
+ return out;
9522
+ }
9523
+ const CHUNK = 500;
9524
+ for (let i = 0;i < contentHashes.length; i += CHUNK) {
9525
+ const chunk = contentHashes.slice(i, i + CHUNK);
9526
+ const placeholders = chunk.map(() => "?").join(",");
9527
+ const rows = db.prepare(`SELECT content_hash, embedding FROM body_embeddings WHERE content_hash IN (${placeholders})`).all(...chunk);
9528
+ for (const row of rows) {
9529
+ out.set(row.content_hash, blobToEmbedding(row.embedding));
9530
+ }
9531
+ }
9532
+ return out;
9533
+ }
9534
+ function upsertBodyEmbeddings(db, entries) {
9535
+ if (entries.length === 0)
9536
+ return;
9537
+ const now = Date.now();
9538
+ const stmt = db.prepare(`
9539
+ INSERT OR REPLACE INTO body_embeddings (content_hash, embedding, model_id, created_at)
9540
+ VALUES (?, ?, ?, ?)
9541
+ `);
9542
+ db.transaction(() => {
9543
+ for (const { contentHash, embedding, modelId } of entries) {
9544
+ stmt.run(contentHash, embeddingToBlob(embedding), modelId, now);
9545
+ }
9546
+ })();
9277
9547
  }
9278
9548
  var MIGRATIONS, REGISTRY_INDEX_CACHE_DDL = `
9279
9549
  CREATE TABLE IF NOT EXISTS registry_index_cache (
@@ -9289,6 +9559,7 @@ var MIGRATIONS, REGISTRY_INDEX_CACHE_DDL = `
9289
9559
  `;
9290
9560
  var init_state_db = __esm(() => {
9291
9561
  init_database();
9562
+ init_sqlite_pragmas();
9292
9563
  init_improve_types();
9293
9564
  init_paths();
9294
9565
  init_warn();
@@ -9363,7 +9634,9 @@ var init_state_db = __esm(() => {
9363
9634
  --
9364
9635
  -- Extensible (metadata_json) columns:
9365
9636
  -- metadata_json TEXT \u2014 JSON object for future proposal fields.
9366
- -- Current fields stored here: sourceRun, review.
9637
+ -- Current fields stored here: sourceRun,
9638
+ -- review, confidence, gateDecision (#577),
9639
+ -- backupContent, eligibilitySource.
9367
9640
  --
9368
9641
  -- ADD COLUMN extension points (future migrations):
9369
9642
  -- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
@@ -9550,6 +9823,127 @@ var init_state_db = __esm(() => {
9550
9823
  CREATE INDEX IF NOT EXISTS idx_extract_sessions_processed
9551
9824
  ON extract_sessions_seen(processed_at);
9552
9825
  `
9826
+ },
9827
+ {
9828
+ id: "005-proposal-fs-imports",
9829
+ up: `
9830
+ CREATE TABLE IF NOT EXISTS proposal_fs_imports (
9831
+ stash_dir TEXT PRIMARY KEY,
9832
+ imported_at TEXT NOT NULL,
9833
+ imported_count INTEGER NOT NULL DEFAULT 0
9834
+ );
9835
+ `
9836
+ },
9837
+ {
9838
+ id: "006-proposals-pending-ref-source",
9839
+ up: `
9840
+ CREATE INDEX IF NOT EXISTS idx_proposals_stash_status_ref_source
9841
+ ON proposals(stash_dir, status, ref, source);
9842
+ `
9843
+ },
9844
+ {
9845
+ id: "007-consolidation-judged",
9846
+ up: `
9847
+ CREATE TABLE IF NOT EXISTS consolidation_judged (
9848
+ entry_key TEXT PRIMARY KEY,
9849
+ content_hash TEXT NOT NULL,
9850
+ judged_at TEXT NOT NULL,
9851
+ outcome TEXT NOT NULL
9852
+ );
9853
+ `
9854
+ },
9855
+ {
9856
+ id: "008-body-embeddings",
9857
+ up: `
9858
+ CREATE TABLE IF NOT EXISTS body_embeddings (
9859
+ content_hash TEXT PRIMARY KEY,
9860
+ embedding BLOB NOT NULL,
9861
+ model_id TEXT NOT NULL,
9862
+ created_at INTEGER NOT NULL
9863
+ );
9864
+ `
9865
+ },
9866
+ {
9867
+ id: "009-asset-salience",
9868
+ up: `
9869
+ CREATE TABLE IF NOT EXISTS asset_salience (
9870
+ asset_ref TEXT PRIMARY KEY,
9871
+ encoding_salience REAL NOT NULL DEFAULT 0.5,
9872
+ outcome_salience REAL NOT NULL DEFAULT 0.0,
9873
+ retrieval_salience REAL NOT NULL DEFAULT 0.0,
9874
+ rank_score REAL NOT NULL DEFAULT 0.0,
9875
+ consecutive_no_ops INTEGER NOT NULL DEFAULT 0,
9876
+ updated_at INTEGER NOT NULL DEFAULT 0
9877
+ );
9878
+
9879
+ -- Hot path: sort / filter by rank_score for selector queries.
9880
+ CREATE INDEX IF NOT EXISTS idx_asset_salience_rank
9881
+ ON asset_salience(rank_score DESC);
9882
+ `
9883
+ },
9884
+ {
9885
+ id: "010-asset-outcome",
9886
+ up: `
9887
+ CREATE TABLE IF NOT EXISTS asset_outcome (
9888
+ asset_ref TEXT PRIMARY KEY,
9889
+ last_retrieved_at INTEGER NOT NULL DEFAULT 0,
9890
+ retrieval_count INTEGER NOT NULL DEFAULT 0,
9891
+ expected_retrieval_rate REAL NOT NULL DEFAULT 0.0,
9892
+ negative_feedback_count INTEGER NOT NULL DEFAULT 0,
9893
+ accepted_change_count INTEGER NOT NULL DEFAULT 0,
9894
+ review_pressure INTEGER NOT NULL DEFAULT 0,
9895
+ outcome_score REAL NOT NULL DEFAULT 0.0,
9896
+ updated_at INTEGER NOT NULL DEFAULT 0
9897
+ );
9898
+
9899
+ -- Hot path: sort assets by review_pressure DESC for #613 admission.
9900
+ CREATE INDEX IF NOT EXISTS idx_asset_outcome_review_pressure
9901
+ ON asset_outcome(review_pressure DESC);
9902
+
9903
+ -- Secondary: sort by outcome_score DESC for outcomeSalience reads.
9904
+ CREATE INDEX IF NOT EXISTS idx_asset_outcome_score
9905
+ ON asset_outcome(outcome_score DESC);
9906
+ `
9907
+ },
9908
+ {
9909
+ id: "011-asset-salience-homeostatic-demoted-at",
9910
+ up: `
9911
+ ALTER TABLE asset_salience ADD COLUMN homeostatic_demoted_at INTEGER DEFAULT NULL;
9912
+ `
9913
+ },
9914
+ {
9915
+ id: "012-improve-gate-thresholds",
9916
+ up: `
9917
+ CREATE TABLE IF NOT EXISTS improve_gate_thresholds (
9918
+ phase TEXT NOT NULL PRIMARY KEY,
9919
+ threshold INTEGER NOT NULL,
9920
+ updated_at INTEGER NOT NULL
9921
+ );
9922
+ `
9923
+ },
9924
+ {
9925
+ id: "013-extract-sessions-content-hash",
9926
+ up: `
9927
+ ALTER TABLE extract_sessions_seen ADD COLUMN content_hash TEXT DEFAULT NULL;
9928
+ `
9929
+ },
9930
+ {
9931
+ id: "014-recombine-hypotheses",
9932
+ up: `
9933
+ CREATE TABLE IF NOT EXISTS recombine_hypotheses (
9934
+ hypothesis_ref TEXT PRIMARY KEY,
9935
+ signature TEXT NOT NULL,
9936
+ member_key TEXT NOT NULL,
9937
+ consecutive_count INTEGER NOT NULL DEFAULT 0,
9938
+ first_seen_at TEXT NOT NULL,
9939
+ last_seen_at TEXT NOT NULL,
9940
+ last_run TEXT,
9941
+ promoted_at TEXT,
9942
+ metadata_json TEXT NOT NULL DEFAULT '{}'
9943
+ );
9944
+ CREATE INDEX IF NOT EXISTS idx_recombine_hypotheses_last_seen
9945
+ ON recombine_hypotheses(last_seen_at);
9946
+ `
9553
9947
  }
9554
9948
  ];
9555
9949
  });
@@ -9593,29 +9987,6 @@ var init_types = __esm(() => {
9593
9987
  init_warn();
9594
9988
  });
9595
9989
 
9596
- // src/runtime.ts
9597
- import { createHash } from "crypto";
9598
- import { createRequire as createRequire2 } from "module";
9599
- function sha256Hex(data) {
9600
- return createHash("sha256").update(data).digest("hex");
9601
- }
9602
- function sleepSync(ms) {
9603
- if (isBun2) {
9604
- bunGlobal().sleepSync(ms);
9605
- return;
9606
- }
9607
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
9608
- }
9609
- function bunGlobal() {
9610
- return globalThis.Bun;
9611
- }
9612
- var isBun2, nodeRequire2, mainPath;
9613
- var init_runtime = __esm(() => {
9614
- isBun2 = !!process.versions?.bun;
9615
- nodeRequire2 = createRequire2(import.meta.url);
9616
- mainPath = isBun2 ? bunGlobal().main : process.argv[1];
9617
- });
9618
-
9619
9990
  // src/indexer/search/search-fields.ts
9620
9991
  function buildSearchFields(entry) {
9621
9992
  const name = entry.name.replace(/[-_]/g, " ").toLowerCase();
@@ -10238,6 +10609,9 @@ var init_inline_refs = __esm(() => {
10238
10609
  import fs9 from "fs";
10239
10610
  import os from "os";
10240
10611
  import path8 from "path";
10612
+ function claudeProjectsDir() {
10613
+ return process.env.AKM_CLAUDE_PROJECTS_DIR ?? path8.join(os.homedir(), ".claude", "projects");
10614
+ }
10241
10615
  function parseClaudeEvent(entry, sessionId, filePath, fallbackTsMs) {
10242
10616
  if (!entry || typeof entry !== "object")
10243
10617
  return;
@@ -10294,11 +10668,15 @@ function parseClaudeEvent(entry, sessionId, filePath, fallbackTsMs) {
10294
10668
  class ClaudeCodeProvider {
10295
10669
  name = "claude-code";
10296
10670
  isAvailable() {
10297
- return fs9.existsSync(CLAUDE_PROJECTS_DIR);
10671
+ return fs9.existsSync(claudeProjectsDir());
10672
+ }
10673
+ watchRoots() {
10674
+ const dir = claudeProjectsDir();
10675
+ return fs9.existsSync(dir) ? [dir] : [];
10298
10676
  }
10299
10677
  *readEvents(input) {
10300
10678
  try {
10301
- for (const jsonlPath of this.#walkJsonl(CLAUDE_PROJECTS_DIR)) {
10679
+ for (const jsonlPath of this.#walkJsonl(claudeProjectsDir())) {
10302
10680
  const stat = fs9.statSync(jsonlPath);
10303
10681
  if (stat.mtimeMs < input.sinceMs)
10304
10682
  continue;
@@ -10326,7 +10704,7 @@ class ClaudeCodeProvider {
10326
10704
  }
10327
10705
  }
10328
10706
  listSessions(input = {}) {
10329
- const root = input.location ?? CLAUDE_PROJECTS_DIR;
10707
+ const root = input.location ?? claudeProjectsDir();
10330
10708
  const sinceMs = input.sinceMs ?? 0;
10331
10709
  const summaries = [];
10332
10710
  try {
@@ -10460,16 +10838,14 @@ class ClaudeCodeProvider {
10460
10838
  const full = path8.join(dir, entry.name);
10461
10839
  if (entry.isDirectory())
10462
10840
  yield* this.#walkJsonl(full);
10463
- else if (entry.name.endsWith(".jsonl"))
10841
+ else if (entry.name.endsWith(".jsonl") && entry.name !== "journal.jsonl")
10464
10842
  yield full;
10465
10843
  }
10466
10844
  } catch {}
10467
10845
  }
10468
10846
  }
10469
- var CLAUDE_PROJECTS_DIR;
10470
10847
  var init_session_log = __esm(() => {
10471
10848
  init_inline_refs();
10472
- CLAUDE_PROJECTS_DIR = path8.join(os.homedir(), ".claude", "projects");
10473
10849
  });
10474
10850
 
10475
10851
  // src/integrations/harnesses/claude/index.ts
@@ -10532,6 +10908,10 @@ class OpenCodeProvider {
10532
10908
  isAvailable() {
10533
10909
  return fs10.existsSync(this.#baseDir);
10534
10910
  }
10911
+ watchRoots() {
10912
+ const sessionRoot = path9.join(this.#baseDir, "storage", "session");
10913
+ return fs10.existsSync(sessionRoot) ? [sessionRoot] : [];
10914
+ }
10535
10915
  *readEvents(input) {
10536
10916
  const candidates = [this.#baseDir, path9.join(this.#baseDir, "log")];
10537
10917
  for (const dir of candidates) {
@@ -15392,7 +15772,7 @@ var init_config_types = __esm(() => {
15392
15772
  });
15393
15773
 
15394
15774
  // src/core/config/config-schema.ts
15395
- var positiveInt, nonNegativeNumber, nonEmptyString, httpUrl, LlmCapabilitiesSchema, LlmConnectionConfigSchema, LlmProfileConfigSchema, EmbeddingOllamaOptionsSchema, EmbeddingConnectionConfigSchema, AgentPlatformSchema, AgentProfileConfigSchema, ImproveProcessConfigSchema, ImproveProfileProcessesSchema, ImproveProfileConfigSchema, ProfilesSchema, DefaultsSchema, SourceConfigEntryOptionsSchema, SourceConfigEntrySchema, RegistryConfigEntrySchema, KitSourceSchema, InstalledStashEntrySchema, OutputConfigSchema, SearchGraphBoostSchema, SearchConfigSchema, FeedbackConfigSchema, ImproveUtilityDecaySchema, ImproveConfigSchema, GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED, INDEX_PASS_PROVIDER_KEYS, INDEX_PASS_KNOWN_KEYS, IndexPassConfigSchema, MetadataEnhanceSchema, StalenessDetectionSchema, IndexConfigSchema, SetupTaskSchedulesSchema, SetupConfigSchema, AkmConfigShape, AkmConfigBaseSchema, AkmConfigSchema;
15775
+ var positiveInt, nonNegativeNumber, nonEmptyString, httpUrl, LlmCapabilitiesSchema, LlmConnectionConfigSchema, LlmProfileConfigSchema, EmbeddingOllamaOptionsSchema, EmbeddingConnectionConfigSchema, AgentPlatformSchema, AgentProfileConfigSchema, ImproveProcessConfigSchema, ImproveProfileProcessesSchema, ImproveProfileConfigSchema, ProfilesSchema, DefaultsSchema, SourceConfigEntryOptionsSchema, SourceConfigEntrySchema, RegistryConfigEntrySchema, KitSourceSchema, InstalledStashEntrySchema, OutputConfigSchema, SearchGraphBoostSchema, SearchConfigSchema, FeedbackConfigSchema, ImproveUtilityDecaySchema, ImproveCalibrationSchema, ImproveExplorationSchema, ImproveSalienceSchema, ImproveConfigSchema, GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED, INDEX_PASS_PROVIDER_KEYS, INDEX_PASS_KNOWN_KEYS, IndexPassConfigSchema, MetadataEnhanceSchema, StalenessDetectionSchema, IndexConfigSchema, SetupTaskSchedulesSchema, SetupConfigSchema, AkmConfigShape, AkmConfigBaseSchema, AkmConfigSchema;
15396
15776
  var init_config_schema = __esm(() => {
15397
15777
  init_zod();
15398
15778
  init_config_types();
@@ -15454,14 +15834,64 @@ var init_config_schema = __esm(() => {
15454
15834
  timeoutMs: exports_external.union([positiveInt, exports_external.null()]).optional(),
15455
15835
  allowedTypes: exports_external.array(exports_external.string().min(1)).optional(),
15456
15836
  minPoolSize: exports_external.number().int().min(0).optional(),
15837
+ dedup: exports_external.object({
15838
+ enabled: exports_external.boolean().optional(),
15839
+ cosineThreshold: exports_external.number().min(0).max(1).optional(),
15840
+ cosineCandidateLimit: exports_external.number().int().positive().optional()
15841
+ }).strict().optional(),
15842
+ judgedCache: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15457
15843
  qualityGate: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15458
15844
  contradictionDetection: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15459
15845
  defaultSince: exports_external.string().min(1).optional(),
15460
15846
  maxTotalChars: positiveInt.optional(),
15847
+ minContentChars: exports_external.number().int().min(0).optional(),
15461
15848
  maxChunkSize: exports_external.number().int().min(1).max(50).optional(),
15849
+ incrementalSince: exports_external.string().optional(),
15850
+ limit: positiveInt.optional(),
15851
+ neighborsPerChanged: exports_external.number().int().min(1).optional(),
15852
+ requirePlannedRefs: exports_external.boolean().optional(),
15853
+ dueDays: exports_external.number().int().min(0).optional(),
15854
+ maxPerRun: positiveInt.optional(),
15855
+ topN: positiveInt.optional(),
15856
+ minPendingCount: exports_external.number().int().min(0).optional(),
15462
15857
  minNewSessions: exports_external.number().int().min(0).optional(),
15858
+ maxSessionsPerRun: exports_external.number().int().min(0).optional(),
15463
15859
  indexSessions: exports_external.boolean().optional(),
15464
15860
  minSessionDuration: exports_external.number().min(0).optional(),
15861
+ p90ChunkSecondsDefault: exports_external.number().finite().positive().optional(),
15862
+ homeostaticDemotion: exports_external.object({
15863
+ enabled: exports_external.boolean().optional(),
15864
+ staleDays: exports_external.number().int().min(0).optional(),
15865
+ demotionFactor: exports_external.number().min(0).max(1).optional()
15866
+ }).strict().optional(),
15867
+ schemaSimilarity: exports_external.object({
15868
+ enabled: exports_external.boolean().optional(),
15869
+ epsilon: exports_external.number().min(0).max(1).optional(),
15870
+ confidencePenalty: exports_external.number().min(0).max(1).optional()
15871
+ }).strict().optional(),
15872
+ hotProbation: exports_external.object({
15873
+ enabled: exports_external.boolean().optional()
15874
+ }).strict().optional(),
15875
+ antiCollapse: exports_external.object({
15876
+ enabled: exports_external.boolean().optional(),
15877
+ maxGeneration: exports_external.number().int().min(1).optional(),
15878
+ lexicalDiversityCheck: exports_external.boolean().optional(),
15879
+ randomClusterFraction: exports_external.number().min(0).max(1).optional()
15880
+ }).strict().optional(),
15881
+ cls: exports_external.object({
15882
+ enabled: exports_external.boolean().optional(),
15883
+ adjacentCount: exports_external.number().int().min(1).optional()
15884
+ }).strict().optional(),
15885
+ fidelityCheck: exports_external.object({
15886
+ enabled: exports_external.boolean().optional()
15887
+ }).strict().optional(),
15888
+ minClusterSize: exports_external.number().int().min(2).optional(),
15889
+ maxClustersPerRun: positiveInt.optional(),
15890
+ relatednessSource: exports_external.enum(["tags", "graph", "both"]).optional(),
15891
+ confirmThreshold: exports_external.number().int().min(1).optional(),
15892
+ minRecurrence: exports_external.number().int().min(2).optional(),
15893
+ maxProposalsPerRun: positiveInt.optional(),
15894
+ emitAs: exports_external.enum(["workflow", "skill"]).optional(),
15465
15895
  applyMode: exports_external.enum(["queue", "promote"]).optional(),
15466
15896
  policy: exports_external.string().min(1).optional(),
15467
15897
  maxAcceptsPerRun: positiveInt.optional(),
@@ -15480,7 +15910,10 @@ var init_config_schema = __esm(() => {
15480
15910
  memoryInference: ImproveProcessConfigSchema.optional(),
15481
15911
  graphExtraction: ImproveProcessConfigSchema.optional(),
15482
15912
  validation: ImproveProcessConfigSchema.optional(),
15483
- triage: ImproveProcessConfigSchema.optional()
15913
+ triage: ImproveProcessConfigSchema.optional(),
15914
+ proactiveMaintenance: ImproveProcessConfigSchema.optional(),
15915
+ recombine: ImproveProcessConfigSchema.optional(),
15916
+ procedural: ImproveProcessConfigSchema.optional()
15484
15917
  }).passthrough().superRefine((val, ctx) => {
15485
15918
  const raw = val;
15486
15919
  if ("feedbackDistillation" in raw) {
@@ -15498,7 +15931,10 @@ var init_config_schema = __esm(() => {
15498
15931
  "graphExtraction",
15499
15932
  "validation",
15500
15933
  "extract",
15501
- "triage"
15934
+ "triage",
15935
+ "proactiveMaintenance",
15936
+ "recombine",
15937
+ "procedural"
15502
15938
  ]);
15503
15939
  for (const k of Object.keys(raw)) {
15504
15940
  if (!allowed.has(k)) {
@@ -15515,6 +15951,8 @@ var init_config_schema = __esm(() => {
15515
15951
  processes: ImproveProfileProcessesSchema.optional(),
15516
15952
  autoAccept: nonNegativeNumber.optional(),
15517
15953
  limit: positiveInt.optional(),
15954
+ maxCycles: positiveInt.optional(),
15955
+ symmetricValence: exports_external.boolean().optional(),
15518
15956
  sync: exports_external.object({
15519
15957
  enabled: exports_external.boolean().optional(),
15520
15958
  push: exports_external.boolean().optional(),
@@ -15595,6 +16033,7 @@ var init_config_schema = __esm(() => {
15595
16033
  }).strict();
15596
16034
  SearchConfigSchema = exports_external.object({
15597
16035
  minScore: nonNegativeNumber.optional(),
16036
+ defaultExcludeTypes: exports_external.array(nonEmptyString).optional(),
15598
16037
  curateRerank: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15599
16038
  graphBoost: SearchGraphBoostSchema.optional()
15600
16039
  }).strict();
@@ -15606,9 +16045,29 @@ var init_config_schema = __esm(() => {
15606
16045
  halfLifeDays: exports_external.number().finite().min(0.1).optional(),
15607
16046
  feedbackStabilityBoost: exports_external.number().finite().min(1).optional()
15608
16047
  }).strict();
16048
+ ImproveCalibrationSchema = exports_external.object({
16049
+ autoTune: exports_external.boolean().optional(),
16050
+ minThreshold: exports_external.number().int().min(0).max(100).optional(),
16051
+ maxThreshold: exports_external.number().int().min(0).max(100).optional(),
16052
+ maxStep: positiveInt.optional(),
16053
+ minSamples: nonNegativeNumber.optional(),
16054
+ targetAcceptRate: exports_external.number().finite().min(0).max(1).optional()
16055
+ }).strict();
16056
+ ImproveExplorationSchema = exports_external.object({
16057
+ enabled: exports_external.boolean().optional(),
16058
+ budgetFraction: exports_external.number().finite().min(0).max(1).optional()
16059
+ }).strict();
16060
+ ImproveSalienceSchema = exports_external.object({
16061
+ outcomeWeightEnabled: exports_external.boolean().optional(),
16062
+ salienceThreshold: exports_external.number().min(0).max(1).optional(),
16063
+ replayBudget: exports_external.number().int().min(0).optional()
16064
+ }).strict();
15609
16065
  ImproveConfigSchema = exports_external.object({
15610
16066
  utilityDecay: ImproveUtilityDecaySchema.optional(),
15611
- eventRetentionDays: nonNegativeNumber.optional()
16067
+ eventRetentionDays: nonNegativeNumber.optional(),
16068
+ calibration: ImproveCalibrationSchema.optional(),
16069
+ exploration: ImproveExplorationSchema.optional(),
16070
+ salience: ImproveSalienceSchema.optional()
15612
16071
  }).strict();
15613
16072
  GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED = [
15614
16073
  "memory",
@@ -15619,7 +16078,8 @@ var init_config_schema = __esm(() => {
15619
16078
  "workflow",
15620
16079
  "lesson",
15621
16080
  "task",
15622
- "wiki"
16081
+ "wiki",
16082
+ "fact"
15623
16083
  ];
15624
16084
  INDEX_PASS_PROVIDER_KEYS = new Set([
15625
16085
  "endpoint",
@@ -15635,7 +16095,8 @@ var init_config_schema = __esm(() => {
15635
16095
  "llm",
15636
16096
  "graphExtractionBatchSize",
15637
16097
  "graphExtractionIncludeTypes",
15638
- "memoryInferenceBatchSize"
16098
+ "memoryInferenceBatchSize",
16099
+ "lazyGraphExtraction"
15639
16100
  ]);
15640
16101
  IndexPassConfigSchema = exports_external.preprocess((raw, ctx) => {
15641
16102
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
@@ -15653,7 +16114,7 @@ var init_config_schema = __esm(() => {
15653
16114
  if (!INDEX_PASS_KNOWN_KEYS.has(key)) {
15654
16115
  ctx.addIssue({
15655
16116
  code: exports_external.ZodIssueCode.custom,
15656
- message: `Unknown key \`${[...ctx.path ?? [], key].join(".")}\`. Per-pass entries support \`llm\` ` + "(boolean opt-out), `graphExtractionBatchSize`, `graphExtractionIncludeTypes`, and " + "`memoryInferenceBatchSize`."
16117
+ message: `Unknown key \`${[...ctx.path ?? [], key].join(".")}\`. Per-pass entries support \`llm\` ` + "(boolean opt-out), `graphExtractionBatchSize`, `graphExtractionIncludeTypes`, " + "`memoryInferenceBatchSize`, and `lazyGraphExtraction`."
15657
16118
  });
15658
16119
  return raw;
15659
16120
  }
@@ -15670,7 +16131,8 @@ var init_config_schema = __esm(() => {
15670
16131
  llm: exports_external.boolean().optional(),
15671
16132
  graphExtractionBatchSize: positiveInt.optional(),
15672
16133
  graphExtractionIncludeTypes: exports_external.array(exports_external.enum(GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED)).nonempty().optional(),
15673
- memoryInferenceBatchSize: positiveInt.optional()
16134
+ memoryInferenceBatchSize: positiveInt.optional(),
16135
+ lazyGraphExtraction: exports_external.boolean().optional()
15674
16136
  }).passthrough());
15675
16137
  MetadataEnhanceSchema = exports_external.object({ enabled: exports_external.boolean().optional() }).strict();
15676
16138
  StalenessDetectionSchema = exports_external.object({
@@ -15991,9 +16453,9 @@ function maybeAutoMigrateConfigFile(configPath, text) {
15991
16453
  "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"
15992
16454
  ].join(`
15993
16455
  `);
15994
- process.stderr.write(`${banner}
16456
+ process.stderr?.write?.(`${banner}
15995
16457
  `);
15996
- process.stdout.write(`${banner}
16458
+ process.stdout?.write?.(`${banner}
15997
16459
  `);
15998
16460
  } catch (err) {
15999
16461
  throw new ConfigError(`Failed to write migrated config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`, "INVALID_CONFIG_FILE", "Check filesystem permissions, free space, and disk health. To skip auto-migration, set AKM_NO_AUTO_MIGRATE=1.");
@@ -16237,6 +16699,7 @@ __export(exports_db, {
16237
16699
  getMeta: () => getMeta,
16238
16700
  getLlmCacheEntry: () => getLlmCacheEntry,
16239
16701
  getLlmCacheEntriesByRefs: () => getLlmCacheEntriesByRefs,
16702
+ getIndexedFilePaths: () => getIndexedFilePaths,
16240
16703
  getIndexDirState: () => getIndexDirState,
16241
16704
  getEntryRefRowsForStashRoot: () => getEntryRefRowsForStashRoot,
16242
16705
  getEntryIdByFilePath: () => getEntryIdByFilePath,
@@ -16245,6 +16708,7 @@ __export(exports_db, {
16245
16708
  getEntryByRef: () => getEntryByRef,
16246
16709
  getEntryById: () => getEntryById,
16247
16710
  getEntriesByDir: () => getEntriesByDir,
16711
+ getEntitiesByEntryIds: () => getEntitiesByEntryIds,
16248
16712
  getEmbeddingCount: () => getEmbeddingCount,
16249
16713
  getEmbeddableEntryCount: () => getEmbeddableEntryCount,
16250
16714
  getDerivedForParent: () => getDerivedForParent,
@@ -16279,9 +16743,7 @@ function openDatabase2(dbPath, options) {
16279
16743
  fs12.mkdirSync(dir, { recursive: true });
16280
16744
  }
16281
16745
  const db = openDatabase(resolvedPath);
16282
- db.exec("PRAGMA journal_mode = WAL");
16283
- db.exec("PRAGMA busy_timeout = 5000");
16284
- db.exec("PRAGMA foreign_keys = ON");
16746
+ applyStandardPragmas(db, { dataDir: dir });
16285
16747
  loadVecExtension(db);
16286
16748
  const resolvedDim = options?.embeddingDim ?? resolveConfiguredEmbeddingDim();
16287
16749
  ensureSchema(db, resolvedDim, { dataDir: dir });
@@ -16302,10 +16764,9 @@ function resolveConfiguredEmbeddingDim() {
16302
16764
  }
16303
16765
  function openExistingDatabase(dbPath) {
16304
16766
  const resolvedPath = dbPath ?? getDbPath();
16767
+ const dir = path11.dirname(resolvedPath);
16305
16768
  const db = openDatabase(resolvedPath);
16306
- db.exec("PRAGMA journal_mode = WAL");
16307
- db.exec("PRAGMA busy_timeout = 5000");
16308
- db.exec("PRAGMA foreign_keys = ON");
16769
+ applyStandardPragmas(db, { dataDir: dir });
16309
16770
  loadVecExtension(db);
16310
16771
  return db;
16311
16772
  }
@@ -16470,6 +16931,7 @@ function ensureSchema(db, embeddingDim, options) {
16470
16931
  CREATE INDEX IF NOT EXISTS idx_llm_cache_updated
16471
16932
  ON llm_enrichment_cache(updated_at);
16472
16933
  `);
16934
+ migrateGraphFilesSchema(db);
16473
16935
  db.exec(`
16474
16936
  CREATE TABLE IF NOT EXISTS graph_meta (
16475
16937
  stash_root TEXT PRIMARY KEY,
@@ -16493,7 +16955,6 @@ function ensureSchema(db, embeddingDim, options) {
16493
16955
  );
16494
16956
 
16495
16957
  CREATE TABLE IF NOT EXISTS graph_files (
16496
- entry_id INTEGER PRIMARY KEY REFERENCES entries(id) ON DELETE CASCADE,
16497
16958
  stash_root TEXT NOT NULL,
16498
16959
  file_path TEXT NOT NULL,
16499
16960
  file_order INTEGER NOT NULL,
@@ -16503,26 +16964,34 @@ function ensureSchema(db, embeddingDim, options) {
16503
16964
  status TEXT NOT NULL DEFAULT 'extracted',
16504
16965
  reason TEXT,
16505
16966
  extraction_run_id TEXT,
16506
- UNIQUE(stash_root, file_path)
16967
+ PRIMARY KEY (stash_root, file_path, body_hash)
16507
16968
  );
16508
16969
 
16970
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_files_path
16971
+ ON graph_files(stash_root, file_path);
16972
+
16509
16973
  CREATE INDEX IF NOT EXISTS idx_graph_files_stash_order
16510
16974
  ON graph_files(stash_root, file_order);
16511
16975
 
16512
16976
  CREATE TABLE IF NOT EXISTS graph_file_entities (
16513
- entry_id INTEGER NOT NULL REFERENCES graph_files(entry_id) ON DELETE CASCADE,
16514
- entity_order INTEGER NOT NULL,
16515
16977
  stash_root TEXT NOT NULL,
16978
+ file_path TEXT NOT NULL,
16979
+ body_hash TEXT NOT NULL,
16980
+ entity_order INTEGER NOT NULL,
16516
16981
  entity_norm TEXT NOT NULL,
16517
16982
  entity TEXT NOT NULL,
16518
- PRIMARY KEY (entry_id, entity_order)
16983
+ PRIMARY KEY (stash_root, file_path, body_hash, entity_order),
16984
+ FOREIGN KEY (stash_root, file_path, body_hash)
16985
+ REFERENCES graph_files(stash_root, file_path, body_hash) ON DELETE CASCADE
16519
16986
  );
16520
16987
 
16521
16988
  CREATE INDEX IF NOT EXISTS idx_graph_file_entities_entity_norm
16522
16989
  ON graph_file_entities(stash_root, entity_norm);
16523
16990
 
16524
16991
  CREATE TABLE IF NOT EXISTS graph_file_relations (
16525
- entry_id INTEGER NOT NULL REFERENCES graph_files(entry_id) ON DELETE CASCADE,
16992
+ stash_root TEXT NOT NULL,
16993
+ file_path TEXT NOT NULL,
16994
+ body_hash TEXT NOT NULL,
16526
16995
  relation_order INTEGER NOT NULL,
16527
16996
  from_entity_norm TEXT NOT NULL,
16528
16997
  from_entity TEXT NOT NULL,
@@ -16530,9 +16999,28 @@ function ensureSchema(db, embeddingDim, options) {
16530
16999
  to_entity TEXT NOT NULL,
16531
17000
  relation_type TEXT,
16532
17001
  confidence REAL,
16533
- PRIMARY KEY (entry_id, relation_order)
17002
+ PRIMARY KEY (stash_root, file_path, body_hash, relation_order),
17003
+ FOREIGN KEY (stash_root, file_path, body_hash)
17004
+ REFERENCES graph_files(stash_root, file_path, body_hash) ON DELETE CASCADE
17005
+ );
17006
+
17007
+ -- #624-P3: lazy graph-extraction queue. Standalone table (NO FK to
17008
+ -- graph_files \u2014 a queued file by definition has no graph row yet).
17009
+ -- Idempotent on (stash_root, file_path); drained highest-priority-first.
17010
+ -- CREATE TABLE IF NOT EXISTS is the forward migration (no DB_VERSION bump).
17011
+ CREATE TABLE IF NOT EXISTS graph_extraction_queue (
17012
+ stash_root TEXT NOT NULL,
17013
+ file_path TEXT NOT NULL,
17014
+ body_hash TEXT NOT NULL,
17015
+ queued_at TEXT NOT NULL DEFAULT (datetime('now')),
17016
+ priority INTEGER NOT NULL DEFAULT 0,
17017
+ PRIMARY KEY (stash_root, file_path)
16534
17018
  );
17019
+
17020
+ CREATE INDEX IF NOT EXISTS idx_graph_extraction_queue_drain
17021
+ ON graph_extraction_queue(stash_root, priority DESC, queued_at);
16535
17022
  `);
17023
+ migrateGraphDataFromLegacy(db);
16536
17024
  db.exec(`
16537
17025
  CREATE TABLE IF NOT EXISTS entries_fts_dirty (
16538
17026
  entry_id INTEGER PRIMARY KEY
@@ -16598,6 +17086,7 @@ function handleVersionUpgrade(db) {
16598
17086
  db.exec("DROP TABLE IF EXISTS index_dir_state");
16599
17087
  db.exec("DROP TABLE IF EXISTS llm_enrichment_cache");
16600
17088
  db.exec("DROP INDEX IF EXISTS idx_llm_cache_updated");
17089
+ db.exec("DROP TABLE IF EXISTS graph_extraction_queue");
16601
17090
  db.exec("DROP TABLE IF EXISTS graph_file_relations");
16602
17091
  db.exec("DROP TABLE IF EXISTS graph_file_entities");
16603
17092
  db.exec("DROP TABLE IF EXISTS graph_files");
@@ -16748,6 +17237,67 @@ function ensureDerivedFromColumn(db) {
16748
17237
  db.exec("CREATE INDEX IF NOT EXISTS idx_entries_derived_from ON entries(derived_from)");
16749
17238
  }, "entries table may not exist on a brand-new DB before CREATE \u2014 caller is responsible");
16750
17239
  }
17240
+ function tableExists(db, name) {
17241
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1").get(name);
17242
+ return row !== undefined && row !== null;
17243
+ }
17244
+ function migrateGraphFilesSchema(db) {
17245
+ bestEffort(() => {
17246
+ const cols = db.prepare("PRAGMA table_info(graph_files)").all();
17247
+ const isLegacyShape = cols.some((c) => c.name === "entry_id");
17248
+ if (!isLegacyShape)
17249
+ return;
17250
+ db.exec("DROP TABLE IF EXISTS graph_file_relations_legacy");
17251
+ db.exec("DROP TABLE IF EXISTS graph_file_entities_legacy");
17252
+ db.exec("DROP TABLE IF EXISTS graph_files_legacy");
17253
+ db.exec("ALTER TABLE graph_files RENAME TO graph_files_legacy");
17254
+ if (tableExists(db, "graph_file_entities")) {
17255
+ db.exec("ALTER TABLE graph_file_entities RENAME TO graph_file_entities_legacy");
17256
+ }
17257
+ if (tableExists(db, "graph_file_relations")) {
17258
+ db.exec("ALTER TABLE graph_file_relations RENAME TO graph_file_relations_legacy");
17259
+ }
17260
+ }, "graph_files may not exist on a brand-new DB before CREATE \u2014 caller is responsible");
17261
+ }
17262
+ function migrateGraphDataFromLegacy(db) {
17263
+ if (!tableExists(db, "graph_files_legacy"))
17264
+ return;
17265
+ let migratedFiles = 0;
17266
+ bestEffort(() => {
17267
+ db.transaction(() => {
17268
+ const res = db.prepare(`INSERT OR IGNORE INTO graph_files
17269
+ (stash_root, file_path, body_hash, file_order, file_type, confidence, status, reason, extraction_run_id)
17270
+ SELECT stash_root, file_path, body_hash, file_order, file_type, confidence, status, reason, extraction_run_id
17271
+ FROM graph_files_legacy
17272
+ WHERE body_hash IS NOT NULL AND body_hash != ''`).run();
17273
+ migratedFiles = Number(res.changes);
17274
+ if (tableExists(db, "graph_file_entities_legacy")) {
17275
+ db.exec(`INSERT OR IGNORE INTO graph_file_entities
17276
+ (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
17277
+ SELECT gf.stash_root, gf.file_path, gf.body_hash, e.entity_order, e.entity_norm, e.entity
17278
+ FROM graph_file_entities_legacy e
17279
+ JOIN graph_files_legacy gf ON gf.entry_id = e.entry_id
17280
+ WHERE gf.body_hash IS NOT NULL AND gf.body_hash != ''`);
17281
+ }
17282
+ if (tableExists(db, "graph_file_relations_legacy")) {
17283
+ db.exec(`INSERT OR IGNORE INTO graph_file_relations
17284
+ (stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence)
17285
+ SELECT gf.stash_root, gf.file_path, gf.body_hash, r.relation_order, r.from_entity_norm, r.from_entity, r.to_entity_norm, r.to_entity, r.relation_type, r.confidence
17286
+ FROM graph_file_relations_legacy r
17287
+ JOIN graph_files_legacy gf ON gf.entry_id = r.entry_id
17288
+ WHERE gf.body_hash IS NOT NULL AND gf.body_hash != ''`);
17289
+ }
17290
+ })();
17291
+ }, "graph data migration is best-effort; legacy tables are dropped regardless below");
17292
+ bestEffort(() => {
17293
+ db.exec("DROP TABLE IF EXISTS graph_file_relations_legacy");
17294
+ db.exec("DROP TABLE IF EXISTS graph_file_entities_legacy");
17295
+ db.exec("DROP TABLE IF EXISTS graph_files_legacy");
17296
+ }, "drop legacy graph tables after migration");
17297
+ if (migratedFiles > 0) {
17298
+ warn(`[akm] graph index re-keyed (#624): migrated ${migratedFiles} extracted file(s) to the new schema \u2014 no re-extraction needed. Index + embeddings untouched.`);
17299
+ }
17300
+ }
16751
17301
  function getDerivedForParent(db, parentRef) {
16752
17302
  if (!parentRef)
16753
17303
  return null;
@@ -16837,6 +17387,25 @@ function deleteRelatedRows(db, ids) {
16837
17387
  bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
16838
17388
  bestEffort(() => db.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk), "delete usage_events for entries");
16839
17389
  }
17390
+ const affectedStashRoots = new Set;
17391
+ for (let i = 0;i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
17392
+ const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
17393
+ const placeholders = chunk.map(() => "?").join(",");
17394
+ bestEffort(() => {
17395
+ const rows = db.prepare(`SELECT DISTINCT stash_dir FROM entries WHERE id IN (${placeholders})`).all(...chunk);
17396
+ for (const row of rows) {
17397
+ if (row.stash_dir)
17398
+ affectedStashRoots.add(row.stash_dir);
17399
+ }
17400
+ }, "resolve stash roots for graph_meta recompute");
17401
+ }
17402
+ for (const stashRoot of affectedStashRoots) {
17403
+ bestEffort(() => db.prepare(`UPDATE graph_meta
17404
+ SET extracted_files = (SELECT COUNT(*) FROM graph_files WHERE stash_root = ?),
17405
+ entity_count = (SELECT COUNT(*) FROM graph_file_entities WHERE stash_root = ?),
17406
+ relation_count = (SELECT COUNT(*) FROM graph_file_relations WHERE stash_root = ?)
17407
+ WHERE stash_root = ?`).run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
17408
+ }
16840
17409
  }
16841
17410
  function deleteEntriesByIds(db, ids) {
16842
17411
  if (ids.length === 0)
@@ -16966,17 +17535,17 @@ function searchBlobVec(db, queryEmbedding, k) {
16966
17535
  return [];
16967
17536
  }
16968
17537
  }
16969
- function searchFts(db, query, limit, entryType) {
17538
+ function searchFts(db, query, limit, entryType, excludeTypes) {
16970
17539
  const ftsQuery = sanitizeFtsQuery(query);
16971
17540
  if (!ftsQuery)
16972
17541
  return [];
16973
- const exactResults = runFtsQuery(db, ftsQuery, limit, entryType);
17542
+ const exactResults = runFtsQuery(db, ftsQuery, limit, entryType, excludeTypes);
16974
17543
  if (exactResults.length > 0)
16975
17544
  return exactResults;
16976
17545
  const prefixQuery = buildPrefixQuery(ftsQuery);
16977
17546
  if (!prefixQuery)
16978
17547
  return [];
16979
- return runFtsQuery(db, prefixQuery, limit, entryType);
17548
+ return runFtsQuery(db, prefixQuery, limit, entryType, excludeTypes);
16980
17549
  }
16981
17550
  function buildPrefixQuery(ftsQuery) {
16982
17551
  const tokens = ftsQuery.split(/\s+/).filter(Boolean);
@@ -16992,9 +17561,10 @@ function buildPrefixQuery(ftsQuery) {
16992
17561
  return null;
16993
17562
  return prefixTokens.join(" ");
16994
17563
  }
16995
- function runFtsQuery(db, ftsQuery, limit, entryType) {
17564
+ function runFtsQuery(db, ftsQuery, limit, entryType, excludeTypes) {
16996
17565
  let sql;
16997
17566
  let params;
17567
+ const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
16998
17568
  if (entryType && entryType !== "any") {
16999
17569
  sql = `
17000
17570
  SELECT e.id, e.file_path AS filePath, e.entry_json, e.search_text AS searchText,
@@ -17008,16 +17578,18 @@ function runFtsQuery(db, ftsQuery, limit, entryType) {
17008
17578
  `;
17009
17579
  params = [ftsQuery, entryType, limit];
17010
17580
  } else {
17581
+ const excludeClause = excludes.length > 0 ? `AND e.entry_type NOT IN (${excludes.map(() => "?").join(", ")})` : "";
17011
17582
  sql = `
17012
17583
  SELECT e.id, e.file_path AS filePath, e.entry_json, e.search_text AS searchText,
17013
17584
  bm25(entries_fts, 0, 10.0, 5.0, 3.0, 2.0, 1.0) AS bm25Score
17014
17585
  FROM entries_fts f
17015
17586
  JOIN entries e ON e.id = f.entry_id
17016
17587
  WHERE entries_fts MATCH ?
17588
+ ${excludeClause}
17017
17589
  ORDER BY bm25Score, e.id ASC
17018
17590
  LIMIT ?
17019
17591
  `;
17020
- params = [ftsQuery, limit];
17592
+ params = [ftsQuery, ...excludes, limit];
17021
17593
  }
17022
17594
  try {
17023
17595
  const rows = db.prepare(sql).all(...params);
@@ -17073,12 +17645,16 @@ function parseEntryRows(rows, context) {
17073
17645
  }
17074
17646
  return entries;
17075
17647
  }
17076
- function getAllEntries(db, entryType) {
17648
+ function getAllEntries(db, entryType, excludeTypes) {
17077
17649
  let sql;
17078
17650
  let params;
17651
+ const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
17079
17652
  if (entryType && entryType !== "any") {
17080
17653
  sql = "SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries WHERE entry_type = ?";
17081
17654
  params = [entryType];
17655
+ } else if (excludes.length > 0) {
17656
+ sql = `SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries WHERE entry_type NOT IN (${excludes.map(() => "?").join(", ")})`;
17657
+ params = [...excludes];
17082
17658
  } else {
17083
17659
  sql = "SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries";
17084
17660
  params = [];
@@ -17086,6 +17662,33 @@ function getAllEntries(db, entryType) {
17086
17662
  const rows = db.prepare(sql).all(...params);
17087
17663
  return parseEntryRows(rows, "getAllEntries");
17088
17664
  }
17665
+ function getEntitiesByEntryIds(db, entryIds) {
17666
+ const result = new Map;
17667
+ if (entryIds.length === 0)
17668
+ return result;
17669
+ for (let i = 0;i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
17670
+ const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
17671
+ const placeholders = chunk.map(() => "?").join(", ");
17672
+ const rows = db.prepare(`SELECT e.id AS entry_id, gfe.entity_norm AS entity_norm
17673
+ FROM entries e
17674
+ JOIN graph_files gf
17675
+ ON gf.stash_root = e.stash_dir AND gf.file_path = e.file_path
17676
+ JOIN graph_file_entities gfe
17677
+ ON gfe.stash_root = gf.stash_root
17678
+ AND gfe.file_path = gf.file_path
17679
+ AND gfe.body_hash = gf.body_hash
17680
+ WHERE e.id IN (${placeholders})
17681
+ ORDER BY e.id, gfe.entity_order`).all(...chunk);
17682
+ for (const row of rows) {
17683
+ const list = result.get(row.entry_id);
17684
+ if (list)
17685
+ list.push(row.entity_norm);
17686
+ else
17687
+ result.set(row.entry_id, [row.entity_norm]);
17688
+ }
17689
+ }
17690
+ return result;
17691
+ }
17089
17692
  function findEntryIdByRef(db, ref) {
17090
17693
  const parsed = parseAssetRef(ref);
17091
17694
  const nameVariants = [parsed.name];
@@ -17136,6 +17739,10 @@ function getEntryIdByFilePath(db, filePath) {
17136
17739
  const row = db.prepare("SELECT id FROM entries WHERE file_path = ? LIMIT 1").get(filePath);
17137
17740
  return row?.id;
17138
17741
  }
17742
+ function getIndexedFilePaths(db) {
17743
+ const rows = db.prepare("SELECT DISTINCT file_path FROM entries WHERE file_path IS NOT NULL AND file_path <> ''").all();
17744
+ return new Set(rows.map((r) => r.file_path));
17745
+ }
17139
17746
  function getEntryFilePathById(db, id) {
17140
17747
  const row = db.prepare("SELECT file_path FROM entries WHERE id = ?").get(id);
17141
17748
  return row?.file_path;
@@ -17263,18 +17870,56 @@ function clearStaleCacheEntries(db) {
17263
17870
  function computeBodyHash(body) {
17264
17871
  return sha256Hex(body);
17265
17872
  }
17873
+ function bareRef(ref) {
17874
+ try {
17875
+ const parsed = parseAssetRef(ref);
17876
+ return `${parsed.type}:${parsed.name}`;
17877
+ } catch {
17878
+ return ref;
17879
+ }
17880
+ }
17266
17881
  function getRetrievalCounts(db, refs) {
17267
17882
  if (refs.length === 0)
17268
17883
  return new Map;
17269
- const result = new Map;
17270
- for (let i = 0;i < refs.length; i += SQLITE_CHUNK_SIZE) {
17271
- const chunk = refs.slice(i, i + SQLITE_CHUNK_SIZE);
17884
+ const bareToInputs = new Map;
17885
+ for (const ref of refs) {
17886
+ const bare = bareRef(ref);
17887
+ const existing = bareToInputs.get(bare);
17888
+ if (existing)
17889
+ existing.push(ref);
17890
+ else
17891
+ bareToInputs.set(bare, [ref]);
17892
+ }
17893
+ const bareForms = [...bareToInputs.keys()];
17894
+ const countsByBare = new Map;
17895
+ for (let i = 0;i < bareForms.length; i += SQLITE_CHUNK_SIZE) {
17896
+ const chunk = bareForms.slice(i, i + SQLITE_CHUNK_SIZE);
17272
17897
  const placeholders = chunk.map(() => "?").join(", ");
17273
- const rows = db.prepare(`SELECT entry_ref, COUNT(*) AS cnt FROM usage_events
17274
- WHERE event_type IN ('search','show') AND entry_ref IN (${placeholders})
17275
- GROUP BY entry_ref`).all(...chunk);
17276
- for (const r of rows)
17277
- result.set(r.entry_ref, r.cnt);
17898
+ const rows = db.prepare(`SELECT
17899
+ CASE
17900
+ WHEN instr(entry_ref, '//') > 0
17901
+ THEN substr(entry_ref, instr(entry_ref, '//') + 2)
17902
+ ELSE entry_ref
17903
+ END AS bare_ref,
17904
+ COUNT(*) AS cnt
17905
+ FROM usage_events
17906
+ WHERE event_type IN ('search','show','curate')
17907
+ AND entry_ref IS NOT NULL
17908
+ AND CASE
17909
+ WHEN instr(entry_ref, '//') > 0
17910
+ THEN substr(entry_ref, instr(entry_ref, '//') + 2)
17911
+ ELSE entry_ref
17912
+ END IN (${placeholders})
17913
+ GROUP BY bare_ref`).all(...chunk);
17914
+ for (const r of rows) {
17915
+ countsByBare.set(r.bare_ref, (countsByBare.get(r.bare_ref) ?? 0) + r.cnt);
17916
+ }
17917
+ }
17918
+ const result = new Map;
17919
+ for (const [bare, count] of countsByBare) {
17920
+ for (const input of bareToInputs.get(bare) ?? []) {
17921
+ result.set(input, count);
17922
+ }
17278
17923
  }
17279
17924
  return result;
17280
17925
  }
@@ -17438,7 +18083,7 @@ function collectTagSetFromEntries(db, entryType) {
17438
18083
  }
17439
18084
  return tags;
17440
18085
  }
17441
- var DB_VERSION = 17, EMBEDDING_DIM = 384, GRAPH_SCHEMA_VERSION = 3, vecStatus, VEC_DOCS_URL = "https://github.com/itlackey/akm/blob/main/docs/configuration.md#sqlite-vec-extension", VEC_FALLBACK_THRESHOLD = 1e4, vecInitWarnedDbs, SQLITE_CHUNK_SIZE = 500, upsertStmtsByDb, FEEDBACK_LR = 0.1, FEEDBACK_REWARD_POSITIVE = 1, FEEDBACK_REWARD_NEGATIVE = 0, MAX_NEG_DELTA_PER_CALL = 0.15, UTILITY_REVIEW_THRESHOLD = 0.5, HIGH_UTILITY_THRESHOLD = 0.5;
18086
+ var DB_VERSION = 17, EMBEDDING_DIM = 384, GRAPH_SCHEMA_VERSION = 4, vecStatus, VEC_DOCS_URL = "https://github.com/itlackey/akm/blob/main/docs/configuration.md#sqlite-vec-extension", VEC_FALLBACK_THRESHOLD = 1e4, vecInitWarnedDbs, SQLITE_CHUNK_SIZE = 500, upsertStmtsByDb, FEEDBACK_LR = 0.1, FEEDBACK_REWARD_POSITIVE = 1, FEEDBACK_REWARD_NEGATIVE = 0, MAX_NEG_DELTA_PER_CALL = 0.15, UTILITY_REVIEW_THRESHOLD = 0.5, HIGH_UTILITY_THRESHOLD = 0.5;
17442
18087
  var init_db = __esm(() => {
17443
18088
  init_asset_ref();
17444
18089
  init_best_effort();
@@ -17448,6 +18093,7 @@ var init_db = __esm(() => {
17448
18093
  init_types();
17449
18094
  init_runtime();
17450
18095
  init_database();
18096
+ init_sqlite_pragmas();
17451
18097
  init_db_backup();
17452
18098
  vecStatus = new WeakMap;
17453
18099
  vecInitWarnedDbs = new WeakSet;
@@ -17457,13 +18103,15 @@ var init_db = __esm(() => {
17457
18103
  // src/indexer/db/graph-db.ts
17458
18104
  var exports_graph_db = {};
17459
18105
  __export(exports_graph_db, {
17460
- resolveEntryIdForPath: () => resolveEntryIdForPath,
17461
18106
  replaceStoredGraph: () => replaceStoredGraph,
17462
18107
  loadStoredGraphSnapshot: () => loadStoredGraphSnapshot,
17463
18108
  loadStoredGraphMeta: () => loadStoredGraphMeta,
17464
18109
  loadGraphMetaOnly: () => loadGraphMetaOnly,
17465
18110
  loadGraphFilesOnly: () => loadGraphFilesOnly,
17466
- loadGraphEntitiesByEntry: () => loadGraphEntitiesByEntry,
18111
+ loadGraphEntitiesByPath: () => loadGraphEntitiesByPath,
18112
+ hasGraphData: () => hasGraphData,
18113
+ enqueueGraphExtraction: () => enqueueGraphExtraction,
18114
+ drainExtractionQueue: () => drainExtractionQueue,
17467
18115
  deleteStoredGraph: () => deleteStoredGraph
17468
18116
  });
17469
18117
  import fs13 from "fs";
@@ -17486,17 +18134,6 @@ function uniqueSorted(values) {
17486
18134
  function normalizeEntity(value) {
17487
18135
  return value.trim().toLowerCase();
17488
18136
  }
17489
- function resolveEntryIdForPath(db, stashRoot, filePath) {
17490
- try {
17491
- const row = db.prepare("SELECT id FROM entries WHERE stash_dir = ? AND file_path = ? LIMIT 1").get(stashRoot, filePath);
17492
- if (row)
17493
- return row.id;
17494
- const fallback = db.prepare("SELECT id FROM entries WHERE file_path = ? LIMIT 1").get(filePath);
17495
- return fallback?.id ?? null;
17496
- } catch {
17497
- return null;
17498
- }
17499
- }
17500
18137
  function replaceStoredGraph(db, graph) {
17501
18138
  const upsertMeta = db.prepare(`INSERT INTO graph_meta (
17502
18139
  stash_root,
@@ -17536,21 +18173,21 @@ function replaceStoredGraph(db, graph) {
17536
18173
  cache_misses = excluded.cache_misses,
17537
18174
  truncation_count = excluded.truncation_count,
17538
18175
  failure_count = excluded.failure_count`);
17539
- const selectExisting = db.prepare("SELECT entry_id, file_path, body_hash FROM graph_files WHERE stash_root = ?");
17540
- const deleteFile = db.prepare("DELETE FROM graph_files WHERE entry_id = ?");
17541
- const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE entry_id = ?");
17542
- const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE entry_id = ?");
18176
+ const selectExisting = db.prepare("SELECT file_path, body_hash, file_order FROM graph_files WHERE stash_root = ?");
18177
+ const deleteFile = db.prepare("DELETE FROM graph_files WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
18178
+ const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
18179
+ const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
17543
18180
  const insertFile = db.prepare(`INSERT INTO graph_files (
17544
- entry_id, stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
17545
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
18181
+ stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
18182
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
17546
18183
  const updateFileMeta = db.prepare(`UPDATE graph_files
17547
18184
  SET file_order = ?, file_type = ?, confidence = ?, status = ?, reason = ?, extraction_run_id = ?
17548
- WHERE entry_id = ?`);
17549
- const insertEntity = db.prepare(`INSERT INTO graph_file_entities (entry_id, entity_order, stash_root, entity_norm, entity)
17550
- VALUES (?, ?, ?, ?, ?)`);
18185
+ WHERE stash_root = ? AND file_path = ? AND body_hash = ?`);
18186
+ const insertEntity = db.prepare(`INSERT INTO graph_file_entities (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
18187
+ VALUES (?, ?, ?, ?, ?, ?)`);
17551
18188
  const insertRelation = db.prepare(`INSERT INTO graph_file_relations (
17552
- entry_id, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
17553
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
18189
+ stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
18190
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
17554
18191
  const quality = graph.quality;
17555
18192
  const telemetry = graph.telemetry;
17556
18193
  db.transaction(() => {
@@ -17559,44 +18196,35 @@ function replaceStoredGraph(db, graph) {
17559
18196
  const existingByPath = new Map;
17560
18197
  for (const row of existingRows)
17561
18198
  existingByPath.set(row.file_path, row);
17562
- let orphanCount = 0;
17563
- const presentEntryIds = new Set;
18199
+ const presentPaths = new Set;
17564
18200
  for (const [fileOrder, node] of graph.files.entries()) {
17565
18201
  const bodyHash = node.bodyHash && node.bodyHash.length > 0 ? node.bodyHash : "";
17566
- const entryId = resolveEntryIdForPath(db, graph.stashRoot, node.path);
17567
- if (entryId == null) {
17568
- orphanCount += 1;
17569
- continue;
17570
- }
17571
- presentEntryIds.add(entryId);
18202
+ presentPaths.add(node.path);
17572
18203
  const existing = existingByPath.get(node.path);
17573
- if (existing && existing.entry_id === entryId && existing.body_hash === bodyHash) {
17574
- updateFileMeta.run(fileOrder, node.type, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null, entryId);
18204
+ if (existing && existing.body_hash === bodyHash) {
18205
+ updateFileMeta.run(fileOrder, node.type, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null, graph.stashRoot, node.path, bodyHash);
17575
18206
  continue;
17576
18207
  }
17577
18208
  if (existing) {
17578
- deleteEntities.run(existing.entry_id);
17579
- deleteRelations.run(existing.entry_id);
17580
- deleteFile.run(existing.entry_id);
18209
+ deleteEntities.run(graph.stashRoot, existing.file_path, existing.body_hash);
18210
+ deleteRelations.run(graph.stashRoot, existing.file_path, existing.body_hash);
18211
+ deleteFile.run(graph.stashRoot, existing.file_path, existing.body_hash);
17581
18212
  }
17582
- insertFile.run(entryId, graph.stashRoot, node.path, fileOrder, node.type, bodyHash, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null);
18213
+ insertFile.run(graph.stashRoot, node.path, fileOrder, node.type, bodyHash, node.confidence ?? null, node.status ?? (node.entities.length > 0 ? "extracted" : "empty"), node.reason ?? (node.entities.length > 0 ? "none" : "no_graph_content"), node.extractionRunId ?? telemetry?.extractionRunId ?? null);
17583
18214
  for (const [entityOrder, entity] of node.entities.entries()) {
17584
- insertEntity.run(entryId, entityOrder, graph.stashRoot, normalizeEntity(entity), entity);
18215
+ insertEntity.run(graph.stashRoot, node.path, bodyHash, entityOrder, normalizeEntity(entity), entity);
17585
18216
  }
17586
18217
  for (const [relationOrder, relation] of node.relations.entries()) {
17587
- insertRelation.run(entryId, relationOrder, normalizeEntity(relation.from), relation.from, normalizeEntity(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
18218
+ insertRelation.run(graph.stashRoot, node.path, bodyHash, relationOrder, normalizeEntity(relation.from), relation.from, normalizeEntity(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
17588
18219
  }
17589
18220
  }
17590
18221
  for (const row of existingRows) {
17591
- if (!presentEntryIds.has(row.entry_id)) {
17592
- deleteEntities.run(row.entry_id);
17593
- deleteRelations.run(row.entry_id);
17594
- deleteFile.run(row.entry_id);
18222
+ if (!presentPaths.has(row.file_path)) {
18223
+ deleteEntities.run(graph.stashRoot, row.file_path, row.body_hash);
18224
+ deleteRelations.run(graph.stashRoot, row.file_path, row.body_hash);
18225
+ deleteFile.run(graph.stashRoot, row.file_path, row.body_hash);
17595
18226
  }
17596
18227
  }
17597
- if (orphanCount > 0) {
17598
- warn(`[graph] replaceStoredGraph: skipped ${orphanCount} file(s) with no resolvable entry under ${graph.stashRoot}.`);
17599
- }
17600
18228
  })();
17601
18229
  }
17602
18230
  function deleteStoredGraph(db, stashPath) {
@@ -17605,6 +18233,44 @@ function deleteStoredGraph(db, stashPath) {
17605
18233
  db.prepare("DELETE FROM graph_meta WHERE stash_root = ?").run(stashPath);
17606
18234
  })();
17607
18235
  }
18236
+ function hasGraphData(db, stashRoot, filePath) {
18237
+ try {
18238
+ const row = db.prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1").get(stashRoot, filePath);
18239
+ return row !== undefined;
18240
+ } catch {
18241
+ return false;
18242
+ }
18243
+ }
18244
+ function enqueueGraphExtraction(db, stashRoot, filePath, bodyHash, priority = 0) {
18245
+ try {
18246
+ db.prepare(`INSERT INTO graph_extraction_queue (stash_root, file_path, body_hash, priority)
18247
+ VALUES (?, ?, ?, ?)
18248
+ ON CONFLICT(stash_root, file_path) DO UPDATE SET
18249
+ body_hash = excluded.body_hash,
18250
+ priority = MAX(graph_extraction_queue.priority, excluded.priority),
18251
+ queued_at = datetime('now')`).run(stashRoot, filePath, bodyHash, priority);
18252
+ } catch (err) {
18253
+ rethrowIfTestIsolationError(err);
18254
+ }
18255
+ }
18256
+ function drainExtractionQueue(db, stashRoot, limit) {
18257
+ try {
18258
+ return db.transaction(() => {
18259
+ const rows = db.prepare(`SELECT file_path, body_hash, priority
18260
+ FROM graph_extraction_queue
18261
+ WHERE stash_root = ?
18262
+ ORDER BY priority DESC, queued_at ASC
18263
+ LIMIT ?`).all(stashRoot, limit);
18264
+ const del = db.prepare("DELETE FROM graph_extraction_queue WHERE stash_root = ? AND file_path = ?");
18265
+ for (const row of rows)
18266
+ del.run(stashRoot, row.file_path);
18267
+ return rows.map((row) => ({ filePath: row.file_path, bodyHash: row.body_hash, priority: row.priority }));
18268
+ })();
18269
+ } catch (err) {
18270
+ rethrowIfTestIsolationError(err);
18271
+ return [];
18272
+ }
18273
+ }
17608
18274
  function loadGraphMetaOnly(stashPath, db) {
17609
18275
  return loadStoredGraphMeta(stashPath, db);
17610
18276
  }
@@ -17612,12 +18278,11 @@ function loadGraphFilesOnly(stashPath, db) {
17612
18278
  try {
17613
18279
  return withReadableGraphDb(db, (readDb) => {
17614
18280
  try {
17615
- const rows = readDb.prepare(`SELECT entry_id, file_path, file_type, body_hash, confidence, status, reason
18281
+ const rows = readDb.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason
17616
18282
  FROM graph_files
17617
18283
  WHERE stash_root = ?
17618
18284
  ORDER BY file_order`).all(stashPath);
17619
18285
  return rows.map((row) => ({
17620
- entryId: row.entry_id,
17621
18286
  path: row.file_path,
17622
18287
  type: row.file_type,
17623
18288
  bodyHash: row.body_hash,
@@ -17634,9 +18299,11 @@ function loadGraphFilesOnly(stashPath, db) {
17634
18299
  return [];
17635
18300
  }
17636
18301
  }
17637
- function loadGraphEntitiesByEntry(db, entryId) {
18302
+ function loadGraphEntitiesByPath(db, stashRoot, filePath, bodyHash) {
17638
18303
  try {
17639
- const rows = db.prepare("SELECT entity FROM graph_file_entities WHERE entry_id = ? ORDER BY entity_order").all(entryId);
18304
+ const rows = db.prepare(`SELECT entity FROM graph_file_entities
18305
+ WHERE stash_root = ? AND file_path = ? AND body_hash = ?
18306
+ ORDER BY entity_order`).all(stashRoot, filePath, bodyHash);
17640
18307
  return rows.map((r) => r.entity);
17641
18308
  } catch {
17642
18309
  return [];
@@ -17711,23 +18378,28 @@ function loadStoredGraphSnapshot(stashPath, db) {
17711
18378
  if (!meta)
17712
18379
  return null;
17713
18380
  try {
17714
- const fileRows = readDb.prepare(`SELECT entry_id, file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
18381
+ const fileRows = readDb.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
17715
18382
  FROM graph_files
17716
18383
  WHERE stash_root = ?
17717
18384
  ORDER BY file_order`).all(stashPath);
17718
- const entityRows = readDb.prepare(`SELECT gfe.entry_id AS entry_id, gf.file_path AS file_path, gfe.entity AS entity
18385
+ const entityRows = readDb.prepare(`SELECT gf.file_path AS file_path, gfe.entity AS entity
17719
18386
  FROM graph_file_entities gfe
17720
- JOIN graph_files gf ON gf.entry_id = gfe.entry_id
18387
+ JOIN graph_files gf
18388
+ ON gf.stash_root = gfe.stash_root
18389
+ AND gf.file_path = gfe.file_path
18390
+ AND gf.body_hash = gfe.body_hash
17721
18391
  WHERE gf.stash_root = ?
17722
18392
  ORDER BY gf.file_order, gfe.entity_order`).all(stashPath);
17723
- const relationRows = readDb.prepare(`SELECT gfr.entry_id AS entry_id,
17724
- gf.file_path AS file_path,
18393
+ const relationRows = readDb.prepare(`SELECT gf.file_path AS file_path,
17725
18394
  gfr.from_entity AS from_entity,
17726
18395
  gfr.to_entity AS to_entity,
17727
18396
  gfr.relation_type AS relation_type,
17728
18397
  gfr.confidence AS confidence
17729
18398
  FROM graph_file_relations gfr
17730
- JOIN graph_files gf ON gf.entry_id = gfr.entry_id
18399
+ JOIN graph_files gf
18400
+ ON gf.stash_root = gfr.stash_root
18401
+ AND gf.file_path = gfr.file_path
18402
+ AND gf.body_hash = gfr.body_hash
17731
18403
  WHERE gf.stash_root = ?
17732
18404
  ORDER BY gf.file_order, gfr.relation_order`).all(stashPath);
17733
18405
  const entitiesByPath = new Map;
@@ -17786,7 +18458,6 @@ function loadStoredGraphSnapshot(stashPath, db) {
17786
18458
  var init_graph_db = __esm(() => {
17787
18459
  init_errors();
17788
18460
  init_paths();
17789
- init_warn();
17790
18461
  init_db();
17791
18462
  });
17792
18463