@swarmvaultai/engine 0.1.16 → 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-EEWB4WGH.js";
24
+ } from "./chunk-NCSZ4AKP.js";
25
25
 
26
26
  // src/agents.ts
27
27
  import fs from "fs/promises";
@@ -55,6 +55,7 @@ function targetPathForAgent(rootDir, agent) {
55
55
  case "codex":
56
56
  case "goose":
57
57
  case "pi":
58
+ case "opencode":
58
59
  return path.join(rootDir, "AGENTS.md");
59
60
  case "claude":
60
61
  return path.join(rootDir, "CLAUDE.md");
@@ -93,6 +94,7 @@ async function installAgent(rootDir, agent) {
93
94
  case "codex":
94
95
  case "goose":
95
96
  case "pi":
97
+ case "opencode":
96
98
  await upsertManagedBlock(target, buildManagedBlock("agents"));
97
99
  return target;
98
100
  case "claude": {
@@ -127,80 +129,106 @@ async function installConfiguredAgents(rootDir) {
127
129
  }
128
130
 
129
131
  // src/ingest.ts
130
- import fs3 from "fs/promises";
131
- import path4 from "path";
132
+ import fs5 from "fs/promises";
133
+ import path5 from "path";
132
134
  import { Readability } from "@mozilla/readability";
135
+ import ignore from "ignore";
133
136
  import { JSDOM } from "jsdom";
134
137
  import mime from "mime-types";
135
138
  import TurndownService from "turndown";
136
139
 
137
140
  // src/code-analysis.ts
138
- import path2 from "path";
141
+ import fs3 from "fs/promises";
142
+ import path3 from "path";
139
143
  import ts from "typescript";
140
- function scriptKindFor(language) {
141
- switch (language) {
142
- case "typescript":
143
- return ts.ScriptKind.TS;
144
- case "tsx":
145
- return ts.ScriptKind.TSX;
146
- case "jsx":
147
- return ts.ScriptKind.JSX;
148
- default:
149
- 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
+ );
150
169
  }
170
+ return treeSitterModulePromise;
151
171
  }
152
- function isRelativeSpecifier(specifier) {
153
- 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;
154
179
  }
155
- function formatDiagnosticCategory(category) {
156
- switch (category) {
157
- case ts.DiagnosticCategory.Error:
158
- return "error";
159
- case ts.DiagnosticCategory.Warning:
160
- return "warning";
161
- case ts.DiagnosticCategory.Suggestion:
162
- return "suggestion";
163
- default:
164
- return "message";
180
+ async function loadLanguage(language) {
181
+ const cached = languageCache.get(language);
182
+ if (cached) {
183
+ return cached;
165
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;
166
193
  }
167
- function declarationSignature(node, sourceFile) {
168
- const sourceText = sourceFile.getFullText();
169
- if (ts.isFunctionDeclaration(node) && node.body) {
170
- 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;
171
209
  }
172
- if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)) {
173
- const membersPos = node.members.pos;
174
- return truncate(
175
- normalizeWhitespace(
176
- sourceText.slice(node.getStart(sourceFile), membersPos).replace(/\{\s*$/, "").trim()
177
- ),
178
- 180
179
- );
210
+ if (language === "python") {
211
+ const dotted = normalized.replace(/\/__init__$/i, "").replace(/\//g, ".").replace(/^src\./, "");
212
+ return dotted || path2.posix.basename(normalized);
180
213
  }
181
- return truncate(normalizeWhitespace(node.getText(sourceFile)), 180);
214
+ return normalized.endsWith("/index") ? normalized.slice(0, -"/index".length) || path2.posix.basename(normalized) : normalized;
182
215
  }
183
- function importSpecifierText(specifier) {
184
- 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
+ );
185
223
  }
186
- function exportSpecifierText(specifier) {
187
- 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}`}`;
188
229
  }
189
- function collectCallNames(root, availableNames, selfName) {
190
- if (!root) {
191
- return [];
192
- }
193
- const names = [];
194
- const visit = (node) => {
195
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && availableNames.has(node.expression.text)) {
196
- if (node.expression.text !== selfName) {
197
- names.push(node.expression.text);
198
- }
199
- }
200
- ts.forEachChild(node, visit);
201
- };
202
- visit(root);
203
- return uniqueBy(names, (name) => name);
230
+ function escapeRegExp(value) {
231
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
204
232
  }
205
233
  function collectCallNamesFromText(text, availableNames, selfName) {
206
234
  if (!text) {
@@ -218,132 +246,7 @@ function collectCallNamesFromText(text, availableNames, selfName) {
218
246
  }
219
247
  return uniqueBy(names, (name) => name);
220
248
  }
221
- function heritageNames(clauses, token) {
222
- return uniqueBy(
223
- (clauses ?? []).filter((clause) => clause.token === token).flatMap(
224
- (clause) => clause.types.map((typeNode) => {
225
- if (ts.isIdentifier(typeNode.expression)) {
226
- return typeNode.expression.text;
227
- }
228
- if (ts.isPropertyAccessExpression(typeNode.expression)) {
229
- return typeNode.expression.getText();
230
- }
231
- return typeNode.getText();
232
- })
233
- ),
234
- (name) => name
235
- );
236
- }
237
- function isNodeExported(node) {
238
- return Boolean(
239
- ts.canHaveModifiers(node) && ts.getModifiers(node)?.some(
240
- (modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword || modifier.kind === ts.SyntaxKind.DefaultKeyword
241
- )
242
- );
243
- }
244
- function makeSymbolId(sourceId, name, seen) {
245
- const base = slugify(name);
246
- const count = (seen.get(base) ?? 0) + 1;
247
- seen.set(base, count);
248
- return `symbol:${sourceId}:${count === 1 ? base : `${base}-${count}`}`;
249
- }
250
- function summarizeModule(manifest, code) {
251
- const localImports = code.imports.filter((item) => !item.isExternal && !item.reExport).length;
252
- const externalImports = code.imports.filter((item) => item.isExternal).length;
253
- const exportedCount = code.symbols.filter((symbol) => symbol.exported).length;
254
- const parts = [`${code.language} module`, `defining ${code.symbols.length} top-level symbol(s)`, `exporting ${exportedCount} symbol(s)`];
255
- if (localImports > 0) {
256
- parts.push(`importing ${localImports} local module(s)`);
257
- }
258
- if (externalImports > 0) {
259
- parts.push(`depending on ${externalImports} external package import(s)`);
260
- }
261
- if (code.diagnostics.length > 0) {
262
- parts.push(`with ${code.diagnostics.length} parser diagnostic(s)`);
263
- }
264
- return `${manifest.title} is a ${parts.join(", ")}.`;
265
- }
266
- function codeClaims(manifest, code) {
267
- const claims = [];
268
- if (code.exports.length > 0) {
269
- claims.push({
270
- text: `${manifest.title} exports ${code.exports.slice(0, 4).join(", ")}${code.exports.length > 4 ? ", and more" : ""}.`,
271
- confidence: 1,
272
- status: "extracted",
273
- polarity: "neutral",
274
- citation: manifest.sourceId
275
- });
276
- }
277
- if (code.symbols.length > 0) {
278
- claims.push({
279
- text: `${manifest.title} defines ${code.symbols.slice(0, 5).map((symbol) => symbol.name).join(", ")}${code.symbols.length > 5 ? ", and more" : ""}.`,
280
- confidence: 1,
281
- status: "extracted",
282
- polarity: "neutral",
283
- citation: manifest.sourceId
284
- });
285
- }
286
- if (code.imports.length > 0) {
287
- claims.push({
288
- text: `${manifest.title} imports ${code.imports.slice(0, 4).map((item) => item.specifier).join(", ")}${code.imports.length > 4 ? ", and more" : ""}.`,
289
- confidence: 1,
290
- status: "extracted",
291
- polarity: "neutral",
292
- citation: manifest.sourceId
293
- });
294
- }
295
- if (code.diagnostics.length > 0) {
296
- claims.push({
297
- text: `${manifest.title} has ${code.diagnostics.length} parser diagnostic(s) that should be reviewed before trusting the module summary.`,
298
- confidence: 1,
299
- status: "extracted",
300
- polarity: "negative",
301
- citation: manifest.sourceId
302
- });
303
- }
304
- return claims.slice(0, 4).map((claim, index) => ({
305
- id: `claim:${manifest.sourceId}:${index + 1}`,
306
- ...claim
307
- }));
308
- }
309
- function codeQuestions(manifest, code) {
310
- const questions = [
311
- code.exports.length > 0 ? `Which downstream pages should explain how ${manifest.title} exports are consumed?` : "",
312
- code.imports.some((item) => !item.isExternal) ? `How does ${manifest.title} coordinate with its imported local modules?` : "",
313
- code.dependencies[0] ? `Why does ${manifest.title} depend on ${code.dependencies[0]}?` : "",
314
- `What broader responsibility does ${manifest.title} serve in the codebase?`
315
- ].filter(Boolean);
316
- return uniqueBy(questions, (question) => question).slice(0, 4);
317
- }
318
- function resolveVariableKind(statement) {
319
- return statement.declarationList.flags & ts.NodeFlags.Const ? "variable" : "variable";
320
- }
321
- function splitLines(content) {
322
- return content.split(/\r?\n/);
323
- }
324
- function leadingIndent(line) {
325
- const match = line.match(/^[ \t]*/);
326
- return match ? match[0].replace(/\t/g, " ").length : 0;
327
- }
328
- function normalizeSymbolReference(value) {
329
- const withoutGenerics = value.replace(/<[^>]*>/g, "");
330
- const withoutDecorators = withoutGenerics.replace(/['"&*()[\]{}]/g, " ");
331
- const trimmed = withoutDecorators.trim();
332
- const lastSegment = trimmed.split(/::|\./).filter(Boolean).at(-1) ?? trimmed;
333
- return lastSegment.replace(/[,:;]+$/g, "").trim();
334
- }
335
- function singleLineSignature(line) {
336
- return truncate(
337
- normalizeWhitespace(
338
- line.replace(/\{\s*$/, "").replace(/:\s*$/, ":").trim()
339
- ),
340
- 180
341
- );
342
- }
343
- function buildDiagnostic(code, message, line, column = 1, category = "warning") {
344
- return { code, category, message, line, column };
345
- }
346
- function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics) {
249
+ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics, metadata) {
347
250
  const topLevelNames = new Set(draftSymbols.map((symbol) => symbol.name));
348
251
  for (const symbol of draftSymbols) {
349
252
  if (symbol.callNames.length === 0 && symbol.bodyText) {
@@ -364,6 +267,8 @@ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportL
364
267
  return {
365
268
  moduleId: `module:${manifest.sourceId}`,
366
269
  language,
270
+ moduleName: metadata?.moduleName ?? manifestModuleName(manifest, language),
271
+ namespace: metadata?.namespace,
367
272
  imports,
368
273
  dependencies: uniqueBy(
369
274
  imports.filter((item) => item.isExternal).map((item) => item.specifier),
@@ -374,631 +279,957 @@ function finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportL
374
279
  diagnostics
375
280
  };
376
281
  }
377
- function collectPythonBlock(lines, startIndex) {
378
- const startIndent = leadingIndent(lines[startIndex] ?? "");
379
- const parts = [];
380
- for (let index = startIndex + 1; index < lines.length; index += 1) {
381
- const line = lines[index] ?? "";
382
- const trimmed = line.trim();
383
- if (!trimmed) {
384
- parts.push(line);
385
- continue;
386
- }
387
- if (leadingIndent(line) <= startIndent) {
388
- break;
389
- }
390
- 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;
391
291
  }
392
- 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;
393
299
  }
394
- function parsePythonImportList(value) {
395
- return value.split(",").map((item) => item.trim()).filter(Boolean).map((item) => {
396
- const [rawSpecifier, rawAlias] = item.split(/\s+as\s+/i);
397
- return {
398
- specifier: rawSpecifier.trim(),
399
- alias: rawAlias?.trim()
400
- };
401
- });
300
+ function exportedByCapitalization(name) {
301
+ return /^[A-Z]/.test(name);
402
302
  }
403
- function parsePythonAllExportList(value) {
404
- const match = value.match(/\[(.*)\]/);
405
- 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) {
406
311
  return [];
407
312
  }
408
313
  return uniqueBy(
409
- 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),
410
315
  (item) => item
411
316
  );
412
317
  }
413
- function analyzePythonCode(manifest, content) {
414
- const lines = splitLines(content);
415
- const imports = [];
416
- const draftSymbols = [];
417
- const exportLabels = [];
318
+ function quotedPath(value) {
319
+ return value.replace(/^["'<]+|[">]+$/g, "").trim();
320
+ }
321
+ function diagnosticsFromTree(rootNode) {
322
+ if (!rootNode.hasError) {
323
+ return [];
324
+ }
418
325
  const diagnostics = [];
419
- let explicitExports = [];
420
- for (let index = 0; index < lines.length; index += 1) {
421
- const rawLine = lines[index] ?? "";
422
- const trimmed = rawLine.trim();
423
- if (!trimmed || trimmed.startsWith("#") || leadingIndent(rawLine) > 0) {
424
- continue;
326
+ const seen = /* @__PURE__ */ new Set();
327
+ const visit = (node) => {
328
+ if (!node) {
329
+ return;
425
330
  }
426
- const importMatch = trimmed.match(/^import\s+(.+)$/);
427
- if (importMatch) {
428
- for (const item of parsePythonImportList(importMatch[1])) {
429
- imports.push({
430
- specifier: item.specifier,
431
- importedSymbols: [],
432
- namespaceImport: item.alias,
433
- isExternal: !isRelativeSpecifier(item.specifier),
434
- 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
435
341
  });
436
342
  }
437
- continue;
438
- }
439
- const fromImportMatch = trimmed.match(/^from\s+([.\w]+)\s+import\s+(.+)$/);
440
- if (fromImportMatch) {
441
- const importedSymbols = fromImportMatch[2].split(",").map((item) => item.trim()).filter(Boolean);
442
- imports.push({
443
- specifier: fromImportMatch[1],
444
- importedSymbols,
445
- isExternal: !isRelativeSpecifier(fromImportMatch[1]),
446
- reExport: false
447
- });
448
- continue;
449
- }
450
- const classMatch = trimmed.match(/^class\s+([A-Za-z_]\w*)\s*(?:\(([^)]*)\))?\s*:/);
451
- if (classMatch) {
452
- const baseNames = classMatch[2] ? classMatch[2].split(",").map((item) => normalizeSymbolReference(item)).filter(Boolean) : [];
453
- draftSymbols.push({
454
- name: classMatch[1],
455
- kind: "class",
456
- signature: singleLineSignature(trimmed),
457
- exported: !classMatch[1].startsWith("_"),
458
- callNames: [],
459
- extendsNames: baseNames,
460
- implementsNames: [],
461
- bodyText: collectPythonBlock(lines, index)
462
- });
463
- continue;
464
- }
465
- if (trimmed.startsWith("class ")) {
466
- diagnostics.push(buildDiagnostic(1001, "Python class declaration is missing a trailing colon.", index + 1));
467
- continue;
468
- }
469
- const functionMatch = trimmed.match(/^(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/);
470
- if (functionMatch) {
471
- draftSymbols.push({
472
- name: functionMatch[1],
473
- kind: "function",
474
- signature: singleLineSignature(trimmed),
475
- exported: !functionMatch[1].startsWith("_"),
476
- callNames: [],
477
- extendsNames: [],
478
- implementsNames: [],
479
- bodyText: collectPythonBlock(lines, index)
480
- });
481
- continue;
482
343
  }
483
- if (trimmed.startsWith("def ") || trimmed.startsWith("async def ")) {
484
- diagnostics.push(buildDiagnostic(1002, "Python function declaration is missing a trailing colon.", index + 1));
485
- continue;
486
- }
487
- const allMatch = trimmed.match(/^__all__\s*=\s*\[(.*)\]\s*$/);
488
- if (allMatch) {
489
- explicitExports = parsePythonAllExportList(trimmed);
490
- continue;
491
- }
492
- const variableMatch = trimmed.match(/^([A-Za-z_]\w*)\s*(?::[^=]+)?=\s*(.+)$/);
493
- if (variableMatch && !["True", "False", "None"].includes(variableMatch[1])) {
494
- draftSymbols.push({
495
- name: variableMatch[1],
496
- kind: "variable",
497
- signature: singleLineSignature(trimmed),
498
- exported: !variableMatch[1].startsWith("_"),
499
- callNames: [],
500
- extendsNames: [],
501
- implementsNames: [],
502
- bodyText: variableMatch[2]
503
- });
344
+ for (const child of node.children) {
345
+ visit(child);
504
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 [];
505
355
  }
506
- if (explicitExports.length > 0) {
507
- const explicitExportSet = new Set(explicitExports);
508
- for (const symbol of draftSymbols) {
509
- symbol.exported = explicitExportSet.has(symbol.name);
510
- }
511
- exportLabels.push(...explicitExports);
512
- }
513
- return finalizeCodeAnalysis(manifest, "python", imports, draftSymbols, exportLabels, diagnostics);
514
- }
515
- function collectBracedBlock(lines, startIndex) {
516
- const parts = [];
517
- let depth = 0;
518
- let started = false;
519
- for (let index = startIndex; index < lines.length; index += 1) {
520
- const line = lines[index] ?? "";
521
- parts.push(line);
522
- for (const character of line) {
523
- if (character === "{") {
524
- depth += 1;
525
- started = true;
526
- } else if (character === "}") {
527
- depth -= 1;
528
- }
529
- }
530
- if (started && depth <= 0) {
531
- 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
532
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;
533
385
  }
534
386
  return {
535
- text: parts.join("\n"),
536
- 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
537
392
  };
538
393
  }
539
- function exportedByCapitalization(name) {
540
- 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
+ };
541
419
  }
542
- function parseGoImportLine(line) {
543
- 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+([^;]+);$/);
544
432
  if (!match) {
545
433
  return void 0;
546
434
  }
547
435
  return {
548
- specifier: match[2],
436
+ specifier: match[1].trim(),
549
437
  importedSymbols: [],
550
- namespaceImport: match[1] && ![".", "_"].includes(match[1]) ? match[1] : void 0,
551
- isExternal: !isRelativeSpecifier(match[2]),
438
+ isExternal: !match[1].trim().startsWith("."),
552
439
  reExport: false
553
440
  };
554
441
  }
555
- function receiverTypeName(receiver) {
556
- const tokens = receiver.replace(/[()*]/g, " ").split(/\s+/).map((item) => item.trim()).filter(Boolean);
557
- 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
+ };
558
468
  }
559
- function analyzeGoCode(manifest, content) {
560
- const lines = splitLines(content);
469
+ function pythonCodeAnalysis(manifest, rootNode, diagnostics) {
561
470
  const imports = [];
562
471
  const draftSymbols = [];
563
- const exportLabels = [];
564
- const diagnostics = [];
565
- let inImportBlock = false;
566
- for (let index = 0; index < lines.length; index += 1) {
567
- const rawLine = lines[index] ?? "";
568
- const trimmed = rawLine.trim();
569
- if (!trimmed || trimmed.startsWith("//")) {
472
+ for (const child of rootNode.namedChildren) {
473
+ if (!child) {
570
474
  continue;
571
475
  }
572
- if (inImportBlock) {
573
- if (trimmed === ")") {
574
- inImportBlock = false;
575
- continue;
576
- }
577
- const parsed = parseGoImportLine(trimmed);
578
- if (parsed) {
579
- imports.push(parsed);
580
- }
476
+ if (child.type === "import_statement") {
477
+ imports.push(...parsePythonImportStatement(child.text));
581
478
  continue;
582
479
  }
583
- const importBlockMatch = trimmed.match(/^import\s+\($/);
584
- if (importBlockMatch) {
585
- inImportBlock = true;
480
+ if (child.type === "import_from_statement") {
481
+ imports.push(...parsePythonFromImportStatement(child.text));
586
482
  continue;
587
483
  }
588
- const singleImportMatch = trimmed.match(/^import\s+(.+)$/);
589
- if (singleImportMatch) {
590
- const parsed = parseGoImportLine(singleImportMatch[1]);
591
- if (parsed) {
592
- imports.push(parsed);
484
+ if (child.type === "class_definition") {
485
+ const name = extractIdentifier(child.childForFieldName("name"));
486
+ if (!name) {
487
+ continue;
593
488
  }
594
- continue;
595
- }
596
- const typeMatch = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/);
597
- if (typeMatch) {
598
- const exported = exportedByCapitalization(typeMatch[1]);
489
+ const superclasses = parseCommaSeparatedReferences(nodeText(child.childForFieldName("superclasses")).replace(/^\(|\)$/g, ""));
599
490
  draftSymbols.push({
600
- name: typeMatch[1],
601
- kind: typeMatch[2] === "interface" ? "interface" : "class",
602
- signature: singleLineSignature(trimmed),
603
- exported,
491
+ name,
492
+ kind: "class",
493
+ signature: singleLineSignature(child.text),
494
+ exported: !name.startsWith("_"),
604
495
  callNames: [],
605
- extendsNames: [],
496
+ extendsNames: superclasses,
606
497
  implementsNames: [],
607
- bodyText: collectBracedBlock(lines, index).text
498
+ bodyText: nodeText(child.childForFieldName("body"))
608
499
  });
609
- if (exported) {
610
- exportLabels.push(typeMatch[1]);
611
- }
612
500
  continue;
613
501
  }
614
- const aliasTypeMatch = trimmed.match(/^type\s+([A-Za-z_]\w*)\b(?!\s+(?:struct|interface)\b)/);
615
- if (aliasTypeMatch) {
616
- const exported = exportedByCapitalization(aliasTypeMatch[1]);
617
- draftSymbols.push({
618
- name: aliasTypeMatch[1],
619
- kind: "type_alias",
620
- signature: singleLineSignature(trimmed),
621
- exported,
622
- callNames: [],
623
- extendsNames: [],
624
- implementsNames: [],
625
- bodyText: trimmed
626
- });
627
- if (exported) {
628
- exportLabels.push(aliasTypeMatch[1]);
502
+ if (child.type === "function_definition") {
503
+ const name = extractIdentifier(child.childForFieldName("name"));
504
+ if (!name) {
505
+ continue;
629
506
  }
630
- continue;
631
- }
632
- const funcMatch = trimmed.match(/^func\s+(?:\(([^)]*)\)\s*)?([A-Za-z_]\w*)\s*\(/);
633
- if (funcMatch) {
634
- const receiverType = funcMatch[1] ? receiverTypeName(funcMatch[1]) : "";
635
- const symbolName = receiverType ? `${receiverType}.${funcMatch[2]}` : funcMatch[2];
636
- const exported = exportedByCapitalization(funcMatch[2]);
637
- const block = collectBracedBlock(lines, index);
638
507
  draftSymbols.push({
639
- name: symbolName,
508
+ name,
640
509
  kind: "function",
641
- signature: singleLineSignature(trimmed),
642
- exported,
643
- callNames: [],
644
- extendsNames: [],
645
- implementsNames: [],
646
- bodyText: block.text
647
- });
648
- if (exported) {
649
- exportLabels.push(symbolName);
650
- }
651
- index = block.endIndex;
652
- continue;
653
- }
654
- const variableMatch = trimmed.match(/^(?:var|const)\s+([A-Za-z_]\w*)\b/);
655
- if (variableMatch) {
656
- const exported = exportedByCapitalization(variableMatch[1]);
657
- draftSymbols.push({
658
- name: variableMatch[1],
659
- kind: "variable",
660
- signature: singleLineSignature(trimmed),
661
- exported,
510
+ signature: singleLineSignature(child.text),
511
+ exported: !name.startsWith("_"),
662
512
  callNames: [],
663
513
  extendsNames: [],
664
514
  implementsNames: [],
665
- bodyText: trimmed
515
+ bodyText: nodeText(child.childForFieldName("body"))
666
516
  });
667
- if (exported) {
668
- exportLabels.push(variableMatch[1]);
669
- }
670
517
  }
671
518
  }
672
- return finalizeCodeAnalysis(manifest, "go", imports, draftSymbols, exportLabels, diagnostics);
673
- }
674
- function analyzeRustUseStatement(statement) {
675
- const cleaned = statement.replace(/^pub\s+/, "").replace(/^use\s+/, "").replace(/;$/, "").trim();
676
- const aliasMatch = cleaned.match(/\s+as\s+([A-Za-z_]\w*)$/);
677
- const withoutAlias = aliasMatch ? cleaned.slice(0, aliasMatch.index).trim() : cleaned;
678
- const braceMatch = withoutAlias.match(/^(.*)::\{(.+)\}$/);
679
- const importedSymbols = braceMatch ? braceMatch[2].split(",").map((item) => item.trim()).filter(Boolean) : [aliasMatch ? `${normalizeSymbolReference(withoutAlias)} as ${aliasMatch[1]}` : normalizeSymbolReference(withoutAlias)].filter(
680
- Boolean
681
- );
682
- const specifier = braceMatch ? braceMatch[1].trim() : withoutAlias;
683
- return {
684
- specifier,
685
- importedSymbols,
686
- isExternal: !/^(crate|self|super)::/.test(specifier),
687
- reExport: statement.trim().startsWith("pub use ")
688
- };
519
+ return finalizeCodeAnalysis(manifest, "python", imports, draftSymbols, [], diagnostics);
689
520
  }
690
- function rustVisibilityPrefix(trimmed) {
691
- return /^(pub(?:\([^)]*\))?\s+)/.test(trimmed);
692
- }
693
- function analyzeRustCode(manifest, content) {
694
- const lines = splitLines(content);
521
+ function goCodeAnalysis(manifest, rootNode, diagnostics) {
695
522
  const imports = [];
696
523
  const draftSymbols = [];
697
524
  const exportLabels = [];
698
- const diagnostics = [];
699
- const symbolByName = /* @__PURE__ */ new Map();
700
- for (let index = 0; index < lines.length; index += 1) {
701
- const rawLine = lines[index] ?? "";
702
- const trimmed = rawLine.trim();
703
- if (!trimmed || trimmed.startsWith("//")) {
525
+ let packageName;
526
+ for (const child of rootNode.namedChildren) {
527
+ if (!child) {
704
528
  continue;
705
529
  }
706
- const useMatch = trimmed.match(/^(?:pub\s+)?use\s+.+;$/);
707
- if (useMatch) {
708
- imports.push(analyzeRustUseStatement(trimmed));
530
+ if (child.type === "package_clause") {
531
+ packageName = extractIdentifier(child.namedChildren.at(0) ?? null);
709
532
  continue;
710
533
  }
711
- const functionMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?fn\s+([A-Za-z_]\w*)\s*\(/);
712
- if (functionMatch) {
713
- const exported = rustVisibilityPrefix(trimmed);
714
- const block = collectBracedBlock(lines, index);
715
- const symbol = {
716
- name: functionMatch[1],
717
- kind: "function",
718
- signature: singleLineSignature(trimmed),
719
- exported,
720
- callNames: [],
721
- extendsNames: [],
722
- implementsNames: [],
723
- bodyText: block.text
724
- };
725
- draftSymbols.push(symbol);
726
- symbolByName.set(symbol.name, symbol);
727
- if (exported) {
728
- 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
+ }
729
540
  }
730
- index = block.endIndex;
731
541
  continue;
732
542
  }
733
- const structMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)\b/);
734
- if (structMatch) {
735
- const exported = rustVisibilityPrefix(trimmed);
736
- const block = collectBracedBlock(lines, index);
737
- const symbol = {
738
- name: structMatch[1],
739
- kind: "class",
740
- signature: singleLineSignature(trimmed),
741
- exported,
742
- callNames: [],
743
- extendsNames: [],
744
- implementsNames: [],
745
- bodyText: block.text
746
- };
747
- draftSymbols.push(symbol);
748
- symbolByName.set(symbol.name, symbol);
749
- if (exported) {
750
- 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
+ }
751
568
  }
752
- index = block.endIndex;
753
569
  continue;
754
570
  }
755
- const enumMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)\b/);
756
- if (enumMatch) {
757
- const exported = rustVisibilityPrefix(trimmed);
758
- const block = collectBracedBlock(lines, index);
759
- const symbol = {
760
- name: enumMatch[1],
761
- kind: "enum",
762
- signature: singleLineSignature(trimmed),
763
- exported,
764
- callNames: [],
765
- extendsNames: [],
766
- implementsNames: [],
767
- bodyText: block.text
768
- };
769
- draftSymbols.push(symbol);
770
- symbolByName.set(symbol.name, symbol);
771
- if (exported) {
772
- 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;
773
575
  }
774
- index = block.endIndex;
775
- continue;
776
- }
777
- const traitMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?trait\s+([A-Za-z_]\w*)\b/);
778
- if (traitMatch) {
779
- const exported = rustVisibilityPrefix(trimmed);
780
- const block = collectBracedBlock(lines, index);
781
- const symbol = {
782
- name: traitMatch[1],
783
- kind: "interface",
784
- 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),
785
583
  exported,
786
584
  callNames: [],
787
585
  extendsNames: [],
788
586
  implementsNames: [],
789
- bodyText: block.text
790
- };
791
- draftSymbols.push(symbol);
792
- symbolByName.set(symbol.name, symbol);
587
+ bodyText: nodeText(child.childForFieldName("body"))
588
+ });
793
589
  if (exported) {
794
- exportLabels.push(symbol.name);
590
+ exportLabels.push(symbolName);
795
591
  }
796
- 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) {
797
605
  continue;
798
606
  }
799
- const aliasMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)\s*=/);
800
- if (aliasMatch) {
801
- const exported = rustVisibilityPrefix(trimmed);
802
- const symbol = {
803
- name: aliasMatch[1],
804
- kind: "type_alias",
805
- signature: singleLineSignature(trimmed),
806
- exported,
807
- callNames: [],
808
- extendsNames: [],
809
- implementsNames: [],
810
- bodyText: trimmed
811
- };
812
- draftSymbols.push(symbol);
813
- symbolByName.set(symbol.name, symbol);
814
- if (exported) {
815
- exportLabels.push(symbol.name);
816
- }
607
+ if (child.type === "use_declaration") {
608
+ imports.push(parseRustUse(child.text));
817
609
  continue;
818
610
  }
819
- const variableMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?(?:const|static)\s+([A-Za-z_]\w*)\b/);
820
- if (variableMatch) {
821
- const exported = rustVisibilityPrefix(trimmed);
822
- const symbol = {
823
- name: variableMatch[1],
824
- kind: "variable",
825
- signature: singleLineSignature(trimmed),
826
- exported,
827
- callNames: [],
828
- extendsNames: [],
829
- implementsNames: [],
830
- bodyText: trimmed
831
- };
832
- draftSymbols.push(symbol);
833
- symbolByName.set(symbol.name, symbol);
834
- if (exported) {
835
- 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);
836
618
  }
837
619
  continue;
838
620
  }
839
- const implMatch = trimmed.match(/^impl(?:<[^>]+>)?\s+(.+?)\s+for\s+([A-Za-z_][\w:<>]*)/);
840
- if (implMatch) {
841
- const traitName = normalizeSymbolReference(implMatch[1]);
842
- const typeName = normalizeSymbolReference(implMatch[2]);
843
- const symbol = symbolByName.get(typeName);
844
- if (symbol && traitName) {
845
- symbol.implementsNames.push(traitName);
846
- }
621
+ if (!name) {
622
+ continue;
847
623
  }
848
- }
849
- for (const rawLine of lines) {
850
- const trimmed = rawLine.trim();
851
- const traitMatch = trimmed.match(/^(?:pub(?:\([^)]*\))?\s+)?trait\s+([A-Za-z_]\w*)\b/);
852
- 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) {
853
641
  continue;
854
642
  }
855
- const exported = rustVisibilityPrefix(trimmed);
643
+ const exported = child.namedChildren.some((item) => item?.type === "visibility_modifier");
856
644
  const symbol = {
857
- name: traitMatch[1],
858
- kind: "interface",
859
- signature: singleLineSignature(trimmed),
645
+ name,
646
+ kind,
647
+ signature: singleLineSignature(child.text),
860
648
  exported,
861
649
  callNames: [],
862
- extendsNames: [],
650
+ extendsNames,
863
651
  implementsNames: [],
864
- bodyText: trimmed
652
+ bodyText: nodeText(child.childForFieldName("body")) || child.text
865
653
  };
866
654
  draftSymbols.push(symbol);
867
- symbolByName.set(symbol.name, symbol);
655
+ symbolsByName.set(name, symbol);
868
656
  if (exported) {
869
- exportLabels.push(symbol.name);
657
+ exportLabels.push(name);
870
658
  }
871
659
  }
872
660
  return finalizeCodeAnalysis(manifest, "rust", imports, draftSymbols, exportLabels, diagnostics);
873
661
  }
874
- function analyzeJavaImport(statement) {
875
- const cleaned = statement.replace(/^import\s+/, "").replace(/^static\s+/, "").replace(/;$/, "").trim();
876
- const symbolName = normalizeSymbolReference(cleaned.replace(/\.\*$/, ""));
877
- return {
878
- specifier: cleaned.replace(/\.\*$/, ""),
879
- importedSymbols: symbolName ? [symbolName] : [],
880
- isExternal: true,
881
- reExport: false
882
- };
883
- }
884
- function parseJavaImplements(value) {
885
- return (value ?? "").split(",").map((item) => normalizeSymbolReference(item)).filter(Boolean);
886
- }
887
- function analyzeJavaCode(manifest, content) {
888
- const lines = splitLines(content);
662
+ function javaCodeAnalysis(manifest, rootNode, diagnostics) {
889
663
  const imports = [];
890
664
  const draftSymbols = [];
891
665
  const exportLabels = [];
892
- const diagnostics = [];
893
- let depth = 0;
894
- for (let index = 0; index < lines.length; index += 1) {
895
- const rawLine = lines[index] ?? "";
896
- const trimmed = rawLine.trim();
897
- const lineDepth = depth;
898
- if (lineDepth === 0 && trimmed.startsWith("import ")) {
899
- imports.push(analyzeJavaImport(trimmed));
900
- }
901
- if (lineDepth === 0) {
902
- const classMatch = trimmed.match(
903
- /^(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
904
694
  );
905
- if (classMatch) {
906
- const exported = Boolean(classMatch[1]);
907
- const block = collectBracedBlock(lines, index);
908
- draftSymbols.push({
909
- name: classMatch[2],
910
- kind: "class",
911
- signature: singleLineSignature(trimmed),
912
- exported,
913
- callNames: [],
914
- extendsNames: classMatch[3] ? [classMatch[3]] : [],
915
- implementsNames: parseJavaImplements(classMatch[4]),
916
- bodyText: block.text
917
- });
918
- if (exported) {
919
- exportLabels.push(classMatch[2]);
920
- }
921
- index = block.endIndex;
922
- depth = 0;
923
- 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);
924
733
  }
925
- const interfaceMatch = trimmed.match(
926
- /^(public\s+)?(?:sealed\s+|non-sealed\s+)?interface\s+([A-Za-z_]\w*)\b(?:\s+extends\s+([A-Za-z_][\w.,<>\s]*))?/
927
- );
928
- if (interfaceMatch) {
929
- const exported = Boolean(interfaceMatch[1]);
930
- const block = collectBracedBlock(lines, index);
931
- draftSymbols.push({
932
- name: interfaceMatch[2],
933
- kind: "interface",
934
- signature: singleLineSignature(trimmed),
935
- exported,
936
- callNames: [],
937
- extendsNames: parseJavaImplements(interfaceMatch[3]),
938
- implementsNames: [],
939
- bodyText: block.text
940
- });
941
- if (exported) {
942
- 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
+ }
943
770
  }
944
- index = block.endIndex;
945
- depth = 0;
771
+ }
772
+ if (child.type === "namespace_declaration") {
946
773
  continue;
947
774
  }
948
- const enumMatch = trimmed.match(/^(public\s+)?enum\s+([A-Za-z_]\w*)\b(?:\s+implements\s+([A-Za-z_][\w.,<>\s]*))?/);
949
- if (enumMatch) {
950
- const exported = Boolean(enumMatch[1]);
951
- const block = collectBracedBlock(lines, index);
952
- draftSymbols.push({
953
- name: enumMatch[2],
954
- kind: "enum",
955
- signature: singleLineSignature(trimmed),
956
- exported,
957
- callNames: [],
958
- extendsNames: [],
959
- implementsNames: parseJavaImplements(enumMatch[3]),
960
- bodyText: block.text
961
- });
962
- if (exported) {
963
- exportLabels.push(enumMatch[2]);
964
- }
965
- index = block.endIndex;
966
- 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) {
967
899
  continue;
968
900
  }
969
- const recordMatch = trimmed.match(
970
- /^(public\s+)?record\s+([A-Za-z_]\w*)\b(?:\s*\([^)]*\))?(?:\s+implements\s+([A-Za-z_][\w.,<>\s]*))?/
971
- );
972
- if (recordMatch) {
973
- const exported = Boolean(recordMatch[1]);
974
- const block = collectBracedBlock(lines, index);
975
- draftSymbols.push({
976
- name: recordMatch[2],
977
- kind: "class",
978
- signature: singleLineSignature(trimmed),
979
- exported,
980
- callNames: [],
981
- extendsNames: [],
982
- implementsNames: parseJavaImplements(recordMatch[3]),
983
- bodyText: block.text
984
- });
985
- if (exported) {
986
- exportLabels.push(recordMatch[2]);
987
- }
988
- index = block.endIndex;
989
- 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) {
990
926
  continue;
991
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
+ }
992
942
  }
993
- for (const character of rawLine) {
994
- if (character === "{") {
995
- depth += 1;
996
- } else if (character === "}") {
997
- 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);
998
1056
  }
999
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
+ }
1000
1207
  }
1001
- 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
+ };
1002
1233
  }
1003
1234
  function analyzeTypeScriptLikeCode(manifest, content) {
1004
1235
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
@@ -1212,10 +1443,10 @@ function analyzeTypeScriptLikeCode(manifest, content) {
1212
1443
  column: (position?.character ?? 0) + 1
1213
1444
  };
1214
1445
  });
1215
- return finalizeCodeAnalysis(manifest, language, imports, draftSymbols, exportLabels, diagnostics);
1446
+ return finalizeCodeAnalysis2(manifest, language, imports, draftSymbols, exportLabels, diagnostics);
1216
1447
  }
1217
1448
  function inferCodeLanguage(filePath, mimeType = "") {
1218
- const extension = path2.extname(filePath).toLowerCase();
1449
+ const extension = path3.extname(filePath).toLowerCase();
1219
1450
  if (extension === ".ts" || extension === ".mts" || extension === ".cts") {
1220
1451
  return "typescript";
1221
1452
  }
@@ -1240,78 +1471,344 @@ function inferCodeLanguage(filePath, mimeType = "") {
1240
1471
  if (extension === ".java") {
1241
1472
  return "java";
1242
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
+ }
1243
1486
  return void 0;
1244
1487
  }
1245
1488
  function modulePageTitle(manifest) {
1246
1489
  return `${manifest.title} module`;
1247
1490
  }
1248
1491
  function importResolutionCandidates(basePath, specifier, extensions) {
1249
- const resolved = path2.resolve(path2.dirname(basePath), specifier);
1250
- if (path2.extname(resolved)) {
1251
- 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];
1252
1495
  }
1253
- const direct = extensions.map((extension) => path2.normalize(`${resolved}${extension}`));
1254
- const indexFiles = extensions.map((extension) => path2.normalize(path2.join(resolved, `index${extension}`)));
1255
- 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);
1256
1499
  }
1257
- function resolveJsLikeImportSourceId(manifest, specifier, manifests) {
1258
- if (manifest.originType !== "file" || !manifest.originalPath || !isRelativeSpecifier(specifier)) {
1259
- 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);
1260
1512
  }
1261
- const candidates = new Set(
1262
- importResolutionCandidates(manifest.originalPath, specifier, [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"])
1263
- );
1264
- return manifests.find(
1265
- (candidate) => candidate.sourceKind === "code" && candidate.originalPath && candidates.has(path2.normalize(candidate.originalPath))
1266
- )?.sourceId;
1267
1513
  }
1268
- function resolvePythonImportSourceId(manifest, specifier, manifests) {
1269
- if (manifest.originType !== "file" || !manifest.originalPath) {
1270
- 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);
1271
1527
  }
1272
- if (specifier.startsWith(".")) {
1273
- const dotMatch = specifier.match(/^\.+/);
1274
- const depth = dotMatch ? dotMatch[0].length : 0;
1275
- const relativeModule = specifier.slice(depth).replace(/\./g, "/");
1276
- const baseDir = path2.dirname(manifest.originalPath);
1277
- const parentDir = path2.resolve(baseDir, ...Array(Math.max(depth - 1, 0)).fill(".."));
1278
- const moduleBase = relativeModule ? path2.join(parentDir, relativeModule) : parentDir;
1279
- const candidates = /* @__PURE__ */ new Set([path2.normalize(`${moduleBase}.py`), path2.normalize(path2.join(moduleBase, "__init__.py"))]);
1280
- return manifests.find(
1281
- (candidate) => candidate.sourceKind === "code" && candidate.originalPath && candidates.has(path2.normalize(candidate.originalPath))
1282
- )?.sourceId;
1283
- }
1284
- const modulePath = specifier.replace(/\./g, "/");
1285
- const suffixes = [`/${modulePath}.py`, `/${modulePath}/__init__.py`];
1286
- return manifests.find((candidate) => {
1287
- if (candidate.sourceKind !== "code" || !candidate.originalPath) {
1288
- 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);
1289
1671
  }
1290
- const normalizedOriginalPath = path2.normalize(candidate.originalPath);
1291
- return suffixes.some((suffix) => normalizedOriginalPath.endsWith(path2.normalize(suffix)));
1292
- })?.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
+ );
1293
1688
  }
1294
- 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) {
1295
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
+ }
1296
1721
  switch (language) {
1297
1722
  case "javascript":
1298
1723
  case "jsx":
1299
1724
  case "typescript":
1300
1725
  case "tsx":
1301
- return resolveJsLikeImportSourceId(manifest, specifier, manifests);
1726
+ return repoRelativePath && isRelativeSpecifier(codeImport.specifier) ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
1302
1727
  case "python":
1303
- 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);
1304
1749
  default:
1305
- return void 0;
1750
+ return [];
1306
1751
  }
1307
1752
  }
