akm-cli 0.9.0-beta.11 → 0.9.0-beta.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +163 -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/health.html +25 -27
  16. package/dist/cli.js +2 -2
  17. package/dist/commands/agent/contribute-cli.js +16 -3
  18. package/dist/commands/feedback-cli.js +48 -44
  19. package/dist/commands/health/html-report.js +140 -16
  20. package/dist/commands/health.js +277 -1
  21. package/dist/commands/improve/calibration.js +161 -0
  22. package/dist/commands/improve/consolidate.js +595 -105
  23. package/dist/commands/improve/dedup.js +482 -0
  24. package/dist/commands/improve/distill.js +119 -64
  25. package/dist/commands/improve/encoding-salience.js +205 -0
  26. package/dist/commands/improve/extract-cli.js +115 -1
  27. package/dist/commands/improve/extract-prompt.js +32 -1
  28. package/dist/commands/improve/extract-watch.js +140 -0
  29. package/dist/commands/improve/extract.js +210 -30
  30. package/dist/commands/improve/feedback-valence.js +54 -0
  31. package/dist/commands/improve/homeostatic.js +467 -0
  32. package/dist/commands/improve/improve-auto-accept.js +80 -7
  33. package/dist/commands/improve/improve-profiles.js +8 -0
  34. package/dist/commands/improve/improve.js +991 -61
  35. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  36. package/dist/commands/improve/outcome-loop.js +256 -0
  37. package/dist/commands/improve/proactive-maintenance.js +9 -35
  38. package/dist/commands/improve/procedural.js +409 -0
  39. package/dist/commands/improve/recombine.js +488 -0
  40. package/dist/commands/improve/reflect.js +20 -1
  41. package/dist/commands/improve/related-sessions.js +120 -0
  42. package/dist/commands/improve/salience.js +386 -0
  43. package/dist/commands/improve/triage.js +95 -0
  44. package/dist/commands/lint/agent-linter.js +19 -24
  45. package/dist/commands/lint/base-linter.js +173 -60
  46. package/dist/commands/lint/command-linter.js +19 -24
  47. package/dist/commands/lint/env-key-rules.js +34 -1
  48. package/dist/commands/lint/index.js +30 -13
  49. package/dist/commands/lint/memory-linter.js +1 -1
  50. package/dist/commands/lint/registry.js +5 -2
  51. package/dist/commands/lint/task-linter.js +3 -3
  52. package/dist/commands/lint/workflow-linter.js +26 -1
  53. package/dist/commands/proposal/validators/proposals.js +4 -0
  54. package/dist/commands/read/curate.js +284 -86
  55. package/dist/commands/read/search-cli.js +7 -0
  56. package/dist/commands/read/search.js +1 -0
  57. package/dist/commands/sources/installed-stashes.js +5 -1
  58. package/dist/core/asset/frontmatter.js +166 -167
  59. package/dist/core/asset/markdown.js +8 -0
  60. package/dist/core/config/config-schema.js +211 -3
  61. package/dist/core/config/config.js +2 -2
  62. package/dist/core/logs-db.js +4 -3
  63. package/dist/core/state-db.js +555 -29
  64. package/dist/indexer/db/db.js +250 -27
  65. package/dist/indexer/db/graph-db.js +81 -86
  66. package/dist/indexer/graph/graph-boost.js +51 -41
  67. package/dist/indexer/passes/memory-inference.js +10 -3
  68. package/dist/indexer/passes/staleness-detect.js +2 -5
  69. package/dist/indexer/search/db-search.js +15 -4
  70. package/dist/indexer/search/ranking.js +4 -0
  71. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  72. package/dist/integrations/harnesses/opencode/session-log.js +9 -0
  73. package/dist/integrations/session-logs/index.js +16 -0
  74. package/dist/llm/embedder.js +27 -3
  75. package/dist/llm/embedders/local.js +66 -2
  76. package/dist/llm/graph-extract.js +2 -1
  77. package/dist/llm/memory-infer.js +4 -8
  78. package/dist/llm/metadata-enhance.js +9 -1
  79. package/dist/output/shapes/curate.js +14 -2
  80. package/dist/output/text/helpers.js +9 -0
  81. package/dist/runtime.js +25 -1
  82. package/dist/scripts/migrate-storage.js +1025 -567
  83. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +435 -269
  84. package/dist/storage/sqlite-pragmas.js +146 -0
  85. package/dist/workflows/db.js +3 -4
  86. package/dist/workflows/validate-summary.js +2 -7
  87. package/docs/data-and-telemetry.md +1 -0
  88. package/package.json +5 -4
@@ -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,6 +7461,300 @@ var init_dist = __esm(() => {
7804
7461
  $visitAsync = visit.visitAsync;
7805
7462
  });
7806
7463
 
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
+
7807
7758
  // src/workflows/schema.ts
7808
7759
  var WORKFLOW_SCHEMA_VERSION = 1;
7809
7760
 
@@ -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*$/;
@@ -8387,6 +8339,7 @@ function applyTaskMetadata(entry, ctx) {
8387
8339
  }
