@swarmvaultai/engine 0.6.7 → 0.6.8

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
@@ -22,13 +22,44 @@ import {
22
22
  uniqueBy,
23
23
  writeFileIfChanged,
24
24
  writeJsonFile
25
- } from "./chunk-HRRPWXRZ.js";
25
+ } from "./chunk-5Q4IV4O3.js";
26
26
 
27
27
  // src/agents.ts
28
28
  import crypto from "crypto";
29
29
  import fs from "fs/promises";
30
30
  import path from "path";
31
+ import { fileURLToPath } from "url";
31
32
  import YAML from "yaml";
33
+ function resolveHooksDir() {
34
+ const moduleUrl = import.meta.url;
35
+ const modulePath = fileURLToPath(moduleUrl);
36
+ const moduleDir = path.dirname(modulePath);
37
+ if (moduleDir.endsWith(`${path.sep}dist`)) {
38
+ return path.join(moduleDir, "hooks");
39
+ }
40
+ if (moduleDir.endsWith(`${path.sep}src`)) {
41
+ return path.resolve(moduleDir, "..", "dist", "hooks");
42
+ }
43
+ return path.resolve(moduleDir, "hooks");
44
+ }
45
+ var BUILT_HOOKS_DIR = resolveHooksDir();
46
+ var hookContentCache = /* @__PURE__ */ new Map();
47
+ async function readBuiltHook(hookFile) {
48
+ const cached = hookContentCache.get(hookFile);
49
+ if (cached !== void 0) {
50
+ return cached;
51
+ }
52
+ const hookPath2 = path.join(BUILT_HOOKS_DIR, hookFile);
53
+ try {
54
+ const content = await fs.readFile(hookPath2, "utf8");
55
+ hookContentCache.set(hookFile, content);
56
+ return content;
57
+ } catch (error) {
58
+ throw new Error(
59
+ `SwarmVault hook bundle not found at ${hookPath2}. Run 'pnpm --filter @swarmvaultai/engine build' so the hook scripts are emitted to dist/hooks/. Underlying error: ${error instanceof Error ? error.message : String(error)}`
60
+ );
61
+ }
62
+ }
32
63
  var managedStart = "<!-- swarmvault:managed:start -->";
33
64
  var managedEnd = "<!-- swarmvault:managed:end -->";
34
65
  var legacyManagedStart = "<!-- vault:managed:start -->";
