verimu 0.0.18 → 0.0.19

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/cli.mjs CHANGED
@@ -13052,9 +13052,9 @@ var require_traverse = __commonJS({
13052
13052
  Object.defineProperty(exports, "__esModule", {
13053
13053
  value: true
13054
13054
  });
13055
- exports.default = traverse2;
13055
+ exports.default = traverse;
13056
13056
  var _index = require_definitions();
13057
- function traverse2(node, handlers, state) {
13057
+ function traverse(node, handlers, state) {
13058
13058
  if (typeof handlers === "function") {
13059
13059
  handlers = {
13060
13060
  enter: handlers
@@ -14096,7 +14096,7 @@ import { createRequire } from "module";
14096
14096
 
14097
14097
  // src/scan.ts
14098
14098
  import { writeFile } from "fs/promises";
14099
- import { basename, join as join2, parse as parse2 } from "path";
14099
+ import { basename, join as join3, parse as parse2 } from "path";
14100
14100
 
14101
14101
  // src/scanners/npm/npm-scanner.ts
14102
14102
  import { readFile } from "fs/promises";
@@ -16750,6 +16750,15 @@ var ConsoleReporter = class {
16750
16750
  lines.push(
16751
16751
  ` Findings: direct_evidence=${directEvidence}, indirect_no_evidence=${indirectNoEvidence}, unsupported=${unsupported}, analysis_error=${analysisErrors}`
16752
16752
  );
16753
+ if (result.usageContext.ecosystemStatus.length > 0) {
16754
+ lines.push(" Analyzer status:");
16755
+ for (const status of result.usageContext.ecosystemStatus) {
16756
+ const note = status.note ? ` (${status.note})` : "";
16757
+ lines.push(
16758
+ ` ${status.ecosystem}: ${status.status} via ${status.analyzer}${note}`
16759
+ );
16760
+ }
16761
+ }
16753
16762
  if (result.usageContext.artifactPath) {
16754
16763
  lines.push(` Artifact: ${result.usageContext.artifactPath}`);
16755
16764
  }
@@ -17001,7 +17010,7 @@ var import_types = __toESM(require_lib3(), 1);
17001
17010
  import { readdir, readFile as readFile14 } from "fs/promises";
17002
17011
  import { join } from "path";
17003
17012
  import { parse } from "@babel/parser";
17004
- import traverse from "@babel/traverse";
17013
+ import traverseModule from "@babel/traverse";
17005
17014
  var JS_EXTENSIONS = /* @__PURE__ */ new Set([
17006
17015
  ".js",
17007
17016
  ".jsx",
@@ -17031,6 +17040,24 @@ var JsAstAnalyzer = class {
17031
17040
  return this.ecosystems.has(ecosystem);
17032
17041
  }
17033
17042
  async analyze(context) {
17043
+ const traverseFn = resolveTraverseFunction(traverseModule);
17044
+ if (!traverseFn) {
17045
+ return {
17046
+ packages: context.packages.map((pkg2) => ({
17047
+ packageName: pkg2.packageName,
17048
+ ecosystem: pkg2.ecosystem,
17049
+ status: "analysis_error",
17050
+ snippets: [],
17051
+ notes: "Failed to resolve @babel/traverse runtime export"
17052
+ })),
17053
+ errors: [{
17054
+ analyzer: this.name,
17055
+ ecosystem: context.ecosystem,
17056
+ error: "Failed to resolve @babel/traverse runtime export"
17057
+ }],
17058
+ snippetsProduced: 0
17059
+ };
17060
+ }
17034
17061
  const packageMap = this.buildPackageMaps(context.packages);
17035
17062
  const resultMap = /* @__PURE__ */ new Map();
17036
17063
  const snippetKeyMap = /* @__PURE__ */ new Map();
@@ -17094,13 +17121,13 @@ var JsAstAnalyzer = class {
17094
17121
  const matchCandidates = [];
17095
17122
  const matchSeen = /* @__PURE__ */ new Set();
17096
17123
  const symbolToPackage = /* @__PURE__ */ new Map();
17097
- const addMatch = (packageKey2, line, matchKind, calledSymbol, confidence = 0.8) => {
17098
- const candidateKey = `${packageKey2}:${line}:${matchKind}:${calledSymbol ?? ""}`;
17124
+ const addMatch = (packageKey3, line, matchKind, calledSymbol, confidence = 0.8) => {
17125
+ const candidateKey = `${packageKey3}:${line}:${matchKind}:${calledSymbol ?? ""}`;
17099
17126
  if (matchSeen.has(candidateKey)) return;
17100
17127
  matchSeen.add(candidateKey);
17101
- matchCandidates.push({ packageKey: packageKey2, line, matchKind, calledSymbol, confidence });
17128
+ matchCandidates.push({ packageKey: packageKey3, line, matchKind, calledSymbol, confidence });
17102
17129
  };
17103
- traverse(ast, {
17130
+ traverseFn(ast, {
17104
17131
  ImportDeclaration: (path14) => {
17105
17132
  const source = path14.node.source;
17106
17133
  if (!(0, import_types.isStringLiteral)(source)) return;
@@ -17222,6 +17249,18 @@ var JsAstAnalyzer = class {
17222
17249
  return `${ecosystem}::${packageName}`;
17223
17250
  }
17224
17251
  };
17252
+ function resolveTraverseFunction(moduleValue) {
17253
+ if (typeof moduleValue === "function") {
17254
+ return moduleValue;
17255
+ }
17256
+ if (typeof moduleValue === "object" && moduleValue !== null && "default" in moduleValue) {
17257
+ const candidate = moduleValue.default;
17258
+ if (typeof candidate === "function") {
17259
+ return candidate;
17260
+ }
17261
+ }
17262
+ return null;
17263
+ }
17225
17264
  async function collectSourceFiles(rootPath) {
17226
17265
  const files = [];
17227
17266
  async function walk(dirPath) {
@@ -17317,9 +17356,9 @@ function collectIdentifiers(pattern) {
17317
17356
  function resolveCallMatch(callee, symbolToPackage) {
17318
17357
  const normalized = unwrapExpression(callee);
17319
17358
  if ((0, import_types.isIdentifier)(normalized)) {
17320
- const packageKey2 = symbolToPackage.get(normalized.name);
17321
- if (!packageKey2) return null;
17322
- return { packageKey: packageKey2, calledSymbol: normalized.name };
17359
+ const packageKey3 = symbolToPackage.get(normalized.name);
17360
+ if (!packageKey3) return null;
17361
+ return { packageKey: packageKey3, calledSymbol: normalized.name };
17323
17362
  }
17324
17363
  if ((0, import_types.isMemberExpression)(normalized) || (0, import_types.isOptionalMemberExpression)(normalized)) {
17325
17364
  return resolveMemberCallMatch(normalized, symbolToPackage);
@@ -17336,13 +17375,13 @@ function unwrapExpression(expression) {
17336
17375
  function resolveMemberCallMatch(memberExpression, symbolToPackage) {
17337
17376
  const objectExpr = unwrapExpression(memberExpression.object);
17338
17377
  if (!(0, import_types.isIdentifier)(objectExpr)) return null;
17339
- const packageKey2 = symbolToPackage.get(objectExpr.name);
17340
- if (!packageKey2) return null;
17378
+ const packageKey3 = symbolToPackage.get(objectExpr.name);
17379
+ if (!packageKey3) return null;
17341
17380
  const propertyName = propertyNameOf(memberExpression);
17342
17381
  if (!propertyName) {
17343
- return { packageKey: packageKey2, calledSymbol: objectExpr.name };
17382
+ return { packageKey: packageKey3, calledSymbol: objectExpr.name };
17344
17383
  }
17345
- return { packageKey: packageKey2, calledSymbol: `${objectExpr.name}.${propertyName}` };
17384
+ return { packageKey: packageKey3, calledSymbol: `${objectExpr.name}.${propertyName}` };
17346
17385
  }
17347
17386
  function propertyNameOf(memberExpression) {
17348
17387
  if (memberExpression.computed) {
@@ -17359,34 +17398,1021 @@ function propertyNameOf(memberExpression) {
17359
17398
  return null;
17360
17399
  }
17361
17400
 
17362
- // src/context/analyzers/unsupported-analyzer.ts
17363
- var UnsupportedAnalyzer = class {
17364
- name;
17365
- ecosystems;
17366
- note;
17367
- constructor(name, ecosystems, note) {
17368
- this.name = name;
17369
- this.ecosystems = new Set(ecosystems);
17370
- this.note = note;
17401
+ // src/context/analyzers/shared.ts
17402
+ import { readdir as readdir2, readFile as readFile15 } from "fs/promises";
17403
+ import { join as join2 } from "path";
17404
+ var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
17405
+ ".git",
17406
+ ".hg",
17407
+ ".svn",
17408
+ "node_modules",
17409
+ "dist",
17410
+ "build",
17411
+ "coverage",
17412
+ ".next",
17413
+ ".nuxt",
17414
+ ".turbo",
17415
+ "vendor",
17416
+ ".venv",
17417
+ "venv",
17418
+ "target",
17419
+ "bin",
17420
+ "obj"
17421
+ ]);
17422
+ function packageKey(ecosystem, packageName) {
17423
+ return `${ecosystem}::${packageName}`;
17424
+ }
17425
+ function initState(packages) {
17426
+ const resultMap = /* @__PURE__ */ new Map();
17427
+ const snippetKeyMap = /* @__PURE__ */ new Map();
17428
+ for (const pkg2 of packages) {
17429
+ const key = packageKey(pkg2.ecosystem, pkg2.packageName);
17430
+ resultMap.set(key, {
17431
+ packageName: pkg2.packageName,
17432
+ ecosystem: pkg2.ecosystem,
17433
+ status: "indirect_no_evidence",
17434
+ snippets: []
17435
+ });
17436
+ snippetKeyMap.set(key, /* @__PURE__ */ new Set());
17437
+ }
17438
+ return {
17439
+ resultMap,
17440
+ snippetKeyMap,
17441
+ errors: [],
17442
+ snippetsProduced: 0
17443
+ };
17444
+ }
17445
+ function errorResultFromMessage(context, analyzerName, message, notes) {
17446
+ return {
17447
+ packages: context.packages.map((pkg2) => ({
17448
+ packageName: pkg2.packageName,
17449
+ ecosystem: pkg2.ecosystem,
17450
+ status: "analysis_error",
17451
+ snippets: [],
17452
+ notes
17453
+ })),
17454
+ errors: [{ analyzer: analyzerName, ecosystem: context.ecosystem, error: message }],
17455
+ snippetsProduced: 0
17456
+ };
17457
+ }
17458
+ function toAnalyzerResult(state) {
17459
+ for (const result of state.resultMap.values()) {
17460
+ result.snippets = dedupeSnippets(result.snippets);
17461
+ if (result.snippets.length > 0) {
17462
+ result.status = "direct_evidence";
17463
+ }
17371
17464
  }
17465
+ return {
17466
+ packages: Array.from(state.resultMap.values()),
17467
+ errors: state.errors,
17468
+ snippetsProduced: state.snippetsProduced
17469
+ };
17470
+ }
17471
+ async function collectSourceFiles2(rootPath, extensions, ignoredDirs = DEFAULT_IGNORED_DIRS) {
17472
+ const files = [];
17473
+ async function walk(dirPath) {
17474
+ const entries = await readdir2(dirPath, { withFileTypes: true });
17475
+ for (const entry of entries) {
17476
+ const fullPath = join2(dirPath, entry.name);
17477
+ if (entry.isDirectory()) {
17478
+ if (ignoredDirs.has(entry.name)) continue;
17479
+ await walk(fullPath);
17480
+ continue;
17481
+ }
17482
+ if (!entry.isFile()) continue;
17483
+ if (!extensions.has(extensionOf2(entry.name))) continue;
17484
+ files.push(fullPath);
17485
+ }
17486
+ }
17487
+ await walk(rootPath);
17488
+ return files;
17489
+ }
17490
+ async function readSourceFile(analyzerName, ecosystem, filePath, errors) {
17491
+ try {
17492
+ return await readFile15(filePath, "utf-8");
17493
+ } catch (err) {
17494
+ errors.push({
17495
+ analyzer: analyzerName,
17496
+ ecosystem,
17497
+ error: `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
17498
+ });
17499
+ return null;
17500
+ }
17501
+ }
17502
+ function addCandidate(context, state, filePath, sourceText, candidate) {
17503
+ if (state.snippetsProduced >= context.maxSnippetsTotal) return;
17504
+ const packageResult = state.resultMap.get(candidate.packageKey);
17505
+ if (!packageResult) return;
17506
+ if (packageResult.snippets.length >= context.maxSnippetsPerPackage) return;
17507
+ const snippet = buildSnippet({
17508
+ projectPath: context.projectPath,
17509
+ filePath,
17510
+ sourceText,
17511
+ line: candidate.line,
17512
+ numContextLines: context.numContextLines,
17513
+ matchKind: candidate.matchKind,
17514
+ calledSymbol: candidate.calledSymbol,
17515
+ confidence: candidate.confidence ?? 0.8
17516
+ });
17517
+ const dedupeKey = `${snippet.filePath}:${snippet.startLine}:${snippet.endLine}:${snippet.matchKind}:${snippet.calledSymbol ?? ""}`;
17518
+ const packageSnippetKeys = state.snippetKeyMap.get(candidate.packageKey);
17519
+ if (!packageSnippetKeys || packageSnippetKeys.has(dedupeKey)) return;
17520
+ packageSnippetKeys.add(dedupeKey);
17521
+ packageResult.snippets.push(snippet);
17522
+ state.snippetsProduced += 1;
17523
+ }
17524
+ function extensionOf2(fileName) {
17525
+ const index = fileName.lastIndexOf(".");
17526
+ return index === -1 ? "" : fileName.slice(index).toLowerCase();
17527
+ }
17528
+ function basePackageName(name) {
17529
+ const slash = name.includes("/") ? name.split("/").at(-1) ?? name : name;
17530
+ const colon = slash.includes(":") ? slash.split(":").at(-1) ?? slash : slash;
17531
+ return colon;
17532
+ }
17533
+ function toIdentifierToken(value) {
17534
+ return value.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
17535
+ }
17536
+ function uniqueTokens(values) {
17537
+ const result = [];
17538
+ const seen = /* @__PURE__ */ new Set();
17539
+ for (const value of values) {
17540
+ const trimmed = value.trim();
17541
+ if (!trimmed) continue;
17542
+ const key = trimmed.toLowerCase();
17543
+ if (seen.has(key)) continue;
17544
+ seen.add(key);
17545
+ result.push(trimmed);
17546
+ }
17547
+ return result;
17548
+ }
17549
+
17550
+ // src/context/analyzers/python-ast-analyzer.ts
17551
+ var PYTHON_EXTENSIONS = /* @__PURE__ */ new Set([".py"]);
17552
+ var PythonAstAnalyzer = class {
17553
+ name = "python-ast-analyzer";
17554
+ ecosystems = /* @__PURE__ */ new Set(["pip", "poetry", "uv"]);
17372
17555
  supports(ecosystem) {
17373
17556
  return this.ecosystems.has(ecosystem);
17374
17557
  }
17375
17558
  async analyze(context) {
17376
- const packages = context.packages.map((pkg2) => ({
17377
- packageName: pkg2.packageName,
17378
- ecosystem: pkg2.ecosystem,
17379
- status: "unsupported",
17380
- snippets: [],
17381
- notes: this.note
17382
- }));
17559
+ const state = initState(context.packages);
17560
+ const packagePatterns = context.packages.map(
17561
+ (pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
17562
+ );
17563
+ let files;
17564
+ try {
17565
+ files = await collectSourceFiles2(context.projectPath, PYTHON_EXTENSIONS);
17566
+ } catch (err) {
17567
+ return errorResultFromMessage(
17568
+ context,
17569
+ this.name,
17570
+ err instanceof Error ? err.message : String(err),
17571
+ "Failed to enumerate Python source files"
17572
+ );
17573
+ }
17574
+ for (const filePath of files) {
17575
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
17576
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
17577
+ if (sourceText === null) continue;
17578
+ const aliasToPackage = /* @__PURE__ */ new Map();
17579
+ const lines = sourceText.split(/\r?\n/);
17580
+ for (let idx = 0; idx < lines.length; idx++) {
17581
+ const line = lines[idx];
17582
+ const trimmed = stripInlineComment(line).trim();
17583
+ const lineNumber = idx + 1;
17584
+ if (!trimmed) continue;
17585
+ const importMatch = trimmed.match(/^import\s+(.+)$/);
17586
+ if (importMatch) {
17587
+ const segments = importMatch[1].split(",").map((part) => part.trim()).filter(Boolean);
17588
+ for (const segment of segments) {
17589
+ const parsed = segment.match(/^([A-Za-z_][A-Za-z0-9_\.]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$/);
17590
+ if (!parsed) continue;
17591
+ const moduleName = parsed[1];
17592
+ const alias = parsed[2] ?? moduleName.split(".").at(0) ?? moduleName;
17593
+ this.addImportForModule(
17594
+ context,
17595
+ state,
17596
+ filePath,
17597
+ sourceText,
17598
+ packagePatterns,
17599
+ aliasToPackage,
17600
+ moduleName,
17601
+ alias,
17602
+ lineNumber
17603
+ );
17604
+ }
17605
+ continue;
17606
+ }
17607
+ const fromMatch = trimmed.match(
17608
+ /^from\s+([A-Za-z_][A-Za-z0-9_\.]*)\s+import\s+(.+)$/
17609
+ );
17610
+ if (fromMatch) {
17611
+ const moduleName = fromMatch[1];
17612
+ const imported = fromMatch[2].split(",").map((part) => part.trim()).filter(Boolean);
17613
+ for (const part of imported) {
17614
+ const parsed = part.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$/);
17615
+ if (!parsed) continue;
17616
+ const name = parsed[1];
17617
+ const alias = parsed[2] ?? name;
17618
+ this.addImportForModule(
17619
+ context,
17620
+ state,
17621
+ filePath,
17622
+ sourceText,
17623
+ packagePatterns,
17624
+ aliasToPackage,
17625
+ moduleName,
17626
+ alias,
17627
+ lineNumber
17628
+ );
17629
+ }
17630
+ continue;
17631
+ }
17632
+ for (const [alias, pkgKey] of aliasToPackage.entries()) {
17633
+ const escapedAlias = escapeRegex(alias);
17634
+ const directCall = new RegExp(`\\b${escapedAlias}\\s*\\(`);
17635
+ if (directCall.test(trimmed)) {
17636
+ addCandidate(context, state, filePath, sourceText, {
17637
+ packageKey: pkgKey,
17638
+ line: lineNumber,
17639
+ matchKind: "call",
17640
+ calledSymbol: alias,
17641
+ confidence: 0.78
17642
+ });
17643
+ }
17644
+ const memberCall = new RegExp(`\\b${escapedAlias}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`);
17645
+ const match = trimmed.match(memberCall);
17646
+ if (match) {
17647
+ addCandidate(context, state, filePath, sourceText, {
17648
+ packageKey: pkgKey,
17649
+ line: lineNumber,
17650
+ matchKind: "call",
17651
+ calledSymbol: `${alias}.${match[1]}`,
17652
+ confidence: 0.8
17653
+ });
17654
+ }
17655
+ }
17656
+ }
17657
+ }
17658
+ return toAnalyzerResult(state);
17659
+ }
17660
+ addImportForModule(context, state, filePath, sourceText, packagePatterns, aliasToPackage, moduleName, alias, lineNumber) {
17661
+ const normalizedModule = moduleName.toLowerCase();
17662
+ for (const pkg2 of packagePatterns) {
17663
+ const matched = pkg2.modules.some(
17664
+ (candidate) => normalizedModule === candidate || normalizedModule.startsWith(`${candidate}.`)
17665
+ );
17666
+ if (!matched) continue;
17667
+ aliasToPackage.set(alias, pkg2.key);
17668
+ aliasToPackage.set(moduleName.split(".").at(0) ?? moduleName, pkg2.key);
17669
+ addCandidate(context, state, filePath, sourceText, {
17670
+ packageKey: pkg2.key,
17671
+ line: lineNumber,
17672
+ matchKind: "import",
17673
+ confidence: 0.95
17674
+ });
17675
+ }
17676
+ }
17677
+ patternForPackage(packageName, ecosystem) {
17678
+ const normalized = toIdentifierToken(packageName).replace(/_/g, "-");
17679
+ const base = toIdentifierToken(basePackageName(packageName));
17680
+ const packageModules = [
17681
+ normalized.replace(/-/g, "_"),
17682
+ base.replace(/-/g, "_"),
17683
+ base.replace(/_/g, "")
17684
+ ];
17685
+ if (normalized === "pyyaml" || base === "pyyaml") {
17686
+ packageModules.push("yaml");
17687
+ }
17383
17688
  return {
17384
- packages,
17385
- errors: [],
17386
- snippetsProduced: 0
17689
+ key: packageKey(ecosystem, packageName),
17690
+ modules: uniqueTokens(packageModules.map((v) => v.toLowerCase()))
17387
17691
  };
17388
17692
  }
17389
17693
  };
17694
+ function stripInlineComment(line) {
17695
+ const hashIndex = line.indexOf("#");
17696
+ return hashIndex === -1 ? line : line.slice(0, hashIndex);
17697
+ }
17698
+ function escapeRegex(value) {
17699
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17700
+ }
17701
+
17702
+ // src/context/analyzers/java-ast-analyzer.ts
17703
+ var JAVA_EXTENSIONS = /* @__PURE__ */ new Set([".java"]);
17704
+ var JavaAstAnalyzer = class {
17705
+ name = "java-ast-analyzer";
17706
+ ecosystems = /* @__PURE__ */ new Set(["maven"]);
17707
+ supports(ecosystem) {
17708
+ return this.ecosystems.has(ecosystem);
17709
+ }
17710
+ async analyze(context) {
17711
+ const state = initState(context.packages);
17712
+ const packagePatterns = context.packages.map(
17713
+ (pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
17714
+ );
17715
+ let files;
17716
+ try {
17717
+ files = await collectSourceFiles2(context.projectPath, JAVA_EXTENSIONS);
17718
+ } catch (err) {
17719
+ return errorResultFromMessage(
17720
+ context,
17721
+ this.name,
17722
+ err instanceof Error ? err.message : String(err),
17723
+ "Failed to enumerate Java source files"
17724
+ );
17725
+ }
17726
+ for (const filePath of files) {
17727
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
17728
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
17729
+ if (sourceText === null) continue;
17730
+ const lines = sourceText.split(/\r?\n/);
17731
+ const symbolToPackage = /* @__PURE__ */ new Map();
17732
+ for (let idx = 0; idx < lines.length; idx++) {
17733
+ const line = stripLineComment(lines[idx]).trim();
17734
+ const lineNumber = idx + 1;
17735
+ if (!line) continue;
17736
+ const importMatch = line.match(/^import\s+(?:static\s+)?([A-Za-z0-9_.*]+)\s*;/);
17737
+ if (importMatch) {
17738
+ const importPath = importMatch[1].replace(/\.\*$/, "");
17739
+ const simpleName = importPath.split(".").at(-1) ?? importPath;
17740
+ for (const pkg2 of packagePatterns) {
17741
+ if (!pkg2.candidates.some((candidate) => importPath.startsWith(candidate))) continue;
17742
+ addCandidate(context, state, filePath, sourceText, {
17743
+ packageKey: pkg2.key,
17744
+ line: lineNumber,
17745
+ matchKind: "import",
17746
+ confidence: 0.94
17747
+ });
17748
+ if (simpleName && /^[A-Za-z_][A-Za-z0-9_]*$/.test(simpleName)) {
17749
+ symbolToPackage.set(simpleName, pkg2.key);
17750
+ }
17751
+ }
17752
+ continue;
17753
+ }
17754
+ const varDecl = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)/);
17755
+ if (varDecl) {
17756
+ const typeName = varDecl[1];
17757
+ const variableName = varDecl[2];
17758
+ const pkgKey2 = symbolToPackage.get(typeName);
17759
+ if (pkgKey2) {
17760
+ symbolToPackage.set(variableName, pkgKey2);
17761
+ }
17762
+ }
17763
+ const callMatch = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
17764
+ if (!callMatch) continue;
17765
+ const lhs = callMatch[1];
17766
+ const member = callMatch[2];
17767
+ const pkgKey = symbolToPackage.get(lhs);
17768
+ if (!pkgKey) continue;
17769
+ addCandidate(context, state, filePath, sourceText, {
17770
+ packageKey: pkgKey,
17771
+ line: lineNumber,
17772
+ matchKind: "call",
17773
+ calledSymbol: `${lhs}.${member}`,
17774
+ confidence: 0.8
17775
+ });
17776
+ }
17777
+ }
17778
+ return toAnalyzerResult(state);
17779
+ }
17780
+ patternForPackage(packageName, ecosystem) {
17781
+ const [groupIdRaw, artifactIdRaw] = packageName.split(":");
17782
+ const groupId = (groupIdRaw ?? "").trim();
17783
+ const artifactId = (artifactIdRaw ?? "").trim();
17784
+ const candidates = uniqueTokens([
17785
+ groupId,
17786
+ groupId && artifactId ? `${groupId}.${artifactId.replace(/-/g, ".")}` : "",
17787
+ artifactId ? artifactId.replace(/-/g, ".") : "",
17788
+ artifactId ? toIdentifierToken(artifactId).replace(/_/g, ".") : ""
17789
+ ]);
17790
+ return {
17791
+ key: packageKey(ecosystem, packageName),
17792
+ candidates
17793
+ };
17794
+ }
17795
+ };
17796
+ function stripLineComment(line) {
17797
+ const idx = line.indexOf("//");
17798
+ return idx === -1 ? line : line.slice(0, idx);
17799
+ }
17800
+
17801
+ // src/context/analyzers/dotnet-ast-analyzer.ts
17802
+ var CSHARP_EXTENSIONS = /* @__PURE__ */ new Set([".cs"]);
17803
+ var DotnetAstAnalyzer = class {
17804
+ name = "dotnet-ast-analyzer";
17805
+ ecosystems = /* @__PURE__ */ new Set(["nuget"]);
17806
+ supports(ecosystem) {
17807
+ return this.ecosystems.has(ecosystem);
17808
+ }
17809
+ async analyze(context) {
17810
+ const state = initState(context.packages);
17811
+ const packagePatterns = context.packages.map(
17812
+ (pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
17813
+ );
17814
+ let files;
17815
+ try {
17816
+ files = await collectSourceFiles2(context.projectPath, CSHARP_EXTENSIONS);
17817
+ } catch (err) {
17818
+ return errorResultFromMessage(
17819
+ context,
17820
+ this.name,
17821
+ err instanceof Error ? err.message : String(err),
17822
+ "Failed to enumerate C# source files"
17823
+ );
17824
+ }
17825
+ for (const filePath of files) {
17826
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
17827
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
17828
+ if (sourceText === null) continue;
17829
+ const lines = sourceText.split(/\r?\n/);
17830
+ const symbolToPackage = /* @__PURE__ */ new Map();
17831
+ for (let idx = 0; idx < lines.length; idx++) {
17832
+ const line = stripLineComment2(lines[idx]).trim();
17833
+ const lineNumber = idx + 1;
17834
+ if (!line) continue;
17835
+ const aliasUsing = line.match(/^using\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([A-Za-z0-9_.]+)\s*;/);
17836
+ if (aliasUsing) {
17837
+ const alias = aliasUsing[1];
17838
+ const targetNamespace = aliasUsing[2];
17839
+ for (const pkg2 of packagePatterns) {
17840
+ if (!pkg2.namespaceCandidates.some((candidate) => targetNamespace.startsWith(candidate))) continue;
17841
+ symbolToPackage.set(alias, pkg2.key);
17842
+ addCandidate(context, state, filePath, sourceText, {
17843
+ packageKey: pkg2.key,
17844
+ line: lineNumber,
17845
+ matchKind: "import",
17846
+ confidence: 0.93
17847
+ });
17848
+ }
17849
+ continue;
17850
+ }
17851
+ const usingMatch = line.match(/^using\s+([A-Za-z0-9_.]+)\s*;/);
17852
+ if (usingMatch) {
17853
+ const namespaceName = usingMatch[1];
17854
+ const tailSymbol = namespaceName.split(".").at(-1) ?? namespaceName;
17855
+ for (const pkg2 of packagePatterns) {
17856
+ if (!pkg2.namespaceCandidates.some((candidate) => namespaceName.startsWith(candidate))) continue;
17857
+ symbolToPackage.set(tailSymbol, pkg2.key);
17858
+ addCandidate(context, state, filePath, sourceText, {
17859
+ packageKey: pkg2.key,
17860
+ line: lineNumber,
17861
+ matchKind: "import",
17862
+ confidence: 0.93
17863
+ });
17864
+ }
17865
+ continue;
17866
+ }
17867
+ const varDecl = line.match(/^([A-Za-z_][A-Za-z0-9_.]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)/);
17868
+ if (varDecl) {
17869
+ const typeName = varDecl[1].split(".").at(-1) ?? varDecl[1];
17870
+ const variableName = varDecl[2];
17871
+ const pkgKey = symbolToPackage.get(typeName);
17872
+ if (pkgKey) {
17873
+ symbolToPackage.set(variableName, pkgKey);
17874
+ }
17875
+ }
17876
+ const memberCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
17877
+ if (memberCall) {
17878
+ const lhs = memberCall[1];
17879
+ const method = memberCall[2];
17880
+ const pkgKey = symbolToPackage.get(lhs) ?? this.findSymbolCandidatePackage(lhs, packagePatterns);
17881
+ if (pkgKey) {
17882
+ addCandidate(context, state, filePath, sourceText, {
17883
+ packageKey: pkgKey,
17884
+ line: lineNumber,
17885
+ matchKind: "call",
17886
+ calledSymbol: `${lhs}.${method}`,
17887
+ confidence: 0.79
17888
+ });
17889
+ }
17890
+ }
17891
+ }
17892
+ }
17893
+ return toAnalyzerResult(state);
17894
+ }
17895
+ patternForPackage(packageName, ecosystem) {
17896
+ const dotted = packageName.replace(/-/g, ".");
17897
+ const segments = dotted.split(".");
17898
+ const symbolCandidates = uniqueTokens([
17899
+ segments.at(-1) ?? "",
17900
+ segments.at(-2) ?? "",
17901
+ packageName.split(".").at(-1) ?? ""
17902
+ ]);
17903
+ const namespaceCandidates = uniqueTokens([
17904
+ dotted,
17905
+ segments.slice(0, -1).join("."),
17906
+ segments.slice(0, Math.min(2, segments.length)).join(".")
17907
+ ]);
17908
+ return {
17909
+ key: packageKey(ecosystem, packageName),
17910
+ namespaceCandidates,
17911
+ symbolCandidates
17912
+ };
17913
+ }
17914
+ findSymbolCandidatePackage(symbol, packagePatterns) {
17915
+ for (const pkg2 of packagePatterns) {
17916
+ if (pkg2.symbolCandidates.includes(symbol)) {
17917
+ return pkg2.key;
17918
+ }
17919
+ }
17920
+ return null;
17921
+ }
17922
+ };
17923
+ function stripLineComment2(line) {
17924
+ const idx = line.indexOf("//");
17925
+ return idx === -1 ? line : line.slice(0, idx);
17926
+ }
17927
+
17928
+ // src/context/analyzers/rust-ast-analyzer.ts
17929
+ var RUST_EXTENSIONS = /* @__PURE__ */ new Set([".rs"]);
17930
+ var RustAstAnalyzer = class {
17931
+ name = "rust-ast-analyzer";
17932
+ ecosystems = /* @__PURE__ */ new Set(["cargo"]);
17933
+ supports(ecosystem) {
17934
+ return this.ecosystems.has(ecosystem);
17935
+ }
17936
+ async analyze(context) {
17937
+ const state = initState(context.packages);
17938
+ const packagePatterns = context.packages.map(
17939
+ (pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
17940
+ );
17941
+ let files;
17942
+ try {
17943
+ files = await collectSourceFiles2(context.projectPath, RUST_EXTENSIONS);
17944
+ } catch (err) {
17945
+ return errorResultFromMessage(
17946
+ context,
17947
+ this.name,
17948
+ err instanceof Error ? err.message : String(err),
17949
+ "Failed to enumerate Rust source files"
17950
+ );
17951
+ }
17952
+ for (const filePath of files) {
17953
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
17954
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
17955
+ if (sourceText === null) continue;
17956
+ const lines = sourceText.split(/\r?\n/);
17957
+ const symbolToPackage = /* @__PURE__ */ new Map();
17958
+ for (let idx = 0; idx < lines.length; idx++) {
17959
+ const line = stripLineComment3(lines[idx]).trim();
17960
+ const lineNumber = idx + 1;
17961
+ if (!line) continue;
17962
+ const useMatch = line.match(/^use\s+([A-Za-z_][A-Za-z0-9_:]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/);
17963
+ if (useMatch) {
17964
+ const pathExpr = useMatch[1];
17965
+ const alias = useMatch[2] ?? pathExpr.split("::").at(0) ?? pathExpr;
17966
+ const crate = pathExpr.split("::").at(0) ?? pathExpr;
17967
+ for (const pkg2 of packagePatterns) {
17968
+ if (!pkg2.crateNames.includes(crate)) continue;
17969
+ symbolToPackage.set(alias, pkg2.key);
17970
+ symbolToPackage.set(crate, pkg2.key);
17971
+ addCandidate(context, state, filePath, sourceText, {
17972
+ packageKey: pkg2.key,
17973
+ line: lineNumber,
17974
+ matchKind: "import",
17975
+ confidence: 0.94
17976
+ });
17977
+ }
17978
+ continue;
17979
+ }
17980
+ const externMatch = line.match(/^extern\s+crate\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/);
17981
+ if (externMatch) {
17982
+ const crate = externMatch[1];
17983
+ const alias = externMatch[2] ?? crate;
17984
+ for (const pkg2 of packagePatterns) {
17985
+ if (!pkg2.crateNames.includes(crate)) continue;
17986
+ symbolToPackage.set(alias, pkg2.key);
17987
+ symbolToPackage.set(crate, pkg2.key);
17988
+ addCandidate(context, state, filePath, sourceText, {
17989
+ packageKey: pkg2.key,
17990
+ line: lineNumber,
17991
+ matchKind: "import",
17992
+ confidence: 0.94
17993
+ });
17994
+ }
17995
+ continue;
17996
+ }
17997
+ const scopedCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
17998
+ if (scopedCall) {
17999
+ const lhs = scopedCall[1];
18000
+ const func = scopedCall[2];
18001
+ const pkgKey = symbolToPackage.get(lhs);
18002
+ if (pkgKey) {
18003
+ addCandidate(context, state, filePath, sourceText, {
18004
+ packageKey: pkgKey,
18005
+ line: lineNumber,
18006
+ matchKind: "call",
18007
+ calledSymbol: `${lhs}::${func}`,
18008
+ confidence: 0.8
18009
+ });
18010
+ }
18011
+ }
18012
+ const methodCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18013
+ if (methodCall) {
18014
+ const lhs = methodCall[1];
18015
+ const method = methodCall[2];
18016
+ const pkgKey = symbolToPackage.get(lhs);
18017
+ if (pkgKey) {
18018
+ addCandidate(context, state, filePath, sourceText, {
18019
+ packageKey: pkgKey,
18020
+ line: lineNumber,
18021
+ matchKind: "call",
18022
+ calledSymbol: `${lhs}.${method}`,
18023
+ confidence: 0.78
18024
+ });
18025
+ }
18026
+ }
18027
+ }
18028
+ }
18029
+ return toAnalyzerResult(state);
18030
+ }
18031
+ patternForPackage(packageName, ecosystem) {
18032
+ const crateName = packageName.replace(/-/g, "_");
18033
+ return {
18034
+ key: packageKey(ecosystem, packageName),
18035
+ crateNames: uniqueTokens([crateName, packageName])
18036
+ };
18037
+ }
18038
+ };
18039
+ function stripLineComment3(line) {
18040
+ const idx = line.indexOf("//");
18041
+ return idx === -1 ? line : line.slice(0, idx);
18042
+ }
18043
+
18044
+ // src/context/analyzers/go-ast-analyzer.ts
18045
+ var GO_EXTENSIONS = /* @__PURE__ */ new Set([".go"]);
18046
+ var GoAstAnalyzer = class {
18047
+ name = "go-ast-analyzer";
18048
+ ecosystems = /* @__PURE__ */ new Set(["go"]);
18049
+ supports(ecosystem) {
18050
+ return this.ecosystems.has(ecosystem);
18051
+ }
18052
+ async analyze(context) {
18053
+ const state = initState(context.packages);
18054
+ const packagePatterns = context.packages.map((pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem));
18055
+ let files;
18056
+ try {
18057
+ files = await collectSourceFiles2(context.projectPath, GO_EXTENSIONS);
18058
+ } catch (err) {
18059
+ return errorResultFromMessage(
18060
+ context,
18061
+ this.name,
18062
+ err instanceof Error ? err.message : String(err),
18063
+ "Failed to enumerate Go source files"
18064
+ );
18065
+ }
18066
+ for (const filePath of files) {
18067
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18068
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18069
+ if (sourceText === null) continue;
18070
+ const aliasesByPackage = /* @__PURE__ */ new Map();
18071
+ for (const pkg2 of packagePatterns) {
18072
+ aliasesByPackage.set(pkg2.key, new Set(pkg2.aliases));
18073
+ }
18074
+ const lines = sourceText.split(/\r?\n/);
18075
+ let inImportBlock = false;
18076
+ for (let idx = 0; idx < lines.length; idx++) {
18077
+ const lineNumber = idx + 1;
18078
+ const line = lines[idx];
18079
+ const trimmed = line.trim();
18080
+ if (trimmed.startsWith("import (")) {
18081
+ inImportBlock = true;
18082
+ continue;
18083
+ }
18084
+ if (inImportBlock && trimmed === ")") {
18085
+ inImportBlock = false;
18086
+ continue;
18087
+ }
18088
+ const match = this.extractImport(trimmed, inImportBlock);
18089
+ if (match) {
18090
+ for (const pkg2 of packagePatterns) {
18091
+ if (match.importPath !== pkg2.packageName) continue;
18092
+ const alias = match.alias && match.alias !== "_" && match.alias !== "." ? toIdentifierToken(match.alias) : pkg2.aliases[0];
18093
+ if (alias) {
18094
+ aliasesByPackage.get(pkg2.key)?.add(alias);
18095
+ }
18096
+ addCandidate(context, state, filePath, sourceText, {
18097
+ packageKey: pkg2.key,
18098
+ line: lineNumber,
18099
+ matchKind: "import",
18100
+ confidence: 0.95
18101
+ });
18102
+ }
18103
+ }
18104
+ for (const pkg2 of packagePatterns) {
18105
+ const aliases = aliasesByPackage.get(pkg2.key);
18106
+ if (!aliases) continue;
18107
+ for (const alias of aliases) {
18108
+ if (!alias) continue;
18109
+ const escapedAlias = escapeRegex2(alias);
18110
+ const callRegex = new RegExp(`\\b${escapedAlias}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`);
18111
+ const callMatch = line.match(callRegex);
18112
+ if (!callMatch) continue;
18113
+ addCandidate(context, state, filePath, sourceText, {
18114
+ packageKey: pkg2.key,
18115
+ line: lineNumber,
18116
+ matchKind: "call",
18117
+ calledSymbol: `${alias}.${callMatch[1]}`,
18118
+ confidence: 0.8
18119
+ });
18120
+ }
18121
+ }
18122
+ }
18123
+ }
18124
+ return toAnalyzerResult(state);
18125
+ }
18126
+ patternForPackage(packageName, ecosystem) {
18127
+ const baseName = basePackageName(packageName);
18128
+ const identifierBase = toIdentifierToken(baseName.replace(/^v[0-9]+$/, ""));
18129
+ const shortened = identifierBase.replace(/go$/i, "") || identifierBase;
18130
+ return {
18131
+ key: packageKey(ecosystem, packageName),
18132
+ packageName,
18133
+ aliases: uniqueTokens([identifierBase, shortened])
18134
+ };
18135
+ }
18136
+ extractImport(trimmedLine, inImportBlock) {
18137
+ if (!inImportBlock && !trimmedLine.startsWith("import ")) return null;
18138
+ if (inImportBlock) {
18139
+ const blockMatch = trimmedLine.match(/^(?:(\.|_|[A-Za-z_][A-Za-z0-9_]*)\s+)?\"([^\"]+)\"/);
18140
+ if (!blockMatch) return null;
18141
+ return {
18142
+ alias: blockMatch[1] ?? null,
18143
+ importPath: blockMatch[2]
18144
+ };
18145
+ }
18146
+ const singleMatch = trimmedLine.match(/^import\s+(?:(\.|_|[A-Za-z_][A-Za-z0-9_]*)\s+)?\"([^\"]+)\"/);
18147
+ if (!singleMatch) return null;
18148
+ return {
18149
+ alias: singleMatch[1] ?? null,
18150
+ importPath: singleMatch[2]
18151
+ };
18152
+ }
18153
+ };
18154
+ function escapeRegex2(value) {
18155
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18156
+ }
18157
+
18158
+ // src/context/analyzers/ruby-ast-analyzer.ts
18159
+ var RUBY_EXTENSIONS = /* @__PURE__ */ new Set([".rb"]);
18160
+ var RubyAstAnalyzer = class {
18161
+ name = "ruby-ast-analyzer";
18162
+ ecosystems = /* @__PURE__ */ new Set(["ruby"]);
18163
+ supports(ecosystem) {
18164
+ return this.ecosystems.has(ecosystem);
18165
+ }
18166
+ async analyze(context) {
18167
+ const state = initState(context.packages);
18168
+ const packagePatterns = context.packages.map(
18169
+ (pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
18170
+ );
18171
+ let files;
18172
+ try {
18173
+ files = await collectSourceFiles2(context.projectPath, RUBY_EXTENSIONS);
18174
+ } catch (err) {
18175
+ return errorResultFromMessage(
18176
+ context,
18177
+ this.name,
18178
+ err instanceof Error ? err.message : String(err),
18179
+ "Failed to enumerate Ruby source files"
18180
+ );
18181
+ }
18182
+ for (const filePath of files) {
18183
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18184
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18185
+ if (sourceText === null) continue;
18186
+ const lines = sourceText.split(/\r?\n/);
18187
+ const symbolToPackage = /* @__PURE__ */ new Map();
18188
+ for (let idx = 0; idx < lines.length; idx++) {
18189
+ const line = stripInlineComment2(lines[idx]).trim();
18190
+ const lineNumber = idx + 1;
18191
+ if (!line) continue;
18192
+ const requireMatch = line.match(/^require(?:_relative)?\s+['"]([^'"]+)['"]/);
18193
+ if (requireMatch) {
18194
+ const requiredPath = requireMatch[1];
18195
+ for (const pkg2 of packagePatterns) {
18196
+ if (!pkg2.requireCandidates.some((candidate) => requiredPath.includes(candidate))) continue;
18197
+ addCandidate(context, state, filePath, sourceText, {
18198
+ packageKey: pkg2.key,
18199
+ line: lineNumber,
18200
+ matchKind: "require",
18201
+ confidence: 0.93
18202
+ });
18203
+ for (const constant of pkg2.constantCandidates) {
18204
+ symbolToPackage.set(constant, pkg2.key);
18205
+ }
18206
+ }
18207
+ continue;
18208
+ }
18209
+ const includeMatch = line.match(/^include\s+([A-Za-z_][A-Za-z0-9_:]*)/);
18210
+ if (includeMatch) {
18211
+ const moduleName = includeMatch[1];
18212
+ for (const pkg2 of packagePatterns) {
18213
+ if (!pkg2.constantCandidates.some((candidate) => moduleName.startsWith(candidate))) continue;
18214
+ symbolToPackage.set(moduleName.split("::").at(0) ?? moduleName, pkg2.key);
18215
+ addCandidate(context, state, filePath, sourceText, {
18216
+ packageKey: pkg2.key,
18217
+ line: lineNumber,
18218
+ matchKind: "import",
18219
+ confidence: 0.9
18220
+ });
18221
+ }
18222
+ continue;
18223
+ }
18224
+ const namespacedCall = line.match(/\b([A-Z][A-Za-z0-9_:]*)\s*(?:\.|::)\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18225
+ if (namespacedCall) {
18226
+ const lhs = namespacedCall[1].split("::").at(0) ?? namespacedCall[1];
18227
+ const method = namespacedCall[2];
18228
+ const pkgKey = symbolToPackage.get(lhs) ?? this.findConstantCandidatePackage(lhs, packagePatterns);
18229
+ if (pkgKey) {
18230
+ addCandidate(context, state, filePath, sourceText, {
18231
+ packageKey: pkgKey,
18232
+ line: lineNumber,
18233
+ matchKind: "call",
18234
+ calledSymbol: `${lhs}.${method}`,
18235
+ confidence: 0.78
18236
+ });
18237
+ }
18238
+ }
18239
+ }
18240
+ }
18241
+ return toAnalyzerResult(state);
18242
+ }
18243
+ patternForPackage(packageName, ecosystem) {
18244
+ const base = basePackageName(packageName);
18245
+ const normalized = base.toLowerCase();
18246
+ const requireCandidates = uniqueTokens([
18247
+ normalized,
18248
+ normalized.replace(/-/g, "/"),
18249
+ normalized.replace(/-/g, "_")
18250
+ ]);
18251
+ const constantParts = normalized.replace(/[^a-z0-9_/-]/g, "").split(/[\/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1));
18252
+ const constant = constantParts.join("::");
18253
+ const collapsedConstant = constantParts.join("");
18254
+ return {
18255
+ key: packageKey(ecosystem, packageName),
18256
+ requireCandidates,
18257
+ constantCandidates: uniqueTokens([constant, collapsedConstant, constantParts.at(0) ?? ""])
18258
+ };
18259
+ }
18260
+ findConstantCandidatePackage(constant, packagePatterns) {
18261
+ for (const pkg2 of packagePatterns) {
18262
+ if (pkg2.constantCandidates.includes(constant)) {
18263
+ return pkg2.key;
18264
+ }
18265
+ }
18266
+ return null;
18267
+ }
18268
+ };
18269
+ function stripInlineComment2(line) {
18270
+ const idx = line.indexOf("#");
18271
+ return idx === -1 ? line : line.slice(0, idx);
18272
+ }
18273
+
18274
+ // src/context/analyzers/php-ast-analyzer.ts
18275
+ var PHP_EXTENSIONS = /* @__PURE__ */ new Set([".php"]);
18276
+ var PhpAstAnalyzer = class {
18277
+ name = "php-ast-analyzer";
18278
+ ecosystems = /* @__PURE__ */ new Set(["composer"]);
18279
+ supports(ecosystem) {
18280
+ return this.ecosystems.has(ecosystem);
18281
+ }
18282
+ async analyze(context) {
18283
+ const state = initState(context.packages);
18284
+ const packagePatterns = context.packages.map(
18285
+ (pkg2) => this.patternForPackage(pkg2.packageName, pkg2.ecosystem)
18286
+ );
18287
+ let files;
18288
+ try {
18289
+ files = await collectSourceFiles2(context.projectPath, PHP_EXTENSIONS);
18290
+ } catch (err) {
18291
+ return errorResultFromMessage(
18292
+ context,
18293
+ this.name,
18294
+ err instanceof Error ? err.message : String(err),
18295
+ "Failed to enumerate PHP source files"
18296
+ );
18297
+ }
18298
+ for (const filePath of files) {
18299
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18300
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18301
+ if (sourceText === null) continue;
18302
+ const lines = sourceText.split(/\r?\n/);
18303
+ const symbolToPackage = /* @__PURE__ */ new Map();
18304
+ for (let idx = 0; idx < lines.length; idx++) {
18305
+ const line = stripLineComment4(lines[idx]).trim();
18306
+ const lineNumber = idx + 1;
18307
+ if (!line) continue;
18308
+ const useMatch = line.match(/^use\s+([A-Za-z0-9_\\]+)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/i);
18309
+ if (useMatch) {
18310
+ const namespacePath = useMatch[1];
18311
+ const alias = useMatch[2] ?? namespacePath.split("\\").at(-1) ?? namespacePath;
18312
+ for (const pkg2 of packagePatterns) {
18313
+ const normalizedNamespace = namespacePath.toLowerCase();
18314
+ if (!pkg2.namespaceCandidates.some((candidate) => normalizedNamespace.startsWith(candidate.toLowerCase()))) continue;
18315
+ symbolToPackage.set(alias, pkg2.key);
18316
+ addCandidate(context, state, filePath, sourceText, {
18317
+ packageKey: pkg2.key,
18318
+ line: lineNumber,
18319
+ matchKind: "import",
18320
+ confidence: 0.94
18321
+ });
18322
+ }
18323
+ continue;
18324
+ }
18325
+ const requireMatch = line.match(/^require(?:_once)?\s*\(?\s*['"]([^'"]+)['"]\s*\)?\s*;/i);
18326
+ if (requireMatch) {
18327
+ const pathExpr = requireMatch[1].toLowerCase();
18328
+ for (const pkg2 of packagePatterns) {
18329
+ const hasVendor = pathExpr.includes(pkg2.vendor.toLowerCase());
18330
+ const hasPackage = pathExpr.includes(pkg2.package.toLowerCase());
18331
+ if (!hasVendor && !hasPackage) continue;
18332
+ addCandidate(context, state, filePath, sourceText, {
18333
+ packageKey: pkg2.key,
18334
+ line: lineNumber,
18335
+ matchKind: "require",
18336
+ confidence: 0.9
18337
+ });
18338
+ }
18339
+ continue;
18340
+ }
18341
+ const staticCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18342
+ if (staticCall) {
18343
+ const lhs = staticCall[1];
18344
+ const method = staticCall[2];
18345
+ const pkgKey = symbolToPackage.get(lhs) ?? this.findSymbolCandidatePackage(lhs, packagePatterns);
18346
+ if (pkgKey) {
18347
+ addCandidate(context, state, filePath, sourceText, {
18348
+ packageKey: pkgKey,
18349
+ line: lineNumber,
18350
+ matchKind: "call",
18351
+ calledSymbol: `${lhs}::${method}`,
18352
+ confidence: 0.8
18353
+ });
18354
+ }
18355
+ }
18356
+ const constructorMatch = line.match(/new\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
18357
+ if (constructorMatch) {
18358
+ const className = constructorMatch[1];
18359
+ const pkgKey = symbolToPackage.get(className) ?? this.findSymbolCandidatePackage(className, packagePatterns);
18360
+ if (pkgKey) {
18361
+ addCandidate(context, state, filePath, sourceText, {
18362
+ packageKey: pkgKey,
18363
+ line: lineNumber,
18364
+ matchKind: "call",
18365
+ calledSymbol: `new ${className}`,
18366
+ confidence: 0.76
18367
+ });
18368
+ }
18369
+ }
18370
+ }
18371
+ }
18372
+ return toAnalyzerResult(state);
18373
+ }
18374
+ patternForPackage(packageName, ecosystem) {
18375
+ const [vendorRaw, packageRaw] = packageName.split("/");
18376
+ const vendor = vendorRaw ?? packageName;
18377
+ const packagePart = packageRaw ?? packageName;
18378
+ const namespaceCandidates = uniqueTokens([
18379
+ pascalize(vendor),
18380
+ pascalize(packagePart),
18381
+ `${pascalize(vendor)}\\${pascalize(packagePart)}`,
18382
+ `${pascalize(vendor)}\\${pascalize(packagePart.replace(/-/g, "_"))}`
18383
+ ]);
18384
+ const symbolCandidates = uniqueTokens([
18385
+ pascalize(packagePart),
18386
+ pascalize(vendor),
18387
+ pascalize(packagePart).split("\\").at(-1) ?? ""
18388
+ ]);
18389
+ return {
18390
+ key: packageKey(ecosystem, packageName),
18391
+ vendor,
18392
+ package: packagePart,
18393
+ namespaceCandidates,
18394
+ symbolCandidates
18395
+ };
18396
+ }
18397
+ findSymbolCandidatePackage(symbol, packagePatterns) {
18398
+ const normalized = symbol.toLowerCase();
18399
+ for (const pkg2 of packagePatterns) {
18400
+ if (pkg2.symbolCandidates.some((candidate) => candidate.toLowerCase() === normalized)) {
18401
+ return pkg2.key;
18402
+ }
18403
+ }
18404
+ return null;
18405
+ }
18406
+ };
18407
+ function pascalize(input) {
18408
+ return input.split(/[\/_.-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("\\");
18409
+ }
18410
+ function stripLineComment4(line) {
18411
+ const slashIdx = line.indexOf("//");
18412
+ if (slashIdx !== -1) return line.slice(0, slashIdx);
18413
+ const hashIdx = line.indexOf("#");
18414
+ return hashIdx === -1 ? line : line.slice(0, hashIdx);
18415
+ }
17390
18416
 
17391
18417
  // src/context/usage-context-engine.ts
17392
18418
  var DEFAULT_MAX_SNIPPETS_PER_PACKAGE = 20;
@@ -17396,41 +18422,13 @@ var UsageContextEngine = class {
17396
18422
  constructor(analyzers) {
17397
18423
  this.analyzers = analyzers ?? [
17398
18424
  new JsAstAnalyzer(),
17399
- new UnsupportedAnalyzer(
17400
- "python-ast-analyzer",
17401
- ["pip", "poetry", "uv"],
17402
- "Python AST analyzer is not yet implemented in this release"
17403
- ),
17404
- new UnsupportedAnalyzer(
17405
- "java-ast-analyzer",
17406
- ["maven"],
17407
- "Java AST analyzer is not yet implemented in this release"
17408
- ),
17409
- new UnsupportedAnalyzer(
17410
- "dotnet-ast-analyzer",
17411
- ["nuget"],
17412
- "NuGet/C# AST analyzer is not yet implemented in this release"
17413
- ),
17414
- new UnsupportedAnalyzer(
17415
- "rust-ast-analyzer",
17416
- ["cargo"],
17417
- "Rust AST analyzer is not yet implemented in this release"
17418
- ),
17419
- new UnsupportedAnalyzer(
17420
- "go-ast-analyzer",
17421
- ["go"],
17422
- "Go AST analyzer is not yet implemented in this release"
17423
- ),
17424
- new UnsupportedAnalyzer(
17425
- "ruby-ast-analyzer",
17426
- ["ruby"],
17427
- "Ruby AST analyzer is not yet implemented in this release"
17428
- ),
17429
- new UnsupportedAnalyzer(
17430
- "php-ast-analyzer",
17431
- ["composer"],
17432
- "PHP AST analyzer is not yet implemented in this release"
17433
- )
18425
+ new PythonAstAnalyzer(),
18426
+ new JavaAstAnalyzer(),
18427
+ new DotnetAstAnalyzer(),
18428
+ new RustAstAnalyzer(),
18429
+ new GoAstAnalyzer(),
18430
+ new RubyAstAnalyzer(),
18431
+ new PhpAstAnalyzer()
17434
18432
  ];
17435
18433
  }
17436
18434
  async analyze(input) {
@@ -17490,7 +18488,7 @@ var UsageContextEngine = class {
17490
18488
  try {
17491
18489
  const result = await analyzer.analyze(runContext);
17492
18490
  const resultByKey = new Map(
17493
- result.packages.map((pkg2) => [packageKey(pkg2.ecosystem, pkg2.packageName), pkg2])
18491
+ result.packages.map((pkg2) => [packageKey2(pkg2.ecosystem, pkg2.packageName), pkg2])
17494
18492
  );
17495
18493
  errors.push(...result.errors);
17496
18494
  remainingSnippets = Math.max(0, remainingSnippets - result.snippetsProduced);
@@ -17498,7 +18496,7 @@ var UsageContextEngine = class {
17498
18496
  let unsupportedCount = 0;
17499
18497
  let analysisErrorCount = 0;
17500
18498
  for (const pkg2 of packages) {
17501
- const analyzed = resultByKey.get(packageKey(pkg2.ecosystem, pkg2.packageName)) ?? {
18499
+ const analyzed = resultByKey.get(packageKey2(pkg2.ecosystem, pkg2.packageName)) ?? {
17502
18500
  packageName: pkg2.packageName,
17503
18501
  ecosystem: pkg2.ecosystem,
17504
18502
  status: "analysis_error",
@@ -17603,13 +18601,13 @@ var UsageContextEngine = class {
17603
18601
  buildVulnerablePackages(vulnerabilities, dependencies) {
17604
18602
  const directMap = /* @__PURE__ */ new Map();
17605
18603
  for (const dependency of dependencies) {
17606
- const key = packageKey(dependency.ecosystem, dependency.name);
18604
+ const key = packageKey2(dependency.ecosystem, dependency.name);
17607
18605
  const existing = directMap.get(key) ?? false;
17608
18606
  directMap.set(key, existing || dependency.direct);
17609
18607
  }
17610
18608
  const grouped = /* @__PURE__ */ new Map();
17611
18609
  for (const vulnerability of vulnerabilities) {
17612
- const key = packageKey(vulnerability.ecosystem, vulnerability.packageName);
18610
+ const key = packageKey2(vulnerability.ecosystem, vulnerability.packageName);
17613
18611
  const existing = grouped.get(key);
17614
18612
  if (existing) {
17615
18613
  existing.vulnerabilities.push(vulnerability);
@@ -17633,7 +18631,7 @@ var UsageContextEngine = class {
17633
18631
  return n > 0 ? n : fallback;
17634
18632
  }
17635
18633
  };
17636
- function packageKey(ecosystem, packageName) {
18634
+ function packageKey2(ecosystem, packageName) {
17637
18635
  return `${ecosystem}::${packageName}`;
17638
18636
  }
17639
18637
  function groupByEcosystem(packages) {
@@ -17779,9 +18777,9 @@ function deriveArtifactOutputPaths(cycloneDxOutput) {
17779
18777
  }
17780
18778
  return {
17781
18779
  cyclonedx: cycloneDxOutput,
17782
- spdx: join2(parsed.dir, `${baseName}.spdx.json`),
17783
- swid: join2(parsed.dir, `${baseName}.swid.xml`),
17784
- usageContext: join2(parsed.dir, `${baseName}.usage-context.json`)
18780
+ spdx: join3(parsed.dir, `${baseName}.spdx.json`),
18781
+ swid: join3(parsed.dir, `${baseName}.swid.xml`),
18782
+ usageContext: join3(parsed.dir, `${baseName}.usage-context.json`)
17785
18783
  };
17786
18784
  }
17787
18785
  function buildUploadPayload(report) {
@@ -17840,16 +18838,27 @@ function renderPlatformScan(projectPath, result) {
17840
18838
  return lines.join("\n");
17841
18839
  }
17842
18840
  function collectVulnerabilities(result) {
17843
- return (result.scanResponse.scan_results ?? []).flatMap(
17844
- (scanResult) => (scanResult.vulnerabilities ?? []).map((vuln) => ({
17845
- dependencyName: scanResult.dependency_name,
17846
- version: scanResult.version,
17847
- cveId: vuln.cve_id,
17848
- severity: normalizeSeverity(vuln.severity ?? "UNKNOWN"),
17849
- summary: pickSummary(vuln),
17850
- fixedVersion: pickFixedVersion(vuln)
17851
- }))
17852
- );
18841
+ const deduped = /* @__PURE__ */ new Map();
18842
+ for (const scanResult of result.scanResponse.scan_results ?? []) {
18843
+ for (const vuln of scanResult.vulnerabilities ?? []) {
18844
+ const next = {
18845
+ dependencyName: scanResult.dependency_name,
18846
+ version: scanResult.version,
18847
+ cveId: vuln.cve_id,
18848
+ severity: normalizeSeverity(vuln.severity ?? "UNKNOWN"),
18849
+ summary: pickSummary(vuln),
18850
+ fixedVersion: pickFixedVersion(vuln)
18851
+ };
18852
+ const key = `${next.dependencyName}::${next.version}::${next.cveId}`;
18853
+ const existing = deduped.get(key);
18854
+ if (!existing) {
18855
+ deduped.set(key, next);
18856
+ continue;
18857
+ }
18858
+ deduped.set(key, mergeVulnerability(existing, next));
18859
+ }
18860
+ }
18861
+ return Array.from(deduped.values());
17853
18862
  }
17854
18863
  function pickSummary(vuln) {
17855
18864
  const value = vuln.summary ?? vuln.description;
@@ -17882,6 +18891,26 @@ function normalizeSeverity(severity) {
17882
18891
  return "UNKNOWN";
17883
18892
  }
17884
18893
  }
18894
+ function mergeVulnerability(current, incoming) {
18895
+ const severity = severityOrder2(incoming.severity) < severityOrder2(current.severity) ? incoming.severity : current.severity;
18896
+ const summary = pickPreferredSummary(current.summary, incoming.summary);
18897
+ const fixedVersion = current.fixedVersion ?? incoming.fixedVersion ?? null;
18898
+ return {
18899
+ ...current,
18900
+ severity,
18901
+ summary,
18902
+ fixedVersion
18903
+ };
18904
+ }
18905
+ function pickPreferredSummary(a, b) {
18906
+ const normalizedA = (a ?? "").trim();
18907
+ const normalizedB = (b ?? "").trim();
18908
+ if (!normalizedA) return normalizedB || "No description available";
18909
+ if (!normalizedB) return normalizedA;
18910
+ if (normalizedA === "No description available") return normalizedB;
18911
+ if (normalizedB === "No description available") return normalizedA;
18912
+ return normalizedB.length > normalizedA.length ? normalizedB : normalizedA;
18913
+ }
17885
18914
  function summarizeBySeverity(severities) {
17886
18915
  const summary = {
17887
18916
  CRITICAL: 0,