@swarmvaultai/engine 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  uniqueBy,
24
24
  writeFileIfChanged,
25
25
  writeJsonFile
26
- } from "./chunk-G2TH6ZTA.js";
26
+ } from "./chunk-4MSSM2GH.js";
27
27
  import {
28
28
  estimatePageTokens,
29
29
  estimateTokens,
@@ -727,7 +727,7 @@ function renderPromotionSessionMarkdown(decisions, promotedPageIds, options) {
727
727
 
728
728
  // src/consolidate.ts
729
729
  import fs7 from "fs/promises";
730
- import path7 from "path";
730
+ import path8 from "path";
731
731
  import matter4 from "gray-matter";
732
732
 
733
733
  // src/logs.ts
@@ -5625,7 +5625,9 @@ function candidateExtensionsFor(language) {
5625
5625
  case "jsx":
5626
5626
  case "typescript":
5627
5627
  case "tsx":
5628
- return [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"];
5628
+ return [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs", ".vue"];
5629
+ case "vue":
5630
+ return [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs", ".vue"];
5629
5631
  case "bash":
5630
5632
  return [".sh", ".bash", ".zsh"];
5631
5633
  case "python":
@@ -6017,6 +6019,7 @@ function findImportCandidates(manifest, codeImport, lookup) {
6017
6019
  case "jsx":
6018
6020
  case "typescript":
6019
6021
  case "tsx":
6022
+ case "vue":
6020
6023
  return repoRelativePath && isRelativeSpecifier(codeImport.specifier) ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
6021
6024
  case "python":
6022
6025
  if (repoRelativePath && codeImport.specifier.startsWith(".")) {
@@ -6084,6 +6087,7 @@ function importLooksLocal(manifest, codeImport, candidates) {
6084
6087
  case "jsx":
6085
6088
  case "typescript":
6086
6089
  case "tsx":
6090
+ case "vue":
6087
6091
  return isRelativeSpecifier(codeImport.specifier);
6088
6092
  case "python":
6089
6093
  return codeImport.specifier.startsWith(".");
@@ -6142,9 +6146,128 @@ function enrichResolvedCodeImports(manifest, analysis, artifact) {
6142
6146
  function escapeRegExp2(value) {
6143
6147
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6144
6148
  }
6149
+ var VUE_SCRIPT_BLOCK_REGEX = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
6150
+ function vueScriptLanguageFromAttributes(attributes) {
6151
+ const langMatch = attributes.match(/\blang\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i);
6152
+ const lang = (langMatch?.[1] ?? langMatch?.[2] ?? langMatch?.[3] ?? "").trim().toLowerCase();
6153
+ const hasJsx = /\bjsx\b/.test(attributes) || lang === "tsx" || lang === "jsx";
6154
+ if (lang === "ts" || lang === "typescript" || lang === "tsx") {
6155
+ return hasJsx ? "tsx" : "typescript";
6156
+ }
6157
+ if (lang === "js" || lang === "javascript" || lang === "jsx") {
6158
+ return hasJsx ? "jsx" : "javascript";
6159
+ }
6160
+ return "typescript";
6161
+ }
6162
+ function extractVueScriptBlocks(source) {
6163
+ const blocks = [];
6164
+ VUE_SCRIPT_BLOCK_REGEX.lastIndex = 0;
6165
+ let match = VUE_SCRIPT_BLOCK_REGEX.exec(source);
6166
+ while (match !== null) {
6167
+ const attributes = match[1] ?? "";
6168
+ const content = match[2] ?? "";
6169
+ const openTagEnd = match.index + match[0].indexOf(">") + 1;
6170
+ const lineOffset = source.slice(0, openTagEnd).split("\n").length - 1;
6171
+ const setup = /\bsetup\b/.test(attributes);
6172
+ blocks.push({
6173
+ content,
6174
+ lineOffset,
6175
+ language: vueScriptLanguageFromAttributes(attributes),
6176
+ setup
6177
+ });
6178
+ match = VUE_SCRIPT_BLOCK_REGEX.exec(source);
6179
+ }
6180
+ return blocks;
6181
+ }
6182
+ function mergeVueScriptAnalyses(outer, inners) {
6183
+ if (inners.length === 0) {
6184
+ return outer;
6185
+ }
6186
+ const mergedImports = [...outer.code.imports];
6187
+ const seenImportKeys = new Set(
6188
+ mergedImports.map((imp) => `${imp.specifier}\0${imp.reExport ? "re" : "im"}\0${imp.isTypeOnly ? "t" : "v"}`)
6189
+ );
6190
+ const mergedSymbols = [...outer.code.symbols];
6191
+ const seenSymbolKeys = new Set(mergedSymbols.map((symbol) => `${symbol.name}\0${symbol.kind}`));
6192
+ const mergedExports = [...outer.code.exports];
6193
+ const seenExports = new Set(mergedExports);
6194
+ const mergedDiagnostics = [...outer.code.diagnostics];
6195
+ const mergedDependencies = new Set(outer.code.dependencies);
6196
+ const mergedRationales = [...outer.rationales];
6197
+ const seenRationaleKeys = new Set(mergedRationales.map((r) => `${r.symbolName ?? ""}:${r.text.toLowerCase()}`));
6198
+ for (const inner of inners) {
6199
+ for (const imp of inner.code.imports) {
6200
+ const key = `${imp.specifier}\0${imp.reExport ? "re" : "im"}\0${imp.isTypeOnly ? "t" : "v"}`;
6201
+ if (!seenImportKeys.has(key)) {
6202
+ mergedImports.push(imp);
6203
+ seenImportKeys.add(key);
6204
+ }
6205
+ }
6206
+ for (const symbol of inner.code.symbols) {
6207
+ const key = `${symbol.name}\0${symbol.kind}`;
6208
+ if (!seenSymbolKeys.has(key)) {
6209
+ mergedSymbols.push(symbol);
6210
+ seenSymbolKeys.add(key);
6211
+ }
6212
+ }
6213
+ for (const label of inner.code.exports) {
6214
+ if (!seenExports.has(label)) {
6215
+ mergedExports.push(label);
6216
+ seenExports.add(label);
6217
+ }
6218
+ }
6219
+ for (const diag of inner.code.diagnostics) {
6220
+ mergedDiagnostics.push({
6221
+ ...diag,
6222
+ line: diag.line + inner.lineOffset
6223
+ });
6224
+ }
6225
+ for (const dep of inner.code.dependencies) {
6226
+ mergedDependencies.add(dep);
6227
+ }
6228
+ for (const rationale of inner.rationales) {
6229
+ const key = `${rationale.symbolName ?? ""}:${rationale.text.toLowerCase()}`;
6230
+ if (!seenRationaleKeys.has(key)) {
6231
+ mergedRationales.push(rationale);
6232
+ seenRationaleKeys.add(key);
6233
+ }
6234
+ }
6235
+ }
6236
+ return {
6237
+ code: {
6238
+ ...outer.code,
6239
+ imports: mergedImports,
6240
+ dependencies: Array.from(mergedDependencies),
6241
+ symbols: mergedSymbols,
6242
+ exports: mergedExports,
6243
+ diagnostics: mergedDiagnostics
6244
+ },
6245
+ rationales: mergedRationales
6246
+ };
6247
+ }
6248
+ async function analyzeVueSource(manifest, extractedText) {
6249
+ const outer = await analyzeTreeSitterCode(manifest, extractedText, "vue");
6250
+ const scriptBlocks = extractVueScriptBlocks(extractedText);
6251
+ if (scriptBlocks.length === 0) {
6252
+ return outer;
6253
+ }
6254
+ const innerResults = [];
6255
+ for (const block of scriptBlocks) {
6256
+ if (!block.content.trim()) {
6257
+ continue;
6258
+ }
6259
+ const innerManifest = {
6260
+ ...manifest,
6261
+ language: block.language
6262
+ };
6263
+ const analyzed = analyzeTypeScriptLikeCode(innerManifest, block.content);
6264
+ innerResults.push({ code: analyzed.code, rationales: analyzed.rationales, lineOffset: block.lineOffset });
6265
+ }
6266
+ return mergeVueScriptAnalyses(outer, innerResults);
6267
+ }
6145
6268
  async function analyzeCodeSource(manifest, extractedText, schemaHash) {
6146
6269
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
6147
- const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
6270
+ const { code, rationales } = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : language === "vue" ? await analyzeVueSource(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
6148
6271
  return {
6149
6272
  analysisVersion: 7,
6150
6273
  sourceId: manifest.sourceId,
@@ -6548,6 +6671,7 @@ function filterGraphBySourceClass(graph, sourceClass) {
6548
6671
  }
6549
6672
 
6550
6673
  // src/graph-enrichment.ts
6674
+ var DEFAULT_SIMILARITY_IDF_FLOOR = 0.5;
6551
6675
  var STOPWORDS = /* @__PURE__ */ new Set([
6552
6676
  "about",
6553
6677
  "after",
@@ -6629,21 +6753,70 @@ function hasDistinctScope(left, right) {
6629
6753
  const rightOnly = [...rightSources].some((sourceId) => !leftSources.has(sourceId));
6630
6754
  return leftOnly || rightOnly;
6631
6755
  }
6632
- function supportCount(values) {
6633
- return values?.size ?? 0;
6634
- }
6635
- function similarityScore(reasons) {
6636
- const concept = supportCount(reasons.get("shared_concept"));
6637
- const entity = supportCount(reasons.get("shared_entity"));
6638
- const symbol = supportCount(reasons.get("shared_symbol"));
6639
- const rationale = supportCount(reasons.get("shared_rationale_theme"));
6640
- const sourceType = supportCount(reasons.get("shared_source_type"));
6641
- const tag = supportCount(reasons.get("shared_tag"));
6642
- const categoryCount = [...reasons.keys()].length;
6643
- const weighted = (concept ? 0.46 + Math.min(0.12, (concept - 1) * 0.04) : 0) + (entity ? 0.34 + Math.min(0.1, (entity - 1) * 0.03) : 0) + (symbol ? 0.24 + Math.min(0.08, (symbol - 1) * 0.02) : 0) + (rationale ? 0.18 + Math.min(0.08, (rationale - 1) * 0.03) : 0) + (sourceType ? 0.1 : 0) + (tag ? 0.12 + Math.min(0.04, (tag - 1) * 0.02) : 0);
6644
- const categoryBonus = categoryCount >= 3 ? 0.08 : categoryCount === 2 ? 0.04 : 0;
6756
+ var CATEGORY_BASE_WEIGHT = {
6757
+ shared_concept: 0.46,
6758
+ shared_entity: 0.34,
6759
+ shared_symbol: 0.24,
6760
+ shared_rationale_theme: 0.18,
6761
+ shared_source_type: 0.1,
6762
+ shared_tag: 0.12
6763
+ };
6764
+ function buildIdfTable(featureDocFrequency, documentCount) {
6765
+ const idf = /* @__PURE__ */ new Map();
6766
+ const safeDocCount = Math.max(1, documentCount);
6767
+ for (const [reason, values] of featureDocFrequency.entries()) {
6768
+ const inner = /* @__PURE__ */ new Map();
6769
+ for (const [value, df] of values.entries()) {
6770
+ inner.set(value, Math.log((safeDocCount + 1) / (df + 1)) + 1);
6771
+ }
6772
+ idf.set(reason, inner);
6773
+ }
6774
+ return idf;
6775
+ }
6776
+ function similarityScore(reasons, idfTable, floor) {
6777
+ let weighted = 0;
6778
+ let activeCategories = 0;
6779
+ for (const [reason, values] of reasons.entries()) {
6780
+ const idfByValue = idfTable.get(reason);
6781
+ let categoryContribution = 0;
6782
+ let hitCount = 0;
6783
+ for (const value of values) {
6784
+ const idfValue = idfByValue?.get(value) ?? 0;
6785
+ if (idfValue < floor) {
6786
+ continue;
6787
+ }
6788
+ hitCount++;
6789
+ const base = CATEGORY_BASE_WEIGHT[reason] ?? 0.1;
6790
+ if (hitCount === 1) {
6791
+ categoryContribution += Math.min(base * 2, base * idfValue);
6792
+ } else {
6793
+ categoryContribution += Math.min(0.12, 0.04 * idfValue);
6794
+ }
6795
+ }
6796
+ if (categoryContribution > 0) {
6797
+ weighted += categoryContribution;
6798
+ activeCategories++;
6799
+ }
6800
+ }
6801
+ const categoryBonus = activeCategories >= 3 ? 0.08 : activeCategories === 2 ? 0.04 : 0;
6645
6802
  return Math.min(0.96, weighted + categoryBonus);
6646
6803
  }
6804
+ function pruneReasonsByIdf(reasons, idfTable, floor) {
6805
+ const pruned = /* @__PURE__ */ new Map();
6806
+ for (const [reason, values] of reasons.entries()) {
6807
+ const idfByValue = idfTable.get(reason);
6808
+ const keep = /* @__PURE__ */ new Set();
6809
+ for (const value of values) {
6810
+ if ((idfByValue?.get(value) ?? 0) >= floor) {
6811
+ keep.add(value);
6812
+ }
6813
+ }
6814
+ if (keep.size > 0) {
6815
+ pruned.set(reason, keep);
6816
+ }
6817
+ }
6818
+ return pruned;
6819
+ }
6647
6820
  function describeSimilarityReasons(reasons) {
6648
6821
  if (!reasons?.length) {
6649
6822
  return "This link is inferred from multiple shared graph features.";
@@ -6702,11 +6875,27 @@ function nodeContexts(nodes, manifests, analyses) {
6702
6875
  return { node, featureValues: features };
6703
6876
  }).filter((context) => context.featureValues.size > 0);
6704
6877
  }
6705
- function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses) {
6878
+ function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses, options) {
6879
+ const idfFloor = options?.similarityIdfFloor ?? DEFAULT_SIMILARITY_IDF_FLOOR;
6880
+ const similarityEdgeCap = Math.max(0, options?.similarityEdgeCap ?? Number.POSITIVE_INFINITY);
6706
6881
  const contexts = nodeContexts(nodes, manifests, analyses);
6707
6882
  const contextsById = new Map(contexts.map((context) => [context.node.id, context]));
6708
6883
  const directPairs = new Set(edges.map((edge) => pairKey(edge.source, edge.target)));
6709
6884
  const pairReasons = /* @__PURE__ */ new Map();
6885
+ const featureDocFrequency = /* @__PURE__ */ new Map();
6886
+ for (const context of contexts) {
6887
+ for (const [reason, values] of context.featureValues.entries()) {
6888
+ let inner = featureDocFrequency.get(reason);
6889
+ if (!inner) {
6890
+ inner = /* @__PURE__ */ new Map();
6891
+ featureDocFrequency.set(reason, inner);
6892
+ }
6893
+ for (const value of values) {
6894
+ inner.set(value, (inner.get(value) ?? 0) + 1);
6895
+ }
6896
+ }
6897
+ }
6898
+ const idfTable = buildIdfTable(featureDocFrequency, contexts.length);
6710
6899
  for (const reason of ["shared_concept", "shared_entity", "shared_symbol", "shared_rationale_theme", "shared_source_type"]) {
6711
6900
  const buckets = /* @__PURE__ */ new Map();
6712
6901
  for (const context of contexts) {
@@ -6749,20 +6938,24 @@ function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses) {
6749
6938
  }
6750
6939
  }
6751
6940
  }
6752
- return [...pairReasons.entries()].flatMap(([key, reasons]) => {
6941
+ const candidates = [...pairReasons.entries()].flatMap(([key, reasons]) => {
6753
6942
  const [leftId, rightId] = key.split("|");
6754
6943
  const left = contextsById.get(leftId)?.node;
6755
6944
  const right = contextsById.get(rightId)?.node;
6756
6945
  if (!left || !right) {
6757
6946
  return [];
6758
6947
  }
6759
- const confidence = similarityScore(reasons);
6948
+ const prunedReasons = pruneReasonsByIdf(reasons, idfTable, idfFloor);
6949
+ if (prunedReasons.size === 0) {
6950
+ return [];
6951
+ }
6952
+ const confidence = similarityScore(prunedReasons, idfTable, idfFloor);
6760
6953
  if (confidence < 0.5) {
6761
6954
  return [];
6762
6955
  }
6763
6956
  return [
6764
6957
  {
6765
- id: `similar:${sha256(`${left.id}|${right.id}|${[...reasons.keys()].sort().join(",")}`).slice(0, 16)}`,
6958
+ id: `similar:${sha256(`${left.id}|${right.id}|${[...prunedReasons.keys()].sort().join(",")}`).slice(0, 16)}`,
6766
6959
  source: left.id,
6767
6960
  target: right.id,
6768
6961
  relation: "semantically_similar_to",
@@ -6773,11 +6966,15 @@ function buildSemanticSimilarityEdges(nodes, edges, manifests, analyses) {
6773
6966
  [...left.sourceIds, ...right.sourceIds].sort((a, b) => a.localeCompare(b)),
6774
6967
  (value) => value
6775
6968
  ),
6776
- similarityReasons: [...reasons.keys()].sort((a, b) => a.localeCompare(b)),
6969
+ similarityReasons: [...prunedReasons.keys()].sort((a, b) => a.localeCompare(b)),
6777
6970
  similarityBasis: "feature_overlap"
6778
6971
  }
6779
6972
  ];
6780
6973
  }).sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
6974
+ if (candidates.length > similarityEdgeCap) {
6975
+ return candidates.slice(0, similarityEdgeCap);
6976
+ }
6977
+ return candidates;
6781
6978
  }
6782
6979
  function buildTopicHyperedges(graph) {
6783
6980
  const nodesById = new Map(graph.nodes.map((node) => [node.id, node]));
@@ -6857,8 +7054,8 @@ function buildModuleFormHyperedges(graph) {
6857
7054
  ];
6858
7055
  });
6859
7056
  }
6860
- function enrichGraph(graph, manifests, analyses, extraSimilarityEdges = []) {
6861
- const similarityEdges = buildSemanticSimilarityEdges(graph.nodes, graph.edges, manifests, analyses);
7057
+ function enrichGraph(graph, manifests, analyses, extraSimilarityEdges = [], options) {
7058
+ const similarityEdges = buildSemanticSimilarityEdges(graph.nodes, graph.edges, manifests, analyses, options);
6862
7059
  const enrichedEdges = uniqueBy([...graph.edges, ...similarityEdges, ...extraSimilarityEdges], (edge) => edge.id).sort(
6863
7060
  (left, right) => left.id.localeCompare(right.id)
6864
7061
  );
@@ -6875,12 +7072,213 @@ function enrichGraph(graph, manifests, analyses, extraSimilarityEdges = []) {
6875
7072
  };
6876
7073
  }
6877
7074
 
6878
- // src/graph-tools.ts
7075
+ // src/graph-query-core.ts
7076
+ var NODE_TYPE_PRIORITY = {
7077
+ concept: 6,
7078
+ entity: 5,
7079
+ source: 4,
7080
+ module: 3,
7081
+ symbol: 2,
7082
+ rationale: 1
7083
+ };
6879
7084
  function normalizeTarget(value) {
7085
+ return value.replace(/\s+/g, " ").trim().normalize("NFKD").replace(/\p{Mn}+/gu, "").toLowerCase();
7086
+ }
7087
+ function uniqueStrings(values) {
7088
+ const seen = /* @__PURE__ */ new Set();
7089
+ const out = [];
7090
+ for (const value of values) {
7091
+ if (!value) continue;
7092
+ if (seen.has(value)) continue;
7093
+ seen.add(value);
7094
+ out.push(value);
7095
+ }
7096
+ return out;
7097
+ }
7098
+ function scoreMatch(query, candidate) {
7099
+ const q = normalizeTarget(query);
7100
+ const c = normalizeTarget(candidate);
7101
+ if (!q || !c) return 0;
7102
+ if (c === q) return 100;
7103
+ if (c.startsWith(q)) return 80;
7104
+ if (c.includes(q)) return 60;
7105
+ const qTokens = q.split(/\s+/).filter(Boolean);
7106
+ const cTokens = new Set(c.split(/\s+/).filter(Boolean));
7107
+ const overlap = qTokens.filter((token) => cTokens.has(token)).length;
7108
+ return overlap ? overlap * 10 : 0;
7109
+ }
7110
+ function buildAdjacency(graph) {
7111
+ const adjacency = /* @__PURE__ */ new Map();
7112
+ const push = (nodeId, item) => {
7113
+ const list = adjacency.get(nodeId);
7114
+ if (list) {
7115
+ list.push(item);
7116
+ } else {
7117
+ adjacency.set(nodeId, [item]);
7118
+ }
7119
+ };
7120
+ for (const edge of graph.edges) {
7121
+ push(edge.source, { edge, nodeId: edge.target, direction: "outgoing" });
7122
+ push(edge.target, { edge, nodeId: edge.source, direction: "incoming" });
7123
+ }
7124
+ for (const [nodeId, items] of adjacency.entries()) {
7125
+ items.sort((left, right) => right.edge.confidence - left.edge.confidence || left.edge.relation.localeCompare(right.edge.relation));
7126
+ adjacency.set(nodeId, items);
7127
+ }
7128
+ return adjacency;
7129
+ }
7130
+ function compareLabelCandidates(left, right) {
7131
+ const priorityDelta = (NODE_TYPE_PRIORITY[right.type] ?? 0) - (NODE_TYPE_PRIORITY[left.type] ?? 0);
7132
+ if (priorityDelta !== 0) return priorityDelta;
7133
+ const degreeDelta = (right.degree ?? 0) - (left.degree ?? 0);
7134
+ if (degreeDelta !== 0) return degreeDelta;
7135
+ return left.id.localeCompare(right.id);
7136
+ }
7137
+ function resolveCoreNode(graph, target) {
7138
+ const byId = new Map(graph.nodes.map((node) => [node.id, node]));
7139
+ if (byId.has(target)) return byId.get(target);
7140
+ const normalized = normalizeTarget(target);
7141
+ const labelMatches = graph.nodes.filter((node) => normalizeTarget(node.label) === normalized || normalizeTarget(node.id) === normalized);
7142
+ if (labelMatches.length) {
7143
+ return labelMatches.slice().sort(compareLabelCandidates)[0];
7144
+ }
7145
+ const pages = graph.pages ?? [];
7146
+ const pageHit = pages.map((page) => ({
7147
+ page,
7148
+ score: Math.max(scoreMatch(target, page.title), scoreMatch(target, page.path))
7149
+ })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.page.title.localeCompare(right.page.title))[0];
7150
+ if (pageHit) {
7151
+ const primary = graph.nodes.find((node) => node.pageId === pageHit.page.id);
7152
+ if (primary) return primary;
7153
+ }
7154
+ const fuzzy = graph.nodes.map((node) => ({ node, score: Math.max(scoreMatch(target, node.label), scoreMatch(target, node.id)) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || compareLabelCandidates(left.node, right.node))[0];
7155
+ return fuzzy?.node;
7156
+ }
7157
+ function runCoreGraphPath(graph, from, to) {
7158
+ const start = resolveCoreNode(graph, from);
7159
+ const end = resolveCoreNode(graph, to);
7160
+ if (!start || !end) {
7161
+ return {
7162
+ from,
7163
+ to,
7164
+ resolvedFromNodeId: start?.id,
7165
+ resolvedToNodeId: end?.id,
7166
+ found: false,
7167
+ nodeIds: [],
7168
+ edgeIds: [],
7169
+ pageIds: [],
7170
+ summary: "Could not resolve one or both graph targets."
7171
+ };
7172
+ }
7173
+ const adjacency = buildAdjacency(graph);
7174
+ const queue = [start.id];
7175
+ const visited = /* @__PURE__ */ new Set([start.id]);
7176
+ const previous = /* @__PURE__ */ new Map();
7177
+ while (queue.length) {
7178
+ const current2 = queue.shift();
7179
+ if (current2 === end.id) break;
7180
+ for (const neighbor of adjacency.get(current2) ?? []) {
7181
+ if (visited.has(neighbor.nodeId)) continue;
7182
+ visited.add(neighbor.nodeId);
7183
+ previous.set(neighbor.nodeId, { nodeId: current2, edgeId: neighbor.edge.id });
7184
+ queue.push(neighbor.nodeId);
7185
+ }
7186
+ }
7187
+ if (!visited.has(end.id)) {
7188
+ return {
7189
+ from,
7190
+ to,
7191
+ resolvedFromNodeId: start.id,
7192
+ resolvedToNodeId: end.id,
7193
+ found: false,
7194
+ nodeIds: [],
7195
+ edgeIds: [],
7196
+ pageIds: [],
7197
+ summary: `No path found between ${start.label} and ${end.label}.`
7198
+ };
7199
+ }
7200
+ const nodeIds = [];
7201
+ const edgeIds = [];
7202
+ let current = end.id;
7203
+ while (current !== start.id) {
7204
+ nodeIds.push(current);
7205
+ const prev = previous.get(current);
7206
+ if (!prev) break;
7207
+ edgeIds.push(prev.edgeId);
7208
+ current = prev.nodeId;
7209
+ }
7210
+ nodeIds.push(start.id);
7211
+ nodeIds.reverse();
7212
+ edgeIds.reverse();
7213
+ const nodeById2 = new Map(graph.nodes.map((node) => [node.id, node]));
7214
+ const pageIds = uniqueStrings(
7215
+ nodeIds.flatMap((nodeId) => {
7216
+ const node = nodeById2.get(nodeId);
7217
+ return node?.pageId ? [node.pageId] : [];
7218
+ })
7219
+ );
7220
+ return {
7221
+ from,
7222
+ to,
7223
+ resolvedFromNodeId: start.id,
7224
+ resolvedToNodeId: end.id,
7225
+ found: true,
7226
+ nodeIds,
7227
+ edgeIds,
7228
+ pageIds,
7229
+ summary: nodeIds.map((nodeId) => nodeById2.get(nodeId)?.label ?? nodeId).join(" -> ")
7230
+ };
7231
+ }
7232
+ function runCoreGraphExplain(graph, target) {
7233
+ const node = resolveCoreNode(graph, target);
7234
+ if (!node) return void 0;
7235
+ const adjacency = buildAdjacency(graph);
7236
+ const nodeById2 = new Map(graph.nodes.map((candidate) => [candidate.id, candidate]));
7237
+ const neighbors = [];
7238
+ for (const neighbor of adjacency.get(node.id) ?? []) {
7239
+ const targetNode = nodeById2.get(neighbor.nodeId);
7240
+ if (!targetNode) continue;
7241
+ neighbors.push({
7242
+ nodeId: targetNode.id,
7243
+ label: targetNode.label,
7244
+ type: targetNode.type,
7245
+ pageId: targetNode.pageId,
7246
+ relation: neighbor.edge.relation,
7247
+ direction: neighbor.direction,
7248
+ confidence: neighbor.edge.confidence,
7249
+ evidenceClass: neighbor.edge.evidenceClass
7250
+ });
7251
+ }
7252
+ neighbors.sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
7253
+ const pagesById = new Map((graph.pages ?? []).map((page2) => [page2.id, page2]));
7254
+ const page = node.pageId ? pagesById.get(node.pageId) : void 0;
7255
+ const community = node.communityId ? graph.communities?.find((candidate) => candidate.id === node.communityId) : void 0;
7256
+ const hyperedges = (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.nodeIds.includes(node.id)).slice().sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
7257
+ const summary = [
7258
+ `Node: ${node.label}`,
7259
+ `Type: ${node.type}`,
7260
+ `Community: ${node.communityId ?? "none"}`,
7261
+ `Neighbors: ${neighbors.length}`,
7262
+ `Group patterns: ${hyperedges.length}`,
7263
+ `Page: ${page?.path ?? "none"}`
7264
+ ].join("\n");
7265
+ return {
7266
+ target,
7267
+ node,
7268
+ page,
7269
+ community: community ? { id: community.id, label: community.label } : void 0,
7270
+ neighbors,
7271
+ hyperedges,
7272
+ summary
7273
+ };
7274
+ }
7275
+
7276
+ // src/graph-tools.ts
7277
+ function normalizeTarget2(value) {
6880
7278
  return normalizeWhitespace(value).normalize("NFKD").replace(/\p{Mn}+/gu, "").toLowerCase();
6881
7279
  }
6882
7280
  function computeNormLabel(label) {
6883
- return normalizeTarget(label);
7281
+ return normalizeTarget2(label);
6884
7282
  }
6885
7283
  function nodeById(graph) {
6886
7284
  return new Map(graph.nodes.map((node) => [node.id, node]));
@@ -6891,9 +7289,9 @@ function pageById(graph) {
6891
7289
  function hyperedgesForNode(graph, nodeId) {
6892
7290
  return (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.nodeIds.includes(nodeId)).sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
6893
7291
  }
6894
- function scoreMatch(query, candidate) {
6895
- const normalizedQuery = normalizeTarget(query);
6896
- const normalizedCandidate = normalizeTarget(candidate);
7292
+ function scoreMatch2(query, candidate) {
7293
+ const normalizedQuery = normalizeTarget2(query);
7294
+ const normalizedCandidate = normalizeTarget2(candidate);
6897
7295
  if (!normalizedQuery || !normalizedCandidate) {
6898
7296
  return 0;
6899
7297
  }
@@ -6919,7 +7317,7 @@ function pageSearchMatches(graph, question, searchResults) {
6919
7317
  const pages = pageById(graph);
6920
7318
  return searchResults.map((result) => {
6921
7319
  const page = pages.get(result.pageId);
6922
- const score = Math.max(scoreMatch(question, result.title), scoreMatch(question, result.path));
7320
+ const score = Math.max(scoreMatch2(question, result.title), scoreMatch2(question, result.path));
6923
7321
  if (!page || score <= 0) {
6924
7322
  return null;
6925
7323
  }
@@ -6936,7 +7334,7 @@ function nodeMatches(graph, query) {
6936
7334
  type: "node",
6937
7335
  id: node.id,
6938
7336
  label: node.label,
6939
- score: Math.max(scoreMatch(query, node.label), scoreMatch(query, node.id))
7337
+ score: Math.max(scoreMatch2(query, node.label), scoreMatch2(query, node.id))
6940
7338
  })).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label));
6941
7339
  }
6942
7340
  function hyperedgeMatches(graph, query) {
@@ -6944,7 +7342,7 @@ function hyperedgeMatches(graph, query) {
6944
7342
  type: "hyperedge",
6945
7343
  id: hyperedge.id,
6946
7344
  label: hyperedge.label,
6947
- score: Math.max(scoreMatch(query, hyperedge.label), scoreMatch(query, hyperedge.why), scoreMatch(query, hyperedge.relation))
7345
+ score: Math.max(scoreMatch2(query, hyperedge.label), scoreMatch2(query, hyperedge.why), scoreMatch2(query, hyperedge.relation))
6948
7346
  })).filter((match) => match.score > 0).sort((left, right) => right.score - left.score || left.label.localeCompare(right.label));
6949
7347
  }
6950
7348
  function graphAdjacency(graph) {
@@ -6965,7 +7363,7 @@ function graphAdjacency(graph) {
6965
7363
  }
6966
7364
  return adjacency;
6967
7365
  }
6968
- var NODE_TYPE_PRIORITY = {
7366
+ var NODE_TYPE_PRIORITY2 = {
6969
7367
  concept: 6,
6970
7368
  entity: 5,
6971
7369
  source: 4,
@@ -6974,9 +7372,9 @@ var NODE_TYPE_PRIORITY = {
6974
7372
  rationale: 1
6975
7373
  };
6976
7374
  function nodeTypePriority(type) {
6977
- return NODE_TYPE_PRIORITY[type] ?? 0;
7375
+ return NODE_TYPE_PRIORITY2[type] ?? 0;
6978
7376
  }
6979
- function compareLabelCandidates(left, right) {
7377
+ function compareLabelCandidates2(left, right) {
6980
7378
  const priorityDelta = nodeTypePriority(right.type) - nodeTypePriority(left.type);
6981
7379
  if (priorityDelta !== 0) {
6982
7380
  return priorityDelta;
@@ -6988,30 +7386,23 @@ function compareLabelCandidates(left, right) {
6988
7386
  return left.id.localeCompare(right.id);
6989
7387
  }
6990
7388
  function resolveNode(graph, target) {
6991
- const normalized = normalizeTarget(target);
7389
+ const normalized = normalizeTarget2(target);
6992
7390
  const byId = nodeById(graph);
6993
7391
  if (byId.has(target)) {
6994
7392
  return byId.get(target);
6995
7393
  }
6996
- const labelMatches = graph.nodes.filter((node) => normalizeTarget(node.label) === normalized || normalizeTarget(node.id) === normalized);
7394
+ const labelMatches = graph.nodes.filter((node) => normalizeTarget2(node.label) === normalized || normalizeTarget2(node.id) === normalized);
6997
7395
  if (labelMatches.length) {
6998
- return labelMatches.sort(compareLabelCandidates)[0];
7396
+ return labelMatches.sort(compareLabelCandidates2)[0];
6999
7397
  }
7000
7398
  const pages = graph.pages.map((page) => ({
7001
7399
  page,
7002
- score: Math.max(scoreMatch(target, page.title), scoreMatch(target, page.path))
7400
+ score: Math.max(scoreMatch2(target, page.title), scoreMatch2(target, page.path))
7003
7401
  })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || left.page.title.localeCompare(right.page.title));
7004
7402
  if (pages[0]) {
7005
7403
  return primaryNodeForPage(graph, pages[0].page);
7006
7404
  }
7007
- return graph.nodes.map((node) => ({ node, score: Math.max(scoreMatch(target, node.label), scoreMatch(target, node.id)) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || compareLabelCandidates(left.node, right.node))[0]?.node;
7008
- }
7009
- function communityLabel(graph, communityId) {
7010
- if (!communityId) {
7011
- return void 0;
7012
- }
7013
- const community = graph.communities?.find((item) => item.id === communityId);
7014
- return community ? { id: community.id, label: community.label } : void 0;
7405
+ return graph.nodes.map((node) => ({ node, score: Math.max(scoreMatch2(target, node.label), scoreMatch2(target, node.id)) })).filter((item) => item.score > 0).sort((left, right) => right.score - left.score || compareLabelCandidates2(left.node, right.node))[0]?.node;
7015
7406
  }
7016
7407
  function queryGraph(graph, question, searchResults, options) {
7017
7408
  const traversal = options?.traversal ?? "bfs";
@@ -7102,128 +7493,29 @@ function queryGraph(graph, question, searchResults, options) {
7102
7493
  };
7103
7494
  }
7104
7495
  function shortestGraphPath(graph, from, to) {
7105
- const start = resolveNode(graph, from);
7106
- const end = resolveNode(graph, to);
7107
- if (!start || !end) {
7108
- return {
7109
- from,
7110
- to,
7111
- resolvedFromNodeId: start?.id,
7112
- resolvedToNodeId: end?.id,
7113
- found: false,
7114
- nodeIds: [],
7115
- edgeIds: [],
7116
- pageIds: [],
7117
- summary: "Could not resolve one or both graph targets."
7118
- };
7119
- }
7120
- const adjacency = graphAdjacency(graph);
7121
- const queue = [start.id];
7122
- const visited = /* @__PURE__ */ new Set([start.id]);
7123
- const previous = /* @__PURE__ */ new Map();
7124
- while (queue.length) {
7125
- const current2 = queue.shift();
7126
- if (current2 === end.id) {
7127
- break;
7128
- }
7129
- for (const neighbor of adjacency.get(current2) ?? []) {
7130
- if (visited.has(neighbor.nodeId)) {
7131
- continue;
7132
- }
7133
- visited.add(neighbor.nodeId);
7134
- previous.set(neighbor.nodeId, { nodeId: current2, edgeId: neighbor.edge.id });
7135
- queue.push(neighbor.nodeId);
7136
- }
7137
- }
7138
- if (!visited.has(end.id)) {
7139
- return {
7140
- from,
7141
- to,
7142
- resolvedFromNodeId: start.id,
7143
- resolvedToNodeId: end.id,
7144
- found: false,
7145
- nodeIds: [],
7146
- edgeIds: [],
7147
- pageIds: [],
7148
- summary: `No path found between ${start.label} and ${end.label}.`
7149
- };
7150
- }
7151
- const nodeIds = [];
7152
- const edgeIds = [];
7153
- let current = end.id;
7154
- while (current !== start.id) {
7155
- nodeIds.push(current);
7156
- const prev = previous.get(current);
7157
- if (!prev) {
7158
- break;
7159
- }
7160
- edgeIds.push(prev.edgeId);
7161
- current = prev.nodeId;
7496
+ return runCoreGraphPath(graph, from, to);
7497
+ }
7498
+ function explainGraphTarget(graph, target) {
7499
+ const result = runCoreGraphExplain(graph, target);
7500
+ if (!result) {
7501
+ throw new Error(`Could not resolve graph target: ${target}`);
7162
7502
  }
7163
- nodeIds.push(start.id);
7164
- nodeIds.reverse();
7165
- edgeIds.reverse();
7166
7503
  const nodes = nodeById(graph);
7167
- const pageIds = uniqueBy(
7168
- nodeIds.flatMap((nodeId) => {
7169
- const node = nodes.get(nodeId);
7170
- return node?.pageId ? [node.pageId] : [];
7171
- }),
7172
- (item) => item
7173
- );
7174
- return {
7175
- from,
7176
- to,
7177
- resolvedFromNodeId: start.id,
7178
- resolvedToNodeId: end.id,
7179
- found: true,
7180
- nodeIds,
7181
- edgeIds,
7182
- pageIds,
7183
- summary: nodeIds.map((nodeId) => nodes.get(nodeId)?.label ?? nodeId).join(" -> ")
7184
- };
7185
- }
7186
- function explainGraphTarget(graph, target) {
7187
- const node = resolveNode(graph, target);
7188
- if (!node) {
7189
- throw new Error(`Could not resolve graph target: ${target}`);
7190
- }
7191
- const pages = pageById(graph);
7192
- const page = node.pageId ? pages.get(node.pageId) : void 0;
7193
- const neighbors = [];
7194
- const nodes = nodeById(graph);
7195
- for (const neighbor of graphAdjacency(graph).get(node.id) ?? []) {
7196
- const targetNode = nodes.get(neighbor.nodeId);
7197
- if (!targetNode) {
7198
- continue;
7199
- }
7200
- neighbors.push({
7201
- nodeId: targetNode.id,
7202
- label: targetNode.label,
7203
- type: targetNode.type,
7204
- pageId: targetNode.pageId,
7205
- relation: neighbor.edge.relation,
7206
- direction: neighbor.direction,
7207
- confidence: neighbor.edge.confidence,
7208
- evidenceClass: neighbor.edge.evidenceClass
7209
- });
7210
- }
7211
- neighbors.sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
7504
+ const node = nodes.get(result.node.id) ?? result.node;
7505
+ const page = node.pageId ? pageById(graph).get(node.pageId) : void 0;
7506
+ const neighbors = result.neighbors.map((neighbor) => ({
7507
+ ...neighbor,
7508
+ type: nodes.get(neighbor.nodeId)?.type ?? neighbor.type,
7509
+ evidenceClass: neighbor.evidenceClass
7510
+ }));
7212
7511
  return {
7213
7512
  target,
7214
7513
  node,
7215
7514
  page,
7216
- community: communityLabel(graph, node.communityId),
7515
+ community: result.community,
7217
7516
  neighbors,
7218
7517
  hyperedges: hyperedgesForNode(graph, node.id),
7219
- summary: [
7220
- `Node: ${node.label}`,
7221
- `Type: ${node.type}`,
7222
- `Community: ${node.communityId ?? "none"}`,
7223
- `Neighbors: ${neighbors.length}`,
7224
- `Group patterns: ${hyperedgesForNode(graph, node.id).length}`,
7225
- `Page: ${page?.path ?? "none"}`
7226
- ].join("\n")
7518
+ summary: result.summary
7227
7519
  };
7228
7520
  }
7229
7521
  function topGodNodes(graph, limit = 10) {
@@ -7237,7 +7529,7 @@ function listHyperedges(graph, target, limit = 25) {
7237
7529
  if (node) {
7238
7530
  return hyperedgesForNode(graph, node.id).slice(0, limit);
7239
7531
  }
7240
- const page = graph.pages.find((candidate) => normalizeTarget(candidate.path) === normalizeTarget(target) || candidate.id === target);
7532
+ const page = graph.pages.find((candidate) => normalizeTarget2(candidate.path) === normalizeTarget2(target) || candidate.id === target);
7241
7533
  if (!page) {
7242
7534
  return [];
7243
7535
  }
@@ -7283,8 +7575,8 @@ function blastRadius(graph, target, options) {
7283
7575
  const resolved = resolveNode(graph, target);
7284
7576
  const moduleNode = resolved?.type === "module" ? resolved : resolved?.moduleId ? graph.nodes.find((n) => n.id === resolved.moduleId) : void 0;
7285
7577
  if (!moduleNode) {
7286
- const normalizedTarget = normalizeTarget(target);
7287
- const candidate = graph.nodes.filter((n) => n.type === "module").find((n) => normalizeTarget(n.label).includes(normalizedTarget) || normalizeTarget(n.id).includes(normalizedTarget));
7578
+ const normalizedTarget = normalizeTarget2(target);
7579
+ const candidate = graph.nodes.filter((n) => n.type === "module").find((n) => normalizeTarget2(n.label).includes(normalizedTarget) || normalizeTarget2(n.id).includes(normalizedTarget));
7288
7580
  if (!candidate) {
7289
7581
  return {
7290
7582
  target,
@@ -7340,6 +7632,105 @@ function blastRadius(graph, target, options) {
7340
7632
  };
7341
7633
  }
7342
7634
 
7635
+ // src/large-repo-defaults.ts
7636
+ var LARGE_REPO_NODE_THRESHOLD = 1e3;
7637
+ var DEFAULT_SMALL_GOD_NODE_LIMIT = 20;
7638
+ var DEFAULT_LARGE_GOD_NODE_LIMIT = 10;
7639
+ var DEFAULT_SIMILARITY_IDF_FLOOR2 = 0.5;
7640
+ var SIMILARITY_EDGE_CAP_MAX = 2e4;
7641
+ var SIMILARITY_EDGE_CAP_PER_NODE = 5;
7642
+ var MIN_FOLD_BELOW = 3;
7643
+ function clampPositiveInteger(value, fallback) {
7644
+ if (!Number.isFinite(value) || value <= 0) {
7645
+ return fallback;
7646
+ }
7647
+ return Math.max(1, Math.floor(value));
7648
+ }
7649
+ function clampNonNegativeNumber(value, fallback) {
7650
+ if (!Number.isFinite(value) || value < 0) {
7651
+ return fallback;
7652
+ }
7653
+ return value;
7654
+ }
7655
+ function resolveLargeRepoDefaults(input) {
7656
+ const nodeCount = Math.max(0, Math.floor(input.nodeCount));
7657
+ const totalCommunities = Math.max(0, Math.floor(input.totalCommunities ?? 0));
7658
+ const graphConfig = input.config?.graph;
7659
+ const isLargeRepo = nodeCount > LARGE_REPO_NODE_THRESHOLD;
7660
+ const defaultGodNodeLimit = isLargeRepo ? DEFAULT_LARGE_GOD_NODE_LIMIT : DEFAULT_SMALL_GOD_NODE_LIMIT;
7661
+ const godNodeLimit = graphConfig?.godNodeLimit !== void 0 ? clampPositiveInteger(graphConfig.godNodeLimit, defaultGodNodeLimit) : defaultGodNodeLimit;
7662
+ const defaultSimilarityEdgeCap = Math.min(SIMILARITY_EDGE_CAP_MAX, Math.max(0, SIMILARITY_EDGE_CAP_PER_NODE * nodeCount));
7663
+ const similarityEdgeCap = graphConfig?.similarityEdgeCap !== void 0 ? clampPositiveInteger(graphConfig.similarityEdgeCap, defaultSimilarityEdgeCap) : defaultSimilarityEdgeCap;
7664
+ const similarityIdfFloor = graphConfig?.similarityIdfFloor !== void 0 ? clampNonNegativeNumber(graphConfig.similarityIdfFloor, DEFAULT_SIMILARITY_IDF_FLOOR2) : DEFAULT_SIMILARITY_IDF_FLOOR2;
7665
+ const defaultFoldBelow = Math.max(MIN_FOLD_BELOW, Math.ceil(totalCommunities / 50));
7666
+ const foldCommunitiesBelow = graphConfig?.foldCommunitiesBelow !== void 0 ? clampPositiveInteger(graphConfig.foldCommunitiesBelow, defaultFoldBelow) : defaultFoldBelow;
7667
+ return {
7668
+ godNodeLimit,
7669
+ foldCommunitiesBelow,
7670
+ similarityEdgeCap,
7671
+ similarityIdfFloor
7672
+ };
7673
+ }
7674
+
7675
+ // src/source-classification.ts
7676
+ import path6 from "path";
7677
+ var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
7678
+ var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
7679
+ var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
7680
+ function matchesAnyGlob(relativePath, patterns) {
7681
+ return patterns.some(
7682
+ (pattern) => path6.matchesGlob(relativePath, pattern) || path6.matchesGlob(path6.posix.basename(relativePath), pattern)
7683
+ );
7684
+ }
7685
+ function classifyRepoPath(relativePath, repoAnalysis) {
7686
+ const normalized = relativePath.replace(/\\/g, "/");
7687
+ const custom = repoAnalysis?.classifyGlobs;
7688
+ if (custom?.first_party?.length && matchesAnyGlob(normalized, custom.first_party)) {
7689
+ return "first_party";
7690
+ }
7691
+ for (const sourceClass of ["third_party", "resource", "generated"]) {
7692
+ const patterns = custom?.[sourceClass];
7693
+ if (patterns?.length && matchesAnyGlob(normalized, patterns)) {
7694
+ return sourceClass;
7695
+ }
7696
+ }
7697
+ const segments = normalized.split("/").filter(Boolean);
7698
+ if (segments.some((segment) => THIRD_PARTY_SEGMENTS.has(segment))) {
7699
+ return "third_party";
7700
+ }
7701
+ if (segments.some((segment) => GENERATED_SEGMENTS.has(segment))) {
7702
+ return "generated";
7703
+ }
7704
+ if (segments.some((segment) => segment.endsWith(".xcassets") || segment.endsWith(".imageset"))) {
7705
+ return "resource";
7706
+ }
7707
+ return "first_party";
7708
+ }
7709
+ function normalizeExtractClasses(repoAnalysis, extra = []) {
7710
+ const configured = repoAnalysis?.extractClasses?.length ? repoAnalysis.extractClasses : ["first_party"];
7711
+ return ALL_SOURCE_CLASSES.filter((sourceClass) => (/* @__PURE__ */ new Set([...configured, ...extra])).has(sourceClass));
7712
+ }
7713
+ function aggregateSourceClass(values) {
7714
+ const available = ALL_SOURCE_CLASSES.filter((sourceClass) => values.includes(sourceClass));
7715
+ if (!available.length) {
7716
+ return void 0;
7717
+ }
7718
+ if (available.includes("first_party")) {
7719
+ return "first_party";
7720
+ }
7721
+ if (available.includes("resource")) {
7722
+ return "resource";
7723
+ }
7724
+ if (available.includes("third_party")) {
7725
+ return "third_party";
7726
+ }
7727
+ return "generated";
7728
+ }
7729
+ function aggregateManifestSourceClass(manifests, sourceIds) {
7730
+ const byId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest.sourceClass]));
7731
+ return aggregateSourceClass(sourceIds.map((sourceId) => byId.get(sourceId)));
7732
+ }
7733
+
7343
7734
  // src/markdown.ts
7344
7735
  function tierFrontmatterFragment(tier) {
7345
7736
  if (!tier) {
@@ -7357,7 +7748,7 @@ function tierFrontmatterFragment(tier) {
7357
7748
  }
7358
7749
  return fragment;
7359
7750
  }
7360
- function uniqueStrings(values) {
7751
+ function uniqueStrings2(values) {
7361
7752
  return uniqueBy(values.filter(Boolean), (value) => value);
7362
7753
  }
7363
7754
  var GUIDED_SOURCE_MARKER_PREFIX = "<!-- swarmvault-guided-source:";
@@ -7388,7 +7779,7 @@ function extractGuidedSourceBlocks(content) {
7388
7779
  blocks.push(content.slice(startIndex, blockEnd).trim());
7389
7780
  cursor = blockEnd;
7390
7781
  }
7391
- return uniqueStrings(blocks);
7782
+ return uniqueStrings2(blocks);
7392
7783
  }
7393
7784
  function appendGuidedSourceBlocks(body, existingContent) {
7394
7785
  const blocks = extractGuidedSourceBlocks(existingContent);
@@ -7416,12 +7807,29 @@ function cssclassesFor(kind) {
7416
7807
  return ["swarmvault", `sv-${kind.replace(/_/g, "-")}`];
7417
7808
  }
7418
7809
  function decoratedTags(baseTags, decorations) {
7419
- return uniqueStrings([
7810
+ return uniqueStrings2([
7420
7811
  ...baseTags,
7421
7812
  ...(decorations?.projectIds ?? []).map((projectId) => `project/${projectId}`),
7422
7813
  ...decorations?.extraTags ?? []
7423
7814
  ]);
7424
7815
  }
7816
+ function sortDerivedTags(tags, leaders) {
7817
+ const deduped = uniqueStrings2(tags);
7818
+ const pinned = [];
7819
+ const rest = [];
7820
+ for (const tag of deduped) {
7821
+ if (leaders.includes(tag)) continue;
7822
+ rest.push(tag);
7823
+ }
7824
+ for (const leader of leaders) {
7825
+ if (deduped.includes(leader)) pinned.push(leader);
7826
+ }
7827
+ rest.sort((left, right) => left.localeCompare(right));
7828
+ return [...pinned, ...rest];
7829
+ }
7830
+ function inheritedSourceTags(sourceAnalyses) {
7831
+ return uniqueStrings2(sourceAnalyses.flatMap((analysis) => analysis.tags ?? []));
7832
+ }
7425
7833
  function pagePathFor(kind, slug) {
7426
7834
  switch (kind) {
7427
7835
  case "source":
@@ -7630,7 +8038,7 @@ function buildModulePage(input) {
7630
8038
  const nodeIds = [code.moduleId, ...code.symbols.map((symbol) => symbol.id)];
7631
8039
  const localModuleBacklinks = input.localModules.map((moduleRef) => moduleRef.page.id);
7632
8040
  const relatedOutputs = input.relatedOutputs ?? [];
7633
- const backlinks = uniqueStrings([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]);
8041
+ const backlinks = uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]);
7634
8042
  const { sourceHashes, sourceSemanticHashes } = sourceHashesForManifest(manifest);
7635
8043
  const importsSection = code.imports.length ? code.imports.map((item) => {
7636
8044
  const localModule = item.resolvedSourceId ? input.localModules.find((moduleRef) => moduleRef.sourceId === item.resolvedSourceId && moduleRef.reExport === item.reExport) : void 0;
@@ -7677,9 +8085,9 @@ function buildModulePage(input) {
7677
8085
  backlinks,
7678
8086
  schema_hash: schemaHash,
7679
8087
  ...sourceHashFrontmatter(sourceHashes, sourceSemanticHashes),
7680
- related_page_ids: uniqueStrings([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
8088
+ related_page_ids: uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
7681
8089
  related_node_ids: [],
7682
- related_source_ids: uniqueStrings([
8090
+ related_source_ids: uniqueStrings2([
7683
8091
  manifest.sourceId,
7684
8092
  ...input.localModules.map((moduleRef) => moduleRef.sourceId),
7685
8093
  ...relatedOutputs.flatMap((page) => page.sourceIds)
@@ -7754,9 +8162,9 @@ function buildModulePage(input) {
7754
8162
  schemaHash,
7755
8163
  sourceHashes,
7756
8164
  sourceSemanticHashes,
7757
- relatedPageIds: uniqueStrings([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
8165
+ relatedPageIds: uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
7758
8166
  relatedNodeIds: [],
7759
- relatedSourceIds: uniqueStrings([
8167
+ relatedSourceIds: uniqueStrings2([
7760
8168
  manifest.sourceId,
7761
8169
  ...input.localModules.map((moduleRef) => moduleRef.sourceId),
7762
8170
  ...relatedOutputs.flatMap((page) => page.sourceIds)
@@ -7775,13 +8183,16 @@ function buildAggregatePage(kind, name, descriptions, sourceAnalyses, sourceHash
7775
8183
  const sourceIds = sourceAnalyses.map((item) => item.sourceId);
7776
8184
  const otherPages = [...sourceAnalyses.map((item) => `source:${item.sourceId}`), ...relatedOutputs.map((page) => page.id)];
7777
8185
  const summary = descriptions.find(Boolean) ?? `${kind} aggregated from ${sourceIds.length} source(s).`;
8186
+ const leaderTags = metadata.status === "candidate" ? [kind, "candidate"] : [kind];
8187
+ const inheritedTags = inheritedSourceTags(sourceAnalyses);
8188
+ const derivedTags = sortDerivedTags([...decoratedTags(leaderTags, decorations), ...inheritedTags], leaderTags);
7778
8189
  const frontmatter = {
7779
8190
  page_id: pageId,
7780
8191
  kind,
7781
8192
  cssclasses: cssclassesFor(kind),
7782
8193
  title: name,
7783
8194
  ...decorations?.sourceClass ? { source_class: decorations.sourceClass } : {},
7784
- tags: decoratedTags(metadata.status === "candidate" ? [kind, "candidate"] : [kind], decorations),
8195
+ tags: derivedTags,
7785
8196
  source_ids: sourceIds,
7786
8197
  project_ids: decorations?.projectIds ?? [],
7787
8198
  node_ids: [pageId],
@@ -8076,8 +8487,8 @@ function topGroupPatterns(graph) {
8076
8487
  (left, right) => right.confidence - left.confidence || right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label)
8077
8488
  ).slice(0, 8);
8078
8489
  }
8079
- function fragmentedCommunityPresentation(graph, communityPages) {
8080
- const thinCommunities = (graph.communities ?? []).filter((community) => community.nodeIds.length <= 2).sort((left, right) => right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label));
8490
+ function fragmentedCommunityPresentation(graph, communityPages, foldBelow = 3) {
8491
+ const thinCommunities = (graph.communities ?? []).filter((community) => community.nodeIds.length < foldBelow).sort((left, right) => right.nodeIds.length - left.nodeIds.length || left.label.localeCompare(right.label));
8081
8492
  const visibleCommunities = thinCommunities.slice(0, 6).map((community) => {
8082
8493
  const page = communityPages.find((candidate) => candidate.id === `graph:${community.id}`);
8083
8494
  return {
@@ -8126,7 +8537,7 @@ function suggestedGraphQuestions(graph) {
8126
8537
  }
8127
8538
  }
8128
8539
  const isolatedNodeQuestions = graph.nodes.filter((node) => (node.degree ?? 0) <= 1 && node.type !== "source").slice(0, 3).map((node) => `What connects ${node.label} to the rest of the vault?`);
8129
- return uniqueStrings([
8540
+ return uniqueStrings2([
8130
8541
  ...thinCommunities.map((community) => `What sources would strengthen community ${community.label}?`),
8131
8542
  ...bridgeNodes.map((node) => `Why does ${node.label} connect multiple communities in the vault?`),
8132
8543
  ...ambiguousEdgeQuestions,
@@ -8174,13 +8585,44 @@ function buildKnowledgeGaps(graph) {
8174
8585
  }
8175
8586
  return { isolatedNodes, thinCommunityCount, ambiguousEdgeRatio, warnings };
8176
8587
  }
8588
+ var SOURCE_CLASS_LABELS = {
8589
+ first_party: "First-party",
8590
+ third_party: "Third-party",
8591
+ resource: "Resource",
8592
+ generated: "Generated"
8593
+ };
8594
+ function benchmarkByClassTableLines(byClass) {
8595
+ if (!byClass) {
8596
+ return [];
8597
+ }
8598
+ const lines = [
8599
+ "### Benchmark By Source Class",
8600
+ "",
8601
+ "| Class | Sources | Pages | Nodes | God Nodes | Naive Tokens | Guided Tokens | Reduction |",
8602
+ "| ----- | ------- | ----- | ----- | --------- | ------------ | ------------- | --------- |"
8603
+ ];
8604
+ for (const sourceClass of ALL_SOURCE_CLASSES) {
8605
+ const entry = byClass[sourceClass];
8606
+ const reductionPct = (entry.reductionRatio * 100).toFixed(1);
8607
+ lines.push(
8608
+ `| ${SOURCE_CLASS_LABELS[sourceClass]} | ${entry.sourceCount} | ${entry.pageCount} | ${entry.nodeCount} | ${entry.godNodeCount} | ${entry.naiveCorpusTokens} | ${entry.finalContextTokens} | ${reductionPct}% |`
8609
+ );
8610
+ }
8611
+ lines.push("");
8612
+ return lines;
8613
+ }
8177
8614
  function buildGraphReportArtifact(input) {
8178
8615
  const firstPartyGraph = filterGraphBySourceClass(input.graph, "first_party");
8179
8616
  const reportGraph = firstPartyGraph.nodes.length ? firstPartyGraph : input.graph;
8180
8617
  const pagesById = new Map(reportGraph.pages.map((page) => [page.id, page]));
8181
- const godNodes = reportGraph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, 8);
8618
+ const repoDefaults = resolveLargeRepoDefaults({
8619
+ nodeCount: input.graph.nodes.length,
8620
+ totalCommunities: input.graph.communities?.length ?? 0,
8621
+ config: input.config
8622
+ });
8623
+ const godNodes = reportGraph.nodes.filter((node) => node.isGodNode).sort((left, right) => (right.degree ?? 0) - (left.degree ?? 0)).slice(0, repoDefaults.godNodeLimit);
8182
8624
  const bridgeNodes = reportGraph.nodes.filter((node) => (node.bridgeScore ?? 0) > 0).sort((left, right) => (right.bridgeScore ?? 0) - (left.bridgeScore ?? 0)).slice(0, 8);
8183
- const communityPresentation = fragmentedCommunityPresentation(reportGraph, input.communityPages);
8625
+ const communityPresentation = fragmentedCommunityPresentation(reportGraph, input.communityPages, repoDefaults.foldCommunitiesBelow);
8184
8626
  const surprisingConnections = topSurprisingConnections(reportGraph, pagesById);
8185
8627
  const groupPatterns = topGroupPatterns(reportGraph);
8186
8628
  const breakdown = sourceClassBreakdown(input.graph);
@@ -8220,14 +8662,32 @@ function buildGraphReportArtifact(input) {
8220
8662
  generatedAt: input.benchmark.generatedAt,
8221
8663
  stale: input.benchmarkStale ?? false,
8222
8664
  summary: input.benchmark.summary,
8223
- questionCount: input.benchmark.sampleQuestions.length
8665
+ questionCount: input.benchmark.sampleQuestions.length,
8666
+ byClass: input.benchmark.byClass ? Object.fromEntries(
8667
+ ALL_SOURCE_CLASSES.map((sourceClass) => {
8668
+ const entry = input.benchmark?.byClass?.[sourceClass];
8669
+ return [
8670
+ sourceClass,
8671
+ {
8672
+ sourceCount: entry?.sourceCount ?? 0,
8673
+ pageCount: entry?.pageCount ?? 0,
8674
+ nodeCount: entry?.nodeCount ?? 0,
8675
+ godNodeCount: entry?.godNodeCount ?? 0,
8676
+ finalContextTokens: entry?.finalContextTokens ?? 0,
8677
+ naiveCorpusTokens: entry?.corpusTokens ?? 0,
8678
+ reductionRatio: entry?.reductionRatio ?? 0
8679
+ }
8680
+ ];
8681
+ })
8682
+ ) : void 0
8224
8683
  } : void 0,
8225
8684
  godNodes: godNodes.map((node) => ({
8226
8685
  nodeId: node.id,
8227
8686
  label: node.label,
8228
8687
  pageId: node.pageId,
8229
8688
  degree: node.degree,
8230
- bridgeScore: node.bridgeScore
8689
+ bridgeScore: node.bridgeScore,
8690
+ ...node.surpriseReason ? { surpriseReason: node.surpriseReason } : {}
8231
8691
  })),
8232
8692
  bridgeNodes: bridgeNodes.map((node) => ({
8233
8693
  nodeId: node.id,
@@ -8277,7 +8737,7 @@ function buildGraphReportPage(input) {
8277
8737
  const pathValue = pagePathFor("graph_report", "report");
8278
8738
  const pagesById = new Map(input.graph.pages.map((page) => [page.id, page]));
8279
8739
  const nodesById = new Map(input.graph.nodes.map((node) => [node.id, node]));
8280
- const relatedNodeIds = uniqueStrings([
8740
+ const relatedNodeIds = uniqueStrings2([
8281
8741
  ...input.report.godNodes.map((node) => node.nodeId),
8282
8742
  ...input.report.bridgeNodes.map((node) => node.nodeId),
8283
8743
  ...input.report.surprisingConnections.flatMap((connection) => [
@@ -8287,14 +8747,14 @@ function buildGraphReportPage(input) {
8287
8747
  ]),
8288
8748
  ...input.report.groupPatterns.flatMap((hyperedge) => hyperedge.nodeIds)
8289
8749
  ]);
8290
- const relatedPageIds = uniqueStrings([
8750
+ const relatedPageIds = uniqueStrings2([
8291
8751
  ...input.report.godNodes.map((node) => node.pageId ?? ""),
8292
8752
  ...input.report.bridgeNodes.map((node) => node.pageId ?? ""),
8293
8753
  ...input.report.communityPages.map((page) => page.id),
8294
8754
  ...input.report.recentResearchSources.map((page) => page.pageId),
8295
8755
  ...input.report.groupPatterns.flatMap((hyperedge) => hyperedge.sourcePageIds)
8296
8756
  ]);
8297
- const relatedSourceIds = uniqueStrings([
8757
+ const relatedSourceIds = uniqueStrings2([
8298
8758
  ...relatedNodeIds.flatMap((nodeId) => nodesById.get(nodeId)?.sourceIds ?? []),
8299
8759
  ...input.report.recentResearchSources.flatMap((page) => pagesById.get(page.pageId)?.sourceIds ?? [])
8300
8760
  ]);
@@ -8354,7 +8814,8 @@ function buildGraphReportPage(input) {
8354
8814
  `- Unique Nodes Considered: ${input.report.benchmark.summary.uniqueVisitedNodes}`,
8355
8815
  `- Reduction Ratio: ${(input.report.benchmark.summary.reductionRatio * 100).toFixed(1)}%`,
8356
8816
  `- Questions: ${input.report.benchmark.questionCount}`,
8357
- ""
8817
+ "",
8818
+ ...benchmarkByClassTableLines(input.report.benchmark.byClass)
8358
8819
  ] : ["- No benchmark results yet.", ""],
8359
8820
  "## Top God Nodes",
8360
8821
  "",
@@ -8451,14 +8912,14 @@ function buildCommunitySummaryPage(input) {
8451
8912
  const nodesById = new Map(input.graph.nodes.map((node) => [node.id, node]));
8452
8913
  const pagesById = new Map(input.graph.pages.map((page) => [page.id, page]));
8453
8914
  const communityNodes = input.community.nodeIds.map((nodeId) => nodesById.get(nodeId)).filter((node) => Boolean(node));
8454
- const communityPageIds = uniqueStrings(communityNodes.map((node) => node.pageId ?? ""));
8915
+ const communityPageIds = uniqueStrings2(communityNodes.map((node) => node.pageId ?? ""));
8455
8916
  const communityPages = communityPageIds.map((id) => pagesById.get(id)).filter((page) => Boolean(page));
8456
8917
  const externalEdges = input.graph.edges.filter((edge) => {
8457
8918
  const source = nodesById.get(edge.source);
8458
8919
  const target = nodesById.get(edge.target);
8459
8920
  return source?.communityId === input.community.id && target?.communityId && target.communityId !== input.community.id;
8460
8921
  }).slice(0, 8);
8461
- const relatedSourceIds = uniqueStrings(communityNodes.flatMap((node) => node.sourceIds));
8922
+ const relatedSourceIds = uniqueStrings2(communityNodes.flatMap((node) => node.sourceIds));
8462
8923
  const frontmatter = {
8463
8924
  page_id: pageId,
8464
8925
  kind: "community_summary",
@@ -8479,7 +8940,7 @@ function buildCommunitySummaryPage(input) {
8479
8940
  schema_hash: input.schemaHash,
8480
8941
  source_hashes: {},
8481
8942
  source_semantic_hashes: {},
8482
- related_page_ids: uniqueStrings(["graph:report", ...communityPageIds]),
8943
+ related_page_ids: uniqueStrings2(["graph:report", ...communityPageIds]),
8483
8944
  related_node_ids: input.community.nodeIds,
8484
8945
  related_source_ids: relatedSourceIds
8485
8946
  };
@@ -8519,7 +8980,7 @@ function buildCommunitySummaryPage(input) {
8519
8980
  schemaHash: input.schemaHash,
8520
8981
  sourceHashes: {},
8521
8982
  sourceSemanticHashes: {},
8522
- relatedPageIds: uniqueStrings(["graph:report", ...communityPageIds]),
8983
+ relatedPageIds: uniqueStrings2(["graph:report", ...communityPageIds]),
8523
8984
  relatedNodeIds: input.community.nodeIds,
8524
8985
  relatedSourceIds,
8525
8986
  createdAt: input.metadata.createdAt,
@@ -8882,7 +9343,7 @@ function buildExploreHubPage(input) {
8882
9343
 
8883
9344
  // src/pages.ts
8884
9345
  import fs6 from "fs/promises";
8885
- import path6 from "path";
9346
+ import path7 from "path";
8886
9347
  import matter3 from "gray-matter";
8887
9348
  function normalizeStringArray(value) {
8888
9349
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -9027,7 +9488,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
9027
9488
  const now = (/* @__PURE__ */ new Date()).toISOString();
9028
9489
  const fallbackCreatedAt = defaults.createdAt ?? now;
9029
9490
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
9030
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path6.basename(relativePath, ".md");
9491
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path7.basename(relativePath, ".md");
9031
9492
  const kind = inferPageKind(relativePath, parsed.data.kind);
9032
9493
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
9033
9494
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -9075,18 +9536,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
9075
9536
  };
9076
9537
  }
9077
9538
  async function loadInsightPages(wikiDir) {
9078
- const insightsDir = path6.join(wikiDir, "insights");
9539
+ const insightsDir = path7.join(wikiDir, "insights");
9079
9540
  if (!await fileExists(insightsDir)) {
9080
9541
  return [];
9081
9542
  }
9082
- const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path6.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
9543
+ const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path7.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
9083
9544
  const insights = [];
9084
9545
  for (const absolutePath of files) {
9085
- const relativePath = toPosix(path6.relative(wikiDir, absolutePath));
9546
+ const relativePath = toPosix(path7.relative(wikiDir, absolutePath));
9086
9547
  const content = await fs6.readFile(absolutePath, "utf8");
9087
9548
  const parsed = matter3(content);
9088
9549
  const stats = await fs6.stat(absolutePath);
9089
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path6.basename(absolutePath, ".md");
9550
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path7.basename(absolutePath, ".md");
9090
9551
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
9091
9552
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
9092
9553
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -9313,6 +9774,36 @@ async function heuristicProceduralTitle(workflow, stepTitles, provider) {
9313
9774
  function insightPagePath(tier, slugSource) {
9314
9775
  return `insights/${tier}/${slugify(slugSource)}.md`;
9315
9776
  }
9777
+ function extractStoredPageTags(stored) {
9778
+ try {
9779
+ const parsed = matter4(stored.content);
9780
+ const raw = parsed.data?.tags;
9781
+ if (!Array.isArray(raw)) return [];
9782
+ return raw.filter((value) => typeof value === "string" && value.length > 0);
9783
+ } catch {
9784
+ return [];
9785
+ }
9786
+ }
9787
+ function sortDerivedTagsForInsight(tags, leaders) {
9788
+ const seen = /* @__PURE__ */ new Set();
9789
+ const deduped = [];
9790
+ for (const tag of tags) {
9791
+ if (!tag || seen.has(tag)) continue;
9792
+ seen.add(tag);
9793
+ deduped.push(tag);
9794
+ }
9795
+ const pinned = [];
9796
+ const rest = [];
9797
+ for (const tag of deduped) {
9798
+ if (leaders.includes(tag)) continue;
9799
+ rest.push(tag);
9800
+ }
9801
+ for (const leader of leaders) {
9802
+ if (seen.has(leader)) pinned.push(leader);
9803
+ }
9804
+ rest.sort((left, right) => left.localeCompare(right));
9805
+ return [...pinned, ...rest];
9806
+ }
9316
9807
  function buildConsolidatedPage(input) {
9317
9808
  const { tier, title, summary, relativePath, sourcePages, confidence, now } = input;
9318
9809
  const pageId = `insight:${slugify(`${tier}-${relativePath.replace(/^insights\//, "").replace(/\.md$/, "")}`)}`;
@@ -9322,11 +9813,16 @@ function buildConsolidatedPage(input) {
9322
9813
  const relatedPageIds = sourcePages.map((page2) => page2.id);
9323
9814
  const consolidatedFromPageIds = sourcePages.map((page2) => page2.id);
9324
9815
  const createdAt = now.toISOString();
9816
+ const leaderTags = ["insight", tier];
9817
+ const projectTags = projectIds.map((projectId) => `project/${projectId}`);
9818
+ const inheritedTags = (input.inheritedTags ?? []).filter((tag) => typeof tag === "string" && tag.length > 0);
9819
+ const tags = sortDerivedTagsForInsight([...leaderTags, ...projectTags, ...inheritedTags], leaderTags);
9325
9820
  const frontmatter = {
9326
9821
  page_id: pageId,
9327
9822
  title,
9328
9823
  kind: "insight",
9329
9824
  tier,
9825
+ tags,
9330
9826
  consolidated_from_page_ids: consolidatedFromPageIds,
9331
9827
  consolidation_confidence: Math.max(0, Math.min(1, confidence)),
9332
9828
  source_ids: sourceIds,
@@ -9389,7 +9885,7 @@ function buildConsolidatedPage(input) {
9389
9885
  async function markSourcePagesSuperseded(wikiDir, sourcePages, newPageId) {
9390
9886
  const updatedPaths = [];
9391
9887
  for (const stored of sourcePages) {
9392
- const absolutePath = path7.join(wikiDir, stored.page.path);
9888
+ const absolutePath = path8.join(wikiDir, stored.page.path);
9393
9889
  if (!await fileExists(absolutePath)) {
9394
9890
  continue;
9395
9891
  }
@@ -9405,8 +9901,8 @@ async function markSourcePagesSuperseded(wikiDir, sourcePages, newPageId) {
9405
9901
  return updatedPaths;
9406
9902
  }
9407
9903
  async function writeConsolidatedPage(wikiDir, relativePath, content) {
9408
- const absolutePath = path7.join(wikiDir, relativePath);
9409
- await ensureDir(path7.dirname(absolutePath));
9904
+ const absolutePath = path8.join(wikiDir, relativePath);
9905
+ await ensureDir(path8.dirname(absolutePath));
9410
9906
  return writeFileIfChanged(absolutePath, content);
9411
9907
  }
9412
9908
  async function runConsolidation(rootDir, config = {}, provider, options = {}) {
@@ -9458,6 +9954,7 @@ async function runConsolidation(rootDir, config = {}, provider, options = {}) {
9458
9954
  summary: titleSummary.summary,
9459
9955
  relativePath,
9460
9956
  sourcePages: groupPages,
9957
+ inheritedTags: group.pages.flatMap((stored) => extractStoredPageTags(stored)),
9461
9958
  confidence,
9462
9959
  now
9463
9960
  });
@@ -9496,10 +9993,11 @@ async function runConsolidation(rootDir, config = {}, provider, options = {}) {
9496
9993
  summary: titleSummary.summary,
9497
9994
  relativePath,
9498
9995
  sourcePages: pages.map((stored) => stored.page),
9996
+ inheritedTags: pages.flatMap((stored) => extractStoredPageTags(stored)),
9499
9997
  confidence,
9500
9998
  now
9501
9999
  });
9502
- const existingPath = path7.join(wikiDir, relativePath);
10000
+ const existingPath = path8.join(wikiDir, relativePath);
9503
10001
  if (await fileExists(existingPath)) {
9504
10002
  decisions.push(` skip: semantic page already exists for node ${nodeId}`);
9505
10003
  continue;
@@ -9548,10 +10046,11 @@ async function runConsolidation(rootDir, config = {}, provider, options = {}) {
9548
10046
  summary: titleSummary.summary,
9549
10047
  relativePath,
9550
10048
  sourcePages: ordered.map((stored) => stored.page),
10049
+ inheritedTags: ordered.flatMap((stored) => extractStoredPageTags(stored)),
9551
10050
  confidence,
9552
10051
  now
9553
10052
  });
9554
- const existingPath = path7.join(wikiDir, relativePath);
10053
+ const existingPath = path8.join(wikiDir, relativePath);
9555
10054
  if (await fileExists(existingPath)) {
9556
10055
  decisions.push(` skip: procedural page already exists for workflow ${workflow}`);
9557
10056
  continue;
@@ -9588,7 +10087,7 @@ async function runConsolidation(rootDir, config = {}, provider, options = {}) {
9588
10087
 
9589
10088
  // src/freshness.ts
9590
10089
  import fs8 from "fs/promises";
9591
- import path8 from "path";
10090
+ import path9 from "path";
9592
10091
  import matter5 from "gray-matter";
9593
10092
  var DEFAULT_HALF_LIFE_DAYS = 365;
9594
10093
  var DEFAULT_STALE_THRESHOLD = 0.3;
@@ -9729,7 +10228,7 @@ async function persistDecayFrontmatter(wikiDir, pages) {
9729
10228
  if (page.managedBy === "human") {
9730
10229
  continue;
9731
10230
  }
9732
- const absolutePath = path8.join(wikiDir, page.path);
10231
+ const absolutePath = path9.join(wikiDir, page.path);
9733
10232
  if (!await fileExists(absolutePath)) {
9734
10233
  continue;
9735
10234
  }
@@ -9775,7 +10274,7 @@ async function runDecayPass(input) {
9775
10274
  import { readFileSync } from "fs";
9776
10275
  import fs9 from "fs/promises";
9777
10276
  import { createRequire as createRequire2 } from "module";
9778
- import path9 from "path";
10277
+ import path10 from "path";
9779
10278
  import matter6 from "gray-matter";
9780
10279
 
9781
10280
  // src/graph-interchange.ts
@@ -9843,7 +10342,8 @@ function normalizeSwarmNodeProps(node, page) {
9843
10342
  ...node.confidence !== void 0 ? { confidence: node.confidence } : {},
9844
10343
  ...node.degree !== void 0 ? { degree: node.degree } : {},
9845
10344
  ...node.bridgeScore !== void 0 ? { bridgeScore: node.bridgeScore } : {},
9846
- ...node.isGodNode !== void 0 ? { isGodNode: node.isGodNode } : {}
10345
+ ...node.isGodNode !== void 0 ? { isGodNode: node.isGodNode } : {},
10346
+ ...node.surpriseReason ? { surpriseReason: node.surpriseReason } : {}
9847
10347
  };
9848
10348
  }
9849
10349
  function normalizeHyperedgeNodeProps(hyperedge) {
@@ -9915,6 +10415,30 @@ function graphCounts(graph) {
9915
10415
  function htmlEscape(text) {
9916
10416
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
9917
10417
  }
10418
+ var SOURCE_CLASS_LABELS2 = {
10419
+ first_party: "First-party",
10420
+ third_party: "Third-party",
10421
+ resource: "Resource",
10422
+ generated: "Generated"
10423
+ };
10424
+ function renderBenchmarkByClassSection(report) {
10425
+ const byClass = report?.benchmark?.byClass;
10426
+ if (!byClass) {
10427
+ return "";
10428
+ }
10429
+ const rows = ALL_SOURCE_CLASSES.map((sourceClass) => {
10430
+ const entry = byClass[sourceClass];
10431
+ const reductionPct = (entry.reductionRatio * 100).toFixed(1);
10432
+ return `<tr><td>${htmlEscape(SOURCE_CLASS_LABELS2[sourceClass])}</td><td>${entry.sourceCount}</td><td>${entry.pageCount}</td><td>${entry.nodeCount}</td><td>${entry.godNodeCount}</td><td>${entry.naiveCorpusTokens}</td><td>${entry.finalContextTokens}</td><td>${reductionPct}%</td></tr>`;
10433
+ }).join("\n");
10434
+ return `<h2>Benchmark By Source Class</h2>
10435
+ <table>
10436
+ <thead><tr><th>Class</th><th>Sources</th><th>Pages</th><th>Nodes</th><th>God Nodes</th><th>Naive Tokens</th><th>Guided Tokens</th><th>Reduction</th></tr></thead>
10437
+ <tbody>
10438
+ ${rows}
10439
+ </tbody>
10440
+ </table>`;
10441
+ }
9918
10442
  function nodeTypeColor(type) {
9919
10443
  const colors = {
9920
10444
  source: "#f59e0b",
@@ -10188,6 +10712,8 @@ ${warnings.length ? `<h2>Warnings</h2>
10188
10712
  ${warnings.map((w) => `<li>${htmlEscape(w)}</li>`).join("\n")}
10189
10713
  </ul>` : ""}
10190
10714
 
10715
+ ${renderBenchmarkByClassSection(report)}
10716
+
10191
10717
  <h2>Pages</h2>
10192
10718
  <input type="text" id="page-filter" placeholder="Filter pages..." />
10193
10719
  <div id="pages-container">
@@ -10226,11 +10752,43 @@ var _visNetworkJs;
10226
10752
  function loadVisNetworkJs() {
10227
10753
  if (!_visNetworkJs) {
10228
10754
  const require3 = createRequire2(import.meta.url);
10229
- const pkgDir = path9.dirname(require3.resolve("vis-network/package.json"));
10230
- _visNetworkJs = readFileSync(path9.join(pkgDir, "standalone/umd/vis-network.min.js"), "utf8");
10755
+ const pkgDir = path10.dirname(require3.resolve("vis-network/package.json"));
10756
+ _visNetworkJs = readFileSync(path10.join(pkgDir, "standalone/umd/vis-network.min.js"), "utf8");
10231
10757
  }
10232
10758
  return _visNetworkJs;
10233
10759
  }
10760
+ function synthesizeHyperedgeHubs(hyperedges, nodes) {
10761
+ const nodeIds = new Set(nodes.map((node) => node.id));
10762
+ const hubNodes = [];
10763
+ const hubEdges = [];
10764
+ for (const hyperedge of hyperedges) {
10765
+ const participantIds = hyperedge.nodeIds.filter((nodeId) => nodeIds.has(nodeId));
10766
+ if (participantIds.length < 2) continue;
10767
+ const hubId = `hyper:${hyperedge.id}`;
10768
+ hubNodes.push({
10769
+ id: hubId,
10770
+ hyperedgeId: hyperedge.id,
10771
+ label: hyperedge.relation,
10772
+ relation: hyperedge.relation,
10773
+ participantIds,
10774
+ confidence: hyperedge.confidence ?? 0,
10775
+ evidenceClass: hyperedge.evidenceClass ?? "inferred",
10776
+ why: hyperedge.why ?? ""
10777
+ });
10778
+ for (const participantId of participantIds) {
10779
+ hubEdges.push({
10780
+ id: `hyper-edge:${hyperedge.id}:${participantId}`,
10781
+ hyperedgeId: hyperedge.id,
10782
+ source: hubId,
10783
+ target: participantId,
10784
+ relation: hyperedge.relation,
10785
+ confidence: hyperedge.confidence ?? 0,
10786
+ evidenceClass: hyperedge.evidenceClass ?? "inferred"
10787
+ });
10788
+ }
10789
+ }
10790
+ return { hubNodes, hubEdges };
10791
+ }
10234
10792
  function hexToObsidianColor(hex) {
10235
10793
  return { a: 1, rgb: Number.parseInt(hex.replace("#", ""), 16) };
10236
10794
  }
@@ -10637,7 +11195,8 @@ function renderHtmlStandalone(graph) {
10637
11195
  bridgeScore: node.bridgeScore ?? null,
10638
11196
  confidence: node.confidence ?? null,
10639
11197
  sourceClass: node.sourceClass ?? null,
10640
- tags: node.tags ?? []
11198
+ tags: node.tags ?? [],
11199
+ pageId: node.pageId ?? null
10641
11200
  }));
10642
11201
  const edgesData = cappedEdges.map((edge) => ({
10643
11202
  id: edge.id,
@@ -10652,10 +11211,67 @@ function renderHtmlStandalone(graph) {
10652
11211
  label: c.label,
10653
11212
  nodeIds: c.nodeIds
10654
11213
  }));
11214
+ const corePagesData = graph.pages.filter((page) => page.nodeIds.some((nodeId) => cappedNodeIds.has(nodeId))).map((page) => ({ id: page.id, path: page.path, title: page.title }));
11215
+ const coreHyperedgesData = (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.nodeIds.some((nodeId) => cappedNodeIds.has(nodeId))).map((hyperedge) => ({
11216
+ id: hyperedge.id,
11217
+ label: hyperedge.label,
11218
+ relation: hyperedge.relation,
11219
+ nodeIds: hyperedge.nodeIds,
11220
+ confidence: hyperedge.confidence,
11221
+ evidenceClass: hyperedge.evidenceClass,
11222
+ why: hyperedge.why
11223
+ }));
11224
+ const coreNodesData = cappedNodes.map((node) => ({
11225
+ id: node.id,
11226
+ label: node.label,
11227
+ type: node.type,
11228
+ pageId: node.pageId ?? null,
11229
+ communityId: node.communityId ?? null,
11230
+ degree: node.degree ?? 0,
11231
+ confidence: node.confidence ?? null
11232
+ }));
11233
+ const coreEdgesData = cappedEdges.map((edge) => ({
11234
+ id: edge.id,
11235
+ source: edge.source,
11236
+ target: edge.target,
11237
+ relation: edge.relation,
11238
+ evidenceClass: edge.evidenceClass,
11239
+ confidence: edge.confidence
11240
+ }));
11241
+ const { hubNodes, hubEdges } = synthesizeHyperedgeHubs(graph.hyperedges ?? [], cappedNodes);
11242
+ const hubNodesData = hubNodes.map((hub) => ({
11243
+ id: hub.id,
11244
+ label: hub.label,
11245
+ type: "hyperedge",
11246
+ isHub: true,
11247
+ hyperedgeId: hub.hyperedgeId,
11248
+ relation: hub.relation,
11249
+ confidence: hub.confidence,
11250
+ evidenceClass: hub.evidenceClass
11251
+ }));
11252
+ const hubEdgesData = hubEdges.map((edge) => ({
11253
+ id: edge.id,
11254
+ from: edge.source,
11255
+ to: edge.target,
11256
+ relation: edge.relation,
11257
+ evidenceClass: edge.evidenceClass,
11258
+ confidence: edge.confidence,
11259
+ isHubEdge: true,
11260
+ hyperedgeId: edge.hyperedgeId
11261
+ }));
10655
11262
  const graphJson = JSON.stringify({
10656
- nodes: nodesData,
10657
- edges: edgesData,
10658
- communities: communitiesData
11263
+ nodes: [...nodesData, ...hubNodesData],
11264
+ edges: [...edgesData, ...hubEdgesData],
11265
+ communities: communitiesData,
11266
+ // Core payload for the inline query/path/explain runtime. Kept separate
11267
+ // from the vis.js payload so hub scaffolding never leaks into traversal.
11268
+ core: {
11269
+ nodes: coreNodesData,
11270
+ edges: coreEdgesData,
11271
+ pages: corePagesData,
11272
+ hyperedges: coreHyperedgesData,
11273
+ communities: communitiesData
11274
+ }
10659
11275
  });
10660
11276
  return `<!doctype html>
10661
11277
  <html lang="en">
@@ -10681,6 +11297,21 @@ function renderHtmlStandalone(graph) {
10681
11297
  #legend .item { display: flex; align-items: center; gap: 6px; font-size: 12px; margin: 3px 0; }
10682
11298
  #legend .dot { width: 10px; height: 10px; border-radius: 50%; }
10683
11299
  #stats { position: absolute; top: 12px; right: 340px; z-index: 10; background: #1e293b; padding: 8px 12px; border-radius: 6px; font-size: 12px; color: #94a3b8; border: 1px solid #334155; }
11300
+ #tools { position: absolute; top: 52px; left: 12px; z-index: 10; width: 300px; background: #1e293b; padding: 12px; border-radius: 8px; border: 1px solid #334155; max-height: calc(100vh - 80px); overflow-y: auto; }
11301
+ #tools h3 { font-size: 13px; color: #f8fafc; margin-bottom: 6px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
11302
+ #tools .panel { border-top: 1px solid #334155; padding-top: 10px; margin-top: 10px; }
11303
+ #tools .panel:first-of-type { border-top: none; margin-top: 0; padding-top: 0; }
11304
+ #tools .row { display: flex; gap: 6px; margin-bottom: 6px; }
11305
+ #tools .row.radio { gap: 12px; font-size: 12px; color: #cbd5e1; }
11306
+ #tools input[type=text] { flex: 1; padding: 6px 8px; border-radius: 4px; border: 1px solid #475569; background: #0f172a; color: #e2e8f0; font-size: 12px; }
11307
+ #tools button { padding: 6px 10px; border-radius: 4px; border: 1px solid #475569; background: #334155; color: #e2e8f0; font-size: 12px; cursor: pointer; }
11308
+ #tools button:hover { background: #475569; }
11309
+ #tools .result { font-size: 12px; color: #cbd5e1; margin-top: 6px; line-height: 1.4; white-space: pre-wrap; }
11310
+ #tools .result .hdr { color: #f8fafc; font-weight: 600; margin-top: 6px; }
11311
+ #tools .result ol, #tools .result ul { padding-left: 18px; margin: 4px 0; }
11312
+ #tools .result .item { color: #38bdf8; cursor: pointer; text-decoration: underline; }
11313
+ #tools .result .relation { color: #94a3b8; font-size: 11px; }
11314
+ #tools .result .error { color: #f87171; }
10684
11315
  </style>
10685
11316
  </head>
10686
11317
  <body>
@@ -10692,6 +11323,41 @@ function renderHtmlStandalone(graph) {
10692
11323
  <div id="sidebarFields"></div>
10693
11324
  <div class="neighbors" id="sidebarNeighbors"></div>
10694
11325
  </div>
11326
+ <div id="tools" data-testid="graph-tools">
11327
+ <section class="panel" data-testid="graph-query-panel">
11328
+ <h3>Query</h3>
11329
+ <div class="row">
11330
+ <input type="text" id="queryInput" data-testid="graph-query-input" placeholder="Ask a question about the graph..." />
11331
+ <button type="button" id="queryRun" data-testid="graph-query-run">Run</button>
11332
+ </div>
11333
+ <div class="row radio">
11334
+ <label><input type="radio" name="queryTraversal" value="bfs" checked /> BFS</label>
11335
+ <label><input type="radio" name="queryTraversal" value="dfs" /> DFS</label>
11336
+ </div>
11337
+ <div class="result" id="queryResult" data-testid="graph-query-result"></div>
11338
+ </section>
11339
+ <section class="panel" data-testid="graph-path-panel">
11340
+ <h3>Path</h3>
11341
+ <div class="row">
11342
+ <input type="text" id="pathFrom" data-testid="graph-path-from" placeholder="From node id or label..." />
11343
+ </div>
11344
+ <div class="row">
11345
+ <input type="text" id="pathTo" data-testid="graph-path-to" placeholder="To node id or label..." />
11346
+ </div>
11347
+ <div class="row">
11348
+ <button type="button" id="pathFind" data-testid="graph-path-find">Find</button>
11349
+ </div>
11350
+ <div class="result" id="pathResult" data-testid="graph-path-result"></div>
11351
+ </section>
11352
+ <section class="panel" data-testid="graph-explain-panel">
11353
+ <h3>Explain</h3>
11354
+ <div class="row">
11355
+ <input type="text" id="explainInput" data-testid="graph-explain-input" placeholder="Node id or label..." />
11356
+ <button type="button" id="explainRun" data-testid="graph-explain-run">Explain</button>
11357
+ </div>
11358
+ <div class="result" id="explainResult" data-testid="graph-explain-result"></div>
11359
+ </section>
11360
+ </div>
10695
11361
  <div id="legend"></div>
10696
11362
  <script>
10697
11363
  var GRAPH_DATA = ${graphJson};
@@ -10722,6 +11388,22 @@ function renderHtmlStandalone(graph) {
10722
11388
  GRAPH_DATA.nodes.forEach(function(n) { nodeMap[n.id] = n; });
10723
11389
 
10724
11390
  var visNodes = new vis.DataSet(GRAPH_DATA.nodes.map(function(n) {
11391
+ if (n.isHub) {
11392
+ // Hub nodes are viewer-only scaffolding \u2014 keep them small, dashed,
11393
+ // and painted with a secondary accent so they read as grouping
11394
+ // glue rather than first-class entities.
11395
+ return {
11396
+ id: n.id,
11397
+ label: n.label,
11398
+ shape: "dot",
11399
+ color: { background: "#0f172a", border: "#a78bfa" },
11400
+ size: 10,
11401
+ font: { color: "#c4b5fd", size: 10 },
11402
+ borderWidth: 2,
11403
+ borderWidthSelected: 3,
11404
+ shapeProperties: { borderDashes: [4, 3] }
11405
+ };
11406
+ }
10725
11407
  var size = 8 + Math.min(32, n.degree * 2);
10726
11408
  return {
10727
11409
  id: n.id,
@@ -10734,6 +11416,17 @@ function renderHtmlStandalone(graph) {
10734
11416
  }));
10735
11417
 
10736
11418
  var visEdges = new vis.DataSet(GRAPH_DATA.edges.map(function(e) {
11419
+ if (e.isHubEdge) {
11420
+ return {
11421
+ id: e.id,
11422
+ from: e.from,
11423
+ to: e.to,
11424
+ color: { color: "#a78bfa", opacity: 0.5 },
11425
+ width: 1,
11426
+ dashes: [4, 3],
11427
+ arrows: { to: { enabled: false } }
11428
+ };
11429
+ }
10737
11430
  var dashed = (e.evidenceClass === "inferred" || e.evidenceClass === "ambiguous");
10738
11431
  return {
10739
11432
  id: e.id,
@@ -10833,44 +11526,555 @@ function renderHtmlStandalone(graph) {
10833
11526
  link.textContent = nbLabel + " (" + nb.relation + ")";
10834
11527
  sidebarNeighbors.appendChild(link);
10835
11528
  });
10836
- } else {
10837
- renderField(sidebarNeighbors, "Neighbors", "none");
11529
+ } else {
11530
+ renderField(sidebarNeighbors, "Neighbors", "none");
11531
+ }
11532
+
11533
+ sidebar.classList.add("open");
11534
+ });
11535
+
11536
+ sidebarNeighbors.addEventListener("click", function(e) {
11537
+ var target = e.target;
11538
+ while (target && target !== sidebarNeighbors) {
11539
+ if (target.dataset && target.dataset.nodeId) {
11540
+ var nid = target.dataset.nodeId;
11541
+ network.selectNodes([nid]);
11542
+ network.focus(nid, { scale: 1.2, animation: { duration: 400 } });
11543
+ network.body.emitter.emit("click", { nodes: [nid] });
11544
+ return;
11545
+ }
11546
+ target = target.parentElement;
11547
+ }
11548
+ });
11549
+
11550
+ document.getElementById("searchInput").addEventListener("input", function() {
11551
+ var query = this.value.toLowerCase().trim();
11552
+ if (!query) {
11553
+ visNodes.forEach(function(n) {
11554
+ visNodes.update({ id: n.id, opacity: 1.0, font: { color: "#e2e8f0", size: 11 } });
11555
+ });
11556
+ return;
11557
+ }
11558
+ visNodes.forEach(function(n) {
11559
+ var match = n.label.toLowerCase().indexOf(query) !== -1;
11560
+ visNodes.update({
11561
+ id: n.id,
11562
+ opacity: match ? 1.0 : 0.15,
11563
+ font: { color: match ? "#f8fafc" : "#475569", size: match ? 13 : 9 }
11564
+ });
11565
+ });
11566
+ });
11567
+
11568
+ // ---------------------------------------------------------------------
11569
+ // Embedded graph query/path/explain runtime.
11570
+ //
11571
+ // Dependency-free port of the graph-query-core helpers that the live
11572
+ // graph serve / MCP surface uses. Operates only on the GRAPH_DATA.core
11573
+ // payload (real nodes/edges/pages/hyperedges/communities) so viewer-only
11574
+ // hub scaffolding never leaks into traversal. No network calls; no
11575
+ // provider-backed features.
11576
+ // ---------------------------------------------------------------------
11577
+ var CORE = GRAPH_DATA.core;
11578
+ var CORE_NODE_TYPE_PRIORITY = { concept: 6, entity: 5, source: 4, module: 3, symbol: 2, rationale: 1 };
11579
+
11580
+ function coreNormalize(value) {
11581
+ if (value == null) return "";
11582
+ return String(value).replace(/\\s+/g, " ").trim().normalize("NFKD").replace(/[\\u0300-\\u036f]+/g, "").toLowerCase();
11583
+ }
11584
+
11585
+ function coreScore(query, candidate) {
11586
+ var q = coreNormalize(query);
11587
+ var c = coreNormalize(candidate);
11588
+ if (!q || !c) return 0;
11589
+ if (c === q) return 100;
11590
+ if (c.indexOf(q) === 0) return 80;
11591
+ if (c.indexOf(q) !== -1) return 60;
11592
+ var qTokens = q.split(/\\s+/).filter(Boolean);
11593
+ var cTokens = {};
11594
+ c.split(/\\s+/).filter(Boolean).forEach(function(tok) { cTokens[tok] = true; });
11595
+ var overlap = 0;
11596
+ qTokens.forEach(function(tok) { if (cTokens[tok]) overlap++; });
11597
+ return overlap ? overlap * 10 : 0;
11598
+ }
11599
+
11600
+ function coreUnique(values) {
11601
+ var seen = {};
11602
+ var out = [];
11603
+ for (var i = 0; i < values.length; i++) {
11604
+ var v = values[i];
11605
+ if (!v) continue;
11606
+ if (seen[v]) continue;
11607
+ seen[v] = true;
11608
+ out.push(v);
11609
+ }
11610
+ return out;
11611
+ }
11612
+
11613
+ function coreBuildAdjacency() {
11614
+ var adj = {};
11615
+ function push(id, item) {
11616
+ if (!adj[id]) adj[id] = [];
11617
+ adj[id].push(item);
11618
+ }
11619
+ for (var i = 0; i < CORE.edges.length; i++) {
11620
+ var edge = CORE.edges[i];
11621
+ push(edge.source, { edge: edge, nodeId: edge.target, direction: "outgoing" });
11622
+ push(edge.target, { edge: edge, nodeId: edge.source, direction: "incoming" });
11623
+ }
11624
+ Object.keys(adj).forEach(function(nid) {
11625
+ adj[nid].sort(function(a, b) {
11626
+ return (b.edge.confidence - a.edge.confidence) || a.edge.relation.localeCompare(b.edge.relation);
11627
+ });
11628
+ });
11629
+ return adj;
11630
+ }
11631
+
11632
+ var CORE_ADJ = coreBuildAdjacency();
11633
+ var CORE_NODE_BY_ID = {};
11634
+ CORE.nodes.forEach(function(n) { CORE_NODE_BY_ID[n.id] = n; });
11635
+ var CORE_PAGE_BY_ID = {};
11636
+ (CORE.pages || []).forEach(function(p) { CORE_PAGE_BY_ID[p.id] = p; });
11637
+ var CORE_COMM_BY_ID = {};
11638
+ (CORE.communities || []).forEach(function(c) { CORE_COMM_BY_ID[c.id] = c; });
11639
+
11640
+ function coreCompareLabel(a, b) {
11641
+ var pa = CORE_NODE_TYPE_PRIORITY[a.type] || 0;
11642
+ var pb = CORE_NODE_TYPE_PRIORITY[b.type] || 0;
11643
+ if (pb !== pa) return pb - pa;
11644
+ var da = a.degree || 0;
11645
+ var db = b.degree || 0;
11646
+ if (db !== da) return db - da;
11647
+ return a.id.localeCompare(b.id);
11648
+ }
11649
+
11650
+ function coreResolveNode(target) {
11651
+ if (CORE_NODE_BY_ID[target]) return CORE_NODE_BY_ID[target];
11652
+ var normalized = coreNormalize(target);
11653
+ var labelMatches = CORE.nodes.filter(function(n) {
11654
+ return coreNormalize(n.label) === normalized || coreNormalize(n.id) === normalized;
11655
+ });
11656
+ if (labelMatches.length) {
11657
+ return labelMatches.slice().sort(coreCompareLabel)[0];
11658
+ }
11659
+ var pageHit = (CORE.pages || [])
11660
+ .map(function(p) {
11661
+ return { page: p, score: Math.max(coreScore(target, p.title), coreScore(target, p.path)) };
11662
+ })
11663
+ .filter(function(item) { return item.score > 0; })
11664
+ .sort(function(left, right) {
11665
+ return (right.score - left.score) || left.page.title.localeCompare(right.page.title);
11666
+ })[0];
11667
+ if (pageHit) {
11668
+ var primary = CORE.nodes.filter(function(n) { return n.pageId === pageHit.page.id; })[0];
11669
+ if (primary) return primary;
11670
+ }
11671
+ var fuzzy = CORE.nodes
11672
+ .map(function(n) { return { node: n, score: Math.max(coreScore(target, n.label), coreScore(target, n.id)) }; })
11673
+ .filter(function(item) { return item.score > 0; })
11674
+ .sort(function(left, right) {
11675
+ return (right.score - left.score) || coreCompareLabel(left.node, right.node);
11676
+ })[0];
11677
+ return fuzzy ? fuzzy.node : undefined;
11678
+ }
11679
+
11680
+ function coreUniqueMatches(matches) {
11681
+ var seen = {};
11682
+ var out = [];
11683
+ for (var i = 0; i < matches.length; i++) {
11684
+ var m = matches[i];
11685
+ var key = m.type + ":" + m.id;
11686
+ if (seen[key]) continue;
11687
+ seen[key] = true;
11688
+ out.push(m);
11689
+ }
11690
+ return out;
11691
+ }
11692
+
11693
+ function runGraphQuery(question, traversalOpt, budgetOpt) {
11694
+ var traversal = traversalOpt === "dfs" ? "dfs" : "bfs";
11695
+ var budget = Math.max(3, Math.min((budgetOpt != null ? budgetOpt : 12), 50));
11696
+ var pageMatches = (CORE.pages || [])
11697
+ .map(function(p) { return { type: "page", id: p.id, label: p.title, score: Math.max(coreScore(question, p.title), coreScore(question, p.path)) }; })
11698
+ .filter(function(m) { return m.score > 0; });
11699
+ var nodeMatches = CORE.nodes
11700
+ .map(function(n) { return { type: "node", id: n.id, label: n.label, score: Math.max(coreScore(question, n.label), coreScore(question, n.id)) }; })
11701
+ .filter(function(m) { return m.score > 0; });
11702
+ var hyperMatches = (CORE.hyperedges || [])
11703
+ .map(function(h) { return { type: "hyperedge", id: h.id, label: h.label, score: Math.max(coreScore(question, h.label), coreScore(question, h.why || ""), coreScore(question, h.relation)) }; })
11704
+ .filter(function(m) { return m.score > 0; });
11705
+ var matches = coreUniqueMatches(pageMatches.concat(nodeMatches).concat(hyperMatches))
11706
+ .sort(function(a, b) { return (b.score - a.score) || a.label.localeCompare(b.label); })
11707
+ .slice(0, 12);
11708
+
11709
+ var nodesByPageId = {};
11710
+ CORE.nodes.forEach(function(n) {
11711
+ if (!n.pageId) return;
11712
+ if (!nodesByPageId[n.pageId]) nodesByPageId[n.pageId] = [];
11713
+ nodesByPageId[n.pageId].push(n.id);
11714
+ });
11715
+
11716
+ var seedList = [];
11717
+ matches.forEach(function(m) {
11718
+ if (m.type === "page") {
11719
+ (nodesByPageId[m.id] || []).forEach(function(id) { seedList.push(id); });
11720
+ } else if (m.type === "node") {
11721
+ seedList.push(m.id);
11722
+ } else if (m.type === "hyperedge") {
11723
+ var hy = (CORE.hyperedges || []).filter(function(h) { return h.id === m.id; })[0];
11724
+ if (hy) hy.nodeIds.forEach(function(id) { seedList.push(id); });
11725
+ }
11726
+ });
11727
+ var seeds = coreUnique(seedList);
11728
+
11729
+ var visitedNodeIds = [];
11730
+ var visitedEdgeIds = {};
11731
+ var seen = {};
11732
+ var frontier = seeds.slice();
11733
+ while (frontier.length && visitedNodeIds.length < budget) {
11734
+ var current = traversal === "dfs" ? frontier.pop() : frontier.shift();
11735
+ if (!current || seen[current]) continue;
11736
+ seen[current] = true;
11737
+ visitedNodeIds.push(current);
11738
+ var adj = CORE_ADJ[current] || [];
11739
+ for (var i = 0; i < adj.length; i++) {
11740
+ var nb = adj[i];
11741
+ visitedEdgeIds[nb.edge.id] = true;
11742
+ if (!seen[nb.nodeId]) frontier.push(nb.nodeId);
11743
+ if (visitedNodeIds.length + frontier.length >= budget * 2) break;
11744
+ }
11745
+ }
11746
+
11747
+ var pageIdsList = [];
11748
+ matches.forEach(function(m) { if (m.type === "page") pageIdsList.push(m.id); });
11749
+ visitedNodeIds.forEach(function(nid) {
11750
+ var n = CORE_NODE_BY_ID[nid];
11751
+ if (n && n.pageId) pageIdsList.push(n.pageId);
11752
+ });
11753
+ var pageIds = coreUnique(pageIdsList);
11754
+ var communities = coreUnique(
11755
+ visitedNodeIds.map(function(nid) { return CORE_NODE_BY_ID[nid] && CORE_NODE_BY_ID[nid].communityId; }).filter(Boolean)
11756
+ );
11757
+ var hyperedgeIds = coreUnique(
11758
+ (CORE.hyperedges || [])
11759
+ .filter(function(h) { return h.nodeIds.some(function(nid) { return visitedNodeIds.indexOf(nid) !== -1; }); })
11760
+ .map(function(h) { return h.id; })
11761
+ );
11762
+ var seedPageIds = coreUnique(matches.filter(function(m) { return m.type === "page"; }).map(function(m) { return m.id; }));
11763
+ var visitedEdgeIdList = Object.keys(visitedEdgeIds);
11764
+
11765
+ var summary = [
11766
+ "Seeds: " + (seeds.join(", ") || "none"),
11767
+ "Visited nodes: " + visitedNodeIds.length,
11768
+ "Visited edges: " + visitedEdgeIdList.length,
11769
+ "Touched group patterns: " + hyperedgeIds.length,
11770
+ "Communities: " + (communities.join(", ") || "none"),
11771
+ "Pages: " + (pageIds.join(", ") || "none")
11772
+ ].join("\\n");
11773
+
11774
+ return {
11775
+ question: question,
11776
+ traversal: traversal,
11777
+ seedNodeIds: seeds,
11778
+ seedPageIds: seedPageIds,
11779
+ visitedNodeIds: visitedNodeIds,
11780
+ visitedEdgeIds: visitedEdgeIdList,
11781
+ hyperedgeIds: hyperedgeIds,
11782
+ pageIds: pageIds,
11783
+ communities: communities,
11784
+ matches: matches,
11785
+ summary: summary
11786
+ };
11787
+ }
11788
+
11789
+ function runGraphPath(from, to) {
11790
+ var start = coreResolveNode(from);
11791
+ var end = coreResolveNode(to);
11792
+ if (!start || !end) {
11793
+ return {
11794
+ from: from,
11795
+ to: to,
11796
+ resolvedFromNodeId: start ? start.id : undefined,
11797
+ resolvedToNodeId: end ? end.id : undefined,
11798
+ found: false,
11799
+ nodeIds: [],
11800
+ edgeIds: [],
11801
+ pageIds: [],
11802
+ summary: "Could not resolve one or both graph targets."
11803
+ };
11804
+ }
11805
+ var queue = [start.id];
11806
+ var visited = {}; visited[start.id] = true;
11807
+ var previous = {};
11808
+ while (queue.length) {
11809
+ var current = queue.shift();
11810
+ if (current === end.id) break;
11811
+ var adj = CORE_ADJ[current] || [];
11812
+ for (var i = 0; i < adj.length; i++) {
11813
+ var nb = adj[i];
11814
+ if (visited[nb.nodeId]) continue;
11815
+ visited[nb.nodeId] = true;
11816
+ previous[nb.nodeId] = { nodeId: current, edgeId: nb.edge.id };
11817
+ queue.push(nb.nodeId);
11818
+ }
11819
+ }
11820
+ if (!visited[end.id]) {
11821
+ return {
11822
+ from: from,
11823
+ to: to,
11824
+ resolvedFromNodeId: start.id,
11825
+ resolvedToNodeId: end.id,
11826
+ found: false,
11827
+ nodeIds: [],
11828
+ edgeIds: [],
11829
+ pageIds: [],
11830
+ summary: "No path found between " + start.label + " and " + end.label + "."
11831
+ };
11832
+ }
11833
+ var nodeIds = [];
11834
+ var edgeIds = [];
11835
+ var cursor = end.id;
11836
+ while (cursor !== start.id) {
11837
+ nodeIds.push(cursor);
11838
+ var prev = previous[cursor];
11839
+ if (!prev) break;
11840
+ edgeIds.push(prev.edgeId);
11841
+ cursor = prev.nodeId;
11842
+ }
11843
+ nodeIds.push(start.id);
11844
+ nodeIds.reverse();
11845
+ edgeIds.reverse();
11846
+ var pageIds = coreUnique(nodeIds.map(function(nid) { return CORE_NODE_BY_ID[nid] && CORE_NODE_BY_ID[nid].pageId; }).filter(Boolean));
11847
+ var summary = nodeIds.map(function(nid) { return (CORE_NODE_BY_ID[nid] && CORE_NODE_BY_ID[nid].label) || nid; }).join(" -> ");
11848
+ return {
11849
+ from: from,
11850
+ to: to,
11851
+ resolvedFromNodeId: start.id,
11852
+ resolvedToNodeId: end.id,
11853
+ found: true,
11854
+ nodeIds: nodeIds,
11855
+ edgeIds: edgeIds,
11856
+ pageIds: pageIds,
11857
+ summary: summary
11858
+ };
11859
+ }
11860
+
11861
+ function runGraphExplain(target) {
11862
+ var node = coreResolveNode(target);
11863
+ if (!node) return undefined;
11864
+ var neighbors = [];
11865
+ var adj = CORE_ADJ[node.id] || [];
11866
+ for (var i = 0; i < adj.length; i++) {
11867
+ var nb = adj[i];
11868
+ var t = CORE_NODE_BY_ID[nb.nodeId];
11869
+ if (!t) continue;
11870
+ neighbors.push({
11871
+ nodeId: t.id,
11872
+ label: t.label,
11873
+ type: t.type,
11874
+ pageId: t.pageId || undefined,
11875
+ relation: nb.edge.relation,
11876
+ direction: nb.direction,
11877
+ confidence: nb.edge.confidence,
11878
+ evidenceClass: nb.edge.evidenceClass
11879
+ });
11880
+ }
11881
+ neighbors.sort(function(a, b) { return (b.confidence - a.confidence) || a.label.localeCompare(b.label); });
11882
+ var page = node.pageId ? CORE_PAGE_BY_ID[node.pageId] : undefined;
11883
+ var community = node.communityId ? CORE_COMM_BY_ID[node.communityId] : undefined;
11884
+ var hyperedges = (CORE.hyperedges || [])
11885
+ .filter(function(h) { return h.nodeIds.indexOf(node.id) !== -1; })
11886
+ .slice()
11887
+ .sort(function(a, b) { return (b.confidence - a.confidence) || a.label.localeCompare(b.label); });
11888
+ var summary = [
11889
+ "Node: " + node.label,
11890
+ "Type: " + node.type,
11891
+ "Community: " + (node.communityId || "none"),
11892
+ "Neighbors: " + neighbors.length,
11893
+ "Group patterns: " + hyperedges.length,
11894
+ "Page: " + (page ? page.path : "none")
11895
+ ].join("\\n");
11896
+ return {
11897
+ target: target,
11898
+ node: node,
11899
+ page: page,
11900
+ community: community ? { id: community.id, label: community.label } : undefined,
11901
+ neighbors: neighbors,
11902
+ hyperedges: hyperedges,
11903
+ summary: summary
11904
+ };
11905
+ }
11906
+
11907
+ // Expose helpers for test harnesses and browser console introspection.
11908
+ window.runGraphQuery = runGraphQuery;
11909
+ window.runGraphPath = runGraphPath;
11910
+ window.runGraphExplain = runGraphExplain;
11911
+
11912
+ function focusNode(nodeId) {
11913
+ try {
11914
+ network.selectNodes([nodeId]);
11915
+ network.focus(nodeId, { scale: 1.2, animation: { duration: 300 } });
11916
+ network.body.emitter.emit("click", { nodes: [nodeId] });
11917
+ } catch (err) {
11918
+ // ignore \u2014 focus is best effort in static exports
11919
+ }
11920
+ }
11921
+
11922
+ function renderList(parent, items, onClick) {
11923
+ items.forEach(function(entry) {
11924
+ var line = document.createElement("div");
11925
+ line.className = "item";
11926
+ line.textContent = entry.text;
11927
+ line.addEventListener("click", function() { if (onClick) onClick(entry.id); });
11928
+ parent.appendChild(line);
11929
+ });
11930
+ }
11931
+
11932
+ function renderQueryPanel(result) {
11933
+ var host = document.getElementById("queryResult");
11934
+ host.textContent = "";
11935
+ if (!result) return;
11936
+ var summaryEl = document.createElement("div");
11937
+ summaryEl.textContent = result.summary;
11938
+ host.appendChild(summaryEl);
11939
+ if (result.visitedNodeIds.length) {
11940
+ var hdr = document.createElement("div");
11941
+ hdr.className = "hdr";
11942
+ hdr.textContent = "Visited (" + result.traversal.toUpperCase() + ")";
11943
+ host.appendChild(hdr);
11944
+ renderList(host, result.visitedNodeIds.map(function(nid, idx) {
11945
+ var n = CORE_NODE_BY_ID[nid];
11946
+ return { id: nid, text: (idx + 1) + ". " + ((n && n.label) || nid) };
11947
+ }), focusNode);
11948
+ }
11949
+ }
11950
+
11951
+ function renderPathPanel(result) {
11952
+ var host = document.getElementById("pathResult");
11953
+ host.textContent = "";
11954
+ if (!result) return;
11955
+ var summaryEl = document.createElement("div");
11956
+ summaryEl.textContent = result.summary;
11957
+ host.appendChild(summaryEl);
11958
+ if (result.found && result.nodeIds.length) {
11959
+ var edgeById = {};
11960
+ CORE.edges.forEach(function(e) { edgeById[e.id] = e; });
11961
+ var ol = document.createElement("ol");
11962
+ for (var i = 0; i < result.nodeIds.length; i++) {
11963
+ var nid = result.nodeIds[i];
11964
+ var n = CORE_NODE_BY_ID[nid];
11965
+ var li = document.createElement("li");
11966
+ var btn = document.createElement("span");
11967
+ btn.className = "item";
11968
+ btn.textContent = (n && n.label) || nid;
11969
+ (function(targetId) { btn.addEventListener("click", function() { focusNode(targetId); }); })(nid);
11970
+ li.appendChild(btn);
11971
+ if (i < result.edgeIds.length) {
11972
+ var edge = edgeById[result.edgeIds[i]];
11973
+ if (edge) {
11974
+ var rel = document.createElement("span");
11975
+ rel.className = "relation";
11976
+ rel.textContent = " -[" + edge.relation + "]-> ";
11977
+ li.appendChild(rel);
11978
+ }
11979
+ }
11980
+ ol.appendChild(li);
11981
+ }
11982
+ host.appendChild(ol);
11983
+ }
11984
+ }
11985
+
11986
+ function renderExplainPanel(result, target) {
11987
+ var host = document.getElementById("explainResult");
11988
+ host.textContent = "";
11989
+ if (!result) {
11990
+ var err = document.createElement("div");
11991
+ err.className = "error";
11992
+ err.textContent = "Could not resolve graph target: " + target;
11993
+ host.appendChild(err);
11994
+ return;
11995
+ }
11996
+ var summaryEl = document.createElement("div");
11997
+ summaryEl.textContent = result.summary;
11998
+ host.appendChild(summaryEl);
11999
+ if (result.neighbors.length) {
12000
+ var byRel = {};
12001
+ result.neighbors.forEach(function(nb) {
12002
+ if (!byRel[nb.relation]) byRel[nb.relation] = [];
12003
+ byRel[nb.relation].push(nb);
12004
+ });
12005
+ Object.keys(byRel).sort().forEach(function(rel) {
12006
+ var hdr = document.createElement("div");
12007
+ hdr.className = "hdr";
12008
+ hdr.textContent = rel + " (" + byRel[rel].length + ")";
12009
+ host.appendChild(hdr);
12010
+ renderList(host, byRel[rel].map(function(nb) {
12011
+ var arrow = nb.direction === "incoming" ? "<- " : "-> ";
12012
+ return { id: nb.nodeId, text: arrow + nb.label + " [" + nb.evidenceClass + ", " + nb.confidence.toFixed(2) + "]" };
12013
+ }), focusNode);
12014
+ });
12015
+ }
12016
+ if (result.community) {
12017
+ var ch = document.createElement("div");
12018
+ ch.className = "hdr";
12019
+ ch.textContent = "Community";
12020
+ host.appendChild(ch);
12021
+ var cb = document.createElement("div");
12022
+ cb.textContent = result.community.label;
12023
+ host.appendChild(cb);
12024
+ }
12025
+ if (result.hyperedges && result.hyperedges.length) {
12026
+ var hh = document.createElement("div");
12027
+ hh.className = "hdr";
12028
+ hh.textContent = "Group Patterns (" + result.hyperedges.length + ")";
12029
+ host.appendChild(hh);
12030
+ result.hyperedges.forEach(function(h) {
12031
+ var line = document.createElement("div");
12032
+ line.textContent = h.label + " [" + h.relation + ", " + h.confidence.toFixed(2) + "]";
12033
+ host.appendChild(line);
12034
+ });
10838
12035
  }
12036
+ }
10839
12037
 
10840
- sidebar.classList.add("open");
10841
- });
12038
+ function runPanelQuery() {
12039
+ var question = document.getElementById("queryInput").value.trim();
12040
+ if (!question) {
12041
+ renderQueryPanel(null);
12042
+ return;
12043
+ }
12044
+ var radios = document.getElementsByName("queryTraversal");
12045
+ var traversal = "bfs";
12046
+ for (var i = 0; i < radios.length; i++) {
12047
+ if (radios[i].checked) { traversal = radios[i].value; break; }
12048
+ }
12049
+ renderQueryPanel(runGraphQuery(question, traversal));
12050
+ }
10842
12051
 
10843
- sidebarNeighbors.addEventListener("click", function(e) {
10844
- var target = e.target;
10845
- while (target && target !== sidebarNeighbors) {
10846
- if (target.dataset && target.dataset.nodeId) {
10847
- var nid = target.dataset.nodeId;
10848
- network.selectNodes([nid]);
10849
- network.focus(nid, { scale: 1.2, animation: { duration: 400 } });
10850
- network.body.emitter.emit("click", { nodes: [nid] });
10851
- return;
10852
- }
10853
- target = target.parentElement;
12052
+ function runPanelPath() {
12053
+ var from = document.getElementById("pathFrom").value.trim();
12054
+ var to = document.getElementById("pathTo").value.trim();
12055
+ if (!from || !to) {
12056
+ renderPathPanel(null);
12057
+ return;
10854
12058
  }
10855
- });
12059
+ renderPathPanel(runGraphPath(from, to));
12060
+ }
10856
12061
 
10857
- document.getElementById("searchInput").addEventListener("input", function() {
10858
- var query = this.value.toLowerCase().trim();
10859
- if (!query) {
10860
- visNodes.forEach(function(n) {
10861
- visNodes.update({ id: n.id, opacity: 1.0, font: { color: "#e2e8f0", size: 11 } });
10862
- });
12062
+ function runPanelExplain() {
12063
+ var target = document.getElementById("explainInput").value.trim();
12064
+ if (!target) {
12065
+ renderExplainPanel(null, "");
10863
12066
  return;
10864
12067
  }
10865
- visNodes.forEach(function(n) {
10866
- var match = n.label.toLowerCase().indexOf(query) !== -1;
10867
- visNodes.update({
10868
- id: n.id,
10869
- opacity: match ? 1.0 : 0.15,
10870
- font: { color: match ? "#f8fafc" : "#475569", size: match ? 13 : 9 }
10871
- });
10872
- });
10873
- });
12068
+ renderExplainPanel(runGraphExplain(target), target);
12069
+ }
12070
+
12071
+ document.getElementById("queryRun").addEventListener("click", runPanelQuery);
12072
+ document.getElementById("queryInput").addEventListener("keydown", function(e) { if (e.key === "Enter") runPanelQuery(); });
12073
+ document.getElementById("pathFind").addEventListener("click", runPanelPath);
12074
+ document.getElementById("pathFrom").addEventListener("keydown", function(e) { if (e.key === "Enter") runPanelPath(); });
12075
+ document.getElementById("pathTo").addEventListener("keydown", function(e) { if (e.key === "Enter") runPanelPath(); });
12076
+ document.getElementById("explainRun").addEventListener("click", runPanelExplain);
12077
+ document.getElementById("explainInput").addEventListener("keydown", function(e) { if (e.key === "Enter") runPanelExplain(); });
10874
12078
  </script>
10875
12079
  </body>
10876
12080
  </html>`;
@@ -10884,9 +12088,9 @@ async function loadGraph(rootDir) {
10884
12088
  return graph;
10885
12089
  }
10886
12090
  async function writeGraphExport(outputPath, content) {
10887
- await ensureDir(path9.dirname(outputPath));
12091
+ await ensureDir(path10.dirname(outputPath));
10888
12092
  await fs9.writeFile(outputPath, content, "utf8");
10889
- return path9.resolve(outputPath);
12093
+ return path10.resolve(outputPath);
10890
12094
  }
10891
12095
  async function exportGraphFormat(rootDir, format, outputPath) {
10892
12096
  const graph = await loadGraph(rootDir);
@@ -10897,7 +12101,7 @@ async function exportGraphFormat(rootDir, format, outputPath) {
10897
12101
  async function exportGraphReportHtml(rootDir, outputPath) {
10898
12102
  const { paths } = await loadVaultConfig(rootDir);
10899
12103
  const graph = await loadGraph(rootDir);
10900
- const report = await readJsonFile(path9.join(paths.wikiDir, "graph", "report.json"));
12104
+ const report = await readJsonFile(path10.join(paths.wikiDir, "graph", "report.json"));
10901
12105
  const html = renderGraphReportHtml(graph, report);
10902
12106
  const resolvedPath = await writeGraphExport(outputPath, html);
10903
12107
  return { format: "report", outputPath: resolvedPath };
@@ -10929,11 +12133,11 @@ function typePluralDir(nodeType) {
10929
12133
  function obsidianNodeSlug(node, pageById2) {
10930
12134
  if (node.pageId) {
10931
12135
  const page = pageById2.get(node.pageId);
10932
- if (page) return path9.basename(page.path, ".md");
12136
+ if (page) return path10.basename(page.path, ".md");
10933
12137
  }
10934
12138
  return slugify(node.label);
10935
12139
  }
10936
- function buildAdjacency(edges) {
12140
+ function buildAdjacency2(edges) {
10937
12141
  const adjacency = /* @__PURE__ */ new Map();
10938
12142
  for (const edge of edges) {
10939
12143
  if (!adjacency.has(edge.source)) adjacency.set(edge.source, []);
@@ -10966,7 +12170,7 @@ async function listFilesRecursive2(dir, base = "") {
10966
12170
  for (const entry of entries) {
10967
12171
  const rel = base ? `${base}/${entry.name}` : entry.name;
10968
12172
  if (entry.isDirectory()) {
10969
- results.push(...await listFilesRecursive2(path9.join(dir, entry.name), rel));
12173
+ results.push(...await listFilesRecursive2(path10.join(dir, entry.name), rel));
10970
12174
  } else {
10971
12175
  results.push(rel);
10972
12176
  }
@@ -11012,12 +12216,12 @@ function typedLinkFrontmatter(nodeIds, adjacency, nodesById, wikilinkTarget) {
11012
12216
  async function exportObsidianVault(rootDir, outputDir) {
11013
12217
  const graph = await loadGraph(rootDir);
11014
12218
  const { paths } = await loadVaultConfig(rootDir);
11015
- const resolvedOutputDir = path9.resolve(outputDir);
12219
+ const resolvedOutputDir = path10.resolve(outputDir);
11016
12220
  await ensureDir(resolvedOutputDir);
11017
12221
  const nodesById = graphNodeById(graph);
11018
12222
  const pageById2 = graphPageById(graph);
11019
12223
  const communities = sortedCommunities(graph);
11020
- const adjacency = buildAdjacency(graph.edges);
12224
+ const adjacency = buildAdjacency2(graph.edges);
11021
12225
  const nodesByPageId = /* @__PURE__ */ new Map();
11022
12226
  for (const node of graph.nodes) {
11023
12227
  if (node.pageId && pageById2.has(node.pageId)) {
@@ -11052,9 +12256,9 @@ async function exportObsidianVault(rootDir, outputDir) {
11052
12256
  const pageByPath = new Map(graph.pages.map((p) => [p.path, p]));
11053
12257
  for (const relPath of wikiFiles) {
11054
12258
  if (!relPath.endsWith(".md")) continue;
11055
- const srcFile = path9.join(paths.wikiDir, relPath);
11056
- const destFile = path9.join(resolvedOutputDir, relPath);
11057
- await ensureDir(path9.dirname(destFile));
12259
+ const srcFile = path10.join(paths.wikiDir, relPath);
12260
+ const destFile = path10.join(resolvedOutputDir, relPath);
12261
+ await ensureDir(path10.dirname(destFile));
11058
12262
  let rawContent;
11059
12263
  try {
11060
12264
  rawContent = await fs9.readFile(srcFile, "utf8");
@@ -11112,9 +12316,9 @@ ${connLines.join("\n")}
11112
12316
  }
11113
12317
  for (const node of orphanNodes) {
11114
12318
  const relPath = orphanFilePath.get(node.id);
11115
- const destFile = path9.join(resolvedOutputDir, relPath);
11116
- await ensureDir(path9.dirname(destFile));
11117
- const slug = path9.basename(relPath, ".md");
12319
+ const destFile = path10.join(resolvedOutputDir, relPath);
12320
+ await ensureDir(path10.dirname(destFile));
12321
+ const slug = path10.basename(relPath, ".md");
11118
12322
  const aliases = node.label !== slug ? [node.label] : [];
11119
12323
  const frontmatter = {
11120
12324
  id: node.id,
@@ -11169,8 +12373,8 @@ ${connLines.join("\n")}
11169
12373
  });
11170
12374
  });
11171
12375
  const communitySlug = deduplicateFileName(safeFileName(community.label), usedCommunityFileNames);
11172
- const destFile = path9.join(resolvedOutputDir, "graph", "communities", `${communitySlug}.md`);
11173
- await ensureDir(path9.dirname(destFile));
12376
+ const destFile = path10.join(resolvedOutputDir, "graph", "communities", `${communitySlug}.md`);
12377
+ await ensureDir(path10.dirname(destFile));
11174
12378
  const lines = [`# ${community.label}`, "", "## Members", ""];
11175
12379
  for (const member of memberNodes) {
11176
12380
  const target = wikilinkTarget.get(member.id);
@@ -11202,13 +12406,13 @@ ${connLines.join("\n")}
11202
12406
  await fs9.writeFile(destFile, content, "utf8");
11203
12407
  fileCount++;
11204
12408
  }
11205
- const outputsAssetsDir = path9.join(paths.wikiDir, "outputs", "assets");
12409
+ const outputsAssetsDir = path10.join(paths.wikiDir, "outputs", "assets");
11206
12410
  try {
11207
12411
  const assetFiles = await listFilesRecursive2(outputsAssetsDir);
11208
12412
  for (const relAsset of assetFiles) {
11209
- const src = path9.join(outputsAssetsDir, relAsset);
11210
- const dest = path9.join(resolvedOutputDir, "outputs", "assets", relAsset);
11211
- await ensureDir(path9.dirname(dest));
12413
+ const src = path10.join(outputsAssetsDir, relAsset);
12414
+ const dest = path10.join(resolvedOutputDir, "outputs", "assets", relAsset);
12415
+ await ensureDir(path10.dirname(dest));
11212
12416
  await fs9.copyFile(src, dest);
11213
12417
  fileCount++;
11214
12418
  }
@@ -11217,15 +12421,15 @@ ${connLines.join("\n")}
11217
12421
  try {
11218
12422
  const rawAssetFiles = await listFilesRecursive2(paths.rawAssetsDir);
11219
12423
  for (const relAsset of rawAssetFiles) {
11220
- const src = path9.join(paths.rawAssetsDir, relAsset);
11221
- const dest = path9.join(resolvedOutputDir, "raw", "assets", relAsset);
11222
- await ensureDir(path9.dirname(dest));
12424
+ const src = path10.join(paths.rawAssetsDir, relAsset);
12425
+ const dest = path10.join(resolvedOutputDir, "raw", "assets", relAsset);
12426
+ await ensureDir(path10.dirname(dest));
11223
12427
  await fs9.copyFile(src, dest);
11224
12428
  fileCount++;
11225
12429
  }
11226
12430
  } catch {
11227
12431
  }
11228
- const obsidianDir = path9.join(resolvedOutputDir, ".obsidian");
12432
+ const obsidianDir = path10.join(resolvedOutputDir, ".obsidian");
11229
12433
  await ensureDir(obsidianDir);
11230
12434
  const projectIds = Object.keys(
11231
12435
  graph.pages.reduce(
@@ -11250,7 +12454,7 @@ ${connLines.join("\n")}
11250
12454
  }));
11251
12455
  const colorGroups = [...nodeTypeGroups, ...projectColorGroups];
11252
12456
  await fs9.writeFile(
11253
- path9.join(obsidianDir, "app.json"),
12457
+ path10.join(obsidianDir, "app.json"),
11254
12458
  JSON.stringify(
11255
12459
  { newFileLocation: "folder", newFileFolderPath: "outputs", attachmentFolderPath: "raw/assets", useMarkdownLinks: false },
11256
12460
  null,
@@ -11259,12 +12463,12 @@ ${connLines.join("\n")}
11259
12463
  "utf8"
11260
12464
  );
11261
12465
  await fs9.writeFile(
11262
- path9.join(obsidianDir, "core-plugins.json"),
12466
+ path10.join(obsidianDir, "core-plugins.json"),
11263
12467
  JSON.stringify(["file-explorer", "global-search", "graph", "backlink", "tag-pane", "page-preview", "outline"], null, 2),
11264
12468
  "utf8"
11265
12469
  );
11266
12470
  await fs9.writeFile(
11267
- path9.join(obsidianDir, "graph.json"),
12471
+ path10.join(obsidianDir, "graph.json"),
11268
12472
  JSON.stringify(
11269
12473
  { colorGroups, "collapse-filter": false, search: "", showTags: true, showAttachments: false, showOrphans: true },
11270
12474
  null,
@@ -11272,9 +12476,9 @@ ${connLines.join("\n")}
11272
12476
  ),
11273
12477
  "utf8"
11274
12478
  );
11275
- await fs9.writeFile(path9.join(obsidianDir, "types.json"), JSON.stringify({ types: OBSIDIAN_PROPERTY_TYPES }, null, 2), "utf8");
12479
+ await fs9.writeFile(path10.join(obsidianDir, "types.json"), JSON.stringify({ types: OBSIDIAN_PROPERTY_TYPES }, null, 2), "utf8");
11276
12480
  fileCount += 4;
11277
- const dashboardDir = path9.join(resolvedOutputDir, "graph", "dashboards");
12481
+ const dashboardDir = path10.join(resolvedOutputDir, "graph", "dashboards");
11278
12482
  await ensureDir(dashboardDir);
11279
12483
  const dvPages = [
11280
12484
  {
@@ -11311,7 +12515,7 @@ ${connLines.join("\n")}
11311
12515
  ${dv.query}
11312
12516
  \`\`\`
11313
12517
  `;
11314
- await fs9.writeFile(path9.join(dashboardDir, `${dv.name}.md`), matter6.stringify(dvBody, dvFrontmatter), "utf8");
12518
+ await fs9.writeFile(path10.join(dashboardDir, `${dv.name}.md`), matter6.stringify(dvBody, dvFrontmatter), "utf8");
11315
12519
  fileCount++;
11316
12520
  }
11317
12521
  return { format: "obsidian", outputPath: resolvedOutputDir, fileCount };
@@ -11393,13 +12597,13 @@ async function exportObsidianCanvas(rootDir, outputPath) {
11393
12597
  color: COLORS[communityIndex % COLORS.length]
11394
12598
  });
11395
12599
  } else {
11396
- const communityLabel2 = community.id === "community:unassigned" ? "Unassigned" : community.label;
12600
+ const communityLabel = community.id === "community:unassigned" ? "Unassigned" : community.label;
11397
12601
  canvasNodes.push({
11398
12602
  id: canvasId,
11399
12603
  type: "text",
11400
12604
  text: `**${node.label}**
11401
12605
  Type: ${node.type}
11402
- Community: ${communityLabel2}`,
12606
+ Community: ${communityLabel}`,
11403
12607
  x: nodeX,
11404
12608
  y: nodeY,
11405
12609
  width: NODE_WIDTH,
@@ -11434,7 +12638,7 @@ Community: ${communityLabel2}`,
11434
12638
 
11435
12639
  // src/graph-push.ts
11436
12640
  import fs10 from "fs/promises";
11437
- import path10 from "path";
12641
+ import path11 from "path";
11438
12642
  import neo4j from "neo4j-driver";
11439
12643
 
11440
12644
  // src/benchmark.ts
@@ -11561,6 +12765,37 @@ function defaultBenchmarkQuestionsForGraph(graph, maxQuestions = 3) {
11561
12765
  }
11562
12766
  return uniqueBy(questions, (item) => item).slice(0, normalizedLimit);
11563
12767
  }
12768
+ function buildBenchmarkByClass(input) {
12769
+ const entries = {};
12770
+ for (const sourceClass of ALL_SOURCE_CLASSES) {
12771
+ const corpusWords = Math.max(0, Math.round(input.perClassCorpusWords[sourceClass] ?? 0));
12772
+ const corpusTokens = corpusWords > 0 ? Math.max(1, Math.round(corpusWords * (100 / 75))) : 0;
12773
+ const sourceCount = input.graph.sources.filter((source) => source.sourceClass === sourceClass).length;
12774
+ const pageCount = input.graph.pages.filter((page) => page.sourceClass === sourceClass).length;
12775
+ const nodeCount = input.graph.nodes.filter((node) => node.sourceClass === sourceClass).length;
12776
+ const godNodeCount = input.graph.nodes.filter((node) => node.sourceClass === sourceClass && Boolean(node.isGodNode)).length;
12777
+ const perQuestionRaw = input.perClassPerQuestion[sourceClass] ?? [];
12778
+ const perQuestion = perQuestionRaw.filter((entry) => entry.queryTokens > 0).map((entry) => ({
12779
+ ...entry,
12780
+ reduction: corpusTokens > 0 ? Number((1 - entry.queryTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0
12781
+ }));
12782
+ const finalContextTokens = perQuestion.length ? Math.max(1, Math.round(perQuestion.reduce((total, entry) => total + entry.queryTokens, 0) / perQuestion.length)) : 0;
12783
+ const reductionRatio = finalContextTokens && corpusTokens > 0 ? Number((1 - finalContextTokens / Math.max(1, corpusTokens)).toFixed(3)) : 0;
12784
+ entries[sourceClass] = {
12785
+ sourceClass,
12786
+ sourceCount,
12787
+ pageCount,
12788
+ nodeCount,
12789
+ godNodeCount,
12790
+ corpusWords,
12791
+ corpusTokens,
12792
+ finalContextTokens,
12793
+ reductionRatio,
12794
+ perQuestion
12795
+ };
12796
+ }
12797
+ return entries;
12798
+ }
11564
12799
  function buildBenchmarkArtifact(input) {
11565
12800
  const corpusTokens = Math.max(1, Math.round(input.corpusWords * (100 / 75)));
11566
12801
  const perQuestion = input.perQuestion.filter((entry) => entry.queryTokens > 0).map((entry) => ({
@@ -11581,6 +12816,23 @@ function buildBenchmarkArtifact(input) {
11581
12816
  avgReduction: reductionRatio,
11582
12817
  reductionRatio
11583
12818
  };
12819
+ const emptyPerClassWords = {
12820
+ first_party: 0,
12821
+ third_party: 0,
12822
+ resource: 0,
12823
+ generated: 0
12824
+ };
12825
+ const emptyPerClassQuestions = {
12826
+ first_party: [],
12827
+ third_party: [],
12828
+ resource: [],
12829
+ generated: []
12830
+ };
12831
+ const byClass = input.byClass ?? buildBenchmarkByClass({
12832
+ graph: input.graph,
12833
+ perClassCorpusWords: emptyPerClassWords,
12834
+ perClassPerQuestion: emptyPerClassQuestions
12835
+ });
11584
12836
  return {
11585
12837
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11586
12838
  graphHash: graphHash(input.graph),
@@ -11592,7 +12844,8 @@ function buildBenchmarkArtifact(input) {
11592
12844
  reductionRatio,
11593
12845
  sampleQuestions: input.questions,
11594
12846
  perQuestion,
11595
- summary
12847
+ summary,
12848
+ byClass
11596
12849
  };
11597
12850
  }
11598
12851
 
@@ -11606,8 +12859,8 @@ function requireConfigValue(value, name) {
11606
12859
  throw new Error(`Neo4j push requires ${name}. Configure \`graphSinks.neo4j.${name}\` or pass the matching CLI flag.`);
11607
12860
  }
11608
12861
  async function deriveVaultId(rootDir) {
11609
- const realRoot = await fs10.realpath(rootDir).catch(() => path10.resolve(rootDir));
11610
- const label = slugify(path10.basename(realRoot));
12862
+ const realRoot = await fs10.realpath(rootDir).catch(() => path11.resolve(rootDir));
12863
+ const label = slugify(path11.basename(realRoot));
11611
12864
  return `${label}-${sha256(realRoot).slice(0, 12)}`;
11612
12865
  }
11613
12866
  async function resolveNeo4jPushConfig(rootDir, options) {
@@ -11722,7 +12975,7 @@ async function writeSyncNode(session, input) {
11722
12975
  ].join("\n"),
11723
12976
  {
11724
12977
  vaultId: input.vaultId,
11725
- rootDir: path10.resolve(input.rootDir),
12978
+ rootDir: path11.resolve(input.rootDir),
11726
12979
  graphGeneratedAt: input.graph.generatedAt,
11727
12980
  graphHash: graphHash(input.graph),
11728
12981
  pushedAt: input.pushedAt,
@@ -11809,25 +13062,25 @@ async function pushGraphNeo4j(rootDir, options = {}) {
11809
13062
 
11810
13063
  // src/hooks.ts
11811
13064
  import fs11 from "fs/promises";
11812
- import path11 from "path";
13065
+ import path12 from "path";
11813
13066
  import process2 from "process";
11814
13067
  var hookStart = "# >>> swarmvault hook >>>";
11815
13068
  var hookEnd = "# <<< swarmvault hook <<<";
11816
13069
  async function findNearestGitRoot(startPath) {
11817
- let current = path11.resolve(startPath);
13070
+ let current = path12.resolve(startPath);
11818
13071
  try {
11819
13072
  const stat = await fs11.stat(current);
11820
13073
  if (!stat.isDirectory()) {
11821
- current = path11.dirname(current);
13074
+ current = path12.dirname(current);
11822
13075
  }
11823
13076
  } catch {
11824
- current = path11.dirname(current);
13077
+ current = path12.dirname(current);
11825
13078
  }
11826
13079
  while (true) {
11827
- if (await fileExists(path11.join(current, ".git"))) {
13080
+ if (await fileExists(path12.join(current, ".git"))) {
11828
13081
  return current;
11829
13082
  }
11830
- const parent = path11.dirname(current);
13083
+ const parent = path12.dirname(current);
11831
13084
  if (parent === current) {
11832
13085
  return null;
11833
13086
  }
@@ -11839,8 +13092,8 @@ function shellQuote(value) {
11839
13092
  }
11840
13093
  function resolveSwarmvaultExecutableCandidate() {
11841
13094
  const argvPath = process2.argv[1];
11842
- if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path11.sep}@swarmvaultai${path11.sep}cli${path11.sep}`) || argvPath.includes(`${path11.sep}packages${path11.sep}cli${path11.sep}`))) {
11843
- return path11.resolve(argvPath);
13095
+ if (typeof argvPath === "string" && argvPath.trim() && (argvPath.includes(`${path12.sep}@swarmvaultai${path12.sep}cli${path12.sep}`) || argvPath.includes(`${path12.sep}packages${path12.sep}cli${path12.sep}`))) {
13096
+ return path12.resolve(argvPath);
11844
13097
  }
11845
13098
  return "swarmvault";
11846
13099
  }
@@ -11859,7 +13112,7 @@ function managedHookBlock(vaultRoot) {
11859
13112
  ].join("\n");
11860
13113
  }
11861
13114
  function hookPath(repoRoot, hookName) {
11862
- return path11.join(repoRoot, ".git", "hooks", hookName);
13115
+ return path12.join(repoRoot, ".git", "hooks", hookName);
11863
13116
  }
11864
13117
  async function readHookStatus(filePath) {
11865
13118
  if (!await fileExists(filePath)) {
@@ -11883,7 +13136,7 @@ ${block}`.trimEnd();
11883
13136
  next = `#!/bin/sh
11884
13137
  ${block}`.trimEnd();
11885
13138
  }
11886
- await ensureDir(path11.dirname(filePath));
13139
+ await ensureDir(path12.dirname(filePath));
11887
13140
  await fs11.writeFile(filePath, `${next}
11888
13141
  `, { mode: 493, encoding: "utf8" });
11889
13142
  await fs11.chmod(filePath, 493);
@@ -11926,7 +13179,7 @@ async function installGitHooks(rootDir) {
11926
13179
  if (!repoRoot) {
11927
13180
  throw new Error("No git repository found above the current vault.");
11928
13181
  }
11929
- const block = managedHookBlock(path11.resolve(rootDir));
13182
+ const block = managedHookBlock(path12.resolve(rootDir));
11930
13183
  await upsertHookFile(hookPath(repoRoot, "post-commit"), block);
11931
13184
  await upsertHookFile(hookPath(repoRoot, "post-checkout"), block);
11932
13185
  return getGitHookStatus(rootDir);
@@ -11960,7 +13213,7 @@ import TurndownService2 from "turndown";
11960
13213
  // src/extraction.ts
11961
13214
  import fs12 from "fs/promises";
11962
13215
  import os2 from "os";
11963
- import path12 from "path";
13216
+ import path13 from "path";
11964
13217
  import { Readable } from "stream";
11965
13218
  import { parse as parseCsvSync } from "csv-parse/sync";
11966
13219
  import { strFromU8, unzipSync } from "fflate";
@@ -12003,6 +13256,119 @@ function firstMarkdownHeading(text) {
12003
13256
  }
12004
13257
  return void 0;
12005
13258
  }
13259
+ var NON_CODE_RATIONALE_MARKERS = ["NOTE", "WHY", "HACK", "IMPORTANT", "RATIONALE", "TODO", "FIXME", "WARNING", "WARN"];
13260
+ var NON_CODE_RATIONALE_MARKER_SET = new Set(NON_CODE_RATIONALE_MARKERS);
13261
+ function matchFixedPrefix(text) {
13262
+ const stripped = text.replace(/^[\s>*_\-\u2022\u25CB\u25CF]+/, "").trimStart();
13263
+ if (!stripped) {
13264
+ return null;
13265
+ }
13266
+ const match = /^([A-Za-z]+)[:\-\u2013\u2014]\s+(.+)$/s.exec(stripped);
13267
+ if (!match) {
13268
+ return null;
13269
+ }
13270
+ const upper = match[1].toUpperCase();
13271
+ if (!NON_CODE_RATIONALE_MARKER_SET.has(upper)) {
13272
+ return null;
13273
+ }
13274
+ const remainder = normalizeWhitespace(match[2]).trim();
13275
+ if (!remainder) {
13276
+ return null;
13277
+ }
13278
+ return { marker: upper, remainder };
13279
+ }
13280
+ function extractRationaleFromMarkdown(content, sourceId) {
13281
+ const nodes = parseMarkdownNodes(content);
13282
+ if (!nodes.length) {
13283
+ return [];
13284
+ }
13285
+ const rationales = [];
13286
+ let currentHeading;
13287
+ const recordMatch = (text) => {
13288
+ const match = matchFixedPrefix(text);
13289
+ if (!match) {
13290
+ return;
13291
+ }
13292
+ rationales.push({
13293
+ id: `rationale:${sourceId}:${rationales.length}`,
13294
+ text: truncate(match.remainder, 280),
13295
+ citation: sourceId,
13296
+ kind: match.marker.toLowerCase(),
13297
+ symbolName: currentHeading
13298
+ });
13299
+ };
13300
+ const visitBlock = (node) => {
13301
+ if (node.type === "heading") {
13302
+ const headingText = markdownNodeText(node).trim();
13303
+ if (headingText) {
13304
+ currentHeading = headingText;
13305
+ }
13306
+ return;
13307
+ }
13308
+ if (node.type === "blockquote") {
13309
+ for (const child of node.children ?? []) {
13310
+ if (child.type === "paragraph") {
13311
+ recordMatch(markdownNodeText(child));
13312
+ } else {
13313
+ visitBlock(child);
13314
+ }
13315
+ }
13316
+ return;
13317
+ }
13318
+ if (node.type === "list") {
13319
+ for (const item of node.children ?? []) {
13320
+ if (item.type !== "listItem") {
13321
+ continue;
13322
+ }
13323
+ const itemParagraphs = [];
13324
+ const nestedLists = [];
13325
+ for (const child of item.children ?? []) {
13326
+ if (child.type === "paragraph") {
13327
+ itemParagraphs.push(markdownNodeText(child));
13328
+ } else if (child.type === "list") {
13329
+ nestedLists.push(child);
13330
+ }
13331
+ }
13332
+ const combined = itemParagraphs.join(" ").trim();
13333
+ if (combined) {
13334
+ recordMatch(combined);
13335
+ }
13336
+ for (const nested of nestedLists) {
13337
+ visitBlock(nested);
13338
+ }
13339
+ }
13340
+ }
13341
+ };
13342
+ for (const node of nodes) {
13343
+ visitBlock(node);
13344
+ }
13345
+ return rationales;
13346
+ }
13347
+ function extractRationaleFromPlainText(content, sourceId, fallbackSymbolName) {
13348
+ if (!content.trim()) {
13349
+ return [];
13350
+ }
13351
+ const paragraphs = content.split(/\r?\n\s*\r?\n+/);
13352
+ const rationales = [];
13353
+ for (const paragraph of paragraphs) {
13354
+ const normalized = normalizeWhitespace(paragraph);
13355
+ if (!normalized) {
13356
+ continue;
13357
+ }
13358
+ const match = matchFixedPrefix(normalized);
13359
+ if (!match) {
13360
+ continue;
13361
+ }
13362
+ rationales.push({
13363
+ id: `rationale:${sourceId}:${rationales.length}`,
13364
+ text: truncate(match.remainder, 280),
13365
+ citation: sourceId,
13366
+ kind: match.marker.toLowerCase(),
13367
+ symbolName: fallbackSymbolName
13368
+ });
13369
+ }
13370
+ return rationales;
13371
+ }
12006
13372
 
12007
13373
  // src/extraction.ts
12008
13374
  var imageVisionExtractionSchema = z.object({
@@ -12083,9 +13449,9 @@ async function materializeAttachmentPath(input) {
12083
13449
  if (!input.bytes) {
12084
13450
  throw new Error("Image extraction requires a file path or bytes.");
12085
13451
  }
12086
- const tempDir = await fs12.mkdtemp(path12.join(os2.tmpdir(), "swarmvault-image-extract-"));
13452
+ const tempDir = await fs12.mkdtemp(path13.join(os2.tmpdir(), "swarmvault-image-extract-"));
12087
13453
  const extension = input.mimeType.split("/")[1]?.split("+")[0] ?? "bin";
12088
- const tempPath = path12.join(tempDir, `source.${extension}`);
13454
+ const tempPath = path13.join(tempDir, `source.${extension}`);
12089
13455
  await fs12.writeFile(tempPath, input.bytes);
12090
13456
  return {
12091
13457
  filePath: tempPath,
@@ -12421,7 +13787,7 @@ function zipDirname(value) {
12421
13787
  return index === -1 ? "" : value.slice(0, index);
12422
13788
  }
12423
13789
  function resolveZipTarget(basePath, target) {
12424
- return path12.posix.normalize(path12.posix.join(zipDirname(basePath), target));
13790
+ return path13.posix.normalize(path13.posix.join(zipDirname(basePath), target));
12425
13791
  }
12426
13792
  function relationshipTargets(xml, basePath) {
12427
13793
  const document = parseXmlDocument(xml);
@@ -12606,7 +13972,7 @@ async function extractJupyterNotebook(input) {
12606
13972
  }
12607
13973
  }
12608
13974
  if (!notebookTitle && input.fileName) {
12609
- notebookTitle = path12.basename(input.fileName, path12.extname(input.fileName));
13975
+ notebookTitle = path13.basename(input.fileName, path13.extname(input.fileName));
12610
13976
  }
12611
13977
  const sections = [];
12612
13978
  let markdownCellCount = 0;
@@ -12695,7 +14061,7 @@ async function extractCsvText(input) {
12695
14061
  const rows = parsed.map((row) => row.map((value) => normalizeTableCell(value)));
12696
14062
  const { headers, bodyRows } = detectHeaderRow(rows);
12697
14063
  const hintLines = columnHints(headers, bodyRows);
12698
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0;
14064
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0;
12699
14065
  const extractedText = [
12700
14066
  title ? `# ${title}` : null,
12701
14067
  `Format: ${delimiter === " " ? "TSV" : "CSV"}`,
@@ -12760,7 +14126,7 @@ async function extractSpreadsheetWorkbook(input, sourceKind, extractor) {
12760
14126
  sheetSections.push(...markdownTable(headers, bodyRows));
12761
14127
  sheetSections.push("");
12762
14128
  }
12763
- const title = normalizeWhitespace(String(workbook.Props?.Title ?? "")) || (input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0);
14129
+ const title = normalizeWhitespace(String(workbook.Props?.Title ?? "")) || (input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0);
12764
14130
  const extractedText = [
12765
14131
  title ? `# ${title}` : null,
12766
14132
  `Sheets: ${allSheetNames.length}`,
@@ -12822,7 +14188,7 @@ async function extractPptxText(input) {
12822
14188
  if (slideTexts.length) {
12823
14189
  slideSections.push(slideTexts.join("\n"));
12824
14190
  }
12825
- const slideRelsPath = `${zipDirname(slidePath)}/_rels/${path12.posix.basename(slidePath)}.rels`;
14191
+ const slideRelsPath = `${zipDirname(slidePath)}/_rels/${path13.posix.basename(slidePath)}.rels`;
12826
14192
  const slideRelsXml = zipEntryText(archive, slideRelsPath);
12827
14193
  if (slideRelsXml) {
12828
14194
  const slideRels = relationshipTargets(slideRelsXml, slidePath);
@@ -12839,7 +14205,7 @@ async function extractPptxText(input) {
12839
14205
  slideSections.push("");
12840
14206
  }
12841
14207
  const metadata = parseOfficeCoreMetadata(input.bytes);
12842
- const title = metadata?.title || (input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0);
14208
+ const title = metadata?.title || (input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0);
12843
14209
  const extractedText = [title ? `# ${title}` : null, `Slides: ${slideTargets.length}`, "", ...slideSections].filter((item) => Boolean(item)).join("\n").trim();
12844
14210
  return {
12845
14211
  title,
@@ -12893,7 +14259,7 @@ async function extractEpubChapters(input) {
12893
14259
  ).filter(([id, item]) => Boolean(id && item.href))
12894
14260
  );
12895
14261
  const spineIds = Array.from(packageDocument.getElementsByTagName("*")).filter((node) => node.localName === "itemref").map((node) => node.getAttribute("idref")?.trim()).filter((value) => Boolean(value));
12896
- const bookTitle = xmlTextNodes(packageXml, "title")[0] || (input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0);
14262
+ const bookTitle = xmlTextNodes(packageXml, "title")[0] || (input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0);
12897
14263
  const author = xmlTextNodes(packageXml, "creator")[0];
12898
14264
  const chapters = [];
12899
14265
  for (const spineId of spineIds) {
@@ -13197,7 +14563,7 @@ async function extractOdtText(input) {
13197
14563
  const textNodes = collectOdfTextNodes(contentXml);
13198
14564
  const headingCount = textNodes.filter((node) => node.heading).length;
13199
14565
  const paragraphCount = textNodes.filter((node) => !node.heading).length;
13200
- const title = metadata?.title || textNodes.find((node) => node.heading === 1)?.text || (input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0);
14566
+ const title = metadata?.title || textNodes.find((node) => node.heading === 1)?.text || (input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0);
13201
14567
  const body = renderOdfTextNodes(textNodes);
13202
14568
  const extractedText = [title ? `# ${title}` : null, "", body].filter((item) => item !== null).join("\n").trim();
13203
14569
  return {
@@ -13241,7 +14607,7 @@ async function extractOdpText(input) {
13241
14607
  }
13242
14608
  slideSections.push("");
13243
14609
  });
13244
- const title = metadata?.title || (input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0);
14610
+ const title = metadata?.title || (input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0);
13245
14611
  const extractedText = [title ? `# ${title}` : null, `Slides: ${pages.length}`, "", ...slideSections].filter((item) => Boolean(item)).join("\n").trim();
13246
14612
  const warnings = pages.length > 60 ? ["ODP extraction truncated to the first 60 slides."] : void 0;
13247
14613
  return {
@@ -13424,7 +14790,7 @@ async function extractStructuredData(input) {
13424
14790
  const previewText = decodeTextBytes(input.bytes);
13425
14791
  const previewLines = previewText.split(/\r?\n/).slice(0, 40);
13426
14792
  const truncated = previewText.split(/\r?\n/).length > previewLines.length;
13427
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0;
14793
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0;
13428
14794
  const extractedText = [
13429
14795
  title ? `# ${title}` : null,
13430
14796
  `Format: ${format.toUpperCase()}`,
@@ -13533,7 +14899,7 @@ async function extractBibTeXText(input) {
13533
14899
  const totalEntries = entries.length;
13534
14900
  const truncated = entries.length > 200;
13535
14901
  const typeSummary = [...citationTypes.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([type, count]) => `${type} (${count})`).join(", ");
13536
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : "BibTeX library";
14902
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : "BibTeX library";
13537
14903
  const extractedText = [
13538
14904
  `# ${title}`,
13539
14905
  "",
@@ -13593,7 +14959,7 @@ async function extractRtfText(input) {
13593
14959
  paragraphs.push(text);
13594
14960
  }
13595
14961
  }
13596
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0;
14962
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0;
13597
14963
  const extractedText = [title ? `# ${title}` : null, "", ...paragraphs].filter((item) => item !== null).join("\n\n").trim();
13598
14964
  return {
13599
14965
  title,
@@ -13694,7 +15060,7 @@ async function extractOrgText(input) {
13694
15060
  for (const child of document.children ?? []) {
13695
15061
  renderOrgNode(child, bodyLines);
13696
15062
  }
13697
- const title = documentTitle.trim() || (input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0);
15063
+ const title = documentTitle.trim() || (input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0);
13698
15064
  const extractedText = [title ? `# ${title}` : null, "", ...bodyLines].filter((item) => item !== null).join("\n").trim();
13699
15065
  return {
13700
15066
  title,
@@ -13726,7 +15092,7 @@ async function extractAsciiDocText(input) {
13726
15092
  const html = processor.convert(source, { safe: "safe", standalone: false });
13727
15093
  const markdown = htmlToMarkdown(html);
13728
15094
  const docTitle = (typeof loaded.getTitle === "function" ? loaded.getTitle() : void 0) ?? void 0;
13729
- const fileTitle = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0;
15095
+ const fileTitle = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0;
13730
15096
  const title = docTitle?.trim() || fileTitle;
13731
15097
  const extractedText = [title ? `# ${title}` : null, "", markdown].filter((item) => item !== null).join("\n").trim();
13732
15098
  return {
@@ -13758,7 +15124,7 @@ async function extractTranscriptText(input) {
13758
15124
  end: Math.max(0, node.data?.end ?? 0),
13759
15125
  text: normalizeWhitespace((node.data?.text ?? "").replace(/\s*\n+\s*/g, " "))
13760
15126
  })).filter((cue) => cue.text);
13761
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : void 0;
15127
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : void 0;
13762
15128
  const extractedText = [
13763
15129
  title ? `# ${title}` : null,
13764
15130
  `Format: ${input.fileName?.toLowerCase().endsWith(".vtt") ? "WebVTT" : "SRT"}`,
@@ -13794,7 +15160,7 @@ async function extractTranscriptText(input) {
13794
15160
  async function extractEmailText(input) {
13795
15161
  try {
13796
15162
  const { simpleParser } = await import("mailparser");
13797
- const fallbackTitle = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : "Email";
15163
+ const fallbackTitle = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : "Email";
13798
15164
  const parsed = await simpleParser(input.bytes);
13799
15165
  const normalized = normalizeParsedEmail(parsed, fallbackTitle);
13800
15166
  return {
@@ -13816,7 +15182,7 @@ async function extractEmailText(input) {
13816
15182
  }
13817
15183
  async function extractMboxMessages(input) {
13818
15184
  try {
13819
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : "Mailbox";
15185
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : "Mailbox";
13820
15186
  const { simpleParser } = await import("mailparser");
13821
15187
  const messages = await loadZipMessageBuffers(input.bytes);
13822
15188
  const extracted = [];
@@ -13852,7 +15218,7 @@ async function extractMboxMessages(input) {
13852
15218
  async function extractCalendarEvents(input) {
13853
15219
  try {
13854
15220
  const ical = await import("node-ical");
13855
- const calendarTitle = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : "Calendar";
15221
+ const calendarTitle = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : "Calendar";
13856
15222
  const parsed = ical.default.sync.parseICS(decodeTextBytes(input.bytes));
13857
15223
  const events = [];
13858
15224
  for (const item of Object.values(parsed)) {
@@ -14035,7 +15401,7 @@ async function isSlackExportDirectory(directoryPath) {
14035
15401
  return false;
14036
15402
  }
14037
15403
  for (const entry of entries) {
14038
- const channelDir = path12.join(directoryPath, entry);
15404
+ const channelDir = path13.join(directoryPath, entry);
14039
15405
  const stat = await fs12.stat(channelDir).catch(() => null);
14040
15406
  if (!stat?.isDirectory()) {
14041
15407
  continue;
@@ -14050,7 +15416,7 @@ async function isSlackExportDirectory(directoryPath) {
14050
15416
  async function extractSlackExportArchive(input) {
14051
15417
  try {
14052
15418
  const archive = unzipSync(new Uint8Array(input.bytes));
14053
- const title = input.fileName ? path12.basename(input.fileName, path12.extname(input.fileName)) : "Slack Export";
15419
+ const title = input.fileName ? path13.basename(input.fileName, path13.extname(input.fileName)) : "Slack Export";
14054
15420
  return parseSlackExportEntries(archiveEntriesAsText(archive), title);
14055
15421
  } catch (error) {
14056
15422
  return {
@@ -14060,7 +15426,7 @@ async function extractSlackExportArchive(input) {
14060
15426
  }
14061
15427
  }
14062
15428
  async function extractSlackExportDirectory(directoryPath) {
14063
- const title = path12.basename(directoryPath) || "Slack Export";
15429
+ const title = path13.basename(directoryPath) || "Slack Export";
14064
15430
  try {
14065
15431
  const entries = /* @__PURE__ */ new Map();
14066
15432
  const queue = [directoryPath];
@@ -14068,12 +15434,12 @@ async function extractSlackExportDirectory(directoryPath) {
14068
15434
  const current = queue.shift();
14069
15435
  const children = await fs12.readdir(current, { withFileTypes: true });
14070
15436
  for (const child of children) {
14071
- const absoluteChild = path12.join(current, child.name);
15437
+ const absoluteChild = path13.join(current, child.name);
14072
15438
  if (child.isDirectory()) {
14073
15439
  queue.push(absoluteChild);
14074
15440
  continue;
14075
15441
  }
14076
- const relativeChild = path12.posix.relative(directoryPath, absoluteChild.split(path12.sep).join(path12.posix.sep));
15442
+ const relativeChild = path13.posix.relative(directoryPath, absoluteChild.split(path13.sep).join(path13.posix.sep));
14077
15443
  entries.set(relativeChild, await fs12.readFile(absoluteChild, "utf8"));
14078
15444
  }
14079
15445
  }
@@ -14225,65 +15591,6 @@ function buildConfiguredRedactor(config) {
14225
15591
  return buildRedactor(resolved.patterns, resolved.placeholder);
14226
15592
  }
14227
15593
 
14228
- // src/source-classification.ts
14229
- import path13 from "path";
14230
- var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
14231
- var THIRD_PARTY_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "vendor", "Pods"]);
14232
- var GENERATED_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", ".next", "coverage", "DerivedData", "target"]);
14233
- function matchesAnyGlob(relativePath, patterns) {
14234
- return patterns.some(
14235
- (pattern) => path13.matchesGlob(relativePath, pattern) || path13.matchesGlob(path13.posix.basename(relativePath), pattern)
14236
- );
14237
- }
14238
- function classifyRepoPath(relativePath, repoAnalysis) {
14239
- const normalized = relativePath.replace(/\\/g, "/");
14240
- const custom = repoAnalysis?.classifyGlobs;
14241
- if (custom?.first_party?.length && matchesAnyGlob(normalized, custom.first_party)) {
14242
- return "first_party";
14243
- }
14244
- for (const sourceClass of ["third_party", "resource", "generated"]) {
14245
- const patterns = custom?.[sourceClass];
14246
- if (patterns?.length && matchesAnyGlob(normalized, patterns)) {
14247
- return sourceClass;
14248
- }
14249
- }
14250
- const segments = normalized.split("/").filter(Boolean);
14251
- if (segments.some((segment) => THIRD_PARTY_SEGMENTS.has(segment))) {
14252
- return "third_party";
14253
- }
14254
- if (segments.some((segment) => GENERATED_SEGMENTS.has(segment))) {
14255
- return "generated";
14256
- }
14257
- if (segments.some((segment) => segment.endsWith(".xcassets") || segment.endsWith(".imageset"))) {
14258
- return "resource";
14259
- }
14260
- return "first_party";
14261
- }
14262
- function normalizeExtractClasses(repoAnalysis, extra = []) {
14263
- const configured = repoAnalysis?.extractClasses?.length ? repoAnalysis.extractClasses : ["first_party"];
14264
- return ALL_SOURCE_CLASSES.filter((sourceClass) => (/* @__PURE__ */ new Set([...configured, ...extra])).has(sourceClass));
14265
- }
14266
- function aggregateSourceClass(values) {
14267
- const available = ALL_SOURCE_CLASSES.filter((sourceClass) => values.includes(sourceClass));
14268
- if (!available.length) {
14269
- return void 0;
14270
- }
14271
- if (available.includes("first_party")) {
14272
- return "first_party";
14273
- }
14274
- if (available.includes("resource")) {
14275
- return "resource";
14276
- }
14277
- if (available.includes("third_party")) {
14278
- return "third_party";
14279
- }
14280
- return "generated";
14281
- }
14282
- function aggregateManifestSourceClass(manifests, sourceIds) {
14283
- const byId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest.sourceClass]));
14284
- return aggregateSourceClass(sourceIds.map((sourceId) => byId.get(sourceId)));
14285
- }
14286
-
14287
15594
  // src/source-registry.ts
14288
15595
  import fs13 from "fs/promises";
14289
15596
  import path14 from "path";
@@ -14542,7 +15849,7 @@ var MARKDOWN_SEMANTIC_FRONTMATTER_KEYS = [
14542
15849
  "canonical_url",
14543
15850
  "source_type"
14544
15851
  ];
14545
- function uniqueStrings2(values) {
15852
+ function uniqueStrings3(values) {
14546
15853
  return [...new Set(values.filter(Boolean))];
14547
15854
  }
14548
15855
  function ingestRunStatePath(stateDir, runId) {
@@ -14941,7 +16248,7 @@ function normalizeSemanticMarkdownList(value) {
14941
16248
  if (!Array.isArray(value)) {
14942
16249
  return void 0;
14943
16250
  }
14944
- const items = uniqueStrings2(
16251
+ const items = uniqueStrings3(
14945
16252
  value.flatMap((item) => typeof item === "string" ? [normalizeWhitespace(item.trim())] : []).filter(Boolean)
14946
16253
  );
14947
16254
  return items.length ? items : void 0;
@@ -15273,7 +16580,7 @@ async function captureArxivMarkdown(input, options) {
15273
16580
  canonical_url: normalizedUrl,
15274
16581
  title,
15275
16582
  authors,
15276
- tags: uniqueStrings2(categories),
16583
+ tags: uniqueStrings3(categories),
15277
16584
  arxiv_id: arxivId,
15278
16585
  author: options.author,
15279
16586
  contributor: options.contributor,
@@ -15349,14 +16656,14 @@ function firstMetaContent(document, selectors) {
15349
16656
  return void 0;
15350
16657
  }
15351
16658
  function metaContents(document, selectors) {
15352
- return uniqueStrings2(
16659
+ return uniqueStrings3(
15353
16660
  selectors.flatMap(
15354
16661
  (selector) => [...document.querySelectorAll(selector)].map((node) => node.getAttribute("content")?.trim() ?? "").filter(Boolean)
15355
16662
  )
15356
16663
  );
15357
16664
  }
15358
16665
  function splitKeywords(value) {
15359
- return uniqueStrings2(
16666
+ return uniqueStrings3(
15360
16667
  (value ?? "").split(/[;,]/g).map((item) => item.trim()).filter(Boolean)
15361
16668
  );
15362
16669
  }
@@ -15370,7 +16677,7 @@ async function captureArticleMarkdown(rootDir, input, options, extra = { sourceT
15370
16677
  const canonicalHref = document.querySelector('link[rel="canonical"]')?.getAttribute("href")?.trim();
15371
16678
  const canonicalUrl = canonicalHref ? normalizeOriginUrl(new URL(canonicalHref, resolved.finalUrl).toString()) : resolved.finalUrl;
15372
16679
  const title = firstMetaContent(document, ['meta[name="citation_title"]', 'meta[property="og:title"]', 'meta[name="twitter:title"]']) ?? (document.title.trim() || canonicalUrl);
15373
- const authors = uniqueStrings2([
16680
+ const authors = uniqueStrings3([
15374
16681
  ...metaContents(document, ['meta[name="citation_author"]']),
15375
16682
  ...metaContents(document, ['meta[name="author"]', 'meta[property="article:author"]'])
15376
16683
  ]);
@@ -15381,7 +16688,7 @@ async function captureArticleMarkdown(rootDir, input, options, extra = { sourceT
15381
16688
  'meta[name="pubdate"]'
15382
16689
  ]);
15383
16690
  const updatedAt = firstMetaContent(document, ['meta[property="article:modified_time"]', 'meta[name="lastmod"]']);
15384
- const tags = uniqueStrings2([
16691
+ const tags = uniqueStrings3([
15385
16692
  ...metaContents(document, ['meta[property="article:tag"]']),
15386
16693
  ...splitKeywords(firstMetaContent(document, ['meta[name="keywords"]']))
15387
16694
  ]);
@@ -17339,7 +18646,7 @@ async function readExtractionArtifact(rootDir, manifest) {
17339
18646
  }
17340
18647
 
17341
18648
  // src/mcp.ts
17342
- import fs22 from "fs/promises";
18649
+ import fs23 from "fs/promises";
17343
18650
  import path27 from "path";
17344
18651
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
17345
18652
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -17511,7 +18818,7 @@ function contentTokens(text, minLength = 4) {
17511
18818
  }
17512
18819
 
17513
18820
  // src/analysis.ts
17514
- var ANALYSIS_FORMAT_VERSION = 7;
18821
+ var ANALYSIS_FORMAT_VERSION = 8;
17515
18822
  var sourceAnalysisSchema = z2.object({
17516
18823
  title: z2.string().min(1),
17517
18824
  summary: z2.string().min(1),
@@ -17535,6 +18842,42 @@ var HEURISTIC_SECTION_SOURCE_KINDS = /* @__PURE__ */ new Map([
17535
18842
  ["email", "Message"],
17536
18843
  ["calendar", "Description"]
17537
18844
  ]);
18845
+ var MARKDOWN_RATIONALE_KINDS = /* @__PURE__ */ new Set([
18846
+ "markdown",
18847
+ "html",
18848
+ "pdf",
18849
+ "docx",
18850
+ "epub",
18851
+ "odt",
18852
+ "rtf",
18853
+ "org",
18854
+ "asciidoc",
18855
+ "jupyter"
18856
+ ]);
18857
+ var PLAIN_TEXT_RATIONALE_KINDS = /* @__PURE__ */ new Set(["text", "transcript", "chat_export", "email", "calendar"]);
18858
+ function filenameStemForSource(manifest) {
18859
+ const candidate = manifest.repoRelativePath ?? manifest.originalPath ?? manifest.storedPath;
18860
+ const base = path18.basename(candidate);
18861
+ const stem = base.replace(/\.[^.]+$/, "");
18862
+ return stem || manifest.title;
18863
+ }
18864
+ function extractNonCodeRationales(manifest, rawText) {
18865
+ if (!rawText.trim()) {
18866
+ return [];
18867
+ }
18868
+ if (MARKDOWN_RATIONALE_KINDS.has(manifest.sourceKind)) {
18869
+ const fallback = filenameStemForSource(manifest);
18870
+ const rationales = extractRationaleFromMarkdown(rawText, manifest.sourceId);
18871
+ return rationales.map((entry) => ({
18872
+ ...entry,
18873
+ symbolName: entry.symbolName ?? fallback
18874
+ }));
18875
+ }
18876
+ if (PLAIN_TEXT_RATIONALE_KINDS.has(manifest.sourceKind)) {
18877
+ return extractRationaleFromPlainText(rawText, manifest.sourceId, filenameStemForSource(manifest));
18878
+ }
18879
+ return [];
18880
+ }
17538
18881
  function extractTopTerms(text, count) {
17539
18882
  const frequency = /* @__PURE__ */ new Map();
17540
18883
  for (const token of contentTokens(text)) {
@@ -17866,6 +19209,12 @@ async function analyzeSource(manifest, extractedText, provider, paths, schema) {
17866
19209
  analysis = heuristicAnalysis(manifest, content, schema.hash);
17867
19210
  }
17868
19211
  }
19212
+ if (manifest.sourceKind !== "code" && !analysis.rationales.length) {
19213
+ const extra = extractNonCodeRationales(manifest, extractedText ?? "");
19214
+ if (extra.length) {
19215
+ analysis = { ...analysis, rationales: extra };
19216
+ }
19217
+ }
17869
19218
  const normalized = normalizeSourceAnalysis(manifest, analysis);
17870
19219
  await writeJsonFile(cachePath, normalized);
17871
19220
  return normalized;
@@ -19106,7 +20455,7 @@ async function guidedSourceSessionStatePath(rootDir, sessionId) {
19106
20455
  // src/vault.ts
19107
20456
  var COMPILE_PROGRESS_THRESHOLD = 120;
19108
20457
  var COMPILE_PROGRESS_UPDATE_INTERVAL = 50;
19109
- function uniqueStrings3(values) {
20458
+ function uniqueStrings4(values) {
19110
20459
  return uniqueBy(values.filter(Boolean), (value) => value);
19111
20460
  }
19112
20461
  function createCompileProgressReporter(phase, totalItems) {
@@ -19297,7 +20646,7 @@ async function resolveImageGenerationProvider(rootDir) {
19297
20646
  if (!providerConfig) {
19298
20647
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
19299
20648
  }
19300
- const { createProvider: createProvider2 } = await import("./registry-SUXWCWB4.js");
20649
+ const { createProvider: createProvider2 } = await import("./registry-QAG2ZYH3.js");
19301
20650
  return createProvider2(preferredProviderId, providerConfig, rootDir);
19302
20651
  }
19303
20652
  async function generateOutputArtifacts(rootDir, input) {
@@ -19501,7 +20850,7 @@ function normalizeProjectRoot(root) {
19501
20850
  function projectEntries(config) {
19502
20851
  return Object.entries(config.projects ?? {}).map(([id, project]) => ({
19503
20852
  id,
19504
- roots: uniqueStrings3(project.roots.map(normalizeProjectRoot)).filter(Boolean),
20853
+ roots: uniqueStrings4(project.roots.map(normalizeProjectRoot)).filter(Boolean),
19505
20854
  schemaPath: project.schemaPath
19506
20855
  })).sort((left, right) => left.id.localeCompare(right.id));
19507
20856
  }
@@ -19549,11 +20898,11 @@ function resolveSourceProjects(rootDir, manifests, config) {
19549
20898
  return Object.fromEntries(manifests.map((manifest) => [manifest.sourceId, resolveSourceProjectId(rootDir, manifest, config)]));
19550
20899
  }
19551
20900
  function scopedProjectIdsFromSources(sourceIds, sourceProjects) {
19552
- const projectIds = uniqueStrings3(sourceIds.map((sourceId) => sourceProjects[sourceId] ?? "").filter(Boolean));
20901
+ const projectIds = uniqueStrings4(sourceIds.map((sourceId) => sourceProjects[sourceId] ?? "").filter(Boolean));
19553
20902
  return projectIds.length === 1 ? projectIds : [];
19554
20903
  }
19555
20904
  function schemaProjectIdsFromPages(pageIds, pageMap2) {
19556
- return uniqueStrings3(
20905
+ return uniqueStrings4(
19557
20906
  pageIds.flatMap((pageId) => pageMap2.get(pageId)?.projectIds ?? []).filter(Boolean).sort((left, right) => left.localeCompare(right))
19558
20907
  );
19559
20908
  }
@@ -19562,7 +20911,7 @@ function categoryTagsForSchema(schema, texts) {
19562
20911
  if (!haystack) {
19563
20912
  return [];
19564
20913
  }
19565
- return uniqueStrings3(
20914
+ return uniqueStrings4(
19566
20915
  schemaCategoryLabels({ path: "", hash: "", content: schema.content }).filter((label) => haystack.includes(label.toLowerCase())).map((label) => `category/${slugify(label)}`)
19567
20916
  ).slice(0, 3);
19568
20917
  }
@@ -19802,8 +21151,8 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
19802
21151
  const manifestBySourceId = new Map(manifests.map((manifest) => [manifest.sourceId, manifest]));
19803
21152
  const timelineManifests = manifests.filter((manifest) => manifestDetailValue(manifest, "occurred_at")).sort((left, right) => (manifestDetailValue(right, "occurred_at") ?? "").localeCompare(manifestDetailValue(left, "occurred_at") ?? "")).slice(0, 25);
19804
21153
  const recentSourcePages = [...sourcePages].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, 20);
19805
- const analyses = await loadAnalysesBySourceIds(paths, uniqueStrings3(sourcePages.flatMap((page) => page.sourceIds)));
19806
- const openQuestions = uniqueStrings3(
21154
+ const analyses = await loadAnalysesBySourceIds(paths, uniqueStrings4(sourcePages.flatMap((page) => page.sourceIds)));
21155
+ const openQuestions = uniqueStrings4(
19807
21156
  analyses.flatMap((analysis) => analysis.questions.map((question) => `${analysis.title}: ${question}`))
19808
21157
  ).slice(0, 20);
19809
21158
  const sourceSessions = await listGuidedSourceSessions(paths.rootDir);
@@ -20047,7 +21396,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20047
21396
  kind: "index",
20048
21397
  title: "Source Sessions",
20049
21398
  tags: ["index", "dashboard", "source-sessions"],
20050
- source_ids: uniqueStrings3([
21399
+ source_ids: uniqueStrings4([
20051
21400
  ...sessionPages.flatMap((page) => page.sourceIds),
20052
21401
  ...sourceSessions.flatMap((session) => session.sourceIds)
20053
21402
  ]),
@@ -20058,7 +21407,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20058
21407
  confidence: 1,
20059
21408
  created_at: metadata.createdAt,
20060
21409
  updated_at: metadata.updatedAt,
20061
- compiled_from: uniqueStrings3([
21410
+ compiled_from: uniqueStrings4([
20062
21411
  ...sessionPages.flatMap((page) => page.sourceIds),
20063
21412
  ...sourceSessions.flatMap((session) => session.sourceIds)
20064
21413
  ]),
@@ -20100,7 +21449,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20100
21449
  kind: "index",
20101
21450
  title: "Source Guides",
20102
21451
  tags: ["index", "dashboard", "source-guides"],
20103
- source_ids: uniqueStrings3([
21452
+ source_ids: uniqueStrings4([
20104
21453
  ...guidePages.flatMap((page) => page.sourceIds),
20105
21454
  ...stagedGuideBundles.flatMap((bundle) => bundle.entries.flatMap((entry) => entry.sourceIds))
20106
21455
  ]),
@@ -20111,7 +21460,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20111
21460
  confidence: 1,
20112
21461
  created_at: metadata.createdAt,
20113
21462
  updated_at: metadata.updatedAt,
20114
- compiled_from: uniqueStrings3([
21463
+ compiled_from: uniqueStrings4([
20115
21464
  ...guidePages.flatMap((page) => page.sourceIds),
20116
21465
  ...stagedGuideBundles.flatMap((bundle) => bundle.entries.flatMap((entry) => entry.sourceIds))
20117
21466
  ]),
@@ -20162,7 +21511,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20162
21511
  kind: "index",
20163
21512
  title: "Research Map",
20164
21513
  tags: ["index", "dashboard", "research-map"],
20165
- source_ids: uniqueStrings3([
21514
+ source_ids: uniqueStrings4([
20166
21515
  ...conceptPages.flatMap((page) => page.sourceIds),
20167
21516
  ...entityPages.flatMap((page) => page.sourceIds),
20168
21517
  ...guidePages.flatMap((page) => page.sourceIds)
@@ -20174,7 +21523,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20174
21523
  confidence: 1,
20175
21524
  created_at: metadata.createdAt,
20176
21525
  updated_at: metadata.updatedAt,
20177
- compiled_from: uniqueStrings3([
21526
+ compiled_from: uniqueStrings4([
20178
21527
  ...conceptPages.flatMap((page) => page.sourceIds),
20179
21528
  ...entityPages.flatMap((page) => page.sourceIds),
20180
21529
  ...guidePages.flatMap((page) => page.sourceIds)
@@ -20324,7 +21673,7 @@ async function buildDashboardRecords(config, paths, graph, schemaHash, report) {
20324
21673
  return records;
20325
21674
  }
20326
21675
  function indexCompiledFrom(pages) {
20327
- return uniqueStrings3(pages.flatMap((page) => page.sourceIds));
21676
+ return uniqueStrings4(pages.flatMap((page) => page.sourceIds));
20328
21677
  }
20329
21678
  function autoResolution(nodeCount, edgeCount) {
20330
21679
  if (nodeCount <= 20) return 0.5;
@@ -20338,6 +21687,21 @@ function pruneDanglingEdges(nodes, edges) {
20338
21687
  function applyNormLabel(nodes) {
20339
21688
  return nodes.map((node) => node.normLabel ? node : { ...node, normLabel: computeNormLabel(node.label) });
20340
21689
  }
21690
+ function describeGodNodeReason(degree, communityCount, degreeMean, degreeStd) {
21691
+ const parts = [`degree ${degree}`];
21692
+ if (communityCount >= 3) {
21693
+ parts.push(`across ${communityCount} communities`);
21694
+ } else if (communityCount === 2) {
21695
+ parts.push("bridges 2 communities");
21696
+ }
21697
+ if (degreeStd > 0) {
21698
+ const sigma = (degree - degreeMean) / degreeStd;
21699
+ if (sigma >= 1.5) {
21700
+ parts.push(`${sigma.toFixed(1)}\u03C3 above mean`);
21701
+ }
21702
+ }
21703
+ return parts.join(", ");
21704
+ }
20341
21705
  function deriveGraphMetrics(nodes, edges, options) {
20342
21706
  const adjacency = /* @__PURE__ */ new Map();
20343
21707
  const connect = (left, right) => {
@@ -20406,6 +21770,9 @@ function deriveGraphMetrics(nodes, edges, options) {
20406
21770
  }
20407
21771
  const degreeValues = nodes.filter((node) => node.type !== "source").map((node) => degreeMap.get(node.id) ?? 0).sort((left, right) => right - left);
20408
21772
  const godNodeThreshold = degreeValues[Math.max(0, Math.floor(degreeValues.length * 0.1) - 1)] ?? 0;
21773
+ const degreeMean = degreeValues.length > 0 ? degreeValues.reduce((sum, value) => sum + value, 0) / degreeValues.length : 0;
21774
+ const degreeVariance = degreeValues.length > 0 ? degreeValues.reduce((sum, value) => sum + (value - degreeMean) ** 2, 0) / degreeValues.length : 0;
21775
+ const degreeStd = Math.sqrt(degreeVariance);
20409
21776
  const nextNodes = nodes.map((node) => {
20410
21777
  const neighborCommunities = new Set(
20411
21778
  [...adjacency.get(node.id) ?? []].map((neighborId) => communityMap.get(neighborId) ?? communityMap.get(node.id)).filter((communityId) => Boolean(communityId))
@@ -20413,12 +21780,14 @@ function deriveGraphMetrics(nodes, edges, options) {
20413
21780
  const degree = degreeMap.get(node.id) ?? 0;
20414
21781
  const bridgeScore = node.type === "source" ? neighborCommunities.size : Math.max(0, neighborCommunities.size - 1);
20415
21782
  const inferredCommunityId = communityMap.get(node.id) ?? [...adjacency.get(node.id) ?? []].map((neighborId) => communityMap.get(neighborId)).find((communityId) => Boolean(communityId));
21783
+ const isGodNode = node.type !== "source" && degree >= godNodeThreshold && degree > 0;
20416
21784
  return {
20417
21785
  ...node,
20418
21786
  communityId: inferredCommunityId,
20419
21787
  degree,
20420
21788
  bridgeScore,
20421
- isGodNode: node.type !== "source" && degree >= godNodeThreshold && degree > 0
21789
+ isGodNode,
21790
+ surpriseReason: isGodNode ? describeGodNodeReason(degree, neighborCommunities.size, degreeMean, degreeStd) : void 0
20422
21791
  };
20423
21792
  });
20424
21793
  return {
@@ -20427,7 +21796,16 @@ function deriveGraphMetrics(nodes, edges, options) {
20427
21796
  };
20428
21797
  }
20429
21798
  function resetGraphNodeMetrics(nodes) {
20430
- return nodes.map(({ communityId: _communityId, degree: _degree, bridgeScore: _bridgeScore, isGodNode: _isGodNode, ...node }) => node);
21799
+ return nodes.map(
21800
+ ({
21801
+ communityId: _communityId,
21802
+ degree: _degree,
21803
+ bridgeScore: _bridgeScore,
21804
+ isGodNode: _isGodNode,
21805
+ surpriseReason: _surpriseReason,
21806
+ ...node
21807
+ }) => node
21808
+ );
20431
21809
  }
20432
21810
  function manifestRepoPath(manifest) {
20433
21811
  return toPosix(manifest.repoRelativePath ?? path25.basename(manifest.originalPath ?? manifest.storedPath));
@@ -20833,6 +22211,34 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex, opti
20833
22211
  provenance: [analysis.sourceId, targetSourceId]
20834
22212
  });
20835
22213
  }
22214
+ } else if (analysis.rationales.length) {
22215
+ const manifest = manifestsById.get(analysis.sourceId);
22216
+ if (manifest) {
22217
+ const sourceNodeId = `source:${analysis.sourceId}`;
22218
+ for (const rationale of analysis.rationales) {
22219
+ rationaleMap.set(rationale.id, {
22220
+ id: rationale.id,
22221
+ type: "rationale",
22222
+ label: truncate(rationale.text, 80),
22223
+ pageId: sourceNodeId,
22224
+ freshness: "fresh",
22225
+ confidence: 1,
22226
+ sourceIds: [analysis.sourceId],
22227
+ projectIds: scopedProjectIdsFromSources([analysis.sourceId], sourceProjects),
22228
+ sourceClass: manifest.sourceClass
22229
+ });
22230
+ pushEdge({
22231
+ id: `${rationale.id}->${sourceNodeId}:rationale_for`,
22232
+ source: rationale.id,
22233
+ target: sourceNodeId,
22234
+ relation: "rationale_for",
22235
+ status: "extracted",
22236
+ evidenceClass: "extracted",
22237
+ confidence: 1,
22238
+ provenance: [analysis.sourceId]
22239
+ });
22240
+ }
22241
+ }
20836
22242
  }
20837
22243
  }
20838
22244
  const conceptClaims = /* @__PURE__ */ new Map();
@@ -20883,6 +22289,10 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex, opti
20883
22289
  ...conceptMap.values(),
20884
22290
  ...entityMap.values()
20885
22291
  ];
22292
+ const repoDefaults = resolveLargeRepoDefaults({
22293
+ nodeCount: graphNodes.length,
22294
+ config: options?.config
22295
+ });
20886
22296
  const enriched = enrichGraph(
20887
22297
  {
20888
22298
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -20893,7 +22303,12 @@ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex, opti
20893
22303
  pages
20894
22304
  },
20895
22305
  manifests,
20896
- analyses
22306
+ analyses,
22307
+ [],
22308
+ {
22309
+ similarityIdfFloor: repoDefaults.similarityIdfFloor,
22310
+ similarityEdgeCap: repoDefaults.similarityEdgeCap
22311
+ }
20897
22312
  );
20898
22313
  const metrics = deriveGraphMetrics(graphNodes, enriched.edges, { resolution: options?.communityResolution });
20899
22314
  const finalNodes = applyNormLabel(metrics.nodes);
@@ -20924,7 +22339,7 @@ function recentResearchSourcePages(graph, previousCompiledAt) {
20924
22339
  sourceType: page.sourceType
20925
22340
  }));
20926
22341
  }
20927
- async function buildGraphOrientationPages(graph, paths, schemaHash, previousCompiledAt, contradictions = []) {
22342
+ async function buildGraphOrientationPages(graph, paths, schemaHash, previousCompiledAt, contradictions = [], config) {
20928
22343
  const benchmark = await readJsonFile(paths.benchmarkPath);
20929
22344
  const communityRecords = [];
20930
22345
  for (const community of graph.communities ?? []) {
@@ -20934,7 +22349,7 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
20934
22349
  absolutePath,
20935
22350
  {
20936
22351
  managedBy: "system",
20937
- compiledFrom: uniqueStrings3(
22352
+ compiledFrom: uniqueStrings4(
20938
22353
  community.nodeIds.flatMap((nodeId) => graph.nodes.find((node) => node.id === nodeId)?.sourceIds ?? [])
20939
22354
  ),
20940
22355
  confidence: 1
@@ -20955,14 +22370,15 @@ async function buildGraphOrientationPages(graph, paths, schemaHash, previousComp
20955
22370
  benchmarkStale: benchmark ? benchmark.graphHash !== graphHash(graph) : false,
20956
22371
  recentResearchSources: recentResearchSourcePages(graph, previousCompiledAt),
20957
22372
  graphHash: graphHash(graph),
20958
- contradictions
22373
+ contradictions,
22374
+ config
20959
22375
  });
20960
22376
  const reportAbsolutePath = path25.join(paths.wikiDir, "graph", "report.md");
20961
22377
  const reportRecord = await buildManagedGraphPage(
20962
22378
  reportAbsolutePath,
20963
22379
  {
20964
22380
  managedBy: "system",
20965
- compiledFrom: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
22381
+ compiledFrom: uniqueStrings4(graph.pages.flatMap((page) => page.sourceIds)),
20966
22382
  confidence: 1
20967
22383
  },
20968
22384
  (metadata) => buildGraphReportPage({
@@ -21298,7 +22714,7 @@ async function syncVaultArtifacts(rootDir, input) {
21298
22714
  const itemKind = kind === "concepts" ? "concept" : "entity";
21299
22715
  const slug = slugify(aggregate.name);
21300
22716
  const pageId = `${itemKind}:${slug}`;
21301
- const sourceIds = uniqueStrings3(aggregate.sourceAnalyses.map((item) => item.sourceId));
22717
+ const sourceIds = uniqueStrings4(aggregate.sourceAnalyses.map((item) => item.sourceId));
21302
22718
  const projectIds = scopedProjectIdsFromSources(sourceIds, input.sourceProjects);
21303
22719
  const schemaHash = effectiveHashForProject(input.schemas, projectIds[0] ?? null);
21304
22720
  const previousEntry = input.previousState?.candidateHistory?.[pageId];
@@ -21369,7 +22785,8 @@ async function syncVaultArtifacts(rootDir, input) {
21369
22785
  const compiledPages = records.map((record) => record.page);
21370
22786
  const basePages = [...compiledPages, ...input.outputPages, ...input.insightPages];
21371
22787
  const structuralGraph = buildGraph(input.manifests, input.analyses, basePages, input.sourceProjects, input.codeIndex, {
21372
- communityResolution: config.graph?.communityResolution
22788
+ communityResolution: config.graph?.communityResolution,
22789
+ config
21373
22790
  });
21374
22791
  const contradictions = detectContradictions(input.analyses);
21375
22792
  for (const contradiction of contradictions) {
@@ -21407,7 +22824,8 @@ async function syncVaultArtifacts(rootDir, input) {
21407
22824
  paths,
21408
22825
  globalSchemaHash,
21409
22826
  input.previousState?.generatedAt,
21410
- contradictions
22827
+ contradictions,
22828
+ config
21411
22829
  );
21412
22830
  const preliminaryPages = [...basePages, ...graphOrientation.records.map((record) => record.page)];
21413
22831
  const dashboardRecords = await buildDashboardRecords(
@@ -21560,7 +22978,7 @@ async function syncVaultArtifacts(rootDir, input) {
21560
22978
  const nextPagePaths = new Set(records.map((record) => record.page.path));
21561
22979
  const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
21562
22980
  const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path25.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
21563
- const obsoletePaths = uniqueStrings3([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
22981
+ const obsoletePaths = uniqueStrings4([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
21564
22982
  const changedFiles = [];
21565
22983
  for (const record of records) {
21566
22984
  const absolutePath = path25.join(paths.wikiDir, record.page.path);
@@ -21619,7 +23037,7 @@ async function syncVaultArtifacts(rootDir, input) {
21619
23037
  return {
21620
23038
  graph,
21621
23039
  allPages,
21622
- changedPages: uniqueStrings3([...changedPages, ...writeChanges]),
23040
+ changedPages: uniqueStrings4([...changedPages, ...writeChanges]),
21623
23041
  promotedPageIds,
21624
23042
  candidatePageCount: candidatePages.length,
21625
23043
  staged: false
@@ -21648,7 +23066,9 @@ async function refreshIndexesAndSearch(rootDir, pages) {
21648
23066
  },
21649
23067
  paths,
21650
23068
  globalSchemaHash,
21651
- compileState?.generatedAt
23069
+ compileState?.generatedAt,
23070
+ [],
23071
+ config
21652
23072
  ) : { records: [], report: null };
21653
23073
  const dashboardRecords = currentGraph ? await buildDashboardRecords(
21654
23074
  config,
@@ -21817,7 +23237,7 @@ async function prepareOutputPageSave(rootDir, input) {
21817
23237
  status: "active",
21818
23238
  createdAt: now,
21819
23239
  updatedAt: now,
21820
- compiledFrom: uniqueStrings3(input.relatedSourceIds ?? input.citations),
23240
+ compiledFrom: uniqueStrings4(input.relatedSourceIds ?? input.citations),
21821
23241
  managedBy: "system",
21822
23242
  confidence: 0.74
21823
23243
  }
@@ -21858,7 +23278,7 @@ async function prepareExploreHubSave(rootDir, input) {
21858
23278
  status: "active",
21859
23279
  createdAt: now,
21860
23280
  updatedAt: now,
21861
- compiledFrom: uniqueStrings3(input.citations),
23281
+ compiledFrom: uniqueStrings4(input.citations),
21862
23282
  managedBy: "system",
21863
23283
  confidence: 0.76
21864
23284
  }
@@ -22536,7 +23956,7 @@ async function promoteCandidate(rootDir, target) {
22536
23956
  ...parsed.data,
22537
23957
  status: "active",
22538
23958
  updated_at: nextUpdatedAt,
22539
- tags: uniqueStrings3([candidate.kind, ...Array.isArray(parsed.data.tags) ? parsed.data.tags : []]).filter(
23959
+ tags: uniqueStrings4([candidate.kind, ...Array.isArray(parsed.data.tags) ? parsed.data.tags : []]).filter(
22540
23960
  (tag) => tag !== "candidate"
22541
23961
  )
22542
23962
  });
@@ -23130,7 +24550,7 @@ async function compileVault(rootDir, options = {}) {
23130
24550
  const currentInsightHashes = pageHashes(storedInsightPages);
23131
24551
  const previousState = await readJsonFile(paths.compileStatePath);
23132
24552
  const rootSchemaChanged = !previousState || previousState.rootSchemaHash !== schemas.root.hash;
23133
- const effectiveSchemaChanged = !previousState || previousGlobalSchemaHash(previousState) !== schemas.effective.global.hash || uniqueStrings3([...Object.keys(previousState?.effectiveSchemaHashes?.projects ?? {}), ...Object.keys(schemas.effective.projects)]).some(
24553
+ const effectiveSchemaChanged = !previousState || previousGlobalSchemaHash(previousState) !== schemas.effective.global.hash || uniqueStrings4([...Object.keys(previousState?.effectiveSchemaHashes?.projects ?? {}), ...Object.keys(schemas.effective.projects)]).some(
23134
24554
  (projectId) => previousProjectSchemaHash(previousState, projectId) !== effectiveHashForProject(schemas, projectId)
23135
24555
  );
23136
24556
  const nextProjectConfigHash = projectConfigHash(config);
@@ -23324,7 +24744,7 @@ async function compileVault(rootDir, options = {}) {
23324
24744
  config: config.freshness
23325
24745
  });
23326
24746
  if (decayResult.updatedPaths.length) {
23327
- sync.changedPages = uniqueStrings3([...sync.changedPages, ...decayResult.updatedPaths]);
24747
+ sync.changedPages = uniqueStrings4([...sync.changedPages, ...decayResult.updatedPaths]);
23328
24748
  }
23329
24749
  } catch {
23330
24750
  }
@@ -23333,7 +24753,7 @@ async function compileVault(rootDir, options = {}) {
23333
24753
  try {
23334
24754
  const consolidation = await runConsolidation(rootDir, config.consolidation ?? {});
23335
24755
  if (consolidation.newPages.length) {
23336
- sync.changedPages = uniqueStrings3([...sync.changedPages, ...consolidation.newPages.map((page) => page.path)]);
24756
+ sync.changedPages = uniqueStrings4([...sync.changedPages, ...consolidation.newPages.map((page) => page.path)]);
23337
24757
  }
23338
24758
  } catch {
23339
24759
  }
@@ -23694,7 +25114,7 @@ ${orchestrationNotes.join("\n")}
23694
25114
  citations: allCitations,
23695
25115
  format: outputFormat,
23696
25116
  relatedPageCount: stepPages.length,
23697
- relatedNodeCount: uniqueStrings3(stepPages.flatMap((page) => page.nodeIds)).length,
25117
+ relatedNodeCount: uniqueStrings4(stepPages.flatMap((page) => page.nodeIds)).length,
23698
25118
  projectId: stepPages[0]?.projectIds[0] ?? null
23699
25119
  });
23700
25120
  const hubInput = {
@@ -23704,7 +25124,7 @@ ${orchestrationNotes.join("\n")}
23704
25124
  citations: allCitations,
23705
25125
  schemaHash: composeVaultSchema(
23706
25126
  schemas.root,
23707
- uniqueStrings3(stepPages.flatMap((page) => page.projectIds).sort((left, right) => left.localeCompare(right))).map((projectId) => schemas.projects[projectId]).filter((schema) => Boolean(schema?.hash))
25127
+ uniqueStrings4(stepPages.flatMap((page) => page.projectIds).sort((left, right) => left.localeCompare(right))).map((projectId) => schemas.projects[projectId]).filter((schema) => Boolean(schema?.hash))
23708
25128
  ).hash,
23709
25129
  outputFormat,
23710
25130
  outputAssets: hubAssetBundle.outputAssets,
@@ -23764,7 +25184,7 @@ ${orchestrationNotes.join("\n")}
23764
25184
  providerId: provider.id,
23765
25185
  success: true,
23766
25186
  relatedSourceIds: [...relatedSourceIds],
23767
- relatedPageIds: uniqueStrings3([...relatedPageIds, ...stepPages.map((page) => page.id), hubPage.id]),
25187
+ relatedPageIds: uniqueStrings4([...relatedPageIds, ...stepPages.map((page) => page.id), hubPage.id]),
23768
25188
  relatedNodeIds: [...relatedNodeIds],
23769
25189
  citations: allCitations,
23770
25190
  tokenUsage: tokenUsage.inputTokens > 0 || tokenUsage.outputTokens > 0 ? {
@@ -23879,10 +25299,19 @@ async function benchmarkVault(rootDir, options = {}) {
23879
25299
  const manifests = await listManifests(rootDir);
23880
25300
  const pageContentsById = /* @__PURE__ */ new Map();
23881
25301
  let corpusWords = 0;
25302
+ const perClassCorpusWords = {
25303
+ first_party: 0,
25304
+ third_party: 0,
25305
+ resource: 0,
25306
+ generated: 0
25307
+ };
23882
25308
  for (const manifest of manifests) {
23883
25309
  const extractedText = await readExtractedText(rootDir, manifest);
23884
25310
  if (extractedText) {
23885
- corpusWords += estimateCorpusWords([extractedText]);
25311
+ const words = estimateCorpusWords([extractedText]);
25312
+ corpusWords += words;
25313
+ const manifestClass = manifest.sourceClass ?? "first_party";
25314
+ perClassCorpusWords[manifestClass] = (perClassCorpusWords[manifestClass] ?? 0) + words;
23886
25315
  }
23887
25316
  }
23888
25317
  for (const page of graph.pages) {
@@ -23911,11 +25340,51 @@ async function benchmarkVault(rootDir, options = {}) {
23911
25340
  };
23912
25341
  })
23913
25342
  );
25343
+ const perClassPerQuestion = {
25344
+ first_party: [],
25345
+ third_party: [],
25346
+ resource: [],
25347
+ generated: []
25348
+ };
25349
+ for (const sourceClass of ALL_SOURCE_CLASSES) {
25350
+ const filteredGraph = filterGraphBySourceClass(graph, sourceClass);
25351
+ if (!filteredGraph.nodes.length) {
25352
+ continue;
25353
+ }
25354
+ const classPageContents = /* @__PURE__ */ new Map();
25355
+ for (const page of filteredGraph.pages) {
25356
+ const content = pageContentsById.get(page.id);
25357
+ if (content !== void 0) {
25358
+ classPageContents.set(page.id, content);
25359
+ }
25360
+ }
25361
+ const classResults = await Promise.all(
25362
+ sampleQuestions.map(async (question) => {
25363
+ const result = await runResolvedGraphQuery(rootDir, filteredGraph, question, { budget: 12 });
25364
+ const metrics = benchmarkQueryTokens(filteredGraph, result, classPageContents);
25365
+ return {
25366
+ question,
25367
+ queryTokens: metrics.queryTokens,
25368
+ reduction: metrics.reduction,
25369
+ visitedNodeIds: result.visitedNodeIds,
25370
+ visitedEdgeIds: result.visitedEdgeIds,
25371
+ pageIds: result.pageIds
25372
+ };
25373
+ })
25374
+ );
25375
+ perClassPerQuestion[sourceClass] = classResults;
25376
+ }
25377
+ const byClass = buildBenchmarkByClass({
25378
+ graph,
25379
+ perClassCorpusWords,
25380
+ perClassPerQuestion
25381
+ });
23914
25382
  const artifact = buildBenchmarkArtifact({
23915
25383
  graph,
23916
25384
  corpusWords,
23917
25385
  questions: sampleQuestions,
23918
- perQuestion
25386
+ perQuestion,
25387
+ byClass
23919
25388
  });
23920
25389
  await writeJsonFile(paths.benchmarkPath, artifact);
23921
25390
  await refreshIndexesAndSearch(rootDir, graph.pages);
@@ -23947,9 +25416,15 @@ async function readGraphReport(rootDir) {
23947
25416
  const { paths } = await loadVaultConfig(rootDir);
23948
25417
  return readJsonFile(path25.join(paths.wikiDir, "graph", "report.json"));
23949
25418
  }
23950
- async function listGodNodes(rootDir, limit = 10) {
25419
+ async function listGodNodes(rootDir, limit) {
23951
25420
  const graph = await ensureCompiledGraph(rootDir);
23952
- return topGodNodes(graph, limit);
25421
+ const { config } = await loadVaultConfig(rootDir);
25422
+ const defaults = resolveLargeRepoDefaults({
25423
+ nodeCount: graph.nodes.length,
25424
+ config
25425
+ });
25426
+ const effectiveLimit = limit ?? defaults.godNodeLimit;
25427
+ return topGodNodes(graph, effectiveLimit);
23953
25428
  }
23954
25429
  async function blastRadiusVault(rootDir, target, options) {
23955
25430
  const graph = await ensureCompiledGraph(rootDir);
@@ -24230,7 +25705,7 @@ async function lintVault(rootDir, options = {}) {
24230
25705
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
24231
25706
  success: true,
24232
25707
  relatedPageIds: graph.pages.map((page) => page.id),
24233
- relatedSourceIds: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
25708
+ relatedSourceIds: uniqueStrings4(graph.pages.flatMap((page) => page.sourceIds)),
24234
25709
  lintFindingCount: contradictionFindings.length,
24235
25710
  lines: [`findings=${contradictionFindings.length}`, `deep=false`, `web=false`, `conflicts=true`]
24236
25711
  });
@@ -24245,7 +25720,7 @@ async function lintVault(rootDir, options = {}) {
24245
25720
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
24246
25721
  success: true,
24247
25722
  relatedPageIds: graph.pages.map((page) => page.id),
24248
- relatedSourceIds: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
25723
+ relatedSourceIds: uniqueStrings4(graph.pages.flatMap((page) => page.sourceIds)),
24249
25724
  lintFindingCount: decayFindings.length,
24250
25725
  lines: [`findings=${decayFindings.length}`, `deep=false`, `web=false`, `conflicts=false`, `decay=true`]
24251
25726
  });
@@ -24260,7 +25735,7 @@ async function lintVault(rootDir, options = {}) {
24260
25735
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
24261
25736
  success: true,
24262
25737
  relatedPageIds: graph.pages.map((page) => page.id),
24263
- relatedSourceIds: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
25738
+ relatedSourceIds: uniqueStrings4(graph.pages.flatMap((page) => page.sourceIds)),
24264
25739
  lintFindingCount: tierFindings.length,
24265
25740
  lines: [`findings=${tierFindings.length}`, `deep=false`, `web=false`, `conflicts=false`, `tiers=true`]
24266
25741
  });
@@ -24284,7 +25759,7 @@ async function lintVault(rootDir, options = {}) {
24284
25759
  providerId: provider?.id,
24285
25760
  success: true,
24286
25761
  relatedPageIds: graph.pages.map((page) => page.id),
24287
- relatedSourceIds: uniqueStrings3(graph.pages.flatMap((page) => page.sourceIds)),
25762
+ relatedSourceIds: uniqueStrings4(graph.pages.flatMap((page) => page.sourceIds)),
24288
25763
  lintFindingCount: findings.length,
24289
25764
  lines: [
24290
25765
  `findings=${findings.length}`,
@@ -24314,6 +25789,7 @@ async function consolidateVault(rootDir, options = {}) {
24314
25789
  }
24315
25790
 
24316
25791
  // src/watch.ts
25792
+ import fs22 from "fs/promises";
24317
25793
  import path26 from "path";
24318
25794
  import process3 from "process";
24319
25795
  import chokidar from "chokidar";
@@ -24417,15 +25893,96 @@ function workspaceIgnoreRoots(rootDir, paths) {
24417
25893
  async function resolveWatchTargets(rootDir, paths, options) {
24418
25894
  const targets = /* @__PURE__ */ new Set([path26.resolve(paths.inboxDir)]);
24419
25895
  if (options.repo) {
24420
- for (const repoRoot of await listTrackedRepoRoots(rootDir)) {
25896
+ for (const repoRoot of await resolveWatchedRepoRoots(rootDir, { overrideRoots: options.overrideRoots })) {
24421
25897
  targets.add(path26.resolve(repoRoot));
24422
25898
  }
24423
25899
  }
24424
25900
  return [...targets].sort((left, right) => left.localeCompare(right));
24425
25901
  }
25902
+ function resolveRootRelative(rootDir, candidate) {
25903
+ return path26.isAbsolute(candidate) ? path26.resolve(candidate) : path26.resolve(rootDir, candidate);
25904
+ }
25905
+ async function resolveWatchedRepoRoots(rootDir, options = {}) {
25906
+ const override = options.overrideRoots?.filter(Boolean) ?? [];
25907
+ if (override.length > 0) {
25908
+ return dedupeSorted(override.map((candidate) => resolveRootRelative(rootDir, candidate)));
25909
+ }
25910
+ const config = options.config ?? (await loadVaultConfig(rootDir).catch(() => null))?.config;
25911
+ const watchConfig = config?.watch ?? {};
25912
+ const explicit = watchConfig.repoRoots?.filter(Boolean) ?? [];
25913
+ const baseRoots = explicit.length > 0 ? explicit.map((candidate) => resolveRootRelative(rootDir, candidate)) : await listTrackedRepoRoots(rootDir);
25914
+ const excluded = new Set(
25915
+ (watchConfig.excludeRepoRoots ?? []).filter(Boolean).map((candidate) => resolveRootRelative(rootDir, candidate))
25916
+ );
25917
+ return dedupeSorted(baseRoots.filter((candidate) => !excluded.has(path26.resolve(candidate))));
25918
+ }
25919
+ async function listWatchedRoots(rootDir, options = {}) {
25920
+ return resolveWatchedRepoRoots(rootDir, options);
25921
+ }
25922
+ function dedupeSorted(values) {
25923
+ return [...new Set(values.map((value) => path26.resolve(value)))].sort((left, right) => left.localeCompare(right));
25924
+ }
25925
+ async function readConfigJson(rootDir) {
25926
+ const configPath = path26.join(rootDir, "swarmvault.config.json");
25927
+ const raw = await fs22.readFile(configPath, "utf8");
25928
+ const parsed = JSON.parse(raw);
25929
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
25930
+ throw new Error("swarmvault.config.json must contain a JSON object.");
25931
+ }
25932
+ return { path: configPath, content: parsed };
25933
+ }
25934
+ async function writeConfigJson(configPath, content) {
25935
+ await fs22.writeFile(configPath, `${JSON.stringify(content, null, 2)}
25936
+ `, "utf8");
25937
+ }
25938
+ function getWatchBlock(config) {
25939
+ const block = config.watch;
25940
+ if (block && typeof block === "object" && !Array.isArray(block)) {
25941
+ return block;
25942
+ }
25943
+ return {};
25944
+ }
25945
+ async function addWatchedRoot(rootDir, candidate) {
25946
+ const resolved = resolveRootRelative(rootDir, candidate);
25947
+ const { path: configPath, content } = await readConfigJson(rootDir);
25948
+ const watchBlock = getWatchBlock(content);
25949
+ const current = Array.isArray(watchBlock.repoRoots) ? watchBlock.repoRoots.filter((value) => typeof value === "string") : [];
25950
+ const resolvedSet = new Set(current.map((value) => resolveRootRelative(rootDir, value)));
25951
+ if (!resolvedSet.has(resolved)) {
25952
+ current.push(resolved);
25953
+ }
25954
+ watchBlock.repoRoots = [...new Set(current.map((value) => resolveRootRelative(rootDir, value)))].sort(
25955
+ (left, right) => left.localeCompare(right)
25956
+ );
25957
+ content.watch = watchBlock;
25958
+ await writeConfigJson(configPath, content);
25959
+ return resolved;
25960
+ }
25961
+ async function removeWatchedRoot(rootDir, candidate) {
25962
+ const resolved = resolveRootRelative(rootDir, candidate);
25963
+ const { path: configPath, content } = await readConfigJson(rootDir);
25964
+ const watchBlock = getWatchBlock(content);
25965
+ const current = Array.isArray(watchBlock.repoRoots) ? watchBlock.repoRoots.filter((value) => typeof value === "string") : [];
25966
+ const filtered = current.filter((value) => resolveRootRelative(rootDir, value) !== resolved);
25967
+ const removed = filtered.length !== current.length;
25968
+ if (filtered.length > 0) {
25969
+ watchBlock.repoRoots = filtered;
25970
+ content.watch = watchBlock;
25971
+ } else if ("repoRoots" in watchBlock) {
25972
+ delete watchBlock.repoRoots;
25973
+ if (Object.keys(watchBlock).length === 0) {
25974
+ delete content.watch;
25975
+ } else {
25976
+ content.watch = watchBlock;
25977
+ }
25978
+ }
25979
+ await writeConfigJson(configPath, content);
25980
+ return removed;
25981
+ }
24426
25982
  async function performWatchCycle(rootDir, paths, options, codeOnly = false) {
24427
25983
  const imported = await importInbox(rootDir, paths.inboxDir);
24428
- const repoSync = options.repo ? await syncTrackedReposForWatch(rootDir) : null;
25984
+ const repoRoots = options.repo ? await resolveWatchedRepoRoots(rootDir, { overrideRoots: options.overrideRoots }) : void 0;
25985
+ const repoSync = options.repo ? await syncTrackedReposForWatch(rootDir, void 0, repoRoots) : null;
24429
25986
  const compile = await compileVault(rootDir, { codeOnly });
24430
25987
  const pendingSemanticRefresh = repoSync ? await mergePendingSemanticRefresh(rootDir, repoSync.pendingSemanticRefresh) : await readPendingSemanticRefresh(rootDir);
24431
25988
  const stalePagePaths = await markPagesStaleForSources(
@@ -24794,7 +26351,7 @@ async function watchVault(rootDir, options = {}) {
24794
26351
  }
24795
26352
  async function getWatchStatus(rootDir) {
24796
26353
  const persisted = await readWatchStatusArtifact(rootDir);
24797
- const watchedRepoRoots = await listTrackedRepoRoots(rootDir);
26354
+ const watchedRepoRoots = await resolveWatchedRepoRoots(rootDir);
24798
26355
  const pendingSemanticRefresh = await readPendingSemanticRefresh(rootDir);
24799
26356
  return {
24800
26357
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -24805,7 +26362,7 @@ async function getWatchStatus(rootDir) {
24805
26362
  }
24806
26363
 
24807
26364
  // src/mcp.ts
24808
- var SERVER_VERSION = "0.10.0";
26365
+ var SERVER_VERSION = "0.11.0";
24809
26366
  async function createMcpServer(rootDir) {
24810
26367
  const server = new McpServer({
24811
26368
  name: "swarmvault",
@@ -25237,7 +26794,7 @@ async function createMcpServer(rootDir) {
25237
26794
  }
25238
26795
  const { paths } = await loadVaultConfig(rootDir);
25239
26796
  const absolutePath = path27.resolve(paths.wikiDir, relativePath);
25240
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs22.readFile(absolutePath, "utf8"));
26797
+ return asTextResource(`swarmvault://pages/${encodedPath}`, await fs23.readFile(absolutePath, "utf8"));
25241
26798
  }
25242
26799
  );
25243
26800
  server.registerResource(
@@ -25270,7 +26827,7 @@ async function createMcpServer(rootDir) {
25270
26827
  if (!isPathWithin(paths.sessionsDir, absolutePath) || !await fileExists(absolutePath)) {
25271
26828
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
25272
26829
  }
25273
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs22.readFile(absolutePath, "utf8"));
26830
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs23.readFile(absolutePath, "utf8"));
25274
26831
  }
25275
26832
  );
25276
26833
  return server;
@@ -25329,7 +26886,7 @@ function asTextResource(uri, text) {
25329
26886
  }
25330
26887
 
25331
26888
  // src/schedule.ts
25332
- import fs23 from "fs/promises";
26889
+ import fs24 from "fs/promises";
25333
26890
  import path28 from "path";
25334
26891
  function scheduleStatePath(schedulesDir, jobId) {
25335
26892
  return path28.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
@@ -25438,13 +26995,13 @@ async function acquireJobLease(rootDir, jobId) {
25438
26995
  const { paths } = await loadVaultConfig(rootDir);
25439
26996
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
25440
26997
  await ensureDir(paths.schedulesDir);
25441
- const handle = await fs23.open(leasePath, "wx");
26998
+ const handle = await fs24.open(leasePath, "wx");
25442
26999
  await handle.writeFile(`${process.pid}
25443
27000
  ${(/* @__PURE__ */ new Date()).toISOString()}
25444
27001
  `);
25445
27002
  await handle.close();
25446
27003
  return async () => {
25447
- await fs23.rm(leasePath, { force: true });
27004
+ await fs24.rm(leasePath, { force: true });
25448
27005
  };
25449
27006
  }
25450
27007
  async function listSchedules(rootDir) {
@@ -25603,7 +27160,7 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
25603
27160
 
25604
27161
  // src/sources.ts
25605
27162
  import { spawn as spawn2 } from "child_process";
25606
- import fs24 from "fs/promises";
27163
+ import fs25 from "fs/promises";
25607
27164
  import path29 from "path";
25608
27165
  import matter13 from "gray-matter";
25609
27166
  import { JSDOM as JSDOM3 } from "jsdom";
@@ -25625,7 +27182,7 @@ var DOCS_HINT_SEGMENTS = /* @__PURE__ */ new Set([
25625
27182
  "apis",
25626
27183
  "getting-started"
25627
27184
  ]);
25628
- function uniqueStrings4(values) {
27185
+ function uniqueStrings5(values) {
25629
27186
  return uniqueBy(values.filter(Boolean), (value) => value);
25630
27187
  }
25631
27188
  function sourceOutputSchemaHash(schemas, projectIds) {
@@ -25634,7 +27191,7 @@ function sourceOutputSchemaHash(schemas, projectIds) {
25634
27191
  }
25635
27192
  return composeVaultSchema(
25636
27193
  schemas.root,
25637
- uniqueStrings4([...projectIds].sort((left, right) => left.localeCompare(right))).map((projectId) => schemas.projects[projectId]).filter((schema) => Boolean(schema?.hash))
27194
+ uniqueStrings5([...projectIds].sort((left, right) => left.localeCompare(right))).map((projectId) => schemas.projects[projectId]).filter((schema) => Boolean(schema?.hash))
25638
27195
  ).hash;
25639
27196
  }
25640
27197
  function normalizeManagedStatus(value) {
@@ -25656,7 +27213,7 @@ function withinRoot2(rootPath, targetPath) {
25656
27213
  async function findNearestGitRoot3(startPath) {
25657
27214
  let current = path29.resolve(startPath);
25658
27215
  try {
25659
- const stat = await fs24.stat(current);
27216
+ const stat = await fs25.stat(current);
25660
27217
  if (!stat.isDirectory()) {
25661
27218
  current = path29.dirname(current);
25662
27219
  }
@@ -25772,7 +27329,7 @@ async function fetchHtml(url) {
25772
27329
  async function crawlDocsSource(url, maxPages, maxDepth) {
25773
27330
  const startUrl = new URL(normalizeUrlWithoutHash(url));
25774
27331
  const initial = await fetchHtml(startUrl.toString());
25775
- const sameDomainDocsLinks = uniqueStrings4(
27332
+ const sameDomainDocsLinks = uniqueStrings5(
25776
27333
  initial.links.filter((candidate) => {
25777
27334
  const parsed = new URL(candidate);
25778
27335
  return isAllowedDocsCandidate(parsed, startUrl);
@@ -25837,7 +27394,7 @@ function matchesManagedSourceSpec(existing, input) {
25837
27394
  async function resolveManagedSourceInput(rootDir, input) {
25838
27395
  const absoluteInput = path29.resolve(rootDir, input);
25839
27396
  if (!(input.startsWith("http://") || input.startsWith("https://"))) {
25840
- const stat = await fs24.stat(absoluteInput).catch(() => null);
27397
+ const stat = await fs25.stat(absoluteInput).catch(() => null);
25841
27398
  if (!stat) {
25842
27399
  throw new Error(`Source not found: ${input}`);
25843
27400
  }
@@ -25958,7 +27515,7 @@ async function runGitCommand(cwd, args) {
25958
27515
  async function syncGitHubRepoSource(rootDir, entry) {
25959
27516
  const workingDir = await managedSourceWorkingDir(rootDir, entry.id);
25960
27517
  const checkoutDir = path29.join(workingDir, "checkout");
25961
- await fs24.rm(checkoutDir, { recursive: true, force: true });
27518
+ await fs25.rm(checkoutDir, { recursive: true, force: true });
25962
27519
  await ensureDir(workingDir);
25963
27520
  if (!entry.url) {
25964
27521
  throw new Error(`Managed source ${entry.id} is missing its repository URL.`);
@@ -25998,7 +27555,7 @@ async function syncCrawlSource(rootDir, entry, options) {
25998
27555
  }
25999
27556
  return {
26000
27557
  title: crawl.title,
26001
- sourceIds: uniqueStrings4(currentSourceIds).sort((left, right) => left.localeCompare(right)),
27558
+ sourceIds: uniqueStrings5(currentSourceIds).sort((left, right) => left.localeCompare(right)),
26002
27559
  counts: {
26003
27560
  scannedCount: crawl.pages.length,
26004
27561
  importedCount,
@@ -26097,10 +27654,10 @@ function renderDeterministicSourceBrief(input) {
26097
27654
  const sourcePages = input.sourcePages.filter((page) => page.kind === "source").slice(0, 6);
26098
27655
  const conceptPages = input.sourcePages.filter((page) => page.kind === "concept").slice(0, 6);
26099
27656
  const entityPages = input.sourcePages.filter((page) => page.kind === "entity").slice(0, 6);
26100
- const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 5);
27657
+ const questions = uniqueStrings5(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 5);
26101
27658
  const summary = truncate(
26102
27659
  normalizeWhitespace(
26103
- uniqueStrings4(input.analyses.map((analysis) => analysis.summary).filter(Boolean)).join(" ") || `${input.source.title} has been compiled into a local source graph.`
27660
+ uniqueStrings5(input.analyses.map((analysis) => analysis.summary).filter(Boolean)).join(" ") || `${input.source.title} has been compiled into a local source graph.`
26104
27661
  ),
26105
27662
  320
26106
27663
  );
@@ -26224,7 +27781,7 @@ async function writeSourceBriefForScope(rootDir, source) {
26224
27781
  const relatedPages = graph ? scopedSourcePages(graph, source.sourceIds) : [];
26225
27782
  const relatedPageIds = relatedPages.slice(0, 12).map((page) => page.id);
26226
27783
  const relatedNodeIds = graph ? scopedNodeIds(graph, source.sourceIds).slice(0, 20) : [];
26227
- const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
27784
+ const projectIds = uniqueStrings5(relatedPages.flatMap((page) => page.projectIds));
26228
27785
  const now = (/* @__PURE__ */ new Date()).toISOString();
26229
27786
  const output = buildOutputPage({
26230
27787
  title: `Source Brief: ${source.title}`,
@@ -26251,7 +27808,7 @@ async function writeSourceBriefForScope(rootDir, source) {
26251
27808
  });
26252
27809
  const absolutePath = path29.join(paths.wikiDir, output.page.path);
26253
27810
  await ensureDir(path29.dirname(absolutePath));
26254
- await fs24.writeFile(absolutePath, output.content, "utf8");
27811
+ await fs25.writeFile(absolutePath, output.content, "utf8");
26255
27812
  return absolutePath;
26256
27813
  }
26257
27814
  async function writeSourceBrief(rootDir, source) {
@@ -26343,7 +27900,7 @@ function scopeOccurredAt(manifests) {
26343
27900
  return manifests.map((manifest) => manifest.details?.occurred_at).filter((value) => typeof value === "string" && value.trim().length > 0).sort((left, right) => right.localeCompare(left))[0];
26344
27901
  }
26345
27902
  function scopeParticipants(manifests) {
26346
- return uniqueStrings4(manifests.flatMap((manifest) => splitDelimitedDetail(manifest.details?.participants)));
27903
+ return uniqueStrings5(manifests.flatMap((manifest) => splitDelimitedDetail(manifest.details?.participants)));
26347
27904
  }
26348
27905
  function scopeContainerTitle(manifests) {
26349
27906
  return manifests.find((manifest) => manifest.details?.container_title)?.details?.container_title ?? manifests[0]?.sourceGroupTitle;
@@ -26363,9 +27920,9 @@ function classifyGuidedEvidenceState(scope, targetPage, contradictions) {
26363
27920
  function renderDeterministicSourceReview(input) {
26364
27921
  const canonicalPages = input.sourcePages.filter((page) => page.kind === "source" || page.kind === "concept" || page.kind === "entity").slice(0, 10);
26365
27922
  const modulePages = input.sourcePages.filter((page) => page.kind === "module").slice(0, 8);
26366
- const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 8);
26367
- const concepts = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.concepts.map((concept) => concept.name))).slice(0, 8);
26368
- const entities = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.entities.map((entity) => entity.name))).slice(0, 8);
27923
+ const questions = uniqueStrings5(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 8);
27924
+ const concepts = uniqueStrings5(input.analyses.flatMap((analysis) => analysis.concepts.map((concept) => concept.name))).slice(0, 8);
27925
+ const entities = uniqueStrings5(input.analyses.flatMap((analysis) => analysis.entities.map((entity) => entity.name))).slice(0, 8);
26369
27926
  const contradictions = input.report?.contradictions.filter(
26370
27927
  (contradiction) => input.scope.sourceIds.includes(contradiction.sourceIdA) || input.scope.sourceIds.includes(contradiction.sourceIdB)
26371
27928
  ) ?? [];
@@ -26471,7 +28028,7 @@ async function buildSourceReviewStagedPage(rootDir, scope) {
26471
28028
  const relatedPages = graph ? scopedSourcePages(graph, scope.sourceIds) : [];
26472
28029
  const relatedPageIds = relatedPages.slice(0, 16).map((page) => page.id);
26473
28030
  const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 24) : [];
26474
- const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
28031
+ const projectIds = uniqueStrings5(relatedPages.flatMap((page) => page.projectIds));
26475
28032
  const now = (/* @__PURE__ */ new Date()).toISOString();
26476
28033
  const output = buildOutputPage({
26477
28034
  title: `Source Review: ${scope.title}`,
@@ -26561,7 +28118,7 @@ function insightTitleForTarget(page, scope) {
26561
28118
  return `${scope.title} Notes`;
26562
28119
  }
26563
28120
  function insightTagsForTarget(page) {
26564
- return uniqueStrings4(["insight", "guided-session", `guided/${page?.kind ?? "topic"}`]);
28121
+ return uniqueStrings5(["insight", "guided-session", `guided/${page?.kind ?? "topic"}`]);
26565
28122
  }
26566
28123
  function guidedUpdateMarker(scopeId) {
26567
28124
  return {
@@ -26590,14 +28147,14 @@ ${block}
26590
28147
  function renderDeterministicSourceGuide(input) {
26591
28148
  const { canonicalPages, newPages, reinforcingPages } = classifySourceGuidePageBuckets(input.sourcePages, input.scope.sourceIds);
26592
28149
  const modulePages = input.sourcePages.filter((page) => page.kind === "module").slice(0, 6);
26593
- const takeaways = uniqueStrings4(
28150
+ const takeaways = uniqueStrings5(
26594
28151
  input.analyses.flatMap((analysis) => [
26595
28152
  analysis.summary,
26596
28153
  ...analysis.concepts.map((concept) => concept.description),
26597
28154
  ...analysis.entities.map((entity) => entity.description)
26598
28155
  ]).filter(Boolean).map((value) => normalizeWhitespace(value))
26599
28156
  ).slice(0, 7).map((value) => truncate(value, 180));
26600
- const questions = uniqueStrings4(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
28157
+ const questions = uniqueStrings5(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
26601
28158
  const contradictions = input.report?.contradictions.filter(
26602
28159
  (contradiction) => input.scope.sourceIds.includes(contradiction.sourceIdA) || input.scope.sourceIds.includes(contradiction.sourceIdB)
26603
28160
  ) ?? [];
@@ -26719,7 +28276,7 @@ async function buildSourceGuideStagedPage(rootDir, scope) {
26719
28276
  const selectedTargets = selectGuidedTargetPages(scope, relatedPages, defaultGuidedSessionQuestions());
26720
28277
  const relatedPageIds = relatedPages.slice(0, 18).map((page) => page.id);
26721
28278
  const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
26722
- const projectIds = uniqueStrings4(relatedPages.flatMap((page) => page.projectIds));
28279
+ const projectIds = uniqueStrings5(relatedPages.flatMap((page) => page.projectIds));
26723
28280
  const now = (/* @__PURE__ */ new Date()).toISOString();
26724
28281
  const output = buildOutputPage({
26725
28282
  title: `Source Guide: ${scope.title}`,
@@ -26821,7 +28378,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
26821
28378
  const analyses = await loadSourceAnalyses(rootDir, scope.sourceIds);
26822
28379
  const report = await readGraphReport(rootDir);
26823
28380
  const contradictions = findContradictionsForScope(scope, report);
26824
- const relatedPageIds = uniqueStrings4([
28381
+ const relatedPageIds = uniqueStrings5([
26825
28382
  ...sourcePages.slice(0, 18).map((page) => page.id),
26826
28383
  ...session.targetedPagePaths.map((relativePath) => {
26827
28384
  const page = graph?.pages.find((candidate) => candidate.path === relativePath);
@@ -26829,7 +28386,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
26829
28386
  })
26830
28387
  ]);
26831
28388
  const relatedNodeIds = graph ? scopedNodeIds(graph, scope.sourceIds).slice(0, 28) : [];
26832
- const projectIds = uniqueStrings4(sourcePages.flatMap((page) => page.projectIds));
28389
+ const projectIds = uniqueStrings5(sourcePages.flatMap((page) => page.projectIds));
26833
28390
  const evidenceState = contradictions.length > 0 ? "conflicting" : session.targetedPagePaths.some(
26834
28391
  (targetPath) => sourcePages.some((page) => page.path === targetPath && page.sourceIds.some((sourceId) => !scope.sourceIds.includes(sourceId)))
26835
28392
  ) ? "reinforcing" : session.targetedPagePaths.length ? "new" : "needs_judgment";
@@ -26864,7 +28421,7 @@ async function buildSourceSessionSavedPage(rootDir, scope, session) {
26864
28421
  if (followups) {
26865
28422
  return followups.split(/\n+/).map((line) => line.trim()).filter(Boolean).map((line) => `- ${line.replace(/^-+\s*/, "")}`);
26866
28423
  }
26867
- const analysisQuestions = uniqueStrings4(analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
28424
+ const analysisQuestions = uniqueStrings5(analyses.flatMap((analysis) => analysis.questions)).slice(0, 6);
26868
28425
  return analysisQuestions.length ? analysisQuestions.map((question) => `- ${question}`) : ["- No follow-up questions recorded yet."];
26869
28426
  })(),
26870
28427
  "",
@@ -26918,7 +28475,7 @@ async function persistSourceSessionPage(rootDir, scope, session) {
26918
28475
  const output = await buildSourceSessionSavedPage(rootDir, scope, session);
26919
28476
  const absolutePath = path29.join(paths.wikiDir, output.page.path);
26920
28477
  await ensureDir(path29.dirname(absolutePath));
26921
- await fs24.writeFile(absolutePath, output.content, "utf8");
28478
+ await fs25.writeFile(absolutePath, output.content, "utf8");
26922
28479
  return { pageId: output.page.id, sessionPath: absolutePath };
26923
28480
  }
26924
28481
  async function buildGuidedUpdatePages(rootDir, scope, session) {
@@ -26939,7 +28496,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
26939
28496
  const selectedTargets = selectGuidedTargetPages(scope, sourcePages, session.questions);
26940
28497
  const useCanonicalTargets = config.profile.guidedSessionMode === "canonical_review" && selectedTargets.length > 0;
26941
28498
  const targetPages = useCanonicalTargets ? selectedTargets : [selectedTargets[0] ?? null];
26942
- session.targetedPagePaths = uniqueStrings4(
28499
+ session.targetedPagePaths = uniqueStrings5(
26943
28500
  useCanonicalTargets ? selectedTargets.map((page) => page.path) : selectedTargets.length ? selectedTargets.map((page) => page.path) : session.targetedPagePaths
26944
28501
  );
26945
28502
  return await Promise.all(
@@ -26947,7 +28504,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
26947
28504
  const evidenceState = classifyGuidedEvidenceState(scope, targetPage, contradictions);
26948
28505
  const relativePath = useCanonicalTargets && targetPage ? targetPage.path : targetPage ? insightRelativePathForTarget(targetPage, scope) : `insights/topics/${slugify(scope.title)}.md`;
26949
28506
  const absolutePath = path29.join(paths.wikiDir, relativePath);
26950
- const existingContent = await fileExists(absolutePath) ? await fs24.readFile(absolutePath, "utf8") : "";
28507
+ const existingContent = await fileExists(absolutePath) ? await fs25.readFile(absolutePath, "utf8") : "";
26951
28508
  const parsed = existingContent ? matter13(existingContent) : { data: {}, content: "" };
26952
28509
  const existingData = parsed.data;
26953
28510
  const existingSourceIds = Array.isArray(existingData.source_ids) ? existingData.source_ids.filter((value) => typeof value === "string") : [];
@@ -27018,24 +28575,24 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
27018
28575
  page_id: typeof existingData.page_id === "string" && existingData.page_id.trim() || (useCanonicalTargets && targetPage ? targetPage.id : `insight:${slugify(relativePath.replace(/\.md$/, ""))}`),
27019
28576
  kind: useCanonicalTargets && targetPage ? targetPage.kind : "insight",
27020
28577
  title,
27021
- tags: uniqueStrings4([
28578
+ tags: uniqueStrings5([
27022
28579
  ...Array.isArray(existingData.tags) ? existingData.tags.filter((value) => typeof value === "string") : [],
27023
28580
  ...useCanonicalTargets ? ["guided-session", `guided/${targetPage?.kind ?? "page"}`] : insightTagsForTarget(targetPage)
27024
28581
  ]),
27025
- source_ids: uniqueStrings4([...existingSourceIds, ...scope.sourceIds]),
27026
- project_ids: uniqueStrings4([...existingProjectIds, ...targetPage?.projectIds ?? []]),
27027
- node_ids: uniqueStrings4([...existingNodeIds, ...targetPage?.nodeIds ?? []]),
28582
+ source_ids: uniqueStrings5([...existingSourceIds, ...scope.sourceIds]),
28583
+ project_ids: uniqueStrings5([...existingProjectIds, ...targetPage?.projectIds ?? []]),
28584
+ node_ids: uniqueStrings5([...existingNodeIds, ...targetPage?.nodeIds ?? []]),
27028
28585
  freshness: "fresh",
27029
28586
  status: existingData.status === "archived" ? "archived" : "active",
27030
28587
  confidence: 0.83,
27031
28588
  created_at: createdAt,
27032
28589
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
27033
- compiled_from: uniqueStrings4([
28590
+ compiled_from: uniqueStrings5([
27034
28591
  ...Array.isArray(existingData.compiled_from) ? existingData.compiled_from.filter((value) => typeof value === "string") : [],
27035
28592
  ...scope.sourceIds
27036
28593
  ]),
27037
28594
  managed_by: typeof existingData.managed_by === "string" && (existingData.managed_by === "human" || existingData.managed_by === "system") ? existingData.managed_by : useCanonicalTargets ? "system" : "human",
27038
- backlinks: uniqueStrings4([
28595
+ backlinks: uniqueStrings5([
27039
28596
  ...existingBacklinks,
27040
28597
  ...targetPage ? [targetPage.id] : [],
27041
28598
  `output:source-sessions/${scope.id}`,
@@ -27062,7 +28619,7 @@ async function buildGuidedUpdatePages(rootDir, scope, session) {
27062
28619
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
27063
28620
  });
27064
28621
  if (!useCanonicalTargets && !selectedTargets.length) {
27065
- session.targetedPagePaths = uniqueStrings4([...session.targetedPagePaths, relativePath]);
28622
+ session.targetedPagePaths = uniqueStrings5([...session.targetedPagePaths, relativePath]);
27066
28623
  }
27067
28624
  return { page, content, label: "guided-update" };
27068
28625
  })
@@ -27385,7 +28942,7 @@ async function deleteManagedSource(rootDir, id) {
27385
28942
  sources.filter((source) => source.id !== id)
27386
28943
  );
27387
28944
  const workingDir = await managedSourceWorkingDir(rootDir, id);
27388
- await fs24.rm(workingDir, { recursive: true, force: true });
28945
+ await fs25.rm(workingDir, { recursive: true, force: true });
27389
28946
  return { removed: target };
27390
28947
  }
27391
28948
 
@@ -27393,7 +28950,7 @@ async function deleteManagedSource(rootDir, id) {
27393
28950
  import { execFile as execFile2 } from "child_process";
27394
28951
  import { randomUUID } from "crypto";
27395
28952
  import { EventEmitter } from "events";
27396
- import fs25 from "fs/promises";
28953
+ import fs26 from "fs/promises";
27397
28954
  import http from "http";
27398
28955
  import path30 from "path";
27399
28956
  import { promisify as promisify2 } from "util";
@@ -27549,7 +29106,7 @@ function toViewerLintFindings(findings) {
27549
29106
  var execFileAsync2 = promisify2(execFile2);
27550
29107
  async function isReadableFile(absolutePath) {
27551
29108
  try {
27552
- const stats = await fs25.stat(absolutePath);
29109
+ const stats = await fs26.stat(absolutePath);
27553
29110
  return stats.isFile();
27554
29111
  } catch {
27555
29112
  return false;
@@ -27564,7 +29121,7 @@ async function readViewerPage(rootDir, relativePath) {
27564
29121
  if (!isPathWithin(paths.wikiDir, absolutePath) || !await isReadableFile(absolutePath)) {
27565
29122
  return null;
27566
29123
  }
27567
- const raw = await fs25.readFile(absolutePath, "utf8");
29124
+ const raw = await fs26.readFile(absolutePath, "utf8");
27568
29125
  const parsed = matter14(raw);
27569
29126
  return {
27570
29127
  path: relativePath,
@@ -27584,7 +29141,7 @@ async function readViewerAsset(rootDir, relativePath) {
27584
29141
  return null;
27585
29142
  }
27586
29143
  return {
27587
- buffer: await fs25.readFile(absolutePath),
29144
+ buffer: await fs26.readFile(absolutePath),
27588
29145
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
27589
29146
  };
27590
29147
  }
@@ -27705,7 +29262,7 @@ async function startGraphServer(rootDir, port, options = {}) {
27705
29262
  response.end(JSON.stringify({ error: "Graph report artifact not found. Run `swarmvault compile` first." }));
27706
29263
  return;
27707
29264
  }
27708
- const body = await fs25.readFile(reportPath, "utf8");
29265
+ const body = await fs26.readFile(reportPath, "utf8");
27709
29266
  response.writeHead(200, { "content-type": "application/json" });
27710
29267
  response.end(body);
27711
29268
  return;
@@ -27910,7 +29467,7 @@ async function startGraphServer(rootDir, port, options = {}) {
27910
29467
  response.end("Viewer build not found. Run `pnpm build` first.");
27911
29468
  return;
27912
29469
  }
27913
- const staticBody = await fs25.readFile(filePath);
29470
+ const staticBody = await fs26.readFile(filePath);
27914
29471
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
27915
29472
  response.end(staticBody);
27916
29473
  } catch (error) {
@@ -27976,7 +29533,7 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
27976
29533
  } : null;
27977
29534
  })
27978
29535
  );
27979
- const rawHtml = await fs25.readFile(indexPath, "utf8");
29536
+ const rawHtml = await fs26.readFile(indexPath, "utf8");
27980
29537
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
27981
29538
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
27982
29539
  const scriptPath = scriptMatch?.[1] ? path30.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
@@ -27984,8 +29541,8 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
27984
29541
  if (!scriptPath || !await fileExists(scriptPath)) {
27985
29542
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
27986
29543
  }
27987
- const script = await fs25.readFile(scriptPath, "utf8");
27988
- const style = stylePath && await fileExists(stylePath) ? await fs25.readFile(stylePath, "utf8") : "";
29544
+ const script = await fs26.readFile(scriptPath, "utf8");
29545
+ const style = stylePath && await fileExists(stylePath) ? await fs26.readFile(stylePath, "utf8") : "";
27989
29546
  const report = await readJsonFile(path30.join(paths.wikiDir, "graph", "report.json"));
27990
29547
  const embeddedData = JSON.stringify(
27991
29548
  { graph: buildViewerGraphArtifact(graph, { report, full: options.full ?? false }), pages: pages.filter(Boolean), report },
@@ -28009,8 +29566,8 @@ async function exportGraphHtml(rootDir, outputPath, options = {}) {
28009
29566
  "</html>",
28010
29567
  ""
28011
29568
  ].filter(Boolean).join("\n");
28012
- await fs25.mkdir(path30.dirname(outputPath), { recursive: true });
28013
- await fs25.writeFile(outputPath, html, "utf8");
29569
+ await fs26.mkdir(path30.dirname(outputPath), { recursive: true });
29570
+ await fs26.writeFile(outputPath, html, "utf8");
28014
29571
  return path30.resolve(outputPath);
28015
29572
  }
28016
29573
  export {
@@ -28020,9 +29577,11 @@ export {
28020
29577
  DEFAULT_PROMOTION_CONFIG,
28021
29578
  DEFAULT_REDACTION_PATTERNS,
28022
29579
  DEFAULT_STALE_THRESHOLD,
29580
+ LARGE_REPO_NODE_THRESHOLD,
28023
29581
  acceptApproval,
28024
29582
  addInput,
28025
29583
  addManagedSource,
29584
+ addWatchedRoot,
28026
29585
  applyDecayToPages,
28027
29586
  archiveCandidate,
28028
29587
  assertProviderCapability,
@@ -28080,6 +29639,7 @@ export {
28080
29639
  listPages,
28081
29640
  listSchedules,
28082
29641
  listTrackedRepoRoots,
29642
+ listWatchedRoots,
28083
29643
  loadVaultConfig,
28084
29644
  loadVaultSchema,
28085
29645
  loadVaultSchemas,
@@ -28097,11 +29657,14 @@ export {
28097
29657
  readPage,
28098
29658
  rejectApproval,
28099
29659
  reloadManagedSources,
29660
+ removeWatchedRoot,
28100
29661
  resetDecay,
28101
29662
  resolveConsolidationConfig,
28102
29663
  resolveDecayConfig,
29664
+ resolveLargeRepoDefaults,
28103
29665
  resolvePaths,
28104
29666
  resolveRedactionPatterns,
29667
+ resolveWatchedRepoRoots,
28105
29668
  resumeSourceSession,
28106
29669
  reviewManagedSource,
28107
29670
  reviewSourceScope,
@@ -28117,6 +29680,7 @@ export {
28117
29680
  startMcpServer,
28118
29681
  syncTrackedRepos,
28119
29682
  syncTrackedReposForWatch,
29683
+ synthesizeHyperedgeHubs,
28120
29684
  trimToTokenBudget,
28121
29685
  uninstallGitHooks,
28122
29686
  watchVault