@swarmvaultai/engine 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  uniqueBy,
22
22
  writeFileIfChanged,
23
23
  writeJsonFile
24
- } from "./chunk-333AMRSV.js";
24
+ } from "./chunk-NCSZ4AKP.js";
25
25
 
26
26
  // src/agents.ts
27
27
  import fs from "fs/promises";
@@ -129,80 +129,106 @@ async function installConfiguredAgents(rootDir) {
129
129
  }
130
130
 
131
131
  // src/ingest.ts
132
- import fs3 from "fs/promises";
133
- import path4 from "path";
132
+ import fs5 from "fs/promises";
133
+ import path5 from "path";
134
134
  import { Readability } from "@mozilla/readability";
135
+ import ignore from "ignore";
135
136
  import { JSDOM } from "jsdom";
136
137
  import mime from "mime-types";
137
138
  import TurndownService from "turndown";
138
139
 
139
140
  // src/code-analysis.ts
140
- import path2 from "path";
141
+ import fs3 from "fs/promises";
142
+ import path3 from "path";
141
143
  import ts from "typescript";
142
- function scriptKindFor(language) {
143
- switch (language) {
144
- case "typescript":
145
- return ts.ScriptKind.TS;
146
- case "tsx":
147
- return ts.ScriptKind.TSX;
148
- case "jsx":
149
- return ts.ScriptKind.JSX;
150
- default:
151
- return ts.ScriptKind.JS;
144
+
145
+ // src/code-tree-sitter.ts
146
+ import fs2 from "fs/promises";
147
+ import { createRequire } from "module";
148
+ import path2 from "path";
149
+ var require2 = createRequire(import.meta.url);
150
+ var TREE_SITTER_PACKAGE_ROOT = path2.dirname(path2.dirname(require2.resolve("@vscode/tree-sitter-wasm")));
151
+ var treeSitterModulePromise;
152
+ var treeSitterInitPromise;
153
+ var languageCache = /* @__PURE__ */ new Map();
154
+ var grammarFileByLanguage = {
155
+ python: "tree-sitter-python.wasm",
156
+ go: "tree-sitter-go.wasm",
157
+ rust: "tree-sitter-rust.wasm",
158
+ java: "tree-sitter-java.wasm",
159
+ csharp: "tree-sitter-c-sharp.wasm",
160
+ c: "tree-sitter-cpp.wasm",
161
+ cpp: "tree-sitter-cpp.wasm",
162
+ php: "tree-sitter-php.wasm"
163
+ };
164
+ async function getTreeSitterModule() {
165
+ if (!treeSitterModulePromise) {
166
+ treeSitterModulePromise = import(require2.resolve("@vscode/tree-sitter-wasm")).then(
167
+ (module) => module.default ?? module
168
+ );
152
169
  }
170
+ return treeSitterModulePromise;
153
171
  }
154
- function isRelativeSpecifier(specifier) {
155
- return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith(".");
172
+ async function ensureTreeSitterInit(module) {
173
+ if (!treeSitterInitPromise) {
174
+ treeSitterInitPromise = module.Parser.init({
175
+ locateFile: () => path2.join(TREE_SITTER_PACKAGE_ROOT, "wasm", "tree-sitter.wasm")
176
+ });
177
+ }
178
+ return treeSitterInitPromise;
156
179
  }
157
- function formatDiagnosticCategory(category) {
158
- switch (category) {
159
- case ts.DiagnosticCategory.Error:
160
- return "error";
161
- case ts.DiagnosticCategory.Warning:
162
- return "warning";
163
- case ts.DiagnosticCategory.Suggestion:
164
- return "suggestion";
165
- default:
166
- return "message";
180
+ async function loadLanguage(language) {
181
+ const cached = languageCache.get(language);
182
+ if (cached) {
183
+ return cached;
167
184
  }
185
+ const loader = (async () => {
186
+ const module = await getTreeSitterModule();
187
+ await ensureTreeSitterInit(module);
188
+ const bytes = await fs2.readFile(path2.join(TREE_SITTER_PACKAGE_ROOT, "wasm", grammarFileByLanguage[language]));
189
+ return module.Language.load(bytes);
190
+ })();
191
+ languageCache.set(language, loader);
192
+ return loader;
168
193
  }
169
- function declarationSignature(node, sourceFile) {
170
- const sourceText = sourceFile.getFullText();
171
- if (ts.isFunctionDeclaration(node) && node.body) {
172
- return truncate(normalizeWhitespace(sourceText.slice(node.getStart(sourceFile), node.body.getStart(sourceFile)).trim()), 180);
194
+ function normalizeSymbolReference(value) {
195
+ const withoutGenerics = value.replace(/<[^>]*>/g, "");
196
+ const withoutDecorators = withoutGenerics.replace(/['"&*()[\]{}]/g, " ");
197
+ const trimmed = withoutDecorators.trim();
198
+ const lastSegment = trimmed.split(/::|\\|\.|->/).filter(Boolean).at(-1) ?? trimmed;
199
+ return lastSegment.replace(/[,:;]+$/g, "").trim();
200
+ }
201
+ function stripCodeExtension(filePath) {
202
+ return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
203
+ }
204
+ function manifestModuleName(manifest, language) {
205
+ const repoPath = manifest.repoRelativePath ?? path2.basename(manifest.originalPath ?? manifest.storedPath);
206
+ const normalized = toPosix(stripCodeExtension(repoPath)).replace(/^\.\/+/, "");
207
+ if (!normalized) {
208
+ return void 0;
173
209
  }
174
- if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)) {
175
- const membersPos = node.members.pos;
176
- return truncate(
177
- normalizeWhitespace(
178
- sourceText.slice(node.getStart(sourceFile), membersPos).replace(/\{\s*$/, "").trim()
179
- ),
180
- 180
181
- );
210
+ if (language === "python") {
211
+ const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
212
+ return dotted || path2.posix.basename(normalized);
182
213
  }
183
- return truncate(normalizeWhitespace(node.getText(sourceFile)), 180);
214
+ return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path2.posix.basename(normalized) : normalized;
184
215
  }
185
- function importSpecifierText(specifier) {
186
- return specifier.propertyName ? `${specifier.propertyName.text} as ${specifier.name.text}` : specifier.name.text;
216
+ function singleLineSignature(value) {
217
+ return truncate(
218
+ normalizeWhitespace(
219
+ value.replace(/\{\s*$/, "").replace(/:\s*$/, ":").trim()
220
+ ),
221
+ 180
222
+ );
187
223
  }
188
- function exportSpecifierText(specifier) {
189
- return specifier.propertyName ? `${specifier.propertyName.text} as ${specifier.name.text}` : specifier.name.text;
224
+ function makeSymbolId(sourceId, name, seen) {
225
+ const base = slugify(name);
226
+ const count = (seen.get(base) ?? 0) + 1;
227
+ seen.set(base, count);
228
+ return `symbol:${sourceId}:${count === 1 ? base : `${base}-${count}`}`;
190
229
  }
191
- function collectCallNames(root, availableNames, selfName) {
192
- if (!root) {
193
- return [];
194
- }
195
- const names = [];
196
- const visit = (node) => {
197
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && availableNames.has(node.expression.text)) {
198
- if (node.expression.text !== selfName) {
199
- names.push(node.expression.text);
200
- }
201
- }
202
- ts.forEachChild(node, visit);
203
- };
204
- visit(root);
205
- return uniqueBy(names, (name) => name);
230
+ function escapeRegExp(value) {
231
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
206
232
  }
207
233
  function collectCallNamesFromText(text, availableNames, selfName) {
208
234
  if (!text) {
@@ -220,132 +246,7 @@ function collectCallNamesFromText(text, availableNames, selfName) {
220
246
  }
221
247
  return uniqueBy(names, (name) => name);
222
248
  }
223
- function heritageNames(clauses, token) {
224
- return uniqueBy(
225
- (clauses ?? []).filter((clause) => clause.token === token).flatMap(
226
- (clause) => clause.types.map((typeNode) => {
227
- if (ts.isIdentifier(typeNode.expression)) {
228
- return typeNode.expression.text;
229
- }
230
- if (ts.isPropertyAccessExpression(typeNode.expression)) {
231
- return typeNode.expression.getText();
232
- }
233
- return typeNode.getText();
234
- })
235
- ),
236
- (name) => name
237
- );
238
- }
239
- function isNodeExported(node) {
240
- return Boolean(
241
- ts.canHaveModifiers(node) && ts.getModifiers(node)?.some(
242
- (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword
243
- )
244
- );
245
- }
246
- function makeSymbolId(sourceId, name, seen) {
247
- const base = slugify(name);
248
- const count = (seen.get(base) ?? 0) + 1;
249
- seen.set(base, count);
250
- return `symbol:${sourceId}:${count === 1 ? base : `${base}-${count}`}`;
251
- }
252
- function summarizeModule(manifest, code) {
253
- const localImports = code.imports.filter((item) => !item.isExternal && !item.reExport).length;
254
- const externalImports = code.imports.filter((item) => item.isExternal).length;
255
- const exportedCount = code.symbols.filter((symbol) => symbol.exported).length;
256
- const parts = [`${code.language} module`, `defining ${code.symbols.length} top-level symbol(s)`, `exporting ${exportedCount} symbol(s)`];
257
- if (localImports > 0) {
258
- parts.push(`importing ${localImports} local module(s)`);
259
- }
260
- if (externalImports > 0) {
261
- parts.push(`depending on ${externalImports} external package import(s)`);
262
- }
263
- if (code.diagnostics.length > 0) {
264
- parts.push(`with ${code.diagnostics.length} parser diagnostic(s)`);
265
- }
266
- return `${manifest.title} is a ${parts.join(", ")}.`;
267
- }
268
- function codeClaims(manifest, code) {
269
- const claims = [];
270
- if (code.exports.length > 0) {
271
- claims.push({
272
- text: `${manifest.title} exports ${code.exports.slice(0, 4).join(", ")}${code.exports.length > 4 ? ", and more" : ""}.`,
273
- confidence: 1,
274
- status: "extracted",
275
- polarity: "neutral",
276
- citation: manifest.sourceId
277
- });
278
- }
279
- if (code.symbols.length > 0) {
280
- claims.push({
281
- text: `${manifest.title} defines ${code.symbols.slice(0, 5).map((symbol) => symbol.name).join(", ")}${code.symbols.length > 5 ? ", and more" : ""}.`,
282
- confidence: 1,
283
- status: "extracted",
284
- polarity: "neutral",
285
- citation: manifest.sourceId
286
- });
287
- }
288
- if (code.imports.length > 0) {
289
- claims.push({
290
- text: `${manifest.title} imports ${code.imports.slice(0, 4).map((item) => item.specifier).join(", ")}${code.imports.length > 4 ? ", and more" : ""}.`,
291
- confidence: 1,
292
- status: "extracted",
293
- polarity: "neutral",
294
- citation: manifest.sourceId
295
- });
296
- }
297
- if (code.diagnostics.length > 0) {
298
- claims.push({
299
- text: `${manifest.title} has ${code.diagnostics.length} parser diagnostic(s) that should be reviewed before trusting the module summary.`,
300
- confidence: 1,
301
- status: "extracted",
302
- polarity: "negative",
303
- citation: manifest.sourceId
304
- });
305
- }
306
- return claims.slice(0, 4).map((claim, index) => ({
307
- id: `claim:${manifest.sourceId}:${index + 1}`,
308
- ...claim
309
- }));
310
- }
311
- function codeQuestions(manifest, code) {
312
- const questions = [
313
- code.exports.length > 0 ? `Which downstream pages should explain how ${manifest.title} exports are consumed?` : "",
314
- code.imports.some((item) => !item.isExternal) ? `How does ${manifest.title} coordinate with its imported local modules?` : "",
315
- code.dependencies[0] ? `Why does ${manifest.title} depend on ${code.dependencies[0]}?` : "",
316
- `What broader responsibility does ${manifest.title} serve in the codebase?`
317
- ].filter(Boolean);
318
- return uniqueBy(questions, (question) => question).slice(0, 4);
319
- }
320
- function resolveVariableKind(statement) {
321
- return statement.declarationList.flags & ts.NodeFlags.Const ? "variable" : "variable";
322
- }
323
- function splitLines(content) {
324
- return content.split(/\r?\n/);
325
- }
326
- function leadingIndent(line) {
327
- const match = line.match(/^[ \t]*/);
328
- return match ? match[0].replace(/\t/g, " ").length : 0;
329
- }
330
- function normalizeSymbolReference(value) {
331
- const withoutGenerics = value.replace(/<[^>]*>/g, "");
332
- const withoutDecorators = withoutGenerics.replace(/['"&*()[\]{}]/g, " ");
333
- const trimmed = withoutDecorators.trim();
334
- const lastSegment = trimmed.split(/::|\./).filter(Boolean).at(-1) ?? trimmed;
335
- return lastSegment.replace(/[,:;]+$/g, "").trim();
336
- }
337
- function singleLineSignature(line) {
338
- return truncate(
339
- normalizeWhitespace(
340
- line.replace(/\{\s*$/, "").replace(/:\s*$/, ":").trim()
341
- ),
342
- 180
343
- );
344
- }
345
- function buildDiagnostic(code, message, line, column = 1, category = "warning") {
346
- return { code, category, message, line, column };
347
- }
348
- function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics) {
249
+ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
349
250
  const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
350
251
  for (const symbol of draftSymbols) {
351
252
  if (symbol.callNames.length === 0 && symbol.bodyText) {
@@ -366,6 +267,8 @@ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportL
366
267
  return {
367
268
  moduleId: `module:${manifest.sourceId}`,
368
269
  language,
270
+ moduleName: metadata?.moduleName ?? manifestModuleName(manifest, language),
271
+ namespace: metadata?.namespace,
369
272
  imports,
370
273
  dependencies: uniqueBy(
371
274
  imports.filter((item) => item.isExternal).map((item) => item.specifier),
@@ -376,631 +279,957 @@ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportL
376
279
  diagnostics
377
280
  };
378
281
  }
379
- function collectPythonBlock(lines, startIndex) {
380
- const startIndent = leadingIndent(lines[startIndex] ?? "");
381
- const parts = [];
382
- for (let index = startIndex + 1; index < lines.length; index += 1) {
383
- const line = lines[index] ?? "";
384
- const trimmed = line.trim();
385
- if (!trimmed) {
386
- parts.push(line);
387
- continue;
388
- }
389
- if (leadingIndent(line) <= startIndent) {
390
- break;
391
- }
392
- parts.push(line);
282
+ function nodeText(node) {
283
+ return node?.text ?? "";
284
+ }
285
+ function findNamedChild(node, type) {
286
+ return node?.namedChildren.find((child) => child?.type === type) ?? null;
287
+ }
288
+ function extractIdentifier(node) {
289
+ if (!node) {
290
+ return void 0;
393
291
  }
394
- return parts.join("\n");
292
+ if (["identifier", "field_identifier", "type_identifier", "name", "package_identifier"].includes(node.type)) {
293
+ return node.text.trim();
294
+ }
295
+ const preferred = node.childForFieldName("name") ?? node.namedChildren.find(
296
+ (child) => child && ["identifier", "field_identifier", "type_identifier", "name", "package_identifier"].includes(child.type)
297
+ ) ?? node.namedChildren.at(-1) ?? null;
298
+ return preferred ? extractIdentifier(preferred) : void 0;
395
299
  }
396
- function parsePythonImportList(value) {
397
- return value.split(",").map((item) => item.trim()).filter(Boolean).map((item) => {
398
- const [rawSpecifier, rawAlias] = item.split(/\s+as\s+/i);
399
- return {
400
- specifier: rawSpecifier.trim(),
401
- alias: rawAlias?.trim()
402
- };
403
- });
300
+ function exportedByCapitalization(name) {
301
+ return /^[A-Z]/.test(name);
404
302
  }
405
- function parsePythonAllExportList(value) {
406
- const match = value.match(/\[(.*)\]/);
407
- if (!match) {
303
+ function parseCommaSeparatedReferences(value) {
304
+ return uniqueBy(
305
+ value.split(",").map((item) => item.replace(/\b(public|private|protected|internal|virtual|sealed|static|readonly|new|abstract|final)\b/g, "").trim()).map((item) => normalizeSymbolReference(item)).filter(Boolean),
306
+ (item) => item
307
+ );
308
+ }
309
+ function descendantTypeNames(node) {
310
+ if (!node) {
408
311
  return [];
409
312
  }
410
313
  return uniqueBy(
411
- match[1].split(",").map((item) => item.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean),
314
+ node.descendantsOfType(["type_identifier", "identifier", "name"]).filter((item) => item !== null).map((item) => normalizeSymbolReference(item.text)).filter(Boolean),
412
315
  (item) => item
413
316
  );
414
317
  }
415
- function analyzePythonCode(manifest, content) {
416
- const lines = splitLines(content);
417
- const imports = [];
418
- const draftSymbols = [];
419
- const exportLabels = [];
318
+ function quotedPath(value) {
319
+ return value.replace(/^["'<]+|[">]+$/g, "").trim();
320
+ }
321
+ function diagnosticsFromTree(rootNode) {
322
+ if (!rootNode.hasError) {
323
+ return [];
324
+ }
420
325
  const diagnostics = [];
421
- let explicitExports = [];
422
- for (let index = 0; index < lines.length; index += 1) {
423
- const rawLine = lines[index] ?? "";
424
- const trimmed = rawLine.trim();
425
- if (!trimmed || trimmed.startsWith("#") || leadingIndent(rawLine) > 0) {
426
- continue;
326
+ const seen = /* @__PURE__ */ new Set();
327
+ const visit = (node) => {
328
+ if (!node) {
329
+ return;
427
330
  }
428
- const importMatch = trimmed.match(/^import\s+(.+)$/);
429
- if (importMatch) {
430
- for (const item of parsePythonImportList(importMatch[1])) {
431
- imports.push({
432
- specifier: item.specifier,
433
- importedSymbols: [],
434
- namespaceImport: item.alias,
435
- isExternal: !isRelativeSpecifier(item.specifier),
436
- reExport: false
331
+ if (node.isError || node.isMissing) {
332
+ const key = `${node.startPosition.row}:${node.startPosition.column}:${node.type}:${node.text}`;
333
+ if (!seen.has(key)) {
334
+ seen.add(key);
335
+ diagnostics.push({
336
+ code: node.isMissing ? 9002 : 9001,
337
+ category: "error",
338
+ message: node.isMissing ? `Missing ${node.type} near \`${truncate(normalizeWhitespace(node.text), 80)}\`.` : `Syntax error near \`${truncate(normalizeWhitespace(node.text), 80)}\`.`,
339
+ line: node.startPosition.row + 1,
340
+ column: node.startPosition.column + 1
437
341
  });
438
342
  }
439
- continue;
440
- }
441
- const fromImportMatch = trimmed.match(/^from\s+([.\w]+)\s+import\s+(.+)$/);
442
- if (fromImportMatch) {
443
- const importedSymbols = fromImportMatch[2].split(",").map((item) => item.trim()).filter(Boolean);
444
- imports.push({
445
- specifier: fromImportMatch[1],
446
- importedSymbols,
447
- isExternal: !isRelativeSpecifier(fromImportMatch[1]),
448
- reExport: false
449
- });
450
- continue;
451
- }
452
- const classMatch = trimmed.match(/^class\s+([A-Za-z_]\w*)\s*(?:\(([^)]*)\))?\s*:/);
453
- if (classMatch) {
454
- const baseNames = classMatch[2] ? classMatch[2].split(",").map((item) => normalizeSymbolReference(item)).filter(Boolean) : [];
455
- draftSymbols.push({
456
- name: classMatch[1],
457
- kind: "class",
458
- signature: singleLineSignature(trimmed),
459
- exported: !classMatch[1].startsWith("_"),
460
- callNames: [],
461
- extendsNames: baseNames,
462
- implementsNames: [],
463
- bodyText: collectPythonBlock(lines, index)
464
- });
465
- continue;
466
- }
467
- if (trimmed.startsWith("class ")) {
468
- diagnostics.push(buildDiagnostic(1001, "Python class declaration is missing a trailing colon.", index + 1));
469
- continue;
470
- }
471
- const functionMatch = trimmed.match(/^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/);
472
- if (functionMatch) {
473
- draftSymbols.push({
474
- name: functionMatch[1],
475
- kind: "function",
476
- signature: singleLineSignature(trimmed),
477
- exported: !functionMatch[1].startsWith("_"),
478
- callNames: [],
479
- extendsNames: [],
480
- implementsNames: [],
481
- bodyText: collectPythonBlock(lines, index)
482
- });
483
- continue;
484
343
  }
485
- if (trimmed.startsWith("def ") || trimmed.startsWith("async def ")) {
486
- diagnostics.push(buildDiagnostic(1002, "Python function declaration is missing a trailing colon.", index + 1));
487
- continue;
488
- }
489
- const allMatch = trimmed.match(/^__all__\s*=\s*\[(.*)\]\s*$/);
490
- if (allMatch) {
491
- explicitExports = parsePythonAllExportList(trimmed);
492
- continue;
493
- }
494
- const variableMatch = trimmed.match(/^([A-Za-z_]\w*)\s*(?::[^=]+)?=\s*(.+)$/);
495
- if (variableMatch && !["True", "False", "None"].includes(variableMatch[1])) {
496
- draftSymbols.push({
497
- name: variableMatch[1],
498
- kind: "variable",
499
- signature: singleLineSignature(trimmed),
500
- exported: !variableMatch[1].startsWith("_"),
501
- callNames: [],
502
- extendsNames: [],
503
- implementsNames: [],
504
- bodyText: variableMatch[2]
505
- });
344
+ for (const child of node.children) {
345
+ visit(child);
506
346
  }
347
+ };
348
+ visit(rootNode);
349
+ return diagnostics.slice(0, 20);
350
+ }
351
+ function parsePythonImportStatement(text) {
352
+ const match = text.trim().match(/^import\s+(.+)$/);
353
+ if (!match) {
354
+ return [];
507
355
  }
508
- if (explicitExports.length > 0) {
509
- const explicitExportSet = new Set(explicitExports);
510
- for (const symbol of draftSymbols) {
511
- symbol.exported = explicitExportSet.has(symbol.name);
512
- }
513
- exportLabels.push(...explicitExports);
514
- }
515
- return finalizeCodeAnalysis(manifest, "python", imports, draftSymbols, exportLabels, diagnostics);
516
- }
517
- function collectBracedBlock(lines, startIndex) {
518
- const parts = [];
519
- let depth = 0;
520
- let started = false;
521
- for (let index = startIndex; index < lines.length; index += 1) {
522
- const line = lines[index] ?? "";
523
- parts.push(line);
524
- for (const character of line) {
525
- if (character === "{") {
526
- depth += 1;
527
- started = true;
528
- } else if (character === "}") {
529
- depth -= 1;
530
- }
531
- }
532
- if (started && depth <= 0) {
533
- return { text: parts.join("\n"), endIndex: index };
356
+ return match[1].split(",").map((item) => item.trim()).filter(Boolean).map((item) => {
357
+ const [specifier, alias] = item.split(/\s+as\s+/i);
358
+ return {
359
+ specifier: specifier.trim(),
360
+ importedSymbols: [],
361
+ namespaceImport: alias?.trim(),
362
+ isExternal: !specifier.trim().startsWith("."),
363
+ reExport: false
364
+ };
365
+ });
366
+ }
367
+ function parsePythonFromImportStatement(text) {
368
+ const match = text.trim().match(/^from\s+([.\w]+)\s+import\s+(.+)$/);
369
+ if (!match) {
370
+ return [];
371
+ }
372
+ return [
373
+ {
374
+ specifier: match[1],
375
+ importedSymbols: match[2].split(",").map((item) => item.trim()).filter(Boolean),
376
+ isExternal: !match[1].startsWith("."),
377
+ reExport: false
534
378
  }
379
+ ];
380
+ }
381
+ function parseGoImport(text) {
382
+ const match = text.trim().match(/^(?:([._A-Za-z]\w*)\s+)?"([^"]+)"$/);
383
+ if (!match) {
384
+ return void 0;
535
385
  }
536
386
  return {
537
- text: parts.join("\n"),
538
- endIndex: started ? lines.length - 1 : startIndex
387
+ specifier: match[2],
388
+ importedSymbols: [],
389
+ namespaceImport: match[1] && ![".", "_"].includes(match[1]) ? match[1] : void 0,
390
+ isExternal: !match[2].startsWith("."),
391
+ reExport: false
539
392
  };
540
393
  }
541
- function exportedByCapitalization(name) {
542
- return /^[A-Z]/.test(name);
394
+ function parseRustUse(text) {
395
+ const cleaned = text.replace(/^pub\s+/, "").replace(/^use\s+/, "").replace(/;$/, "").trim();
396
+ const aliasMatch = cleaned.match(/\s+as\s+([A-Za-z_]\w*)$/);
397
+ const withoutAlias = aliasMatch ? cleaned.slice(0, aliasMatch.index).trim() : cleaned;
398
+ const braceMatch = withoutAlias.match(/^(.*)::\{(.+)\}$/);
399
+ const importedSymbols = braceMatch ? braceMatch[2].split(",").map((item) => item.trim()).filter(Boolean) : [aliasMatch ? `${normalizeSymbolReference(withoutAlias)} as ${aliasMatch[1]}` : normalizeSymbolReference(withoutAlias)].filter(
400
+ Boolean
401
+ );
402
+ const specifier = braceMatch ? braceMatch[1].trim() : withoutAlias;
403
+ return {
404
+ specifier,
405
+ importedSymbols,
406
+ isExternal: !/^(crate|self|super)::/.test(specifier),
407
+ reExport: text.trim().startsWith("pub use ")
408
+ };
409
+ }
410
+ function parseJavaImport(text) {
411
+ const cleaned = text.replace(/^import\s+/, "").replace(/^static\s+/, "").replace(/;$/, "").trim();
412
+ const symbolName = normalizeSymbolReference(cleaned.replace(/\.\*$/, ""));
413
+ return {
414
+ specifier: cleaned.replace(/\.\*$/, ""),
415
+ importedSymbols: symbolName ? [symbolName] : [],
416
+ isExternal: true,
417
+ reExport: false
418
+ };
543
419
  }
544
- function parseGoImportLine(line) {
545
- const match = line.trim().match(/^(?:([._A-Za-z]\w*)\s+)?"([^"]+)"$/);
420
+ function parseCSharpUsing(text) {
421
+ const aliasMatch = text.trim().match(/^using\s+([A-Za-z_]\w*)\s*=\s*([^;]+);$/);
422
+ if (aliasMatch) {
423
+ return {
424
+ specifier: aliasMatch[2].trim(),
425
+ importedSymbols: [],
426
+ namespaceImport: aliasMatch[1],
427
+ isExternal: !aliasMatch[2].trim().startsWith("."),
428
+ reExport: false
429
+ };
430
+ }
431
+ const match = text.trim().match(/^using\s+([^;]+);$/);
546
432
  if (!match) {
547
433
  return void 0;
548
434
  }
549
435
  return {
550
- specifier: match[2],
436
+ specifier: match[1].trim(),
551
437
  importedSymbols: [],
552
- namespaceImport: match[1] && ![".", "_"].includes(match[1]) ? match[1] : void 0,
553
- isExternal: !isRelativeSpecifier(match[2]),
438
+ isExternal: !match[1].trim().startsWith("."),
554
439
  reExport: false
555
440
  };
556
441
  }
557
- function receiverTypeName(receiver) {
558
- const tokens = receiver.replace(/[()*]/g, " ").split(/\s+/).map((item) => item.trim()).filter(Boolean);
559
- return normalizeSymbolReference(tokens.at(-1) ?? "");
442
+ function parsePhpUse(text) {
443
+ const cleaned = text.trim().replace(/^use\s+/, "").replace(/;$/, "");
444
+ return cleaned.split(",").map((item) => item.trim()).filter(Boolean).map((item) => {
445
+ const aliasMatch = item.match(/^(.+?)\s+as\s+([A-Za-z_]\w*)$/i);
446
+ const specifier = (aliasMatch ? aliasMatch[1] : item).trim();
447
+ return {
448
+ specifier,
449
+ importedSymbols: [],
450
+ namespaceImport: aliasMatch?.[2],
451
+ isExternal: !specifier.startsWith("."),
452
+ reExport: false
453
+ };
454
+ });
455
+ }
456
+ function parseCppInclude(text) {
457
+ const match = text.trim().match(/^#include\s+([<"].+[>"])$/);
458
+ if (!match) {
459
+ return void 0;
460
+ }
461
+ const specifier = quotedPath(match[1]);
462
+ return {
463
+ specifier,
464
+ importedSymbols: [],
465
+ isExternal: match[1].startsWith("<"),
466
+ reExport: false
467
+ };
560
468
  }
561
- function analyzeGoCode(manifest, content) {
562
- const lines = splitLines(content);
469
+ function pythonCodeAnalysis(manifest, rootNode, diagnostics) {
563
470
  const imports = [];
564
471
  const draftSymbols = [];
565
- const exportLabels = [];
566
- const diagnostics = [];
567
- let inImportBlock = false;
568
- for (let index = 0; index < lines.length; index += 1) {
569
- const rawLine = lines[index] ?? "";
570
- const trimmed = rawLine.trim();
571
- if (!trimmed || trimmed.startsWith("//")) {
472
+ for (const child of rootNode.namedChildren) {
473
+ if (!child) {
572
474
  continue;
573
475
  }
574
- if (inImportBlock) {
575
- if (trimmed === ")") {
576
- inImportBlock = false;
577
- continue;
578
- }
579
- const parsed = parseGoImportLine(trimmed);
580
- if (parsed) {
581
- imports.push(parsed);
582
- }
476
+ if (child.type === "import_statement") {
477
+ imports.push(...parsePythonImportStatement(child.text));
583
478
  continue;
584
479
  }
585
- const importBlockMatch = trimmed.match(/^import\s+\($/);
586
- if (importBlockMatch) {
587
- inImportBlock = true;
480
+ if (child.type === "import_from_statement") {
481
+ imports.push(...parsePythonFromImportStatement(child.text));
588
482
  continue;
589
483
  }
590
- const singleImportMatch = trimmed.match(/^import\s+(.+)$/);
591
- if (singleImportMatch) {
592
- const parsed = parseGoImportLine(singleImportMatch[1]);
593
- if (parsed) {
594
- imports.push(parsed);
484
+ if (child.type === "class_definition") {
485
+ const name = extractIdentifier(child.childForFieldName("name"));
486
+ if (!name) {
487
+ continue;
595
488
  }
596
- continue;
597
- }
598
- const typeMatch = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/);
599
- if (typeMatch) {
600
- const exported = exportedByCapitalization(typeMatch[1]);
489
+ const superclasses = parseCommaSeparatedReferences(nodeText(child.childForFieldName("superclasses")).replace(/^\(|\)$/g, ""));
601
490
  draftSymbols.push({
602
- name: typeMatch[1],
603
- kind: typeMatch[2] === "interface" ? "interface" : "class",
604
- signature: singleLineSignature(trimmed),
605
- exported,
491
+ name,
492
+ kind: "class",
493
+ signature: singleLineSignature(child.text),
494
+ exported: !name.startsWith("_"),
606
495
  callNames: [],
607
- extendsNames: [],
496
+ extendsNames: superclasses,
608
497
  implementsNames: [],
609
- bodyText: collectBracedBlock(lines, index).text
498
+ bodyText: nodeText(child.childForFieldName("body"))
610
499
  });
611
- if (exported) {
612
- exportLabels.push(typeMatch[1]);
613
- }
614
500
  continue;
615
501
  }
616
- const aliasTypeMatch = trimmed.match(/^type\s+([A-Za-z_]\w*)\b(?!\s+(?:struct|interface)\b)/);
617
- if (aliasTypeMatch) {
618
- const exported = exportedByCapitalization(aliasTypeMatch[1]);
619
- draftSymbols.push({
620
- name: aliasTypeMatch[1],
621
- kind: "type_alias",
622
- signature: singleLineSignature(trimmed),
623
- exported,
624
- callNames: [],
625
- extendsNames: [],
626
- implementsNames: [],
627
- bodyText: trimmed
628
- });
629
- if (exported) {
630
- exportLabels.push(aliasTypeMatch[1]);
502
+ if (child.type === "function_definition") {
503
+ const name = extractIdentifier(child.childForFieldName("name"));
504
+ if (!name) {
505
+ continue;
631
506
  }
632
- continue;
633
- }
634
- const funcMatch = trimmed.match(/^func\s+(?:\(([^)]*)\)\s*)?([A-Za-z_]\w*)\s*\(/);
635
- if (funcMatch) {
636
- const receiverType = funcMatch[1] ? receiverTypeName(funcMatch[1]) : "";
637
- const symbolName = receiverType ? `${receiverType}.${funcMatch[2]}` : funcMatch[2];
638
- const exported = exportedByCapitalization(funcMatch[2]);
639
- const block = collectBracedBlock(lines, index);
640
507
  draftSymbols.push({
641
- name: symbolName,
508
+ name,
642
509
  kind: "function",
643
- signature: singleLineSignature(trimmed),
644
- exported,
645
- callNames: [],
646
- extendsNames: [],
647
- implementsNames: [],
648
- bodyText: block.text
649
- });
650
- if (exported) {
651
- exportLabels.push(symbolName);
652
- }
653
- index = block.endIndex;
654
- continue;
655
- }
656
- const variableMatch = trimmed.match(/^(?:var|const)\s+([A-Za-z_]\w*)\b/);
657
- if (variableMatch) {
658
- const exported = exportedByCapitalization(variableMatch[1]);
659
- draftSymbols.push({
660
- name: variableMatch[1],
661
- kind: "variable",
662
- signature: singleLineSignature(trimmed),
663
- exported,
510
+ signature: singleLineSignature(child.text),
511
+ exported: !name.startsWith("_"),
664
512
  callNames: [],
665
513
  extendsNames: [],
666
514
  implementsNames: [],
667
- bodyText: trimmed
515
+ bodyText: nodeText(child.childForFieldName("body"))
668
516
  });
669
- if (exported) {
670
- exportLabels.push(variableMatch[1]);
671
- }
672
517
  }
673
518
  }
674
- return finalizeCodeAnalysis(manifest, "go", imports, draftSymbols, exportLabels, diagnostics);
675
- }
676
- function analyzeRustUseStatement(statement) {
677
- const cleaned = statement.replace(/^pub\s+/, "").replace(/^use\s+/, "").replace(/;$/, "").trim();
678
- const aliasMatch = cleaned.match(/\s+as\s+([A-Za-z_]\w*)$/);
679
- const withoutAlias = aliasMatch ? cleaned.slice(0, aliasMatch.index).trim() : cleaned;
680
- const braceMatch = withoutAlias.match(/^(.*)::\{(.+)\}$/);
681
- const importedSymbols = braceMatch ? braceMatch[2].split(",").map((item) => item.trim()).filter(Boolean) : [aliasMatch ? `${normalizeSymbolReference(withoutAlias)} as ${aliasMatch[1]}` : normalizeSymbolReference(withoutAlias)].filter(
682
- Boolean
683
- );
684
- const specifier = braceMatch ? braceMatch[1].trim() : withoutAlias;
685
- return {
686
- specifier,
687
- importedSymbols,
688
- isExternal: !/^(crate|self|super)::/.test(specifier),
689
- reExport: statement.trim().startsWith("pub use ")
690
- };
519
+ return finalizeCodeAnalysis(manifest, "python", imports, draftSymbols, [], diagnostics);
691
520
  }
692
- function rustVisibilityPrefix(trimmed) {
693
- return /^(pub(?:\([^)]*\))?\s+)/.test(trimmed);
694
- }
695
- function analyzeRustCode(manifest, content) {
696
- const lines = splitLines(content);
521
+ function goCodeAnalysis(manifest, rootNode, diagnostics) {
697
522
  const imports = [];
698
523
  const draftSymbols = [];
699
524
  const exportLabels = [];
700
- const diagnostics = [];
701
- const symbolByName = /* @__PURE__ */ new Map();
702
- for (let index = 0; index < lines.length; index += 1) {
703
- const rawLine = lines[index] ?? "";
704
- const trimmed = rawLine.trim();
705
- if (!trimmed || trimmed.startsWith("//")) {
525
+ let packageName;
526
+ for (const child of rootNode.namedChildren) {
527
+ if (!child) {
706
528
  continue;
707
529
  }
708
- const useMatch = trimmed.match(/^(?:pub\s+)?use\s+.+;$/);
709
- if (useMatch) {
710
- imports.push(analyzeRustUseStatement(trimmed));
530
+ if (child.type === "package_clause") {
531
+ packageName = extractIdentifier(child.namedChildren.at(0) ?? null);
711
532
  continue;
712
533
  }
713
- const functionMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?fn\s+([A-Za-z_]\w*)\s*\(/);
714
- if (functionMatch) {
715
- const exported = rustVisibilityPrefix(trimmed);
716
- const block = collectBracedBlock(lines, index);
717
- const symbol = {
718
- name: functionMatch[1],
719
- kind: "function",
720
- signature: singleLineSignature(trimmed),
721
- exported,
722
- callNames: [],
723
- extendsNames: [],
724
- implementsNames: [],
725
- bodyText: block.text
726
- };
727
- draftSymbols.push(symbol);
728
- symbolByName.set(symbol.name, symbol);
729
- if (exported) {
730
- exportLabels.push(symbol.name);
534
+ if (child.type === "import_declaration") {
535
+ for (const spec of child.descendantsOfType("import_spec")) {
536
+ const parsed = spec ? parseGoImport(spec.text) : void 0;
537
+ if (parsed) {
538
+ imports.push(parsed);
539
+ }
731
540
  }
732
- index = block.endIndex;
733
541
  continue;
734
542
  }
735
- const structMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)\b/);
736
- if (structMatch) {
737
- const exported = rustVisibilityPrefix(trimmed);
738
- const block = collectBracedBlock(lines, index);
739
- const symbol = {
740
- name: structMatch[1],
741
- kind: "class",
742
- signature: singleLineSignature(trimmed),
743
- exported,
744
- callNames: [],
745
- extendsNames: [],
746
- implementsNames: [],
747
- bodyText: block.text
748
- };
749
- draftSymbols.push(symbol);
750
- symbolByName.set(symbol.name, symbol);
751
- if (exported) {
752
- exportLabels.push(symbol.name);
543
+ if (child.type === "type_declaration") {
544
+ for (const spec of child.descendantsOfType("type_spec")) {
545
+ if (!spec) {
546
+ continue;
547
+ }
548
+ const name = extractIdentifier(spec.childForFieldName("name"));
549
+ const typeNode = spec.childForFieldName("type");
550
+ if (!name || !typeNode) {
551
+ continue;
552
+ }
553
+ const kind = typeNode.type === "struct_type" ? "struct" : typeNode.type === "interface_type" ? "interface" : "type_alias";
554
+ const exported = exportedByCapitalization(name);
555
+ draftSymbols.push({
556
+ name,
557
+ kind,
558
+ signature: singleLineSignature(spec.text),
559
+ exported,
560
+ callNames: [],
561
+ extendsNames: [],
562
+ implementsNames: [],
563
+ bodyText: typeNode.text
564
+ });
565
+ if (exported) {
566
+ exportLabels.push(name);
567
+ }
753
568
  }
754
- index = block.endIndex;
755
569
  continue;
756
570
  }
757
- const enumMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)\b/);
758
- if (enumMatch) {
759
- const exported = rustVisibilityPrefix(trimmed);
760
- const block = collectBracedBlock(lines, index);
761
- const symbol = {
762
- name: enumMatch[1],
763
- kind: "enum",
764
- signature: singleLineSignature(trimmed),
765
- exported,
766
- callNames: [],
767
- extendsNames: [],
768
- implementsNames: [],
769
- bodyText: block.text
770
- };
771
- draftSymbols.push(symbol);
772
- symbolByName.set(symbol.name, symbol);
773
- if (exported) {
774
- exportLabels.push(symbol.name);
571
+ if (child.type === "function_declaration" || child.type === "method_declaration") {
572
+ const name = extractIdentifier(child.childForFieldName("name"));
573
+ if (!name) {
574
+ continue;
775
575
  }
776
- index = block.endIndex;
777
- continue;
778
- }
779
- const traitMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?trait\s+([A-Za-z_]\w*)\b/);
780
- if (traitMatch) {
781
- const exported = rustVisibilityPrefix(trimmed);
782
- const block = collectBracedBlock(lines, index);
783
- const symbol = {
784
- name: traitMatch[1],
785
- kind: "interface",
786
- signature: singleLineSignature(trimmed),
576
+ const receiverType = child.type === "method_declaration" ? normalizeSymbolReference(nodeText(child.childForFieldName("receiver")).replace(/[()]/g, " ").split(/\s+/).at(-1) ?? "") : "";
577
+ const symbolName = receiverType ? `${receiverType}.${name}` : name;
578
+ const exported = exportedByCapitalization(name);
579
+ draftSymbols.push({
580
+ name: symbolName,
581
+ kind: "function",
582
+ signature: singleLineSignature(child.text),
787
583
  exported,
788
584
  callNames: [],
789
585
  extendsNames: [],
790
586
  implementsNames: [],
791
- bodyText: block.text
792
- };
793
- draftSymbols.push(symbol);
794
- symbolByName.set(symbol.name, symbol);
587
+ bodyText: nodeText(child.childForFieldName("body"))
588
+ });
795
589
  if (exported) {
796
- exportLabels.push(symbol.name);
590
+ exportLabels.push(symbolName);
797
591
  }
798
- index = block.endIndex;
592
+ }
593
+ }
594
+ return finalizeCodeAnalysis(manifest, "go", imports, draftSymbols, exportLabels, diagnostics, {
595
+ namespace: packageName
596
+ });
597
+ }
598
+ function rustCodeAnalysis(manifest, rootNode, diagnostics) {
599
+ const imports = [];
600
+ const draftSymbols = [];
601
+ const exportLabels = [];
602
+ const symbolsByName = /* @__PURE__ */ new Map();
603
+ for (const child of rootNode.namedChildren) {
604
+ if (!child) {
799
605
  continue;
800
606
  }
801
- const aliasMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)\s*=/);
802
- if (aliasMatch) {
803
- const exported = rustVisibilityPrefix(trimmed);
804
- const symbol = {
805
- name: aliasMatch[1],
806
- kind: "type_alias",
807
- signature: singleLineSignature(trimmed),
808
- exported,
809
- callNames: [],
810
- extendsNames: [],
811
- implementsNames: [],
812
- bodyText: trimmed
813
- };
814
- draftSymbols.push(symbol);
815
- symbolByName.set(symbol.name, symbol);
816
- if (exported) {
817
- exportLabels.push(symbol.name);
818
- }
607
+ if (child.type === "use_declaration") {
608
+ imports.push(parseRustUse(child.text));
819
609
  continue;
820
610
  }
821
- const variableMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?(?:const|static)\s+([A-Za-z_]\w*)\b/);
822
- if (variableMatch) {
823
- const exported = rustVisibilityPrefix(trimmed);
824
- const symbol = {
825
- name: variableMatch[1],
826
- kind: "variable",
827
- signature: singleLineSignature(trimmed),
828
- exported,
829
- callNames: [],
830
- extendsNames: [],
831
- implementsNames: [],
832
- bodyText: trimmed
833
- };
834
- draftSymbols.push(symbol);
835
- symbolByName.set(symbol.name, symbol);
836
- if (exported) {
837
- exportLabels.push(symbol.name);
611
+ const name = child.type === "function_item" ? extractIdentifier(child.childForFieldName("name")) : extractIdentifier(child.childForFieldName("name"));
612
+ if (child.type === "impl_item") {
613
+ const traitName = normalizeSymbolReference(nodeText(child.childForFieldName("trait")));
614
+ const typeName = normalizeSymbolReference(nodeText(child.childForFieldName("type")));
615
+ const target = symbolsByName.get(typeName);
616
+ if (target && traitName) {
617
+ target.implementsNames.push(traitName);
838
618
  }
839
619
  continue;
840
620
  }
841
- const implMatch = trimmed.match(/^impl(?:<[^>]+>)?\s+(.+?)\s+for\s+([A-Za-z_][\w:<>]*)/);
842
- if (implMatch) {
843
- const traitName = normalizeSymbolReference(implMatch[1]);
844
- const typeName = normalizeSymbolReference(implMatch[2]);
845
- const symbol = symbolByName.get(typeName);
846
- if (symbol && traitName) {
847
- symbol.implementsNames.push(traitName);
848
- }
621
+ if (!name) {
622
+ continue;
849
623
  }
850
- }
851
- for (const rawLine of lines) {
852
- const trimmed = rawLine.trim();
853
- const traitMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?trait\s+([A-Za-z_]\w*)\b/);
854
- if (!traitMatch || symbolByName.has(traitMatch[1])) {
624
+ let kind;
625
+ let extendsNames = [];
626
+ if (child.type === "struct_item") {
627
+ kind = "struct";
628
+ } else if (child.type === "trait_item") {
629
+ kind = "trait";
630
+ extendsNames = parseCommaSeparatedReferences(nodeText(child.childForFieldName("bounds")).replace(/\+/g, ","));
631
+ } else if (child.type === "enum_item") {
632
+ kind = "enum";
633
+ } else if (child.type === "function_item") {
634
+ kind = "function";
635
+ } else if (child.type === "type_item") {
636
+ kind = "type_alias";
637
+ } else if (child.type === "const_item" || child.type === "static_item") {
638
+ kind = "variable";
639
+ }
640
+ if (!kind) {
855
641
  continue;
856
642
  }
857
- const exported = rustVisibilityPrefix(trimmed);
643
+ const exported = child.namedChildren.some((item) => item?.type === "visibility_modifier");
858
644
  const symbol = {
859
- name: traitMatch[1],
860
- kind: "interface",
861
- signature: singleLineSignature(trimmed),
645
+ name,
646
+ kind,
647
+ signature: singleLineSignature(child.text),
862
648
  exported,
863
649
  callNames: [],
864
- extendsNames: [],
650
+ extendsNames,
865
651
  implementsNames: [],
866
- bodyText: trimmed
652
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
867
653
  };
868
654
  draftSymbols.push(symbol);
869
- symbolByName.set(symbol.name, symbol);
655
+ symbolsByName.set(name, symbol);
870
656
  if (exported) {
871
- exportLabels.push(symbol.name);
657
+ exportLabels.push(name);
872
658
  }
873
659
  }
874
660
  return finalizeCodeAnalysis(manifest, "rust", imports, draftSymbols, exportLabels, diagnostics);
875
661
  }
876
- function analyzeJavaImport(statement) {
877
- const cleaned = statement.replace(/^import\s+/, "").replace(/^static\s+/, "").replace(/;$/, "").trim();
878
- const symbolName = normalizeSymbolReference(cleaned.replace(/\.\*$/, ""));
879
- return {
880
- specifier: cleaned.replace(/\.\*$/, ""),
881
- importedSymbols: symbolName ? [symbolName] : [],
882
- isExternal: true,
883
- reExport: false
884
- };
885
- }
886
- function parseJavaImplements(value) {
887
- return (value ?? "").split(",").map((item) => normalizeSymbolReference(item)).filter(Boolean);
888
- }
889
- function analyzeJavaCode(manifest, content) {
890
- const lines = splitLines(content);
662
+ function javaCodeAnalysis(manifest, rootNode, diagnostics) {
891
663
  const imports = [];
892
664
  const draftSymbols = [];
893
665
  const exportLabels = [];
894
- const diagnostics = [];
895
- let depth = 0;
896
- for (let index = 0; index < lines.length; index += 1) {
897
- const rawLine = lines[index] ?? "";
898
- const trimmed = rawLine.trim();
899
- const lineDepth = depth;
900
- if (lineDepth === 0 && trimmed.startsWith("import ")) {
901
- imports.push(analyzeJavaImport(trimmed));
902
- }
903
- if (lineDepth === 0) {
904
- const classMatch = trimmed.match(
905
- /^(public\s+)?(?:abstract\s+|final\s+|sealed\s+|non-sealed\s+)*class\s+([A-Za-z_]\w*)\b(?:\s+extends\s+([A-Za-z_][\w.<>]*))?(?:\s+implements\s+([A-Za-z_][\w.,<>\s]*))?/
666
+ let packageName;
667
+ for (const child of rootNode.namedChildren) {
668
+ if (!child) {
669
+ continue;
670
+ }
671
+ if (child.type === "package_declaration") {
672
+ packageName = child.text.replace(/^package\s+/, "").replace(/;$/, "").trim();
673
+ continue;
674
+ }
675
+ if (child.type === "import_declaration") {
676
+ imports.push(parseJavaImport(child.text));
677
+ continue;
678
+ }
679
+ const name = extractIdentifier(child.childForFieldName("name"));
680
+ if (!name) {
681
+ continue;
682
+ }
683
+ let kind;
684
+ let extendsNames = [];
685
+ let implementsNames = [];
686
+ if (child.type === "class_declaration") {
687
+ kind = "class";
688
+ extendsNames = descendantTypeNames(child.childForFieldName("superclass"));
689
+ implementsNames = descendantTypeNames(child.childForFieldName("interfaces"));
690
+ } else if (child.type === "interface_declaration") {
691
+ kind = "interface";
692
+ extendsNames = descendantTypeNames(
693
+ child.descendantsOfType("extends_interfaces").find((item) => item !== null) ?? null
906
694
  );
907
- if (classMatch) {
908
- const exported = Boolean(classMatch[1]);
909
- const block = collectBracedBlock(lines, index);
910
- draftSymbols.push({
911
- name: classMatch[2],
912
- kind: "class",
913
- signature: singleLineSignature(trimmed),
914
- exported,
915
- callNames: [],
916
- extendsNames: classMatch[3] ? [classMatch[3]] : [],
917
- implementsNames: parseJavaImplements(classMatch[4]),
918
- bodyText: block.text
919
- });
920
- if (exported) {
921
- exportLabels.push(classMatch[2]);
922
- }
923
- index = block.endIndex;
924
- depth = 0;
925
- continue;
695
+ } else if (child.type === "enum_declaration") {
696
+ kind = "enum";
697
+ }
698
+ if (!kind) {
699
+ continue;
700
+ }
701
+ const exported = /\bpublic\b/.test(child.text);
702
+ draftSymbols.push({
703
+ name,
704
+ kind,
705
+ signature: singleLineSignature(child.text),
706
+ exported,
707
+ callNames: [],
708
+ extendsNames,
709
+ implementsNames,
710
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
711
+ });
712
+ if (exported) {
713
+ exportLabels.push(name);
714
+ }
715
+ }
716
+ return finalizeCodeAnalysis(manifest, "java", imports, draftSymbols, exportLabels, diagnostics, {
717
+ namespace: packageName
718
+ });
719
+ }
720
+ function csharpCodeAnalysis(manifest, rootNode, diagnostics) {
721
+ const imports = [];
722
+ const draftSymbols = [];
723
+ const exportLabels = [];
724
+ let namespaceName;
725
+ for (const child of rootNode.namedChildren) {
726
+ if (!child) {
727
+ continue;
728
+ }
729
+ if (child.type === "using_directive") {
730
+ const parsed = parseCSharpUsing(child.text);
731
+ if (parsed) {
732
+ imports.push(parsed);
926
733
  }
927
- const interfaceMatch = trimmed.match(
928
- /^(public\s+)?(?:sealed\s+|non-sealed\s+)?interface\s+([A-Za-z_]\w*)\b(?:\s+extends\s+([A-Za-z_][\w.,<>\s]*))?/
929
- );
930
- if (interfaceMatch) {
931
- const exported = Boolean(interfaceMatch[1]);
932
- const block = collectBracedBlock(lines, index);
933
- draftSymbols.push({
934
- name: interfaceMatch[2],
935
- kind: "interface",
936
- signature: singleLineSignature(trimmed),
937
- exported,
938
- callNames: [],
939
- extendsNames: parseJavaImplements(interfaceMatch[3]),
940
- implementsNames: [],
941
- bodyText: block.text
942
- });
943
- if (exported) {
944
- exportLabels.push(interfaceMatch[2]);
734
+ continue;
735
+ }
736
+ if (child.type === "file_scoped_namespace_declaration" || child.type === "namespace_declaration") {
737
+ namespaceName = nodeText(child.childForFieldName("name")) || namespaceName;
738
+ if (child.type === "namespace_declaration") {
739
+ for (const nested of child.namedChildren) {
740
+ if (nested && nested !== child.childForFieldName("name")) {
741
+ if (["class_declaration", "interface_declaration", "enum_declaration", "struct_declaration", "record_declaration"].includes(
742
+ nested.type
743
+ )) {
744
+ const nestedName = extractIdentifier(nested.childForFieldName("name"));
745
+ if (!nestedName) {
746
+ continue;
747
+ }
748
+ const effectiveBaseList = parseCommaSeparatedReferences(
749
+ nodeText(findNamedChild(nested, "base_list") ?? nested.childForFieldName("base_list")).replace(/^:/, "")
750
+ );
751
+ const kind2 = nested.type === "interface_declaration" ? "interface" : nested.type === "enum_declaration" ? "enum" : nested.type === "struct_declaration" ? "struct" : "class";
752
+ const exported2 = /\b(public|internal|protected)\b/.test(nested.text);
753
+ const extendsNames2 = kind2 === "class" || kind2 === "struct" ? effectiveBaseList.slice(0, 1) : [];
754
+ const implementsNames2 = kind2 === "interface" ? [] : kind2 === "enum" ? [] : effectiveBaseList.slice(kind2 === "class" || kind2 === "struct" ? 1 : 0);
755
+ draftSymbols.push({
756
+ name: nestedName,
757
+ kind: kind2,
758
+ signature: singleLineSignature(nested.text),
759
+ exported: exported2,
760
+ callNames: [],
761
+ extendsNames: extendsNames2,
762
+ implementsNames: implementsNames2,
763
+ bodyText: nodeText(nested.childForFieldName("body")) || nested.text
764
+ });
765
+ if (exported2) {
766
+ exportLabels.push(nestedName);
767
+ }
768
+ }
769
+ }
945
770
  }
946
- index = block.endIndex;
947
- depth = 0;
771
+ }
772
+ if (child.type === "namespace_declaration") {
948
773
  continue;
949
774
  }
950
- const enumMatch = trimmed.match(/^(public\s+)?enum\s+([A-Za-z_]\w*)\b(?:\s+implements\s+([A-Za-z_][\w.,<>\s]*))?/);
951
- if (enumMatch) {
952
- const exported = Boolean(enumMatch[1]);
953
- const block = collectBracedBlock(lines, index);
954
- draftSymbols.push({
955
- name: enumMatch[2],
956
- kind: "enum",
957
- signature: singleLineSignature(trimmed),
958
- exported,
959
- callNames: [],
960
- extendsNames: [],
961
- implementsNames: parseJavaImplements(enumMatch[3]),
962
- bodyText: block.text
963
- });
964
- if (exported) {
965
- exportLabels.push(enumMatch[2]);
966
- }
967
- index = block.endIndex;
968
- depth = 0;
775
+ }
776
+ if (!["class_declaration", "interface_declaration", "enum_declaration", "struct_declaration", "record_declaration"].includes(child.type)) {
777
+ continue;
778
+ }
779
+ const name = extractIdentifier(child.childForFieldName("name"));
780
+ if (!name) {
781
+ continue;
782
+ }
783
+ const baseList = parseCommaSeparatedReferences(
784
+ nodeText(findNamedChild(child, "base_list") ?? child.childForFieldName("base_list")).replace(/^:/, "")
785
+ );
786
+ const kind = child.type === "interface_declaration" ? "interface" : child.type === "enum_declaration" ? "enum" : child.type === "struct_declaration" ? "struct" : "class";
787
+ const exported = /\b(public|internal|protected)\b/.test(child.text);
788
+ const extendsNames = kind === "class" || kind === "struct" ? baseList.slice(0, 1) : [];
789
+ const implementsNames = kind === "interface" ? [] : kind === "enum" ? [] : baseList.slice(kind === "class" || kind === "struct" ? 1 : 0);
790
+ draftSymbols.push({
791
+ name,
792
+ kind,
793
+ signature: singleLineSignature(child.text),
794
+ exported,
795
+ callNames: [],
796
+ extendsNames,
797
+ implementsNames,
798
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
799
+ });
800
+ if (exported) {
801
+ exportLabels.push(name);
802
+ }
803
+ }
804
+ return finalizeCodeAnalysis(manifest, "csharp", imports, draftSymbols, exportLabels, diagnostics, {
805
+ namespace: namespaceName
806
+ });
807
+ }
808
+ function phpCodeAnalysis(manifest, rootNode, diagnostics) {
809
+ const imports = [];
810
+ const draftSymbols = [];
811
+ const exportLabels = [];
812
+ let namespaceName;
813
+ for (const child of rootNode.namedChildren) {
814
+ if (!child || child.type === "php_tag") {
815
+ continue;
816
+ }
817
+ if (child.type === "namespace_definition") {
818
+ namespaceName = nodeText(child.childForFieldName("name")) || namespaceName;
819
+ continue;
820
+ }
821
+ if (child.type === "namespace_use_declaration") {
822
+ imports.push(...parsePhpUse(child.text));
823
+ continue;
824
+ }
825
+ const name = extractIdentifier(child.childForFieldName("name"));
826
+ if (!name) {
827
+ continue;
828
+ }
829
+ let kind;
830
+ let extendsNames = [];
831
+ let implementsNames = [];
832
+ if (child.type === "class_declaration") {
833
+ kind = "class";
834
+ extendsNames = parseCommaSeparatedReferences(
835
+ nodeText(findNamedChild(child, "base_clause") ?? child.childForFieldName("base_clause"))
836
+ );
837
+ implementsNames = parseCommaSeparatedReferences(
838
+ nodeText(findNamedChild(child, "class_interface_clause") ?? child.childForFieldName("class_interface_clause"))
839
+ );
840
+ } else if (child.type === "interface_declaration") {
841
+ kind = "interface";
842
+ extendsNames = parseCommaSeparatedReferences(
843
+ nodeText(findNamedChild(child, "base_clause") ?? child.childForFieldName("base_clause"))
844
+ );
845
+ } else if (child.type === "trait_declaration") {
846
+ kind = "trait";
847
+ } else if (child.type === "enum_declaration") {
848
+ kind = "enum";
849
+ } else if (child.type === "function_definition") {
850
+ kind = "function";
851
+ }
852
+ if (!kind) {
853
+ continue;
854
+ }
855
+ draftSymbols.push({
856
+ name,
857
+ kind,
858
+ signature: singleLineSignature(child.text),
859
+ exported: true,
860
+ callNames: [],
861
+ extendsNames,
862
+ implementsNames,
863
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
864
+ });
865
+ exportLabels.push(name);
866
+ }
867
+ return finalizeCodeAnalysis(manifest, "php", imports, draftSymbols, exportLabels, diagnostics, {
868
+ namespace: namespaceName
869
+ });
870
+ }
871
+ function cFamilyCodeAnalysis(manifest, language, rootNode, diagnostics) {
872
+ const imports = [];
873
+ const draftSymbols = [];
874
+ const exportLabels = [];
875
+ const functionNameFromDeclarator = (node) => {
876
+ if (!node) {
877
+ return void 0;
878
+ }
879
+ const declarator = node.childForFieldName("declarator");
880
+ if (declarator) {
881
+ return functionNameFromDeclarator(declarator);
882
+ }
883
+ return extractIdentifier(node);
884
+ };
885
+ for (const child of rootNode.namedChildren) {
886
+ if (!child) {
887
+ continue;
888
+ }
889
+ if (child.type === "preproc_include") {
890
+ const parsed = parseCppInclude(child.text);
891
+ if (parsed) {
892
+ imports.push(parsed);
893
+ }
894
+ continue;
895
+ }
896
+ if (["class_specifier", "struct_specifier", "enum_specifier"].includes(child.type)) {
897
+ const name = extractIdentifier(child.childForFieldName("name"));
898
+ if (!name) {
969
899
  continue;
970
900
  }
971
- const recordMatch = trimmed.match(
972
- /^(public\s+)?record\s+([A-Za-z_]\w*)\b(?:\s*\([^)]*\))?(?:\s+implements\s+([A-Za-z_][\w.,<>\s]*))?/
973
- );
974
- if (recordMatch) {
975
- const exported = Boolean(recordMatch[1]);
976
- const block = collectBracedBlock(lines, index);
977
- draftSymbols.push({
978
- name: recordMatch[2],
979
- kind: "class",
980
- signature: singleLineSignature(trimmed),
981
- exported,
982
- callNames: [],
983
- extendsNames: [],
984
- implementsNames: parseJavaImplements(recordMatch[3]),
985
- bodyText: block.text
986
- });
987
- if (exported) {
988
- exportLabels.push(recordMatch[2]);
989
- }
990
- index = block.endIndex;
991
- depth = 0;
901
+ const kind = child.type === "enum_specifier" ? "enum" : child.type === "struct_specifier" ? "struct" : "class";
902
+ const baseClassClause = findNamedChild(child, "base_class_clause") ?? child.childForFieldName("base_class_clause");
903
+ const bases = baseClassClause ? uniqueBy(
904
+ baseClassClause.namedChildren.filter((item) => item !== null && item.type !== "access_specifier").map((item) => normalizeSymbolReference(item.text.replace(/\b(public|private|protected|virtual)\b/g, "").trim())).filter(Boolean),
905
+ (item) => item
906
+ ) : [];
907
+ const exported = !/\bstatic\b/.test(child.text);
908
+ draftSymbols.push({
909
+ name,
910
+ kind,
911
+ signature: singleLineSignature(child.text),
912
+ exported,
913
+ callNames: [],
914
+ extendsNames: bases,
915
+ implementsNames: [],
916
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
917
+ });
918
+ if (exported) {
919
+ exportLabels.push(name);
920
+ }
921
+ continue;
922
+ }
923
+ if (child.type === "function_definition") {
924
+ const name = functionNameFromDeclarator(child.childForFieldName("declarator"));
925
+ if (!name) {
992
926
  continue;
993
927
  }
928
+ const exported = !/\bstatic\b/.test(child.text);
929
+ draftSymbols.push({
930
+ name,
931
+ kind: "function",
932
+ signature: singleLineSignature(child.text),
933
+ exported,
934
+ callNames: [],
935
+ extendsNames: [],
936
+ implementsNames: [],
937
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
938
+ });
939
+ if (exported) {
940
+ exportLabels.push(name);
941
+ }
994
942
  }
995
- for (const character of rawLine) {
996
- if (character === "{") {
997
- depth += 1;
998
- } else if (character === "}") {
999
- depth -= 1;
943
+ }
944
+ return finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics);
945
+ }
946
+ async function analyzeTreeSitterCode(manifest, content, language) {
947
+ const module = await getTreeSitterModule();
948
+ await ensureTreeSitterInit(module);
949
+ const parser = new module.Parser();
950
+ parser.setLanguage(await loadLanguage(language));
951
+ const tree = parser.parse(content);
952
+ if (!tree) {
953
+ return finalizeCodeAnalysis(
954
+ manifest,
955
+ language,
956
+ [],
957
+ [],
958
+ [],
959
+ [
960
+ {
961
+ code: 9e3,
962
+ category: "error",
963
+ message: `Failed to parse ${language} source.`,
964
+ line: 1,
965
+ column: 1
966
+ }
967
+ ]
968
+ );
969
+ }
970
+ try {
971
+ const diagnostics = diagnosticsFromTree(tree.rootNode);
972
+ switch (language) {
973
+ case "python":
974
+ return pythonCodeAnalysis(manifest, tree.rootNode, diagnostics);
975
+ case "go":
976
+ return goCodeAnalysis(manifest, tree.rootNode, diagnostics);
977
+ case "rust":
978
+ return rustCodeAnalysis(manifest, tree.rootNode, diagnostics);
979
+ case "java":
980
+ return javaCodeAnalysis(manifest, tree.rootNode, diagnostics);
981
+ case "csharp":
982
+ return csharpCodeAnalysis(manifest, tree.rootNode, diagnostics);
983
+ case "php":
984
+ return phpCodeAnalysis(manifest, tree.rootNode, diagnostics);
985
+ case "c":
986
+ case "cpp":
987
+ return cFamilyCodeAnalysis(manifest, language, tree.rootNode, diagnostics);
988
+ }
989
+ } finally {
990
+ tree.delete();
991
+ }
992
+ }
993
+
994
+ // src/code-analysis.ts
995
+ function scriptKindFor(language) {
996
+ switch (language) {
997
+ case "typescript":
998
+ return ts.ScriptKind.TS;
999
+ case "tsx":
1000
+ return ts.ScriptKind.TSX;
1001
+ case "jsx":
1002
+ return ts.ScriptKind.JSX;
1003
+ default:
1004
+ return ts.ScriptKind.JS;
1005
+ }
1006
+ }
1007
+ function isRelativeSpecifier(specifier) {
1008
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith(".");
1009
+ }
1010
+ function isLocalIncludeSpecifier(specifier) {
1011
+ return specifier.startsWith(".") || specifier.startsWith("/") || specifier.includes("/");
1012
+ }
1013
+ function formatDiagnosticCategory(category) {
1014
+ switch (category) {
1015
+ case ts.DiagnosticCategory.Error:
1016
+ return "error";
1017
+ case ts.DiagnosticCategory.Warning:
1018
+ return "warning";
1019
+ case ts.DiagnosticCategory.Suggestion:
1020
+ return "suggestion";
1021
+ default:
1022
+ return "message";
1023
+ }
1024
+ }
1025
+ function declarationSignature(node, sourceFile) {
1026
+ const sourceText = sourceFile.getFullText();
1027
+ if (ts.isFunctionDeclaration(node) && node.body) {
1028
+ return truncate(normalizeWhitespace(sourceText.slice(node.getStart(sourceFile), node.body.getStart(sourceFile)).trim()), 180);
1029
+ }
1030
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)) {
1031
+ const membersPos = node.members.pos;
1032
+ return truncate(
1033
+ normalizeWhitespace(
1034
+ sourceText.slice(node.getStart(sourceFile), membersPos).replace(/\{\s*$/, "").trim()
1035
+ ),
1036
+ 180
1037
+ );
1038
+ }
1039
+ return truncate(normalizeWhitespace(node.getText(sourceFile)), 180);
1040
+ }
1041
+ function importSpecifierText(specifier) {
1042
+ return specifier.propertyName ? `${specifier.propertyName.text} as ${specifier.name.text}` : specifier.name.text;
1043
+ }
1044
+ function exportSpecifierText(specifier) {
1045
+ return specifier.propertyName ? `${specifier.propertyName.text} as ${specifier.name.text}` : specifier.name.text;
1046
+ }
1047
+ function collectCallNames(root, availableNames, selfName) {
1048
+ if (!root) {
1049
+ return [];
1050
+ }
1051
+ const names = [];
1052
+ const visit = (node) => {
1053
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && availableNames.has(node.expression.text)) {
1054
+ if (node.expression.text !== selfName) {
1055
+ names.push(node.expression.text);
1000
1056
  }
1001
1057
  }
1058
+ ts.forEachChild(node, visit);
1059
+ };
1060
+ visit(root);
1061
+ return uniqueBy(names, (name) => name);
1062
+ }
1063
+ function collectCallNamesFromText2(text, availableNames, selfName) {
1064
+ if (!text) {
1065
+ return [];
1066
+ }
1067
+ const names = [];
1068
+ for (const name of availableNames) {
1069
+ if (name === selfName) {
1070
+ continue;
1071
+ }
1072
+ const pattern = new RegExp(`\\b${escapeRegExp2(name)}\\s*\\(`, "g");
1073
+ if (pattern.test(text)) {
1074
+ names.push(name);
1075
+ }
1076
+ }
1077
+ return uniqueBy(names, (name) => name);
1078
+ }
1079
+ function heritageNames(clauses, token) {
1080
+ return uniqueBy(
1081
+ (clauses ?? []).filter((clause) => clause.token === token).flatMap(
1082
+ (clause) => clause.types.map((typeNode) => {
1083
+ if (ts.isIdentifier(typeNode.expression)) {
1084
+ return typeNode.expression.text;
1085
+ }
1086
+ if (ts.isPropertyAccessExpression(typeNode.expression)) {
1087
+ return typeNode.expression.getText();
1088
+ }
1089
+ return typeNode.getText();
1090
+ })
1091
+ ),
1092
+ (name) => name
1093
+ );
1094
+ }
1095
+ function isNodeExported(node) {
1096
+ return Boolean(
1097
+ ts.canHaveModifiers(node) && ts.getModifiers(node)?.some(
1098
+ (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword
1099
+ )
1100
+ );
1101
+ }
1102
+ function makeSymbolId2(sourceId, name, seen) {
1103
+ const base = slugify(name);
1104
+ const count = (seen.get(base) ?? 0) + 1;
1105
+ seen.set(base, count);
1106
+ return `symbol:${sourceId}:${count === 1 ? base : `${base}-${count}`}`;
1107
+ }
1108
+ function summarizeModule(manifest, code) {
1109
+ const localImports = code.imports.filter((item) => !item.isExternal && !item.reExport).length;
1110
+ const externalImports = code.imports.filter((item) => item.isExternal).length;
1111
+ const exportedCount = code.symbols.filter((symbol) => symbol.exported).length;
1112
+ const parts = [`${code.language} module`, `defining ${code.symbols.length} top-level symbol(s)`, `exporting ${exportedCount} symbol(s)`];
1113
+ if (localImports > 0) {
1114
+ parts.push(`importing ${localImports} local module(s)`);
1115
+ }
1116
+ if (externalImports > 0) {
1117
+ parts.push(`depending on ${externalImports} external package import(s)`);
1118
+ }
1119
+ if (code.diagnostics.length > 0) {
1120
+ parts.push(`with ${code.diagnostics.length} parser diagnostic(s)`);
1121
+ }
1122
+ return `${manifest.title} is a ${parts.join(", ")}.`;
1123
+ }
1124
+ function codeClaims(manifest, code) {
1125
+ const claims = [];
1126
+ if (code.exports.length > 0) {
1127
+ claims.push({
1128
+ text: `${manifest.title} exports ${code.exports.slice(0, 4).join(", ")}${code.exports.length > 4 ? ", and more" : ""}.`,
1129
+ confidence: 1,
1130
+ status: "extracted",
1131
+ polarity: "neutral",
1132
+ citation: manifest.sourceId
1133
+ });
1134
+ }
1135
+ if (code.symbols.length > 0) {
1136
+ claims.push({
1137
+ text: `${manifest.title} defines ${code.symbols.slice(0, 5).map((symbol) => symbol.name).join(", ")}${code.symbols.length > 5 ? ", and more" : ""}.`,
1138
+ confidence: 1,
1139
+ status: "extracted",
1140
+ polarity: "neutral",
1141
+ citation: manifest.sourceId
1142
+ });
1143
+ }
1144
+ if (code.imports.length > 0) {
1145
+ claims.push({
1146
+ text: `${manifest.title} imports ${code.imports.slice(0, 4).map((item) => item.specifier).join(", ")}${code.imports.length > 4 ? ", and more" : ""}.`,
1147
+ confidence: 1,
1148
+ status: "extracted",
1149
+ polarity: "neutral",
1150
+ citation: manifest.sourceId
1151
+ });
1152
+ }
1153
+ if (code.diagnostics.length > 0) {
1154
+ claims.push({
1155
+ text: `${manifest.title} has ${code.diagnostics.length} parser diagnostic(s) that should be reviewed before trusting the module summary.`,
1156
+ confidence: 1,
1157
+ status: "extracted",
1158
+ polarity: "negative",
1159
+ citation: manifest.sourceId
1160
+ });
1161
+ }
1162
+ return claims.slice(0, 4).map((claim, index) => ({
1163
+ id: `claim:${manifest.sourceId}:${index + 1}`,
1164
+ ...claim
1165
+ }));
1166
+ }
1167
+ function codeQuestions(manifest, code) {
1168
+ const questions = [
1169
+ code.exports.length > 0 ? `Which downstream pages should explain how ${manifest.title} exports are consumed?` : "",
1170
+ code.imports.some((item) => !item.isExternal) ? `How does ${manifest.title} coordinate with its imported local modules?` : "",
1171
+ code.dependencies[0] ? `Why does ${manifest.title} depend on ${code.dependencies[0]}?` : "",
1172
+ `What broader responsibility does ${manifest.title} serve in the codebase?`
1173
+ ].filter(Boolean);
1174
+ return uniqueBy(questions, (question) => question).slice(0, 4);
1175
+ }
1176
+ function resolveVariableKind(statement) {
1177
+ return statement.declarationList.flags & ts.NodeFlags.Const ? "variable" : "variable";
1178
+ }
1179
+ function normalizeSymbolReference2(value) {
1180
+ const withoutGenerics = value.replace(/<[^>]*>/g, "");
1181
+ const withoutDecorators = withoutGenerics.replace(/['"&*()[\]{}]/g, " ");
1182
+ const trimmed = withoutDecorators.trim();
1183
+ const lastSegment = trimmed.split(/::|\./).filter(Boolean).at(-1) ?? trimmed;
1184
+ return lastSegment.replace(/[,:;]+$/g, "").trim();
1185
+ }
1186
+ function stripCodeExtension2(filePath) {
1187
+ return filePath.replace(/\.(?:[cm]?jsx?|tsx?|mts|cts|py|go|rs|java|cs|php|c|cc|cpp|cxx|h|hh|hpp|hxx)$/i, "");
1188
+ }
1189
+ function manifestModuleName2(manifest, language) {
1190
+ const repoPath = manifest.repoRelativePath ?? path3.basename(manifest.originalPath ?? manifest.storedPath);
1191
+ const normalized = toPosix(stripCodeExtension2(repoPath)).replace(/^\.\/+/, "");
1192
+ if (!normalized) {
1193
+ return void 0;
1194
+ }
1195
+ if (language === "python") {
1196
+ const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
1197
+ return dotted || path3.posix.basename(normalized);
1198
+ }
1199
+ return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path3.posix.basename(normalized) : normalized;
1200
+ }
1201
+ function finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
1202
+ const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
1203
+ for (const symbol of draftSymbols) {
1204
+ if (symbol.callNames.length === 0 && symbol.bodyText) {
1205
+ symbol.callNames = collectCallNamesFromText2(symbol.bodyText, topLevelNames, symbol.name);
1206
+ }
1002
1207
  }
1003
- return finalizeCodeAnalysis(manifest, "java", imports, draftSymbols, exportLabels, diagnostics);
1208
+ const seenSymbolIds = /* @__PURE__ */ new Map();
1209
+ const symbols = draftSymbols.map((symbol) => ({
1210
+ id: makeSymbolId2(manifest.sourceId, symbol.name, seenSymbolIds),
1211
+ name: symbol.name,
1212
+ kind: symbol.kind,
1213
+ signature: symbol.signature,
1214
+ exported: symbol.exported,
1215
+ calls: uniqueBy(symbol.callNames, (name) => name),
1216
+ extends: uniqueBy(symbol.extendsNames.map((name) => normalizeSymbolReference2(name)).filter(Boolean), (name) => name),
1217
+ implements: uniqueBy(symbol.implementsNames.map((name) => normalizeSymbolReference2(name)).filter(Boolean), (name) => name)
1218
+ }));
1219
+ return {
1220
+ moduleId: `module:${manifest.sourceId}`,
1221
+ language,
1222
+ moduleName: metadata?.moduleName ?? manifestModuleName2(manifest, language),
1223
+ namespace: metadata?.namespace,
1224
+ imports,
1225
+ dependencies: uniqueBy(
1226
+ imports.filter((item) => item.isExternal).map((item) => item.specifier),
1227
+ (specifier) => specifier
1228
+ ),
1229
+ symbols,
1230
+ exports: uniqueBy([...symbols.filter((symbol) => symbol.exported).map((symbol) => symbol.name), ...exportLabels], (label) => label),
1231
+ diagnostics
1232
+ };
1004
1233
  }
1005
1234
  function analyzeTypeScriptLikeCode(manifest, content) {
1006
1235
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
@@ -1214,10 +1443,10 @@ function analyzeTypeScriptLikeCode(manifest, content) {
1214
1443
  column: (position?.character ?? 0) + 1
1215
1444
  };
1216
1445
  });
1217
- return finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics);
1446
+ return finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics);
1218
1447
  }
1219
1448
  function inferCodeLanguage(filePath, mimeType = "") {
1220
- const extension = path2.extname(filePath).toLowerCase();
1449
+ const extension = path3.extname(filePath).toLowerCase();
1221
1450
  if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
1222
1451
  return "typescript";
1223
1452
  }
@@ -1242,78 +1471,344 @@ function inferCodeLanguage(filePath, mimeType = "") {
1242
1471
  if (extension === ".java") {
1243
1472
  return "java";
1244
1473
  }
1474
+ if (extension === ".cs") {
1475
+ return "csharp";
1476
+ }
1477
+ if (extension === ".php") {
1478
+ return "php";
1479
+ }
1480
+ if (extension === ".c") {
1481
+ return "c";
1482
+ }
1483
+ if ([".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"].includes(extension)) {
1484
+ return "cpp";
1485
+ }
1245
1486
  return void 0;
1246
1487
  }
1247
1488
  function modulePageTitle(manifest) {
1248
1489
  return `${manifest.title} module`;
1249
1490
  }
1250
1491
  function importResolutionCandidates(basePath, specifier, extensions) {
1251
- const resolved = path2.resolve(path2.dirname(basePath), specifier);
1252
- if (path2.extname(resolved)) {
1253
- return [path2.normalize(resolved)];
1492
+ const resolved = path3.posix.normalize(path3.posix.join(path3.posix.dirname(basePath), specifier));
1493
+ if (path3.posix.extname(resolved)) {
1494
+ return [resolved];
1254
1495
  }
1255
- const direct = extensions.map((extension) => path2.normalize(`${resolved}${extension}`));
1256
- const indexFiles = extensions.map((extension) => path2.normalize(path2.join(resolved, `index${extension}`)));
1257
- return uniqueBy([path2.normalize(resolved), ...direct, ...indexFiles], (candidate) => candidate);
1496
+ const direct = extensions.map((extension) => path3.posix.normalize(`${resolved}${extension}`));
1497
+ const indexFiles = extensions.map((extension) => path3.posix.normalize(path3.posix.join(resolved, `index${extension}`)));
1498
+ return uniqueBy([resolved, ...direct, ...indexFiles], (candidate) => candidate);
1258
1499
  }
1259
- function resolveJsLikeImportSourceId(manifest, specifier, manifests) {
1260
- if (manifest.originType !== "file" || !manifest.originalPath || !isRelativeSpecifier(specifier)) {
1261
- return void 0;
1500
+ function normalizeAlias(value) {
1501
+ return value.replace(/\\/g, "/").replace(/\/+/g, "/").trim();
1502
+ }
1503
+ function recordAlias(target, value) {
1504
+ const normalized = normalizeAlias(value ?? "");
1505
+ if (!normalized) {
1506
+ return;
1507
+ }
1508
+ target.add(normalized);
1509
+ const lowered = normalized.toLowerCase();
1510
+ if (lowered !== normalized) {
1511
+ target.add(lowered);
1262
1512
  }
1263
- const candidates = new Set(
1264
- importResolutionCandidates(manifest.originalPath, specifier, [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"])
1265
- );
1266
- return manifests.find(
1267
- (candidate) => candidate.sourceKind === "code" && candidate.originalPath && candidates.has(path2.normalize(candidate.originalPath))
1268
- )?.sourceId;
1269
1513
  }
1270
- function resolvePythonImportSourceId(manifest, specifier, manifests) {
1271
- if (manifest.originType !== "file" || !manifest.originalPath) {
1272
- return void 0;
1514
+ function manifestBasenameWithoutExtension(manifest) {
1515
+ const target = manifest.repoRelativePath ?? manifest.originalPath ?? manifest.storedPath;
1516
+ return path3.posix.basename(stripCodeExtension2(normalizeAlias(target)));
1517
+ }
1518
+ async function readNearestGoModulePath(startPath, cache) {
1519
+ let current = path3.resolve(startPath);
1520
+ try {
1521
+ const stat = await fs3.stat(current);
1522
+ if (!stat.isDirectory()) {
1523
+ current = path3.dirname(current);
1524
+ }
1525
+ } catch {
1526
+ current = path3.dirname(current);
1273
1527
  }
1274
- if (specifier.startsWith(".")) {
1275
- const dotMatch = specifier.match(/^\.+/);
1276
- const depth = dotMatch ? dotMatch[0].length : 0;
1277
- const relativeModule = specifier.slice(depth).replace(/\./g, "/");
1278
- const baseDir = path2.dirname(manifest.originalPath);
1279
- const parentDir = path2.resolve(baseDir, ...Array(Math.max(depth - 1, 0)).fill(".."));
1280
- const moduleBase = relativeModule ? path2.join(parentDir, relativeModule) : parentDir;
1281
- const candidates = /* @__PURE__ */ new Set([path2.normalize(`${moduleBase}.py`), path2.normalize(path2.join(moduleBase, "__init__.py"))]);
1282
- return manifests.find(
1283
- (candidate) => candidate.sourceKind === "code" && candidate.originalPath && candidates.has(path2.normalize(candidate.originalPath))
1284
- )?.sourceId;
1285
- }
1286
- const modulePath = specifier.replace(/\./g, "/");
1287
- const suffixes = [`/${modulePath}.py`, `/${modulePath}/__init__.py`];
1288
- return manifests.find((candidate) => {
1289
- if (candidate.sourceKind !== "code" || !candidate.originalPath) {
1290
- return false;
1528
+ while (true) {
1529
+ if (cache.has(current)) {
1530
+ const cached = cache.get(current);
1531
+ return cached === null ? void 0 : cached;
1532
+ }
1533
+ const goModPath = path3.join(current, "go.mod");
1534
+ if (await fs3.access(goModPath).then(() => true).catch(() => false)) {
1535
+ const content = await fs3.readFile(goModPath, "utf8");
1536
+ const match = content.match(/^\s*module\s+(\S+)/m);
1537
+ const modulePath = match?.[1]?.trim() ?? null;
1538
+ cache.set(current, modulePath);
1539
+ return modulePath ?? void 0;
1540
+ }
1541
+ const parent = path3.dirname(current);
1542
+ if (parent === current) {
1543
+ cache.set(current, null);
1544
+ return void 0;
1545
+ }
1546
+ current = parent;
1547
+ }
1548
+ }
1549
+ function rustModuleAlias(repoRelativePath) {
1550
+ const withoutExt = stripCodeExtension2(normalizeAlias(repoRelativePath));
1551
+ const trimmed = withoutExt.replace(/^src\//, "").replace(/\/mod$/i, "");
1552
+ if (!trimmed || trimmed === "lib" || trimmed === "main") {
1553
+ return "crate";
1554
+ }
1555
+ return `crate::${trimmed.replace(/\//g, "::")}`;
1556
+ }
1557
+ function candidateExtensionsFor(language) {
1558
+ switch (language) {
1559
+ case "javascript":
1560
+ case "jsx":
1561
+ case "typescript":
1562
+ case "tsx":
1563
+ return [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"];
1564
+ case "python":
1565
+ return [".py"];
1566
+ case "go":
1567
+ return [".go"];
1568
+ case "rust":
1569
+ return [".rs"];
1570
+ case "java":
1571
+ return [".java"];
1572
+ case "csharp":
1573
+ return [".cs"];
1574
+ case "php":
1575
+ return [".php"];
1576
+ case "c":
1577
+ return [".c", ".h"];
1578
+ case "cpp":
1579
+ return [".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"];
1580
+ }
1581
+ }
1582
+ async function buildCodeIndex(rootDir, manifests, analyses) {
1583
+ const analysesBySourceId = new Map(analyses.map((analysis) => [analysis.sourceId, analysis]));
1584
+ const goModuleCache = /* @__PURE__ */ new Map();
1585
+ const entries = [];
1586
+ for (const manifest of manifests) {
1587
+ const analysis = analysesBySourceId.get(manifest.sourceId);
1588
+ if (!analysis?.code) {
1589
+ continue;
1590
+ }
1591
+ const aliases = /* @__PURE__ */ new Set();
1592
+ const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : void 0;
1593
+ const normalizedModuleName = analysis.code.moduleName ? normalizeAlias(analysis.code.moduleName) : void 0;
1594
+ const normalizedNamespace = analysis.code.namespace ? normalizeAlias(analysis.code.namespace) : void 0;
1595
+ const basename = manifestBasenameWithoutExtension(manifest);
1596
+ if (repoRelativePath) {
1597
+ recordAlias(aliases, repoRelativePath);
1598
+ recordAlias(aliases, stripCodeExtension2(repoRelativePath));
1599
+ if (stripCodeExtension2(repoRelativePath).endsWith("/index")) {
1600
+ recordAlias(aliases, stripCodeExtension2(repoRelativePath).slice(0, -"/index".length));
1601
+ }
1602
+ }
1603
+ recordAlias(aliases, normalizedModuleName);
1604
+ recordAlias(aliases, normalizedNamespace);
1605
+ switch (analysis.code.language) {
1606
+ case "python":
1607
+ recordAlias(aliases, normalizedModuleName?.replace(/\//g, "."));
1608
+ break;
1609
+ case "rust":
1610
+ if (repoRelativePath) {
1611
+ recordAlias(aliases, rustModuleAlias(repoRelativePath));
1612
+ }
1613
+ break;
1614
+ case "go": {
1615
+ if (normalizedNamespace) {
1616
+ recordAlias(aliases, normalizedNamespace);
1617
+ }
1618
+ const originalPath = manifest.originalPath ? path3.resolve(manifest.originalPath) : path3.resolve(rootDir, manifest.storedPath);
1619
+ const goModulePath = await readNearestGoModulePath(originalPath, goModuleCache);
1620
+ if (goModulePath && repoRelativePath) {
1621
+ const dir = path3.posix.dirname(repoRelativePath);
1622
+ const packageAlias = dir === "." ? goModulePath : `${goModulePath}/${dir}`;
1623
+ recordAlias(aliases, packageAlias);
1624
+ }
1625
+ break;
1626
+ }
1627
+ case "java":
1628
+ case "csharp":
1629
+ if (normalizedNamespace) {
1630
+ recordAlias(aliases, `${normalizedNamespace}.${basename}`);
1631
+ }
1632
+ break;
1633
+ case "php":
1634
+ if (normalizedNamespace) {
1635
+ recordAlias(aliases, `${normalizedNamespace}\\${basename}`);
1636
+ }
1637
+ break;
1638
+ default:
1639
+ break;
1640
+ }
1641
+ entries.push({
1642
+ sourceId: manifest.sourceId,
1643
+ moduleId: analysis.code.moduleId,
1644
+ language: analysis.code.language,
1645
+ repoRelativePath,
1646
+ originalPath: manifest.originalPath,
1647
+ moduleName: analysis.code.moduleName,
1648
+ namespace: analysis.code.namespace,
1649
+ aliases: [...aliases].sort((left, right) => left.localeCompare(right))
1650
+ });
1651
+ }
1652
+ return {
1653
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1654
+ entries
1655
+ };
1656
+ }
1657
+ function createCodeIndexLookup(artifact) {
1658
+ const bySourceId = /* @__PURE__ */ new Map();
1659
+ const byRepoPath = /* @__PURE__ */ new Map();
1660
+ const byAlias = /* @__PURE__ */ new Map();
1661
+ for (const entry of artifact.entries) {
1662
+ bySourceId.set(entry.sourceId, entry);
1663
+ if (entry.repoRelativePath) {
1664
+ byRepoPath.set(normalizeAlias(entry.repoRelativePath), entry);
1665
+ }
1666
+ for (const alias of entry.aliases) {
1667
+ const key = normalizeAlias(alias);
1668
+ const bucket = byAlias.get(key) ?? [];
1669
+ bucket.push(entry);
1670
+ byAlias.set(key, bucket);
1291
1671
  }
1292
- const normalizedOriginalPath = path2.normalize(candidate.originalPath);
1293
- return suffixes.some((suffix) => normalizedOriginalPath.endsWith(path2.normalize(suffix)));
1294
- })?.sourceId;
1672
+ }
1673
+ return { artifact, bySourceId, byRepoPath, byAlias };
1674
+ }
1675
+ function aliasMatches(lookup, ...aliases) {
1676
+ return uniqueBy(
1677
+ aliases.flatMap(
1678
+ (alias) => alias ? lookup.byAlias.get(normalizeAlias(alias)) ?? lookup.byAlias.get(normalizeAlias(alias).toLowerCase()) ?? [] : []
1679
+ ),
1680
+ (entry) => entry.sourceId
1681
+ );
1682
+ }
1683
+ function repoPathMatches(lookup, ...repoPaths) {
1684
+ return uniqueBy(
1685
+ repoPaths.map((repoPath) => lookup.byRepoPath.get(normalizeAlias(repoPath))).filter((entry) => Boolean(entry)),
1686
+ (entry) => entry.sourceId
1687
+ );
1295
1688
  }
1296
- function resolveCodeImportSourceId(manifest, specifier, manifests) {
1689
+ function resolvePythonRelativeAliases(repoRelativePath, specifier) {
1690
+ const dotMatch = specifier.match(/^\.+/);
1691
+ const depth = dotMatch ? dotMatch[0].length : 0;
1692
+ const relativeModule = specifier.slice(depth).replace(/\./g, "/");
1693
+ const baseDir = path3.posix.dirname(repoRelativePath);
1694
+ const parentDir = path3.posix.normalize(path3.posix.join(baseDir, ...Array(Math.max(depth - 1, 0)).fill("..")));
1695
+ const moduleBase = relativeModule ? path3.posix.join(parentDir, relativeModule) : parentDir;
1696
+ return uniqueBy([`${moduleBase}.py`, path3.posix.join(moduleBase, "__init__.py")], (item) => item);
1697
+ }
1698
+ function resolveRustAliases(manifest, specifier) {
1699
+ const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : "";
1700
+ const currentAlias = repoRelativePath ? rustModuleAlias(repoRelativePath) : void 0;
1701
+ if (!specifier.startsWith("self::") && !specifier.startsWith("super::")) {
1702
+ return [specifier];
1703
+ }
1704
+ if (!currentAlias) {
1705
+ return [];
1706
+ }
1707
+ const currentParts = currentAlias.replace(/^crate::?/, "").split("::").filter(Boolean);
1708
+ if (specifier.startsWith("self::")) {
1709
+ return [`crate${currentParts.length ? `::${currentParts.join("::")}` : ""}::${specifier.slice("self::".length)}`];
1710
+ }
1711
+ return [
1712
+ `crate${currentParts.length > 1 ? `::${currentParts.slice(0, -1).join("::")}` : ""}::${specifier.slice("super::".length)}`.replace(/::+/g, "::").replace(/::$/, "")
1713
+ ];
1714
+ }
1715
+ function findImportCandidates(manifest, codeImport, lookup) {
1297
1716
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType);
1717
+ const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : void 0;
1718
+ if (!language) {
1719
+ return [];
1720
+ }
1298
1721
  switch (language) {
1299
1722
  case "javascript":
1300
1723
  case "jsx":
1301
1724
  case "typescript":
1302
1725
  case "tsx":
1303
- return resolveJsLikeImportSourceId(manifest, specifier, manifests);
1726
+ return repoRelativePath && isRelativeSpecifier(codeImport.specifier) ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
1304
1727
  case "python":
1305
- return resolvePythonImportSourceId(manifest, specifier, manifests);
1728
+ if (repoRelativePath && codeImport.specifier.startsWith(".")) {
1729
+ return repoPathMatches(lookup, ...resolvePythonRelativeAliases(repoRelativePath, codeImport.specifier));
1730
+ }
1731
+ return aliasMatches(lookup, codeImport.specifier);
1732
+ case "go":
1733
+ case "java":
1734
+ case "csharp":
1735
+ return aliasMatches(lookup, codeImport.specifier);
1736
+ case "php":
1737
+ if (repoRelativePath && isLocalIncludeSpecifier(codeImport.specifier)) {
1738
+ return repoPathMatches(
1739
+ lookup,
1740
+ ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))
1741
+ );
1742
+ }
1743
+ return aliasMatches(lookup, codeImport.specifier, codeImport.specifier.replace(/\\/g, "/"));
1744
+ case "rust":
1745
+ return aliasMatches(lookup, codeImport.specifier, ...resolveRustAliases(manifest, codeImport.specifier));
1746
+ case "c":
1747
+ case "cpp":
1748
+ return repoRelativePath && !codeImport.isExternal ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
1306
1749
  default:
1307
- return void 0;
1750
+ return [];
1308
1751
  }
1309
1752
  }
1310
- function escapeRegExp(value) {
1753
+ function importLooksLocal(manifest, codeImport, candidates) {
1754
+ if (candidates.length > 0) {
1755
+ return true;
1756
+ }
1757
+ const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType);
1758
+ switch (language) {
1759
+ case "javascript":
1760
+ case "jsx":
1761
+ case "typescript":
1762
+ case "tsx":
1763
+ return isRelativeSpecifier(codeImport.specifier);
1764
+ case "python":
1765
+ return codeImport.specifier.startsWith(".");
1766
+ case "rust":
1767
+ return /^(crate|self|super)::/.test(codeImport.specifier);
1768
+ case "php":
1769
+ case "c":
1770
+ case "cpp":
1771
+ return !codeImport.isExternal;
1772
+ default:
1773
+ return false;
1774
+ }
1775
+ }
1776
+ function resolveCodeImport(manifest, codeImport, lookup) {
1777
+ const candidates = findImportCandidates(manifest, codeImport, lookup);
1778
+ const resolved = candidates.length === 1 ? candidates[0] : void 0;
1779
+ return {
1780
+ ...codeImport,
1781
+ isExternal: importLooksLocal(manifest, codeImport, candidates) ? false : codeImport.isExternal,
1782
+ resolvedSourceId: resolved?.sourceId,
1783
+ resolvedRepoPath: resolved?.repoRelativePath
1784
+ };
1785
+ }
1786
+ function enrichResolvedCodeImports(manifest, analysis, artifact) {
1787
+ if (!analysis.code) {
1788
+ return analysis;
1789
+ }
1790
+ const lookup = createCodeIndexLookup(artifact);
1791
+ const imports = analysis.code.imports.map((codeImport) => resolveCodeImport(manifest, codeImport, lookup));
1792
+ return {
1793
+ ...analysis,
1794
+ code: {
1795
+ ...analysis.code,
1796
+ imports,
1797
+ dependencies: uniqueBy(
1798
+ imports.filter((item) => item.isExternal).map((item) => item.specifier),
1799
+ (specifier) => specifier
1800
+ )
1801
+ }
1802
+ };
1803
+ }
1804
+ function escapeRegExp2(value) {
1311
1805
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1312
1806
  }
1313
- function analyzeCodeSource(manifest, extractedText, schemaHash) {
1807
+ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
1314
1808
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
1315
- const code = language === "python" ? analyzePythonCode(manifest, extractedText) : language === "go" ? analyzeGoCode(manifest, extractedText) : language === "rust" ? analyzeRustCode(manifest, extractedText) : language === "java" ? analyzeJavaCode(manifest, extractedText) : analyzeTypeScriptLikeCode(manifest, extractedText);
1809
+ const code = language === "javascript" || language === "jsx" || language === "typescript" || language === "tsx" ? analyzeTypeScriptLikeCode(manifest, extractedText) : await analyzeTreeSitterCode(manifest, extractedText, language);
1316
1810
  return {
1811
+ analysisVersion: 3,
1317
1812
  sourceId: manifest.sourceId,
1318
1813
  sourceHash: manifest.contentHash,
1319
1814
  schemaHash,
@@ -1329,18 +1824,18 @@ function analyzeCodeSource(manifest, extractedText, schemaHash) {
1329
1824
  }
1330
1825
 
1331
1826
  // src/logs.ts
1332
- import fs2 from "fs/promises";
1333
- import path3 from "path";
1827
+ import fs4 from "fs/promises";
1828
+ import path4 from "path";
1334
1829
  import matter from "gray-matter";
1335
1830
  async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
1336
1831
  const { paths } = await initWorkspace(rootDir);
1337
1832
  await ensureDir(paths.sessionsDir);
1338
1833
  const timestamp = startedAt.replace(/[:.]/g, "-");
1339
1834
  const baseName = `${timestamp}-${operation}-${slugify(title)}`;
1340
- let candidate = path3.join(paths.sessionsDir, `${baseName}.md`);
1835
+ let candidate = path4.join(paths.sessionsDir, `${baseName}.md`);
1341
1836
  let counter = 2;
1342
1837
  while (await fileExists(candidate)) {
1343
- candidate = path3.join(paths.sessionsDir, `${baseName}-${counter}.md`);
1838
+ candidate = path4.join(paths.sessionsDir, `${baseName}-${counter}.md`);
1344
1839
  counter++;
1345
1840
  }
1346
1841
  return candidate;
@@ -1348,11 +1843,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
1348
1843
  async function appendLogEntry(rootDir, action, title, lines = []) {
1349
1844
  const { paths } = await initWorkspace(rootDir);
1350
1845
  await ensureDir(paths.wikiDir);
1351
- const logPath = path3.join(paths.wikiDir, "log.md");
1846
+ const logPath = path4.join(paths.wikiDir, "log.md");
1352
1847
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1353
1848
  const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
1354
- const existing = await fileExists(logPath) ? await fs2.readFile(logPath, "utf8") : "# Log\n\n";
1355
- await fs2.writeFile(logPath, `${existing}${entry}
1849
+ const existing = await fileExists(logPath) ? await fs4.readFile(logPath, "utf8") : "# Log\n\n";
1850
+ await fs4.writeFile(logPath, `${existing}${entry}
1356
1851
  `, "utf8");
1357
1852
  }
1358
1853
  async function recordSession(rootDir, input) {
@@ -1362,8 +1857,8 @@ async function recordSession(rootDir, input) {
1362
1857
  const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
1363
1858
  const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
1364
1859
  const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
1365
- const sessionId = path3.basename(sessionPath, ".md");
1366
- const relativeSessionPath = path3.relative(rootDir, sessionPath).split(path3.sep).join(path3.posix.sep);
1860
+ const sessionId = path4.basename(sessionPath, ".md");
1861
+ const relativeSessionPath = path4.relative(rootDir, sessionPath).split(path4.sep).join(path4.posix.sep);
1367
1862
  const frontmatter = Object.fromEntries(
1368
1863
  Object.entries({
1369
1864
  session_id: sessionId,
@@ -1411,7 +1906,7 @@ async function recordSession(rootDir, input) {
1411
1906
  frontmatter
1412
1907
  );
1413
1908
  await writeFileIfChanged(sessionPath, content);
1414
- const logPath = path3.join(paths.wikiDir, "log.md");
1909
+ const logPath = path4.join(paths.wikiDir, "log.md");
1415
1910
  const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
1416
1911
  const entry = [
1417
1912
  `## [${timestamp}] ${input.operation} | ${input.title}`,
@@ -1419,8 +1914,8 @@ async function recordSession(rootDir, input) {
1419
1914
  ...(input.lines ?? []).map((line) => `- ${line}`),
1420
1915
  ""
1421
1916
  ].join("\n");
1422
- const existing = await fileExists(logPath) ? await fs2.readFile(logPath, "utf8") : "# Log\n\n";
1423
- await fs2.writeFile(logPath, `${existing}${entry}
1917
+ const existing = await fileExists(logPath) ? await fs4.readFile(logPath, "utf8") : "# Log\n\n";
1918
+ await fs4.writeFile(logPath, `${existing}${entry}
1424
1919
  `, "utf8");
1425
1920
  return { sessionPath, sessionId };
1426
1921
  }
@@ -1431,6 +1926,8 @@ async function appendWatchRun(rootDir, run) {
1431
1926
 
1432
1927
  // src/ingest.ts
1433
1928
  var DEFAULT_MAX_ASSET_SIZE = 10 * 1024 * 1024;
1929
+ var DEFAULT_MAX_DIRECTORY_FILES = 5e3;
1930
+ var BUILT_IN_REPO_IGNORES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", "coverage", ".venv", "vendor", "target"]);
1434
1931
  function inferKind(mimeType, filePath) {
1435
1932
  if (inferCodeLanguage(filePath, mimeType)) {
1436
1933
  return "code";
@@ -1462,9 +1959,67 @@ function guessMimeType(target) {
1462
1959
  function normalizeIngestOptions(options) {
1463
1960
  return {
1464
1961
  includeAssets: options?.includeAssets ?? true,
1465
- maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE))
1962
+ maxAssetSize: Math.max(0, Math.floor(options?.maxAssetSize ?? DEFAULT_MAX_ASSET_SIZE)),
1963
+ repoRoot: options?.repoRoot ? path5.resolve(options.repoRoot) : void 0,
1964
+ include: (options?.include ?? []).map((pattern) => pattern.trim()).filter(Boolean),
1965
+ exclude: (options?.exclude ?? []).map((pattern) => pattern.trim()).filter(Boolean),
1966
+ maxFiles: Math.max(1, Math.floor(options?.maxFiles ?? DEFAULT_MAX_DIRECTORY_FILES)),
1967
+ gitignore: options?.gitignore ?? true
1466
1968
  };
1467
1969
  }
1970
+ function matchesAnyGlob(relativePath, patterns) {
1971
+ return patterns.some(
1972
+ (pattern) => path5.matchesGlob(relativePath, pattern) || path5.matchesGlob(path5.posix.basename(relativePath), pattern)
1973
+ );
1974
+ }
1975
+ function supportedDirectoryKind(sourceKind) {
1976
+ return sourceKind !== "binary";
1977
+ }
1978
+ async function findNearestGitRoot(startPath) {
1979
+ let current = path5.resolve(startPath);
1980
+ try {
1981
+ const stat = await fs5.stat(current);
1982
+ if (!stat.isDirectory()) {
1983
+ current = path5.dirname(current);
1984
+ }
1985
+ } catch {
1986
+ current = path5.dirname(current);
1987
+ }
1988
+ while (true) {
1989
+ if (await fileExists(path5.join(current, ".git"))) {
1990
+ return current;
1991
+ }
1992
+ const parent = path5.dirname(current);
1993
+ if (parent === current) {
1994
+ return null;
1995
+ }
1996
+ current = parent;
1997
+ }
1998
+ }
1999
+ function withinRoot(rootPath, targetPath) {
2000
+ const relative = path5.relative(rootPath, targetPath);
2001
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
2002
+ }
2003
+ function repoRelativePathFor(absolutePath, repoRoot) {
2004
+ if (!repoRoot || !withinRoot(repoRoot, absolutePath)) {
2005
+ return void 0;
2006
+ }
2007
+ const relative = toPosix(path5.relative(repoRoot, absolutePath));
2008
+ return relative && !relative.startsWith("..") ? relative : void 0;
2009
+ }
2010
+ function normalizeOriginUrl(input) {
2011
+ try {
2012
+ return new URL(input).toString();
2013
+ } catch {
2014
+ return input;
2015
+ }
2016
+ }
2017
+ function manifestMatchesOrigin(manifest, prepared) {
2018
+ if (prepared.originType === "url") {
2019
+ return Boolean(prepared.url && manifest.url && normalizeOriginUrl(manifest.url) === normalizeOriginUrl(prepared.url));
2020
+ }
2021
+ return Boolean(prepared.originalPath && manifest.originalPath && toPosix(manifest.originalPath) === toPosix(prepared.originalPath));
2022
+ }
1468
2023
  function buildCompositeHash(payloadBytes, attachments = []) {
1469
2024
  if (!attachments.length) {
1470
2025
  return sha256(payloadBytes);
@@ -1473,7 +2028,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
1473
2028
  return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
1474
2029
  }
1475
2030
  function sanitizeAssetRelativePath(value) {
1476
- const normalized = path4.posix.normalize(value.replace(/\\/g, "/"));
2031
+ const normalized = path5.posix.normalize(value.replace(/\\/g, "/"));
1477
2032
  const segments = normalized.split("/").filter(Boolean).map((segment) => {
1478
2033
  if (segment === ".") {
1479
2034
  return "";
@@ -1493,7 +2048,7 @@ function normalizeLocalReference(value) {
1493
2048
  return null;
1494
2049
  }
1495
2050
  const lowered = candidate.toLowerCase();
1496
- if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path4.isAbsolute(candidate)) {
2051
+ if (lowered.startsWith("http://") || lowered.startsWith("https://") || lowered.startsWith("data:") || lowered.startsWith("mailto:") || lowered.startsWith("#") || path5.isAbsolute(candidate)) {
1497
2052
  return null;
1498
2053
  }
1499
2054
  return candidate.replace(/\\/g, "/");
@@ -1555,18 +2110,107 @@ async function convertHtmlToMarkdown(html, url) {
1555
2110
  };
1556
2111
  }
1557
2112
  async function readManifestByHash(manifestsDir, contentHash) {
1558
- const entries = await fs3.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
2113
+ const entries = await fs5.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
1559
2114
  for (const entry of entries) {
1560
2115
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
1561
2116
  continue;
1562
2117
  }
1563
- const manifest = await readJsonFile(path4.join(manifestsDir, entry.name));
2118
+ const manifest = await readJsonFile(path5.join(manifestsDir, entry.name));
1564
2119
  if (manifest?.contentHash === contentHash) {
1565
2120
  return manifest;
1566
2121
  }
1567
2122
  }
1568
2123
  return null;
1569
2124
  }
2125
+ async function readManifestByOrigin(manifestsDir, prepared) {
2126
+ const entries = await fs5.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
2127
+ for (const entry of entries) {
2128
+ if (!entry.isFile() || !entry.name.endsWith(".json")) {
2129
+ continue;
2130
+ }
2131
+ const manifest = await readJsonFile(path5.join(manifestsDir, entry.name));
2132
+ if (manifest && manifestMatchesOrigin(manifest, prepared)) {
2133
+ return manifest;
2134
+ }
2135
+ }
2136
+ return null;
2137
+ }
2138
+ async function loadGitignoreMatcher(repoRoot, enabled) {
2139
+ if (!enabled) {
2140
+ return null;
2141
+ }
2142
+ const gitignorePath = path5.join(repoRoot, ".gitignore");
2143
+ if (!await fileExists(gitignorePath)) {
2144
+ return null;
2145
+ }
2146
+ const matcher = ignore();
2147
+ matcher.add(await fs5.readFile(gitignorePath, "utf8"));
2148
+ return matcher;
2149
+ }
2150
+ function builtInIgnoreReason(relativePath) {
2151
+ for (const segment of relativePath.split("/")) {
2152
+ if (BUILT_IN_REPO_IGNORES.has(segment)) {
2153
+ return `built_in_ignore:${segment}`;
2154
+ }
2155
+ }
2156
+ return null;
2157
+ }
2158
+ async function collectDirectoryFiles(rootDir, inputDir, repoRoot, options) {
2159
+ const matcher = await loadGitignoreMatcher(repoRoot, options.gitignore);
2160
+ const skipped = [];
2161
+ const files = [];
2162
+ const stack = [inputDir];
2163
+ while (stack.length > 0) {
2164
+ const currentDir = stack.pop();
2165
+ if (!currentDir) {
2166
+ continue;
2167
+ }
2168
+ const entries = await fs5.readdir(currentDir, { withFileTypes: true });
2169
+ entries.sort((left, right) => left.name.localeCompare(right.name));
2170
+ for (const entry of entries) {
2171
+ const absolutePath = path5.join(currentDir, entry.name);
2172
+ const relativeToRepo = repoRelativePathFor(absolutePath, repoRoot) ?? toPosix(path5.relative(inputDir, absolutePath));
2173
+ const relativePath = relativeToRepo || entry.name;
2174
+ const builtInReason = builtInIgnoreReason(relativePath);
2175
+ if (builtInReason) {
2176
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: builtInReason });
2177
+ continue;
2178
+ }
2179
+ if (matcher?.ignores(relativePath)) {
2180
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "gitignore" });
2181
+ continue;
2182
+ }
2183
+ if (matchesAnyGlob(relativePath, options.exclude)) {
2184
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "exclude_glob" });
2185
+ continue;
2186
+ }
2187
+ if (entry.isDirectory()) {
2188
+ stack.push(absolutePath);
2189
+ continue;
2190
+ }
2191
+ if (!entry.isFile()) {
2192
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "unsupported_entry" });
2193
+ continue;
2194
+ }
2195
+ if (options.include.length > 0 && !matchesAnyGlob(relativePath, options.include)) {
2196
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "include_glob" });
2197
+ continue;
2198
+ }
2199
+ const mimeType = guessMimeType(absolutePath);
2200
+ const sourceKind = inferKind(mimeType, absolutePath);
2201
+ if (!supportedDirectoryKind(sourceKind)) {
2202
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
2203
+ continue;
2204
+ }
2205
+ if (files.length >= options.maxFiles) {
2206
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "max_files" });
2207
+ continue;
2208
+ }
2209
+ files.push(absolutePath);
2210
+ }
2211
+ }
2212
+ return { files: files.sort((left, right) => left.localeCompare(right)), skipped };
2213
+ }
1570
2214
  function resolveUrlMimeType(input, response) {
1571
2215
  const headerMimeType = response.headers.get("content-type")?.split(";")[0]?.trim();
1572
2216
  const guessedMimeType = guessMimeType(new URL(input).pathname);
@@ -1581,12 +2225,12 @@ function resolveUrlMimeType(input, response) {
1581
2225
  function buildRemoteAssetRelativePath(assetUrl, mimeType) {
1582
2226
  const url = new URL(assetUrl);
1583
2227
  const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
1584
- const extension = path4.posix.extname(normalized);
1585
- const directory = path4.posix.dirname(normalized);
1586
- const basename = extension ? path4.posix.basename(normalized, extension) : path4.posix.basename(normalized);
2228
+ const extension = path5.posix.extname(normalized);
2229
+ const directory = path5.posix.dirname(normalized);
2230
+ const basename = extension ? path5.posix.basename(normalized, extension) : path5.posix.basename(normalized);
1587
2231
  const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
1588
2232
  const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
1589
- return directory === "." ? hashedName : path4.posix.join(directory, hashedName);
2233
+ return directory === "." ? hashedName : path5.posix.join(directory, hashedName);
1590
2234
  }
1591
2235
  async function readResponseBytesWithinLimit(response, maxBytes) {
1592
2236
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
@@ -1710,26 +2354,38 @@ async function persistPreparedInput(rootDir, prepared, paths) {
1710
2354
  await ensureDir(paths.extractsDir);
1711
2355
  const attachments = prepared.attachments ?? [];
1712
2356
  const contentHash = prepared.contentHash ?? buildCompositeHash(prepared.payloadBytes, attachments);
1713
- const existing = await readManifestByHash(paths.manifestsDir, contentHash);
1714
- if (existing) {
1715
- return { manifest: existing, isNew: false };
2357
+ const existingByOrigin = await readManifestByOrigin(paths.manifestsDir, prepared);
2358
+ const existingByHash = existingByOrigin ? null : await readManifestByHash(paths.manifestsDir, contentHash);
2359
+ if (existingByOrigin && existingByOrigin.contentHash === contentHash && existingByOrigin.title === prepared.title && existingByOrigin.sourceKind === prepared.sourceKind && existingByOrigin.language === prepared.language && existingByOrigin.mimeType === prepared.mimeType && existingByOrigin.repoRelativePath === prepared.repoRelativePath) {
2360
+ return { manifest: existingByOrigin, isNew: false, wasUpdated: false };
2361
+ }
2362
+ if (existingByHash) {
2363
+ return { manifest: existingByHash, isNew: false, wasUpdated: false };
1716
2364
  }
2365
+ const previous = existingByOrigin ?? void 0;
2366
+ const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
1717
2367
  const now = (/* @__PURE__ */ new Date()).toISOString();
1718
- const sourceId = `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
1719
- const storedPath = path4.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
1720
- await fs3.writeFile(storedPath, prepared.payloadBytes);
1721
- let extractedTextPath;
1722
- if (prepared.extractedText) {
1723
- extractedTextPath = path4.join(paths.extractsDir, `${sourceId}.md`);
1724
- await fs3.writeFile(extractedTextPath, prepared.extractedText, "utf8");
2368
+ const storedPath = path5.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
2369
+ const extractedTextPath = prepared.extractedText ? path5.join(paths.extractsDir, `${sourceId}.md`) : void 0;
2370
+ const attachmentsDir = path5.join(paths.rawAssetsDir, sourceId);
2371
+ if (previous?.storedPath) {
2372
+ await fs5.rm(path5.resolve(rootDir, previous.storedPath), { force: true });
2373
+ }
2374
+ if (previous?.extractedTextPath) {
2375
+ await fs5.rm(path5.resolve(rootDir, previous.extractedTextPath), { force: true });
2376
+ }
2377
+ await fs5.rm(attachmentsDir, { recursive: true, force: true });
2378
+ await fs5.writeFile(storedPath, prepared.payloadBytes);
2379
+ if (prepared.extractedText && extractedTextPath) {
2380
+ await fs5.writeFile(extractedTextPath, prepared.extractedText, "utf8");
1725
2381
  }
1726
2382
  const manifestAttachments = [];
1727
2383
  for (const attachment of attachments) {
1728
- const absoluteAttachmentPath = path4.join(paths.rawAssetsDir, sourceId, attachment.relativePath);
1729
- await ensureDir(path4.dirname(absoluteAttachmentPath));
1730
- await fs3.writeFile(absoluteAttachmentPath, attachment.bytes);
2384
+ const absoluteAttachmentPath = path5.join(attachmentsDir, attachment.relativePath);
2385
+ await ensureDir(path5.dirname(absoluteAttachmentPath));
2386
+ await fs5.writeFile(absoluteAttachmentPath, attachment.bytes);
1731
2387
  manifestAttachments.push({
1732
- path: toPosix(path4.relative(rootDir, absoluteAttachmentPath)),
2388
+ path: toPosix(path5.relative(rootDir, absoluteAttachmentPath)),
1733
2389
  mimeType: attachment.mimeType,
1734
2390
  originalPath: attachment.originalPath
1735
2391
  });
@@ -1741,37 +2397,39 @@ async function persistPreparedInput(rootDir, prepared, paths) {
1741
2397
  sourceKind: prepared.sourceKind,
1742
2398
  language: prepared.language,
1743
2399
  originalPath: prepared.originalPath,
2400
+ repoRelativePath: prepared.repoRelativePath,
1744
2401
  url: prepared.url,
1745
- storedPath: toPosix(path4.relative(rootDir, storedPath)),
1746
- extractedTextPath: extractedTextPath ? toPosix(path4.relative(rootDir, extractedTextPath)) : void 0,
2402
+ storedPath: toPosix(path5.relative(rootDir, storedPath)),
2403
+ extractedTextPath: extractedTextPath ? toPosix(path5.relative(rootDir, extractedTextPath)) : void 0,
1747
2404
  mimeType: prepared.mimeType,
1748
2405
  contentHash,
1749
- createdAt: now,
2406
+ createdAt: previous?.createdAt ?? now,
1750
2407
  updatedAt: now,
1751
2408
  attachments: manifestAttachments.length ? manifestAttachments : void 0
1752
2409
  };
1753
- await writeJsonFile(path4.join(paths.manifestsDir, `${sourceId}.json`), manifest);
2410
+ await writeJsonFile(path5.join(paths.manifestsDir, `${sourceId}.json`), manifest);
1754
2411
  await appendLogEntry(rootDir, "ingest", prepared.title, [
1755
2412
  `source_id=${sourceId}`,
1756
2413
  `kind=${prepared.sourceKind}`,
1757
2414
  `attachments=${manifestAttachments.length}`,
2415
+ `updated=${previous ? "true" : "false"}`,
1758
2416
  ...prepared.logDetails ?? []
1759
2417
  ]);
1760
- return { manifest, isNew: true };
2418
+ return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
1761
2419
  }
1762
- async function prepareFileInput(_rootDir, absoluteInput) {
1763
- const payloadBytes = await fs3.readFile(absoluteInput);
2420
+ async function prepareFileInput(_rootDir, absoluteInput, repoRoot) {
2421
+ const payloadBytes = await fs5.readFile(absoluteInput);
1764
2422
  const mimeType = guessMimeType(absoluteInput);
1765
2423
  const sourceKind = inferKind(mimeType, absoluteInput);
1766
2424
  const language = inferCodeLanguage(absoluteInput, mimeType);
1767
- const storedExtension = path4.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
2425
+ const storedExtension = path5.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
1768
2426
  let title;
1769
2427
  let extractedText;
1770
2428
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
1771
2429
  extractedText = payloadBytes.toString("utf8");
1772
- title = titleFromText(path4.basename(absoluteInput, path4.extname(absoluteInput)), extractedText);
2430
+ title = titleFromText(path5.basename(absoluteInput, path5.extname(absoluteInput)), extractedText);
1773
2431
  } else {
1774
- title = path4.basename(absoluteInput, path4.extname(absoluteInput));
2432
+ title = path5.basename(absoluteInput, path5.extname(absoluteInput));
1775
2433
  }
1776
2434
  return {
1777
2435
  title,
@@ -1779,6 +2437,7 @@ async function prepareFileInput(_rootDir, absoluteInput) {
1779
2437
  sourceKind,
1780
2438
  language,
1781
2439
  originalPath: toPosix(absoluteInput),
2440
+ repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
1782
2441
  mimeType,
1783
2442
  storedExtension,
1784
2443
  payloadBytes,
@@ -1840,7 +2499,7 @@ async function prepareUrlInput(input, options) {
1840
2499
  sourceKind = "markdown";
1841
2500
  storedExtension = ".md";
1842
2501
  } else {
1843
- const extension = path4.extname(inputUrl.pathname);
2502
+ const extension = path5.extname(inputUrl.pathname);
1844
2503
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
1845
2504
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
1846
2505
  extractedText = payloadBytes.toString("utf8");
@@ -1890,14 +2549,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
1890
2549
  if (sourceKind !== "markdown") {
1891
2550
  continue;
1892
2551
  }
1893
- const content = await fs3.readFile(absolutePath, "utf8");
2552
+ const content = await fs5.readFile(absolutePath, "utf8");
1894
2553
  const refs = extractMarkdownReferences(content);
1895
2554
  if (!refs.length) {
1896
2555
  continue;
1897
2556
  }
1898
2557
  const sourceRefs = [];
1899
2558
  for (const ref of refs) {
1900
- const resolved = path4.resolve(path4.dirname(absolutePath), ref);
2559
+ const resolved = path5.resolve(path5.dirname(absolutePath), ref);
1901
2560
  if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
1902
2561
  continue;
1903
2562
  }
@@ -1931,12 +2590,12 @@ function rewriteMarkdownReferences(content, replacements) {
1931
2590
  });
1932
2591
  }
1933
2592
  async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
1934
- const originalBytes = await fs3.readFile(absolutePath);
2593
+ const originalBytes = await fs5.readFile(absolutePath);
1935
2594
  const originalText = originalBytes.toString("utf8");
1936
- const title = titleFromText(path4.basename(absolutePath, path4.extname(absolutePath)), originalText);
2595
+ const title = titleFromText(path5.basename(absolutePath, path5.extname(absolutePath)), originalText);
1937
2596
  const attachments = [];
1938
2597
  for (const attachmentRef of attachmentRefs) {
1939
- const bytes = await fs3.readFile(attachmentRef.absolutePath);
2598
+ const bytes = await fs5.readFile(attachmentRef.absolutePath);
1940
2599
  attachments.push({
1941
2600
  relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
1942
2601
  mimeType: guessMimeType(attachmentRef.absolutePath),
@@ -1959,7 +2618,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
1959
2618
  sourceKind: "markdown",
1960
2619
  originalPath: toPosix(absolutePath),
1961
2620
  mimeType: "text/markdown",
1962
- storedExtension: path4.extname(absolutePath) || ".md",
2621
+ storedExtension: path5.extname(absolutePath) || ".md",
1963
2622
  payloadBytes: Buffer.from(rewrittenText, "utf8"),
1964
2623
  extractedText: rewrittenText,
1965
2624
  attachments,
@@ -1972,13 +2631,53 @@ function isSupportedInboxKind(sourceKind) {
1972
2631
  async function ingestInput(rootDir, input, options) {
1973
2632
  const { paths } = await initWorkspace(rootDir);
1974
2633
  const normalizedOptions = normalizeIngestOptions(options);
1975
- const prepared = /^https?:\/\//i.test(input) ? await prepareUrlInput(input, normalizedOptions) : await prepareFileInput(rootDir, path4.resolve(rootDir, input));
2634
+ const absoluteInput = path5.resolve(rootDir, input);
2635
+ const repoRoot = /^https?:\/\//i.test(input) || normalizedOptions.repoRoot ? normalizedOptions.repoRoot : await findNearestGitRoot(absoluteInput).then((value) => value ?? path5.dirname(absoluteInput));
2636
+ const prepared = /^https?:\/\//i.test(input) ? await prepareUrlInput(input, normalizedOptions) : await prepareFileInput(rootDir, absoluteInput, repoRoot);
1976
2637
  const result = await persistPreparedInput(rootDir, prepared, paths);
1977
2638
  return result.manifest;
1978
2639
  }
2640
+ async function ingestDirectory(rootDir, inputDir, options) {
2641
+ const { paths } = await initWorkspace(rootDir);
2642
+ const normalizedOptions = normalizeIngestOptions(options);
2643
+ const absoluteInputDir = path5.resolve(rootDir, inputDir);
2644
+ const repoRoot = normalizedOptions.repoRoot ?? await findNearestGitRoot(absoluteInputDir) ?? absoluteInputDir;
2645
+ if (!await fileExists(absoluteInputDir)) {
2646
+ throw new Error(`Directory not found: ${absoluteInputDir}`);
2647
+ }
2648
+ const { files, skipped } = await collectDirectoryFiles(rootDir, absoluteInputDir, repoRoot, normalizedOptions);
2649
+ const imported = [];
2650
+ const updated = [];
2651
+ for (const absolutePath of files) {
2652
+ const prepared = await prepareFileInput(rootDir, absolutePath, repoRoot);
2653
+ const result = await persistPreparedInput(rootDir, prepared, paths);
2654
+ if (result.isNew) {
2655
+ imported.push(result.manifest);
2656
+ } else if (result.wasUpdated) {
2657
+ updated.push(result.manifest);
2658
+ } else {
2659
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "duplicate_content" });
2660
+ }
2661
+ }
2662
+ await appendLogEntry(rootDir, "ingest_directory", toPosix(path5.relative(rootDir, absoluteInputDir)) || ".", [
2663
+ `repo_root=${toPosix(path5.relative(rootDir, repoRoot)) || "."}`,
2664
+ `scanned=${files.length}`,
2665
+ `imported=${imported.length}`,
2666
+ `updated=${updated.length}`,
2667
+ `skipped=${skipped.length}`
2668
+ ]);
2669
+ return {
2670
+ inputDir: absoluteInputDir,
2671
+ repoRoot,
2672
+ scannedCount: files.length,
2673
+ imported,
2674
+ updated,
2675
+ skipped
2676
+ };
2677
+ }
1979
2678
  async function importInbox(rootDir, inputDir) {
1980
2679
  const { paths } = await initWorkspace(rootDir);
1981
- const effectiveInputDir = path4.resolve(rootDir, inputDir ?? paths.inboxDir);
2680
+ const effectiveInputDir = path5.resolve(rootDir, inputDir ?? paths.inboxDir);
1982
2681
  if (!await fileExists(effectiveInputDir)) {
1983
2682
  throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
1984
2683
  }
@@ -1989,31 +2688,31 @@ async function importInbox(rootDir, inputDir) {
1989
2688
  const skipped = [];
1990
2689
  let attachmentCount = 0;
1991
2690
  for (const absolutePath of files) {
1992
- const basename = path4.basename(absolutePath);
2691
+ const basename = path5.basename(absolutePath);
1993
2692
  if (basename.startsWith(".")) {
1994
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: "hidden_file" });
2693
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "hidden_file" });
1995
2694
  continue;
1996
2695
  }
1997
2696
  if (claimedAttachments.has(absolutePath)) {
1998
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
2697
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
1999
2698
  continue;
2000
2699
  }
2001
2700
  const mimeType = guessMimeType(absolutePath);
2002
2701
  const sourceKind = inferKind(mimeType, absolutePath);
2003
2702
  if (!isSupportedInboxKind(sourceKind)) {
2004
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
2703
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: `unsupported_kind:${sourceKind}` });
2005
2704
  continue;
2006
2705
  }
2007
2706
  const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
2008
2707
  const result = await persistPreparedInput(rootDir, prepared, paths);
2009
2708
  if (!result.isNew) {
2010
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: "duplicate_content" });
2709
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "duplicate_content" });
2011
2710
  continue;
2012
2711
  }
2013
2712
  attachmentCount += result.manifest.attachments?.length ?? 0;
2014
2713
  imported.push(result.manifest);
2015
2714
  }
2016
- await appendLogEntry(rootDir, "inbox_import", toPosix(path4.relative(rootDir, effectiveInputDir)) || ".", [
2715
+ await appendLogEntry(rootDir, "inbox_import", toPosix(path5.relative(rootDir, effectiveInputDir)) || ".", [
2017
2716
  `scanned=${files.length}`,
2018
2717
  `imported=${imported.length}`,
2019
2718
  `attachments=${attachmentCount}`,
@@ -2032,9 +2731,9 @@ async function listManifests(rootDir) {
2032
2731
  if (!await fileExists(paths.manifestsDir)) {
2033
2732
  return [];
2034
2733
  }
2035
- const entries = await fs3.readdir(paths.manifestsDir);
2734
+ const entries = await fs5.readdir(paths.manifestsDir);
2036
2735
  const manifests = await Promise.all(
2037
- entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path4.join(paths.manifestsDir, entry)))
2736
+ entries.filter((entry) => entry.endsWith(".json")).map((entry) => readJsonFile(path5.join(paths.manifestsDir, entry)))
2038
2737
  );
2039
2738
  return manifests.filter((manifest) => Boolean(manifest));
2040
2739
  }
@@ -2042,28 +2741,28 @@ async function readExtractedText(rootDir, manifest) {
2042
2741
  if (!manifest.extractedTextPath) {
2043
2742
  return void 0;
2044
2743
  }
2045
- const absolutePath = path4.resolve(rootDir, manifest.extractedTextPath);
2744
+ const absolutePath = path5.resolve(rootDir, manifest.extractedTextPath);
2046
2745
  if (!await fileExists(absolutePath)) {
2047
2746
  return void 0;
2048
2747
  }
2049
- return fs3.readFile(absolutePath, "utf8");
2748
+ return fs5.readFile(absolutePath, "utf8");
2050
2749
  }
2051
2750
 
2052
2751
  // src/mcp.ts
2053
- import fs10 from "fs/promises";
2054
- import path14 from "path";
2752
+ import fs12 from "fs/promises";
2753
+ import path15 from "path";
2055
2754
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2056
2755
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2057
2756
  import { z as z7 } from "zod";
2058
2757
 
2059
2758
  // src/schema.ts
2060
- import fs4 from "fs/promises";
2061
- import path5 from "path";
2759
+ import fs6 from "fs/promises";
2760
+ import path6 from "path";
2062
2761
  function normalizeSchemaContent(content) {
2063
2762
  return content.trim() ? content.trim() : defaultVaultSchema().trim();
2064
2763
  }
2065
2764
  async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
2066
- const content = await fileExists(schemaPath) ? await fs4.readFile(schemaPath, "utf8") : fallback;
2765
+ const content = await fileExists(schemaPath) ? await fs6.readFile(schemaPath, "utf8") : fallback;
2067
2766
  const normalized = normalizeSchemaContent(content);
2068
2767
  return {
2069
2768
  path: schemaPath,
@@ -2072,7 +2771,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
2072
2771
  };
2073
2772
  }
2074
2773
  function resolveProjectSchemaPath(rootDir, schemaPath) {
2075
- return path5.resolve(rootDir, schemaPath);
2774
+ return path6.resolve(rootDir, schemaPath);
2076
2775
  }
2077
2776
  function composeVaultSchema(root, projectSchemas = []) {
2078
2777
  if (!projectSchemas.length) {
@@ -2088,7 +2787,7 @@ function composeVaultSchema(root, projectSchemas = []) {
2088
2787
  (schema) => [
2089
2788
  `## Project Schema`,
2090
2789
  "",
2091
- `Path: ${toPosix(path5.relative(path5.dirname(root.path), schema.path) || schema.path)}`,
2790
+ `Path: ${toPosix(path6.relative(path6.dirname(root.path), schema.path) || schema.path)}`,
2092
2791
  "",
2093
2792
  schema.content
2094
2793
  ].join("\n")
@@ -2164,14 +2863,15 @@ function buildSchemaPrompt(schema, instruction) {
2164
2863
  }
2165
2864
 
2166
2865
  // src/vault.ts
2167
- import fs9 from "fs/promises";
2168
- import path13 from "path";
2866
+ import fs11 from "fs/promises";
2867
+ import path14 from "path";
2169
2868
  import matter7 from "gray-matter";
2170
2869
  import { z as z6 } from "zod";
2171
2870
 
2172
2871
  // src/analysis.ts
2173
- import path6 from "path";
2872
+ import path7 from "path";
2174
2873
  import { z } from "zod";
2874
+ var ANALYSIS_FORMAT_VERSION = 3;
2175
2875
  var sourceAnalysisSchema = z.object({
2176
2876
  title: z.string().min(1),
2177
2877
  summary: z.string().min(1),
@@ -2272,6 +2972,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
2272
2972
  }));
2273
2973
  const claimSentences = normalized.split(/(?<=[.!?])\s+/).filter(Boolean).slice(0, 4);
2274
2974
  return {
2975
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
2275
2976
  sourceId: manifest.sourceId,
2276
2977
  sourceHash: manifest.contentHash,
2277
2978
  schemaHash,
@@ -2316,6 +3017,7 @@ ${truncate(text, 18e3)}`
2316
3017
  sourceAnalysisSchema
2317
3018
  );
2318
3019
  return {
3020
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
2319
3021
  sourceId: manifest.sourceId,
2320
3022
  sourceHash: manifest.contentHash,
2321
3023
  schemaHash: schema.hash,
@@ -2344,17 +3046,18 @@ ${truncate(text, 18e3)}`
2344
3046
  };
2345
3047
  }
2346
3048
  async function analyzeSource(manifest, extractedText, provider, paths, schema) {
2347
- const cachePath = path6.join(paths.analysesDir, `${manifest.sourceId}.json`);
3049
+ const cachePath = path7.join(paths.analysesDir, `${manifest.sourceId}.json`);
2348
3050
  const cached = await readJsonFile(cachePath);
2349
- if (cached && cached.sourceHash === manifest.contentHash && cached.schemaHash === schema.hash) {
3051
+ if (cached && cached.analysisVersion === ANALYSIS_FORMAT_VERSION && cached.sourceHash === manifest.contentHash && cached.schemaHash === schema.hash) {
2350
3052
  return cached;
2351
3053
  }
2352
3054
  const content = normalizeWhitespace(extractedText ?? "");
2353
3055
  let analysis;
2354
3056
  if (manifest.sourceKind === "code" && content) {
2355
- analysis = analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
3057
+ analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
2356
3058
  } else if (!content) {
2357
3059
  analysis = {
3060
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
2358
3061
  sourceId: manifest.sourceId,
2359
3062
  sourceHash: manifest.contentHash,
2360
3063
  schemaHash: schema.hash,
@@ -2399,8 +3102,8 @@ function conflictConfidence(claimA, claimB) {
2399
3102
  }
2400
3103
 
2401
3104
  // src/deep-lint.ts
2402
- import fs5 from "fs/promises";
2403
- import path9 from "path";
3105
+ import fs7 from "fs/promises";
3106
+ import path10 from "path";
2404
3107
  import matter2 from "gray-matter";
2405
3108
  import { z as z4 } from "zod";
2406
3109
 
@@ -2421,7 +3124,7 @@ function normalizeFindingSeverity(value) {
2421
3124
 
2422
3125
  // src/orchestration.ts
2423
3126
  import { spawn } from "child_process";
2424
- import path7 from "path";
3127
+ import path8 from "path";
2425
3128
  import { z as z2 } from "zod";
2426
3129
  var orchestrationRoleResultSchema = z2.object({
2427
3130
  summary: z2.string().optional(),
@@ -2514,7 +3217,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
2514
3217
  }
2515
3218
  async function runCommandRole(rootDir, role, executor, input) {
2516
3219
  const [command, ...args] = executor.command;
2517
- const cwd = executor.cwd ? path7.resolve(rootDir, executor.cwd) : rootDir;
3220
+ const cwd = executor.cwd ? path8.resolve(rootDir, executor.cwd) : rootDir;
2518
3221
  const child = spawn(command, args, {
2519
3222
  cwd,
2520
3223
  env: {
@@ -2608,7 +3311,7 @@ function summarizeRoleQuestions(results) {
2608
3311
  }
2609
3312
 
2610
3313
  // src/web-search/registry.ts
2611
- import path8 from "path";
3314
+ import path9 from "path";
2612
3315
  import { pathToFileURL } from "url";
2613
3316
  import { z as z3 } from "zod";
2614
3317
 
@@ -2706,7 +3409,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
2706
3409
  if (!config.module) {
2707
3410
  throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
2708
3411
  }
2709
- const resolvedModule = path8.isAbsolute(config.module) ? config.module : path8.resolve(rootDir, config.module);
3412
+ const resolvedModule = path9.isAbsolute(config.module) ? config.module : path9.resolve(rootDir, config.module);
2710
3413
  const loaded = await import(pathToFileURL(resolvedModule).href);
2711
3414
  const parsed = customWebSearchModuleSchema.parse(loaded);
2712
3415
  return parsed.createAdapter(id, config, rootDir);
@@ -2766,8 +3469,8 @@ async function loadContextPages(rootDir, graph) {
2766
3469
  );
2767
3470
  return Promise.all(
2768
3471
  contextPages.slice(0, 18).map(async (page) => {
2769
- const absolutePath = path9.join(paths.wikiDir, page.path);
2770
- const raw = await fs5.readFile(absolutePath, "utf8").catch(() => "");
3472
+ const absolutePath = path10.join(paths.wikiDir, page.path);
3473
+ const raw = await fs7.readFile(absolutePath, "utf8").catch(() => "");
2771
3474
  const parsed = matter2(raw);
2772
3475
  return {
2773
3476
  id: page.id,
@@ -2815,7 +3518,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
2815
3518
  code: "missing_citation",
2816
3519
  message: finding.message,
2817
3520
  pagePath: finding.pagePath,
2818
- suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path9.basename(finding.pagePath, ".md")}?` : void 0
3521
+ suggestedQuery: finding.pagePath ? `Which sources support the claims in ${path10.basename(finding.pagePath, ".md")}?` : void 0
2819
3522
  });
2820
3523
  }
2821
3524
  for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
@@ -3169,9 +3872,7 @@ function buildModulePage(input) {
3169
3872
  const relatedOutputs = input.relatedOutputs ?? [];
3170
3873
  const backlinks = uniqueStrings([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]);
3171
3874
  const importsSection = code.imports.length ? code.imports.map((item) => {
3172
- const localModule = input.localModules.find(
3173
- (moduleRef) => moduleRef.specifier === item.specifier && moduleRef.reExport === item.reExport
3174
- );
3875
+ const localModule = item.resolvedSourceId ? input.localModules.find((moduleRef) => moduleRef.sourceId === item.resolvedSourceId && moduleRef.reExport === item.reExport) : void 0;
3175
3876
  const importedBits = [
3176
3877
  item.defaultImport ? `default \`${item.defaultImport}\`` : "",
3177
3878
  item.namespaceImport ? `namespace \`${item.namespaceImport}\`` : "",
@@ -3182,6 +3883,7 @@ function buildModulePage(input) {
3182
3883
  const suffix = importedBits.length ? ` (${importedBits.join("; ")})` : "";
3183
3884
  return `- ${mode} ${importTarget}${suffix}`;
3184
3885
  }) : ["- No imports detected."];
3886
+ const unresolvedLocalImports = code.imports.filter((item) => !item.isExternal && !item.resolvedSourceId).map((item) => `- \`${item.specifier}\`${item.resolvedRepoPath ? ` (expected near \`${item.resolvedRepoPath}\`)` : ""}`);
3185
3887
  const exportsSection = code.exports.length ? code.exports.map((item) => `- \`${item}\``) : ["- No exports detected."];
3186
3888
  const symbolsSection = code.symbols.length ? code.symbols.map(
3187
3889
  (symbol) => `- \`${symbol.name}\` (${symbol.kind}${symbol.exported ? ", exported" : ""}): ${symbol.signature || "No signature recorded."}`
@@ -3228,7 +3930,10 @@ function buildModulePage(input) {
3228
3930
  "",
3229
3931
  `Source ID: \`${manifest.sourceId}\``,
3230
3932
  `Source Path: \`${manifest.originalPath ?? manifest.storedPath}\``,
3933
+ ...manifest.repoRelativePath ? [`Repo Path: \`${manifest.repoRelativePath}\``] : [],
3231
3934
  `Language: \`${code.language}\``,
3935
+ ...code.moduleName ? [`Module Name: \`${code.moduleName}\``] : [],
3936
+ ...code.namespace ? [`Namespace/Package: \`${code.namespace}\``] : [],
3232
3937
  `Source Page: [[${sourcePage.path.replace(/\.md$/, "")}|${sourcePage.title}]]`,
3233
3938
  "",
3234
3939
  "## Summary",
@@ -3251,6 +3956,10 @@ function buildModulePage(input) {
3251
3956
  "",
3252
3957
  ...code.dependencies.length ? code.dependencies.map((dependency) => `- \`${dependency}\``) : ["- No external dependencies detected."],
3253
3958
  "",
3959
+ "## Unresolved Local References",
3960
+ "",
3961
+ ...unresolvedLocalImports.length ? unresolvedLocalImports : ["- No unresolved local references detected."],
3962
+ "",
3254
3963
  "## Inheritance",
3255
3964
  "",
3256
3965
  ...inheritanceSection.length ? inheritanceSection : ["- No inheritance relationships detected."],
@@ -3992,13 +4701,13 @@ function buildOutputAssetManifest(input) {
3992
4701
  }
3993
4702
 
3994
4703
  // src/outputs.ts
3995
- import fs7 from "fs/promises";
3996
- import path11 from "path";
4704
+ import fs9 from "fs/promises";
4705
+ import path12 from "path";
3997
4706
  import matter5 from "gray-matter";
3998
4707
 
3999
4708
  // src/pages.ts
4000
- import fs6 from "fs/promises";
4001
- import path10 from "path";
4709
+ import fs8 from "fs/promises";
4710
+ import path11 from "path";
4002
4711
  import matter4 from "gray-matter";
4003
4712
  function normalizeStringArray(value) {
4004
4713
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -4070,7 +4779,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
4070
4779
  updatedAt: updatedFallback
4071
4780
  };
4072
4781
  }
4073
- const content = await fs6.readFile(absolutePath, "utf8");
4782
+ const content = await fs8.readFile(absolutePath, "utf8");
4074
4783
  const parsed = matter4(content);
4075
4784
  return {
4076
4785
  status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
@@ -4109,7 +4818,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
4109
4818
  const now = (/* @__PURE__ */ new Date()).toISOString();
4110
4819
  const fallbackCreatedAt = defaults.createdAt ?? now;
4111
4820
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
4112
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path10.basename(relativePath, ".md");
4821
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path11.basename(relativePath, ".md");
4113
4822
  const kind = inferPageKind(relativePath, parsed.data.kind);
4114
4823
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
4115
4824
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -4148,18 +4857,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
4148
4857
  };
4149
4858
  }
4150
4859
  async function loadInsightPages(wikiDir) {
4151
- const insightsDir = path10.join(wikiDir, "insights");
4860
+ const insightsDir = path11.join(wikiDir, "insights");
4152
4861
  if (!await fileExists(insightsDir)) {
4153
4862
  return [];
4154
4863
  }
4155
- const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path10.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
4864
+ const files = (await listFilesRecursive(insightsDir)).filter((filePath) => filePath.endsWith(".md")).filter((filePath) => path11.basename(filePath) !== "index.md").sort((left, right) => left.localeCompare(right));
4156
4865
  const insights = [];
4157
4866
  for (const absolutePath of files) {
4158
- const relativePath = toPosix(path10.relative(wikiDir, absolutePath));
4159
- const content = await fs6.readFile(absolutePath, "utf8");
4867
+ const relativePath = toPosix(path11.relative(wikiDir, absolutePath));
4868
+ const content = await fs8.readFile(absolutePath, "utf8");
4160
4869
  const parsed = matter4(content);
4161
- const stats = await fs6.stat(absolutePath);
4162
- const title = typeof parsed.data.title === "string" ? parsed.data.title : path10.basename(absolutePath, ".md");
4870
+ const stats = await fs8.stat(absolutePath);
4871
+ const title = typeof parsed.data.title === "string" ? parsed.data.title : path11.basename(absolutePath, ".md");
4163
4872
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
4164
4873
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
4165
4874
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -4221,27 +4930,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
4221
4930
  return outputPages.map((page) => ({ page, rank: relationRank(page, targetPage) })).filter((item) => item.rank > 0).sort((left, right) => right.rank - left.rank || left.page.title.localeCompare(right.page.title)).map((item) => item.page);
4222
4931
  }
4223
4932
  async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
4224
- const outputsDir = path11.join(wikiDir, "outputs");
4933
+ const outputsDir = path12.join(wikiDir, "outputs");
4225
4934
  const root = baseSlug || "output";
4226
4935
  let candidate = root;
4227
4936
  let counter = 2;
4228
- while (await fileExists(path11.join(outputsDir, `${candidate}.md`))) {
4937
+ while (await fileExists(path12.join(outputsDir, `${candidate}.md`))) {
4229
4938
  candidate = `${root}-${counter}`;
4230
4939
  counter++;
4231
4940
  }
4232
4941
  return candidate;
4233
4942
  }
4234
4943
  async function loadSavedOutputPages(wikiDir) {
4235
- const outputsDir = path11.join(wikiDir, "outputs");
4236
- const entries = await fs7.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
4944
+ const outputsDir = path12.join(wikiDir, "outputs");
4945
+ const entries = await fs9.readdir(outputsDir, { withFileTypes: true }).catch(() => []);
4237
4946
  const outputs = [];
4238
4947
  for (const entry of entries) {
4239
4948
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
4240
4949
  continue;
4241
4950
  }
4242
- const relativePath = path11.posix.join("outputs", entry.name);
4243
- const absolutePath = path11.join(outputsDir, entry.name);
4244
- const content = await fs7.readFile(absolutePath, "utf8");
4951
+ const relativePath = path12.posix.join("outputs", entry.name);
4952
+ const absolutePath = path12.join(outputsDir, entry.name);
4953
+ const content = await fs9.readFile(absolutePath, "utf8");
4245
4954
  const parsed = matter5(content);
4246
4955
  const slug = entry.name.replace(/\.md$/, "");
4247
4956
  const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
@@ -4254,7 +4963,7 @@ async function loadSavedOutputPages(wikiDir) {
4254
4963
  const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
4255
4964
  const backlinks = normalizeStringArray(parsed.data.backlinks);
4256
4965
  const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
4257
- const stats = await fs7.stat(absolutePath);
4966
+ const stats = await fs9.stat(absolutePath);
4258
4967
  const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
4259
4968
  const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
4260
4969
  outputs.push({
@@ -4292,8 +5001,8 @@ async function loadSavedOutputPages(wikiDir) {
4292
5001
  }
4293
5002
 
4294
5003
  // src/search.ts
4295
- import fs8 from "fs/promises";
4296
- import path12 from "path";
5004
+ import fs10 from "fs/promises";
5005
+ import path13 from "path";
4297
5006
  import matter6 from "gray-matter";
4298
5007
  function getDatabaseSync() {
4299
5008
  const builtin = process.getBuiltinModule?.("node:sqlite");
@@ -4313,7 +5022,7 @@ function normalizeStatus(value) {
4313
5022
  return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : void 0;
4314
5023
  }
4315
5024
  async function rebuildSearchIndex(dbPath, pages, wikiDir) {
4316
- await ensureDir(path12.dirname(dbPath));
5025
+ await ensureDir(path13.dirname(dbPath));
4317
5026
  const DatabaseSync = getDatabaseSync();
4318
5027
  const db = new DatabaseSync(dbPath);
4319
5028
  db.exec("PRAGMA journal_mode = WAL;");
@@ -4343,8 +5052,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
4343
5052
  "INSERT INTO pages (id, path, title, body, kind, status, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
4344
5053
  );
4345
5054
  for (const page of pages) {
4346
- const absolutePath = path12.join(wikiDir, page.path);
4347
- const content = await fs8.readFile(absolutePath, "utf8");
5055
+ const absolutePath = path13.join(wikiDir, page.path);
5056
+ const content = await fs10.readFile(absolutePath, "utf8");
4348
5057
  const parsed = matter6(content);
4349
5058
  insertPage.run(
4350
5059
  page.id,
@@ -4447,7 +5156,7 @@ function outputFormatInstruction(format) {
4447
5156
  }
4448
5157
  }
4449
5158
  function outputAssetPath(slug, fileName) {
4450
- return toPosix(path13.join("outputs", "assets", slug, fileName));
5159
+ return toPosix(path14.join("outputs", "assets", slug, fileName));
4451
5160
  }
4452
5161
  function outputAssetId(slug, role) {
4453
5162
  return `output:${slug}:asset:${role}`;
@@ -4587,7 +5296,7 @@ async function resolveImageGenerationProvider(rootDir) {
4587
5296
  if (!providerConfig) {
4588
5297
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
4589
5298
  }
4590
- const { createProvider: createProvider2 } = await import("./registry-ZNW3FDED.js");
5299
+ const { createProvider: createProvider2 } = await import("./registry-JFEW5RUP.js");
4591
5300
  return createProvider2(preferredProviderId, providerConfig, rootDir);
4592
5301
  }
4593
5302
  async function generateOutputArtifacts(rootDir, input) {
@@ -4785,7 +5494,7 @@ async function generateOutputArtifacts(rootDir, input) {
4785
5494
  };
4786
5495
  }
4787
5496
  function normalizeProjectRoot(root) {
4788
- const normalized = toPosix(path13.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
5497
+ const normalized = toPosix(path14.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
4789
5498
  return normalized;
4790
5499
  }
4791
5500
  function projectEntries(config) {
@@ -4811,10 +5520,10 @@ function manifestPathForProject(rootDir, manifest) {
4811
5520
  if (!rawPath) {
4812
5521
  return toPosix(manifest.storedPath);
4813
5522
  }
4814
- if (!path13.isAbsolute(rawPath)) {
5523
+ if (!path14.isAbsolute(rawPath)) {
4815
5524
  return normalizeProjectRoot(rawPath);
4816
5525
  }
4817
- const relative = toPosix(path13.relative(rootDir, rawPath));
5526
+ const relative = toPosix(path14.relative(rootDir, rawPath));
4818
5527
  return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
4819
5528
  }
4820
5529
  function prefixMatches(value, prefix) {
@@ -4987,7 +5696,7 @@ function pageHashes(pages) {
4987
5696
  return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
4988
5697
  }
4989
5698
  async function buildManagedGraphPage(absolutePath, defaults, build) {
4990
- const existingContent = await fileExists(absolutePath) ? await fs9.readFile(absolutePath, "utf8") : null;
5699
+ const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : null;
4991
5700
  let existing = await loadExistingManagedPageState(absolutePath, {
4992
5701
  status: defaults.status ?? "active",
4993
5702
  managedBy: defaults.managedBy
@@ -5025,7 +5734,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
5025
5734
  return built;
5026
5735
  }
5027
5736
  async function buildManagedContent(absolutePath, defaults, build) {
5028
- const existingContent = await fileExists(absolutePath) ? await fs9.readFile(absolutePath, "utf8") : null;
5737
+ const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : null;
5029
5738
  let existing = await loadExistingManagedPageState(absolutePath, {
5030
5739
  status: defaults.status ?? "active",
5031
5740
  managedBy: defaults.managedBy
@@ -5144,7 +5853,7 @@ function deriveGraphMetrics(nodes, edges) {
5144
5853
  communities
5145
5854
  };
5146
5855
  }
5147
- function buildGraph(manifests, analyses, pages, sourceProjects) {
5856
+ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
5148
5857
  const sourceNodes = manifests.map((manifest) => ({
5149
5858
  id: `source:${manifest.sourceId}`,
5150
5859
  type: "source",
@@ -5283,11 +5992,16 @@ function buildGraph(manifests, analyses, pages, sourceProjects) {
5283
5992
  const symbolIdsByName = new Map(analysis.code.symbols.map((symbol) => [symbol.name, symbol.id]));
5284
5993
  const importedSymbolIdsByName = /* @__PURE__ */ new Map();
5285
5994
  for (const codeImport of analysis.code.imports.filter((item) => !item.isExternal)) {
5286
- const targetSourceId = resolveCodeImportSourceId(manifest, codeImport.specifier, manifests);
5995
+ const targetSourceId = codeImport.resolvedSourceId;
5287
5996
  const targetAnalysis = targetSourceId ? analysesBySourceId.get(targetSourceId) : void 0;
5288
5997
  if (!targetSourceId || !targetAnalysis?.code) {
5289
5998
  continue;
5290
5999
  }
6000
+ if (codeImport.importedSymbols.length === 0) {
6001
+ for (const targetSymbol of targetAnalysis.code.symbols.filter((symbol) => symbol.exported)) {
6002
+ importedSymbolIdsByName.set(targetSymbol.name, targetSymbol.id);
6003
+ }
6004
+ }
5291
6005
  for (const importedSymbol of codeImport.importedSymbols) {
5292
6006
  const [rawExportedName, rawLocalName] = importedSymbol.split(/\s+as\s+/i);
5293
6007
  const exportedName = (rawExportedName ?? "").trim();
@@ -5349,7 +6063,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects) {
5349
6063
  }
5350
6064
  }
5351
6065
  for (const codeImport of analysis.code.imports) {
5352
- const targetSourceId = resolveCodeImportSourceId(manifest, codeImport.specifier, manifests);
6066
+ const targetSourceId = codeImport.resolvedSourceId;
5353
6067
  if (!targetSourceId) {
5354
6068
  continue;
5355
6069
  }
@@ -5417,7 +6131,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects) {
5417
6131
  };
5418
6132
  }
5419
6133
  async function writePage(wikiDir, relativePath, content, changedPages) {
5420
- const absolutePath = path13.resolve(wikiDir, relativePath);
6134
+ const absolutePath = path14.resolve(wikiDir, relativePath);
5421
6135
  const changed = await writeFileIfChanged(absolutePath, content);
5422
6136
  if (changed) {
5423
6137
  changedPages.push(relativePath);
@@ -5477,15 +6191,16 @@ function recordsEqual(left, right) {
5477
6191
  async function requiredCompileArtifactsExist(paths) {
5478
6192
  const requiredPaths = [
5479
6193
  paths.graphPath,
6194
+ paths.codeIndexPath,
5480
6195
  paths.searchDbPath,
5481
- path13.join(paths.wikiDir, "index.md"),
5482
- path13.join(paths.wikiDir, "sources", "index.md"),
5483
- path13.join(paths.wikiDir, "code", "index.md"),
5484
- path13.join(paths.wikiDir, "concepts", "index.md"),
5485
- path13.join(paths.wikiDir, "entities", "index.md"),
5486
- path13.join(paths.wikiDir, "outputs", "index.md"),
5487
- path13.join(paths.wikiDir, "projects", "index.md"),
5488
- path13.join(paths.wikiDir, "candidates", "index.md")
6196
+ path14.join(paths.wikiDir, "index.md"),
6197
+ path14.join(paths.wikiDir, "sources", "index.md"),
6198
+ path14.join(paths.wikiDir, "code", "index.md"),
6199
+ path14.join(paths.wikiDir, "concepts", "index.md"),
6200
+ path14.join(paths.wikiDir, "entities", "index.md"),
6201
+ path14.join(paths.wikiDir, "outputs", "index.md"),
6202
+ path14.join(paths.wikiDir, "projects", "index.md"),
6203
+ path14.join(paths.wikiDir, "candidates", "index.md")
5489
6204
  ];
5490
6205
  const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
5491
6206
  return checks.every(Boolean);
@@ -5493,7 +6208,7 @@ async function requiredCompileArtifactsExist(paths) {
5493
6208
  async function loadCachedAnalyses(paths, manifests) {
5494
6209
  return Promise.all(
5495
6210
  manifests.map(async (manifest) => {
5496
- const cached = await readJsonFile(path13.join(paths.analysesDir, `${manifest.sourceId}.json`));
6211
+ const cached = await readJsonFile(path14.join(paths.analysesDir, `${manifest.sourceId}.json`));
5497
6212
  if (!cached) {
5498
6213
  throw new Error(`Missing cached analysis for ${manifest.sourceId}. Run \`swarmvault compile\` first.`);
5499
6214
  }
@@ -5502,10 +6217,10 @@ async function loadCachedAnalyses(paths, manifests) {
5502
6217
  );
5503
6218
  }
5504
6219
  function approvalManifestPath(paths, approvalId) {
5505
- return path13.join(paths.approvalsDir, approvalId, "manifest.json");
6220
+ return path14.join(paths.approvalsDir, approvalId, "manifest.json");
5506
6221
  }
5507
6222
  function approvalGraphPath(paths, approvalId) {
5508
- return path13.join(paths.approvalsDir, approvalId, "state", "graph.json");
6223
+ return path14.join(paths.approvalsDir, approvalId, "state", "graph.json");
5509
6224
  }
5510
6225
  async function readApprovalManifest(paths, approvalId) {
5511
6226
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
@@ -5515,7 +6230,7 @@ async function readApprovalManifest(paths, approvalId) {
5515
6230
  return manifest;
5516
6231
  }
5517
6232
  async function writeApprovalManifest(paths, manifest) {
5518
- await fs9.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
6233
+ await fs11.writeFile(approvalManifestPath(paths, manifest.approvalId), `${JSON.stringify(manifest, null, 2)}
5519
6234
  `, "utf8");
5520
6235
  }
5521
6236
  async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
@@ -5530,7 +6245,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
5530
6245
  continue;
5531
6246
  }
5532
6247
  const previousPage = previousPagesById.get(nextPage.id);
5533
- const currentExists = await fileExists(path13.join(paths.wikiDir, file.relativePath));
6248
+ const currentExists = await fileExists(path14.join(paths.wikiDir, file.relativePath));
5534
6249
  if (previousPage && previousPage.path !== nextPage.path) {
5535
6250
  entries.push({
5536
6251
  pageId: nextPage.id,
@@ -5563,7 +6278,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
5563
6278
  const previousPage = previousPagesByPath.get(deletedPath);
5564
6279
  entries.push({
5565
6280
  pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
5566
- title: previousPage?.title ?? path13.basename(deletedPath, ".md"),
6281
+ title: previousPage?.title ?? path14.basename(deletedPath, ".md"),
5567
6282
  kind: previousPage?.kind ?? "index",
5568
6283
  changeType: "delete",
5569
6284
  status: "pending",
@@ -5575,16 +6290,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
5575
6290
  }
5576
6291
  async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
5577
6292
  const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
5578
- const approvalDir = path13.join(paths.approvalsDir, approvalId);
6293
+ const approvalDir = path14.join(paths.approvalsDir, approvalId);
5579
6294
  await ensureDir(approvalDir);
5580
- await ensureDir(path13.join(approvalDir, "wiki"));
5581
- await ensureDir(path13.join(approvalDir, "state"));
6295
+ await ensureDir(path14.join(approvalDir, "wiki"));
6296
+ await ensureDir(path14.join(approvalDir, "state"));
5582
6297
  for (const file of changedFiles) {
5583
- const targetPath = path13.join(approvalDir, "wiki", file.relativePath);
5584
- await ensureDir(path13.dirname(targetPath));
5585
- await fs9.writeFile(targetPath, file.content, "utf8");
6298
+ const targetPath = path14.join(approvalDir, "wiki", file.relativePath);
6299
+ await ensureDir(path14.dirname(targetPath));
6300
+ await fs11.writeFile(targetPath, file.content, "utf8");
5586
6301
  }
5587
- await fs9.writeFile(path13.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
6302
+ await fs11.writeFile(path14.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
5588
6303
  await writeApprovalManifest(paths, {
5589
6304
  approvalId,
5590
6305
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5644,7 +6359,7 @@ async function syncVaultArtifacts(rootDir, input) {
5644
6359
  confidence: 1
5645
6360
  });
5646
6361
  const sourceRecord = await buildManagedGraphPage(
5647
- path13.join(paths.wikiDir, preview.path),
6362
+ path14.join(paths.wikiDir, preview.path),
5648
6363
  {
5649
6364
  managedBy: "system",
5650
6365
  confidence: 1,
@@ -5666,7 +6381,7 @@ async function syncVaultArtifacts(rootDir, input) {
5666
6381
  records.push(sourceRecord);
5667
6382
  if (modulePreview && analysis.code) {
5668
6383
  const localModules = analysis.code.imports.map((codeImport) => {
5669
- const resolvedSourceId = resolveCodeImportSourceId(manifest, codeImport.specifier, input.manifests);
6384
+ const resolvedSourceId = codeImport.resolvedSourceId;
5670
6385
  if (!resolvedSourceId) {
5671
6386
  return null;
5672
6387
  }
@@ -5689,7 +6404,7 @@ async function syncVaultArtifacts(rootDir, input) {
5689
6404
  );
5690
6405
  records.push(
5691
6406
  await buildManagedGraphPage(
5692
- path13.join(paths.wikiDir, modulePreview.path),
6407
+ path14.join(paths.wikiDir, modulePreview.path),
5693
6408
  {
5694
6409
  managedBy: "system",
5695
6410
  confidence: 1,
@@ -5722,8 +6437,8 @@ async function syncVaultArtifacts(rootDir, input) {
5722
6437
  const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
5723
6438
  const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
5724
6439
  const fallbackPaths = [
5725
- path13.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
5726
- path13.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
6440
+ path14.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
6441
+ path14.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
5727
6442
  ];
5728
6443
  const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
5729
6444
  const preview = emptyGraphPage({
@@ -5740,7 +6455,7 @@ async function syncVaultArtifacts(rootDir, input) {
5740
6455
  status: promoted ? "active" : "candidate"
5741
6456
  });
5742
6457
  const pageRecord = await buildManagedGraphPage(
5743
- path13.join(paths.wikiDir, relativePath),
6458
+ path14.join(paths.wikiDir, relativePath),
5744
6459
  {
5745
6460
  status: promoted ? "active" : "candidate",
5746
6461
  managedBy: "system",
@@ -5780,7 +6495,7 @@ async function syncVaultArtifacts(rootDir, input) {
5780
6495
  }
5781
6496
  const compiledPages = records.map((record) => record.page);
5782
6497
  const allPages = [...compiledPages, ...input.outputPages, ...input.insightPages];
5783
- const graph = buildGraph(input.manifests, input.analyses, allPages, input.sourceProjects);
6498
+ const graph = buildGraph(input.manifests, input.analyses, allPages, input.sourceProjects, input.codeIndex);
5784
6499
  const activeConceptPages = allPages.filter((page) => page.kind === "concept" && page.status !== "candidate");
5785
6500
  const activeEntityPages = allPages.filter((page) => page.kind === "entity" && page.status !== "candidate");
5786
6501
  const modulePages = allPages.filter((page) => page.kind === "module");
@@ -5814,7 +6529,7 @@ async function syncVaultArtifacts(rootDir, input) {
5814
6529
  confidence: 1
5815
6530
  }),
5816
6531
  content: await buildManagedContent(
5817
- path13.join(paths.wikiDir, "projects", "index.md"),
6532
+ path14.join(paths.wikiDir, "projects", "index.md"),
5818
6533
  {
5819
6534
  managedBy: "system",
5820
6535
  compiledFrom: indexCompiledFrom(projectIndexRefs)
@@ -5838,7 +6553,7 @@ async function syncVaultArtifacts(rootDir, input) {
5838
6553
  records.push({
5839
6554
  page: projectIndexRef,
5840
6555
  content: await buildManagedContent(
5841
- path13.join(paths.wikiDir, projectIndexRef.path),
6556
+ path14.join(paths.wikiDir, projectIndexRef.path),
5842
6557
  {
5843
6558
  managedBy: "system",
5844
6559
  compiledFrom: indexCompiledFrom(Object.values(sections).flat())
@@ -5866,7 +6581,7 @@ async function syncVaultArtifacts(rootDir, input) {
5866
6581
  confidence: 1
5867
6582
  }),
5868
6583
  content: await buildManagedContent(
5869
- path13.join(paths.wikiDir, "index.md"),
6584
+ path14.join(paths.wikiDir, "index.md"),
5870
6585
  {
5871
6586
  managedBy: "system",
5872
6587
  compiledFrom: indexCompiledFrom(allPages)
@@ -5896,7 +6611,7 @@ async function syncVaultArtifacts(rootDir, input) {
5896
6611
  confidence: 1
5897
6612
  }),
5898
6613
  content: await buildManagedContent(
5899
- path13.join(paths.wikiDir, relativePath),
6614
+ path14.join(paths.wikiDir, relativePath),
5900
6615
  {
5901
6616
  managedBy: "system",
5902
6617
  compiledFrom: indexCompiledFrom(pages)
@@ -5907,12 +6622,12 @@ async function syncVaultArtifacts(rootDir, input) {
5907
6622
  }
5908
6623
  const nextPagePaths = new Set(records.map((record) => record.page.path));
5909
6624
  const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
5910
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path13.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
6625
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path14.relative(paths.wikiDir, absolutePath))).filter((relativePath) => !nextPagePaths.has(relativePath));
5911
6626
  const obsoletePaths = uniqueStrings2([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
5912
6627
  const changedFiles = [];
5913
6628
  for (const record of records) {
5914
- const absolutePath = path13.join(paths.wikiDir, record.page.path);
5915
- const current = await fileExists(absolutePath) ? await fs9.readFile(absolutePath, "utf8") : null;
6629
+ const absolutePath = path14.join(paths.wikiDir, record.page.path);
6630
+ const current = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : null;
5916
6631
  if (current !== record.content) {
5917
6632
  changedPages.push(record.page.path);
5918
6633
  changedFiles.push({ relativePath: record.page.path, content: record.content });
@@ -5937,9 +6652,10 @@ async function syncVaultArtifacts(rootDir, input) {
5937
6652
  await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
5938
6653
  }
5939
6654
  for (const relativePath of obsoletePaths) {
5940
- await fs9.rm(path13.join(paths.wikiDir, relativePath), { force: true });
6655
+ await fs11.rm(path14.join(paths.wikiDir, relativePath), { force: true });
5941
6656
  }
5942
6657
  await writeJsonFile(paths.graphPath, graph);
6658
+ await writeJsonFile(paths.codeIndexPath, input.codeIndex);
5943
6659
  await writeJsonFile(paths.compileStatePath, {
5944
6660
  generatedAt: graph.generatedAt,
5945
6661
  rootSchemaHash: input.schemas.root.hash,
@@ -5990,15 +6706,15 @@ async function refreshIndexesAndSearch(rootDir, pages) {
5990
6706
  })
5991
6707
  );
5992
6708
  await Promise.all([
5993
- ensureDir(path13.join(paths.wikiDir, "sources")),
5994
- ensureDir(path13.join(paths.wikiDir, "code")),
5995
- ensureDir(path13.join(paths.wikiDir, "concepts")),
5996
- ensureDir(path13.join(paths.wikiDir, "entities")),
5997
- ensureDir(path13.join(paths.wikiDir, "outputs")),
5998
- ensureDir(path13.join(paths.wikiDir, "projects")),
5999
- ensureDir(path13.join(paths.wikiDir, "candidates"))
6709
+ ensureDir(path14.join(paths.wikiDir, "sources")),
6710
+ ensureDir(path14.join(paths.wikiDir, "code")),
6711
+ ensureDir(path14.join(paths.wikiDir, "concepts")),
6712
+ ensureDir(path14.join(paths.wikiDir, "entities")),
6713
+ ensureDir(path14.join(paths.wikiDir, "outputs")),
6714
+ ensureDir(path14.join(paths.wikiDir, "projects")),
6715
+ ensureDir(path14.join(paths.wikiDir, "candidates"))
6000
6716
  ]);
6001
- const projectsIndexPath = path13.join(paths.wikiDir, "projects", "index.md");
6717
+ const projectsIndexPath = path14.join(paths.wikiDir, "projects", "index.md");
6002
6718
  await writeFileIfChanged(
6003
6719
  projectsIndexPath,
6004
6720
  await buildManagedContent(
@@ -6019,7 +6735,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6019
6735
  outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
6020
6736
  candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
6021
6737
  };
6022
- const absolutePath = path13.join(paths.wikiDir, "projects", project.id, "index.md");
6738
+ const absolutePath = path14.join(paths.wikiDir, "projects", project.id, "index.md");
6023
6739
  await writeFileIfChanged(
6024
6740
  absolutePath,
6025
6741
  await buildManagedContent(
@@ -6037,7 +6753,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6037
6753
  )
6038
6754
  );
6039
6755
  }
6040
- const rootIndexPath = path13.join(paths.wikiDir, "index.md");
6756
+ const rootIndexPath = path14.join(paths.wikiDir, "index.md");
6041
6757
  await writeFileIfChanged(
6042
6758
  rootIndexPath,
6043
6759
  await buildManagedContent(
@@ -6057,7 +6773,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6057
6773
  ["outputs/index.md", "outputs", pages.filter((page) => page.kind === "output")],
6058
6774
  ["candidates/index.md", "candidates", pages.filter((page) => page.status === "candidate")]
6059
6775
  ]) {
6060
- const absolutePath = path13.join(paths.wikiDir, relativePath);
6776
+ const absolutePath = path14.join(paths.wikiDir, relativePath);
6061
6777
  await writeFileIfChanged(
6062
6778
  absolutePath,
6063
6779
  await buildManagedContent(
@@ -6070,13 +6786,13 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6070
6786
  )
6071
6787
  );
6072
6788
  }
6073
- const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path13.relative(paths.wikiDir, absolutePath)));
6789
+ const existingProjectIndexPaths = (await listFilesRecursive(paths.projectsDir)).filter((absolutePath) => absolutePath.endsWith(".md")).map((absolutePath) => toPosix(path14.relative(paths.wikiDir, absolutePath)));
6074
6790
  const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
6075
6791
  "projects/index.md",
6076
6792
  ...configuredProjects.map((project) => `projects/${project.id}/index.md`)
6077
6793
  ]);
6078
6794
  await Promise.all(
6079
- existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs9.rm(path13.join(paths.wikiDir, relativePath), { force: true }))
6795
+ existingProjectIndexPaths.filter((relativePath) => !allowedProjectIndexPaths.has(relativePath)).map((relativePath) => fs11.rm(path14.join(paths.wikiDir, relativePath), { force: true }))
6080
6796
  );
6081
6797
  await rebuildSearchIndex(paths.searchDbPath, pages, paths.wikiDir);
6082
6798
  }
@@ -6096,7 +6812,7 @@ async function prepareOutputPageSave(rootDir, input) {
6096
6812
  confidence: 0.74
6097
6813
  }
6098
6814
  });
6099
- const absolutePath = path13.join(paths.wikiDir, output.page.path);
6815
+ const absolutePath = path14.join(paths.wikiDir, output.page.path);
6100
6816
  return {
6101
6817
  page: output.page,
6102
6818
  savedPath: absolutePath,
@@ -6108,15 +6824,15 @@ async function prepareOutputPageSave(rootDir, input) {
6108
6824
  async function persistOutputPage(rootDir, input) {
6109
6825
  const { paths } = await loadVaultConfig(rootDir);
6110
6826
  const prepared = await prepareOutputPageSave(rootDir, input);
6111
- await ensureDir(path13.dirname(prepared.savedPath));
6112
- await fs9.writeFile(prepared.savedPath, prepared.content, "utf8");
6827
+ await ensureDir(path14.dirname(prepared.savedPath));
6828
+ await fs11.writeFile(prepared.savedPath, prepared.content, "utf8");
6113
6829
  for (const assetFile of prepared.assetFiles) {
6114
- const assetPath = path13.join(paths.wikiDir, assetFile.relativePath);
6115
- await ensureDir(path13.dirname(assetPath));
6830
+ const assetPath = path14.join(paths.wikiDir, assetFile.relativePath);
6831
+ await ensureDir(path14.dirname(assetPath));
6116
6832
  if (typeof assetFile.content === "string") {
6117
- await fs9.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6833
+ await fs11.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6118
6834
  } else {
6119
- await fs9.writeFile(assetPath, assetFile.content);
6835
+ await fs11.writeFile(assetPath, assetFile.content);
6120
6836
  }
6121
6837
  }
6122
6838
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -6137,7 +6853,7 @@ async function prepareExploreHubSave(rootDir, input) {
6137
6853
  confidence: 0.76
6138
6854
  }
6139
6855
  });
6140
- const absolutePath = path13.join(paths.wikiDir, hub.page.path);
6856
+ const absolutePath = path14.join(paths.wikiDir, hub.page.path);
6141
6857
  return {
6142
6858
  page: hub.page,
6143
6859
  savedPath: absolutePath,
@@ -6149,15 +6865,15 @@ async function prepareExploreHubSave(rootDir, input) {
6149
6865
  async function persistExploreHub(rootDir, input) {
6150
6866
  const { paths } = await loadVaultConfig(rootDir);
6151
6867
  const prepared = await prepareExploreHubSave(rootDir, input);
6152
- await ensureDir(path13.dirname(prepared.savedPath));
6153
- await fs9.writeFile(prepared.savedPath, prepared.content, "utf8");
6868
+ await ensureDir(path14.dirname(prepared.savedPath));
6869
+ await fs11.writeFile(prepared.savedPath, prepared.content, "utf8");
6154
6870
  for (const assetFile of prepared.assetFiles) {
6155
- const assetPath = path13.join(paths.wikiDir, assetFile.relativePath);
6156
- await ensureDir(path13.dirname(assetPath));
6871
+ const assetPath = path14.join(paths.wikiDir, assetFile.relativePath);
6872
+ await ensureDir(path14.dirname(assetPath));
6157
6873
  if (typeof assetFile.content === "string") {
6158
- await fs9.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6874
+ await fs11.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6159
6875
  } else {
6160
- await fs9.writeFile(assetPath, assetFile.content);
6876
+ await fs11.writeFile(assetPath, assetFile.content);
6161
6877
  }
6162
6878
  }
6163
6879
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -6174,17 +6890,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
6174
6890
  }))
6175
6891
  ]);
6176
6892
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
6177
- const approvalDir = path13.join(paths.approvalsDir, approvalId);
6893
+ const approvalDir = path14.join(paths.approvalsDir, approvalId);
6178
6894
  await ensureDir(approvalDir);
6179
- await ensureDir(path13.join(approvalDir, "wiki"));
6180
- await ensureDir(path13.join(approvalDir, "state"));
6895
+ await ensureDir(path14.join(approvalDir, "wiki"));
6896
+ await ensureDir(path14.join(approvalDir, "state"));
6181
6897
  for (const file of changedFiles) {
6182
- const targetPath = path13.join(approvalDir, "wiki", file.relativePath);
6183
- await ensureDir(path13.dirname(targetPath));
6898
+ const targetPath = path14.join(approvalDir, "wiki", file.relativePath);
6899
+ await ensureDir(path14.dirname(targetPath));
6184
6900
  if ("binary" in file && file.binary) {
6185
- await fs9.writeFile(targetPath, Buffer.from(file.content, "base64"));
6901
+ await fs11.writeFile(targetPath, Buffer.from(file.content, "base64"));
6186
6902
  } else {
6187
- await fs9.writeFile(targetPath, file.content, "utf8");
6903
+ await fs11.writeFile(targetPath, file.content, "utf8");
6188
6904
  }
6189
6905
  }
6190
6906
  const nextPages = sortGraphPages([
@@ -6198,7 +6914,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
6198
6914
  sources: previousGraph?.sources ?? [],
6199
6915
  pages: nextPages
6200
6916
  };
6201
- await fs9.writeFile(path13.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
6917
+ await fs11.writeFile(path14.join(approvalDir, "state", "graph.json"), JSON.stringify(graph, null, 2), "utf8");
6202
6918
  await writeApprovalManifest(paths, {
6203
6919
  approvalId,
6204
6920
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6227,9 +6943,9 @@ async function executeQuery(rootDir, question, format) {
6227
6943
  const searchResults = searchPages(paths.searchDbPath, question, 5);
6228
6944
  const excerpts = await Promise.all(
6229
6945
  searchResults.map(async (result) => {
6230
- const absolutePath = path13.join(paths.wikiDir, result.path);
6946
+ const absolutePath = path14.join(paths.wikiDir, result.path);
6231
6947
  try {
6232
- const content = await fs9.readFile(absolutePath, "utf8");
6948
+ const content = await fs11.readFile(absolutePath, "utf8");
6233
6949
  const parsed = matter7(content);
6234
6950
  return `# ${result.title}
6235
6951
  ${truncate(normalizeWhitespace(parsed.content), 1200)}`;
@@ -6338,13 +7054,19 @@ async function refreshVaultAfterOutputSave(rootDir) {
6338
7054
  const schemas = await loadVaultSchemas(rootDir);
6339
7055
  const manifests = await listManifests(rootDir);
6340
7056
  const sourceProjects = resolveSourceProjects(rootDir, manifests, config);
6341
- const analyses = manifests.length ? await loadCachedAnalyses(paths, manifests) : [];
7057
+ const cachedAnalyses = manifests.length ? await loadCachedAnalyses(paths, manifests) : [];
7058
+ const codeIndex = await buildCodeIndex(rootDir, manifests, cachedAnalyses);
7059
+ const analyses = cachedAnalyses.map((analysis) => {
7060
+ const manifest = manifests.find((item) => item.sourceId === analysis.sourceId);
7061
+ return manifest ? enrichResolvedCodeImports(manifest, analysis, codeIndex) : analysis;
7062
+ });
6342
7063
  const storedOutputs = await loadSavedOutputPages(paths.wikiDir);
6343
7064
  const storedInsights = await loadInsightPages(paths.wikiDir);
6344
7065
  await syncVaultArtifacts(rootDir, {
6345
7066
  schemas,
6346
7067
  manifests,
6347
7068
  analyses,
7069
+ codeIndex,
6348
7070
  sourceProjects,
6349
7071
  outputPages: storedOutputs.map((page) => page.page),
6350
7072
  insightPages: storedInsights.map((page) => page.page),
@@ -6405,7 +7127,7 @@ function sortGraphPages(pages) {
6405
7127
  async function listApprovals(rootDir) {
6406
7128
  const { paths } = await loadVaultConfig(rootDir);
6407
7129
  const manifests = await Promise.all(
6408
- (await fs9.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
7130
+ (await fs11.readdir(paths.approvalsDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map(async (entry) => {
6409
7131
  try {
6410
7132
  return await readApprovalManifest(paths, entry.name);
6411
7133
  } catch {
@@ -6421,8 +7143,8 @@ async function readApproval(rootDir, approvalId) {
6421
7143
  const details = await Promise.all(
6422
7144
  manifest.entries.map(async (entry) => {
6423
7145
  const currentPath = entry.previousPath ?? entry.nextPath;
6424
- const currentContent = currentPath ? await fs9.readFile(path13.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
6425
- const stagedContent = entry.nextPath ? await fs9.readFile(path13.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
7146
+ const currentContent = currentPath ? await fs11.readFile(path14.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
7147
+ const stagedContent = entry.nextPath ? await fs11.readFile(path14.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath), "utf8").catch(() => void 0) : void 0;
6426
7148
  return {
6427
7149
  ...entry,
6428
7150
  currentContent,
@@ -6450,26 +7172,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
6450
7172
  if (!entry.nextPath) {
6451
7173
  throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
6452
7174
  }
6453
- const stagedAbsolutePath = path13.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
6454
- const stagedContent = await fs9.readFile(stagedAbsolutePath, "utf8");
6455
- const targetAbsolutePath = path13.join(paths.wikiDir, entry.nextPath);
6456
- await ensureDir(path13.dirname(targetAbsolutePath));
6457
- await fs9.writeFile(targetAbsolutePath, stagedContent, "utf8");
7175
+ const stagedAbsolutePath = path14.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
7176
+ const stagedContent = await fs11.readFile(stagedAbsolutePath, "utf8");
7177
+ const targetAbsolutePath = path14.join(paths.wikiDir, entry.nextPath);
7178
+ await ensureDir(path14.dirname(targetAbsolutePath));
7179
+ await fs11.writeFile(targetAbsolutePath, stagedContent, "utf8");
6458
7180
  if (entry.changeType === "promote" && entry.previousPath) {
6459
- await fs9.rm(path13.join(paths.wikiDir, entry.previousPath), { force: true });
7181
+ await fs11.rm(path14.join(paths.wikiDir, entry.previousPath), { force: true });
6460
7182
  }
6461
7183
  const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
6462
7184
  if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
6463
- const outputAssetDir = path13.join(paths.wikiDir, "outputs", "assets", path13.basename(nextPage.path, ".md"));
6464
- await fs9.rm(outputAssetDir, { recursive: true, force: true });
7185
+ const outputAssetDir = path14.join(paths.wikiDir, "outputs", "assets", path14.basename(nextPage.path, ".md"));
7186
+ await fs11.rm(outputAssetDir, { recursive: true, force: true });
6465
7187
  for (const asset of nextPage.outputAssets) {
6466
- const stagedAssetPath = path13.join(paths.approvalsDir, approvalId, "wiki", asset.path);
7188
+ const stagedAssetPath = path14.join(paths.approvalsDir, approvalId, "wiki", asset.path);
6467
7189
  if (!await fileExists(stagedAssetPath)) {
6468
7190
  continue;
6469
7191
  }
6470
- const targetAssetPath = path13.join(paths.wikiDir, asset.path);
6471
- await ensureDir(path13.dirname(targetAssetPath));
6472
- await fs9.copyFile(stagedAssetPath, targetAssetPath);
7192
+ const targetAssetPath = path14.join(paths.wikiDir, asset.path);
7193
+ await ensureDir(path14.dirname(targetAssetPath));
7194
+ await fs11.copyFile(stagedAssetPath, targetAssetPath);
6473
7195
  }
6474
7196
  }
6475
7197
  nextPages = nextPages.filter(
@@ -6480,10 +7202,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
6480
7202
  } else {
6481
7203
  const deletedPage = nextPages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? bundleGraph?.pages.find((page) => page.id === entry.pageId || page.path === entry.previousPath) ?? null;
6482
7204
  if (entry.previousPath) {
6483
- await fs9.rm(path13.join(paths.wikiDir, entry.previousPath), { force: true });
7205
+ await fs11.rm(path14.join(paths.wikiDir, entry.previousPath), { force: true });
6484
7206
  }
6485
7207
  if (deletedPage?.kind === "output") {
6486
- await fs9.rm(path13.join(paths.wikiDir, "outputs", "assets", path13.basename(deletedPage.path, ".md")), {
7208
+ await fs11.rm(path14.join(paths.wikiDir, "outputs", "assets", path14.basename(deletedPage.path, ".md")), {
6487
7209
  recursive: true,
6488
7210
  force: true
6489
7211
  });
@@ -6573,7 +7295,7 @@ async function promoteCandidate(rootDir, target) {
6573
7295
  const { paths } = await loadVaultConfig(rootDir);
6574
7296
  const graph = await readJsonFile(paths.graphPath);
6575
7297
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
6576
- const raw = await fs9.readFile(path13.join(paths.wikiDir, candidate.path), "utf8");
7298
+ const raw = await fs11.readFile(path14.join(paths.wikiDir, candidate.path), "utf8");
6577
7299
  const parsed = matter7(raw);
6578
7300
  const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
6579
7301
  const nextContent = matter7.stringify(parsed.content, {
@@ -6585,10 +7307,10 @@ async function promoteCandidate(rootDir, target) {
6585
7307
  )
6586
7308
  });
6587
7309
  const nextPath = candidateActivePath(candidate);
6588
- const nextAbsolutePath = path13.join(paths.wikiDir, nextPath);
6589
- await ensureDir(path13.dirname(nextAbsolutePath));
6590
- await fs9.writeFile(nextAbsolutePath, nextContent, "utf8");
6591
- await fs9.rm(path13.join(paths.wikiDir, candidate.path), { force: true });
7310
+ const nextAbsolutePath = path14.join(paths.wikiDir, nextPath);
7311
+ await ensureDir(path14.dirname(nextAbsolutePath));
7312
+ await fs11.writeFile(nextAbsolutePath, nextContent, "utf8");
7313
+ await fs11.rm(path14.join(paths.wikiDir, candidate.path), { force: true });
6592
7314
  const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
6593
7315
  const nextPages = sortGraphPages(
6594
7316
  (graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
@@ -6632,7 +7354,7 @@ async function archiveCandidate(rootDir, target) {
6632
7354
  const { paths } = await loadVaultConfig(rootDir);
6633
7355
  const graph = await readJsonFile(paths.graphPath);
6634
7356
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
6635
- await fs9.rm(path13.join(paths.wikiDir, candidate.path), { force: true });
7357
+ await fs11.rm(path14.join(paths.wikiDir, candidate.path), { force: true });
6636
7358
  const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
6637
7359
  const nextGraph = {
6638
7360
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6670,18 +7392,18 @@ async function archiveCandidate(rootDir, target) {
6670
7392
  }
6671
7393
  async function ensureObsidianWorkspace(rootDir) {
6672
7394
  const { config } = await loadVaultConfig(rootDir);
6673
- const obsidianDir = path13.join(rootDir, ".obsidian");
7395
+ const obsidianDir = path14.join(rootDir, ".obsidian");
6674
7396
  const projectIds = projectEntries(config).map((project) => project.id);
6675
7397
  await ensureDir(obsidianDir);
6676
7398
  await Promise.all([
6677
- writeJsonFile(path13.join(obsidianDir, "app.json"), {
7399
+ writeJsonFile(path14.join(obsidianDir, "app.json"), {
6678
7400
  alwaysUpdateLinks: true,
6679
7401
  newFileLocation: "folder",
6680
7402
  newFileFolderPath: "wiki/insights",
6681
7403
  useMarkdownLinks: false,
6682
7404
  attachmentFolderPath: "raw/assets"
6683
7405
  }),
6684
- writeJsonFile(path13.join(obsidianDir, "core-plugins.json"), [
7406
+ writeJsonFile(path14.join(obsidianDir, "core-plugins.json"), [
6685
7407
  "file-explorer",
6686
7408
  "global-search",
6687
7409
  "switcher",
@@ -6691,7 +7413,7 @@ async function ensureObsidianWorkspace(rootDir) {
6691
7413
  "tag-pane",
6692
7414
  "page-preview"
6693
7415
  ]),
6694
- writeJsonFile(path13.join(obsidianDir, "graph.json"), {
7416
+ writeJsonFile(path14.join(obsidianDir, "graph.json"), {
6695
7417
  "collapse-filter": false,
6696
7418
  search: "",
6697
7419
  showTags: true,
@@ -6703,7 +7425,7 @@ async function ensureObsidianWorkspace(rootDir) {
6703
7425
  })),
6704
7426
  localJumps: false
6705
7427
  }),
6706
- writeJsonFile(path13.join(obsidianDir, "workspace.json"), {
7428
+ writeJsonFile(path14.join(obsidianDir, "workspace.json"), {
6707
7429
  active: "root",
6708
7430
  lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
6709
7431
  left: {
@@ -6718,7 +7440,7 @@ async function ensureObsidianWorkspace(rootDir) {
6718
7440
  async function initVault(rootDir, options = {}) {
6719
7441
  const { paths } = await initWorkspace(rootDir);
6720
7442
  await installConfiguredAgents(rootDir);
6721
- const insightsIndexPath = path13.join(paths.wikiDir, "insights", "index.md");
7443
+ const insightsIndexPath = path14.join(paths.wikiDir, "insights", "index.md");
6722
7444
  const now = (/* @__PURE__ */ new Date()).toISOString();
6723
7445
  await writeFileIfChanged(
6724
7446
  insightsIndexPath,
@@ -6754,7 +7476,7 @@ async function initVault(rootDir, options = {}) {
6754
7476
  )
6755
7477
  );
6756
7478
  await writeFileIfChanged(
6757
- path13.join(paths.wikiDir, "projects", "index.md"),
7479
+ path14.join(paths.wikiDir, "projects", "index.md"),
6758
7480
  matter7.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
6759
7481
  page_id: "projects:index",
6760
7482
  kind: "index",
@@ -6776,7 +7498,7 @@ async function initVault(rootDir, options = {}) {
6776
7498
  })
6777
7499
  );
6778
7500
  await writeFileIfChanged(
6779
- path13.join(paths.wikiDir, "candidates", "index.md"),
7501
+ path14.join(paths.wikiDir, "candidates", "index.md"),
6780
7502
  matter7.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
6781
7503
  page_id: "candidates:index",
6782
7504
  kind: "index",
@@ -6893,7 +7615,7 @@ async function compileVault(rootDir, options = {}) {
6893
7615
  ),
6894
7616
  Promise.all(
6895
7617
  clean.map(async (manifest) => {
6896
- const cached = await readJsonFile(path13.join(paths.analysesDir, `${manifest.sourceId}.json`));
7618
+ const cached = await readJsonFile(path14.join(paths.analysesDir, `${manifest.sourceId}.json`));
6897
7619
  if (cached) {
6898
7620
  return cached;
6899
7621
  }
@@ -6907,23 +7629,38 @@ async function compileVault(rootDir, options = {}) {
6907
7629
  })
6908
7630
  )
6909
7631
  ]);
6910
- const analyses = [...dirtyAnalyses, ...cleanAnalyses];
7632
+ const initialAnalyses = [...dirtyAnalyses, ...cleanAnalyses];
7633
+ const codeIndex = await buildCodeIndex(rootDir, manifests, initialAnalyses);
7634
+ const analyses = await Promise.all(
7635
+ initialAnalyses.map(async (analysis) => {
7636
+ const manifest = manifests.find((item) => item.sourceId === analysis.sourceId);
7637
+ if (!manifest || !analysis.code) {
7638
+ return analysis;
7639
+ }
7640
+ const enriched = enrichResolvedCodeImports(manifest, analysis, codeIndex);
7641
+ if (analysisSignature(enriched) !== analysisSignature(analysis)) {
7642
+ await writeJsonFile(path14.join(paths.analysesDir, `${analysis.sourceId}.json`), enriched);
7643
+ }
7644
+ return enriched;
7645
+ })
7646
+ );
6911
7647
  await Promise.all([
6912
- ensureDir(path13.join(paths.wikiDir, "sources")),
6913
- ensureDir(path13.join(paths.wikiDir, "code")),
6914
- ensureDir(path13.join(paths.wikiDir, "concepts")),
6915
- ensureDir(path13.join(paths.wikiDir, "entities")),
6916
- ensureDir(path13.join(paths.wikiDir, "outputs")),
6917
- ensureDir(path13.join(paths.wikiDir, "projects")),
6918
- ensureDir(path13.join(paths.wikiDir, "insights")),
6919
- ensureDir(path13.join(paths.wikiDir, "candidates")),
6920
- ensureDir(path13.join(paths.wikiDir, "candidates", "concepts")),
6921
- ensureDir(path13.join(paths.wikiDir, "candidates", "entities"))
7648
+ ensureDir(path14.join(paths.wikiDir, "sources")),
7649
+ ensureDir(path14.join(paths.wikiDir, "code")),
7650
+ ensureDir(path14.join(paths.wikiDir, "concepts")),
7651
+ ensureDir(path14.join(paths.wikiDir, "entities")),
7652
+ ensureDir(path14.join(paths.wikiDir, "outputs")),
7653
+ ensureDir(path14.join(paths.wikiDir, "projects")),
7654
+ ensureDir(path14.join(paths.wikiDir, "insights")),
7655
+ ensureDir(path14.join(paths.wikiDir, "candidates")),
7656
+ ensureDir(path14.join(paths.wikiDir, "candidates", "concepts")),
7657
+ ensureDir(path14.join(paths.wikiDir, "candidates", "entities"))
6922
7658
  ]);
6923
7659
  const sync = await syncVaultArtifacts(rootDir, {
6924
7660
  schemas,
6925
7661
  manifests,
6926
7662
  analyses,
7663
+ codeIndex,
6927
7664
  sourceProjects,
6928
7665
  outputPages,
6929
7666
  insightPages,
@@ -7059,7 +7796,7 @@ async function queryVault(rootDir, options) {
7059
7796
  assetFiles: staged.assetFiles
7060
7797
  }
7061
7798
  ]);
7062
- stagedPath = path13.join(approval.approvalDir, "wiki", staged.page.path);
7799
+ stagedPath = path14.join(approval.approvalDir, "wiki", staged.page.path);
7063
7800
  savedPageId = staged.page.id;
7064
7801
  approvalId = approval.approvalId;
7065
7802
  approvalDir = approval.approvalDir;
@@ -7315,9 +8052,9 @@ ${orchestrationNotes.join("\n")}
7315
8052
  approvalId = approval.approvalId;
7316
8053
  approvalDir = approval.approvalDir;
7317
8054
  stepResults.forEach((result, index) => {
7318
- result.stagedPath = path13.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
8055
+ result.stagedPath = path14.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
7319
8056
  });
7320
- stagedHubPath = path13.join(approval.approvalDir, "wiki", hubPage.path);
8057
+ stagedHubPath = path14.join(approval.approvalDir, "wiki", hubPage.path);
7321
8058
  } else {
7322
8059
  await refreshVaultAfterOutputSave(rootDir);
7323
8060
  }
@@ -7374,15 +8111,15 @@ async function listPages(rootDir) {
7374
8111
  }
7375
8112
  async function readPage(rootDir, relativePath) {
7376
8113
  const { paths } = await loadVaultConfig(rootDir);
7377
- const absolutePath = path13.resolve(paths.wikiDir, relativePath);
8114
+ const absolutePath = path14.resolve(paths.wikiDir, relativePath);
7378
8115
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
7379
8116
  return null;
7380
8117
  }
7381
- const raw = await fs9.readFile(absolutePath, "utf8");
8118
+ const raw = await fs11.readFile(absolutePath, "utf8");
7382
8119
  const parsed = matter7(raw);
7383
8120
  return {
7384
8121
  path: relativePath,
7385
- title: typeof parsed.data.title === "string" ? parsed.data.title : path13.basename(relativePath, path13.extname(relativePath)),
8122
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path14.basename(relativePath, path14.extname(relativePath)),
7386
8123
  frontmatter: parsed.data,
7387
8124
  content: parsed.content
7388
8125
  };
@@ -7418,7 +8155,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
7418
8155
  severity: "warning",
7419
8156
  code: "stale_page",
7420
8157
  message: `Page ${page.title} is stale because the vault schema changed.`,
7421
- pagePath: path13.join(paths.wikiDir, page.path),
8158
+ pagePath: path14.join(paths.wikiDir, page.path),
7422
8159
  relatedPageIds: [page.id]
7423
8160
  });
7424
8161
  }
@@ -7429,7 +8166,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
7429
8166
  severity: "warning",
7430
8167
  code: "stale_page",
7431
8168
  message: `Page ${page.title} is stale because source ${sourceId} changed.`,
7432
- pagePath: path13.join(paths.wikiDir, page.path),
8169
+ pagePath: path14.join(paths.wikiDir, page.path),
7433
8170
  relatedSourceIds: [sourceId],
7434
8171
  relatedPageIds: [page.id]
7435
8172
  });
@@ -7440,13 +8177,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
7440
8177
  severity: "info",
7441
8178
  code: "orphan_page",
7442
8179
  message: `Page ${page.title} has no backlinks.`,
7443
- pagePath: path13.join(paths.wikiDir, page.path),
8180
+ pagePath: path14.join(paths.wikiDir, page.path),
7444
8181
  relatedPageIds: [page.id]
7445
8182
  });
7446
8183
  }
7447
- const absolutePath = path13.join(paths.wikiDir, page.path);
8184
+ const absolutePath = path14.join(paths.wikiDir, page.path);
7448
8185
  if (await fileExists(absolutePath)) {
7449
- const content = await fs9.readFile(absolutePath, "utf8");
8186
+ const content = await fs11.readFile(absolutePath, "utf8");
7450
8187
  if (content.includes("## Claims")) {
7451
8188
  const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
7452
8189
  if (uncited.length) {
@@ -7526,7 +8263,7 @@ async function bootstrapDemo(rootDir, input) {
7526
8263
  }
7527
8264
 
7528
8265
  // src/mcp.ts
7529
- var SERVER_VERSION = "0.1.17";
8266
+ var SERVER_VERSION = "0.1.18";
7530
8267
  async function createMcpServer(rootDir) {
7531
8268
  const server = new McpServer({
7532
8269
  name: "swarmvault",
@@ -7707,7 +8444,7 @@ async function createMcpServer(rootDir) {
7707
8444
  },
7708
8445
  async () => {
7709
8446
  const { paths } = await loadVaultConfig(rootDir);
7710
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path14.relative(paths.sessionsDir, filePath))).sort();
8447
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path15.relative(paths.sessionsDir, filePath))).sort();
7711
8448
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
7712
8449
  }
7713
8450
  );
@@ -7740,8 +8477,8 @@ async function createMcpServer(rootDir) {
7740
8477
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
7741
8478
  }
7742
8479
  const { paths } = await loadVaultConfig(rootDir);
7743
- const absolutePath = path14.resolve(paths.wikiDir, relativePath);
7744
- return asTextResource(`swarmvault://pages/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
8480
+ const absolutePath = path15.resolve(paths.wikiDir, relativePath);
8481
+ return asTextResource(`swarmvault://pages/${encodedPath}`, await fs12.readFile(absolutePath, "utf8"));
7745
8482
  }
7746
8483
  );
7747
8484
  server.registerResource(
@@ -7749,11 +8486,11 @@ async function createMcpServer(rootDir) {
7749
8486
  new ResourceTemplate("swarmvault://sessions/{path}", {
7750
8487
  list: async () => {
7751
8488
  const { paths } = await loadVaultConfig(rootDir);
7752
- const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path14.relative(paths.sessionsDir, filePath))).sort();
8489
+ const files = (await listFilesRecursive(paths.sessionsDir)).filter((filePath) => filePath.endsWith(".md")).map((filePath) => toPosix(path15.relative(paths.sessionsDir, filePath))).sort();
7753
8490
  return {
7754
8491
  resources: files.map((relativePath) => ({
7755
8492
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
7756
- name: path14.basename(relativePath, ".md"),
8493
+ name: path15.basename(relativePath, ".md"),
7757
8494
  title: relativePath,
7758
8495
  description: "SwarmVault session artifact",
7759
8496
  mimeType: "text/markdown"
@@ -7770,11 +8507,11 @@ async function createMcpServer(rootDir) {
7770
8507
  const { paths } = await loadVaultConfig(rootDir);
7771
8508
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
7772
8509
  const relativePath = decodeURIComponent(encodedPath);
7773
- const absolutePath = path14.resolve(paths.sessionsDir, relativePath);
8510
+ const absolutePath = path15.resolve(paths.sessionsDir, relativePath);
7774
8511
  if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
7775
8512
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
7776
8513
  }
7777
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
8514
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs12.readFile(absolutePath, "utf8"));
7778
8515
  }
7779
8516
  );
7780
8517
  return server;
@@ -7822,13 +8559,13 @@ function asTextResource(uri, text) {
7822
8559
  }
7823
8560
 
7824
8561
  // src/schedule.ts
7825
- import fs11 from "fs/promises";
7826
- import path15 from "path";
8562
+ import fs13 from "fs/promises";
8563
+ import path16 from "path";
7827
8564
  function scheduleStatePath(schedulesDir, jobId) {
7828
- return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
8565
+ return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
7829
8566
  }
7830
8567
  function scheduleLockPath(schedulesDir, jobId) {
7831
- return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
8568
+ return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
7832
8569
  }
7833
8570
  function parseEveryDuration(value) {
7834
8571
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -7931,13 +8668,13 @@ async function acquireJobLease(rootDir, jobId) {
7931
8668
  const { paths } = await loadVaultConfig(rootDir);
7932
8669
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
7933
8670
  await ensureDir(paths.schedulesDir);
7934
- const handle = await fs11.open(leasePath, "wx");
8671
+ const handle = await fs13.open(leasePath, "wx");
7935
8672
  await handle.writeFile(`${process.pid}
7936
8673
  ${(/* @__PURE__ */ new Date()).toISOString()}
7937
8674
  `);
7938
8675
  await handle.close();
7939
8676
  return async () => {
7940
- await fs11.rm(leasePath, { force: true });
8677
+ await fs13.rm(leasePath, { force: true });
7941
8678
  };
7942
8679
  }
7943
8680
  async function listSchedules(rootDir) {
@@ -8085,24 +8822,24 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
8085
8822
 
8086
8823
  // src/viewer.ts
8087
8824
  import { execFile } from "child_process";
8088
- import fs12 from "fs/promises";
8825
+ import fs14 from "fs/promises";
8089
8826
  import http from "http";
8090
- import path16 from "path";
8827
+ import path17 from "path";
8091
8828
  import { promisify } from "util";
8092
8829
  import matter8 from "gray-matter";
8093
8830
  import mime2 from "mime-types";
8094
8831
  var execFileAsync = promisify(execFile);
8095
8832
  async function readViewerPage(rootDir, relativePath) {
8096
8833
  const { paths } = await loadVaultConfig(rootDir);
8097
- const absolutePath = path16.resolve(paths.wikiDir, relativePath);
8834
+ const absolutePath = path17.resolve(paths.wikiDir, relativePath);
8098
8835
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
8099
8836
  return null;
8100
8837
  }
8101
- const raw = await fs12.readFile(absolutePath, "utf8");
8838
+ const raw = await fs14.readFile(absolutePath, "utf8");
8102
8839
  const parsed = matter8(raw);
8103
8840
  return {
8104
8841
  path: relativePath,
8105
- title: typeof parsed.data.title === "string" ? parsed.data.title : path16.basename(relativePath, path16.extname(relativePath)),
8842
+ title: typeof parsed.data.title === "string" ? parsed.data.title : path17.basename(relativePath, path17.extname(relativePath)),
8106
8843
  frontmatter: parsed.data,
8107
8844
  content: parsed.content,
8108
8845
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -8110,12 +8847,12 @@ async function readViewerPage(rootDir, relativePath) {
8110
8847
  }
8111
8848
  async function readViewerAsset(rootDir, relativePath) {
8112
8849
  const { paths } = await loadVaultConfig(rootDir);
8113
- const absolutePath = path16.resolve(paths.wikiDir, relativePath);
8850
+ const absolutePath = path17.resolve(paths.wikiDir, relativePath);
8114
8851
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
8115
8852
  return null;
8116
8853
  }
8117
8854
  return {
8118
- buffer: await fs12.readFile(absolutePath),
8855
+ buffer: await fs14.readFile(absolutePath),
8119
8856
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
8120
8857
  };
8121
8858
  }
@@ -8138,12 +8875,12 @@ async function readJsonBody(request) {
8138
8875
  return JSON.parse(raw);
8139
8876
  }
8140
8877
  async function ensureViewerDist(viewerDistDir) {
8141
- const indexPath = path16.join(viewerDistDir, "index.html");
8878
+ const indexPath = path17.join(viewerDistDir, "index.html");
8142
8879
  if (await fileExists(indexPath)) {
8143
8880
  return;
8144
8881
  }
8145
- const viewerProjectDir = path16.dirname(viewerDistDir);
8146
- if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
8882
+ const viewerProjectDir = path17.dirname(viewerDistDir);
8883
+ if (await fileExists(path17.join(viewerProjectDir, "package.json"))) {
8147
8884
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
8148
8885
  }
8149
8886
  }
@@ -8160,7 +8897,7 @@ async function startGraphServer(rootDir, port) {
8160
8897
  return;
8161
8898
  }
8162
8899
  response.writeHead(200, { "content-type": "application/json" });
8163
- response.end(await fs12.readFile(paths.graphPath, "utf8"));
8900
+ response.end(await fs14.readFile(paths.graphPath, "utf8"));
8164
8901
  return;
8165
8902
  }
8166
8903
  if (url.pathname === "/api/search") {
@@ -8259,8 +8996,8 @@ async function startGraphServer(rootDir, port) {
8259
8996
  return;
8260
8997
  }
8261
8998
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
8262
- const target = path16.join(paths.viewerDistDir, relativePath);
8263
- const fallback = path16.join(paths.viewerDistDir, "index.html");
8999
+ const target = path17.join(paths.viewerDistDir, relativePath);
9000
+ const fallback = path17.join(paths.viewerDistDir, "index.html");
8264
9001
  const filePath = await fileExists(target) ? target : fallback;
8265
9002
  if (!await fileExists(filePath)) {
8266
9003
  response.writeHead(503, { "content-type": "text/plain" });
@@ -8268,7 +9005,7 @@ async function startGraphServer(rootDir, port) {
8268
9005
  return;
8269
9006
  }
8270
9007
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
8271
- response.end(await fs12.readFile(filePath));
9008
+ response.end(await fs14.readFile(filePath));
8272
9009
  });
8273
9010
  await new Promise((resolve) => {
8274
9011
  server.listen(effectivePort, resolve);
@@ -8295,7 +9032,7 @@ async function exportGraphHtml(rootDir, outputPath) {
8295
9032
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
8296
9033
  }
8297
9034
  await ensureViewerDist(paths.viewerDistDir);
8298
- const indexPath = path16.join(paths.viewerDistDir, "index.html");
9035
+ const indexPath = path17.join(paths.viewerDistDir, "index.html");
8299
9036
  if (!await fileExists(indexPath)) {
8300
9037
  throw new Error("Viewer build not found. Run `pnpm build` first.");
8301
9038
  }
@@ -8319,16 +9056,16 @@ async function exportGraphHtml(rootDir, outputPath) {
8319
9056
  } : null;
8320
9057
  })
8321
9058
  );
8322
- const rawHtml = await fs12.readFile(indexPath, "utf8");
9059
+ const rawHtml = await fs14.readFile(indexPath, "utf8");
8323
9060
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
8324
9061
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
8325
- const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
8326
- const stylePath = styleMatch?.[1] ? path16.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
9062
+ const scriptPath = scriptMatch?.[1] ? path17.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
9063
+ const stylePath = styleMatch?.[1] ? path17.join(paths.viewerDistDir, styleMatch[1].replace(/^\//, "")) : null;
8327
9064
  if (!scriptPath || !await fileExists(scriptPath)) {
8328
9065
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
8329
9066
  }
8330
- const script = await fs12.readFile(scriptPath, "utf8");
8331
- const style = stylePath && await fileExists(stylePath) ? await fs12.readFile(stylePath, "utf8") : "";
9067
+ const script = await fs14.readFile(scriptPath, "utf8");
9068
+ const style = stylePath && await fileExists(stylePath) ? await fs14.readFile(stylePath, "utf8") : "";
8332
9069
  const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
8333
9070
  const html = [
8334
9071
  "<!doctype html>",
@@ -8347,13 +9084,13 @@ async function exportGraphHtml(rootDir, outputPath) {
8347
9084
  "</html>",
8348
9085
  ""
8349
9086
  ].filter(Boolean).join("\n");
8350
- await fs12.mkdir(path16.dirname(outputPath), { recursive: true });
8351
- await fs12.writeFile(outputPath, html, "utf8");
8352
- return path16.resolve(outputPath);
9087
+ await fs14.mkdir(path17.dirname(outputPath), { recursive: true });
9088
+ await fs14.writeFile(outputPath, html, "utf8");
9089
+ return path17.resolve(outputPath);
8353
9090
  }
8354
9091
 
8355
9092
  // src/watch.ts
8356
- import path17 from "path";
9093
+ import path18 from "path";
8357
9094
  import process2 from "process";
8358
9095
  import chokidar from "chokidar";
8359
9096
  var MAX_BACKOFF_MS = 3e4;
@@ -8498,7 +9235,7 @@ async function watchVault(rootDir, options = {}) {
8498
9235
  };
8499
9236
  }
8500
9237
  function toWatchReason(baseDir, targetPath) {
8501
- return path17.relative(baseDir, targetPath) || ".";
9238
+ return path18.relative(baseDir, targetPath) || ".";
8502
9239
  }
8503
9240
  export {
8504
9241
  acceptApproval,
@@ -8517,6 +9254,7 @@ export {
8517
9254
  getWebSearchAdapterForTask,
8518
9255
  getWorkspaceInfo,
8519
9256
  importInbox,
9257
+ ingestDirectory,
8520
9258
  ingestInput,
8521
9259
  initVault,
8522
9260
  initWorkspace,