8388
8340
  var init_renderers = __esm(() => {
8389
8341
  init_env();
8342
+ init_frontmatter();
8390
8343
  init_markdown();
8391
8344
  init_common();
8392
8345
  init_metadata();
@@ -8840,6 +8793,103 @@ function runMigrations(db, migrations, opts) {
8840
8793
  }
8841
8794
  }
8842
8795
 
8796
+ // src/runtime.ts
8797
+ import { createHash } from "crypto";
8798
+ import { createWriteStream, statfsSync } from "fs";
8799
+ import { createRequire as createRequire2 } from "module";
8800
+ function sha256Hex(data) {
8801
+ return createHash("sha256").update(data).digest("hex");
8802
+ }
8803
+ function statfsType(path5) {
8804
+ try {
8805
+ return statfsSync(path5).type;
8806
+ } catch {
8807
+ return;
8808
+ }
8809
+ }
8810
+ function sleepSync(ms) {
8811
+ if (isBun2) {
8812
+ bunGlobal().sleepSync(ms);
8813
+ return;
8814
+ }
8815
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
8816
+ }
8817
+ function bunGlobal() {
8818
+ return globalThis.Bun;
8819
+ }
8820
+ var isBun2, nodeRequire2, mainPath;
8821
+ var init_runtime = __esm(() => {
8822
+ isBun2 = !!process.versions?.bun;
8823
+ nodeRequire2 = createRequire2(import.meta.url);
8824
+ mainPath = isBun2 ? bunGlobal().main : process.argv[1];
8825
+ });
8826
+
8827
+ // src/storage/sqlite-pragmas.ts
8828
+ function resolveJournalMode(raw) {
8829
+ if (raw === undefined)
8830
+ return "WAL";
8831
+ const normalized = raw.trim().toUpperCase();
8832
+ if (normalized === "")
8833
+ return "WAL";
8834
+ if (VALID_MODES.has(normalized)) {
8835
+ return normalized;
8836
+ }
8837
+ warnInvalidJournalModeOnce(raw);
8838
+ return "WAL";
8839
+ }
8840
+ function resolveConfiguredJournalMode() {
8841
+ return resolveJournalMode(process.env.AKM_SQLITE_JOURNAL_MODE);
8842
+ }
8843
+ function warnInvalidJournalModeOnce(raw) {
8844
+ if (warnedInvalid)
8845
+ return;
8846
+ warnedInvalid = true;
8847
+ warn(`[akm] invalid AKM_SQLITE_JOURNAL_MODE=${JSON.stringify(raw)} \u2014 using WAL (valid: WAL, DELETE, TRUNCATE)`);
8848
+ }
8849
+ function isNetworkFilesystem(fsType) {
8850
+ if (fsType === undefined)
8851
+ return false;
8852
+ return NETWORK_FS_MAGICS.has(fsType);
8853
+ }
8854
+ function applyStandardPragmas(db, opts = {}) {
8855
+ let mode = resolveConfiguredJournalMode();
8856
+ if (mode === "WAL" && opts.dataDir) {
8857
+ const probe = opts.fsTypeProbe ?? statfsType;
8858
+ if (isNetworkFilesystem(probe(opts.dataDir))) {
8859
+ mode = "DELETE";
8860
+ warnNetworkFallbackOnce(opts.dataDir);
8861
+ }
8862
+ }
8863
+ db.exec("PRAGMA busy_timeout = 30000");
8864
+ db.exec(`PRAGMA journal_mode = ${mode}`);
8865
+ if (opts.foreignKeys !== false) {
8866
+ db.exec("PRAGMA foreign_keys = ON");
8867
+ }
8868
+ if (mode !== "WAL") {
8869
+ db.exec("PRAGMA synchronous = FULL");
8870
+ }
8871
+ return mode;
8872
+ }
8873
+ function warnNetworkFallbackOnce(dataDir) {
8874
+ if (warnedNetworkFallback)
8875
+ return;
8876
+ warnedNetworkFallback = true;
8877
+ warn(`[akm] network filesystem detected at ${dataDir} \u2014 WAL unsupported, using DELETE journal mode`);
8878
+ }
8879
+ 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;
8880
+ var init_sqlite_pragmas = __esm(() => {
8881
+ init_warn();
8882
+ init_runtime();
8883
+ VALID_MODES = new Set(["WAL", "DELETE", "TRUNCATE"]);
8884
+ NETWORK_FS_MAGICS = new Set([
8885
+ FS_MAGIC_NFS,
8886
+ FS_MAGIC_SMB,
8887
+ FS_MAGIC_CIFS,
8888
+ FS_MAGIC_SMB2,
8889
+ FS_MAGIC_FUSE
8890
+ ]);
8891
+ });
8892
+
8843
8893
  // src/core/assert.ts
8844
8894
  function assertNever(x, context) {
8845
8895
  let serialized;
@@ -8886,8 +8936,11 @@ __export(exports_state_db, {
8886
8936
  upsertTaskHistory: () => upsertTaskHistory,
8887
8937
  upsertProposal: () => upsertProposal,
8888
8938
  upsertExtractedSession: () => upsertExtractedSession,
8939
+ upsertConsolidationJudged: () => upsertConsolidationJudged,
8940
+ upsertBodyEmbeddings: () => upsertBodyEmbeddings,
8889
8941
  shouldSkipAlreadyExtractedSession: () => shouldSkipAlreadyExtractedSession,
8890
8942
  runMigrations: () => runMigrations2,
8943
+ recordRecombineInduction: () => recordRecombineInduction,
8891
8944
  recordImproveRun: () => recordImproveRun,
8892
8945
  recordFsProposalsImport: () => recordFsProposalsImport,
8893
8946
  readStateEvents: () => readStateEvents,
@@ -8898,9 +8951,12 @@ __export(exports_state_db, {
8898
8951
  purgeOldEvents: () => purgeOldEvents,
8899
8952
  proposalToRowValues: () => proposalToRowValues,
8900
8953
  proposalRowToProposal: () => proposalRowToProposal,
8954
+ persistPhaseThreshold: () => persistPhaseThreshold,
8901
8955
  openStateDatabase: () => openStateDatabase,
8956
+ markRecombineHypothesisPromoted: () => markRecombineHypothesisPromoted,
8902
8957
  listStateProposals: () => listStateProposals,
8903
8958
  listStateProposalIdsByPrefix: () => listStateProposalIdsByPrefix,
8959
+ listProposalGateDecisions: () => listProposalGateDecisions,
8904
8960
  listExistingTableNames: () => listExistingTableNames,
8905
8961
  insertProposalIfAbsent: () => insertProposalIfAbsent,
8906
8962
  insertEvent: () => insertEvent,
@@ -8910,10 +8966,17 @@ __export(exports_state_db, {
8910
8966
  getTaskHistory: () => getTaskHistory,
8911
8967
  getStateProposal: () => getStateProposal,
8912
8968
  getStateDbPath: () => getStateDbPath,
8969
+ getRecombineHypothesis: () => getRecombineHypothesis,
8970
+ getPhaseThreshold: () => getPhaseThreshold,
8913
8971
  getExtractedSessionsMap: () => getExtractedSessionsMap,
8914
8972
  getExtractedSession: () => getExtractedSession,
8973
+ getConsolidationJudgedMap: () => getConsolidationJudgedMap,
8974
+ getBodyEmbeddings: () => getBodyEmbeddings,
8915
8975
  eventRowToEnvelope: () => eventRowToEnvelope,
8976
+ embeddingToBlob: () => embeddingToBlob,
8977
+ decayUnseenRecombineHypotheses: () => decayUnseenRecombineHypotheses,
8916
8978
  computeImproveRunMetrics: () => computeImproveRunMetrics,
8979
+ blobToEmbedding: () => blobToEmbedding,
8917
8980
  REGISTRY_INDEX_CACHE_DDL: () => REGISTRY_INDEX_CACHE_DDL
8918
8981
  });
8919
8982
  import fs5 from "fs";
@@ -8928,9 +8991,7 @@ function openStateDatabase(dbPath) {
8928
8991
  fs5.mkdirSync(dir, { recursive: true });
8929
8992
  }
8930
8993
  const db = openDatabase(resolvedPath);
8931
- db.exec("PRAGMA journal_mode = WAL");
8932
- db.exec("PRAGMA foreign_keys = ON");
8933
- db.exec("PRAGMA busy_timeout = 30000");
8994
+ applyStandardPragmas(db, { dataDir: dir });
8934
8995
  runMigrations2(db);
8935
8996
  return db;
8936
8997
  }
@@ -9094,6 +9155,32 @@ function listStateProposals(db, options = {}) {
9094
9155
  FROM proposals ${where} ORDER BY created_at ASC, rowid ASC`).all(...params);
9095
9156
  return rows.map(proposalRowToProposal);
9096
9157
  }
9158
+ function listProposalGateDecisions(db) {
9159
+ const rows = db.prepare("SELECT metadata_json FROM proposals ORDER BY created_at ASC, rowid ASC").all();
9160
+ const decisions = [];
9161
+ for (const row of rows) {
9162
+ let meta;
9163
+ try {
9164
+ meta = JSON.parse(row.metadata_json);
9165
+ } catch {
9166
+ continue;
9167
+ }
9168
+ const decision = meta.gateDecision;
9169
+ if (decision && typeof decision === "object" && typeof decision.outcome === "string") {
9170
+ decisions.push(decision);
9171
+ }
9172
+ }
9173
+ decisions.sort((a, b) => new Date(a.decidedAt).getTime() - new Date(b.decidedAt).getTime());
9174
+ return decisions;
9175
+ }
9176
+ function getPhaseThreshold(db, phase) {
9177
+ const row = db.prepare("SELECT threshold FROM improve_gate_thresholds WHERE phase = ?").get(phase);
9178
+ return row?.threshold;
9179
+ }
9180
+ function persistPhaseThreshold(db, phase, threshold) {
9181
+ db.prepare(`INSERT OR REPLACE INTO improve_gate_thresholds (phase, threshold, updated_at)
9182
+ VALUES (?, ?, ?)`).run(phase, Math.round(threshold), Date.now());
9183
+ }
9097
9184
  function getStateProposal(db, id, stashDir) {
9098
9185
  const sql = `SELECT id, stash_dir, ref, status, source, created_at, updated_at,
9099
9186
  content, frontmatter_json, metadata_json
@@ -9295,9 +9382,10 @@ function upsertExtractedSession(db, input) {
9295
9382
  db.prepare(`
9296
9383
  INSERT OR REPLACE INTO extract_sessions_seen
9297
9384
  (harness, session_id, processed_at, session_ended_at, outcome,
9298
- candidate_count, proposal_count, rationale, source_run, metadata_json)
9299
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9300
- `).run(input.harness, input.sessionId, input.processedAt, endedAtIso, input.outcome, input.candidateCount, input.proposalCount, input.rationale ?? null, input.sourceRun ?? null, JSON.stringify(input.metadata ?? {}));
9385
+ candidate_count, proposal_count, rationale, source_run, metadata_json,
9386
+ content_hash)
9387
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9388
+ `).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);
9301
9389
  }
9302
9390
  function getExtractedSession(db, harness, sessionId) {
9303
9391
  const row = db.prepare("SELECT * FROM extract_sessions_seen WHERE harness = ? AND session_id = ?").get(harness, sessionId);
@@ -9318,16 +9406,114 @@ function getExtractedSessionsMap(db, harness, sessionIds) {
9318
9406
  }
9319
9407
  return out;
9320
9408
  }
9321
- function shouldSkipAlreadyExtractedSession(prior, liveSessionEndedAtMs) {
9409
+ function shouldSkipAlreadyExtractedSession(prior, currentContentHash) {
9322
9410
  if (!prior)
9323
9411
  return false;
9324
- if (typeof liveSessionEndedAtMs !== "number" || !Number.isFinite(liveSessionEndedAtMs)) {
9325
- return true;
9326
- }
9327
- const priorMs = prior.session_ended_at ? Date.parse(prior.session_ended_at) : Number.NaN;
9328
- if (!Number.isFinite(priorMs))
9412
+ if (prior.content_hash == null)
9329
9413
  return false;
9330
- return liveSessionEndedAtMs <= priorMs;
9414
+ return prior.content_hash === currentContentHash;
9415
+ }
9416
+ function getConsolidationJudgedMap(db, entryKeys) {
9417
+ const out = new Map;
9418
+ if (entryKeys.length === 0)
9419
+ return out;
9420
+ const CHUNK = 500;
9421
+ for (let i = 0;i < entryKeys.length; i += CHUNK) {
9422
+ const chunk = entryKeys.slice(i, i + CHUNK);
9423
+ const placeholders = chunk.map(() => "?").join(",");
9424
+ const rows = db.prepare(`SELECT * FROM consolidation_judged WHERE entry_key IN (${placeholders})`).all(...chunk);
9425
+ for (const row of rows)
9426
+ out.set(row.entry_key, row);
9427
+ }
9428
+ return out;
9429
+ }
9430
+ function upsertConsolidationJudged(db, input) {
9431
+ db.prepare(`
9432
+ INSERT OR REPLACE INTO consolidation_judged
9433
+ (entry_key, content_hash, judged_at, outcome)
9434
+ VALUES (?, ?, ?, ?)
9435
+ `).run(input.entryKey, input.contentHash, input.judgedAt, input.outcome);
9436
+ }
9437
+ function recordRecombineInduction(db, input) {
9438
+ const row = db.prepare(`
9439
+ INSERT INTO recombine_hypotheses
9440
+ (hypothesis_ref, signature, member_key, consecutive_count, first_seen_at, last_seen_at, last_run)
9441
+ VALUES (?, ?, ?, 1, ?, ?, ?)
9442
+ ON CONFLICT(hypothesis_ref) DO UPDATE SET
9443
+ consecutive_count = consecutive_count + (CASE WHEN last_run IS excluded.last_run THEN 0 ELSE 1 END),
9444
+ last_seen_at = excluded.last_seen_at,
9445
+ last_run = excluded.last_run,
9446
+ signature = excluded.signature,
9447
+ member_key = excluded.member_key
9448
+ RETURNING consecutive_count
9449
+ `).get(input.hypothesisRef, input.signature, input.memberKey, input.seenAt, input.seenAt, input.run);
9450
+ return row?.consecutive_count ?? 0;
9451
+ }
9452
+ function getRecombineHypothesis(db, hypothesisRef) {
9453
+ const row = db.prepare("SELECT * FROM recombine_hypotheses WHERE hypothesis_ref = ?").get(hypothesisRef);
9454
+ return row ?? undefined;
9455
+ }
9456
+ function markRecombineHypothesisPromoted(db, hypothesisRef, promotedAt) {
9457
+ db.prepare("UPDATE recombine_hypotheses SET promoted_at = ?, consecutive_count = 0 WHERE hypothesis_ref = ?").run(promotedAt, hypothesisRef);
9458
+ }
9459
+ function decayUnseenRecombineHypotheses(db, currentRun, seenRefs) {
9460
+ if (seenRefs.length === 0) {
9461
+ 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);
9462
+ return Number(res2.changes);
9463
+ }
9464
+ if (seenRefs.length > 900) {
9465
+ 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);
9466
+ return Number(res2.changes);
9467
+ }
9468
+ const placeholders = seenRefs.map(() => "?").join(",");
9469
+ const res = db.prepare(`UPDATE recombine_hypotheses SET consecutive_count = 0
9470
+ WHERE promoted_at IS NULL
9471
+ AND (last_run IS NULL OR last_run != ?)
9472
+ AND consecutive_count != 0
9473
+ AND hypothesis_ref NOT IN (${placeholders})`).run(currentRun, ...seenRefs);
9474
+ return Number(res.changes);
9475
+ }
9476
+ function embeddingToBlob(vec) {
9477
+ const f32 = new Float32Array(vec);
9478
+ return new Uint8Array(f32.buffer);
9479
+ }
9480
+ function blobToEmbedding(blob) {
9481
+ const f32 = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
9482
+ return Array.from(f32);
9483
+ }
9484
+ function getBodyEmbeddings(db, contentHashes, expectedModelId) {
9485
+ const out = new Map;
9486
+ if (contentHashes.length === 0)
9487
+ return out;
9488
+ const firstRow = db.prepare("SELECT model_id FROM body_embeddings LIMIT 1").get();
9489
+ if (firstRow && firstRow.model_id !== expectedModelId) {
9490
+ db.exec("DELETE FROM body_embeddings");
9491
+ return out;
9492
+ }
9493
+ const CHUNK = 500;
9494
+ for (let i = 0;i < contentHashes.length; i += CHUNK) {
9495
+ const chunk = contentHashes.slice(i, i + CHUNK);
9496
+ const placeholders = chunk.map(() => "?").join(",");
9497
+ const rows = db.prepare(`SELECT content_hash, embedding FROM body_embeddings WHERE content_hash IN (${placeholders})`).all(...chunk);
9498
+ for (const row of rows) {
9499
+ out.set(row.content_hash, blobToEmbedding(row.embedding));
9500
+ }
9501
+ }
9502
+ return out;
9503
+ }
9504
+ function upsertBodyEmbeddings(db, entries) {
9505
+ if (entries.length === 0)
9506
+ return;
9507
+ const now = Date.now();
9508
+ const stmt = db.prepare(`
9509
+ INSERT OR REPLACE INTO body_embeddings (content_hash, embedding, model_id, created_at)
9510
+ VALUES (?, ?, ?, ?)
9511
+ `);
9512
+ db.transaction(() => {
9513
+ for (const { contentHash, embedding, modelId } of entries) {
9514
+ stmt.run(contentHash, embeddingToBlob(embedding), modelId, now);
9515
+ }
9516
+ })();
9331
9517
  }
9332
9518
  var MIGRATIONS, REGISTRY_INDEX_CACHE_DDL = `
9333
9519
  CREATE TABLE IF NOT EXISTS registry_index_cache (
@@ -9343,6 +9529,7 @@ var MIGRATIONS, REGISTRY_INDEX_CACHE_DDL = `
9343
9529
  `;
9344
9530
  var init_state_db = __esm(() => {
9345
9531
  init_database();
9532
+ init_sqlite_pragmas();
9346
9533
  init_improve_types();
9347
9534
  init_paths();
9348
9535
  init_warn();
@@ -9623,6 +9810,110 @@ var init_state_db = __esm(() => {
9623
9810
  CREATE INDEX IF NOT EXISTS idx_proposals_stash_status_ref_source
9624
9811
  ON proposals(stash_dir, status, ref, source);
9625
9812
  `
9813
+ },
9814
+ {
9815
+ id: "007-consolidation-judged",
9816
+ up: `
9817
+ CREATE TABLE IF NOT EXISTS consolidation_judged (
9818
+ entry_key TEXT PRIMARY KEY,
9819
+ content_hash TEXT NOT NULL,
9820
+ judged_at TEXT NOT NULL,
9821
+ outcome TEXT NOT NULL
9822
+ );
9823
+ `
9824
+ },
9825
+ {
9826
+ id: "008-body-embeddings",
9827
+ up: `
9828
+ CREATE TABLE IF NOT EXISTS body_embeddings (
9829
+ content_hash TEXT PRIMARY KEY,
9830
+ embedding BLOB NOT NULL,
9831
+ model_id TEXT NOT NULL,
9832
+ created_at INTEGER NOT NULL
9833
+ );
9834
+ `
9835
+ },
9836
+ {
9837
+ id: "009-asset-salience",
9838
+ up: `
9839
+ CREATE TABLE IF NOT EXISTS asset_salience (
9840
+ asset_ref TEXT PRIMARY KEY,
9841
+ encoding_salience REAL NOT NULL DEFAULT 0.5,
9842
+ outcome_salience REAL NOT NULL DEFAULT 0.0,
9843
+ retrieval_salience REAL NOT NULL DEFAULT 0.0,
9844
+ rank_score REAL NOT NULL DEFAULT 0.0,
9845
+ consecutive_no_ops INTEGER NOT NULL DEFAULT 0,
9846
+ updated_at INTEGER NOT NULL DEFAULT 0
9847
+ );
9848
+
9849
+ -- Hot path: sort / filter by rank_score for selector queries.
9850
+ CREATE INDEX IF NOT EXISTS idx_asset_salience_rank
9851
+ ON asset_salience(rank_score DESC);
9852
+ `
9853
+ },
9854
+ {
9855
+ id: "010-asset-outcome",
9856
+ up: `
9857
+ CREATE TABLE IF NOT EXISTS asset_outcome (
9858
+ asset_ref TEXT PRIMARY KEY,
9859
+ last_retrieved_at INTEGER NOT NULL DEFAULT 0,
9860
+ retrieval_count INTEGER NOT NULL DEFAULT 0,
9861
+ expected_retrieval_rate REAL NOT NULL DEFAULT 0.0,
9862
+ negative_feedback_count INTEGER NOT NULL DEFAULT 0,
9863
+ accepted_change_count INTEGER NOT NULL DEFAULT 0,
9864
+ review_pressure INTEGER NOT NULL DEFAULT 0,
9865
+ outcome_score REAL NOT NULL DEFAULT 0.0,
9866
+ updated_at INTEGER NOT NULL DEFAULT 0
9867
+ );
9868
+
9869
+ -- Hot path: sort assets by review_pressure DESC for #613 admission.
9870
+ CREATE INDEX IF NOT EXISTS idx_asset_outcome_review_pressure
9871
+ ON asset_outcome(review_pressure DESC);
9872
+
9873
+ -- Secondary: sort by outcome_score DESC for outcomeSalience reads.
9874
+ CREATE INDEX IF NOT EXISTS idx_asset_outcome_score
9875
+ ON asset_outcome(outcome_score DESC);
9876
+ `
9877
+ },
9878
+ {
9879
+ id: "011-asset-salience-homeostatic-demoted-at",
9880
+ up: `
9881
+ ALTER TABLE asset_salience ADD COLUMN homeostatic_demoted_at INTEGER DEFAULT NULL;
9882
+ `
9883
+ },
9884
+ {
9885
+ id: "012-improve-gate-thresholds",
9886
+ up: `
9887
+ CREATE TABLE IF NOT EXISTS improve_gate_thresholds (
9888
+ phase TEXT NOT NULL PRIMARY KEY,
9889
+ threshold INTEGER NOT NULL,
9890
+ updated_at INTEGER NOT NULL
9891
+ );
9892
+ `
9893
+ },
9894
+ {
9895
+ id: "013-extract-sessions-content-hash",
9896
+ up: `
9897
+ ALTER TABLE extract_sessions_seen ADD COLUMN content_hash TEXT DEFAULT NULL;
9898
+ `
9899
+ },
9900
+ {
9901
+ id: "014-recombine-hypotheses",
9902
+ up: `
9903
+ CREATE TABLE IF NOT EXISTS recombine_hypotheses (
9904
+ hypothesis_ref TEXT PRIMARY KEY,
9905
+ signature TEXT NOT NULL,
9906
+ member_key TEXT NOT NULL,
9907
+ consecutive_count INTEGER NOT NULL DEFAULT 0,
9908
+ first_seen_at TEXT NOT NULL,
9909
+ last_seen_at TEXT NOT NULL,
9910
+ last_run TEXT,
9911
+ promoted_at TEXT,
9912
+ metadata_json TEXT NOT NULL DEFAULT '{}'
9913
+ );
9914
+ CREATE INDEX IF NOT EXISTS idx_recombine_hypotheses_last_seen
9915
+ ON recombine_hypotheses(last_seen_at);
9916
+ `
9626
9917
  }
9627
9918
  ];
9628
9919
  });
@@ -9666,29 +9957,6 @@ var init_types = __esm(() => {
9666
9957
  init_warn();
9667
9958
  });
9668
9959
 
9669
- // src/runtime.ts
9670
- import { createHash } from "crypto";
9671
- import { createRequire as createRequire2 } from "module";
9672
- function sha256Hex(data) {
9673
- return createHash("sha256").update(data).digest("hex");
9674
- }
9675
- function sleepSync(ms) {
9676
- if (isBun2) {
9677
- bunGlobal().sleepSync(ms);
9678
- return;
9679
- }
9680
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
9681
- }
9682
- function bunGlobal() {
9683
- return globalThis.Bun;
9684
- }
9685
- var isBun2, nodeRequire2, mainPath;
9686
- var init_runtime = __esm(() => {
9687
- isBun2 = !!process.versions?.bun;
9688
- nodeRequire2 = createRequire2(import.meta.url);
9689
- mainPath = isBun2 ? bunGlobal().main : process.argv[1];
9690
- });
9691
-
9692
9960
  // src/indexer/search/search-fields.ts
9693
9961
  function buildSearchFields(entry) {
9694
9962
  const name = entry.name.replace(/[-_]/g, " ").toLowerCase();
@@ -10372,6 +10640,10 @@ class ClaudeCodeProvider {
10372
10640
  isAvailable() {
10373
10641
  return fs9.existsSync(claudeProjectsDir());
10374
10642
  }
10643
+ watchRoots() {
10644
+ const dir = claudeProjectsDir();
10645
+ return fs9.existsSync(dir) ? [dir] : [];
10646
+ }
10375
10647
  *readEvents(input) {
10376
10648
  try {
10377
10649
  for (const jsonlPath of this.#walkJsonl(claudeProjectsDir())) {
@@ -10606,6 +10878,10 @@ class OpenCodeProvider {
10606
10878
  isAvailable() {
10607
10879
  return fs10.existsSync(this.#baseDir);
10608
10880
  }
10881
+ watchRoots() {
10882
+ const sessionRoot = path9.join(this.#baseDir, "storage", "session");
10883
+ return fs10.existsSync(sessionRoot) ? [sessionRoot] : [];
10884
+ }
10609
10885
  *readEvents(input) {
10610
10886
  const candidates = [this.#baseDir, path9.join(this.#baseDir, "log")];
10611
10887
  for (const dir of candidates) {
@@ -15466,7 +15742,7 @@ var init_config_types = __esm(() => {
15466
15742
  });
15467
15743
 
15468
15744
  // src/core/config/config-schema.ts
15469
- 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;
15745
+ 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;
15470
15746
  var init_config_schema = __esm(() => {
15471
15747
  init_zod();
15472
15748
  init_config_types();
@@ -15528,6 +15804,12 @@ var init_config_schema = __esm(() => {
15528
15804
  timeoutMs: exports_external.union([positiveInt, exports_external.null()]).optional(),
15529
15805
  allowedTypes: exports_external.array(exports_external.string().min(1)).optional(),
15530
15806
  minPoolSize: exports_external.number().int().min(0).optional(),
15807
+ dedup: exports_external.object({
15808
+ enabled: exports_external.boolean().optional(),
15809
+ cosineThreshold: exports_external.number().min(0).max(1).optional(),
15810
+ cosineCandidateLimit: exports_external.number().int().positive().optional()
15811
+ }).strict().optional(),
15812
+ judgedCache: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15531
15813
  qualityGate: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15532
15814
  contradictionDetection: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15533
15815
  defaultSince: exports_external.string().min(1).optional(),
@@ -15540,12 +15822,45 @@ var init_config_schema = __esm(() => {
15540
15822
  requirePlannedRefs: exports_external.boolean().optional(),
15541
15823
  dueDays: exports_external.number().int().min(0).optional(),
15542
15824
  maxPerRun: positiveInt.optional(),
15543
- importanceWeights: exports_external.record(exports_external.string().min(1), exports_external.number()).optional(),
15544
15825
  minPendingCount: exports_external.number().int().min(0).optional(),
15545
15826
  minNewSessions: exports_external.number().int().min(0).optional(),
15546
15827
  maxSessionsPerRun: exports_external.number().int().min(0).optional(),
15547
15828
  indexSessions: exports_external.boolean().optional(),
15548
15829
  minSessionDuration: exports_external.number().min(0).optional(),
15830
+ p90ChunkSecondsDefault: exports_external.number().finite().positive().optional(),
15831
+ homeostaticDemotion: exports_external.object({
15832
+ enabled: exports_external.boolean().optional(),
15833
+ staleDays: exports_external.number().int().min(0).optional(),
15834
+ demotionFactor: exports_external.number().min(0).max(1).optional()
15835
+ }).strict().optional(),
15836
+ schemaSimilarity: exports_external.object({
15837
+ enabled: exports_external.boolean().optional(),
15838
+ epsilon: exports_external.number().min(0).max(1).optional(),
15839
+ confidencePenalty: exports_external.number().min(0).max(1).optional()
15840
+ }).strict().optional(),
15841
+ hotProbation: exports_external.object({
15842
+ enabled: exports_external.boolean().optional()
15843
+ }).strict().optional(),
15844
+ antiCollapse: exports_external.object({
15845
+ enabled: exports_external.boolean().optional(),
15846
+ maxGeneration: exports_external.number().int().min(1).optional(),
15847
+ lexicalDiversityCheck: exports_external.boolean().optional(),
15848
+ randomClusterFraction: exports_external.number().min(0).max(1).optional()
15849
+ }).strict().optional(),
15850
+ cls: exports_external.object({
15851
+ enabled: exports_external.boolean().optional(),
15852
+ adjacentCount: exports_external.number().int().min(1).optional()
15853
+ }).strict().optional(),
15854
+ fidelityCheck: exports_external.object({
15855
+ enabled: exports_external.boolean().optional()
15856
+ }).strict().optional(),
15857
+ minClusterSize: exports_external.number().int().min(2).optional(),
15858
+ maxClustersPerRun: positiveInt.optional(),
15859
+ relatednessSource: exports_external.enum(["tags", "graph", "both"]).optional(),
15860
+ confirmThreshold: exports_external.number().int().min(1).optional(),
15861
+ minRecurrence: exports_external.number().int().min(2).optional(),
15862
+ maxProposalsPerRun: positiveInt.optional(),
15863
+ emitAs: exports_external.enum(["workflow", "skill"]).optional(),
15549
15864
  applyMode: exports_external.enum(["queue", "promote"]).optional(),
15550
15865
  policy: exports_external.string().min(1).optional(),
15551
15866
  maxAcceptsPerRun: positiveInt.optional(),
@@ -15565,7 +15880,9 @@ var init_config_schema = __esm(() => {
15565
15880
  graphExtraction: ImproveProcessConfigSchema.optional(),
15566
15881
  validation: ImproveProcessConfigSchema.optional(),
15567
15882
  triage: ImproveProcessConfigSchema.optional(),
15568
- proactiveMaintenance: ImproveProcessConfigSchema.optional()
15883
+ proactiveMaintenance: ImproveProcessConfigSchema.optional(),
15884
+ recombine: ImproveProcessConfigSchema.optional(),
15885
+ procedural: ImproveProcessConfigSchema.optional()
15569
15886
  }).passthrough().superRefine((val, ctx) => {
15570
15887
  const raw = val;
15571
15888
  if ("feedbackDistillation" in raw) {
@@ -15584,7 +15901,9 @@ var init_config_schema = __esm(() => {
15584
15901
  "validation",
15585
15902
  "extract",
15586
15903
  "triage",
15587
- "proactiveMaintenance"
15904
+ "proactiveMaintenance",
15905
+ "recombine",
15906
+ "procedural"
15588
15907
  ]);
15589
15908
  for (const k of Object.keys(raw)) {
15590
15909
  if (!allowed.has(k)) {
@@ -15601,6 +15920,7 @@ var init_config_schema = __esm(() => {
15601
15920
  processes: ImproveProfileProcessesSchema.optional(),
15602
15921
  autoAccept: nonNegativeNumber.optional(),
15603
15922
  limit: positiveInt.optional(),
15923
+ symmetricValence: exports_external.boolean().optional(),
15604
15924
  sync: exports_external.object({
15605
15925
  enabled: exports_external.boolean().optional(),
15606
15926
  push: exports_external.boolean().optional(),
@@ -15681,6 +16001,7 @@ var init_config_schema = __esm(() => {
15681
16001
  }).strict();
15682
16002
  SearchConfigSchema = exports_external.object({
15683
16003
  minScore: nonNegativeNumber.optional(),
16004
+ defaultExcludeTypes: exports_external.array(nonEmptyString).optional(),
15684
16005
  curateRerank: exports_external.object({ enabled: exports_external.boolean().optional() }).strict().optional(),
15685
16006
  graphBoost: SearchGraphBoostSchema.optional()
15686
16007
  }).strict();
@@ -15692,9 +16013,29 @@ var init_config_schema = __esm(() => {
15692
16013
  halfLifeDays: exports_external.number().finite().min(0.1).optional(),
15693
16014
  feedbackStabilityBoost: exports_external.number().finite().min(1).optional()
15694
16015
  }).strict();
16016
+ ImproveCalibrationSchema = exports_external.object({
16017
+ autoTune: exports_external.boolean().optional(),
16018
+ minThreshold: exports_external.number().int().min(0).max(100).optional(),
16019
+ maxThreshold: exports_external.number().int().min(0).max(100).optional(),
16020
+ maxStep: positiveInt.optional(),
16021
+ minSamples: nonNegativeNumber.optional(),
16022
+ targetAcceptRate: exports_external.number().finite().min(0).max(1).optional()
16023
+ }).strict();
16024
+ ImproveExplorationSchema = exports_external.object({
16025
+ enabled: exports_external.boolean().optional(),
16026
+ budgetFraction: exports_external.number().finite().min(0).max(1).optional()
16027
+ }).strict();
16028
+ ImproveSalienceSchema = exports_external.object({
16029
+ outcomeWeightEnabled: exports_external.boolean().optional(),
16030
+ salienceThreshold: exports_external.number().min(0).max(1).optional(),
16031
+ replayBudget: exports_external.number().int().min(0).optional()
16032
+ }).strict();
15695
16033
  ImproveConfigSchema = exports_external.object({
15696
16034
  utilityDecay: ImproveUtilityDecaySchema.optional(),
15697
- eventRetentionDays: nonNegativeNumber.optional()
16035
+ eventRetentionDays: nonNegativeNumber.optional(),
16036
+ calibration: ImproveCalibrationSchema.optional(),
16037
+ exploration: ImproveExplorationSchema.optional(),
16038
+ salience: ImproveSalienceSchema.optional()
15698
16039
  }).strict();
15699
16040
  GRAPH_EXTRACTION_INCLUDE_TYPES_ALLOWED = [
15700
16041
  "memory",
@@ -16077,9 +16418,9 @@ function maybeAutoMigrateConfigFile(configPath, text) {
16077
16418
  "\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"
16078
16419
  ].join(`
16079
16420
  `);
16080
- process.stderr.write(`${banner}
16421
+ process.stderr?.write?.(`${banner}
16081
16422
  `);
16082
- process.stdout.write(`${banner}
16423
+ process.stdout?.write?.(`${banner}
16083
16424
  `);
16084
16425
  } catch (err) {
16085
16426
  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.");
@@ -16332,6 +16673,7 @@ __export(exports_db, {
16332
16673
  getEntryByRef: () => getEntryByRef,
16333
16674
  getEntryById: () => getEntryById,
16334
16675
  getEntriesByDir: () => getEntriesByDir,
16676
+ getEntitiesByEntryIds: () => getEntitiesByEntryIds,
16335
16677
  getEmbeddingCount: () => getEmbeddingCount,
16336
16678
  getEmbeddableEntryCount: () => getEmbeddableEntryCount,
16337
16679
  getDerivedForParent: () => getDerivedForParent,
@@ -16366,9 +16708,7 @@ function openDatabase2(dbPath, options) {
16366
16708
  fs12.mkdirSync(dir, { recursive: true });
16367
16709
  }
16368
16710
  const db = openDatabase(resolvedPath);
16369
- db.exec("PRAGMA journal_mode = WAL");
16370
- db.exec("PRAGMA busy_timeout = 30000");
16371
- db.exec("PRAGMA foreign_keys = ON");
16711
+ applyStandardPragmas(db, { dataDir: dir });
16372
16712
  loadVecExtension(db);
16373
16713
  const resolvedDim = options?.embeddingDim ?? resolveConfiguredEmbeddingDim();
16374
16714
  ensureSchema(db, resolvedDim, { dataDir: dir });
@@ -16389,10 +16729,9 @@ function resolveConfiguredEmbeddingDim() {
16389
16729
  }
16390
16730
  function openExistingDatabase(dbPath) {
16391
16731
  const resolvedPath = dbPath ?? getDbPath();
16732
+ const dir = path11.dirname(resolvedPath);
16392
16733
  const db = openDatabase(resolvedPath);
16393
- db.exec("PRAGMA journal_mode = WAL");
16394
- db.exec("PRAGMA busy_timeout = 30000");
16395
- db.exec("PRAGMA foreign_keys = ON");
16734
+ applyStandardPragmas(db, { dataDir: dir });
16396
16735
  loadVecExtension(db);
16397
16736
  return db;
16398
16737
  }
@@ -16557,6 +16896,7 @@ function ensureSchema(db, embeddingDim, options) {
16557
16896
  CREATE INDEX IF NOT EXISTS idx_llm_cache_updated
16558
16897
  ON llm_enrichment_cache(updated_at);
16559
16898
  `);
16899
+ migrateGraphFilesSchema(db);
16560
16900
  db.exec(`
16561
16901
  CREATE TABLE IF NOT EXISTS graph_meta (
16562
16902
  stash_root TEXT PRIMARY KEY,
@@ -16580,7 +16920,6 @@ function ensureSchema(db, embeddingDim, options) {
16580
16920
  );
16581
16921
 
16582
16922
  CREATE TABLE IF NOT EXISTS graph_files (
16583
- entry_id INTEGER PRIMARY KEY REFERENCES entries(id) ON DELETE CASCADE,
16584
16923
  stash_root TEXT NOT NULL,
16585
16924
  file_path TEXT NOT NULL,
16586
16925
  file_order INTEGER NOT NULL,
@@ -16590,26 +16929,34 @@ function ensureSchema(db, embeddingDim, options) {
16590
16929
  status TEXT NOT NULL DEFAULT 'extracted',
16591
16930
  reason TEXT,
16592
16931
  extraction_run_id TEXT,
16593
- UNIQUE(stash_root, file_path)
16932
+ PRIMARY KEY (stash_root, file_path, body_hash)
16594
16933
  );
16595
16934
 
16935
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_files_path
16936
+ ON graph_files(stash_root, file_path);
16937
+
16596
16938
  CREATE INDEX IF NOT EXISTS idx_graph_files_stash_order
16597
16939
  ON graph_files(stash_root, file_order);
16598
16940
 
16599
16941
  CREATE TABLE IF NOT EXISTS graph_file_entities (
16600
- entry_id INTEGER NOT NULL REFERENCES graph_files(entry_id) ON DELETE CASCADE,
16601
- entity_order INTEGER NOT NULL,
16602
16942
  stash_root TEXT NOT NULL,
16943
+ file_path TEXT NOT NULL,
16944
+ body_hash TEXT NOT NULL,
16945
+ entity_order INTEGER NOT NULL,
16603
16946
  entity_norm TEXT NOT NULL,
16604
16947
  entity TEXT NOT NULL,
16605
- PRIMARY KEY (entry_id, entity_order)
16948
+ PRIMARY KEY (stash_root, file_path, body_hash, entity_order),
16949
+ FOREIGN KEY (stash_root, file_path, body_hash)
16950
+ REFERENCES graph_files(stash_root, file_path, body_hash) ON DELETE CASCADE
16606
16951
  );
16607
16952
 
16608
16953
  CREATE INDEX IF NOT EXISTS idx_graph_file_entities_entity_norm
16609
16954
  ON graph_file_entities(stash_root, entity_norm);
16610
16955
 
16611
16956
  CREATE TABLE IF NOT EXISTS graph_file_relations (
16612
- entry_id INTEGER NOT NULL REFERENCES graph_files(entry_id) ON DELETE CASCADE,
16957
+ stash_root TEXT NOT NULL,
16958
+ file_path TEXT NOT NULL,
16959
+ body_hash TEXT NOT NULL,
16613
16960
  relation_order INTEGER NOT NULL,
16614
16961
  from_entity_norm TEXT NOT NULL,
16615
16962
  from_entity TEXT NOT NULL,
@@ -16617,9 +16964,12 @@ function ensureSchema(db, embeddingDim, options) {
16617
16964
  to_entity TEXT NOT NULL,
16618
16965
  relation_type TEXT,
16619
16966
  confidence REAL,
16620
- PRIMARY KEY (entry_id, relation_order)
16967
+ PRIMARY KEY (stash_root, file_path, body_hash, relation_order),
16968
+ FOREIGN KEY (stash_root, file_path, body_hash)
16969
+ REFERENCES graph_files(stash_root, file_path, body_hash) ON DELETE CASCADE
16621
16970
  );
16622
16971
  `);
16972
+ migrateGraphDataFromLegacy(db);
16623
16973
  db.exec(`
16624
16974
  CREATE TABLE IF NOT EXISTS entries_fts_dirty (
16625
16975
  entry_id INTEGER PRIMARY KEY
@@ -16835,6 +17185,67 @@ function ensureDerivedFromColumn(db) {
16835
17185
  db.exec("CREATE INDEX IF NOT EXISTS idx_entries_derived_from ON entries(derived_from)");
16836
17186
  }, "entries table may not exist on a brand-new DB before CREATE \u2014 caller is responsible");
16837
17187
  }
17188
+ function tableExists(db, name) {
17189
+ const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1").get(name);
17190
+ return row !== undefined && row !== null;
17191
+ }
17192
+ function migrateGraphFilesSchema(db) {
17193
+ bestEffort(() => {
17194
+ const cols = db.prepare("PRAGMA table_info(graph_files)").all();
17195
+ const isLegacyShape = cols.some((c) => c.name === "entry_id");
17196
+ if (!isLegacyShape)
17197
+ return;
17198
+ db.exec("DROP TABLE IF EXISTS graph_file_relations_legacy");
17199
+ db.exec("DROP TABLE IF EXISTS graph_file_entities_legacy");
17200
+ db.exec("DROP TABLE IF EXISTS graph_files_legacy");
17201
+ db.exec("ALTER TABLE graph_files RENAME TO graph_files_legacy");
17202
+ if (tableExists(db, "graph_file_entities")) {
17203
+ db.exec("ALTER TABLE graph_file_entities RENAME TO graph_file_entities_legacy");
17204
+ }
17205
+ if (tableExists(db, "graph_file_relations")) {
17206
+ db.exec("ALTER TABLE graph_file_relations RENAME TO graph_file_relations_legacy");
17207
+ }
17208
+ }, "graph_files may not exist on a brand-new DB before CREATE \u2014 caller is responsible");
17209
+ }
17210
+ function migrateGraphDataFromLegacy(db) {
17211
+ if (!tableExists(db, "graph_files_legacy"))
17212
+ return;
17213
+ let migratedFiles = 0;
17214
+ bestEffort(() => {
17215
+ db.transaction(() => {
17216
+ const res = db.prepare(`INSERT OR IGNORE INTO graph_files
17217
+ (stash_root, file_path, body_hash, file_order, file_type, confidence, status, reason, extraction_run_id)
17218
+ SELECT stash_root, file_path, body_hash, file_order, file_type, confidence, status, reason, extraction_run_id
17219
+ FROM graph_files_legacy
17220
+ WHERE body_hash IS NOT NULL AND body_hash != ''`).run();
17221
+ migratedFiles = Number(res.changes);
17222
+ if (tableExists(db, "graph_file_entities_legacy")) {
17223
+ db.exec(`INSERT OR IGNORE INTO graph_file_entities
17224
+ (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
17225
+ SELECT gf.stash_root, gf.file_path, gf.body_hash, e.entity_order, e.entity_norm, e.entity
17226
+ FROM graph_file_entities_legacy e
17227
+ JOIN graph_files_legacy gf ON gf.entry_id = e.entry_id
17228
+ WHERE gf.body_hash IS NOT NULL AND gf.body_hash != ''`);
17229
+ }
17230
+ if (tableExists(db, "graph_file_relations_legacy")) {
17231
+ db.exec(`INSERT OR IGNORE INTO graph_file_relations
17232
+ (stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence)
17233
+ 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
17234
+ FROM graph_file_relations_legacy r
17235
+ JOIN graph_files_legacy gf ON gf.entry_id = r.entry_id
17236
+ WHERE gf.body_hash IS NOT NULL AND gf.body_hash != ''`);
17237
+ }
17238
+ })();
17239
+ }, "graph data migration is best-effort; legacy tables are dropped regardless below");
17240
+ bestEffort(() => {
17241
+ db.exec("DROP TABLE IF EXISTS graph_file_relations_legacy");
17242
+ db.exec("DROP TABLE IF EXISTS graph_file_entities_legacy");
17243
+ db.exec("DROP TABLE IF EXISTS graph_files_legacy");
17244
+ }, "drop legacy graph tables after migration");
17245
+ if (migratedFiles > 0) {
17246
+ warn(`[akm] graph index re-keyed (#624): migrated ${migratedFiles} extracted file(s) to the new schema \u2014 no re-extraction needed. Index + embeddings untouched.`);
17247
+ }
17248
+ }
16838
17249
  function getDerivedForParent(db, parentRef) {
16839
17250
  if (!parentRef)
16840
17251
  return null;
@@ -16924,6 +17335,25 @@ function deleteRelatedRows(db, ids) {
16924
17335
  bestEffort(() => db.prepare(`DELETE FROM utility_scores_scoped WHERE entry_id IN (${placeholders})`).run(...chunk), "delete utility_scores_scoped for entries");
16925
17336
  bestEffort(() => db.prepare(`DELETE FROM usage_events WHERE entry_id IN (${placeholders})`).run(...chunk), "delete usage_events for entries");
16926
17337
  }
17338
+ const affectedStashRoots = new Set;
17339
+ for (let i = 0;i < numericIds.length; i += SQLITE_CHUNK_SIZE) {
17340
+ const chunk = numericIds.slice(i, i + SQLITE_CHUNK_SIZE);
17341
+ const placeholders = chunk.map(() => "?").join(",");
17342
+ bestEffort(() => {
17343
+ const rows = db.prepare(`SELECT DISTINCT stash_dir FROM entries WHERE id IN (${placeholders})`).all(...chunk);
17344
+ for (const row of rows) {
17345
+ if (row.stash_dir)
17346
+ affectedStashRoots.add(row.stash_dir);
17347
+ }
17348
+ }, "resolve stash roots for graph_meta recompute");
17349
+ }
17350
+ for (const stashRoot of affectedStashRoots) {
17351
+ bestEffort(() => db.prepare(`UPDATE graph_meta
17352
+ SET extracted_files = (SELECT COUNT(*) FROM graph_files WHERE stash_root = ?),
17353
+ entity_count = (SELECT COUNT(*) FROM graph_file_entities WHERE stash_root = ?),
17354
+ relation_count = (SELECT COUNT(*) FROM graph_file_relations WHERE stash_root = ?)
17355
+ WHERE stash_root = ?`).run(stashRoot, stashRoot, stashRoot, stashRoot), "sync graph_meta counts after entries delete");
17356
+ }
16927
17357
  }
16928
17358
  function deleteEntriesByIds(db, ids) {
16929
17359
  if (ids.length === 0)
@@ -17053,17 +17483,17 @@ function searchBlobVec(db, queryEmbedding, k) {
17053
17483
  return [];
17054
17484
  }
17055
17485
  }
17056
- function searchFts(db, query, limit, entryType) {
17486
+ function searchFts(db, query, limit, entryType, excludeTypes) {
17057
17487
  const ftsQuery = sanitizeFtsQuery(query);
17058
17488
  if (!ftsQuery)
17059
17489
  return [];
17060
- const exactResults = runFtsQuery(db, ftsQuery, limit, entryType);
17490
+ const exactResults = runFtsQuery(db, ftsQuery, limit, entryType, excludeTypes);
17061
17491
  if (exactResults.length > 0)
17062
17492
  return exactResults;
17063
17493
  const prefixQuery = buildPrefixQuery(ftsQuery);
17064
17494
  if (!prefixQuery)
17065
17495
  return [];
17066
- return runFtsQuery(db, prefixQuery, limit, entryType);
17496
+ return runFtsQuery(db, prefixQuery, limit, entryType, excludeTypes);
17067
17497
  }
17068
17498
  function buildPrefixQuery(ftsQuery) {
17069
17499
  const tokens = ftsQuery.split(/\s+/).filter(Boolean);
@@ -17079,9 +17509,10 @@ function buildPrefixQuery(ftsQuery) {
17079
17509
  return null;
17080
17510
  return prefixTokens.join(" ");
17081
17511
  }
17082
- function runFtsQuery(db, ftsQuery, limit, entryType) {
17512
+ function runFtsQuery(db, ftsQuery, limit, entryType, excludeTypes) {
17083
17513
  let sql;
17084
17514
  let params;
17515
+ const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
17085
17516
  if (entryType && entryType !== "any") {
17086
17517
  sql = `
17087
17518
  SELECT e.id, e.file_path AS filePath, e.entry_json, e.search_text AS searchText,
@@ -17095,16 +17526,18 @@ function runFtsQuery(db, ftsQuery, limit, entryType) {
17095
17526
  `;
17096
17527
  params = [ftsQuery, entryType, limit];
17097
17528
  } else {
17529
+ const excludeClause = excludes.length > 0 ? `AND e.entry_type NOT IN (${excludes.map(() => "?").join(", ")})` : "";
17098
17530
  sql = `
17099
17531
  SELECT e.id, e.file_path AS filePath, e.entry_json, e.search_text AS searchText,
17100
17532
  bm25(entries_fts, 0, 10.0, 5.0, 3.0, 2.0, 1.0) AS bm25Score
17101
17533
  FROM entries_fts f
17102
17534
  JOIN entries e ON e.id = f.entry_id
17103
17535
  WHERE entries_fts MATCH ?
17536
+ ${excludeClause}
17104
17537
  ORDER BY bm25Score, e.id ASC
17105
17538
  LIMIT ?
17106
17539
  `;
17107
- params = [ftsQuery, limit];
17540
+ params = [ftsQuery, ...excludes, limit];
17108
17541
  }
17109
17542
  try {
17110
17543
  const rows = db.prepare(sql).all(...params);
@@ -17160,12 +17593,16 @@ function parseEntryRows(rows, context) {
17160
17593
  }
17161
17594
  return entries;
17162
17595
  }
17163
- function getAllEntries(db, entryType) {
17596
+ function getAllEntries(db, entryType, excludeTypes) {
17164
17597
  let sql;
17165
17598
  let params;
17599
+ const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
17166
17600
  if (entryType && entryType !== "any") {
17167
17601
  sql = "SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries WHERE entry_type = ?";
17168
17602
  params = [entryType];
17603
+ } else if (excludes.length > 0) {
17604
+ 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(", ")})`;
17605
+ params = [...excludes];
17169
17606
  } else {
17170
17607
  sql = "SELECT id, entry_key, dir_path, file_path, stash_dir, entry_json, search_text FROM entries";
17171
17608
  params = [];
@@ -17173,6 +17610,33 @@ function getAllEntries(db, entryType) {
17173
17610
  const rows = db.prepare(sql).all(...params);
17174
17611
  return parseEntryRows(rows, "getAllEntries");
17175
17612
  }
17613
+ function getEntitiesByEntryIds(db, entryIds) {
17614
+ const result = new Map;
17615
+ if (entryIds.length === 0)
17616
+ return result;
17617
+ for (let i = 0;i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
17618
+ const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
17619
+ const placeholders = chunk.map(() => "?").join(", ");
17620
+ const rows = db.prepare(`SELECT e.id AS entry_id, gfe.entity_norm AS entity_norm
17621
+ FROM entries e
17622
+ JOIN graph_files gf
17623
+ ON gf.stash_root = e.stash_dir AND gf.file_path = e.file_path
17624
+ JOIN graph_file_entities gfe
17625
+ ON gfe.stash_root = gf.stash_root
17626
+ AND gfe.file_path = gf.file_path
17627
+ AND gfe.body_hash = gf.body_hash
17628
+ WHERE e.id IN (${placeholders})
17629
+ ORDER BY e.id, gfe.entity_order`).all(...chunk);
17630
+ for (const row of rows) {
17631
+ const list = result.get(row.entry_id);
17632
+ if (list)
17633
+ list.push(row.entity_norm);
17634
+ else
17635
+ result.set(row.entry_id, [row.entity_norm]);
17636
+ }
17637
+ }
17638
+ return result;
17639
+ }
17176
17640
  function findEntryIdByRef(db, ref) {
17177
17641
  const parsed = parseAssetRef(ref);
17178
17642
  const nameVariants = [parsed.name];
@@ -17567,7 +18031,7 @@ function collectTagSetFromEntries(db, entryType) {
17567
18031
  }
17568
18032
  return tags;
17569
18033
  }
17570
- 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;
18034
+ 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;
17571
18035
  var init_db = __esm(() => {
17572
18036
  init_asset_ref();
17573
18037
  init_best_effort();
@@ -17577,6 +18041,7 @@ var init_db = __esm(() => {
17577
18041
  init_types();
17578
18042
  init_runtime();
17579
18043
  init_database();
18044
+ init_sqlite_pragmas();
17580
18045
  init_db_backup();
17581
18046
  vecStatus = new WeakMap;
17582
18047
  vecInitWarnedDbs = new WeakSet;
@@ -17586,13 +18051,13 @@ var init_db = __esm(() => {
17586
18051
  // src/indexer/db/graph-db.ts
17587
18052
  var exports_graph_db = {};
17588
18053
  __export(exports_graph_db, {
17589
- resolveEntryIdForPath: () => resolveEntryIdForPath,
17590
18054
  replaceStoredGraph: () => replaceStoredGraph,
17591
18055
  loadStoredGraphSnapshot: () => loadStoredGraphSnapshot,
17592
18056
  loadStoredGraphMeta: () => loadStoredGraphMeta,
17593
18057
  loadGraphMetaOnly: () => loadGraphMetaOnly,
17594
18058
  loadGraphFilesOnly: () => loadGraphFilesOnly,
17595
- loadGraphEntitiesByEntry: () => loadGraphEntitiesByEntry,
18059
+ loadGraphEntitiesByPath: () => loadGraphEntitiesByPath,
18060
+ hasGraphData: () => hasGraphData,
17596
18061
  deleteStoredGraph: () => deleteStoredGraph
17597
18062
  });
17598
18063
  import fs13 from "fs";
@@ -17615,17 +18080,6 @@ function uniqueSorted(values) {
17615
18080
  function normalizeEntity(value) {
17616
18081
  return value.trim().toLowerCase();
17617
18082
  }
17618
- function resolveEntryIdForPath(db, stashRoot, filePath) {
17619
- try {
17620
- const row = db.prepare("SELECT id FROM entries WHERE stash_dir = ? AND file_path = ? LIMIT 1").get(stashRoot, filePath);
17621
- if (row)
17622
- return row.id;
17623
- const fallback = db.prepare("SELECT id FROM entries WHERE file_path = ? LIMIT 1").get(filePath);
17624
- return fallback?.id ?? null;
17625
- } catch {
17626
- return null;
17627
- }
17628
- }
17629
18083
  function replaceStoredGraph(db, graph) {
17630
18084
  const upsertMeta = db.prepare(`INSERT INTO graph_meta (
17631
18085
  stash_root,
@@ -17665,21 +18119,21 @@ function replaceStoredGraph(db, graph) {
17665
18119
  cache_misses = excluded.cache_misses,
17666
18120
  truncation_count = excluded.truncation_count,
17667
18121
  failure_count = excluded.failure_count`);
17668
- const selectExisting = db.prepare("SELECT entry_id, file_path, body_hash FROM graph_files WHERE stash_root = ?");
17669
- const deleteFile = db.prepare("DELETE FROM graph_files WHERE entry_id = ?");
17670
- const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE entry_id = ?");
17671
- const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE entry_id = ?");
18122
+ const selectExisting = db.prepare("SELECT file_path, body_hash, file_order FROM graph_files WHERE stash_root = ?");
18123
+ const deleteFile = db.prepare("DELETE FROM graph_files WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
18124
+ const deleteEntities = db.prepare("DELETE FROM graph_file_entities WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
18125
+ const deleteRelations = db.prepare("DELETE FROM graph_file_relations WHERE stash_root = ? AND file_path = ? AND body_hash = ?");
17672
18126
  const insertFile = db.prepare(`INSERT INTO graph_files (
17673
- entry_id, stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
17674
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
18127
+ stash_root, file_path, file_order, file_type, body_hash, confidence, status, reason, extraction_run_id
18128
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
17675
18129
  const updateFileMeta = db.prepare(`UPDATE graph_files
17676
18130
  SET file_order = ?, file_type = ?, confidence = ?, status = ?, reason = ?, extraction_run_id = ?
17677
- WHERE entry_id = ?`);
17678
- const insertEntity = db.prepare(`INSERT INTO graph_file_entities (entry_id, entity_order, stash_root, entity_norm, entity)
17679
- VALUES (?, ?, ?, ?, ?)`);
18131
+ WHERE stash_root = ? AND file_path = ? AND body_hash = ?`);
18132
+ const insertEntity = db.prepare(`INSERT INTO graph_file_entities (stash_root, file_path, body_hash, entity_order, entity_norm, entity)
18133
+ VALUES (?, ?, ?, ?, ?, ?)`);
17680
18134
  const insertRelation = db.prepare(`INSERT INTO graph_file_relations (
17681
- entry_id, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
17682
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
18135
+ stash_root, file_path, body_hash, relation_order, from_entity_norm, from_entity, to_entity_norm, to_entity, relation_type, confidence
18136
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
17683
18137
  const quality = graph.quality;
17684
18138
  const telemetry = graph.telemetry;
17685
18139
  db.transaction(() => {
@@ -17688,44 +18142,35 @@ function replaceStoredGraph(db, graph) {
17688
18142
  const existingByPath = new Map;
17689
18143
  for (const row of existingRows)
17690
18144
  existingByPath.set(row.file_path, row);
17691
- let orphanCount = 0;
17692
- const presentEntryIds = new Set;
18145
+ const presentPaths = new Set;
17693
18146
  for (const [fileOrder, node] of graph.files.entries()) {
17694
18147
  const bodyHash = node.bodyHash && node.bodyHash.length > 0 ? node.bodyHash : "";
17695
- const entryId = resolveEntryIdForPath(db, graph.stashRoot, node.path);
17696
- if (entryId == null) {
17697
- orphanCount += 1;
17698
- continue;
17699
- }
17700
- presentEntryIds.add(entryId);
18148
+ presentPaths.add(node.path);
17701
18149
  const existing = existingByPath.get(node.path);
17702
- if (existing && existing.entry_id === entryId && existing.body_hash === bodyHash) {
17703
- 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);
18150
+ if (existing && existing.body_hash === bodyHash) {
18151
+ 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);
17704
18152
  continue;
17705
18153
  }
17706
18154
  if (existing) {
17707
- deleteEntities.run(existing.entry_id);
17708
- deleteRelations.run(existing.entry_id);
17709
- deleteFile.run(existing.entry_id);
18155
+ deleteEntities.run(graph.stashRoot, existing.file_path, existing.body_hash);
18156
+ deleteRelations.run(graph.stashRoot, existing.file_path, existing.body_hash);
18157
+ deleteFile.run(graph.stashRoot, existing.file_path, existing.body_hash);
17710
18158
  }
17711
- 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);
18159
+ 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);
17712
18160
  for (const [entityOrder, entity] of node.entities.entries()) {
17713
- insertEntity.run(entryId, entityOrder, graph.stashRoot, normalizeEntity(entity), entity);
18161
+ insertEntity.run(graph.stashRoot, node.path, bodyHash, entityOrder, normalizeEntity(entity), entity);
17714
18162
  }
17715
18163
  for (const [relationOrder, relation] of node.relations.entries()) {
17716
- insertRelation.run(entryId, relationOrder, normalizeEntity(relation.from), relation.from, normalizeEntity(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
18164
+ insertRelation.run(graph.stashRoot, node.path, bodyHash, relationOrder, normalizeEntity(relation.from), relation.from, normalizeEntity(relation.to), relation.to, relation.type ?? null, relation.confidence ?? null);
17717
18165
  }
17718
18166
  }
17719
18167
  for (const row of existingRows) {
17720
- if (!presentEntryIds.has(row.entry_id)) {
17721
- deleteEntities.run(row.entry_id);
17722
- deleteRelations.run(row.entry_id);
17723
- deleteFile.run(row.entry_id);
18168
+ if (!presentPaths.has(row.file_path)) {
18169
+ deleteEntities.run(graph.stashRoot, row.file_path, row.body_hash);
18170
+ deleteRelations.run(graph.stashRoot, row.file_path, row.body_hash);
18171
+ deleteFile.run(graph.stashRoot, row.file_path, row.body_hash);
17724
18172
  }
17725
18173
  }
17726
- if (orphanCount > 0) {
17727
- warn(`[graph] replaceStoredGraph: skipped ${orphanCount} file(s) with no resolvable entry under ${graph.stashRoot}.`);
17728
- }
17729
18174
  })();
17730
18175
  }
17731
18176
  function deleteStoredGraph(db, stashPath) {
@@ -17734,6 +18179,14 @@ function deleteStoredGraph(db, stashPath) {
17734
18179
  db.prepare("DELETE FROM graph_meta WHERE stash_root = ?").run(stashPath);
17735
18180
  })();
17736
18181
  }
18182
+ function hasGraphData(db, stashRoot, filePath) {
18183
+ try {
18184
+ const row = db.prepare("SELECT 1 AS present FROM graph_files WHERE stash_root = ? AND file_path = ? LIMIT 1").get(stashRoot, filePath);
18185
+ return row !== undefined;
18186
+ } catch {
18187
+ return false;
18188
+ }
18189
+ }
17737
18190
  function loadGraphMetaOnly(stashPath, db) {
17738
18191
  return loadStoredGraphMeta(stashPath, db);
17739
18192
  }
@@ -17741,12 +18194,11 @@ function loadGraphFilesOnly(stashPath, db) {
17741
18194
  try {
17742
18195
  return withReadableGraphDb(db, (readDb) => {
17743
18196
  try {
17744
- const rows = readDb.prepare(`SELECT entry_id, file_path, file_type, body_hash, confidence, status, reason
18197
+ const rows = readDb.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason
17745
18198
  FROM graph_files
17746
18199
  WHERE stash_root = ?
17747
18200
  ORDER BY file_order`).all(stashPath);
17748
18201
  return rows.map((row) => ({
17749
- entryId: row.entry_id,
17750
18202
  path: row.file_path,
17751
18203
  type: row.file_type,
17752
18204
  bodyHash: row.body_hash,
@@ -17763,9 +18215,11 @@ function loadGraphFilesOnly(stashPath, db) {
17763
18215
  return [];
17764
18216
  }
17765
18217
  }
17766
- function loadGraphEntitiesByEntry(db, entryId) {
18218
+ function loadGraphEntitiesByPath(db, stashRoot, filePath, bodyHash) {
17767
18219
  try {
17768
- const rows = db.prepare("SELECT entity FROM graph_file_entities WHERE entry_id = ? ORDER BY entity_order").all(entryId);
18220
+ const rows = db.prepare(`SELECT entity FROM graph_file_entities
18221
+ WHERE stash_root = ? AND file_path = ? AND body_hash = ?
18222
+ ORDER BY entity_order`).all(stashRoot, filePath, bodyHash);
17769
18223
  return rows.map((r) => r.entity);
17770
18224
  } catch {
17771
18225
  return [];
@@ -17840,23 +18294,28 @@ function loadStoredGraphSnapshot(stashPath, db) {
17840
18294
  if (!meta)
17841
18295
  return null;
17842
18296
  try {
17843
- const fileRows = readDb.prepare(`SELECT entry_id, file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
18297
+ const fileRows = readDb.prepare(`SELECT file_path, file_type, body_hash, confidence, status, reason, extraction_run_id
17844
18298
  FROM graph_files
17845
18299
  WHERE stash_root = ?
17846
18300
  ORDER BY file_order`).all(stashPath);
17847
- const entityRows = readDb.prepare(`SELECT gfe.entry_id AS entry_id, gf.file_path AS file_path, gfe.entity AS entity
18301
+ const entityRows = readDb.prepare(`SELECT gf.file_path AS file_path, gfe.entity AS entity
17848
18302
  FROM graph_file_entities gfe
17849
- JOIN graph_files gf ON gf.entry_id = gfe.entry_id
18303
+ JOIN graph_files gf
18304
+ ON gf.stash_root = gfe.stash_root
18305
+ AND gf.file_path = gfe.file_path
18306
+ AND gf.body_hash = gfe.body_hash
17850
18307
  WHERE gf.stash_root = ?
17851
18308
  ORDER BY gf.file_order, gfe.entity_order`).all(stashPath);
17852
- const relationRows = readDb.prepare(`SELECT gfr.entry_id AS entry_id,
17853
- gf.file_path AS file_path,
18309
+ const relationRows = readDb.prepare(`SELECT gf.file_path AS file_path,
17854
18310
  gfr.from_entity AS from_entity,
17855
18311
  gfr.to_entity AS to_entity,
17856
18312
  gfr.relation_type AS relation_type,
17857
18313
  gfr.confidence AS confidence
17858
18314
  FROM graph_file_relations gfr
17859
- JOIN graph_files gf ON gf.entry_id = gfr.entry_id
18315
+ JOIN graph_files gf
18316
+ ON gf.stash_root = gfr.stash_root
18317
+ AND gf.file_path = gfr.file_path
18318
+ AND gf.body_hash = gfr.body_hash
17860
18319
  WHERE gf.stash_root = ?
17861
18320
  ORDER BY gf.file_order, gfr.relation_order`).all(stashPath);
17862
18321
  const entitiesByPath = new Map;
@@ -17915,7 +18374,6 @@ function loadStoredGraphSnapshot(stashPath, db) {
17915
18374
  var init_graph_db = __esm(() => {
17916
18375
  init_errors();
17917
18376
  init_paths();
17918
- init_warn();
17919
18377
  init_db();
17920
18378
  });
17921
18379