verimu 0.0.17 → 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
@@ -14093,11 +14093,10 @@ var require_lib3 = __commonJS({
14093
14093
  // src/cli.ts
14094
14094
  import { resolve } from "path";
14095
14095
  import { createRequire } from "module";
14096
- import { fileURLToPath } from "url";
14097
14096
 
14098
14097
  // src/scan.ts
14099
14098
  import { writeFile } from "fs/promises";
14100
- import { basename, join as join2, parse as parse2 } from "path";
14099
+ import { basename, join as join3, parse as parse2 } from "path";
14101
14100
 
14102
14101
  // src/scanners/npm/npm-scanner.ts
14103
14102
  import { readFile } from "fs/promises";
@@ -16751,6 +16750,15 @@ var ConsoleReporter = class {
16751
16750
  lines.push(
16752
16751
  ` Findings: direct_evidence=${directEvidence}, indirect_no_evidence=${indirectNoEvidence}, unsupported=${unsupported}, analysis_error=${analysisErrors}`
16753
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
+ }
16754
16762
  if (result.usageContext.artifactPath) {
16755
16763
  lines.push(` Artifact: ${result.usageContext.artifactPath}`);
16756
16764
  }
@@ -17002,7 +17010,7 @@ var import_types = __toESM(require_lib3(), 1);
17002
17010
  import { readdir, readFile as readFile14 } from "fs/promises";
17003
17011
  import { join } from "path";
17004
17012
  import { parse } from "@babel/parser";
17005
- import traverse from "@babel/traverse";
17013
+ import traverseModule from "@babel/traverse";
17006
17014
  var JS_EXTENSIONS = /* @__PURE__ */ new Set([
17007
17015
  ".js",
17008
17016
  ".jsx",
@@ -17032,6 +17040,24 @@ var JsAstAnalyzer = class {
17032
17040
  return this.ecosystems.has(ecosystem);
17033
17041
  }
17034
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
+ }
17035
17061
  const packageMap = this.buildPackageMaps(context.packages);
17036
17062
  const resultMap = /* @__PURE__ */ new Map();
17037
17063
  const snippetKeyMap = /* @__PURE__ */ new Map();
@@ -17095,13 +17121,13 @@ var JsAstAnalyzer = class {
17095
17121
  const matchCandidates = [];
17096
17122
  const matchSeen = /* @__PURE__ */ new Set();
17097
17123
  const symbolToPackage = /* @__PURE__ */ new Map();
17098
- const addMatch = (packageKey2, line, matchKind, calledSymbol, confidence = 0.8) => {
17099
- const candidateKey = `${packageKey2}:${line}:${matchKind}:${calledSymbol ?? ""}`;
17124
+ const addMatch = (packageKey3, line, matchKind, calledSymbol, confidence = 0.8) => {
17125
+ const candidateKey = `${packageKey3}:${line}:${matchKind}:${calledSymbol ?? ""}`;
17100
17126
  if (matchSeen.has(candidateKey)) return;
17101
17127
  matchSeen.add(candidateKey);
17102
- matchCandidates.push({ packageKey: packageKey2, line, matchKind, calledSymbol, confidence });
17128
+ matchCandidates.push({ packageKey: packageKey3, line, matchKind, calledSymbol, confidence });
17103
17129
  };
17104
- traverse(ast, {
17130
+ traverseFn(ast, {
17105
17131
  ImportDeclaration: (path14) => {
17106
17132
  const source = path14.node.source;
17107
17133
  if (!(0, import_types.isStringLiteral)(source)) return;
@@ -17223,6 +17249,18 @@ var JsAstAnalyzer = class {
17223
17249
  return `${ecosystem}::${packageName}`;
17224
17250
  }
17225
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
+ }
17226
17264
  async function collectSourceFiles(rootPath) {
17227
17265
  const files = [];
17228
17266
  async function walk(dirPath) {
@@ -17318,9 +17356,9 @@ function collectIdentifiers(pattern) {
17318
17356
  function resolveCallMatch(callee, symbolToPackage) {
17319
17357
  const normalized = unwrapExpression(callee);
17320
17358
  if ((0, import_types.isIdentifier)(normalized)) {
17321
- const packageKey2 = symbolToPackage.get(normalized.name);
17322
- if (!packageKey2) return null;
17323
- 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 };
17324
17362
  }
17325
17363
  if ((0, import_types.isMemberExpression)(normalized) || (0, import_types.isOptionalMemberExpression)(normalized)) {
17326
17364
  return resolveMemberCallMatch(normalized, symbolToPackage);
@@ -17337,13 +17375,13 @@ function unwrapExpression(expression) {
17337
17375
  function resolveMemberCallMatch(memberExpression, symbolToPackage) {
17338
17376
  const objectExpr = unwrapExpression(memberExpression.object);
17339
17377
  if (!(0, import_types.isIdentifier)(objectExpr)) return null;
17340
- const packageKey2 = symbolToPackage.get(objectExpr.name);
17341
- if (!packageKey2) return null;
17378
+ const packageKey3 = symbolToPackage.get(objectExpr.name);
17379
+ if (!packageKey3) return null;
17342
17380
  const propertyName = propertyNameOf(memberExpression);
17343
17381
  if (!propertyName) {
17344
- return { packageKey: packageKey2, calledSymbol: objectExpr.name };
17382
+ return { packageKey: packageKey3, calledSymbol: objectExpr.name };
17345
17383
  }
17346
- return { packageKey: packageKey2, calledSymbol: `${objectExpr.name}.${propertyName}` };
17384
+ return { packageKey: packageKey3, calledSymbol: `${objectExpr.name}.${propertyName}` };
17347
17385
  }
17348
17386
  function propertyNameOf(memberExpression) {
17349
17387
  if (memberExpression.computed) {
@@ -17360,34 +17398,1021 @@ function propertyNameOf(memberExpression) {
17360
17398
  return null;
17361
17399
  }
17362
17400
 
17363
- // src/context/analyzers/unsupported-analyzer.ts
17364
- var UnsupportedAnalyzer = class {
17365
- name;
17366
- ecosystems;
17367
- note;
17368
- constructor(name, ecosystems, note) {
17369
- this.name = name;
17370
- this.ecosystems = new Set(ecosystems);
17371
- 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());
17372
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
+ }
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"]);
17373
17555
  supports(ecosystem) {
17374
17556
  return this.ecosystems.has(ecosystem);
17375
17557
  }
17376
17558
  async analyze(context) {
17377
- const packages = context.packages.map((pkg2) => ({
17378
- packageName: pkg2.packageName,
17379
- ecosystem: pkg2.ecosystem,
17380
- status: "unsupported",
17381
- snippets: [],
17382
- notes: this.note
17383
- }));
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
+ }
17688
+ return {
17689
+ key: packageKey(ecosystem, packageName),
17690
+ modules: uniqueTokens(packageModules.map((v) => v.toLowerCase()))
17691
+ };
17692
+ }
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
+ ]);
17384
17790
  return {
17385
- packages,
17386
- errors: [],
17387
- snippetsProduced: 0
17791
+ key: packageKey(ecosystem, packageName),
17792
+ candidates
17388
17793
  };
17389
17794
  }
17390
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
+ }
17391
18416
 
17392
18417
  // src/context/usage-context-engine.ts
17393
18418
  var DEFAULT_MAX_SNIPPETS_PER_PACKAGE = 20;
@@ -17397,41 +18422,13 @@ var UsageContextEngine = class {
17397
18422
  constructor(analyzers) {
17398
18423
  this.analyzers = analyzers ?? [
17399
18424
  new JsAstAnalyzer(),
17400
- new UnsupportedAnalyzer(
17401
- "python-ast-analyzer",
17402
- ["pip", "poetry", "uv"],
17403
- "Python AST analyzer is not yet implemented in this release"
17404
- ),
17405
- new UnsupportedAnalyzer(
17406
- "java-ast-analyzer",
17407
- ["maven"],
17408
- "Java AST analyzer is not yet implemented in this release"
17409
- ),
17410
- new UnsupportedAnalyzer(
17411
- "dotnet-ast-analyzer",
17412
- ["nuget"],
17413
- "NuGet/C# AST analyzer is not yet implemented in this release"
17414
- ),
17415
- new UnsupportedAnalyzer(
17416
- "rust-ast-analyzer",
17417
- ["cargo"],
17418
- "Rust AST analyzer is not yet implemented in this release"
17419
- ),
17420
- new UnsupportedAnalyzer(
17421
- "go-ast-analyzer",
17422
- ["go"],
17423
- "Go AST analyzer is not yet implemented in this release"
17424
- ),
17425
- new UnsupportedAnalyzer(
17426
- "ruby-ast-analyzer",
17427
- ["ruby"],
17428
- "Ruby AST analyzer is not yet implemented in this release"
17429
- ),
17430
- new UnsupportedAnalyzer(
17431
- "php-ast-analyzer",
17432
- ["composer"],
17433
- "PHP AST analyzer is not yet implemented in this release"
17434
- )
18425
+ new PythonAstAnalyzer(),
18426
+ new JavaAstAnalyzer(),
18427
+ new DotnetAstAnalyzer(),
18428
+ new RustAstAnalyzer(),
18429
+ new GoAstAnalyzer(),
18430
+ new RubyAstAnalyzer(),
18431
+ new PhpAstAnalyzer()
17435
18432
  ];
17436
18433
  }
17437
18434
  async analyze(input) {
@@ -17491,7 +18488,7 @@ var UsageContextEngine = class {
17491
18488
  try {
17492
18489
  const result = await analyzer.analyze(runContext);
17493
18490
  const resultByKey = new Map(
17494
- result.packages.map((pkg2) => [packageKey(pkg2.ecosystem, pkg2.packageName), pkg2])
18491
+ result.packages.map((pkg2) => [packageKey2(pkg2.ecosystem, pkg2.packageName), pkg2])
17495
18492
  );
17496
18493
  errors.push(...result.errors);
17497
18494
  remainingSnippets = Math.max(0, remainingSnippets - result.snippetsProduced);
@@ -17499,7 +18496,7 @@ var UsageContextEngine = class {
17499
18496
  let unsupportedCount = 0;
17500
18497
  let analysisErrorCount = 0;
17501
18498
  for (const pkg2 of packages) {
17502
- const analyzed = resultByKey.get(packageKey(pkg2.ecosystem, pkg2.packageName)) ?? {
18499
+ const analyzed = resultByKey.get(packageKey2(pkg2.ecosystem, pkg2.packageName)) ?? {
17503
18500
  packageName: pkg2.packageName,
17504
18501
  ecosystem: pkg2.ecosystem,
17505
18502
  status: "analysis_error",
@@ -17604,13 +18601,13 @@ var UsageContextEngine = class {
17604
18601
  buildVulnerablePackages(vulnerabilities, dependencies) {
17605
18602
  const directMap = /* @__PURE__ */ new Map();
17606
18603
  for (const dependency of dependencies) {
17607
- const key = packageKey(dependency.ecosystem, dependency.name);
18604
+ const key = packageKey2(dependency.ecosystem, dependency.name);
17608
18605
  const existing = directMap.get(key) ?? false;
17609
18606
  directMap.set(key, existing || dependency.direct);
17610
18607
  }
17611
18608
  const grouped = /* @__PURE__ */ new Map();
17612
18609
  for (const vulnerability of vulnerabilities) {
17613
- const key = packageKey(vulnerability.ecosystem, vulnerability.packageName);
18610
+ const key = packageKey2(vulnerability.ecosystem, vulnerability.packageName);
17614
18611
  const existing = grouped.get(key);
17615
18612
  if (existing) {
17616
18613
  existing.vulnerabilities.push(vulnerability);
@@ -17634,7 +18631,7 @@ var UsageContextEngine = class {
17634
18631
  return n > 0 ? n : fallback;
17635
18632
  }
17636
18633
  };
17637
- function packageKey(ecosystem, packageName) {
18634
+ function packageKey2(ecosystem, packageName) {
17638
18635
  return `${ecosystem}::${packageName}`;
17639
18636
  }
17640
18637
  function groupByEcosystem(packages) {
@@ -17780,9 +18777,9 @@ function deriveArtifactOutputPaths(cycloneDxOutput) {
17780
18777
  }
17781
18778
  return {
17782
18779
  cyclonedx: cycloneDxOutput,
17783
- spdx: join2(parsed.dir, `${baseName}.spdx.json`),
17784
- swid: join2(parsed.dir, `${baseName}.swid.xml`),
17785
- 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`)
17786
18783
  };
17787
18784
  }
17788
18785
  function buildUploadPayload(report) {
@@ -17841,16 +18838,27 @@ function renderPlatformScan(projectPath, result) {
17841
18838
  return lines.join("\n");
17842
18839
  }
17843
18840
  function collectVulnerabilities(result) {
17844
- return (result.scanResponse.scan_results ?? []).flatMap(
17845
- (scanResult) => (scanResult.vulnerabilities ?? []).map((vuln) => ({
17846
- dependencyName: scanResult.dependency_name,
17847
- version: scanResult.version,
17848
- cveId: vuln.cve_id,
17849
- severity: normalizeSeverity(vuln.severity ?? "UNKNOWN"),
17850
- summary: pickSummary(vuln),
17851
- fixedVersion: pickFixedVersion(vuln)
17852
- }))
17853
- );
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());
17854
18862
  }
17855
18863
  function pickSummary(vuln) {
17856
18864
  const value = vuln.summary ?? vuln.description;
@@ -17883,6 +18891,26 @@ function normalizeSeverity(severity) {
17883
18891
  return "UNKNOWN";
17884
18892
  }
17885
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
+ }
17886
18914
  function summarizeBySeverity(severities) {
17887
18915
  const summary = {
17888
18916
  CRITICAL: 0,
@@ -18121,13 +19149,10 @@ function printHelp() {
18121
19149
  Dashboard: https://app.verimu.com
18122
19150
  `);
18123
19151
  }
18124
- var isDirectRun = process.argv[1] ? resolve(process.argv[1]) === fileURLToPath(import.meta.url) : false;
18125
- if (isDirectRun) {
18126
- main().catch((err) => {
18127
- console.error("Fatal:", err);
18128
- process.exit(2);
18129
- });
18130
- }
19152
+ main().catch((err) => {
19153
+ console.error("Fatal:", err);
19154
+ process.exit(2);
19155
+ });
18131
19156
  export {
18132
19157
  parseArgs
18133
19158
  };