1308
- 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) {
1309
1805
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1310
1806
  }
1311
- function analyzeCodeSource(manifest, extractedText, schemaHash) {
1807
+ async function analyzeCodeSource(manifest, extractedText, schemaHash) {
1312
1808
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType) ?? "typescript";
1313
- 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);
1314
1810
  return {
1811
+ analysisVersion: 3,
1315
1812
  sourceId: manifest.sourceId,
1316
1813
  sourceHash: manifest.contentHash,
1317
1814
  schemaHash,
@@ -1327,18 +1824,18 @@ function analyzeCodeSource(manifest, extractedText, schemaHash) {
1327
1824
  }
1328
1825
 
1329
1826
  // src/logs.ts
1330
- import fs2 from "fs/promises";
1331
- import path3 from "path";
1827
+ import fs4 from "fs/promises";
1828
+ import path4 from "path";
1332
1829
  import matter from "gray-matter";
1333
1830
  async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
1334
1831
  const { paths } = await initWorkspace(rootDir);
1335
1832
  await ensureDir(paths.sessionsDir);
1336
1833
  const timestamp = startedAt.replace(/[:.]/g, "-");
1337
1834
  const baseName = `${timestamp}-${operation}-${slugify(title)}`;
1338
- let candidate = path3.join(paths.sessionsDir, `${baseName}.md`);
1835
+ let candidate = path4.join(paths.sessionsDir, `${baseName}.md`);
1339
1836
  let counter = 2;
1340
1837
  while (await fileExists(candidate)) {
1341
- candidate = path3.join(paths.sessionsDir, `${baseName}-${counter}.md`);
1838
+ candidate = path4.join(paths.sessionsDir, `${baseName}-${counter}.md`);
1342
1839
  counter++;
1343
1840
  }
1344
1841
  return candidate;
@@ -1346,11 +1843,11 @@ async function resolveUniqueSessionPath(rootDir, operation, title, startedAt) {
1346
1843
  async function appendLogEntry(rootDir, action, title, lines = []) {
1347
1844
  const { paths } = await initWorkspace(rootDir);
1348
1845
  await ensureDir(paths.wikiDir);
1349
- const logPath = path3.join(paths.wikiDir, "log.md");
1846
+ const logPath = path4.join(paths.wikiDir, "log.md");
1350
1847
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
1351
1848
  const entry = [`## [${timestamp}] ${action} | ${title}`, ...lines.map((line) => `- ${line}`), ""].join("\n");
1352
- const existing = await fileExists(logPath) ? await fs2.readFile(logPath, "utf8") : "# Log\n\n";
1353
- 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}
1354
1851
  `, "utf8");
1355
1852
  }
1356
1853
  async function recordSession(rootDir, input) {
@@ -1360,8 +1857,8 @@ async function recordSession(rootDir, input) {
1360
1857
  const finishedAtIso = new Date(input.finishedAt ?? input.startedAt).toISOString();
1361
1858
  const durationMs = Math.max(0, new Date(finishedAtIso).getTime() - new Date(startedAtIso).getTime());
1362
1859
  const sessionPath = await resolveUniqueSessionPath(rootDir, input.operation, input.title, startedAtIso);
1363
- const sessionId = path3.basename(sessionPath, ".md");
1364
- 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);
1365
1862
  const frontmatter = Object.fromEntries(
1366
1863
  Object.entries({
1367
1864
  session_id: sessionId,
@@ -1409,7 +1906,7 @@ async function recordSession(rootDir, input) {
1409
1906
  frontmatter
1410
1907
  );
1411
1908
  await writeFileIfChanged(sessionPath, content);
1412
- const logPath = path3.join(paths.wikiDir, "log.md");
1909
+ const logPath = path4.join(paths.wikiDir, "log.md");
1413
1910
  const timestamp = startedAtIso.slice(0, 19).replace("T", " ");
1414
1911
  const entry = [
1415
1912
  `## [${timestamp}] ${input.operation} | ${input.title}`,
@@ -1417,8 +1914,8 @@ async function recordSession(rootDir, input) {
1417
1914
  ...(input.lines ?? []).map((line) => `- ${line}`),
1418
1915
  ""
1419
1916
  ].join("\n");
1420
- const existing = await fileExists(logPath) ? await fs2.readFile(logPath, "utf8") : "# Log\n\n";
1421
- 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}
1422
1919
  `, "utf8");
1423
1920
  return { sessionPath, sessionId };
1424
1921
  }
@@ -1429,6 +1926,8 @@ async function appendWatchRun(rootDir, run) {
1429
1926
 
1430
1927
  // src/ingest.ts
1431
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"]);
1432
1931
  function inferKind(mimeType, filePath) {
1433
1932
  if (inferCodeLanguage(filePath, mimeType)) {
1434
1933
  return "code";
@@ -1460,9 +1959,67 @@ function guessMimeType(target) {
1460
1959
  function normalizeIngestOptions(options) {
1461
1960
  return {
1462
1961
  includeAssets: options?.includeAssets ?? true,
1463
- 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
1464
1968
  };
1465
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
+ }
1466
2023
  function buildCompositeHash(payloadBytes, attachments = []) {
1467
2024
  if (!attachments.length) {
1468
2025
  return sha256(payloadBytes);
@@ -1471,7 +2028,7 @@ function buildCompositeHash(payloadBytes, attachments = []) {
1471
2028
  return sha256(`${sha256(payloadBytes)}|${attachmentSignature}`);
1472
2029
  }
1473
2030
  function sanitizeAssetRelativePath(value) {
1474
- const normalized = path4.posix.normalize(value.replace(/\\/g, "/"));
2031
+ const normalized = path5.posix.normalize(value.replace(/\\/g, "/"));
1475
2032
  const segments = normalized.split("/").filter(Boolean).map((segment) => {
1476
2033
  if (segment === ".") {
1477
2034
  return "";
@@ -1491,7 +2048,7 @@ function normalizeLocalReference(value) {
1491
2048
  return null;
1492
2049
  }
1493
2050
  const lowered = candidate.toLowerCase();
1494
- 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)) {
1495
2052
  return null;
1496
2053
  }
1497
2054
  return candidate.replace(/\\/g, "/");
@@ -1553,18 +2110,107 @@ async function convertHtmlToMarkdown(html, url) {
1553
2110
  };
1554
2111
  }
1555
2112
  async function readManifestByHash(manifestsDir, contentHash) {
1556
- const entries = await fs3.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
2113
+ const entries = await fs5.readdir(manifestsDir, { withFileTypes: true }).catch(() => []);
1557
2114
  for (const entry of entries) {
1558
2115
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
1559
2116
  continue;
1560
2117
  }
1561
- const manifest = await readJsonFile(path4.join(manifestsDir, entry.name));
2118
+ const manifest = await readJsonFile(path5.join(manifestsDir, entry.name));
1562
2119
  if (manifest?.contentHash === contentHash) {
1563
2120
  return manifest;
1564
2121
  }
1565
2122
  }
1566
2123
  return null;
1567
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
+ }
1568
2214
  function resolveUrlMimeType(input, response) {
1569
2215
  const headerMimeType = response.headers.get("content-type")?.split(";")[0]?.trim();
1570
2216
  const guessedMimeType = guessMimeType(new URL(input).pathname);
@@ -1579,12 +2225,12 @@ function resolveUrlMimeType(input, response) {
1579
2225
  function buildRemoteAssetRelativePath(assetUrl, mimeType) {
1580
2226
  const url = new URL(assetUrl);
1581
2227
  const normalized = sanitizeAssetRelativePath(`${url.hostname}${url.pathname || "/asset"}`);
1582
- const extension = path4.posix.extname(normalized);
1583
- const directory = path4.posix.dirname(normalized);
1584
- 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);
1585
2231
  const resolvedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
1586
2232
  const hashedName = `${basename || "asset"}-${sha256(assetUrl).slice(0, 8)}${resolvedExtension}`;
1587
- return directory === "." ? hashedName : path4.posix.join(directory, hashedName);
2233
+ return directory === "." ? hashedName : path5.posix.join(directory, hashedName);
1588
2234
  }
1589
2235
  async function readResponseBytesWithinLimit(response, maxBytes) {
1590
2236
  const contentLength = Number.parseInt(response.headers.get("content-length") ?? "", 10);
@@ -1708,26 +2354,38 @@ async function persistPreparedInput(rootDir, prepared, paths) {
1708
2354
  await ensureDir(paths.extractsDir);
1709
2355
  const attachments = prepared.attachments ?? [];
1710
2356
  const contentHash = prepared.contentHash ?? buildCompositeHash(prepared.payloadBytes, attachments);
1711
- const existing = await readManifestByHash(paths.manifestsDir, contentHash);
1712
- if (existing) {
1713
- 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 };
1714
2364
  }
2365
+ const previous = existingByOrigin ?? void 0;
2366
+ const sourceId = previous?.sourceId ?? `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
1715
2367
  const now = (/* @__PURE__ */ new Date()).toISOString();
1716
- const sourceId = `${slugify(prepared.title)}-${contentHash.slice(0, 8)}`;
1717
- const storedPath = path4.join(paths.rawSourcesDir, `${sourceId}${prepared.storedExtension}`);
1718
- await fs3.writeFile(storedPath, prepared.payloadBytes);
1719
- let extractedTextPath;
1720
- if (prepared.extractedText) {
1721
- extractedTextPath = path4.join(paths.extractsDir, `${sourceId}.md`);
1722
- 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");
1723
2381
  }
1724
2382
  const manifestAttachments = [];
1725
2383
  for (const attachment of attachments) {
1726
- const absoluteAttachmentPath = path4.join(paths.rawAssetsDir, sourceId, attachment.relativePath);
1727
- await ensureDir(path4.dirname(absoluteAttachmentPath));
1728
- 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);
1729
2387
  manifestAttachments.push({
1730
- path: toPosix(path4.relative(rootDir, absoluteAttachmentPath)),
2388
+ path: toPosix(path5.relative(rootDir, absoluteAttachmentPath)),
1731
2389
  mimeType: attachment.mimeType,
1732
2390
  originalPath: attachment.originalPath
1733
2391
  });
@@ -1739,37 +2397,39 @@ async function persistPreparedInput(rootDir, prepared, paths) {
1739
2397
  sourceKind: prepared.sourceKind,
1740
2398
  language: prepared.language,
1741
2399
  originalPath: prepared.originalPath,
2400
+ repoRelativePath: prepared.repoRelativePath,
1742
2401
  url: prepared.url,
1743
- storedPath: toPosix(path4.relative(rootDir, storedPath)),
1744
- 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,
1745
2404
  mimeType: prepared.mimeType,
1746
2405
  contentHash,
1747
- createdAt: now,
2406
+ createdAt: previous?.createdAt ?? now,
1748
2407
  updatedAt: now,
1749
2408
  attachments: manifestAttachments.length ? manifestAttachments : void 0
1750
2409
  };
1751
- await writeJsonFile(path4.join(paths.manifestsDir, `${sourceId}.json`), manifest);
2410
+ await writeJsonFile(path5.join(paths.manifestsDir, `${sourceId}.json`), manifest);
1752
2411
  await appendLogEntry(rootDir, "ingest", prepared.title, [
1753
2412
  `source_id=${sourceId}`,
1754
2413
  `kind=${prepared.sourceKind}`,
1755
2414
  `attachments=${manifestAttachments.length}`,
2415
+ `updated=${previous ? "true" : "false"}`,
1756
2416
  ...prepared.logDetails ?? []
1757
2417
  ]);
1758
- return { manifest, isNew: true };
2418
+ return { manifest, isNew: !previous, wasUpdated: Boolean(previous) };
1759
2419
  }