@@ -188,7 +219,7 @@ async function readJsonWithWarnings(filePath, fallback, label) {
188
219
  async function installClaudeHook(rootDir) {
189
220
  const settingsPath = path.join(rootDir, ".claude", "settings.json");
190
221
  const scriptPath = path.join(rootDir, ".claude", "hooks", "swarmvault-graph-first.js");
191
- await writeOwnedFile(scriptPath, buildClaudeHookScript(), true);
222
+ await writeOwnedFile(scriptPath, await readBuiltHook("claude.js"), true);
192
223
  await ensureDir(path.dirname(settingsPath));
193
224
  const { data: settings, warnings } = await readJsonWithWarnings(settingsPath, {}, ".claude/settings.json");
194
225
  if (warnings.length > 0 && await fileExists(settingsPath)) {
@@ -218,361 +249,10 @@ async function installClaudeHook(rootDir) {
218
249
  `, "utf8");
219
250
  return { path: settingsPath, warnings: [] };
220
251
  }
221
- function buildClaudeHookScript() {
222
- return `#!/usr/bin/env node
223
- import crypto from "node:crypto";
224
- import fs from "node:fs/promises";
225
- import os from "node:os";
226
- import path from "node:path";
227
-
228
- ${markerStateSnippet("claude").trim()}
229
-
230
- async function readInput() {
231
- let body = "";
232
- for await (const chunk of process.stdin) {
233
- body += chunk;
234
- }
235
- if (!body.trim()) {
236
- return {};
237
- }
238
- try {
239
- return JSON.parse(body);
240
- } catch {
241
- return {};
242
- }
243
- }
244
-
245
- function emit(value) {
246
- process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
247
- }
248
-
249
- const mode = process.argv[2] ?? "";
250
- const input = await readInput();
251
- const cwd = resolveInputCwd(input);
252
- const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
253
-
254
- if (!(await hasReport(cwd))) {
255
- emit({});
256
- process.exit(0);
257
- }
258
-
259
- if (mode === "session-start") {
260
- await resetSession(cwd);
261
- emit({
262
- hookSpecificOutput: {
263
- hookEventName: "SessionStart",
264
- additionalContext: reportNote
265
- }
266
- });
267
- process.exit(0);
268
- }
269
-
270
- const toolName = resolveToolName(input);
271
- if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
272
- await markReportRead(cwd);
273
- emit({});
274
- process.exit(0);
275
- }
276
-
277
- if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
278
- emit({
279
- hookSpecificOutput: {
280
- hookEventName: "PreToolUse",
281
- additionalContext: reportNote
282
- }
283
- });
284
- process.exit(0);
285
- }
286
-
287
- emit({});
288
- `;
289
- }
290
- function markerStateSnippet(agentKey) {
291
- return `
292
- function markerState(cwd) {
293
- const hash = crypto.createHash("sha256").update(cwd).digest("hex");
294
- const dir = path.join(os.tmpdir(), "swarmvault-agent-hooks", "${agentKey}", hash);
295
- return {
296
- dir,
297
- markerPath: path.join(dir, "report-read")
298
- };
299
- }
300
-
301
- function isReportPath(value, cwd) {
302
- if (typeof value !== "string" || value.length === 0) {
303
- return false;
304
- }
305
- const reportSuffix = path.join("wiki", "graph", "report.md");
306
- const normalized = value.replaceAll("\\\\", "/");
307
- const reportNormalized = reportSuffix.replaceAll("\\\\", "/");
308
- if (normalized.endsWith(reportNormalized)) {
309
- return true;
310
- }
311
- return path.resolve(cwd, value) === path.resolve(cwd, reportSuffix);
312
- }
313
-
314
- function collectCandidatePaths(node, acc = []) {
315
- if (typeof node === "string") {
316
- acc.push(node);
317
- return acc;
318
- }
319
- if (!node || typeof node !== "object") {
320
- return acc;
321
- }
322
- if (Array.isArray(node)) {
323
- for (const item of node) {
324
- collectCandidatePaths(item, acc);
325
- }
326
- return acc;
327
- }
328
- for (const [key, value] of Object.entries(node)) {
329
- if (["path", "filePath", "file_path", "paths", "target", "targets"].includes(key)) {
330
- collectCandidatePaths(value, acc);
331
- continue;
332
- }
333
- collectCandidatePaths(value, acc);
334
- }
335
- return acc;
336
- }
337
-
338
- function resolveInputCwd(input) {
339
- return path.resolve(
340
- input?.cwd ??
341
- input?.directory ??
342
- input?.workspace?.cwd ??
343
- input?.toolInput?.cwd ??
344
- process.cwd()
345
- );
346
- }
347
-
348
- function resolveToolName(input) {
349
- return String(input?.toolName ?? input?.tool_name ?? input?.tool?.name ?? input?.name ?? "");
350
- }
351
-
352
- async function hasReport(cwd) {
353
- try {
354
- await fs.access(path.join(cwd, "wiki", "graph", "report.md"));
355
- return true;
356
- } catch {
357
- return false;
358
- }
359
- }
360
-
361
- async function markReportRead(cwd) {
362
- const state = markerState(cwd);
363
- await fs.mkdir(state.dir, { recursive: true });
364
- await fs.writeFile(state.markerPath, "seen\\n", "utf8");
365
- }
366
-
367
- async function hasSeenReport(cwd) {
368
- const state = markerState(cwd);
369
- try {
370
- await fs.access(state.markerPath);
371
- return true;
372
- } catch {
373
- return false;
374
- }
375
- }
376
-
377
- async function resetSession(cwd) {
378
- const state = markerState(cwd);
379
- await fs.rm(state.dir, { recursive: true, force: true });
380
- }
381
-
382
- function isBroadSearchTool(toolName) {
383
- return /grep|glob|search|find/i.test(toolName);
384
- }
385
- `;
386
- }
387
- function buildGeminiHookScript() {
388
- return `#!/usr/bin/env node
389
- import crypto from "node:crypto";
390
- import fs from "node:fs/promises";
391
- import os from "node:os";
392
- import path from "node:path";
393
-
394
- ${markerStateSnippet("gemini").trim()}
395
-
396
- async function readInput() {
397
- let body = "";
398
- for await (const chunk of process.stdin) {
399
- body += chunk;
400
- }
401
- if (!body.trim()) {
402
- return {};
403
- }
404
- try {
405
- return JSON.parse(body);
406
- } catch {
407
- return {};
408
- }
409
- }
410
-
411
- function emit(value) {
412
- process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
413
- }
414
-
415
- const mode = process.argv[2] ?? "";
416
- const input = await readInput();
417
- const cwd = resolveInputCwd(input);
418
- const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
419
-
420
- if (!(await hasReport(cwd))) {
421
- emit({});
422
- process.exit(0);
423
- }
424
-
425
- if (mode === "session-start") {
426
- await resetSession(cwd);
427
- emit({
428
- systemMessage: reportNote,
429
- hookSpecificOutput: {
430
- hookEventName: "SessionStart",
431
- additionalContext: "SwarmVault graph report: wiki/graph/report.md"
432
- }
433
- });
434
- process.exit(0);
435
- }
436
-
437
- const toolName = resolveToolName(input);
438
- if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
439
- await markReportRead(cwd);
440
- emit({});
441
- process.exit(0);
442
- }
443
-
444
- if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
445
- emit({ systemMessage: reportNote });
446
- process.exit(0);
447
- }
448
-
449
- emit({});
450
- `;
451
- }
452
- function buildCopilotHookScript() {
453
- return `#!/usr/bin/env node
454
- import crypto from "node:crypto";
455
- import fs from "node:fs/promises";
456
- import os from "node:os";
457
- import path from "node:path";
458
-
459
- ${markerStateSnippet("copilot").trim()}
460
-
461
- async function readInput() {
462
- let body = "";
463
- for await (const chunk of process.stdin) {
464
- body += chunk;
465
- }
466
- if (!body.trim()) {
467
- return {};
468
- }
469
- try {
470
- return JSON.parse(body);
471
- } catch {
472
- return {};
473
- }
474
- }
475
-
476
- function emit(value) {
477
- if (value !== undefined) {
478
- process.stdout.write(\`\${JSON.stringify(value)}\\n\`);
479
- }
480
- }
481
-
482
- const mode = process.argv[2] ?? "";
483
- const input = await readInput();
484
- const cwd = resolveInputCwd(input);
485
- const reportNote = "SwarmVault graph report exists at wiki/graph/report.md. Read it before broad grep/glob searching.";
486
-
487
- if (!(await hasReport(cwd))) {
488
- emit({});
489
- process.exit(0);
490
- }
491
-
492
- if (mode === "session-start") {
493
- await resetSession(cwd);
494
- emit({});
495
- process.exit(0);
496
- }
497
-
498
- const toolName = resolveToolName(input);
499
- if (collectCandidatePaths(input).some((value) => isReportPath(value, cwd))) {
500
- await markReportRead(cwd);
501
- emit({});
502
- process.exit(0);
503
- }
504
-
505
- if (isBroadSearchTool(toolName) && !(await hasSeenReport(cwd))) {
506
- emit({
507
- permissionDecision: "deny",
508
- permissionDecisionReason: reportNote
509
- });
510
- process.exit(0);
511
- }
512
-
513
- emit({});
514
- `;
515
- }
516
- function buildOpenCodePlugin() {
517
- return `import path from "node:path";
518
-
519
- const reportRelativePath = path.join("wiki", "graph", "report.md");
520
-
521
- export const name = "swarmvault-graph-first";
522
-
523
- export default async function swarmvaultGraphFirst({ client }) {
524
- let reportSeen = false;
525
-
526
- async function hasReport(cwd) {
527
- try {
528
- await Bun.file(path.join(cwd, reportRelativePath)).arrayBuffer();
529
- return true;
530
- } catch {
531
- return false;
532
- }
533
- }
534
-
535
- async function note(message) {
536
- if (client?.app?.log) {
537
- await client.app.log({
538
- level: "info",
539
- message
540
- });
541
- }
542
- }
543
-
544
- return {
545
- async "session.created"(input) {
546
- reportSeen = false;
547
- const cwd = input?.session?.cwd ?? process.cwd();
548
- if (await hasReport(cwd)) {
549
- await note("SwarmVault graph report exists. Read wiki/graph/report.md before broad workspace searching.");
550
- }
551
- },
552
- async "tool.execute.before"(input) {
553
- const cwd = input?.session?.cwd ?? process.cwd();
554
- if (!(await hasReport(cwd))) {
555
- return;
556
- }
557
-
558
- const argsText = JSON.stringify(input?.args ?? {});
559
- if (argsText.includes("wiki/graph/report.md")) {
560
- reportSeen = true;
561
- return;
562
- }
563
-
564
- if (!reportSeen && ["glob", "grep"].includes(String(input?.tool ?? ""))) {
565
- await note("SwarmVault graph report exists. Read wiki/graph/report.md before broad workspace searching.");
566
- }
567
- }
568
- };
569
- }
570
- `;
571
- }
572
252
  async function installGeminiHook(rootDir) {
573
253
  const settingsPath = path.join(rootDir, ".gemini", "settings.json");
574
254
  const scriptPath = path.join(rootDir, ".gemini", "hooks", "swarmvault-graph-first.js");
575
- await writeOwnedFile(scriptPath, buildGeminiHookScript(), true);
255
+ await writeOwnedFile(scriptPath, await readBuiltHook("gemini.js"), true);
576
256
  const { data: settings, warnings } = await readJsonWithWarnings(settingsPath, {}, ".gemini/settings.json");
577
257
  if (warnings.length > 0 && await fileExists(settingsPath)) {
578
258
  return { paths: [settingsPath, scriptPath], warnings };
@@ -639,7 +319,7 @@ async function installCopilotHook(rootDir) {
639
319
  const hooksDir = path.join(rootDir, ".github", "hooks");
640
320
  const scriptPath = path.join(hooksDir, "swarmvault-graph-first.js");
641
321
  const configPath = path.join(hooksDir, "swarmvault-graph-first.json");
642
- await writeOwnedFile(scriptPath, buildCopilotHookScript(), true);
322
+ await writeOwnedFile(scriptPath, await readBuiltHook("copilot.js"), true);
643
323
  const config = {
644
324
  version: copilotHookVersion,
645
325
  hooks: {
@@ -670,7 +350,7 @@ async function installCopilotHook(rootDir) {
670
350
  }
671
351
  async function installOpenCodeHook(rootDir) {
672
352
  const pluginPath = path.join(rootDir, ".opencode", "plugins", "swarmvault-graph-first.js");
673
- await writeOwnedFile(pluginPath, buildOpenCodePlugin());
353
+ await writeOwnedFile(pluginPath, await readBuiltHook("opencode.js"));
674
354
  return { paths: [pluginPath], warnings: [] };
675
355
  }
676
356
  function stableKeyForAgent(rootDir, agent) {
@@ -1376,7 +1056,6 @@ function buildBenchmarkArtifact(input) {
1376
1056
  reductionRatio,
1377
1057
  sampleQuestions: input.questions,
1378
1058
  perQuestion,
1379
- questionResults: perQuestion,
1380
1059
  summary
1381
1060
  };
1382
1061
  }
@@ -2182,104 +1861,431 @@ function treeSitterCompatibilityDiagnostic(language, error) {
2182
1861
  column: 1
2183
1862
  };
2184
1863
  }
2185
- function parsePythonImportStatement(text) {
2186
- const match = text.trim().match(/^import\s+(.+)$/);
2187
- if (!match) {
2188
- return [];
1864
+ function flattenPythonDottedName(node) {
1865
+ if (!node) {
1866
+ return "";
2189
1867
  }
2190
- return match[1].split(",").map((item) => item.trim()).filter(Boolean).map((item) => {
2191
- const [specifier, alias] = item.split(/\s+as\s+/i);
2192
- return {
2193
- specifier: specifier.trim(),
2194
- importedSymbols: [],
2195
- namespaceImport: alias?.trim(),
2196
- isExternal: !specifier.trim().startsWith("."),
2197
- reExport: false
2198
- };
2199
- });
1868
+ return node.namedChildren.filter((child) => child?.type === "identifier").map((child) => child.text.trim()).filter(Boolean).join(".");
2200
1869
  }
2201
- function parsePythonFromImportStatement(text) {
2202
- const match = text.trim().match(/^from\s+([.\w]+)\s+import\s+(.+)$/);
2203
- if (!match) {
1870
+ function flattenPythonRelativeImport(node) {
1871
+ if (!node) {
1872
+ return "";
1873
+ }
1874
+ const prefixNode = node.namedChildren.find((child) => child?.type === "import_prefix") ?? null;
1875
+ const prefix = prefixNode ? prefixNode.text.trim() : "";
1876
+ const moduleNode = node.namedChildren.find((child) => child?.type === "dotted_name") ?? null;
1877
+ const module = flattenPythonDottedName(moduleNode);
1878
+ return prefix + module;
1879
+ }
1880
+ function parsePythonImportStatement(node) {
1881
+ const imports = [];
1882
+ for (const child of node.namedChildren) {
1883
+ if (!child) {
1884
+ continue;
1885
+ }
1886
+ if (child.type === "dotted_name") {
1887
+ const specifier = flattenPythonDottedName(child);
1888
+ if (!specifier) {
1889
+ continue;
1890
+ }
1891
+ imports.push({
1892
+ specifier,
1893
+ importedSymbols: [],
1894
+ isExternal: !specifier.startsWith("."),
1895
+ reExport: false
1896
+ });
1897
+ } else if (child.type === "aliased_import") {
1898
+ const moduleNode = child.namedChildren.find((inner) => inner?.type === "dotted_name") ?? null;
1899
+ const aliasNode = child.namedChildren.find((inner) => inner?.type === "identifier") ?? null;
1900
+ const specifier = flattenPythonDottedName(moduleNode);
1901
+ if (!specifier) {
1902
+ continue;
1903
+ }
1904
+ imports.push({
1905
+ specifier,
1906
+ importedSymbols: [],
1907
+ namespaceImport: aliasNode?.text.trim(),
1908
+ isExternal: !specifier.startsWith("."),
1909
+ reExport: false
1910
+ });
1911
+ }
1912
+ }
1913
+ return imports;
1914
+ }
1915
+ function parsePythonFromImportStatement(node) {
1916
+ const children = node.namedChildren.filter((child) => child !== null);
1917
+ if (children.length === 0) {
1918
+ return [];
1919
+ }
1920
+ const [moduleNode, ...rest] = children;
1921
+ if (!moduleNode) {
1922
+ return [];
1923
+ }
1924
+ let specifier;
1925
+ if (moduleNode.type === "relative_import") {
1926
+ specifier = flattenPythonRelativeImport(moduleNode);
1927
+ } else if (moduleNode.type === "dotted_name") {
1928
+ specifier = flattenPythonDottedName(moduleNode);
1929
+ } else {
1930
+ return [];
1931
+ }
1932
+ if (!specifier) {
2204
1933
  return [];
2205
1934
  }
1935
+ const symbols = [];
1936
+ let hasWildcard = false;
1937
+ for (const entry of rest) {
1938
+ if (entry.type === "wildcard_import") {
1939
+ hasWildcard = true;
1940
+ continue;
1941
+ }
1942
+ if (entry.type === "dotted_name") {
1943
+ const name = flattenPythonDottedName(entry);
1944
+ if (name) {
1945
+ symbols.push(name);
1946
+ }
1947
+ continue;
1948
+ }
1949
+ if (entry.type === "aliased_import") {
1950
+ const moduleChild = entry.namedChildren.find((inner) => inner?.type === "dotted_name") ?? null;
1951
+ const aliasChild = entry.namedChildren.find((inner) => inner?.type === "identifier") ?? null;
1952
+ const baseName = flattenPythonDottedName(moduleChild);
1953
+ const aliasName = aliasChild?.text.trim();
1954
+ if (baseName) {
1955
+ symbols.push(aliasName ? `${baseName} as ${aliasName}` : baseName);
1956
+ }
1957
+ }
1958
+ }
1959
+ if (hasWildcard) {
1960
+ symbols.push("*");
1961
+ }
2206
1962
  return [
2207
1963
  {
2208
- specifier: match[1],
2209
- importedSymbols: match[2].split(",").map((item) => item.trim()).filter(Boolean),
2210
- isExternal: !match[1].startsWith("."),
1964
+ specifier,
1965
+ importedSymbols: symbols,
1966
+ isExternal: !specifier.startsWith("."),
2211
1967
  reExport: false
2212
1968
  }
2213
1969
  ];
2214
1970
  }
2215
- function parseGoImport(text) {
2216
- const match = text.trim().match(/^(?:([._A-Za-z]\w*)\s+)?"([^"]+)"$/);
2217
- if (!match) {
1971
+ function parseGoImport(spec) {
1972
+ let alias;
1973
+ let dotImport = false;
1974
+ let blankImport = false;
1975
+ let specifier;
1976
+ for (const child of spec.namedChildren) {
1977
+ if (!child) {
1978
+ continue;
1979
+ }
1980
+ switch (child.type) {
1981
+ case "package_identifier":
1982
+ alias = child.text.trim();
1983
+ break;
1984
+ case "dot":
1985
+ dotImport = true;
1986
+ break;
1987
+ case "blank_identifier":
1988
+ blankImport = true;
1989
+ break;
1990
+ case "interpreted_string_literal":
1991
+ case "raw_string_literal": {
1992
+ const content = child.namedChildren.find(
1993
+ (inner) => inner?.type === "interpreted_string_literal_content" || inner?.type === "raw_string_literal_content"
1994
+ ) ?? null;
1995
+ specifier = content ? content.text : child.text.replace(/^[`"]|[`"]$/g, "");
1996
+ break;
1997
+ }
1998
+ default:
1999
+ break;
2000
+ }
2001
+ }
2002
+ if (!specifier) {
2218
2003
  return void 0;
2219
2004
  }
2220
2005
  return {
2221
- specifier: match[2],
2006
+ specifier,
2222
2007
  importedSymbols: [],
2223
- namespaceImport: match[1] && ![".", "_"].includes(match[1]) ? match[1] : void 0,
2224
- isExternal: !match[2].startsWith("."),
2008
+ namespaceImport: !dotImport && !blankImport ? alias : void 0,
2009
+ isExternal: !specifier.startsWith("."),
2225
2010
  reExport: false
2226
2011
  };
2227
2012
  }
2228
- function parseRustUse(text) {
2229
- const cleaned = text.replace(/^pub\s+/, "").replace(/^use\s+/, "").replace(/;$/, "").trim();
2230
- const aliasMatch = cleaned.match(/\s+as\s+([A-Za-z_]\w*)$/);
2231
- const withoutAlias = aliasMatch ? cleaned.slice(0, aliasMatch.index).trim() : cleaned;
2232
- const braceMatch = withoutAlias.match(/^(.*)::\{(.+)\}$/);
2233
- const importedSymbols = braceMatch ? braceMatch[2].split(",").map((item) => item.trim()).filter(Boolean) : [aliasMatch ? `${normalizeSymbolReference(withoutAlias)} as ${aliasMatch[1]}` : normalizeSymbolReference(withoutAlias)].filter(
2234
- Boolean
2235
- );
2236
- const specifier = braceMatch ? braceMatch[1].trim() : withoutAlias;
2237
- return {
2238
- specifier,
2239
- importedSymbols,
2240
- isExternal: !/^(crate|self|super)::/.test(specifier),
2241
- reExport: text.trim().startsWith("pub use ")
2242
- };
2013
+ function flattenRustPath(node) {
2014
+ if (!node) {
2015
+ return [];
2016
+ }
2017
+ if (node.type === "crate" || node.type === "self" || node.type === "super") {
2018
+ return [node.type];
2019
+ }
2020
+ if (node.type === "identifier") {
2021
+ return [node.text.trim()].filter(Boolean);
2022
+ }
2023
+ if (node.type === "scoped_identifier") {
2024
+ const segments = [];
2025
+ for (const child of node.namedChildren) {
2026
+ if (!child) {
2027
+ continue;
2028
+ }
2029
+ segments.push(...flattenRustPath(child));
2030
+ }
2031
+ return segments;
2032
+ }
2033
+ return node.namedChildren.filter((child) => child !== null).flatMap((child) => flattenRustPath(child));
2243
2034
  }
2244
- function parseJavaImport(text) {
2245
- const cleaned = text.replace(/^import\s+/, "").replace(/^static\s+/, "").replace(/;$/, "").trim();
2246
- const symbolName = normalizeSymbolReference(cleaned.replace(/\.\*$/, ""));
2035
+ function collectRustUseLeaves(node, prefix, leaves) {
2036
+ if (!node) {
2037
+ return;
2038
+ }
2039
+ switch (node.type) {
2040
+ case "scoped_identifier": {
2041
+ const segments = flattenRustPath(node);
2042
+ if (segments.length === 0) {
2043
+ return;
2044
+ }
2045
+ leaves.push({
2046
+ segments: [...prefix, ...segments],
2047
+ symbol: segments[segments.length - 1] ?? null,
2048
+ wildcard: false
2049
+ });
2050
+ return;
2051
+ }
2052
+ case "identifier":
2053
+ case "crate":
2054
+ case "self":
2055
+ case "super": {
2056
+ const combined = [...prefix];
2057
+ if (node.type === "self" && prefix.length > 0) {
2058
+ leaves.push({ segments: combined, symbol: null, wildcard: false });
2059
+ return;
2060
+ }
2061
+ combined.push(node.type === "identifier" ? node.text.trim() : node.type);
2062
+ leaves.push({
2063
+ segments: combined,
2064
+ symbol: combined[combined.length - 1] ?? null,
2065
+ wildcard: false
2066
+ });
2067
+ return;
2068
+ }
2069
+ case "scoped_use_list": {
2070
+ const pathNode = node.namedChildren[0] ?? null;
2071
+ const listNode = node.namedChildren[1] ?? null;
2072
+ const nextPrefix = [...prefix, ...flattenRustPath(pathNode)];
2073
+ collectRustUseLeaves(listNode, nextPrefix, leaves);
2074
+ return;
2075
+ }
2076
+ case "use_list": {
2077
+ for (const child of node.namedChildren) {
2078
+ collectRustUseLeaves(child, prefix, leaves);
2079
+ }
2080
+ return;
2081
+ }
2082
+ case "use_wildcard": {
2083
+ const pathNode = node.namedChildren[0] ?? null;
2084
+ const pathSegments = pathNode ? flattenRustPath(pathNode) : [];
2085
+ leaves.push({
2086
+ segments: [...prefix, ...pathSegments],
2087
+ symbol: null,
2088
+ wildcard: true
2089
+ });
2090
+ return;
2091
+ }
2092
+ case "use_as_clause": {
2093
+ const pathNode = node.childForFieldName("path") ?? node.namedChildren[0] ?? null;
2094
+ const aliasNode = node.childForFieldName("alias") ?? node.namedChildren[1] ?? null;
2095
+ const before = leaves.length;
2096
+ collectRustUseLeaves(pathNode, prefix, leaves);
2097
+ const alias = aliasNode?.text.trim();
2098
+ if (alias) {
2099
+ for (let index = before; index < leaves.length; index += 1) {
2100
+ const leaf = leaves[index];
2101
+ if (leaf) {
2102
+ leaf.alias = alias;
2103
+ }
2104
+ }
2105
+ }
2106
+ return;
2107
+ }
2108
+ default: {
2109
+ for (const child of node.namedChildren) {
2110
+ collectRustUseLeaves(child, prefix, leaves);
2111
+ }
2112
+ }
2113
+ }
2114
+ }
2115
+ function isRustPubUse(useNode) {
2116
+ for (const child of useNode.children) {
2117
+ if (!child) {
2118
+ continue;
2119
+ }
2120
+ if (child.type === "visibility_modifier") {
2121
+ return true;
2122
+ }
2123
+ if (child.type === "use") {
2124
+ return false;
2125
+ }
2126
+ }
2127
+ return false;
2128
+ }
2129
+ function parseRustUseDeclaration(useNode) {
2130
+ const inner = useNode.namedChildren.find((child) => child !== null) ?? null;
2131
+ if (!inner) {
2132
+ return [];
2133
+ }
2134
+ const leaves = [];
2135
+ collectRustUseLeaves(inner, [], leaves);
2136
+ if (leaves.length === 0) {
2137
+ return [];
2138
+ }
2139
+ const reExport = isRustPubUse(useNode);
2140
+ return leaves.map((leaf) => {
2141
+ const specifier = leaf.segments.join("::");
2142
+ const importedSymbols = leaf.wildcard ? ["*"] : leaf.alias && leaf.symbol ? [`${leaf.symbol} as ${leaf.alias}`] : leaf.symbol ? [leaf.symbol] : [];
2143
+ return {
2144
+ specifier,
2145
+ importedSymbols,
2146
+ isExternal: !/^(?:crate|self|super)(?:$|::)/.test(specifier),
2147
+ reExport
2148
+ };
2149
+ });
2150
+ }
2151
+ function flattenJavaScopedIdentifier(node) {
2152
+ if (!node) {
2153
+ return "";
2154
+ }
2155
+ if (node.type === "identifier") {
2156
+ return node.text.trim();
2157
+ }
2158
+ if (node.type === "scoped_identifier") {
2159
+ const head = node.namedChildren[0] ?? null;
2160
+ const tail = node.namedChildren[node.namedChildren.length - 1] ?? null;
2161
+ const headText = flattenJavaScopedIdentifier(head);
2162
+ const tailText = tail && tail !== head && tail.type === "identifier" ? tail.text.trim() : "";
2163
+ return headText && tailText ? `${headText}.${tailText}` : headText || tailText;
2164
+ }
2165
+ return node.text.trim();
2166
+ }
2167
+ function parseJavaImport(node) {
2168
+ const pathNode = node.namedChildren.find((child) => child?.type === "scoped_identifier") ?? null;
2169
+ const hasAsterisk = node.namedChildren.some((child) => child?.type === "asterisk");
2170
+ const pathText = flattenJavaScopedIdentifier(pathNode);
2171
+ const specifier = hasAsterisk ? `${pathText}.*` : pathText;
2172
+ const symbolName = hasAsterisk ? "" : (pathText.split(".").pop() ?? "").trim();
2247
2173
  return {
2248
- specifier: cleaned.replace(/\.\*$/, ""),
2174
+ specifier,
2249
2175
  importedSymbols: symbolName ? [symbolName] : [],
2250
2176
  isExternal: true,
2251
2177
  reExport: false
2252
2178
  };
2253
2179
  }
2254
- function parseKotlinImport(text) {
2255
- const cleaned = text.trim().replace(/^import\s+/, "");
2256
- if (!cleaned) {
2257
- return void 0;
2180
+ function flattenKotlinIdentifier(node) {
2181
+ if (!node) {
2182
+ return "";
2183
+ }
2184
+ if (node.type === "simple_identifier") {
2185
+ return node.text.trim();
2258
2186
  }
2259
- const aliasMatch = cleaned.match(/^(.+?)\s+as\s+([A-Za-z_]\w*)$/);
2260
- const specifier = (aliasMatch ? aliasMatch[1] : cleaned).trim();
2187
+ return node.namedChildren.filter((child) => child?.type === "simple_identifier").map((child) => child.text.trim()).filter(Boolean).join(".");
2188
+ }
2189
+ function parseKotlinImport(header) {
2190
+ const identifierNode = header.namedChildren.find((child) => child?.type === "identifier") ?? null;
2191
+ const specifier = flattenKotlinIdentifier(identifierNode);
2261
2192
  if (!specifier) {
2262
2193
  return void 0;
2263
2194
  }
2195
+ const hasWildcard = header.descendantsOfType("wildcard_import").some((child) => child !== null);
2196
+ const aliasNode = header.namedChildren.find((child) => child?.type === "import_alias") ?? null;
2197
+ const aliasName = aliasNode ? flattenKotlinIdentifier(aliasNode.namedChildren.find((child) => child?.type === "type_identifier") ?? null) || aliasNode.text.replace(/^as\s+/, "").trim() : void 0;
2264
2198
  return {
2265
- specifier,
2266
- importedSymbols: [],
2267
- namespaceImport: aliasMatch?.[2],
2268
- isExternal: !specifier.startsWith("."),
2199
+ specifier: hasWildcard ? `${specifier}.*` : specifier,
2200
+ importedSymbols: hasWildcard ? ["*"] : [],
2201
+ namespaceImport: aliasName || void 0,
2202
+ isExternal: true,
2269
2203
  reExport: false
2270
2204
  };
2271
2205
  }
2272
- function parseScalaImport(text) {
2273
- const cleaned = text.trim().replace(/^import\s+/, "");
2274
- if (!cleaned) {
2206
+ function flattenScalaStableIdentifier(node) {
2207
+ if (!node) {
2208
+ return "";
2209
+ }
2210
+ if (node.type === "identifier") {
2211
+ return node.text.trim();
2212
+ }
2213
+ if (node.type === "stable_identifier") {
2214
+ return node.namedChildren.filter((child) => child !== null).map((child) => flattenScalaStableIdentifier(child)).filter(Boolean).join(".");
2215
+ }
2216
+ return node.text.trim();
2217
+ }
2218
+ function parseScalaImport(node) {
2219
+ const pathNode = node.namedChildren.find((child) => child?.type === "stable_identifier") ?? node.namedChildren.find((child) => child?.type === "identifier") ?? null;
2220
+ const basePath = flattenScalaStableIdentifier(pathNode);
2221
+ if (!basePath) {
2275
2222
  return [];
2276
2223
  }
2277
- return cleaned.split(",").map((item) => item.trim()).filter(Boolean).map((item) => ({
2278
- specifier: item.replace(/\s*=>\s*/g, " => "),
2279
- importedSymbols: [],
2280
- isExternal: !item.startsWith("."),
2281
- reExport: false
2282
- }));
2224
+ const selectorsNode = node.namedChildren.find((child) => child?.type === "import_selectors") ?? null;
2225
+ const wildcardNode = node.namedChildren.find((child) => child?.type === "wildcard") ?? null;
2226
+ if (selectorsNode) {
2227
+ const results = [];
2228
+ for (const selector of selectorsNode.namedChildren) {
2229
+ if (!selector) {
2230
+ continue;
2231
+ }
2232
+ if (selector.type === "identifier") {
2233
+ const symbol2 = selector.text.trim();
2234
+ if (symbol2) {
2235
+ results.push({
2236
+ specifier: basePath,
2237
+ importedSymbols: [symbol2],
2238
+ isExternal: !basePath.startsWith("."),
2239
+ reExport: false
2240
+ });
2241
+ }
2242
+ continue;
2243
+ }
2244
+ if (selector.type === "renamed_identifier") {
2245
+ const idChildren = selector.namedChildren.filter((child) => child?.type === "identifier");
2246
+ const [original, alias] = [idChildren[0]?.text.trim(), idChildren[1]?.text.trim()];
2247
+ if (original) {
2248
+ results.push({
2249
+ specifier: basePath,
2250
+ importedSymbols: [alias ? `${original} as ${alias}` : original],
2251
+ isExternal: !basePath.startsWith("."),
2252
+ reExport: false
2253
+ });
2254
+ }
2255
+ continue;
2256
+ }
2257
+ if (selector.type === "wildcard") {
2258
+ results.push({
2259
+ specifier: basePath,
2260
+ importedSymbols: ["*"],
2261
+ isExternal: !basePath.startsWith("."),
2262
+ reExport: false
2263
+ });
2264
+ }
2265
+ }
2266
+ return results;
2267
+ }
2268
+ if (wildcardNode) {
2269
+ return [
2270
+ {
2271
+ specifier: basePath,
2272
+ importedSymbols: ["*"],
2273
+ isExternal: !basePath.startsWith("."),
2274
+ reExport: false
2275
+ }
2276
+ ];
2277
+ }
2278
+ const segments = basePath.split(".");
2279
+ const symbol = segments.pop() ?? basePath;
2280
+ const parent = segments.join(".");
2281
+ return [
2282
+ {
2283
+ specifier: parent || basePath,
2284
+ importedSymbols: [symbol],
2285
+ isExternal: !basePath.startsWith("."),
2286
+ reExport: false
2287
+ }
2288
+ ];
2283
2289
  }
2284
2290
  function bashCommandName(commandNode) {
2285
2291
  if (!commandNode) {
@@ -2389,66 +2395,176 @@ function parseZigImport(node) {
2389
2395
  if (!importCall || nodeText(findNamedChild(importCall, "builtin_identifier") ?? importCall.namedChildren.at(0) ?? null) !== "@import") {
2390
2396
  return void 0;
2391
2397
  }
2392
- const stringNode = importCall.descendantsOfType("string_content").find((item) => item !== null);
2393
- const specifier = stringNode?.text.trim();
2398
+ const stringNode = importCall.descendantsOfType("string_content").find((item) => item !== null);
2399
+ const specifier = stringNode?.text.trim();
2400
+ if (!specifier) {
2401
+ return void 0;
2402
+ }
2403
+ return {
2404
+ specifier,
2405
+ importedSymbols: [],
2406
+ isExternal: !specifier.endsWith(".zig") && !specifier.includes("/") && !specifier.startsWith("."),
2407
+ reExport: false
2408
+ };
2409
+ }
2410
+ function flattenCSharpQualifiedName(node) {
2411
+ if (!node) {
2412
+ return "";
2413
+ }
2414
+ if (node.type === "identifier") {
2415
+ return node.text.trim();
2416
+ }
2417
+ if (node.type === "qualified_name") {
2418
+ const [head, tail] = [node.namedChildren[0] ?? null, node.namedChildren[1] ?? null];
2419
+ const headText = flattenCSharpQualifiedName(head);
2420
+ const tailText = tail?.type === "identifier" ? tail.text.trim() : flattenCSharpQualifiedName(tail);
2421
+ return headText && tailText ? `${headText}.${tailText}` : headText || tailText;
2422
+ }
2423
+ return node.text.trim();
2424
+ }
2425
+ function parseCSharpUsing(node) {
2426
+ const namedChildren = node.namedChildren.filter((child) => child !== null);
2427
+ if (namedChildren.length === 0) {
2428
+ return void 0;
2429
+ }
2430
+ let aliasName;
2431
+ let pathNode = null;
2432
+ if (namedChildren.length >= 2 && namedChildren[0]?.type === "identifier" && namedChildren[1]) {
2433
+ aliasName = namedChildren[0].text.trim();
2434
+ pathNode = namedChildren[1];
2435
+ } else {
2436
+ pathNode = namedChildren[0] ?? null;
2437
+ }
2438
+ const specifier = flattenCSharpQualifiedName(pathNode);
2439
+ if (!specifier) {
2440
+ return void 0;
2441
+ }
2442
+ return {
2443
+ specifier,
2444
+ importedSymbols: [],
2445
+ namespaceImport: aliasName,
2446
+ isExternal: !specifier.startsWith("."),
2447
+ reExport: false
2448
+ };
2449
+ }
2450
+ function flattenPhpQualifiedName(node) {
2451
+ if (!node) {
2452
+ return "";
2453
+ }
2454
+ if (node.type === "name") {
2455
+ return node.text.trim();
2456
+ }
2457
+ if (node.type === "namespace_name") {
2458
+ return node.namedChildren.filter((child) => child?.type === "name").map((child) => child.text.trim()).filter(Boolean).join("\\");
2459
+ }
2460
+ if (node.type === "qualified_name") {
2461
+ const parts = [];
2462
+ for (const child of node.namedChildren) {
2463
+ if (!child) {
2464
+ continue;
2465
+ }
2466
+ if (child.type === "namespace_name") {
2467
+ parts.push(flattenPhpQualifiedName(child));
2468
+ } else if (child.type === "name") {
2469
+ parts.push(child.text.trim());
2470
+ }
2471
+ }
2472
+ return parts.filter(Boolean).join("\\");
2473
+ }
2474
+ return node.text.trim();
2475
+ }
2476
+ function parsePhpUseClause(clause, prefix) {
2477
+ const names = clause.namedChildren.filter((child) => child?.type === "name");
2478
+ const qualified = clause.namedChildren.find((child) => child?.type === "qualified_name") ?? null;
2479
+ let specifier;
2480
+ let aliasName;
2481
+ if (qualified) {
2482
+ specifier = flattenPhpQualifiedName(qualified);
2483
+ if (names.length >= 1 && names[0]) {
2484
+ aliasName = names[0].text.trim();
2485
+ }
2486
+ } else if (names.length >= 1 && names[0]) {
2487
+ specifier = names[0].text.trim();
2488
+ if (names.length >= 2 && names[1]) {
2489
+ aliasName = names[1].text.trim();
2490
+ }
2491
+ } else {
2492
+ return void 0;
2493
+ }
2494
+ if (prefix && specifier) {
2495
+ specifier = `${prefix}\\${specifier}`;
2496
+ }
2394
2497
  if (!specifier) {
2395
2498
  return void 0;
2396
2499
  }
2397
2500
  return {
2398
2501
  specifier,
2399
2502
  importedSymbols: [],
2400
- isExternal: !specifier.endsWith(".zig") && !specifier.includes("/") && !specifier.startsWith("."),
2503
+ namespaceImport: aliasName,
2504
+ isExternal: true,
2401
2505
  reExport: false
2402
2506
  };
2403
2507
  }
2404
- function parseCSharpUsing(text) {
2405
- const aliasMatch = text.trim().match(/^using\s+([A-Za-z_]\w*)\s*=\s*([^;]+);$/);
2406
- if (aliasMatch) {
2407
- return {
2408
- specifier: aliasMatch[2].trim(),
2409
- importedSymbols: [],
2410
- namespaceImport: aliasMatch[1],
2411
- isExternal: !aliasMatch[2].trim().startsWith("."),
2412
- reExport: false
2413
- };
2508
+ function parsePhpUse(node) {
2509
+ const results = [];
2510
+ const groupNode = node.namedChildren.find((child) => child?.type === "namespace_use_group") ?? null;
2511
+ if (groupNode) {
2512
+ const prefixNode = node.namedChildren.find((child) => child?.type === "namespace_name") ?? null;
2513
+ const prefix = flattenPhpQualifiedName(prefixNode);
2514
+ for (const clause of groupNode.namedChildren) {
2515
+ if (!clause || clause.type !== "namespace_use_clause") {
2516
+ continue;
2517
+ }
2518
+ const parsed = parsePhpUseClause(clause, prefix);
2519
+ if (parsed) {
2520
+ results.push(parsed);
2521
+ }
2522
+ }
2523
+ return results;
2414
2524
  }
2415
- const match = text.trim().match(/^using\s+([^;]+);$/);
2416
- if (!match) {
2417
- return void 0;
2525
+ for (const child of node.namedChildren) {
2526
+ if (!child || child.type !== "namespace_use_clause") {
2527
+ continue;
2528
+ }
2529
+ const parsed = parsePhpUseClause(child, "");
2530
+ if (parsed) {
2531
+ results.push(parsed);
2532
+ }
2418
2533
  }
2419
- return {
2420
- specifier: match[1].trim(),
2421
- importedSymbols: [],
2422
- isExternal: !match[1].trim().startsWith("."),
2423
- reExport: false
2424
- };
2425
- }
2426
- function parsePhpUse(text) {
2427
- const cleaned = text.trim().replace(/^use\s+/, "").replace(/;$/, "");
2428
- return cleaned.split(",").map((item) => item.trim()).filter(Boolean).map((item) => {
2429
- const aliasMatch = item.match(/^(.+?)\s+as\s+([A-Za-z_]\w*)$/i);
2430
- const specifier = (aliasMatch ? aliasMatch[1] : item).trim();
2431
- return {
2432
- specifier,
2433
- importedSymbols: [],
2434
- namespaceImport: aliasMatch?.[2],
2435
- isExternal: !specifier.startsWith("."),
2436
- reExport: false
2437
- };
2438
- });
2534
+ return results;
2439
2535
  }
2440
- function parseCppInclude(text) {
2441
- const match = text.trim().match(/^#include\s+([<"].+[>"])$/);
2442
- if (!match) {
2443
- return void 0;
2536
+ function parseCppInclude(node) {
2537
+ for (const child of node.namedChildren) {
2538
+ if (!child) {
2539
+ continue;
2540
+ }
2541
+ if (child.type === "system_lib_string") {
2542
+ const specifier = child.text.replace(/^</, "").replace(/>$/, "").trim();
2543
+ if (!specifier) {
2544
+ return void 0;
2545
+ }
2546
+ return {
2547
+ specifier,
2548
+ importedSymbols: [],
2549
+ isExternal: true,
2550
+ reExport: false
2551
+ };
2552
+ }
2553
+ if (child.type === "string_literal") {
2554
+ const contentNode = child.namedChildren.find((inner) => inner?.type === "string_content") ?? null;
2555
+ const specifier = (contentNode?.text ?? child.text.replace(/^"|"$/g, "")).trim();
2556
+ if (!specifier) {
2557
+ return void 0;
2558
+ }
2559
+ return {
2560
+ specifier,
2561
+ importedSymbols: [],
2562
+ isExternal: false,
2563
+ reExport: false
2564
+ };
2565
+ }
2444
2566
  }
2445
- const specifier = quotedPath(match[1]);
2446
- return {
2447
- specifier,
2448
- importedSymbols: [],
2449
- isExternal: match[1].startsWith("<"),
2450
- reExport: false
2451
- };
2567
+ return void 0;
2452
2568
  }
2453
2569
  function rubyStringContent(node) {
2454
2570
  if (!node) {
@@ -2459,7 +2575,8 @@ function rubyStringContent(node) {
2459
2575
  }
2460
2576
  function normalizePowerShellDotSourceSpecifier(raw) {
2461
2577
  const unquoted = raw.replace(/^['"]+|['"]+$/g, "").trim();
2462
- return unquoted.replace(/^\$PSScriptRoot(?:[\\/]+|$)/i, "./");
2578
+ const withoutScriptRoot = unquoted.replace(/^\$PSScriptRoot(?:[\\/]+|$)/i, "./");
2579
+ return withoutScriptRoot.replace(/\\/g, "/");
2463
2580
  }
2464
2581
  function parsePowerShellImport(commandNode) {
2465
2582
  const commandName = commandNode.descendantsOfType(["command_name", "command_name_expr"]).find((item) => item !== null)?.text.trim();
@@ -2813,11 +2930,11 @@ function pythonCodeAnalysis(manifest, rootNode, diagnostics) {
2813
2930
  continue;
2814
2931
  }
2815
2932
  if (child.type === "import_statement") {
2816
- imports.push(...parsePythonImportStatement(child.text));
2933
+ imports.push(...parsePythonImportStatement(child));
2817
2934
  continue;
2818
2935
  }
2819
2936
  if (child.type === "import_from_statement") {
2820
- imports.push(...parsePythonFromImportStatement(child.text));
2937
+ imports.push(...parsePythonFromImportStatement(child));
2821
2938
  continue;
2822
2939
  }
2823
2940
  if (child.type === "class_definition") {
@@ -2872,7 +2989,7 @@ function goCodeAnalysis(manifest, rootNode, diagnostics) {
2872
2989
  }
2873
2990
  if (child.type === "import_declaration") {
2874
2991
  for (const spec of child.descendantsOfType("import_spec")) {
2875
- const parsed = spec ? parseGoImport(spec.text) : void 0;
2992
+ const parsed = spec ? parseGoImport(spec) : void 0;
2876
2993
  if (parsed) {
2877
2994
  imports.push(parsed);
2878
2995
  }
@@ -2946,7 +3063,7 @@ function rustCodeAnalysis(manifest, rootNode, diagnostics) {
2946
3063
  continue;
2947
3064
  }
2948
3065
  if (child.type === "use_declaration") {
2949
- imports.push(parseRustUse(child.text));
3066
+ imports.push(...parseRustUseDeclaration(child));
2950
3067
  continue;
2951
3068
  }
2952
3069
  if (child.type === "mod_item") {
@@ -3025,11 +3142,15 @@ function javaCodeAnalysis(manifest, rootNode, diagnostics) {
3025
3142
  continue;
3026
3143
  }
3027
3144
  if (child.type === "package_declaration") {
3028
- packageName = child.text.replace(/^package\s+/, "").replace(/;$/, "").trim();
3145
+ const pathNode = child.namedChildren.find((inner) => inner?.type === "scoped_identifier" || inner?.type === "identifier") ?? null;
3146
+ const flattened = flattenJavaScopedIdentifier(pathNode);
3147
+ if (flattened) {
3148
+ packageName = flattened;
3149
+ }
3029
3150
  continue;
3030
3151
  }
3031
3152
  if (child.type === "import_declaration") {
3032
- imports.push(parseJavaImport(child.text));
3153
+ imports.push(parseJavaImport(child));
3033
3154
  continue;
3034
3155
  }
3035
3156
  const name = extractIdentifier(child.childForFieldName("name"));
@@ -3117,7 +3238,7 @@ function kotlinCodeAnalysis(manifest, rootNode, diagnostics) {
3117
3238
  }
3118
3239
  if (child.type === "import_list") {
3119
3240
  for (const importNode of child.descendantsOfType("import_header").filter((item) => item !== null)) {
3120
- const parsed = parseKotlinImport(importNode.text);
3241
+ const parsed = parseKotlinImport(importNode);
3121
3242
  if (parsed) {
3122
3243
  imports.push(parsed);
3123
3244
  }
@@ -3207,7 +3328,7 @@ function scalaCodeAnalysis(manifest, rootNode, diagnostics) {
3207
3328
  continue;
3208
3329
  }
3209
3330
  if (child.type === "import_declaration") {
3210
- imports.push(...parseScalaImport(child.text));
3331
+ imports.push(...parseScalaImport(child));
3211
3332
  continue;
3212
3333
  }
3213
3334
  if (child.type === "function_definition") {
@@ -3388,7 +3509,7 @@ function csharpCodeAnalysis(manifest, rootNode, diagnostics) {
3388
3509
  continue;
3389
3510
  }
3390
3511
  if (child.type === "using_directive") {
3391
- const parsed = parseCSharpUsing(child.text);
3512
+ const parsed = parseCSharpUsing(child);
3392
3513
  if (parsed) {
3393
3514
  imports.push(parsed);
3394
3515
  }
@@ -3480,7 +3601,7 @@ function phpCodeAnalysis(manifest, rootNode, diagnostics) {
3480
3601
  continue;
3481
3602
  }
3482
3603
  if (child.type === "namespace_use_declaration") {
3483
- imports.push(...parsePhpUse(child.text));
3604
+ imports.push(...parsePhpUse(child));
3484
3605
  continue;
3485
3606
  }
3486
3607
  const name = extractIdentifier(child.childForFieldName("name"));
@@ -3704,7 +3825,7 @@ function cFamilyCodeAnalysis(manifest, language, rootNode, diagnostics) {
3704
3825
  continue;
3705
3826
  }
3706
3827
  if (child.type === "preproc_include") {
3707
- const parsed = parseCppInclude(child.text);
3828
+ const parsed = parseCppInclude(child);
3708
3829
  if (parsed) {
3709
3830
  imports.push(parsed);
3710
3831
  }
@@ -4638,15 +4759,36 @@ async function readNearestGoModulePath(startPath, cache) {
4638
4759
  current = parent;
4639
4760
  }
4640
4761
  }
4641
- function rustModuleAlias(repoRelativePath) {
4642
- const withoutExt = stripCodeExtension2(normalizeAlias(repoRelativePath));
4762
+ function rustModuleAliases(repoRelativePath) {
4763
+ const withoutExt = stripCodeExtension2(normalizeAlias(repoRelativePath)).replace(/\/mod$/i, "");
4764
+ if (!withoutExt) {
4765
+ return [];
4766
+ }
4767
+ const result = [];
4768
+ const push = (moduleTail) => {
4769
+ const trimmed = moduleTail.replace(/^\/+|\/+$/g, "");
4770
+ if (!trimmed || trimmed === "lib" || trimmed === "main") {
4771
+ result.push("crate");
4772
+ return;
4773
+ }
4774
+ const rootStripped = trimmed.replace(/\/(?:lib|main)$/i, "");
4775
+ if (rootStripped !== trimmed && rootStripped) {
4776
+ result.push(`crate::${rootStripped.replace(/\//g, "::")}`);
4777
+ }
4778
+ result.push(`crate::${trimmed.replace(/\//g, "::")}`);
4779
+ };
4643
4780
  const srcIdx = withoutExt.lastIndexOf("/src/");
4644
- const withinCrate = srcIdx >= 0 ? withoutExt.slice(srcIdx + "/src/".length) : withoutExt.replace(/^src\//, "");
4645
- const trimmed = withinCrate.replace(/\/mod$/i, "");
4646
- if (!trimmed || trimmed === "lib" || trimmed === "main") {
4647
- return "crate";
4781
+ if (srcIdx >= 0) {
4782
+ push(withoutExt.slice(srcIdx + "/src/".length));
4783
+ }
4784
+ if (withoutExt.startsWith("src/")) {
4785
+ push(withoutExt.slice("src/".length));
4786
+ }
4787
+ const segments = withoutExt.split("/").filter(Boolean);
4788
+ for (let start = 0; start < segments.length; start += 1) {
4789
+ push(segments.slice(start).join("/"));
4648
4790
  }
4649
- return `crate::${trimmed.replace(/\//g, "::")}`;
4791
+ return uniqueBy(result.filter(Boolean), (item) => item);
4650
4792
  }
4651
4793
  function candidateExtensionsFor(language) {
4652
4794
  switch (language) {
@@ -4722,7 +4864,9 @@ async function buildCodeIndex(rootDir, manifests, analyses) {
4722
4864
  break;
4723
4865
  case "rust":
4724
4866
  if (repoRelativePath) {
4725
- recordAlias(aliases, rustModuleAlias(repoRelativePath));
4867
+ for (const alias of rustModuleAliases(repoRelativePath)) {
4868
+ recordAlias(aliases, alias);
4869
+ }
4726
4870
  }
4727
4871
  break;
4728
4872
  case "go": {
@@ -4833,6 +4977,9 @@ function aliasMatches(lookup, ...aliases) {
4833
4977
  (entry) => entry.sourceId
4834
4978
  );
4835
4979
  }
4980
+ function aliasMatchesExact(lookup, alias) {
4981
+ return lookup.byAlias.get(normalizeAlias(alias)) ?? [];
4982
+ }
4836
4983
  function repoPathMatches(lookup, ...repoPaths) {
4837
4984
  return uniqueBy(
4838
4985
  repoPaths.map((repoPath) => lookup.byRepoPath.get(normalizeAlias(repoPath))).filter((entry) => Boolean(entry)),
@@ -4850,30 +4997,119 @@ function resolvePythonRelativeAliases(repoRelativePath, specifier) {
4850
4997
  }
4851
4998
  function resolveRustAliases(manifest, specifier) {
4852
4999
  const repoRelativePath = manifest.repoRelativePath ? normalizeAlias(manifest.repoRelativePath) : "";
4853
- const currentAlias = repoRelativePath ? rustModuleAlias(repoRelativePath) : void 0;
4854
- if (!specifier.startsWith("self::") && !specifier.startsWith("super::")) {
5000
+ if (!specifier.startsWith("self::") && !specifier.startsWith("super::") && specifier !== "self" && specifier !== "super") {
4855
5001
  return [specifier];
4856
5002
  }
4857
- if (!currentAlias) {
5003
+ const candidateAliases = repoRelativePath ? rustModuleAliases(repoRelativePath) : [];
5004
+ if (candidateAliases.length === 0) {
4858
5005
  return [];
4859
5006
  }
4860
- const currentParts = currentAlias.replace(/^crate(?:::)?/, "").split("::").filter(Boolean);
4861
- if (specifier.startsWith("self::")) {
4862
- return [`crate${currentParts.length ? `::${currentParts.join("::")}` : ""}::${specifier.slice("self::".length)}`];
5007
+ const tailAfter = specifier.startsWith("self::") ? specifier.slice("self::".length) : specifier.startsWith("super::") ? specifier.slice("super::".length) : "";
5008
+ const superRelative = specifier.startsWith("super");
5009
+ const expansions = [];
5010
+ for (const currentAlias of candidateAliases) {
5011
+ const currentParts = currentAlias.replace(/^crate(?:::)?/, "").split("::").filter(Boolean);
5012
+ if (superRelative) {
5013
+ if (currentParts.length > 0) {
5014
+ const parentParts = currentParts.slice(0, -1);
5015
+ const expanded2 = `crate${parentParts.length ? `::${parentParts.join("::")}` : ""}${tailAfter ? `::${tailAfter}` : ""}`.replace(/::+/g, "::").replace(/::$/, "");
5016
+ expansions.push(expanded2);
5017
+ }
5018
+ continue;
5019
+ }
5020
+ const expanded = `crate${currentParts.length ? `::${currentParts.join("::")}` : ""}${tailAfter ? `::${tailAfter}` : ""}`.replace(/::+/g, "::").replace(/::$/, "");
5021
+ expansions.push(expanded);
5022
+ }
5023
+ return uniqueBy(expansions, (item) => item);
5024
+ }
5025
+ function rustCrateRootPrefix(repoRelativePath) {
5026
+ if (!repoRelativePath) {
5027
+ return void 0;
5028
+ }
5029
+ const normalized = normalizeAlias(repoRelativePath);
5030
+ const idx = normalized.lastIndexOf("/src/");
5031
+ if (idx >= 0) {
5032
+ return normalized.slice(0, idx + "/src/".length);
4863
5033
  }
4864
- return [
4865
- `crate${currentParts.length > 1 ? `::${currentParts.slice(0, -1).join("::")}` : ""}::${specifier.slice("super::".length)}`.replace(/::+/g, "::").replace(/::$/, "")
4866
- ];
5034
+ if (normalized.startsWith("src/")) {
5035
+ return "src/";
5036
+ }
5037
+ return void 0;
5038
+ }
5039
+ function filterRustCandidatesToSameCrate(candidates, consumerPath) {
5040
+ const normalizedConsumer = consumerPath ? normalizeAlias(consumerPath) : "";
5041
+ const withoutSelf = normalizedConsumer ? candidates.filter((entry) => normalizeAlias(entry.repoRelativePath ?? "") !== normalizedConsumer) : candidates;
5042
+ if (withoutSelf.length <= 1) {
5043
+ return withoutSelf;
5044
+ }
5045
+ const cratePrefix = rustCrateRootPrefix(consumerPath);
5046
+ if (cratePrefix) {
5047
+ const sameCrate = withoutSelf.filter((entry) => normalizeAlias(entry.repoRelativePath ?? "").startsWith(cratePrefix));
5048
+ if (sameCrate.length > 0) {
5049
+ return sameCrate;
5050
+ }
5051
+ }
5052
+ if (normalizedConsumer) {
5053
+ let dir = path6.posix.dirname(normalizedConsumer);
5054
+ while (dir && dir !== "." && dir !== "/") {
5055
+ const prefix = `${dir}/`;
5056
+ const sameTree = withoutSelf.filter((entry) => normalizeAlias(entry.repoRelativePath ?? "").startsWith(prefix));
5057
+ if (sameTree.length > 0) {
5058
+ return sameTree;
5059
+ }
5060
+ const parent = path6.posix.dirname(dir);
5061
+ if (parent === dir) {
5062
+ break;
5063
+ }
5064
+ dir = parent;
5065
+ }
5066
+ }
5067
+ return withoutSelf;
5068
+ }
5069
+ function resolveRustAliasWithStripping(alias, lookup, consumerPath) {
5070
+ const segments = alias.split("::");
5071
+ while (segments.length > 0) {
5072
+ const candidate = segments.join("::");
5073
+ const matches = aliasMatchesExact(lookup, candidate);
5074
+ const filtered = matches.length > 0 ? filterRustCandidatesToSameCrate(matches, consumerPath) : [];
5075
+ if (filtered.length > 0) {
5076
+ return filtered;
5077
+ }
5078
+ if (candidate === "crate" || candidate === "self" || candidate === "super") {
5079
+ break;
5080
+ }
5081
+ segments.pop();
5082
+ }
5083
+ return [];
4867
5084
  }
4868
5085
  function luaSpecifierLooksLocal(specifier) {
4869
5086
  return /^[A-Za-z_][A-Za-z0-9_]*(?:[./][A-Za-z_][A-Za-z0-9_]*)*$/.test(specifier);
4870
5087
  }
4871
- function resolveLuaModuleCandidates(specifier) {
5088
+ function resolveLuaModuleCandidates(specifier, repoRelativePath) {
4872
5089
  const normalized = normalizeAlias(specifier.replace(/\./g, "/"));
4873
5090
  if (!normalized) {
4874
5091
  return [];
4875
5092
  }
4876
- return uniqueBy([`${normalized}.lua`, path6.posix.join(normalized, "init.lua")], (item) => item);
5093
+ const bases = /* @__PURE__ */ new Set([normalized]);
5094
+ bases.add(`src/${normalized}`);
5095
+ bases.add(`lua/${normalized}`);
5096
+ if (repoRelativePath) {
5097
+ let dir = path6.posix.dirname(repoRelativePath);
5098
+ while (dir && dir !== "." && dir !== "/") {
5099
+ bases.add(`${dir}/${normalized}`);
5100
+ const parent = path6.posix.dirname(dir);
5101
+ if (parent === dir) {
5102
+ break;
5103
+ }
5104
+ dir = parent;
5105
+ }
5106
+ }
5107
+ const candidates = [];
5108
+ for (const base of bases) {
5109
+ candidates.push(`${base}.lua`);
5110
+ candidates.push(path6.posix.join(base, "init.lua"));
5111
+ }
5112
+ return uniqueBy(candidates, (item) => item);
4877
5113
  }
4878
5114
  function findImportCandidates(manifest, codeImport, lookup) {
4879
5115
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType);
@@ -4901,7 +5137,7 @@ function findImportCandidates(manifest, codeImport, lookup) {
4901
5137
  case "dart":
4902
5138
  return repoRelativePath && dartSpecifierLooksLocal2(codeImport.specifier) ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
4903
5139
  case "lua":
4904
- return luaSpecifierLooksLocal(codeImport.specifier) ? repoPathMatches(lookup, ...resolveLuaModuleCandidates(codeImport.specifier)) : aliasMatches(lookup, codeImport.specifier, codeImport.specifier.replace(/\./g, "/"));
5140
+ return luaSpecifierLooksLocal(codeImport.specifier) ? repoPathMatches(lookup, ...resolveLuaModuleCandidates(codeImport.specifier, repoRelativePath)) : aliasMatches(lookup, codeImport.specifier, codeImport.specifier.replace(/\./g, "/"));
4905
5141
  case "zig":
4906
5142
  return repoRelativePath && (!codeImport.isExternal || codeImport.specifier.endsWith(".zig")) ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
4907
5143
  case "php":
@@ -4920,8 +5156,15 @@ function findImportCandidates(manifest, codeImport, lookup) {
4920
5156
  codeImport.specifier.replace(/\\/g, "/"),
4921
5157
  stripCodeExtension2(codeImport.specifier.replace(/\\/g, "/"))
4922
5158
  );
4923
- case "rust":
4924
- return aliasMatches(lookup, codeImport.specifier, ...resolveRustAliases(manifest, codeImport.specifier));
5159
+ case "rust": {
5160
+ for (const alias of [codeImport.specifier, ...resolveRustAliases(manifest, codeImport.specifier)]) {
5161
+ const matches = resolveRustAliasWithStripping(alias, lookup, repoRelativePath);
5162
+ if (matches.length > 0) {
5163
+ return matches;
5164
+ }
5165
+ }
5166
+ return [];
5167
+ }
4925
5168
  case "c":
4926
5169
  case "cpp":
4927
5170
  return repoRelativePath && !codeImport.isExternal ? repoPathMatches(lookup, ...importResolutionCandidates(repoRelativePath, codeImport.specifier, candidateExtensionsFor(language))) : aliasMatches(lookup, codeImport.specifier);
@@ -4930,7 +5173,7 @@ function findImportCandidates(manifest, codeImport, lookup) {
4930
5173
  }
4931
5174
  }
4932
5175
  function importLooksLocal(manifest, codeImport, candidates) {
4933
- if (candidates.length > 0) {
5176
+ if (candidates.length === 1) {
4934
5177
  return true;
4935
5178
  }
4936
5179
  const language = manifest.language ?? inferCodeLanguage(manifest.originalPath ?? manifest.storedPath, manifest.mimeType);
@@ -5661,7 +5904,7 @@ async function extractEpubChapters(input) {
5661
5904
  if (!markdown) {
5662
5905
  continue;
5663
5906
  }
5664
- const chapterTitle = firstHtmlHeading(html) || markdown.match(/^#\s+(.+)$/m)?.[1]?.trim() || item.href;
5907
+ const chapterTitle = firstHtmlHeading(html) || item.href;
5665
5908
  const normalizedTitle = normalizeWhitespace(chapterTitle);
5666
5909
  if (!normalizedTitle || /^table of contents$/i.test(normalizedTitle)) {
5667
5910
  continue;
@@ -6294,6 +6537,41 @@ async function appendWatchRun(rootDir, run) {
6294
6537
  await appendJsonLine(paths.jobsLogPath, run);
6295
6538
  }
6296
6539
 
6540
+ // src/markdown-ast.ts
6541
+ import { fromMarkdown } from "mdast-util-from-markdown";
6542
+ function parseMarkdownNodes(text) {
6543
+ try {
6544
+ const root = fromMarkdown(text);
6545
+ return Array.isArray(root.children) ? root.children : [];
6546
+ } catch {
6547
+ return [];
6548
+ }
6549
+ }
6550
+ function markdownNodeText(node) {
6551
+ if (node.type === "text" || node.type === "inlineCode" || node.type === "code") {
6552
+ return normalizeWhitespace(node.value ?? "");
6553
+ }
6554
+ if (node.type === "image") {
6555
+ return normalizeWhitespace(node.alt ?? "");
6556
+ }
6557
+ if (node.type === "break" || node.type === "thematicBreak") {
6558
+ return " ";
6559
+ }
6560
+ return normalizeWhitespace((node.children ?? []).map((child) => markdownNodeText(child)).join(" "));
6561
+ }
6562
+ function firstMarkdownHeading(text) {
6563
+ const nodes = parseMarkdownNodes(text);
6564
+ for (const node of nodes) {
6565
+ if (node.type === "heading") {
6566
+ const title = markdownNodeText(node).trim();
6567
+ if (title) {
6568
+ return title;
6569
+ }
6570
+ }
6571
+ }
6572
+ return void 0;
6573
+ }
6574
+
6297
6575
  // src/source-classification.ts
6298
6576
  import path9 from "path";
6299
6577
  var ALL_SOURCE_CLASSES = ["first_party", "third_party", "resource", "generated"];
@@ -6723,8 +7001,7 @@ function titleFromText(fallback, content, filePath) {
6723
7001
  return rstTitle;
6724
7002
  }
6725
7003
  }
6726
- const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
6727
- return heading || fallback;
7004
+ return firstMarkdownHeading(content) ?? fallback;
6728
7005
  }
6729
7006
  function guessMimeType(target) {
6730
7007
  if (isRstFilePath(target)) {
@@ -6888,8 +7165,7 @@ function normalizeRstExtractedText(content) {
6888
7165
  }
6889
7166
  function titleFromRst(fallback, content) {
6890
7167
  const normalized = normalizeRstExtractedText(content);
6891
- const heading = normalized.match(/^#+\s+(.+)$/m)?.[1]?.trim();
6892
- return heading || fallback;
7168
+ return firstMarkdownHeading(normalized) ?? fallback;
6893
7169
  }
6894
7170
  function extractedTextForPlainSource(filePath, sourceKind, content) {
6895
7171
  if (sourceKind === "text" && isRstFilePath(filePath)) {
@@ -9213,9 +9489,55 @@ import { z as z7 } from "zod";
9213
9489
 
9214
9490
  // src/analysis.ts
9215
9491
  import path14 from "path";
9216
- import nlp from "compromise";
9217
- import { fromMarkdown } from "mdast-util-from-markdown";
9492
+ import nlp2 from "compromise";
9218
9493
  import { z as z2 } from "zod";
9494
+
9495
+ // src/tokenize.ts
9496
+ import nlp from "compromise";
9497
+ var CLOSED_CLASS_POS_SELECTOR = "#Determiner, #Preposition, #Conjunction, #Pronoun, #Auxiliary, #Copula";
9498
+ function splitTermToTokens(term, tokens) {
9499
+ for (const piece of term.split(/[^a-z0-9-]+/)) {
9500
+ const trimmed = piece.replace(/^-+|-+$/g, "");
9501
+ if (trimmed.length >= 2) {
9502
+ tokens.push(trimmed);
9503
+ }
9504
+ }
9505
+ }
9506
+ function tokenize(text) {
9507
+ const lower = text.toLowerCase();
9508
+ try {
9509
+ const terms = nlp(lower).terms().out("array");
9510
+ const tokens = [];
9511
+ for (const term of terms) {
9512
+ splitTermToTokens(term, tokens);
9513
+ }
9514
+ if (tokens.length > 0) {
9515
+ return tokens;
9516
+ }
9517
+ } catch {
9518
+ }
9519
+ return lower.match(/[a-z0-9][a-z0-9-]{1,}/g) ?? [];
9520
+ }
9521
+ function contentTokens(text, minLength = 4) {
9522
+ const lower = text.toLowerCase();
9523
+ const tokens = [];
9524
+ try {
9525
+ const contentDoc = nlp(lower).not(CLOSED_CLASS_POS_SELECTOR);
9526
+ const terms = contentDoc.terms().out("array");
9527
+ for (const term of terms) {
9528
+ splitTermToTokens(term, tokens);
9529
+ }
9530
+ } catch {
9531
+ }
9532
+ if (tokens.length === 0) {
9533
+ for (const piece of lower.match(/[a-z0-9][a-z0-9-]{1,}/g) ?? []) {
9534
+ tokens.push(piece);
9535
+ }
9536
+ }
9537
+ return tokens.filter((token) => token.length >= minLength);
9538
+ }
9539
+
9540
+ // src/analysis.ts
9219
9541
  var ANALYSIS_FORMAT_VERSION = 7;
9220
9542
  var sourceAnalysisSchema = z2.object({
9221
9543
  title: z2.string().min(1),
@@ -9234,46 +9556,6 @@ var sourceAnalysisSchema = z2.object({
9234
9556
  questions: z2.array(z2.string()).max(6).default([]),
9235
9557
  tags: z2.array(z2.string()).max(5).default([])
9236
9558
  });
9237
- var STOPWORDS = /* @__PURE__ */ new Set([
9238
- "about",
9239
- "after",
9240
- "also",
9241
- "been",
9242
- "being",
9243
- "between",
9244
- "both",
9245
- "could",
9246
- "does",
9247
- "each",
9248
- "from",
9249
- "have",
9250
- "into",
9251
- "just",
9252
- "more",
9253
- "much",
9254
- "only",
9255
- "other",
9256
- "over",
9257
- "same",
9258
- "some",
9259
- "such",
9260
- "than",
9261
- "that",
9262
- "their",
9263
- "there",
9264
- "these",
9265
- "they",
9266
- "this",
9267
- "very",
9268
- "what",
9269
- "when",
9270
- "where",
9271
- "which",
9272
- "while",
9273
- "with",
9274
- "would",
9275
- "your"
9276
- ]);
9277
9559
  var HEURISTIC_SECTION_SOURCE_KINDS = /* @__PURE__ */ new Map([
9278
9560
  ["transcript", "Transcript"],
9279
9561
  ["chat_export", "Messages"],
@@ -9282,10 +9564,7 @@ var HEURISTIC_SECTION_SOURCE_KINDS = /* @__PURE__ */ new Map([
9282
9564
  ]);
9283
9565
  function extractTopTerms(text, count) {
9284
9566
  const frequency = /* @__PURE__ */ new Map();
9285
- for (const token of text.toLowerCase().match(/[a-z][a-z0-9-]{3,}/g) ?? []) {
9286
- if (STOPWORDS.has(token)) {
9287
- continue;
9288
- }
9567
+ for (const token of contentTokens(text)) {
9289
9568
  frequency.set(token, (frequency.get(token) ?? 0) + 1);
9290
9569
  }
9291
9570
  return [...frequency.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).slice(0, count).map(([token]) => token);
@@ -9293,7 +9572,7 @@ function extractTopTerms(text, count) {
9293
9572
  function extractEntities(text, count) {
9294
9573
  const candidates = [];
9295
9574
  try {
9296
- const doc = nlp(text);
9575
+ const doc = nlp2(text);
9297
9576
  const segments = [
9298
9577
  doc.match("#ProperNoun+").out("array"),
9299
9578
  doc.people().out("array"),
@@ -9311,10 +9590,6 @@ function extractEntities(text, count) {
9311
9590
  }
9312
9591
  } catch {
9313
9592
  }
9314
- if (candidates.length === 0) {
9315
- const matches = text.match(/\b[A-Z][A-Za-z0-9-]+(?:\s+[A-Z][A-Za-z0-9-]+){0,2}\b/g) ?? [];
9316
- candidates.push(...matches.map((value) => normalizeWhitespace(value)));
9317
- }
9318
9593
  return uniqueBy(candidates, (value) => value.toLowerCase()).slice(0, count);
9319
9594
  }
9320
9595
  function detectPolarity(text) {
@@ -9326,26 +9601,6 @@ function detectPolarity(text) {
9326
9601
  }
9327
9602
  return "neutral";
9328
9603
  }
9329
- function parseMarkdownNodes(text) {
9330
- try {
9331
- const root = fromMarkdown(text);
9332
- return Array.isArray(root.children) ? root.children : [];
9333
- } catch {
9334
- return [];
9335
- }
9336
- }
9337
- function markdownNodeText(node) {
9338
- if (node.type === "text" || node.type === "inlineCode" || node.type === "code") {
9339
- return normalizeWhitespace(node.value ?? "");
9340
- }
9341
- if (node.type === "image") {
9342
- return normalizeWhitespace(node.alt ?? "");
9343
- }
9344
- if (node.type === "break" || node.type === "thematicBreak") {
9345
- return " ";
9346
- }
9347
- return normalizeWhitespace((node.children ?? []).map((child) => markdownNodeText(child)).join(" "));
9348
- }
9349
9604
  function markdownNodesText(nodes) {
9350
9605
  return normalizeWhitespace(nodes.map((node) => markdownNodeText(node)).join("\n"));
9351
9606
  }
@@ -10620,7 +10875,7 @@ function filterGraphBySourceClass(graph, sourceClass) {
10620
10875
  }
10621
10876
 
10622
10877
  // src/graph-enrichment.ts
10623
- var STOPWORDS2 = /* @__PURE__ */ new Set([
10878
+ var STOPWORDS = /* @__PURE__ */ new Set([
10624
10879
  "about",
10625
10880
  "after",
10626
10881
  "also",
@@ -10684,7 +10939,7 @@ function addFeature(bucket, reason, value) {
10684
10939
  }
10685
10940
  function themeTokens(value) {
10686
10941
  return uniqueBy(
10687
- normalizeValue(value).split(/[^a-z0-9]+/i).filter((token) => token.length >= 4 && !STOPWORDS2.has(token)),
10942
+ normalizeValue(value).split(/[^a-z0-9]+/i).filter((token) => token.length >= 4 && !STOPWORDS.has(token)),
10688
10943
  (token) => token
10689
10944
  ).slice(0, 6);
10690
10945
  }
@@ -13319,8 +13574,7 @@ function getDatabaseSync() {
13319
13574
  return builtin.DatabaseSync;
13320
13575
  }
13321
13576
  function toFtsQuery(query) {
13322
- const tokens = query.toLowerCase().match(/[a-z0-9]{2,}/g)?.filter(Boolean) ?? [];
13323
- return tokens.join(" OR ");
13577
+ return tokenize(query).join(" OR ");
13324
13578
  }
13325
13579
  function normalizeKind(value) {
13326
13580
  return value === "index" || value === "source" || value === "module" || value === "concept" || value === "entity" || value === "output" || value === "insight" || value === "graph_report" || value === "community_summary" ? value : void 0;
@@ -13749,7 +14003,7 @@ async function resolveImageGenerationProvider(rootDir) {
13749
14003
  if (!providerConfig) {
13750
14004
  throw new Error(`No provider configured with id "${preferredProviderId}" for task "imageProvider".`);
13751
14005
  }
13752
- const { createProvider: createProvider2 } = await import("./registry-NBLIJHZT.js");
14006
+ const { createProvider: createProvider2 } = await import("./registry-W6ZFRI73.js");
13753
14007
  return createProvider2(preferredProviderId, providerConfig, rootDir);
13754
14008
  }
13755
14009
  async function generateOutputArtifacts(rootDir, input) {
@@ -18016,7 +18270,7 @@ async function bootstrapDemo(rootDir, input) {
18016
18270
  }
18017
18271
 
18018
18272
  // src/mcp.ts
18019
- var SERVER_VERSION = "0.6.7";
18273
+ var SERVER_VERSION = "0.6.8";
18020
18274
  async function createMcpServer(rootDir) {
18021
18275
  const server = new McpServer({
18022
18276
  name: "swarmvault",
@@ -18028,10 +18282,10 @@ async function createMcpServer(rootDir) {
18028
18282
  {
18029
18283
  description: "Return the current SwarmVault workspace paths and high-level counts."
18030
18284
  },
18031
- async () => {
18285
+ safeHandler(async () => {
18032
18286
  const info = await getWorkspaceInfo(rootDir);
18033
18287
  return asToolText(info);
18034
- }
18288
+ })
18035
18289
  );
18036
18290
  server.registerTool(
18037
18291
  "search_pages",
@@ -18042,10 +18296,10 @@ async function createMcpServer(rootDir) {
18042
18296
  limit: z8.number().int().min(1).max(25).optional().describe("Maximum number of results")
18043
18297
  }
18044
18298
  },
18045
- async ({ query, limit }) => {
18299
+ safeHandler(async ({ query, limit }) => {
18046
18300
  const results = await searchVault(rootDir, query, limit ?? 5);
18047
18301
  return asToolText(results);
18048
- }
18302
+ })
18049
18303
  );
18050
18304
  server.registerTool(
18051
18305
  "read_page",
@@ -18055,13 +18309,13 @@ async function createMcpServer(rootDir) {
18055
18309
  path: z8.string().min(1).describe("Path relative to wiki/, for example sources/example.md")
18056
18310
  }
18057
18311
  },
18058
- async ({ path: relativePath }) => {
18312
+ safeHandler(async ({ path: relativePath }) => {
18059
18313
  const page = await readPage(rootDir, relativePath);
18060
18314
  if (!page) {
18061
18315
  return asToolError(`Page not found: ${relativePath}`);
18062
18316
  }
18063
18317
  return asToolText(page);
18064
- }
18318
+ })
18065
18319
  );
18066
18320
  server.registerTool(
18067
18321
  "list_sources",
@@ -18071,10 +18325,10 @@ async function createMcpServer(rootDir) {
18071
18325
  limit: z8.number().int().min(1).max(100).optional().describe("Maximum number of manifests to return")
18072
18326
  }
18073
18327
  },
18074
- async ({ limit }) => {
18328
+ safeHandler(async ({ limit }) => {
18075
18329
  const manifests = await listManifests(rootDir);
18076
18330
  return asToolText(limit ? manifests.slice(0, limit) : manifests);
18077
- }
18331
+ })
18078
18332
  );
18079
18333
  server.registerTool(
18080
18334
  "query_graph",
@@ -18086,22 +18340,22 @@ async function createMcpServer(rootDir) {
18086
18340
  budget: z8.number().int().min(3).max(50).optional().describe("Maximum nodes to summarize")
18087
18341
  }
18088
18342
  },
18089
- async ({ question, traversal, budget }) => {
18343
+ safeHandler(async ({ question, traversal, budget }) => {
18090
18344
  const result = await queryGraphVault(rootDir, question, {
18091
18345
  traversal,
18092
18346
  budget
18093
18347
  });
18094
18348
  return asToolText(result);
18095
- }
18349
+ })
18096
18350
  );
18097
18351
  server.registerTool(
18098
18352
  "graph_report",
18099
18353
  {
18100
18354
  description: "Return the machine-readable graph report and trust artifact."
18101
18355
  },
18102
- async () => {
18356
+ safeHandler(async () => {
18103
18357
  return asToolText(await readGraphReport(rootDir) ?? { error: "Graph report not found. Run `swarmvault compile` first." });
18104
- }
18358
+ })
18105
18359
  );
18106
18360
  server.registerTool(
18107
18361
  "get_node",
@@ -18111,9 +18365,9 @@ async function createMcpServer(rootDir) {
18111
18365
  target: z8.string().min(1).describe("Node or page label/id")
18112
18366
  }
18113
18367
  },
18114
- async ({ target }) => {
18368
+ safeHandler(async ({ target }) => {
18115
18369
  return asToolText(await explainGraphVault(rootDir, target));
18116
- }
18370
+ })
18117
18371
  );
18118
18372
  server.registerTool(
18119
18373
  "get_hyperedges",
@@ -18124,9 +18378,9 @@ async function createMcpServer(rootDir) {
18124
18378
  limit: z8.number().int().min(1).max(50).optional().describe("Maximum hyperedges to return")
18125
18379
  }
18126
18380
  },
18127
- async ({ target, limit }) => {
18381
+ safeHandler(async ({ target, limit }) => {
18128
18382
  return asToolText(await listGraphHyperedges(rootDir, target, limit ?? 25));
18129
- }
18383
+ })
18130
18384
  );
18131
18385
  server.registerTool(
18132
18386
  "get_neighbors",
@@ -18136,10 +18390,10 @@ async function createMcpServer(rootDir) {
18136
18390
  target: z8.string().min(1).describe("Node or page label/id")
18137
18391
  }
18138
18392
  },
18139
- async ({ target }) => {
18393
+ safeHandler(async ({ target }) => {
18140
18394
  const explanation = await explainGraphVault(rootDir, target);
18141
18395
  return asToolText(explanation.neighbors);
18142
- }
18396
+ })
18143
18397
  );
18144
18398
  server.registerTool(
18145
18399
  "shortest_path",
@@ -18150,9 +18404,9 @@ async function createMcpServer(rootDir) {
18150
18404
  to: z8.string().min(1).describe("End node/page label or id")
18151
18405
  }
18152
18406
  },
18153
- async ({ from, to }) => {
18407
+ safeHandler(async ({ from, to }) => {
18154
18408
  return asToolText(await pathGraphVault(rootDir, from, to));
18155
- }
18409
+ })
18156
18410
  );
18157
18411
  server.registerTool(
18158
18412
  "god_nodes",
@@ -18162,9 +18416,9 @@ async function createMcpServer(rootDir) {
18162
18416
  limit: z8.number().int().min(1).max(25).optional().describe("Maximum nodes to return")
18163
18417
  }
18164
18418
  },
18165
- async ({ limit }) => {
18419
+ safeHandler(async ({ limit }) => {
18166
18420
  return asToolText(await listGodNodes(rootDir, limit ?? 10));
18167
- }
18421
+ })
18168
18422
  );
18169
18423
  server.registerTool(
18170
18424
  "query_vault",
@@ -18176,14 +18430,14 @@ async function createMcpServer(rootDir) {
18176
18430
  format: z8.enum(["markdown", "report", "slides", "chart", "image"]).optional().describe("Output format")
18177
18431
  }
18178
18432
  },
18179
- async ({ question, save, format }) => {
18433
+ safeHandler(async ({ question, save, format }) => {
18180
18434
  const result = await queryVault(rootDir, {
18181
18435
  question,
18182
18436
  save: save ?? true,
18183
18437
  format
18184
18438
  });
18185
18439
  return asToolText(result);
18186
- }
18440
+ })
18187
18441
  );
18188
18442
  server.registerTool(
18189
18443
  "ingest_input",
@@ -18193,10 +18447,10 @@ async function createMcpServer(rootDir) {
18193
18447
  input: z8.string().min(1).describe("Local path or URL to ingest")
18194
18448
  }
18195
18449
  },
18196
- async ({ input }) => {
18450
+ safeHandler(async ({ input }) => {
18197
18451
  const result = await ingestInputDetailed(rootDir, input);
18198
18452
  return asToolText(result);
18199
- }
18453
+ })
18200
18454
  );
18201
18455
  server.registerTool(
18202
18456
  "compile_vault",
@@ -18206,20 +18460,20 @@ async function createMcpServer(rootDir) {
18206
18460
  approve: z8.boolean().optional().describe("Stage a review bundle without applying active page changes")
18207
18461
  }
18208
18462
  },
18209
- async ({ approve }) => {
18463
+ safeHandler(async ({ approve }) => {
18210
18464
  const result = await compileVault(rootDir, { approve: approve ?? false });
18211
18465
  return asToolText(result);
18212
- }
18466
+ })
18213
18467
  );
18214
18468
  server.registerTool(
18215
18469
  "lint_vault",
18216
18470
  {
18217
18471
  description: "Run anti-drift and vault health checks."
18218
18472
  },
18219
- async () => {
18473
+ safeHandler(async () => {
18220
18474
  const findings = await lintVault(rootDir);
18221
18475
  return asToolText(findings);
18222
- }
18476
+ })
18223
18477
  );
18224
18478
  server.registerResource(
18225
18479
  "swarmvault-config",
@@ -18390,6 +18644,17 @@ function asToolError(message) {
18390
18644
  ]
18391
18645
  };
18392
18646
  }
18647
+ function safeHandler(handler) {
18648
+ return async (args) => {
18649
+ try {
18650
+ return await handler(args);
18651
+ } catch (error) {
18652
+ const message = error instanceof Error ? error.message : String(error);
18653
+ console.error(`[swarmvault-mcp] tool handler failed: ${message}`);
18654
+ return asToolError(message);
18655
+ }
18656
+ };
18657
+ }
18393
18658
  function asTextResource(uri, text) {
18394
18659
  return {
18395
18660
  contents: [
@@ -18637,13 +18902,22 @@ async function serveSchedules(rootDir, pollMs = 3e4) {
18637
18902
  }
18638
18903
  running = true;
18639
18904
  try {
18640
- const schedules = await listSchedules(rootDir);
18905
+ let schedules = [];
18906
+ try {
18907
+ schedules = await listSchedules(rootDir);
18908
+ } catch (error) {
18909
+ console.error(`[swarmvault-schedule] failed to list schedules: ${error instanceof Error ? error.message : String(error)}`);
18910
+ }
18641
18911
  const due = schedules.filter((item) => item.enabled).filter((item) => !item.nextRunAt || Date.parse(item.nextRunAt) <= Date.now()).sort((left, right) => (left.nextRunAt ?? "").localeCompare(right.nextRunAt ?? ""));
18642
18912
  for (const schedule of due) {
18643
18913
  if (closed) {
18644
18914
  break;
18645
18915
  }
18646
- await runSchedule(rootDir, schedule.jobId);
18916
+ try {
18917
+ await runSchedule(rootDir, schedule.jobId);
18918
+ } catch (error) {
18919
+ console.error(`[swarmvault-schedule] job ${schedule.jobId} crashed: ${error instanceof Error ? error.message : String(error)}`);
18920
+ }
18647
18921
  }
18648
18922
  } finally {
18649
18923
  running = false;
@@ -20586,10 +20860,6 @@ var MAX_BACKOFF_MS = 3e4;
20586
20860
  var BACKOFF_THRESHOLD = 3;
20587
20861
  var CRITICAL_THRESHOLD = 10;
20588
20862
  var REPO_WATCH_IGNORES = /* @__PURE__ */ new Set([".git", ".venv"]);
20589
- function withinRoot3(rootPath, targetPath) {
20590
- const relative = path27.relative(rootPath, targetPath);
20591
- return relative === "" || !relative.startsWith("..") && !path27.isAbsolute(relative);
20592
- }
20593
20863
  function hasIgnoredRepoSegment(baseDir, targetPath) {
20594
20864
  const relativePath = path27.relative(baseDir, targetPath);
20595
20865
  if (!relativePath || relativePath.startsWith("..")) {
@@ -20759,11 +21029,11 @@ async function watchVault(rootDir, options = {}) {
20759
21029
  interval: 100,
20760
21030
  ignored: (targetPath) => {
20761
21031
  const absolutePath = path27.resolve(targetPath);
20762
- const primaryTarget = watchTargets.filter((watchTarget) => withinRoot3(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
21032
+ const primaryTarget = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, absolutePath)).sort((left, right) => right.length - left.length)[0] ?? null;
20763
21033
  if (!primaryTarget) {
20764
21034
  return false;
20765
21035
  }
20766
- if (primaryTarget !== inboxWatchRoot && ignoredRoots.some((ignoreRoot) => withinRoot3(ignoreRoot, absolutePath))) {
21036
+ if (primaryTarget !== inboxWatchRoot && ignoredRoots.some((ignoreRoot) => isPathWithin(ignoreRoot, absolutePath))) {
20767
21037
  return true;
20768
21038
  }
20769
21039
  return hasIgnoredRepoSegment(primaryTarget, absolutePath);
@@ -20947,7 +21217,7 @@ async function watchVault(rootDir, options = {}) {
20947
21217
  }
20948
21218
  };
20949
21219
  const reasonForPath = (targetPath) => {
20950
- const baseDir = watchTargets.filter((watchTarget) => withinRoot3(watchTarget, path27.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
21220
+ const baseDir = watchTargets.filter((watchTarget) => isPathWithin(watchTarget, path27.resolve(targetPath))).sort((left, right) => right.length - left.length)[0] ?? paths.inboxDir;
20951
21221
  return path27.relative(baseDir, targetPath) || ".";
20952
21222
  };
20953
21223
  watcher.on("add", (filePath) => schedule(`add:${reasonForPath(filePath)}`)).on("change", (filePath) => schedule(`change:${reasonForPath(filePath)}`)).on("unlink", (filePath) => schedule(`unlink:${reasonForPath(filePath)}`)).on("addDir", (dirPath) => schedule(`addDir:${reasonForPath(dirPath)}`)).on("unlinkDir", (dirPath) => schedule(`unlinkDir:${reasonForPath(dirPath)}`)).on("error", (caught) => schedule(`error:${caught instanceof Error ? caught.message : String(caught)}`));