@anokye-labs/kbexplorer-engine 0.1.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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/build-manifest-BT0sY84J.d.ts +239 -0
- package/dist/build-manifest-BkgZUL0E.d.cts +239 -0
- package/dist/chunk-ASLGNOYV.js +220 -0
- package/dist/chunk-BA526FDQ.js +16 -0
- package/dist/chunk-DJ2DBEEK.js +424 -0
- package/dist/chunk-FJ4GTN4U.js +973 -0
- package/dist/chunk-HXAS3BSD.js +187 -0
- package/dist/chunk-IWEURXYH.js +273 -0
- package/dist/chunk-JNQVSNLC.js +55 -0
- package/dist/chunk-MW4BLXVK.js +72 -0
- package/dist/chunk-NOGBKE7C.js +351 -0
- package/dist/chunk-XVI6CBSX.js +412 -0
- package/dist/chunk-YTMIERYM.js +67 -0
- package/dist/github-api-source-KV5HZARH.js +4 -0
- package/dist/index.cjs +6218 -0
- package/dist/index.d.cts +1396 -0
- package/dist/index.d.ts +1396 -0
- package/dist/index.js +2892 -0
- package/dist/node-wasm-VSXBOCPD.js +9 -0
- package/dist/plugin-loader-K4VU6ODV.js +2 -0
- package/dist/repo-data-0JvFdLGv.d.cts +461 -0
- package/dist/repo-data-0JvFdLGv.d.ts +461 -0
- package/dist/sources.cjs +1191 -0
- package/dist/sources.d.cts +194 -0
- package/dist/sources.d.ts +194 -0
- package/dist/sources.js +357 -0
- package/dist/sqlite-graph-store-NH2NHL2B.js +2 -0
- package/dist/sqlite-runtime-CysPjjzX.d.cts +96 -0
- package/dist/sqlite-runtime-CysPjjzX.d.ts +96 -0
- package/dist/store-orchestrator-337XWIXO.js +5 -0
- package/dist/store.cjs +1112 -0
- package/dist/store.d.cts +37 -0
- package/dist/store.d.ts +37 -0
- package/dist/store.js +7 -0
- package/package.json +70 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { splitIntoSections, renderSafeMarkdown } from './chunk-FJ4GTN4U.js';
|
|
2
|
+
import yaml from 'yaml';
|
|
3
|
+
|
|
4
|
+
function basename(filePath) {
|
|
5
|
+
const name = filePath.split("/").pop() ?? filePath;
|
|
6
|
+
const dot = name.lastIndexOf(".");
|
|
7
|
+
return dot > 0 ? name.substring(0, dot) : name;
|
|
8
|
+
}
|
|
9
|
+
function extname(filePath) {
|
|
10
|
+
const name = filePath.split("/").pop() ?? filePath;
|
|
11
|
+
const dot = name.lastIndexOf(".");
|
|
12
|
+
return dot > 0 ? name.substring(dot) : "";
|
|
13
|
+
}
|
|
14
|
+
function isMarkdown(path) {
|
|
15
|
+
const ext = extname(path).toLowerCase();
|
|
16
|
+
return ext === ".md" || ext === ".mdx";
|
|
17
|
+
}
|
|
18
|
+
function escapeHtml(s) {
|
|
19
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
20
|
+
}
|
|
21
|
+
function renderHtml(content, filePath) {
|
|
22
|
+
if (isMarkdown(filePath)) {
|
|
23
|
+
return renderSafeMarkdown(content);
|
|
24
|
+
}
|
|
25
|
+
return `<pre><code>${escapeHtml(content)}</code></pre>`;
|
|
26
|
+
}
|
|
27
|
+
function formatBytes(bytes) {
|
|
28
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
29
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
30
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
31
|
+
}
|
|
32
|
+
function getNodePath(node) {
|
|
33
|
+
if (node.source.type === "file") return node.source.path;
|
|
34
|
+
if (node.source.type === "authored") return node.source.file;
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
function globToRegex(pattern) {
|
|
38
|
+
let re = "";
|
|
39
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
40
|
+
const c = pattern[i];
|
|
41
|
+
if (c === "*" && pattern[i + 1] === "*") {
|
|
42
|
+
re += ".*";
|
|
43
|
+
i += 1;
|
|
44
|
+
if (pattern[i + 1] === "/") i += 1;
|
|
45
|
+
} else if (c === "*") {
|
|
46
|
+
re += "[^/]*";
|
|
47
|
+
} else if (c === "?") {
|
|
48
|
+
re += "[^/]";
|
|
49
|
+
} else if (".+^${}()|[]\\".includes(c)) {
|
|
50
|
+
re += "\\" + c;
|
|
51
|
+
} else {
|
|
52
|
+
re += c;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return new RegExp(`^${re}$`);
|
|
56
|
+
}
|
|
57
|
+
function matchesAnyGlob(path, patterns) {
|
|
58
|
+
return patterns.some((p) => globToRegex(p).test(path));
|
|
59
|
+
}
|
|
60
|
+
function buildFileNode(id, title, filePath, content, entry) {
|
|
61
|
+
const isMd = isMarkdown(filePath);
|
|
62
|
+
const display = entry.display ?? (isMd ? void 0 : "code");
|
|
63
|
+
const source = isMd ? { type: "authored", file: filePath } : { type: "file", path: filePath };
|
|
64
|
+
return {
|
|
65
|
+
id,
|
|
66
|
+
title,
|
|
67
|
+
cluster: entry.cluster ?? "default",
|
|
68
|
+
content: renderHtml(content, filePath),
|
|
69
|
+
rawContent: content,
|
|
70
|
+
...entry.emoji !== void 0 ? { emoji: entry.emoji } : {},
|
|
71
|
+
...display !== void 0 ? { display } : {},
|
|
72
|
+
derived: true,
|
|
73
|
+
connections: [],
|
|
74
|
+
identity: `urn:file:${filePath}`,
|
|
75
|
+
source
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function processSingleFile(entry, readFile) {
|
|
79
|
+
const filePath = entry.file;
|
|
80
|
+
const content = await readFile(filePath);
|
|
81
|
+
if (content === null) return [];
|
|
82
|
+
const id = entry.id;
|
|
83
|
+
const title = entry.title ?? basename(filePath);
|
|
84
|
+
if (entry.split === "headings") {
|
|
85
|
+
const isMd = isMarkdown(filePath);
|
|
86
|
+
const source = isMd ? { type: "authored", file: filePath } : { type: "file", path: filePath };
|
|
87
|
+
const sections = splitIntoSections(
|
|
88
|
+
id,
|
|
89
|
+
title,
|
|
90
|
+
content,
|
|
91
|
+
entry.cluster ?? "default",
|
|
92
|
+
entry.emoji ?? "Document",
|
|
93
|
+
source,
|
|
94
|
+
[]
|
|
95
|
+
// cross-ref nodes added in the connection-derivation pass
|
|
96
|
+
);
|
|
97
|
+
if (sections.length > 0) {
|
|
98
|
+
if (entry.display) {
|
|
99
|
+
const parent = sections.find((n) => n.nodeType === "parent");
|
|
100
|
+
if (parent) parent.display = entry.display;
|
|
101
|
+
}
|
|
102
|
+
return sections;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return [buildFileNode(id, title, filePath, content, entry)];
|
|
106
|
+
}
|
|
107
|
+
async function processMerge(entry, readFile) {
|
|
108
|
+
const files = entry.files;
|
|
109
|
+
const parts = [];
|
|
110
|
+
for (const filePath of files) {
|
|
111
|
+
const content = await readFile(filePath);
|
|
112
|
+
if (content === null) continue;
|
|
113
|
+
const name = filePath.split("/").pop() ?? filePath;
|
|
114
|
+
parts.push(`## ${name}
|
|
115
|
+
|
|
116
|
+
${content}`);
|
|
117
|
+
}
|
|
118
|
+
if (parts.length === 0) return [];
|
|
119
|
+
const merged = parts.join("\n\n---\n\n");
|
|
120
|
+
const html = renderSafeMarkdown(merged);
|
|
121
|
+
const display = entry.display ?? "file-list";
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
id: entry.id,
|
|
125
|
+
title: entry.title ?? entry.id,
|
|
126
|
+
cluster: entry.cluster ?? "default",
|
|
127
|
+
content: html,
|
|
128
|
+
rawContent: merged,
|
|
129
|
+
...entry.emoji !== void 0 ? { emoji: entry.emoji } : {},
|
|
130
|
+
display,
|
|
131
|
+
derived: true,
|
|
132
|
+
connections: [],
|
|
133
|
+
source: { type: "file", path: files[0] }
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
async function processGlob(entry, readFile, listFiles) {
|
|
138
|
+
if (!listFiles) return [];
|
|
139
|
+
let paths = await listFiles(entry.glob);
|
|
140
|
+
if (entry.exclude?.length) {
|
|
141
|
+
paths = paths.filter((p) => !matchesAnyGlob(p, entry.exclude));
|
|
142
|
+
}
|
|
143
|
+
const nodes = [];
|
|
144
|
+
for (const filePath of paths) {
|
|
145
|
+
const content = await readFile(filePath);
|
|
146
|
+
if (content === null) continue;
|
|
147
|
+
const fileBase = basename(filePath);
|
|
148
|
+
const nodeId = `${entry.id}-${fileBase}`;
|
|
149
|
+
let title;
|
|
150
|
+
if (entry.titleFrom === "heading") {
|
|
151
|
+
const m = content.match(/^#\s+(.+)/m);
|
|
152
|
+
title = m ? m[1].trim() : fileBase;
|
|
153
|
+
} else {
|
|
154
|
+
title = fileBase;
|
|
155
|
+
}
|
|
156
|
+
nodes.push(buildFileNode(nodeId, title, filePath, content, entry));
|
|
157
|
+
}
|
|
158
|
+
return nodes;
|
|
159
|
+
}
|
|
160
|
+
async function processDirectory(entry, listDirectory) {
|
|
161
|
+
if (!listDirectory) return [];
|
|
162
|
+
const dir = entry.directory;
|
|
163
|
+
const items = await listDirectory(dir);
|
|
164
|
+
const lines = items.map((item) => {
|
|
165
|
+
const icon = item.type === "tree" ? "\u{1F4C1}" : "\u{1F4C4}";
|
|
166
|
+
const name = item.path.split("/").pop() ?? item.path;
|
|
167
|
+
const size = item.size != null ? ` (${formatBytes(item.size)})` : "";
|
|
168
|
+
return `${icon} ${name}${size}`;
|
|
169
|
+
});
|
|
170
|
+
const title = entry.title ?? dir;
|
|
171
|
+
const rawContent = `## ${title}
|
|
172
|
+
|
|
173
|
+
${lines.join("\n")}`;
|
|
174
|
+
const html = renderSafeMarkdown(rawContent);
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
id: entry.id,
|
|
178
|
+
title,
|
|
179
|
+
cluster: entry.cluster ?? "default",
|
|
180
|
+
content: html,
|
|
181
|
+
rawContent,
|
|
182
|
+
emoji: entry.emoji ?? "Folder",
|
|
183
|
+
display: entry.display ?? "tree",
|
|
184
|
+
derived: true,
|
|
185
|
+
connections: [],
|
|
186
|
+
source: { type: "file", path: dir }
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
function resolveImportPath(importPath, fromFile) {
|
|
191
|
+
const lastSlash = fromFile.lastIndexOf("/");
|
|
192
|
+
const fromDir = lastSlash >= 0 ? fromFile.substring(0, lastSlash) : "";
|
|
193
|
+
const combined = fromDir ? `${fromDir}/${importPath}` : importPath;
|
|
194
|
+
const parts = combined.split("/");
|
|
195
|
+
const resolved = [];
|
|
196
|
+
for (const part of parts) {
|
|
197
|
+
if (part === "." || part === "") continue;
|
|
198
|
+
if (part === "..") {
|
|
199
|
+
if (resolved.length) resolved.pop();
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
resolved.push(part);
|
|
203
|
+
}
|
|
204
|
+
return resolved.join("/");
|
|
205
|
+
}
|
|
206
|
+
function extractImportPaths(content, fromFile) {
|
|
207
|
+
const paths = [];
|
|
208
|
+
for (const m of content.matchAll(/\bfrom\s+['"]([^'"]+)['"]/g)) {
|
|
209
|
+
const spec = m[1];
|
|
210
|
+
if (spec.startsWith("."))
|
|
211
|
+
paths.push(resolveImportPath(spec, fromFile));
|
|
212
|
+
}
|
|
213
|
+
for (const m of content.matchAll(/^\s*import\s+['"]([^'"]+)['"]/gm)) {
|
|
214
|
+
const spec = m[1];
|
|
215
|
+
if (spec.startsWith("."))
|
|
216
|
+
paths.push(resolveImportPath(spec, fromFile));
|
|
217
|
+
}
|
|
218
|
+
for (const m of content.matchAll(
|
|
219
|
+
/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
220
|
+
)) {
|
|
221
|
+
const spec = m[1];
|
|
222
|
+
if (spec.startsWith("."))
|
|
223
|
+
paths.push(resolveImportPath(spec, fromFile));
|
|
224
|
+
}
|
|
225
|
+
return [...new Set(paths)];
|
|
226
|
+
}
|
|
227
|
+
function findNodeIdByPath(resolved, pathToId) {
|
|
228
|
+
if (pathToId.has(resolved)) return pathToId.get(resolved);
|
|
229
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs"]) {
|
|
230
|
+
if (pathToId.has(resolved + ext)) return pathToId.get(resolved + ext);
|
|
231
|
+
}
|
|
232
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx"]) {
|
|
233
|
+
if (pathToId.has(`${resolved}/index${ext}`))
|
|
234
|
+
return pathToId.get(`${resolved}/index${ext}`);
|
|
235
|
+
}
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
function deriveConnections(allNodes, entries, entryNodeIds) {
|
|
239
|
+
const pathToId = /* @__PURE__ */ new Map();
|
|
240
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const node of allNodes) {
|
|
242
|
+
nodeById.set(node.id, node);
|
|
243
|
+
const p = getNodePath(node);
|
|
244
|
+
if (p) {
|
|
245
|
+
const existing = pathToId.get(p);
|
|
246
|
+
if (!existing || node.source.type === "authored") {
|
|
247
|
+
pathToId.set(p, node.id);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (!entry.connections) continue;
|
|
253
|
+
const nodeIds = entryNodeIds.get(entry.id) ?? [];
|
|
254
|
+
if (Array.isArray(entry.connections)) {
|
|
255
|
+
for (const nodeId of nodeIds) {
|
|
256
|
+
const node = nodeById.get(nodeId);
|
|
257
|
+
if (node) node.connections.push(...entry.connections);
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (entry.connections === "imports") {
|
|
262
|
+
for (const nodeId of nodeIds) {
|
|
263
|
+
const node = nodeById.get(nodeId);
|
|
264
|
+
if (!node) continue;
|
|
265
|
+
const fromPath = getNodePath(node);
|
|
266
|
+
if (!fromPath) continue;
|
|
267
|
+
const imports = extractImportPaths(node.rawContent, fromPath);
|
|
268
|
+
for (const imp of imports) {
|
|
269
|
+
const targetId = findNodeIdByPath(imp, pathToId);
|
|
270
|
+
if (targetId && targetId !== nodeId) {
|
|
271
|
+
node.connections.push({
|
|
272
|
+
to: targetId,
|
|
273
|
+
type: "references",
|
|
274
|
+
description: `Imports ${imp.split("/").pop() ?? imp}`,
|
|
275
|
+
source: "inferred"
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (entry.connections === "references") {
|
|
283
|
+
for (const nodeId of nodeIds) {
|
|
284
|
+
const node = nodeById.get(nodeId);
|
|
285
|
+
if (!node) continue;
|
|
286
|
+
const lower = node.rawContent.toLowerCase();
|
|
287
|
+
const seen = new Set(node.connections.map((c) => c.to));
|
|
288
|
+
for (const other of allNodes) {
|
|
289
|
+
if (other.id === nodeId || seen.has(other.id)) continue;
|
|
290
|
+
if (lower.includes(other.id.toLowerCase())) {
|
|
291
|
+
node.connections.push({
|
|
292
|
+
to: other.id,
|
|
293
|
+
type: "mentions",
|
|
294
|
+
description: `Mentions ${other.title}`,
|
|
295
|
+
source: "inferred"
|
|
296
|
+
});
|
|
297
|
+
seen.add(other.id);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const words = other.title.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
301
|
+
if (words.length === 0) continue;
|
|
302
|
+
const hits = words.filter((w) => lower.includes(w)).length;
|
|
303
|
+
if (hits >= Math.ceil(words.length * 0.6)) {
|
|
304
|
+
node.connections.push({
|
|
305
|
+
to: other.id,
|
|
306
|
+
type: "mentions",
|
|
307
|
+
description: `Mentions ${other.title}`,
|
|
308
|
+
source: "inferred"
|
|
309
|
+
});
|
|
310
|
+
seen.add(other.id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function loadNodeMap(nodemapRaw, readFile, listFiles, listDirectory) {
|
|
318
|
+
const map = yaml.parse(nodemapRaw);
|
|
319
|
+
if (!map?.nodes?.length) return [];
|
|
320
|
+
const allNodes = [];
|
|
321
|
+
const entryNodeIds = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const entry of map.nodes) {
|
|
323
|
+
let nodes = [];
|
|
324
|
+
if (entry.file) {
|
|
325
|
+
nodes = await processSingleFile(entry, readFile);
|
|
326
|
+
} else if (entry.files) {
|
|
327
|
+
nodes = await processMerge(entry, readFile);
|
|
328
|
+
} else if (entry.glob && entry.each === "file") {
|
|
329
|
+
nodes = await processGlob(entry, readFile, listFiles);
|
|
330
|
+
} else if (entry.directory) {
|
|
331
|
+
nodes = await processDirectory(entry, listDirectory);
|
|
332
|
+
}
|
|
333
|
+
entryNodeIds.set(entry.id, nodes.map((n) => n.id));
|
|
334
|
+
allNodes.push(...nodes);
|
|
335
|
+
}
|
|
336
|
+
deriveConnections(allNodes, map.nodes, entryNodeIds);
|
|
337
|
+
return allNodes;
|
|
338
|
+
}
|
|
339
|
+
async function collectNodemapData(nodemapRaw, tree, readFile, listDirectory) {
|
|
340
|
+
let map;
|
|
341
|
+
try {
|
|
342
|
+
map = yaml.parse(nodemapRaw);
|
|
343
|
+
} catch {
|
|
344
|
+
return { nodemapFiles: {}, nodemapDirs: {} };
|
|
345
|
+
}
|
|
346
|
+
if (!map?.nodes?.length) return { nodemapFiles: {}, nodemapDirs: {} };
|
|
347
|
+
const nodemapFiles = {};
|
|
348
|
+
const nodemapDirs = {};
|
|
349
|
+
const expandGlob = (pattern, exclude) => {
|
|
350
|
+
const regex = globToRegex(pattern);
|
|
351
|
+
let matches = tree.filter((e) => e.type === "blob" && regex.test(e.path)).map((e) => e.path);
|
|
352
|
+
if (exclude?.length) {
|
|
353
|
+
matches = matches.filter((p) => !matchesAnyGlob(p, exclude));
|
|
354
|
+
}
|
|
355
|
+
return matches;
|
|
356
|
+
};
|
|
357
|
+
for (const entry of map.nodes) {
|
|
358
|
+
if (entry.file) {
|
|
359
|
+
const content = await readFile(entry.file);
|
|
360
|
+
if (content !== null) nodemapFiles[entry.file] = content;
|
|
361
|
+
}
|
|
362
|
+
if (entry.files) {
|
|
363
|
+
for (const f of entry.files) {
|
|
364
|
+
const content = await readFile(f);
|
|
365
|
+
if (content !== null) nodemapFiles[f] = content;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (entry.glob && entry.each === "file") {
|
|
369
|
+
for (const match of expandGlob(entry.glob, entry.exclude)) {
|
|
370
|
+
const content = await readFile(match);
|
|
371
|
+
if (content !== null) nodemapFiles[match] = content;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (entry.directory) {
|
|
375
|
+
nodemapDirs[entry.directory] = await listDirectory(entry.directory);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return { nodemapFiles, nodemapDirs };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/structured-content.ts
|
|
382
|
+
var DEFAULT_STRUCTURED_CONTENT_PATH = "content-model";
|
|
383
|
+
function normalizeRepoRelativeDir(raw) {
|
|
384
|
+
if (typeof raw !== "string") return null;
|
|
385
|
+
let value = raw.trim().replace(/\\/g, "/");
|
|
386
|
+
while (value.startsWith("./")) value = value.slice(2);
|
|
387
|
+
value = value.replace(/\/+$/g, "");
|
|
388
|
+
if (!value || /^[a-zA-Z]:\//.test(value) || value.startsWith("/") || value.includes("://") || value === ".." || value.startsWith("../") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
393
|
+
function resolveStructuredContentPath(config, env) {
|
|
394
|
+
const engineEnv = env ?? {};
|
|
395
|
+
const envValue = engineEnv.VITE_KB_STRUCTURED_CONTENT_PATH ?? engineEnv.VITE_KB_CONTENT_MODEL_PATH;
|
|
396
|
+
const normalizedEnv = normalizeRepoRelativeDir(envValue);
|
|
397
|
+
if (normalizedEnv) return normalizedEnv;
|
|
398
|
+
const cfg = config;
|
|
399
|
+
const configured = cfg.structuredContent?.path ?? cfg.contentModel?.path;
|
|
400
|
+
const normalizedConfig = normalizeRepoRelativeDir(configured);
|
|
401
|
+
return normalizedConfig ?? DEFAULT_STRUCTURED_CONTENT_PATH;
|
|
402
|
+
}
|
|
403
|
+
function hasExplicitStructuredContentPath(config, env) {
|
|
404
|
+
const engineEnv = env ?? {};
|
|
405
|
+
const envValue = engineEnv.VITE_KB_STRUCTURED_CONTENT_PATH ?? engineEnv.VITE_KB_CONTENT_MODEL_PATH;
|
|
406
|
+
if (normalizeRepoRelativeDir(envValue)) return true;
|
|
407
|
+
const cfg = config;
|
|
408
|
+
const configured = cfg.structuredContent?.path ?? cfg.contentModel?.path;
|
|
409
|
+
return normalizeRepoRelativeDir(configured) !== null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export { DEFAULT_STRUCTURED_CONTENT_PATH, collectNodemapData, extractImportPaths, hasExplicitStructuredContentPath, loadNodeMap, normalizeRepoRelativeDir, resolveImportPath, resolveStructuredContentPath };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DEFAULT_TRANSFORMS, applyTransforms, buildGraph } from './chunk-DJ2DBEEK.js';
|
|
2
|
+
import { extractClusters } from './chunk-FJ4GTN4U.js';
|
|
3
|
+
import { hashProviderResultPrefix, GRAPH_STORE_DERIVATION_VERSION } from './chunk-HXAS3BSD.js';
|
|
4
|
+
import { GRAPH_STORE_CACHE_KEY_VERSION, GRAPH_STORE_API_VERSION } from '@anokye-labs/kbexplorer-core';
|
|
5
|
+
|
|
6
|
+
async function orchestrateWithProviderResultStore(registry, config, ctx, store, buildCacheKey, transforms = DEFAULT_TRANSFORMS) {
|
|
7
|
+
const providers = registry.getExecutionOrder();
|
|
8
|
+
let allNodes = [];
|
|
9
|
+
let previousContentHash;
|
|
10
|
+
for (const provider of providers) {
|
|
11
|
+
const key = await buildCacheKey(provider.id, previousContentHash);
|
|
12
|
+
const cached = await store.get(key);
|
|
13
|
+
if (cached) {
|
|
14
|
+
allNodes = cloneProviderResult(
|
|
15
|
+
validateProviderResult(cached.value, `cached graph store entry for ${provider.id}`)
|
|
16
|
+
).nodes;
|
|
17
|
+
previousContentHash = await hashProviderResultPrefix(provider.id, allNodes);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const result = await provider.resolve(config, allNodes);
|
|
21
|
+
allNodes.push(...result.nodes);
|
|
22
|
+
const value = cloneProviderResult({ nodes: allNodes, edges: [] });
|
|
23
|
+
await store.put({
|
|
24
|
+
key,
|
|
25
|
+
value,
|
|
26
|
+
dependencies: [dependencyFor(key, previousContentHash)],
|
|
27
|
+
metadata: {
|
|
28
|
+
graphStoreApiVersion: GRAPH_STORE_API_VERSION,
|
|
29
|
+
graphStoreCacheKeyVersion: GRAPH_STORE_CACHE_KEY_VERSION,
|
|
30
|
+
graphStoreDerivationVersion: GRAPH_STORE_DERIVATION_VERSION,
|
|
31
|
+
providerId: provider.id
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
previousContentHash = await hashProviderResultPrefix(provider.id, allNodes);
|
|
35
|
+
}
|
|
36
|
+
const transformed = applyTransforms(allNodes, ctx, transforms);
|
|
37
|
+
return buildGraph(transformed, extractClusters(transformed, config));
|
|
38
|
+
}
|
|
39
|
+
function dependencyFor(key, previousContentHash) {
|
|
40
|
+
return {
|
|
41
|
+
href: previousContentHash ? `${key.sourceId ?? key.providerId}#previous` : key.sourceId ?? key.providerId,
|
|
42
|
+
contentHash: previousContentHash ?? key.contentHash,
|
|
43
|
+
...key.sourceId !== void 0 ? { sourceId: key.sourceId } : {}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function cloneProviderResult(value) {
|
|
47
|
+
if (typeof structuredClone === "function") {
|
|
48
|
+
return structuredClone(value);
|
|
49
|
+
}
|
|
50
|
+
return JSON.parse(JSON.stringify(value));
|
|
51
|
+
}
|
|
52
|
+
function validateProviderResult(value, label) {
|
|
53
|
+
if (!value || !Array.isArray(value.nodes) || !Array.isArray(value.edges)) {
|
|
54
|
+
throw new Error(`Invalid ${label}: expected ProviderResult with nodes and edges arrays.`);
|
|
55
|
+
}
|
|
56
|
+
for (const node of value.nodes) {
|
|
57
|
+
validateNode(node, label);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function validateNode(node, label) {
|
|
62
|
+
if (!node || typeof node.id !== "string" || typeof node.title !== "string" || typeof node.cluster !== "string" || !Array.isArray(node.connections)) {
|
|
63
|
+
throw new Error(`Invalid ${label}: malformed KBNode.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { orchestrateWithProviderResultStore };
|