1760
- async function prepareFileInput(_rootDir, absoluteInput) {
1761
- const payloadBytes = await fs3.readFile(absoluteInput);
2420
+ async function prepareFileInput(_rootDir, absoluteInput, repoRoot) {
2421
+ const payloadBytes = await fs5.readFile(absoluteInput);
1762
2422
  const mimeType = guessMimeType(absoluteInput);
1763
2423
  const sourceKind = inferKind(mimeType, absoluteInput);
1764
2424
  const language = inferCodeLanguage(absoluteInput, mimeType);
1765
- const storedExtension = path4.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
2425
+ const storedExtension = path5.extname(absoluteInput) || `.${mime.extension(mimeType) || "bin"}`;
1766
2426
  let title;
1767
2427
  let extractedText;
1768
2428
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
1769
2429
  extractedText = payloadBytes.toString("utf8");
1770
- title = titleFromText(path4.basename(absoluteInput, path4.extname(absoluteInput)), extractedText);
2430
+ title = titleFromText(path5.basename(absoluteInput, path5.extname(absoluteInput)), extractedText);
1771
2431
  } else {
1772
- title = path4.basename(absoluteInput, path4.extname(absoluteInput));
2432
+ title = path5.basename(absoluteInput, path5.extname(absoluteInput));
1773
2433
  }
1774
2434
  return {
1775
2435
  title,
@@ -1777,6 +2437,7 @@ async function prepareFileInput(_rootDir, absoluteInput) {
1777
2437
  sourceKind,
1778
2438
  language,
1779
2439
  originalPath: toPosix(absoluteInput),
2440
+ repoRelativePath: repoRelativePathFor(absoluteInput, repoRoot),
1780
2441
  mimeType,
1781
2442
  storedExtension,
1782
2443
  payloadBytes,
@@ -1838,7 +2499,7 @@ async function prepareUrlInput(input, options) {
1838
2499
  sourceKind = "markdown";
1839
2500
  storedExtension = ".md";
1840
2501
  } else {
1841
- const extension = path4.extname(inputUrl.pathname);
2502
+ const extension = path5.extname(inputUrl.pathname);
1842
2503
  storedExtension = extension || `.${mime.extension(mimeType) || "bin"}`;
1843
2504
  if (sourceKind === "markdown" || sourceKind === "text" || sourceKind === "code") {
1844
2505
  extractedText = payloadBytes.toString("utf8");
@@ -1888,14 +2549,14 @@ async function collectInboxAttachmentRefs(inputDir, files) {
1888
2549
  if (sourceKind !== "markdown") {
1889
2550
  continue;
1890
2551
  }
1891
- const content = await fs3.readFile(absolutePath, "utf8");
2552
+ const content = await fs5.readFile(absolutePath, "utf8");
1892
2553
  const refs = extractMarkdownReferences(content);
1893
2554
  if (!refs.length) {
1894
2555
  continue;
1895
2556
  }
1896
2557
  const sourceRefs = [];
1897
2558
  for (const ref of refs) {
1898
- const resolved = path4.resolve(path4.dirname(absolutePath), ref);
2559
+ const resolved = path5.resolve(path5.dirname(absolutePath), ref);
1899
2560
  if (!resolved.startsWith(inputDir) || !await fileExists(resolved)) {
1900
2561
  continue;
1901
2562
  }
@@ -1929,12 +2590,12 @@ function rewriteMarkdownReferences(content, replacements) {
1929
2590
  });
1930
2591
  }
1931
2592
  async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
1932
- const originalBytes = await fs3.readFile(absolutePath);
2593
+ const originalBytes = await fs5.readFile(absolutePath);
1933
2594
  const originalText = originalBytes.toString("utf8");
1934
- const title = titleFromText(path4.basename(absolutePath, path4.extname(absolutePath)), originalText);
2595
+ const title = titleFromText(path5.basename(absolutePath, path5.extname(absolutePath)), originalText);
1935
2596
  const attachments = [];
1936
2597
  for (const attachmentRef of attachmentRefs) {
1937
- const bytes = await fs3.readFile(attachmentRef.absolutePath);
2598
+ const bytes = await fs5.readFile(attachmentRef.absolutePath);
1938
2599
  attachments.push({
1939
2600
  relativePath: sanitizeAssetRelativePath(attachmentRef.relativeRef),
1940
2601
  mimeType: guessMimeType(attachmentRef.absolutePath),
@@ -1957,7 +2618,7 @@ async function prepareInboxMarkdownInput(absolutePath, attachmentRefs) {
1957
2618
  sourceKind: "markdown",
1958
2619
  originalPath: toPosix(absolutePath),
1959
2620
  mimeType: "text/markdown",
1960
- storedExtension: path4.extname(absolutePath) || ".md",
2621
+ storedExtension: path5.extname(absolutePath) || ".md",
1961
2622
  payloadBytes: Buffer.from(rewrittenText, "utf8"),
1962
2623
  extractedText: rewrittenText,
1963
2624
  attachments,
@@ -1970,13 +2631,53 @@ function isSupportedInboxKind(sourceKind) {
1970
2631
  async function ingestInput(rootDir, input, options) {
1971
2632
  const { paths } = await initWorkspace(rootDir);
1972
2633
  const normalizedOptions = normalizeIngestOptions(options);
1973
- 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);
1974
2637
  const result = await persistPreparedInput(rootDir, prepared, paths);
1975
2638
  return result.manifest;
1976
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
+ }
1977
2678
  async function importInbox(rootDir, inputDir) {
1978
2679
  const { paths } = await initWorkspace(rootDir);
1979
- const effectiveInputDir = path4.resolve(rootDir, inputDir ?? paths.inboxDir);
2680
+ const effectiveInputDir = path5.resolve(rootDir, inputDir ?? paths.inboxDir);
1980
2681
  if (!await fileExists(effectiveInputDir)) {
1981
2682
  throw new Error(`Inbox directory not found: ${effectiveInputDir}`);
1982
2683
  }
@@ -1987,31 +2688,31 @@ async function importInbox(rootDir, inputDir) {
1987
2688
  const skipped = [];
1988
2689
  let attachmentCount = 0;
1989
2690
  for (const absolutePath of files) {
1990
- const basename = path4.basename(absolutePath);
2691
+ const basename = path5.basename(absolutePath);
1991
2692
  if (basename.startsWith(".")) {
1992
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: "hidden_file" });
2693
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "hidden_file" });
1993
2694
  continue;
1994
2695
  }
1995
2696
  if (claimedAttachments.has(absolutePath)) {
1996
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
2697
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "referenced_attachment" });
1997
2698
  continue;
1998
2699
  }
1999
2700
  const mimeType = guessMimeType(absolutePath);
2000
2701
  const sourceKind = inferKind(mimeType, absolutePath);
2001
2702
  if (!isSupportedInboxKind(sourceKind)) {
2002
- 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}` });
2003
2704
  continue;
2004
2705
  }
2005
2706
  const prepared = sourceKind === "markdown" && refsBySource.has(absolutePath) ? await prepareInboxMarkdownInput(absolutePath, refsBySource.get(absolutePath) ?? []) : await prepareFileInput(rootDir, absolutePath);
2006
2707
  const result = await persistPreparedInput(rootDir, prepared, paths);
2007
2708
  if (!result.isNew) {
2008
- skipped.push({ path: toPosix(path4.relative(rootDir, absolutePath)), reason: "duplicate_content" });
2709
+ skipped.push({ path: toPosix(path5.relative(rootDir, absolutePath)), reason: "duplicate_content" });
2009
2710
  continue;
2010
2711
  }
2011
2712
  attachmentCount += result.manifest.attachments?.length ?? 0;
2012
2713
  imported.push(result.manifest);
2013
2714
  }
2014
- await appendLogEntry(rootDir, "inbox_import", toPosix(path4.relative(rootDir, effectiveInputDir)) || ".", [
2715
+ await appendLogEntry(rootDir, "inbox_import", toPosix(path5.relative(rootDir, effectiveInputDir)) || ".", [
2015
2716
  `scanned=${files.length}`,
2016
2717
  `imported=${imported.length}`,
2017
2718
  `attachments=${attachmentCount}`,
@@ -2030,9 +2731,9 @@ async function listManifests(rootDir) {
2030
2731
  if (!await fileExists(paths.manifestsDir)) {
2031
2732
  return [];
2032
2733
  }
2033
- const entries = await fs3.readdir(paths.manifestsDir);
2734
+ const entries = await fs5.readdir(paths.manifestsDir);
2034
2735
  const manifests = await Promise.all(
2035
- 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)))
2036
2737
  );
2037
2738
  return manifests.filter((manifest) => Boolean(manifest));
2038
2739
  }
@@ -2040,28 +2741,28 @@ async function readExtractedText(rootDir, manifest) {
2040
2741
  if (!manifest.extractedTextPath) {
2041
2742
  return void 0;
2042
2743
  }
2043
- const absolutePath = path4.resolve(rootDir, manifest.extractedTextPath);
2744
+ const absolutePath = path5.resolve(rootDir, manifest.extractedTextPath);
2044
2745
  if (!await fileExists(absolutePath)) {
2045
2746
  return void 0;
2046
2747
  }
2047
- return fs3.readFile(absolutePath, "utf8");
2748
+ return fs5.readFile(absolutePath, "utf8");
2048
2749
  }
2049
2750
 
2050
2751
  // src/mcp.ts
2051
- import fs10 from "fs/promises";
2052
- import path14 from "path";
2752
+ import fs12 from "fs/promises";
2753
+ import path15 from "path";
2053
2754
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2054
2755
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2055
2756
  import { z as z7 } from "zod";
2056
2757
 
2057
2758
  // src/schema.ts
2058
- import fs4 from "fs/promises";
2059
- import path5 from "path";
2759
+ import fs6 from "fs/promises";
2760
+ import path6 from "path";
2060
2761
  function normalizeSchemaContent(content) {
2061
2762
  return content.trim() ? content.trim() : defaultVaultSchema().trim();
2062
2763
  }
2063
2764
  async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
2064
- const content = await fileExists(schemaPath) ? await fs4.readFile(schemaPath, "utf8") : fallback;
2765
+ const content = await fileExists(schemaPath) ? await fs6.readFile(schemaPath, "utf8") : fallback;
2065
2766
  const normalized = normalizeSchemaContent(content);
2066
2767
  return {
2067
2768
  path: schemaPath,
@@ -2070,7 +2771,7 @@ async function readSchemaFile(schemaPath, fallback = defaultVaultSchema()) {
2070
2771
  };
2071
2772
  }
2072
2773
  function resolveProjectSchemaPath(rootDir, schemaPath) {
2073
- return path5.resolve(rootDir, schemaPath);
2774
+ return path6.resolve(rootDir, schemaPath);
2074
2775
  }
2075
2776
  function composeVaultSchema(root, projectSchemas = []) {
2076
2777
  if (!projectSchemas.length) {
@@ -2086,7 +2787,7 @@ function composeVaultSchema(root, projectSchemas = []) {
2086
2787
  (schema) => [
2087
2788
  `## Project Schema`,
2088
2789
  "",
2089
- `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)}`,
2090
2791
  "",
2091
2792
  schema.content
2092
2793
  ].join("\n")
@@ -2162,14 +2863,15 @@ function buildSchemaPrompt(schema, instruction) {
2162
2863
  }
2163
2864
 
2164
2865
  // src/vault.ts
2165
- import fs9 from "fs/promises";
2166
- import path13 from "path";
2866
+ import fs11 from "fs/promises";
2867
+ import path14 from "path";
2167
2868
  import matter7 from "gray-matter";
2168
2869
  import { z as z6 } from "zod";
2169
2870
 
2170
2871
  // src/analysis.ts
2171
- import path6 from "path";
2872
+ import path7 from "path";
2172
2873
  import { z } from "zod";
2874
+ var ANALYSIS_FORMAT_VERSION = 3;
2173
2875
  var sourceAnalysisSchema = z.object({
2174
2876
  title: z.string().min(1),
2175
2877
  summary: z.string().min(1),
@@ -2270,6 +2972,7 @@ function heuristicAnalysis(manifest, text, schemaHash) {
2270
2972
  }));
2271
2973
  const claimSentences = normalized.split(/(?<=[.!?])\s+/).filter(Boolean).slice(0, 4);
2272
2974
  return {
2975
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
2273
2976
  sourceId: manifest.sourceId,
2274
2977
  sourceHash: manifest.contentHash,
2275
2978
  schemaHash,
@@ -2314,6 +3017,7 @@ ${truncate(text, 18e3)}`
2314
3017
  sourceAnalysisSchema
2315
3018
  );
2316
3019
  return {
3020
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
2317
3021
  sourceId: manifest.sourceId,
2318
3022
  sourceHash: manifest.contentHash,
2319
3023
  schemaHash: schema.hash,
@@ -2342,17 +3046,18 @@ ${truncate(text, 18e3)}`
2342
3046
  };
2343
3047
  }
2344
3048
  async function analyzeSource(manifest, extractedText, provider, paths, schema) {
2345
- const cachePath = path6.join(paths.analysesDir, `${manifest.sourceId}.json`);
3049
+ const cachePath = path7.join(paths.analysesDir, `${manifest.sourceId}.json`);
2346
3050
  const cached = await readJsonFile(cachePath);
2347
- 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) {
2348
3052
  return cached;
2349
3053
  }
2350
3054
  const content = normalizeWhitespace(extractedText ?? "");
2351
3055
  let analysis;
2352
3056
  if (manifest.sourceKind === "code" && content) {
2353
- analysis = analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
3057
+ analysis = await analyzeCodeSource(manifest, extractedText ?? "", schema.hash);
2354
3058
  } else if (!content) {
2355
3059
  analysis = {
3060
+ analysisVersion: ANALYSIS_FORMAT_VERSION,
2356
3061
  sourceId: manifest.sourceId,
2357
3062
  sourceHash: manifest.contentHash,
2358
3063
  schemaHash: schema.hash,
@@ -2397,8 +3102,8 @@ function conflictConfidence(claimA, claimB) {
2397
3102
  }
2398
3103
 
2399
3104
  // src/deep-lint.ts
2400
- import fs5 from "fs/promises";
2401
- import path9 from "path";
3105
+ import fs7 from "fs/promises";
3106
+ import path10 from "path";
2402
3107
  import matter2 from "gray-matter";
2403
3108
  import { z as z4 } from "zod";
2404
3109
 
@@ -2419,7 +3124,7 @@ function normalizeFindingSeverity(value) {
2419
3124
 
2420
3125
  // src/orchestration.ts
2421
3126
  import { spawn } from "child_process";
2422
- import path7 from "path";
3127
+ import path8 from "path";
2423
3128
  import { z as z2 } from "zod";
2424
3129
  var orchestrationRoleResultSchema = z2.object({
2425
3130
  summary: z2.string().optional(),
@@ -2512,7 +3217,7 @@ async function runProviderRole(rootDir, role, roleConfig, input) {
2512
3217
  }
2513
3218
  async function runCommandRole(rootDir, role, executor, input) {
2514
3219
  const [command, ...args] = executor.command;
2515
- const cwd = executor.cwd ? path7.resolve(rootDir, executor.cwd) : rootDir;
3220
+ const cwd = executor.cwd ? path8.resolve(rootDir, executor.cwd) : rootDir;
2516
3221
  const child = spawn(command, args, {
2517
3222
  cwd,
2518
3223
  env: {
@@ -2606,7 +3311,7 @@ function summarizeRoleQuestions(results) {
2606
3311
  }
2607
3312
 
2608
3313
  // src/web-search/registry.ts
2609
- import path8 from "path";
3314
+ import path9 from "path";
2610
3315
  import { pathToFileURL } from "url";
2611
3316
  import { z as z3 } from "zod";
2612
3317
 
@@ -2704,7 +3409,7 @@ async function createWebSearchAdapter(id, config, rootDir) {
2704
3409
  if (!config.module) {
2705
3410
  throw new Error(`Web search provider ${id} is type "custom" but no module path was configured.`);
2706
3411
  }
2707
- 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);
2708
3413
  const loaded = await import(pathToFileURL(resolvedModule).href);
2709
3414
  const parsed = customWebSearchModuleSchema.parse(loaded);
2710
3415
  return parsed.createAdapter(id, config, rootDir);
@@ -2764,8 +3469,8 @@ async function loadContextPages(rootDir, graph) {
2764
3469
  );
2765
3470
  return Promise.all(
2766
3471
  contextPages.slice(0, 18).map(async (page) => {
2767
- const absolutePath = path9.join(paths.wikiDir, page.path);
2768
- 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(() => "");
2769
3474
  const parsed = matter2(raw);
2770
3475
  return {
2771
3476
  id: page.id,
@@ -2813,7 +3518,7 @@ function heuristicDeepFindings(contextPages, structuralFindings, graph) {
2813
3518
  code: "missing_citation",
2814
3519
  message: finding.message,
2815
3520
  pagePath: finding.pagePath,
2816
- 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
2817
3522
  });
2818
3523
  }
2819
3524
  for (const page of contextPages.filter((item) => item.kind === "source").slice(0, 3)) {
@@ -3167,9 +3872,7 @@ function buildModulePage(input) {
3167
3872
  const relatedOutputs = input.relatedOutputs ?? [];
3168
3873
  const backlinks = uniqueStrings([sourcePage.id, ...localModuleBacklinks, ...relatedOutputs.map((page) => page.id)]);
3169
3874
  const importsSection = code.imports.length ? code.imports.map((item) => {
3170
- const localModule = input.localModules.find(
3171
- (moduleRef) => moduleRef.specifier === item.specifier && moduleRef.reExport === item.reExport
3172
- );
3875
+ const localModule = item.resolvedSourceId ? input.localModules.find((moduleRef) => moduleRef.sourceId === item.resolvedSourceId && moduleRef.reExport === item.reExport) : void 0;
3173
3876
  const importedBits = [
3174
3877
  item.defaultImport ? `default \`${item.defaultImport}\`` : "",
3175
3878
  item.namespaceImport ? `namespace \`${item.namespaceImport}\`` : "",
@@ -3180,6 +3883,7 @@ function buildModulePage(input) {
3180
3883
  const suffix = importedBits.length ? ` (${importedBits.join("; ")})` : "";
3181
3884
  return `- ${mode} ${importTarget}${suffix}`;
3182
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}\`)` : ""}`);
3183
3887
  const exportsSection = code.exports.length ? code.exports.map((item) => `- \`${item}\``) : ["- No exports detected."];
3184
3888
  const symbolsSection = code.symbols.length ? code.symbols.map(
3185
3889
  (symbol) => `- \`${symbol.name}\` (${symbol.kind}${symbol.exported ? ", exported" : ""}): ${symbol.signature || "No signature recorded."}`
@@ -3226,7 +3930,10 @@ function buildModulePage(input) {
3226
3930
  "",
3227
3931
  `Source ID: \`${manifest.sourceId}\``,
3228
3932
  `Source Path: \`${manifest.originalPath ?? manifest.storedPath}\``,
3933
+ ...manifest.repoRelativePath ? [`Repo Path: \`${manifest.repoRelativePath}\``] : [],
3229
3934
  `Language: \`${code.language}\``,
3935
+ ...code.moduleName ? [`Module Name: \`${code.moduleName}\``] : [],
3936
+ ...code.namespace ? [`Namespace/Package: \`${code.namespace}\``] : [],
3230
3937
  `Source Page: [[${sourcePage.path.replace(/\.md$/, "")}|${sourcePage.title}]]`,
3231
3938
  "",
3232
3939
  "## Summary",
@@ -3249,6 +3956,10 @@ function buildModulePage(input) {
3249
3956
  "",
3250
3957
  ...code.dependencies.length ? code.dependencies.map((dependency) => `- \`${dependency}\``) : ["- No external dependencies detected."],
3251
3958
  "",
3959
+ "## Unresolved Local References",
3960
+ "",
3961
+ ...unresolvedLocalImports.length ? unresolvedLocalImports : ["- No unresolved local references detected."],
3962
+ "",
3252
3963
  "## Inheritance",
3253
3964
  "",
3254
3965
  ...inheritanceSection.length ? inheritanceSection : ["- No inheritance relationships detected."],
@@ -3990,13 +4701,13 @@ function buildOutputAssetManifest(input) {
3990
4701
  }
3991
4702
 
3992
4703
  // src/outputs.ts
3993
- import fs7 from "fs/promises";
3994
- import path11 from "path";
4704
+ import fs9 from "fs/promises";
4705
+ import path12 from "path";
3995
4706
  import matter5 from "gray-matter";
3996
4707
 
3997
4708
  // src/pages.ts
3998
- import fs6 from "fs/promises";
3999
- import path10 from "path";
4709
+ import fs8 from "fs/promises";
4710
+ import path11 from "path";
4000
4711
  import matter4 from "gray-matter";
4001
4712
  function normalizeStringArray(value) {
4002
4713
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
@@ -4068,7 +4779,7 @@ async function loadExistingManagedPageState(absolutePath, defaults = {}) {
4068
4779
  updatedAt: updatedFallback
4069
4780
  };
4070
4781
  }
4071
- const content = await fs6.readFile(absolutePath, "utf8");
4782
+ const content = await fs8.readFile(absolutePath, "utf8");
4072
4783
  const parsed = matter4(content);
4073
4784
  return {
4074
4785
  status: normalizePageStatus(parsed.data.status, defaults.status ?? "active"),
@@ -4107,7 +4818,7 @@ function parseStoredPage(relativePath, content, defaults = {}) {
4107
4818
  const now = (/* @__PURE__ */ new Date()).toISOString();
4108
4819
  const fallbackCreatedAt = defaults.createdAt ?? now;
4109
4820
  const fallbackUpdatedAt = defaults.updatedAt ?? fallbackCreatedAt;
4110
- 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");
4111
4822
  const kind = inferPageKind(relativePath, parsed.data.kind);
4112
4823
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
4113
4824
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
@@ -4146,18 +4857,18 @@ function parseStoredPage(relativePath, content, defaults = {}) {
4146
4857
  };
4147
4858
  }
4148
4859
  async function loadInsightPages(wikiDir) {
4149
- const insightsDir = path10.join(wikiDir, "insights");
4860
+ const insightsDir = path11.join(wikiDir, "insights");
4150
4861
  if (!await fileExists(insightsDir)) {
4151
4862
  return [];
4152
4863
  }
4153
- 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));
4154
4865
  const insights = [];
4155
4866
  for (const absolutePath of files) {
4156
- const relativePath = toPosix(path10.relative(wikiDir, absolutePath));
4157
- const content = await fs6.readFile(absolutePath, "utf8");
4867
+ const relativePath = toPosix(path11.relative(wikiDir, absolutePath));
4868
+ const content = await fs8.readFile(absolutePath, "utf8");
4158
4869
  const parsed = matter4(content);
4159
- const stats = await fs6.stat(absolutePath);
4160
- 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");
4161
4872
  const sourceIds = normalizeStringArray(parsed.data.source_ids);
4162
4873
  const projectIds = normalizeProjectIds(parsed.data.project_ids);
4163
4874
  const nodeIds = normalizeStringArray(parsed.data.node_ids);
@@ -4219,27 +4930,27 @@ function relatedOutputsForPage(targetPage, outputPages) {
4219
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);
4220
4931
  }
4221
4932
  async function resolveUniqueOutputSlug(wikiDir, baseSlug) {
4222
- const outputsDir = path11.join(wikiDir, "outputs");
4933
+ const outputsDir = path12.join(wikiDir, "outputs");
4223
4934
  const root = baseSlug || "output";
4224
4935
  let candidate = root;
4225
4936
  let counter = 2;
4226
- while (await fileExists(path11.join(outputsDir, `${candidate}.md`))) {
4937
+ while (await fileExists(path12.join(outputsDir, `${candidate}.md`))) {
4227
4938
  candidate = `${root}-${counter}`;
4228
4939
  counter++;
4229
4940
  }
4230
4941
  return candidate;
4231
4942
  }
4232
4943
  async function loadSavedOutputPages(wikiDir) {
4233
- const outputsDir = path11.join(wikiDir, "outputs");
4234
- 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(() => []);
4235
4946
  const outputs = [];
4236
4947
  for (const entry of entries) {
4237
4948
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md") {
4238
4949
  continue;
4239
4950
  }
4240
- const relativePath = path11.posix.join("outputs", entry.name);
4241
- const absolutePath = path11.join(outputsDir, entry.name);
4242
- 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");
4243
4954
  const parsed = matter5(content);
4244
4955
  const slug = entry.name.replace(/\.md$/, "");
4245
4956
  const title = typeof parsed.data.title === "string" ? parsed.data.title : slug;
@@ -4252,7 +4963,7 @@ async function loadSavedOutputPages(wikiDir) {
4252
4963
  const relatedSourceIds = normalizeStringArray(parsed.data.related_source_ids);
4253
4964
  const backlinks = normalizeStringArray(parsed.data.backlinks);
4254
4965
  const compiledFrom = normalizeStringArray(parsed.data.compiled_from);
4255
- const stats = await fs7.stat(absolutePath);
4966
+ const stats = await fs9.stat(absolutePath);
4256
4967
  const createdAt = typeof parsed.data.created_at === "string" ? parsed.data.created_at : stats.birthtimeMs > 0 ? stats.birthtime.toISOString() : stats.mtime.toISOString();
4257
4968
  const updatedAt = typeof parsed.data.updated_at === "string" ? parsed.data.updated_at : stats.mtime.toISOString();
4258
4969
  outputs.push({
@@ -4290,8 +5001,8 @@ async function loadSavedOutputPages(wikiDir) {
4290
5001
  }
4291
5002
 
4292
5003
  // src/search.ts
4293
- import fs8 from "fs/promises";
4294
- import path12 from "path";
5004
+ import fs10 from "fs/promises";
5005
+ import path13 from "path";
4295
5006
  import matter6 from "gray-matter";
4296
5007
  function getDatabaseSync() {
4297
5008
  const builtin = process.getBuiltinModule?.("node:sqlite");
@@ -4311,7 +5022,7 @@ function normalizeStatus(value) {
4311
5022
  return value === "draft" || value === "candidate" || value === "active" || value === "archived" ? value : void 0;
4312
5023
  }
4313
5024
  async function rebuildSearchIndex(dbPath, pages, wikiDir) {
4314
- await ensureDir(path12.dirname(dbPath));
5025
+ await ensureDir(path13.dirname(dbPath));
4315
5026
  const DatabaseSync = getDatabaseSync();
4316
5027
  const db = new DatabaseSync(dbPath);
4317
5028
  db.exec("PRAGMA journal_mode = WAL;");
@@ -4341,8 +5052,8 @@ async function rebuildSearchIndex(dbPath, pages, wikiDir) {
4341
5052
  "INSERT INTO pages (id, path, title, body, kind, status, project_ids, project_key) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
4342
5053
  );
4343
5054
  for (const page of pages) {
4344
- const absolutePath = path12.join(wikiDir, page.path);
4345
- const content = await fs8.readFile(absolutePath, "utf8");
5055
+ const absolutePath = path13.join(wikiDir, page.path);
5056
+ const content = await fs10.readFile(absolutePath, "utf8");
4346
5057
  const parsed = matter6(content);
4347
5058
  insertPage.run(
4348
5059
  page.id,
@@ -4445,7 +5156,7 @@ function outputFormatInstruction(format) {
4445
5156
  }
4446
5157
  }
4447
5158
  function outputAssetPath(slug, fileName) {
4448
- return toPosix(path13.join("outputs", "assets", slug, fileName));
5159
+ return toPosix(path14.join("outputs", "assets", slug, fileName));
4449
5160
  }
4450
5161
  function outputAssetId(slug, role) {
4451
5162
  return `output:${slug}:asset:${role}`;
@@ -4585,7 +5296,7 @@ async function resolveImageGenerationProvider(rootDir) {
4585
5296
  if (!providerConfig) {
4586
5297
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
4587
5298
  }
4588
- const { createProvider: createProvider2 } = await import("./registry-YDXVCE4Q.js");
5299
+ const { createProvider: createProvider2 } = await import("./registry-JFEW5RUP.js");
4589
5300
  return createProvider2(preferredProviderId, providerConfig, rootDir);
4590
5301
  }
4591
5302
  async function generateOutputArtifacts(rootDir, input) {
@@ -4783,7 +5494,7 @@ async function generateOutputArtifacts(rootDir, input) {
4783
5494
  };
4784
5495
  }
4785
5496
  function normalizeProjectRoot(root) {
4786
- const normalized = toPosix(path13.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
5497
+ const normalized = toPosix(path14.posix.normalize(root.replace(/\\/g, "/"))).replace(/^\.\/+/, "").replace(/\/+$/, "");
4787
5498
  return normalized;
4788
5499
  }
4789
5500
  function projectEntries(config) {
@@ -4809,10 +5520,10 @@ function manifestPathForProject(rootDir, manifest) {
4809
5520
  if (!rawPath) {
4810
5521
  return toPosix(manifest.storedPath);
4811
5522
  }
4812
- if (!path13.isAbsolute(rawPath)) {
5523
+ if (!path14.isAbsolute(rawPath)) {
4813
5524
  return normalizeProjectRoot(rawPath);
4814
5525
  }
4815
- const relative = toPosix(path13.relative(rootDir, rawPath));
5526
+ const relative = toPosix(path14.relative(rootDir, rawPath));
4816
5527
  return relative.startsWith("..") ? toPosix(rawPath) : normalizeProjectRoot(relative);
4817
5528
  }
4818
5529
  function prefixMatches(value, prefix) {
@@ -4985,7 +5696,7 @@ function pageHashes(pages) {
4985
5696
  return Object.fromEntries(pages.map((page) => [page.page.id, page.contentHash]));
4986
5697
  }
4987
5698
  async function buildManagedGraphPage(absolutePath, defaults, build) {
4988
- const existingContent = await fileExists(absolutePath) ? await fs9.readFile(absolutePath, "utf8") : null;
5699
+ const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : null;
4989
5700
  let existing = await loadExistingManagedPageState(absolutePath, {
4990
5701
  status: defaults.status ?? "active",
4991
5702
  managedBy: defaults.managedBy
@@ -5023,7 +5734,7 @@ async function buildManagedGraphPage(absolutePath, defaults, build) {
5023
5734
  return built;
5024
5735
  }
5025
5736
  async function buildManagedContent(absolutePath, defaults, build) {
5026
- const existingContent = await fileExists(absolutePath) ? await fs9.readFile(absolutePath, "utf8") : null;
5737
+ const existingContent = await fileExists(absolutePath) ? await fs11.readFile(absolutePath, "utf8") : null;
5027
5738
  let existing = await loadExistingManagedPageState(absolutePath, {
5028
5739
  status: defaults.status ?? "active",
5029
5740
  managedBy: defaults.managedBy
@@ -5142,7 +5853,7 @@ function deriveGraphMetrics(nodes, edges) {
5142
5853
  communities
5143
5854
  };
5144
5855
  }
5145
- function buildGraph(manifests, analyses, pages, sourceProjects) {
5856
+ function buildGraph(manifests, analyses, pages, sourceProjects, _codeIndex) {
5146
5857
  const sourceNodes = manifests.map((manifest) => ({
5147
5858
  id: `source:${manifest.sourceId}`,
5148
5859
  type: "source",
@@ -5281,11 +5992,16 @@ function buildGraph(manifests, analyses, pages, sourceProjects) {
5281
5992
  const symbolIdsByName = new Map(analysis.code.symbols.map((symbol) => [symbol.name, symbol.id]));
5282
5993
  const importedSymbolIdsByName = /* @__PURE__ */ new Map();
5283
5994
  for (const codeImport of analysis.code.imports.filter((item) => !item.isExternal)) {
5284
- const targetSourceId = resolveCodeImportSourceId(manifest, codeImport.specifier, manifests);
5995
+ const targetSourceId = codeImport.resolvedSourceId;
5285
5996
  const targetAnalysis = targetSourceId ? analysesBySourceId.get(targetSourceId) : void 0;
5286
5997
  if (!targetSourceId || !targetAnalysis?.code) {
5287
5998
  continue;
5288
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
+ }
5289
6005
  for (const importedSymbol of codeImport.importedSymbols) {
5290
6006
  const [rawExportedName, rawLocalName] = importedSymbol.split(/\s+as\s+/i);
5291
6007
  const exportedName = (rawExportedName ?? "").trim();
@@ -5347,7 +6063,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects) {
5347
6063
  }
5348
6064
  }
5349
6065
  for (const codeImport of analysis.code.imports) {
5350
- const targetSourceId = resolveCodeImportSourceId(manifest, codeImport.specifier, manifests);
6066
+ const targetSourceId = codeImport.resolvedSourceId;
5351
6067
  if (!targetSourceId) {
5352
6068
  continue;
5353
6069
  }
@@ -5415,7 +6131,7 @@ function buildGraph(manifests, analyses, pages, sourceProjects) {
5415
6131
  };
5416
6132
  }
5417
6133
  async function writePage(wikiDir, relativePath, content, changedPages) {
5418
- const absolutePath = path13.resolve(wikiDir, relativePath);
6134
+ const absolutePath = path14.resolve(wikiDir, relativePath);
5419
6135
  const changed = await writeFileIfChanged(absolutePath, content);
5420
6136
  if (changed) {
5421
6137
  changedPages.push(relativePath);
@@ -5475,15 +6191,16 @@ function recordsEqual(left, right) {
5475
6191
  async function requiredCompileArtifactsExist(paths) {
5476
6192
  const requiredPaths = [
5477
6193
  paths.graphPath,
6194
+ paths.codeIndexPath,
5478
6195
  paths.searchDbPath,
5479
- path13.join(paths.wikiDir, "index.md"),
5480
- path13.join(paths.wikiDir, "sources", "index.md"),
5481
- path13.join(paths.wikiDir, "code", "index.md"),
5482
- path13.join(paths.wikiDir, "concepts", "index.md"),
5483
- path13.join(paths.wikiDir, "entities", "index.md"),
5484
- path13.join(paths.wikiDir, "outputs", "index.md"),
5485
- path13.join(paths.wikiDir, "projects", "index.md"),
5486
- 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")
5487
6204
  ];
5488
6205
  const checks = await Promise.all(requiredPaths.map((filePath) => fileExists(filePath)));
5489
6206
  return checks.every(Boolean);
@@ -5491,7 +6208,7 @@ async function requiredCompileArtifactsExist(paths) {
5491
6208
  async function loadCachedAnalyses(paths, manifests) {
5492
6209
  return Promise.all(
5493
6210
  manifests.map(async (manifest) => {
5494
- const cached = await readJsonFile(path13.join(paths.analysesDir, `${manifest.sourceId}.json`));
6211
+ const cached = await readJsonFile(path14.join(paths.analysesDir, `${manifest.sourceId}.json`));
5495
6212
  if (!cached) {
5496
6213
  throw new Error(`Missing cached analysis for ${manifest.sourceId}. Run \`swarmvault compile\` first.`);
5497
6214
  }
@@ -5500,10 +6217,10 @@ async function loadCachedAnalyses(paths, manifests) {
5500
6217
  );
5501
6218
  }
5502
6219
  function approvalManifestPath(paths, approvalId) {
5503
- return path13.join(paths.approvalsDir, approvalId, "manifest.json");
6220
+ return path14.join(paths.approvalsDir, approvalId, "manifest.json");
5504
6221
  }
5505
6222
  function approvalGraphPath(paths, approvalId) {
5506
- return path13.join(paths.approvalsDir, approvalId, "state", "graph.json");
6223
+ return path14.join(paths.approvalsDir, approvalId, "state", "graph.json");
5507
6224
  }
5508
6225
  async function readApprovalManifest(paths, approvalId) {
5509
6226
  const manifest = await readJsonFile(approvalManifestPath(paths, approvalId));
@@ -5513,7 +6230,7 @@ async function readApprovalManifest(paths, approvalId) {
5513
6230
  return manifest;
5514
6231
  }
5515
6232
  async function writeApprovalManifest(paths, manifest) {
5516
- 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)}
5517
6234
  `, "utf8");
5518
6235
  }
5519
6236
  async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousGraph, graph) {
@@ -5528,7 +6245,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
5528
6245
  continue;
5529
6246
  }
5530
6247
  const previousPage = previousPagesById.get(nextPage.id);
5531
- const currentExists = await fileExists(path13.join(paths.wikiDir, file.relativePath));
6248
+ const currentExists = await fileExists(path14.join(paths.wikiDir, file.relativePath));
5532
6249
  if (previousPage && previousPage.path !== nextPage.path) {
5533
6250
  entries.push({
5534
6251
  pageId: nextPage.id,
@@ -5561,7 +6278,7 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
5561
6278
  const previousPage = previousPagesByPath.get(deletedPath);
5562
6279
  entries.push({
5563
6280
  pageId: previousPage?.id ?? `page:${slugify(deletedPath)}`,
5564
- title: previousPage?.title ?? path13.basename(deletedPath, ".md"),
6281
+ title: previousPage?.title ?? path14.basename(deletedPath, ".md"),
5565
6282
  kind: previousPage?.kind ?? "index",
5566
6283
  changeType: "delete",
5567
6284
  status: "pending",
@@ -5573,16 +6290,16 @@ async function buildApprovalEntries(paths, changedFiles, deletedPaths, previousG
5573
6290
  }
5574
6291
  async function stageApprovalBundle(paths, changedFiles, deletedPaths, previousGraph, graph) {
5575
6292
  const approvalId = `compile-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
5576
- const approvalDir = path13.join(paths.approvalsDir, approvalId);
6293
+ const approvalDir = path14.join(paths.approvalsDir, approvalId);
5577
6294
  await ensureDir(approvalDir);
5578
- await ensureDir(path13.join(approvalDir, "wiki"));
5579
- await ensureDir(path13.join(approvalDir, "state"));
6295
+ await ensureDir(path14.join(approvalDir, "wiki"));
6296
+ await ensureDir(path14.join(approvalDir, "state"));
5580
6297
  for (const file of changedFiles) {
5581
- const targetPath = path13.join(approvalDir, "wiki", file.relativePath);
5582
- await ensureDir(path13.dirname(targetPath));
5583
- 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");
5584
6301
  }
5585
- 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");
5586
6303
  await writeApprovalManifest(paths, {
5587
6304
  approvalId,
5588
6305
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5642,7 +6359,7 @@ async function syncVaultArtifacts(rootDir, input) {
5642
6359
  confidence: 1
5643
6360
  });
5644
6361
  const sourceRecord = await buildManagedGraphPage(
5645
- path13.join(paths.wikiDir, preview.path),
6362
+ path14.join(paths.wikiDir, preview.path),
5646
6363
  {
5647
6364
  managedBy: "system",
5648
6365
  confidence: 1,
@@ -5664,7 +6381,7 @@ async function syncVaultArtifacts(rootDir, input) {
5664
6381
  records.push(sourceRecord);
5665
6382
  if (modulePreview && analysis.code) {
5666
6383
  const localModules = analysis.code.imports.map((codeImport) => {
5667
- const resolvedSourceId = resolveCodeImportSourceId(manifest, codeImport.specifier, input.manifests);
6384
+ const resolvedSourceId = codeImport.resolvedSourceId;
5668
6385
  if (!resolvedSourceId) {
5669
6386
  return null;
5670
6387
  }
@@ -5687,7 +6404,7 @@ async function syncVaultArtifacts(rootDir, input) {
5687
6404
  );
5688
6405
  records.push(
5689
6406
  await buildManagedGraphPage(
5690
- path13.join(paths.wikiDir, modulePreview.path),
6407
+ path14.join(paths.wikiDir, modulePreview.path),
5691
6408
  {
5692
6409
  managedBy: "system",
5693
6410
  confidence: 1,
@@ -5720,8 +6437,8 @@ async function syncVaultArtifacts(rootDir, input) {
5720
6437
  const promoted = previousEntry?.status === "active" || promoteCandidates && shouldPromoteCandidate(previousEntry, sourceIds);
5721
6438
  const relativePath = promoted ? activeAggregatePath(itemKind, slug) : candidatePagePathFor(itemKind, slug);
5722
6439
  const fallbackPaths = [
5723
- path13.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
5724
- path13.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
6440
+ path14.join(paths.wikiDir, activeAggregatePath(itemKind, slug)),
6441
+ path14.join(paths.wikiDir, candidatePagePathFor(itemKind, slug))
5725
6442
  ];
5726
6443
  const confidence = nodeConfidence(aggregate.sourceAnalyses.length);
5727
6444
  const preview = emptyGraphPage({
@@ -5738,7 +6455,7 @@ async function syncVaultArtifacts(rootDir, input) {
5738
6455
  status: promoted ? "active" : "candidate"
5739
6456
  });
5740
6457
  const pageRecord = await buildManagedGraphPage(
5741
- path13.join(paths.wikiDir, relativePath),
6458
+ path14.join(paths.wikiDir, relativePath),
5742
6459
  {
5743
6460
  status: promoted ? "active" : "candidate",
5744
6461
  managedBy: "system",
@@ -5778,7 +6495,7 @@ async function syncVaultArtifacts(rootDir, input) {
5778
6495
  }
5779
6496
  const compiledPages = records.map((record) => record.page);
5780
6497
  const allPages = [...compiledPages, ...input.outputPages, ...input.insightPages];
5781
- const graph = buildGraph(input.manifests, input.analyses, allPages, input.sourceProjects);
6498
+ const graph = buildGraph(input.manifests, input.analyses, allPages, input.sourceProjects, input.codeIndex);
5782
6499
  const activeConceptPages = allPages.filter((page) => page.kind === "concept" && page.status !== "candidate");
5783
6500
  const activeEntityPages = allPages.filter((page) => page.kind === "entity" && page.status !== "candidate");
5784
6501
  const modulePages = allPages.filter((page) => page.kind === "module");
@@ -5812,7 +6529,7 @@ async function syncVaultArtifacts(rootDir, input) {
5812
6529
  confidence: 1
5813
6530
  }),
5814
6531
  content: await buildManagedContent(
5815
- path13.join(paths.wikiDir, "projects", "index.md"),
6532
+ path14.join(paths.wikiDir, "projects", "index.md"),
5816
6533
  {
5817
6534
  managedBy: "system",
5818
6535
  compiledFrom: indexCompiledFrom(projectIndexRefs)
@@ -5836,7 +6553,7 @@ async function syncVaultArtifacts(rootDir, input) {
5836
6553
  records.push({
5837
6554
  page: projectIndexRef,
5838
6555
  content: await buildManagedContent(
5839
- path13.join(paths.wikiDir, projectIndexRef.path),
6556
+ path14.join(paths.wikiDir, projectIndexRef.path),
5840
6557
  {
5841
6558
  managedBy: "system",
5842
6559
  compiledFrom: indexCompiledFrom(Object.values(sections).flat())
@@ -5864,7 +6581,7 @@ async function syncVaultArtifacts(rootDir, input) {
5864
6581
  confidence: 1
5865
6582
  }),
5866
6583
  content: await buildManagedContent(
5867
- path13.join(paths.wikiDir, "index.md"),
6584
+ path14.join(paths.wikiDir, "index.md"),
5868
6585
  {
5869
6586
  managedBy: "system",
5870
6587
  compiledFrom: indexCompiledFrom(allPages)
@@ -5894,7 +6611,7 @@ async function syncVaultArtifacts(rootDir, input) {
5894
6611
  confidence: 1
5895
6612
  }),
5896
6613
  content: await buildManagedContent(
5897
- path13.join(paths.wikiDir, relativePath),
6614
+ path14.join(paths.wikiDir, relativePath),
5898
6615
  {
5899
6616
  managedBy: "system",
5900
6617
  compiledFrom: indexCompiledFrom(pages)
@@ -5905,12 +6622,12 @@ async function syncVaultArtifacts(rootDir, input) {
5905
6622
  }
5906
6623
  const nextPagePaths = new Set(records.map((record) => record.page.path));
5907
6624
  const obsoleteGraphPaths = (previousGraph?.pages ?? []).filter((page) => page.kind !== "output" && page.kind !== "insight").map((page) => page.path).filter((relativePath) => !nextPagePaths.has(relativePath));
5908
- 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));
5909
6626
  const obsoletePaths = uniqueStrings2([...obsoleteGraphPaths, ...existingProjectIndexPaths]);
5910
6627
  const changedFiles = [];
5911
6628
  for (const record of records) {
5912
- const absolutePath = path13.join(paths.wikiDir, record.page.path);
5913
- 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;
5914
6631
  if (current !== record.content) {
5915
6632
  changedPages.push(record.page.path);
5916
6633
  changedFiles.push({ relativePath: record.page.path, content: record.content });
@@ -5935,9 +6652,10 @@ async function syncVaultArtifacts(rootDir, input) {
5935
6652
  await writePage(paths.wikiDir, record.page.path, record.content, writeChanges);
5936
6653
  }
5937
6654
  for (const relativePath of obsoletePaths) {
5938
- await fs9.rm(path13.join(paths.wikiDir, relativePath), { force: true });
6655
+ await fs11.rm(path14.join(paths.wikiDir, relativePath), { force: true });
5939
6656
  }
5940
6657
  await writeJsonFile(paths.graphPath, graph);
6658
+ await writeJsonFile(paths.codeIndexPath, input.codeIndex);
5941
6659
  await writeJsonFile(paths.compileStatePath, {
5942
6660
  generatedAt: graph.generatedAt,
5943
6661
  rootSchemaHash: input.schemas.root.hash,
@@ -5988,15 +6706,15 @@ async function refreshIndexesAndSearch(rootDir, pages) {
5988
6706
  })
5989
6707
  );
5990
6708
  await Promise.all([
5991
- ensureDir(path13.join(paths.wikiDir, "sources")),
5992
- ensureDir(path13.join(paths.wikiDir, "code")),
5993
- ensureDir(path13.join(paths.wikiDir, "concepts")),
5994
- ensureDir(path13.join(paths.wikiDir, "entities")),
5995
- ensureDir(path13.join(paths.wikiDir, "outputs")),
5996
- ensureDir(path13.join(paths.wikiDir, "projects")),
5997
- 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"))
5998
6716
  ]);
5999
- const projectsIndexPath = path13.join(paths.wikiDir, "projects", "index.md");
6717
+ const projectsIndexPath = path14.join(paths.wikiDir, "projects", "index.md");
6000
6718
  await writeFileIfChanged(
6001
6719
  projectsIndexPath,
6002
6720
  await buildManagedContent(
@@ -6017,7 +6735,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6017
6735
  outputs: pages.filter((page) => page.kind === "output" && page.projectIds.includes(project.id)),
6018
6736
  candidates: pages.filter((page) => page.status === "candidate" && page.projectIds.includes(project.id))
6019
6737
  };
6020
- const absolutePath = path13.join(paths.wikiDir, "projects", project.id, "index.md");
6738
+ const absolutePath = path14.join(paths.wikiDir, "projects", project.id, "index.md");
6021
6739
  await writeFileIfChanged(
6022
6740
  absolutePath,
6023
6741
  await buildManagedContent(
@@ -6035,7 +6753,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6035
6753
  )
6036
6754
  );
6037
6755
  }
6038
- const rootIndexPath = path13.join(paths.wikiDir, "index.md");
6756
+ const rootIndexPath = path14.join(paths.wikiDir, "index.md");
6039
6757
  await writeFileIfChanged(
6040
6758
  rootIndexPath,
6041
6759
  await buildManagedContent(
@@ -6055,7 +6773,7 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6055
6773
  ["outputs/index.md", "outputs", pages.filter((page) => page.kind === "output")],
6056
6774
  ["candidates/index.md", "candidates", pages.filter((page) => page.status === "candidate")]
6057
6775
  ]) {
6058
- const absolutePath = path13.join(paths.wikiDir, relativePath);
6776
+ const absolutePath = path14.join(paths.wikiDir, relativePath);
6059
6777
  await writeFileIfChanged(
6060
6778
  absolutePath,
6061
6779
  await buildManagedContent(
@@ -6068,13 +6786,13 @@ async function refreshIndexesAndSearch(rootDir, pages) {
6068
6786
  )
6069
6787
  );
6070
6788
  }
6071
- 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)));
6072
6790
  const allowedProjectIndexPaths = /* @__PURE__ */ new Set([
6073
6791
  "projects/index.md",
6074
6792
  ...configuredProjects.map((project) => `projects/${project.id}/index.md`)
6075
6793
  ]);
6076
6794
  await Promise.all(
6077
- 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 }))
6078
6796
  );
6079
6797
  await rebuildSearchIndex(paths.searchDbPath, pages, paths.wikiDir);
6080
6798
  }
@@ -6094,7 +6812,7 @@ async function prepareOutputPageSave(rootDir, input) {
6094
6812
  confidence: 0.74
6095
6813
  }
6096
6814
  });
6097
- const absolutePath = path13.join(paths.wikiDir, output.page.path);
6815
+ const absolutePath = path14.join(paths.wikiDir, output.page.path);
6098
6816
  return {
6099
6817
  page: output.page,
6100
6818
  savedPath: absolutePath,
@@ -6106,15 +6824,15 @@ async function prepareOutputPageSave(rootDir, input) {
6106
6824
  async function persistOutputPage(rootDir, input) {
6107
6825
  const { paths } = await loadVaultConfig(rootDir);
6108
6826
  const prepared = await prepareOutputPageSave(rootDir, input);
6109
- await ensureDir(path13.dirname(prepared.savedPath));
6110
- await fs9.writeFile(prepared.savedPath, prepared.content, "utf8");
6827
+ await ensureDir(path14.dirname(prepared.savedPath));
6828
+ await fs11.writeFile(prepared.savedPath, prepared.content, "utf8");
6111
6829
  for (const assetFile of prepared.assetFiles) {
6112
- const assetPath = path13.join(paths.wikiDir, assetFile.relativePath);
6113
- await ensureDir(path13.dirname(assetPath));
6830
+ const assetPath = path14.join(paths.wikiDir, assetFile.relativePath);
6831
+ await ensureDir(path14.dirname(assetPath));
6114
6832
  if (typeof assetFile.content === "string") {
6115
- await fs9.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6833
+ await fs11.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6116
6834
  } else {
6117
- await fs9.writeFile(assetPath, assetFile.content);
6835
+ await fs11.writeFile(assetPath, assetFile.content);
6118
6836
  }
6119
6837
  }
6120
6838
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -6135,7 +6853,7 @@ async function prepareExploreHubSave(rootDir, input) {
6135
6853
  confidence: 0.76
6136
6854
  }
6137
6855
  });
6138
- const absolutePath = path13.join(paths.wikiDir, hub.page.path);
6856
+ const absolutePath = path14.join(paths.wikiDir, hub.page.path);
6139
6857
  return {
6140
6858
  page: hub.page,
6141
6859
  savedPath: absolutePath,
@@ -6147,15 +6865,15 @@ async function prepareExploreHubSave(rootDir, input) {
6147
6865
  async function persistExploreHub(rootDir, input) {
6148
6866
  const { paths } = await loadVaultConfig(rootDir);
6149
6867
  const prepared = await prepareExploreHubSave(rootDir, input);
6150
- await ensureDir(path13.dirname(prepared.savedPath));
6151
- await fs9.writeFile(prepared.savedPath, prepared.content, "utf8");
6868
+ await ensureDir(path14.dirname(prepared.savedPath));
6869
+ await fs11.writeFile(prepared.savedPath, prepared.content, "utf8");
6152
6870
  for (const assetFile of prepared.assetFiles) {
6153
- const assetPath = path13.join(paths.wikiDir, assetFile.relativePath);
6154
- await ensureDir(path13.dirname(assetPath));
6871
+ const assetPath = path14.join(paths.wikiDir, assetFile.relativePath);
6872
+ await ensureDir(path14.dirname(assetPath));
6155
6873
  if (typeof assetFile.content === "string") {
6156
- await fs9.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6874
+ await fs11.writeFile(assetPath, assetFile.content, assetFile.encoding ?? "utf8");
6157
6875
  } else {
6158
- await fs9.writeFile(assetPath, assetFile.content);
6876
+ await fs11.writeFile(assetPath, assetFile.content);
6159
6877
  }
6160
6878
  }
6161
6879
  return { page: prepared.page, savedPath: prepared.savedPath, outputAssets: prepared.outputAssets };
@@ -6172,17 +6890,17 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
6172
6890
  }))
6173
6891
  ]);
6174
6892
  const approvalId = `schedule-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
6175
- const approvalDir = path13.join(paths.approvalsDir, approvalId);
6893
+ const approvalDir = path14.join(paths.approvalsDir, approvalId);
6176
6894
  await ensureDir(approvalDir);
6177
- await ensureDir(path13.join(approvalDir, "wiki"));
6178
- await ensureDir(path13.join(approvalDir, "state"));
6895
+ await ensureDir(path14.join(approvalDir, "wiki"));
6896
+ await ensureDir(path14.join(approvalDir, "state"));
6179
6897
  for (const file of changedFiles) {
6180
- const targetPath = path13.join(approvalDir, "wiki", file.relativePath);
6181
- await ensureDir(path13.dirname(targetPath));
6898
+ const targetPath = path14.join(approvalDir, "wiki", file.relativePath);
6899
+ await ensureDir(path14.dirname(targetPath));
6182
6900
  if ("binary" in file && file.binary) {
6183
- await fs9.writeFile(targetPath, Buffer.from(file.content, "base64"));
6901
+ await fs11.writeFile(targetPath, Buffer.from(file.content, "base64"));
6184
6902
  } else {
6185
- await fs9.writeFile(targetPath, file.content, "utf8");
6903
+ await fs11.writeFile(targetPath, file.content, "utf8");
6186
6904
  }
6187
6905
  }
6188
6906
  const nextPages = sortGraphPages([
@@ -6196,7 +6914,7 @@ async function stageOutputApprovalBundle(rootDir, stagedPages) {
6196
6914
  sources: previousGraph?.sources ?? [],
6197
6915
  pages: nextPages
6198
6916
  };
6199
- 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");
6200
6918
  await writeApprovalManifest(paths, {
6201
6919
  approvalId,
6202
6920
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6225,9 +6943,9 @@ async function executeQuery(rootDir, question, format) {
6225
6943
  const searchResults = searchPages(paths.searchDbPath, question, 5);
6226
6944
  const excerpts = await Promise.all(
6227
6945
  searchResults.map(async (result) => {
6228
- const absolutePath = path13.join(paths.wikiDir, result.path);
6946
+ const absolutePath = path14.join(paths.wikiDir, result.path);
6229
6947
  try {
6230
- const content = await fs9.readFile(absolutePath, "utf8");
6948
+ const content = await fs11.readFile(absolutePath, "utf8");
6231
6949
  const parsed = matter7(content);
6232
6950
  return `# ${result.title}
6233
6951
  ${truncate(normalizeWhitespace(parsed.content), 1200)}`;
@@ -6336,13 +7054,19 @@ async function refreshVaultAfterOutputSave(rootDir) {
6336
7054
  const schemas = await loadVaultSchemas(rootDir);
6337
7055
  const manifests = await listManifests(rootDir);
6338
7056
  const sourceProjects = resolveSourceProjects(rootDir, manifests, config);
6339
- 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
+ });
6340
7063
  const storedOutputs = await loadSavedOutputPages(paths.wikiDir);
6341
7064
  const storedInsights = await loadInsightPages(paths.wikiDir);
6342
7065
  await syncVaultArtifacts(rootDir, {
6343
7066
  schemas,
6344
7067
  manifests,
6345
7068
  analyses,
7069
+ codeIndex,
6346
7070
  sourceProjects,
6347
7071
  outputPages: storedOutputs.map((page) => page.page),
6348
7072
  insightPages: storedInsights.map((page) => page.page),
@@ -6403,7 +7127,7 @@ function sortGraphPages(pages) {
6403
7127
  async function listApprovals(rootDir) {
6404
7128
  const { paths } = await loadVaultConfig(rootDir);
6405
7129
  const manifests = await Promise.all(
6406
- (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) => {
6407
7131
  try {
6408
7132
  return await readApprovalManifest(paths, entry.name);
6409
7133
  } catch {
@@ -6419,8 +7143,8 @@ async function readApproval(rootDir, approvalId) {
6419
7143
  const details = await Promise.all(
6420
7144
  manifest.entries.map(async (entry) => {
6421
7145
  const currentPath = entry.previousPath ?? entry.nextPath;
6422
- const currentContent = currentPath ? await fs9.readFile(path13.join(paths.wikiDir, currentPath), "utf8").catch(() => void 0) : void 0;
6423
- 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;
6424
7148
  return {
6425
7149
  ...entry,
6426
7150
  currentContent,
@@ -6448,26 +7172,26 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
6448
7172
  if (!entry.nextPath) {
6449
7173
  throw new Error(`Approval entry ${entry.pageId} is missing a staged path.`);
6450
7174
  }
6451
- const stagedAbsolutePath = path13.join(paths.approvalsDir, approvalId, "wiki", entry.nextPath);
6452
- const stagedContent = await fs9.readFile(stagedAbsolutePath, "utf8");
6453
- const targetAbsolutePath = path13.join(paths.wikiDir, entry.nextPath);
6454
- await ensureDir(path13.dirname(targetAbsolutePath));
6455
- 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");
6456
7180
  if (entry.changeType === "promote" && entry.previousPath) {
6457
- await fs9.rm(path13.join(paths.wikiDir, entry.previousPath), { force: true });
7181
+ await fs11.rm(path14.join(paths.wikiDir, entry.previousPath), { force: true });
6458
7182
  }
6459
7183
  const nextPage = bundleGraph?.pages.find((page) => page.id === entry.pageId && page.path === entry.nextPath) ?? parseStoredPage(entry.nextPath, stagedContent);
6460
7184
  if (nextPage.kind === "output" && nextPage.outputAssets?.length) {
6461
- const outputAssetDir = path13.join(paths.wikiDir, "outputs", "assets", path13.basename(nextPage.path, ".md"));
6462
- 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 });
6463
7187
  for (const asset of nextPage.outputAssets) {
6464
- const stagedAssetPath = path13.join(paths.approvalsDir, approvalId, "wiki", asset.path);
7188
+ const stagedAssetPath = path14.join(paths.approvalsDir, approvalId, "wiki", asset.path);
6465
7189
  if (!await fileExists(stagedAssetPath)) {
6466
7190
  continue;
6467
7191
  }
6468
- const targetAssetPath = path13.join(paths.wikiDir, asset.path);
6469
- await ensureDir(path13.dirname(targetAssetPath));
6470
- 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);
6471
7195
  }
6472
7196
  }
6473
7197
  nextPages = nextPages.filter(
@@ -6478,10 +7202,10 @@ async function acceptApproval(rootDir, approvalId, targets = []) {
6478
7202
  } else {
6479
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;
6480
7204
  if (entry.previousPath) {
6481
- await fs9.rm(path13.join(paths.wikiDir, entry.previousPath), { force: true });
7205
+ await fs11.rm(path14.join(paths.wikiDir, entry.previousPath), { force: true });
6482
7206
  }
6483
7207
  if (deletedPage?.kind === "output") {
6484
- 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")), {
6485
7209
  recursive: true,
6486
7210
  force: true
6487
7211
  });
@@ -6571,7 +7295,7 @@ async function promoteCandidate(rootDir, target) {
6571
7295
  const { paths } = await loadVaultConfig(rootDir);
6572
7296
  const graph = await readJsonFile(paths.graphPath);
6573
7297
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
6574
- 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");
6575
7299
  const parsed = matter7(raw);
6576
7300
  const nextUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
6577
7301
  const nextContent = matter7.stringify(parsed.content, {
@@ -6583,10 +7307,10 @@ async function promoteCandidate(rootDir, target) {
6583
7307
  )
6584
7308
  });
6585
7309
  const nextPath = candidateActivePath(candidate);
6586
- const nextAbsolutePath = path13.join(paths.wikiDir, nextPath);
6587
- await ensureDir(path13.dirname(nextAbsolutePath));
6588
- await fs9.writeFile(nextAbsolutePath, nextContent, "utf8");
6589
- 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 });
6590
7314
  const nextPage = parseStoredPage(nextPath, nextContent, { createdAt: candidate.createdAt, updatedAt: nextUpdatedAt });
6591
7315
  const nextPages = sortGraphPages(
6592
7316
  (graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path).concat(nextPage)
@@ -6630,7 +7354,7 @@ async function archiveCandidate(rootDir, target) {
6630
7354
  const { paths } = await loadVaultConfig(rootDir);
6631
7355
  const graph = await readJsonFile(paths.graphPath);
6632
7356
  const candidate = resolveCandidateTarget(graph?.pages ?? [], target);
6633
- await fs9.rm(path13.join(paths.wikiDir, candidate.path), { force: true });
7357
+ await fs11.rm(path14.join(paths.wikiDir, candidate.path), { force: true });
6634
7358
  const nextPages = sortGraphPages((graph?.pages ?? []).filter((page) => page.id !== candidate.id && page.path !== candidate.path));
6635
7359
  const nextGraph = {
6636
7360
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6668,18 +7392,18 @@ async function archiveCandidate(rootDir, target) {
6668
7392
  }
6669
7393
  async function ensureObsidianWorkspace(rootDir) {
6670
7394
  const { config } = await loadVaultConfig(rootDir);
6671
- const obsidianDir = path13.join(rootDir, ".obsidian");
7395
+ const obsidianDir = path14.join(rootDir, ".obsidian");
6672
7396
  const projectIds = projectEntries(config).map((project) => project.id);
6673
7397
  await ensureDir(obsidianDir);
6674
7398
  await Promise.all([
6675
- writeJsonFile(path13.join(obsidianDir, "app.json"), {
7399
+ writeJsonFile(path14.join(obsidianDir, "app.json"), {
6676
7400
  alwaysUpdateLinks: true,
6677
7401
  newFileLocation: "folder",
6678
7402
  newFileFolderPath: "wiki/insights",
6679
7403
  useMarkdownLinks: false,
6680
7404
  attachmentFolderPath: "raw/assets"
6681
7405
  }),
6682
- writeJsonFile(path13.join(obsidianDir, "core-plugins.json"), [
7406
+ writeJsonFile(path14.join(obsidianDir, "core-plugins.json"), [
6683
7407
  "file-explorer",
6684
7408
  "global-search",
6685
7409
  "switcher",
@@ -6689,7 +7413,7 @@ async function ensureObsidianWorkspace(rootDir) {
6689
7413
  "tag-pane",
6690
7414
  "page-preview"
6691
7415
  ]),
6692
- writeJsonFile(path13.join(obsidianDir, "graph.json"), {
7416
+ writeJsonFile(path14.join(obsidianDir, "graph.json"), {
6693
7417
  "collapse-filter": false,
6694
7418
  search: "",
6695
7419
  showTags: true,
@@ -6701,7 +7425,7 @@ async function ensureObsidianWorkspace(rootDir) {
6701
7425
  })),
6702
7426
  localJumps: false
6703
7427
  }),
6704
- writeJsonFile(path13.join(obsidianDir, "workspace.json"), {
7428
+ writeJsonFile(path14.join(obsidianDir, "workspace.json"), {
6705
7429
  active: "root",
6706
7430
  lastOpenFiles: ["wiki/index.md", "wiki/projects/index.md", "wiki/candidates/index.md", "wiki/insights/index.md"],
6707
7431
  left: {
@@ -6716,7 +7440,7 @@ async function ensureObsidianWorkspace(rootDir) {
6716
7440
  async function initVault(rootDir, options = {}) {
6717
7441
  const { paths } = await initWorkspace(rootDir);
6718
7442
  await installConfiguredAgents(rootDir);
6719
- const insightsIndexPath = path13.join(paths.wikiDir, "insights", "index.md");
7443
+ const insightsIndexPath = path14.join(paths.wikiDir, "insights", "index.md");
6720
7444
  const now = (/* @__PURE__ */ new Date()).toISOString();
6721
7445
  await writeFileIfChanged(
6722
7446
  insightsIndexPath,
@@ -6752,7 +7476,7 @@ async function initVault(rootDir, options = {}) {
6752
7476
  )
6753
7477
  );
6754
7478
  await writeFileIfChanged(
6755
- path13.join(paths.wikiDir, "projects", "index.md"),
7479
+ path14.join(paths.wikiDir, "projects", "index.md"),
6756
7480
  matter7.stringify(["# Projects", "", "- Run `swarmvault compile` to build project rollups.", ""].join("\n"), {
6757
7481
  page_id: "projects:index",
6758
7482
  kind: "index",
@@ -6774,7 +7498,7 @@ async function initVault(rootDir, options = {}) {
6774
7498
  })
6775
7499
  );
6776
7500
  await writeFileIfChanged(
6777
- path13.join(paths.wikiDir, "candidates", "index.md"),
7501
+ path14.join(paths.wikiDir, "candidates", "index.md"),
6778
7502
  matter7.stringify(["# Candidates", "", "- Run `swarmvault compile` to stage candidate pages.", ""].join("\n"), {
6779
7503
  page_id: "candidates:index",
6780
7504
  kind: "index",
@@ -6891,7 +7615,7 @@ async function compileVault(rootDir, options = {}) {
6891
7615
  ),
6892
7616
  Promise.all(
6893
7617
  clean.map(async (manifest) => {
6894
- const cached = await readJsonFile(path13.join(paths.analysesDir, `${manifest.sourceId}.json`));
7618
+ const cached = await readJsonFile(path14.join(paths.analysesDir, `${manifest.sourceId}.json`));
6895
7619
  if (cached) {
6896
7620
  return cached;
6897
7621
  }
@@ -6905,23 +7629,38 @@ async function compileVault(rootDir, options = {}) {
6905
7629
  })
6906
7630
  )
6907
7631
  ]);
6908
- 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
+ );
6909
7647
  await Promise.all([
6910
- ensureDir(path13.join(paths.wikiDir, "sources")),
6911
- ensureDir(path13.join(paths.wikiDir, "code")),
6912
- ensureDir(path13.join(paths.wikiDir, "concepts")),
6913
- ensureDir(path13.join(paths.wikiDir, "entities")),
6914
- ensureDir(path13.join(paths.wikiDir, "outputs")),
6915
- ensureDir(path13.join(paths.wikiDir, "projects")),
6916
- ensureDir(path13.join(paths.wikiDir, "insights")),
6917
- ensureDir(path13.join(paths.wikiDir, "candidates")),
6918
- ensureDir(path13.join(paths.wikiDir, "candidates", "concepts")),
6919
- 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"))
6920
7658
  ]);
6921
7659
  const sync = await syncVaultArtifacts(rootDir, {
6922
7660
  schemas,
6923
7661
  manifests,
6924
7662
  analyses,
7663
+ codeIndex,
6925
7664
  sourceProjects,
6926
7665
  outputPages,
6927
7666
  insightPages,
@@ -7057,7 +7796,7 @@ async function queryVault(rootDir, options) {
7057
7796
  assetFiles: staged.assetFiles
7058
7797
  }
7059
7798
  ]);
7060
- stagedPath = path13.join(approval.approvalDir, "wiki", staged.page.path);
7799
+ stagedPath = path14.join(approval.approvalDir, "wiki", staged.page.path);
7061
7800
  savedPageId = staged.page.id;
7062
7801
  approvalId = approval.approvalId;
7063
7802
  approvalDir = approval.approvalDir;
@@ -7313,9 +8052,9 @@ ${orchestrationNotes.join("\n")}
7313
8052
  approvalId = approval.approvalId;
7314
8053
  approvalDir = approval.approvalDir;
7315
8054
  stepResults.forEach((result, index) => {
7316
- result.stagedPath = path13.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
8055
+ result.stagedPath = path14.join(approval.approvalDir, "wiki", stagedStepPages[index]?.page.path ?? "");
7317
8056
  });
7318
- stagedHubPath = path13.join(approval.approvalDir, "wiki", hubPage.path);
8057
+ stagedHubPath = path14.join(approval.approvalDir, "wiki", hubPage.path);
7319
8058
  } else {
7320
8059
  await refreshVaultAfterOutputSave(rootDir);
7321
8060
  }
@@ -7372,15 +8111,15 @@ async function listPages(rootDir) {
7372
8111
  }
7373
8112
  async function readPage(rootDir, relativePath) {
7374
8113
  const { paths } = await loadVaultConfig(rootDir);
7375
- const absolutePath = path13.resolve(paths.wikiDir, relativePath);
8114
+ const absolutePath = path14.resolve(paths.wikiDir, relativePath);
7376
8115
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
7377
8116
  return null;
7378
8117
  }
7379
- const raw = await fs9.readFile(absolutePath, "utf8");
8118
+ const raw = await fs11.readFile(absolutePath, "utf8");
7380
8119
  const parsed = matter7(raw);
7381
8120
  return {
7382
8121
  path: relativePath,
7383
- 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)),
7384
8123
  frontmatter: parsed.data,
7385
8124
  content: parsed.content
7386
8125
  };
@@ -7416,7 +8155,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
7416
8155
  severity: "warning",
7417
8156
  code: "stale_page",
7418
8157
  message: `Page ${page.title} is stale because the vault schema changed.`,
7419
- pagePath: path13.join(paths.wikiDir, page.path),
8158
+ pagePath: path14.join(paths.wikiDir, page.path),
7420
8159
  relatedPageIds: [page.id]
7421
8160
  });
7422
8161
  }
@@ -7427,7 +8166,7 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
7427
8166
  severity: "warning",
7428
8167
  code: "stale_page",
7429
8168
  message: `Page ${page.title} is stale because source ${sourceId} changed.`,
7430
- pagePath: path13.join(paths.wikiDir, page.path),
8169
+ pagePath: path14.join(paths.wikiDir, page.path),
7431
8170
  relatedSourceIds: [sourceId],
7432
8171
  relatedPageIds: [page.id]
7433
8172
  });
@@ -7438,13 +8177,13 @@ function structuralLintFindings(_rootDir, paths, graph, schemas, manifests, sour
7438
8177
  severity: "info",
7439
8178
  code: "orphan_page",
7440
8179
  message: `Page ${page.title} has no backlinks.`,
7441
- pagePath: path13.join(paths.wikiDir, page.path),
8180
+ pagePath: path14.join(paths.wikiDir, page.path),
7442
8181
  relatedPageIds: [page.id]
7443
8182
  });
7444
8183
  }
7445
- const absolutePath = path13.join(paths.wikiDir, page.path);
8184
+ const absolutePath = path14.join(paths.wikiDir, page.path);
7446
8185
  if (await fileExists(absolutePath)) {
7447
- const content = await fs9.readFile(absolutePath, "utf8");
8186
+ const content = await fs11.readFile(absolutePath, "utf8");
7448
8187
  if (content.includes("## Claims")) {
7449
8188
  const uncited = content.split("\n").filter((line) => line.startsWith("- ") && !line.includes("[source:"));
7450
8189
  if (uncited.length) {
@@ -7524,7 +8263,7 @@ async function bootstrapDemo(rootDir, input) {
7524
8263
  }
7525
8264
 
7526
8265
  // src/mcp.ts
7527
- var SERVER_VERSION = "0.1.16";
8266
+ var SERVER_VERSION = "0.1.18";
7528
8267
  async function createMcpServer(rootDir) {
7529
8268
  const server = new McpServer({
7530
8269
  name: "swarmvault",
@@ -7705,7 +8444,7 @@ async function createMcpServer(rootDir) {
7705
8444
  },
7706
8445
  async () => {
7707
8446
  const { paths } = await loadVaultConfig(rootDir);
7708
- 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();
7709
8448
  return asTextResource("swarmvault://sessions", JSON.stringify(files, null, 2));
7710
8449
  }
7711
8450
  );
@@ -7738,8 +8477,8 @@ async function createMcpServer(rootDir) {
7738
8477
  return asTextResource(`swarmvault://pages/${encodedPath}`, `Page not found: ${relativePath}`);
7739
8478
  }
7740
8479
  const { paths } = await loadVaultConfig(rootDir);
7741
- const absolutePath = path14.resolve(paths.wikiDir, relativePath);
7742
- 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"));
7743
8482
  }
7744
8483
  );
7745
8484
  server.registerResource(
@@ -7747,11 +8486,11 @@ async function createMcpServer(rootDir) {
7747
8486
  new ResourceTemplate("swarmvault://sessions/{path}", {
7748
8487
  list: async () => {
7749
8488
  const { paths } = await loadVaultConfig(rootDir);
7750
- 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();
7751
8490
  return {
7752
8491
  resources: files.map((relativePath) => ({
7753
8492
  uri: `swarmvault://sessions/${encodeURIComponent(relativePath)}`,
7754
- name: path14.basename(relativePath, ".md"),
8493
+ name: path15.basename(relativePath, ".md"),
7755
8494
  title: relativePath,
7756
8495
  description: "SwarmVault session artifact",
7757
8496
  mimeType: "text/markdown"
@@ -7768,11 +8507,11 @@ async function createMcpServer(rootDir) {
7768
8507
  const { paths } = await loadVaultConfig(rootDir);
7769
8508
  const encodedPath = typeof variables.path === "string" ? variables.path : "";
7770
8509
  const relativePath = decodeURIComponent(encodedPath);
7771
- const absolutePath = path14.resolve(paths.sessionsDir, relativePath);
8510
+ const absolutePath = path15.resolve(paths.sessionsDir, relativePath);
7772
8511
  if (!absolutePath.startsWith(paths.sessionsDir) || !await fileExists(absolutePath)) {
7773
8512
  return asTextResource(`swarmvault://sessions/${encodedPath}`, `Session not found: ${relativePath}`);
7774
8513
  }
7775
- return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs10.readFile(absolutePath, "utf8"));
8514
+ return asTextResource(`swarmvault://sessions/${encodedPath}`, await fs12.readFile(absolutePath, "utf8"));
7776
8515
  }
7777
8516
  );
7778
8517
  return server;
@@ -7820,13 +8559,13 @@ function asTextResource(uri, text) {
7820
8559
  }
7821
8560
 
7822
8561
  // src/schedule.ts
7823
- import fs11 from "fs/promises";
7824
- import path15 from "path";
8562
+ import fs13 from "fs/promises";
8563
+ import path16 from "path";
7825
8564
  function scheduleStatePath(schedulesDir, jobId) {
7826
- return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
8565
+ return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.json`);
7827
8566
  }
7828
8567
  function scheduleLockPath(schedulesDir, jobId) {
7829
- return path15.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
8568
+ return path16.join(schedulesDir, `${encodeURIComponent(jobId)}.lock`);
7830
8569
  }
7831
8570
  function parseEveryDuration(value) {
7832
8571
  const match = value.trim().match(/^(\d+)(m|h|d)$/i);
@@ -7929,13 +8668,13 @@ async function acquireJobLease(rootDir, jobId) {
7929
8668
  const { paths } = await loadVaultConfig(rootDir);
7930
8669
  const leasePath = scheduleLockPath(paths.schedulesDir, jobId);
7931
8670
  await ensureDir(paths.schedulesDir);
7932
- const handle = await fs11.open(leasePath, "wx");
8671
+ const handle = await fs13.open(leasePath, "wx");
7933
8672
  await handle.writeFile(`${process.pid}
7934
8673
  ${(/* @__PURE__ */ new Date()).toISOString()}
7935
8674
  `);
7936
8675
  await handle.close();
7937
8676
  return async () => {
7938
- await fs11.rm(leasePath, { force: true });
8677
+ await fs13.rm(leasePath, { force: true });
7939
8678
  };
7940
8679
  }
7941
8680
  async function listSchedules(rootDir) {
@@ -8083,24 +8822,24 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
8083
8822
 
8084
8823
  // src/viewer.ts
8085
8824
  import { execFile } from "child_process";
8086
- import fs12 from "fs/promises";
8825
+ import fs14 from "fs/promises";
8087
8826
  import http from "http";
8088
- import path16 from "path";
8827
+ import path17 from "path";
8089
8828
  import { promisify } from "util";
8090
8829
  import matter8 from "gray-matter";
8091
8830
  import mime2 from "mime-types";
8092
8831
  var execFileAsync = promisify(execFile);
8093
8832
  async function readViewerPage(rootDir, relativePath) {
8094
8833
  const { paths } = await loadVaultConfig(rootDir);
8095
- const absolutePath = path16.resolve(paths.wikiDir, relativePath);
8834
+ const absolutePath = path17.resolve(paths.wikiDir, relativePath);
8096
8835
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
8097
8836
  return null;
8098
8837
  }
8099
- const raw = await fs12.readFile(absolutePath, "utf8");
8838
+ const raw = await fs14.readFile(absolutePath, "utf8");
8100
8839
  const parsed = matter8(raw);
8101
8840
  return {
8102
8841
  path: relativePath,
8103
- 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)),
8104
8843
  frontmatter: parsed.data,
8105
8844
  content: parsed.content,
8106
8845
  assets: normalizeOutputAssets(parsed.data.output_assets)
@@ -8108,12 +8847,12 @@ async function readViewerPage(rootDir, relativePath) {
8108
8847
  }
8109
8848
  async function readViewerAsset(rootDir, relativePath) {
8110
8849
  const { paths } = await loadVaultConfig(rootDir);
8111
- const absolutePath = path16.resolve(paths.wikiDir, relativePath);
8850
+ const absolutePath = path17.resolve(paths.wikiDir, relativePath);
8112
8851
  if (!absolutePath.startsWith(paths.wikiDir) || !await fileExists(absolutePath)) {
8113
8852
  return null;
8114
8853
  }
8115
8854
  return {
8116
- buffer: await fs12.readFile(absolutePath),
8855
+ buffer: await fs14.readFile(absolutePath),
8117
8856
  mimeType: mime2.lookup(absolutePath) || "application/octet-stream"
8118
8857
  };
8119
8858
  }
@@ -8136,12 +8875,12 @@ async function readJsonBody(request) {
8136
8875
  return JSON.parse(raw);
8137
8876
  }
8138
8877
  async function ensureViewerDist(viewerDistDir) {
8139
- const indexPath = path16.join(viewerDistDir, "index.html");
8878
+ const indexPath = path17.join(viewerDistDir, "index.html");
8140
8879
  if (await fileExists(indexPath)) {
8141
8880
  return;
8142
8881
  }
8143
- const viewerProjectDir = path16.dirname(viewerDistDir);
8144
- if (await fileExists(path16.join(viewerProjectDir, "package.json"))) {
8882
+ const viewerProjectDir = path17.dirname(viewerDistDir);
8883
+ if (await fileExists(path17.join(viewerProjectDir, "package.json"))) {
8145
8884
  await execFileAsync("pnpm", ["build"], { cwd: viewerProjectDir });
8146
8885
  }
8147
8886
  }
@@ -8158,7 +8897,7 @@ async function startGraphServer(rootDir, port) {
8158
8897
  return;
8159
8898
  }
8160
8899
  response.writeHead(200, { "content-type": "application/json" });
8161
- response.end(await fs12.readFile(paths.graphPath, "utf8"));
8900
+ response.end(await fs14.readFile(paths.graphPath, "utf8"));
8162
8901
  return;
8163
8902
  }
8164
8903
  if (url.pathname === "/api/search") {
@@ -8257,8 +8996,8 @@ async function startGraphServer(rootDir, port) {
8257
8996
  return;
8258
8997
  }
8259
8998
  const relativePath = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
8260
- const target = path16.join(paths.viewerDistDir, relativePath);
8261
- 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");
8262
9001
  const filePath = await fileExists(target) ? target : fallback;
8263
9002
  if (!await fileExists(filePath)) {
8264
9003
  response.writeHead(503, { "content-type": "text/plain" });
@@ -8266,7 +9005,7 @@ async function startGraphServer(rootDir, port) {
8266
9005
  return;
8267
9006
  }
8268
9007
  response.writeHead(200, { "content-type": mime2.lookup(filePath) || "text/plain" });
8269
- response.end(await fs12.readFile(filePath));
9008
+ response.end(await fs14.readFile(filePath));
8270
9009
  });
8271
9010
  await new Promise((resolve) => {
8272
9011
  server.listen(effectivePort, resolve);
@@ -8293,7 +9032,7 @@ async function exportGraphHtml(rootDir, outputPath) {
8293
9032
  throw new Error("Graph artifact not found. Run `swarmvault compile` first.");
8294
9033
  }
8295
9034
  await ensureViewerDist(paths.viewerDistDir);
8296
- const indexPath = path16.join(paths.viewerDistDir, "index.html");
9035
+ const indexPath = path17.join(paths.viewerDistDir, "index.html");
8297
9036
  if (!await fileExists(indexPath)) {
8298
9037
  throw new Error("Viewer build not found. Run `pnpm build` first.");
8299
9038
  }
@@ -8317,16 +9056,16 @@ async function exportGraphHtml(rootDir, outputPath) {
8317
9056
  } : null;
8318
9057
  })
8319
9058
  );
8320
- const rawHtml = await fs12.readFile(indexPath, "utf8");
9059
+ const rawHtml = await fs14.readFile(indexPath, "utf8");
8321
9060
  const scriptMatch = rawHtml.match(/<script type="module" crossorigin src="([^"]+)"><\/script>/);
8322
9061
  const styleMatch = rawHtml.match(/<link rel="stylesheet" crossorigin href="([^"]+)">/);
8323
- const scriptPath = scriptMatch?.[1] ? path16.join(paths.viewerDistDir, scriptMatch[1].replace(/^\//, "")) : null;
8324
- 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;
8325
9064
  if (!scriptPath || !await fileExists(scriptPath)) {
8326
9065
  throw new Error("Viewer script bundle not found. Run `pnpm build` first.");
8327
9066
  }
8328
- const script = await fs12.readFile(scriptPath, "utf8");
8329
- 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") : "";
8330
9069
  const embeddedData = JSON.stringify({ graph, pages: pages.filter(Boolean) }, null, 2).replace(/</g, "\\u003c");
8331
9070
  const html = [
8332
9071
  "<!doctype html>",
@@ -8345,13 +9084,13 @@ async function exportGraphHtml(rootDir, outputPath) {
8345
9084
  "</html>",
8346
9085
  ""
8347
9086
  ].filter(Boolean).join("\n");
8348
- await fs12.mkdir(path16.dirname(outputPath), { recursive: true });
8349
- await fs12.writeFile(outputPath, html, "utf8");
8350
- 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);
8351
9090
  }
8352
9091
 
8353
9092
  // src/watch.ts
8354
- import path17 from "path";
9093
+ import path18 from "path";
8355
9094
  import process2 from "process";
8356
9095
  import chokidar from "chokidar";
8357
9096
  var MAX_BACKOFF_MS = 3e4;
@@ -8496,7 +9235,7 @@ async function watchVault(rootDir, options = {}) {
8496
9235
  };
8497
9236
  }
8498
9237
  function toWatchReason(baseDir, targetPath) {
8499
- return path17.relative(baseDir, targetPath) || ".";
9238
+ return path18.relative(baseDir, targetPath) || ".";
8500
9239
  }
8501
9240
  export {
8502
9241
  acceptApproval,
@@ -8515,6 +9254,7 @@ export {
8515
9254
  getWebSearchAdapterForTask,
8516
9255
  getWorkspaceInfo,
8517
9256
  importInbox,
9257
+ ingestDirectory,
8518
9258
  ingestInput,
8519
9259
  initVault,
8520
9260
  initWorkspace,