@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/chunk-4MSSM2GH.js +1476 -0
- package/dist/index.d.ts +217 -2
- package/dist/index.js +2046 -482
- package/dist/registry-QAG2ZYH3.js +12 -0
- package/dist/viewer/assets/{index-QQ74kUX8.js → index-TXBR63qb.js} +47 -47
- package/dist/viewer/index.html +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
uniqueBy,
|
|
24
24
|
writeFileIfChanged,
|
|
25
25
|
writeJsonFile
|
|
26
|
-
} from "./chunk-
|
|
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
|
|
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
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
const
|
|
6642
|
-
const
|
|
6643
|
-
|
|
6644
|
-
|
|
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
|
-
|
|
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
|
|
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}|${[...
|
|
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: [...
|
|
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-
|
|
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
|
|
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
|
|
6895
|
-
const normalizedQuery =
|
|
6896
|
-
const normalizedCandidate =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
7375
|
+
return NODE_TYPE_PRIORITY2[type] ?? 0;
|
|
6978
7376
|
}
|
|
6979
|
-
function
|
|
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 =
|
|
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) =>
|
|
7394
|
+
const labelMatches = graph.nodes.filter((node) => normalizeTarget2(node.label) === normalized || normalizeTarget2(node.id) === normalized);
|
|
6997
7395
|
if (labelMatches.length) {
|
|
6998
|
-
return labelMatches.sort(
|
|
7396
|
+
return labelMatches.sort(compareLabelCandidates2)[0];
|
|
6999
7397
|
}
|
|
7000
7398
|
const pages = graph.pages.map((page) => ({
|
|
7001
7399
|
page,
|
|
7002
|
-
score: Math.max(
|
|
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(
|
|
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
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
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
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
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:
|
|
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) =>
|
|
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 =
|
|
7287
|
-
const candidate = graph.nodes.filter((n) => n.type === "module").find((n) =>
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
8088
|
+
related_page_ids: uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
|
|
7681
8089
|
related_node_ids: [],
|
|
7682
|
-
related_source_ids:
|
|
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:
|
|
8165
|
+
relatedPageIds: uniqueStrings2([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]),
|
|
7758
8166
|
relatedNodeIds: [],
|
|
7759
|
-
relatedSourceIds:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
|
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 :
|
|
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 =
|
|
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) =>
|
|
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(
|
|
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 :
|
|
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 =
|
|
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 =
|
|
9409
|
-
await ensureDir(
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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 =
|
|
10230
|
-
_visNetworkJs = readFileSync(
|
|
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
|
-
|
|
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
|
-
|
|
10844
|
-
var
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
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
|
-
|
|
10858
|
-
var
|
|
10859
|
-
if (!
|
|
10860
|
-
|
|
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
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
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(
|
|
12091
|
+
await ensureDir(path10.dirname(outputPath));
|
|
10888
12092
|
await fs9.writeFile(outputPath, content, "utf8");
|
|
10889
|
-
return
|
|
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(
|
|
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
|
|
12136
|
+
if (page) return path10.basename(page.path, ".md");
|
|
10933
12137
|
}
|
|
10934
12138
|
return slugify(node.label);
|
|
10935
12139
|
}
|
|
10936
|
-
function
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11056
|
-
const destFile =
|
|
11057
|
-
await ensureDir(
|
|
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 =
|
|
11116
|
-
await ensureDir(
|
|
11117
|
-
const slug =
|
|
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 =
|
|
11173
|
-
await ensureDir(
|
|
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 =
|
|
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 =
|
|
11210
|
-
const dest =
|
|
11211
|
-
await ensureDir(
|
|
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 =
|
|
11221
|
-
const dest =
|
|
11222
|
-
await ensureDir(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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: ${
|
|
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
|
|
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(() =>
|
|
11610
|
-
const label = slugify(
|
|
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:
|
|
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
|
|
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 =
|
|
13070
|
+
let current = path12.resolve(startPath);
|
|
11818
13071
|
try {
|
|
11819
13072
|
const stat = await fs11.stat(current);
|
|
11820
13073
|
if (!stat.isDirectory()) {
|
|
11821
|
-
current =
|
|
13074
|
+
current = path12.dirname(current);
|
|
11822
13075
|
}
|
|
11823
13076
|
} catch {
|
|
11824
|
-
current =
|
|
13077
|
+
current = path12.dirname(current);
|
|
11825
13078
|
}
|
|
11826
13079
|
while (true) {
|
|
11827
|
-
if (await fileExists(
|
|
13080
|
+
if (await fileExists(path12.join(current, ".git"))) {
|
|
11828
13081
|
return current;
|
|
11829
13082
|
}
|
|
11830
|
-
const parent =
|
|
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(`${
|
|
11843
|
-
return
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 ?
|
|
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 ?
|
|
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/${
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 ?
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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-
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
19806
|
-
const openQuestions =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 ||
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
25419
|
+
async function listGodNodes(rootDir, limit) {
|
|
23951
25420
|
const graph = await ensureCompiledGraph(rootDir);
|
|
23952
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
27657
|
+
const questions = uniqueStrings5(input.analyses.flatMap((analysis) => analysis.questions)).slice(0, 5);
|
|
26101
27658
|
const summary = truncate(
|
|
26102
27659
|
normalizeWhitespace(
|
|
26103
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
26367
|
-
const concepts =
|
|
26368
|
-
const entities =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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:
|
|
27026
|
-
project_ids:
|
|
27027
|
-
node_ids:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
27988
|
-
const style = stylePath && await fileExists(stylePath) ? await
|
|
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
|
|
28013
|
-
await
|
|
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
|