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/index.cjs CHANGED
@@ -13057,9 +13057,9 @@ var require_traverse = __commonJS({
13057
13057
  Object.defineProperty(exports2, "__esModule", {
13058
13058
  value: true
13059
13059
  });
13060
- exports2.default = traverse2;
13060
+ exports2.default = traverse;
13061
13061
  var _index = require_definitions();
13062
- function traverse2(node, handlers, state) {
13062
+ function traverse(node, handlers, state) {
13063
13063
  if (typeof handlers === "function") {
13064
13064
  handlers = {
13065
13065
  enter: handlers
@@ -14374,8 +14374,8 @@ function sanitizeTagId(value) {
14374
14374
  }
14375
14375
 
14376
14376
  // src/scan.ts
14377
- var import_promises15 = require("fs/promises");
14378
- var import_path16 = require("path");
14377
+ var import_promises16 = require("fs/promises");
14378
+ var import_path17 = require("path");
14379
14379
 
14380
14380
  // src/scanners/npm/npm-scanner.ts
14381
14381
  var import_promises = require("fs/promises");
@@ -16997,6 +16997,15 @@ var ConsoleReporter = class {
16997
16997
  lines.push(
16998
16998
  ` Findings: direct_evidence=${directEvidence}, indirect_no_evidence=${indirectNoEvidence}, unsupported=${unsupported}, analysis_error=${analysisErrors}`
16999
16999
  );
17000
+ if (result.usageContext.ecosystemStatus.length > 0) {
17001
+ lines.push(" Analyzer status:");
17002
+ for (const status of result.usageContext.ecosystemStatus) {
17003
+ const note = status.note ? ` (${status.note})` : "";
17004
+ lines.push(
17005
+ ` ${status.ecosystem}: ${status.status} via ${status.analyzer}${note}`
17006
+ );
17007
+ }
17008
+ }
17000
17009
  if (result.usageContext.artifactPath) {
17001
17010
  lines.push(` Artifact: ${result.usageContext.artifactPath}`);
17002
17011
  }
@@ -17278,6 +17287,24 @@ var JsAstAnalyzer = class {
17278
17287
  return this.ecosystems.has(ecosystem);
17279
17288
  }
17280
17289
  async analyze(context) {
17290
+ const traverseFn = resolveTraverseFunction(import_traverse.default);
17291
+ if (!traverseFn) {
17292
+ return {
17293
+ packages: context.packages.map((pkg) => ({
17294
+ packageName: pkg.packageName,
17295
+ ecosystem: pkg.ecosystem,
17296
+ status: "analysis_error",
17297
+ snippets: [],
17298
+ notes: "Failed to resolve @babel/traverse runtime export"
17299
+ })),
17300
+ errors: [{
17301
+ analyzer: this.name,
17302
+ ecosystem: context.ecosystem,
17303
+ error: "Failed to resolve @babel/traverse runtime export"
17304
+ }],
17305
+ snippetsProduced: 0
17306
+ };
17307
+ }
17281
17308
  const packageMap = this.buildPackageMaps(context.packages);
17282
17309
  const resultMap = /* @__PURE__ */ new Map();
17283
17310
  const snippetKeyMap = /* @__PURE__ */ new Map();
@@ -17341,13 +17368,13 @@ var JsAstAnalyzer = class {
17341
17368
  const matchCandidates = [];
17342
17369
  const matchSeen = /* @__PURE__ */ new Set();
17343
17370
  const symbolToPackage = /* @__PURE__ */ new Map();
17344
- const addMatch = (packageKey2, line, matchKind, calledSymbol, confidence = 0.8) => {
17345
- const candidateKey = `${packageKey2}:${line}:${matchKind}:${calledSymbol ?? ""}`;
17371
+ const addMatch = (packageKey3, line, matchKind, calledSymbol, confidence = 0.8) => {
17372
+ const candidateKey = `${packageKey3}:${line}:${matchKind}:${calledSymbol ?? ""}`;
17346
17373
  if (matchSeen.has(candidateKey)) return;
17347
17374
  matchSeen.add(candidateKey);
17348
- matchCandidates.push({ packageKey: packageKey2, line, matchKind, calledSymbol, confidence });
17375
+ matchCandidates.push({ packageKey: packageKey3, line, matchKind, calledSymbol, confidence });
17349
17376
  };
17350
- (0, import_traverse.default)(ast, {
17377
+ traverseFn(ast, {
17351
17378
  ImportDeclaration: (path14) => {
17352
17379
  const source = path14.node.source;
17353
17380
  if (!(0, import_types.isStringLiteral)(source)) return;
@@ -17469,6 +17496,18 @@ var JsAstAnalyzer = class {
17469
17496
  return `${ecosystem}::${packageName}`;
17470
17497
  }
17471
17498
  };
17499
+ function resolveTraverseFunction(moduleValue) {
17500
+ if (typeof moduleValue === "function") {
17501
+ return moduleValue;
17502
+ }
17503
+ if (typeof moduleValue === "object" && moduleValue !== null && "default" in moduleValue) {
17504
+ const candidate = moduleValue.default;
17505
+ if (typeof candidate === "function") {
17506
+ return candidate;
17507
+ }
17508
+ }
17509
+ return null;
17510
+ }
17472
17511
  async function collectSourceFiles(rootPath) {
17473
17512
  const files = [];
17474
17513
  async function walk(dirPath) {
@@ -17564,9 +17603,9 @@ function collectIdentifiers(pattern) {
17564
17603
  function resolveCallMatch(callee, symbolToPackage) {
17565
17604
  const normalized = unwrapExpression(callee);
17566
17605
  if ((0, import_types.isIdentifier)(normalized)) {
17567
- const packageKey2 = symbolToPackage.get(normalized.name);
17568
- if (!packageKey2) return null;
17569
- return { packageKey: packageKey2, calledSymbol: normalized.name };
17606
+ const packageKey3 = symbolToPackage.get(normalized.name);
17607
+ if (!packageKey3) return null;
17608
+ return { packageKey: packageKey3, calledSymbol: normalized.name };
17570
17609
  }
17571
17610
  if ((0, import_types.isMemberExpression)(normalized) || (0, import_types.isOptionalMemberExpression)(normalized)) {
17572
17611
  return resolveMemberCallMatch(normalized, symbolToPackage);
@@ -17583,13 +17622,13 @@ function unwrapExpression(expression) {
17583
17622
  function resolveMemberCallMatch(memberExpression, symbolToPackage) {
17584
17623
  const objectExpr = unwrapExpression(memberExpression.object);
17585
17624
  if (!(0, import_types.isIdentifier)(objectExpr)) return null;
17586
- const packageKey2 = symbolToPackage.get(objectExpr.name);
17587
- if (!packageKey2) return null;
17625
+ const packageKey3 = symbolToPackage.get(objectExpr.name);
17626
+ if (!packageKey3) return null;
17588
17627
  const propertyName = propertyNameOf(memberExpression);
17589
17628
  if (!propertyName) {
17590
- return { packageKey: packageKey2, calledSymbol: objectExpr.name };
17629
+ return { packageKey: packageKey3, calledSymbol: objectExpr.name };
17591
17630
  }
17592
- return { packageKey: packageKey2, calledSymbol: `${objectExpr.name}.${propertyName}` };
17631
+ return { packageKey: packageKey3, calledSymbol: `${objectExpr.name}.${propertyName}` };
17593
17632
  }
17594
17633
  function propertyNameOf(memberExpression) {
17595
17634
  if (memberExpression.computed) {
@@ -17606,34 +17645,1021 @@ function propertyNameOf(memberExpression) {
17606
17645
  return null;
17607
17646
  }
17608
17647
 
17609
- // src/context/analyzers/unsupported-analyzer.ts
17610
- var UnsupportedAnalyzer = class {
17611
- name;
17612
- ecosystems;
17613
- note;
17614
- constructor(name, ecosystems, note) {
17615
- this.name = name;
17616
- this.ecosystems = new Set(ecosystems);
17617
- this.note = note;
17648
+ // src/context/analyzers/shared.ts
17649
+ var import_promises15 = require("fs/promises");
17650
+ var import_path16 = require("path");
17651
+ var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
17652
+ ".git",
17653
+ ".hg",
17654
+ ".svn",
17655
+ "node_modules",
17656
+ "dist",
17657
+ "build",
17658
+ "coverage",
17659
+ ".next",
17660
+ ".nuxt",
17661
+ ".turbo",
17662
+ "vendor",
17663
+ ".venv",
17664
+ "venv",
17665
+ "target",
17666
+ "bin",
17667
+ "obj"
17668
+ ]);
17669
+ function packageKey(ecosystem, packageName) {
17670
+ return `${ecosystem}::${packageName}`;
17671
+ }
17672
+ function initState(packages) {
17673
+ const resultMap = /* @__PURE__ */ new Map();
17674
+ const snippetKeyMap = /* @__PURE__ */ new Map();
17675
+ for (const pkg of packages) {
17676
+ const key = packageKey(pkg.ecosystem, pkg.packageName);
17677
+ resultMap.set(key, {
17678
+ packageName: pkg.packageName,
17679
+ ecosystem: pkg.ecosystem,
17680
+ status: "indirect_no_evidence",
17681
+ snippets: []
17682
+ });
17683
+ snippetKeyMap.set(key, /* @__PURE__ */ new Set());
17684
+ }
17685
+ return {
17686
+ resultMap,
17687
+ snippetKeyMap,
17688
+ errors: [],
17689
+ snippetsProduced: 0
17690
+ };
17691
+ }
17692
+ function errorResultFromMessage(context, analyzerName, message, notes) {
17693
+ return {
17694
+ packages: context.packages.map((pkg) => ({
17695
+ packageName: pkg.packageName,
17696
+ ecosystem: pkg.ecosystem,
17697
+ status: "analysis_error",
17698
+ snippets: [],
17699
+ notes
17700
+ })),
17701
+ errors: [{ analyzer: analyzerName, ecosystem: context.ecosystem, error: message }],
17702
+ snippetsProduced: 0
17703
+ };
17704
+ }
17705
+ function toAnalyzerResult(state) {
17706
+ for (const result of state.resultMap.values()) {
17707
+ result.snippets = dedupeSnippets(result.snippets);
17708
+ if (result.snippets.length > 0) {
17709
+ result.status = "direct_evidence";
17710
+ }
17711
+ }
17712
+ return {
17713
+ packages: Array.from(state.resultMap.values()),
17714
+ errors: state.errors,
17715
+ snippetsProduced: state.snippetsProduced
17716
+ };
17717
+ }
17718
+ async function collectSourceFiles2(rootPath, extensions, ignoredDirs = DEFAULT_IGNORED_DIRS) {
17719
+ const files = [];
17720
+ async function walk(dirPath) {
17721
+ const entries = await (0, import_promises15.readdir)(dirPath, { withFileTypes: true });
17722
+ for (const entry of entries) {
17723
+ const fullPath = (0, import_path16.join)(dirPath, entry.name);
17724
+ if (entry.isDirectory()) {
17725
+ if (ignoredDirs.has(entry.name)) continue;
17726
+ await walk(fullPath);
17727
+ continue;
17728
+ }
17729
+ if (!entry.isFile()) continue;
17730
+ if (!extensions.has(extensionOf2(entry.name))) continue;
17731
+ files.push(fullPath);
17732
+ }
17618
17733
  }
17734
+ await walk(rootPath);
17735
+ return files;
17736
+ }
17737
+ async function readSourceFile(analyzerName, ecosystem, filePath, errors) {
17738
+ try {
17739
+ return await (0, import_promises15.readFile)(filePath, "utf-8");
17740
+ } catch (err) {
17741
+ errors.push({
17742
+ analyzer: analyzerName,
17743
+ ecosystem,
17744
+ error: `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
17745
+ });
17746
+ return null;
17747
+ }
17748
+ }
17749
+ function addCandidate(context, state, filePath, sourceText, candidate) {
17750
+ if (state.snippetsProduced >= context.maxSnippetsTotal) return;
17751
+ const packageResult = state.resultMap.get(candidate.packageKey);
17752
+ if (!packageResult) return;
17753
+ if (packageResult.snippets.length >= context.maxSnippetsPerPackage) return;
17754
+ const snippet = buildSnippet({
17755
+ projectPath: context.projectPath,
17756
+ filePath,
17757
+ sourceText,
17758
+ line: candidate.line,
17759
+ numContextLines: context.numContextLines,
17760
+ matchKind: candidate.matchKind,
17761
+ calledSymbol: candidate.calledSymbol,
17762
+ confidence: candidate.confidence ?? 0.8
17763
+ });
17764
+ const dedupeKey = `${snippet.filePath}:${snippet.startLine}:${snippet.endLine}:${snippet.matchKind}:${snippet.calledSymbol ?? ""}`;
17765
+ const packageSnippetKeys = state.snippetKeyMap.get(candidate.packageKey);
17766
+ if (!packageSnippetKeys || packageSnippetKeys.has(dedupeKey)) return;
17767
+ packageSnippetKeys.add(dedupeKey);
17768
+ packageResult.snippets.push(snippet);
17769
+ state.snippetsProduced += 1;
17770
+ }
17771
+ function extensionOf2(fileName) {
17772
+ const index = fileName.lastIndexOf(".");
17773
+ return index === -1 ? "" : fileName.slice(index).toLowerCase();
17774
+ }
17775
+ function basePackageName(name) {
17776
+ const slash = name.includes("/") ? name.split("/").at(-1) ?? name : name;
17777
+ const colon = slash.includes(":") ? slash.split(":").at(-1) ?? slash : slash;
17778
+ return colon;
17779
+ }
17780
+ function toIdentifierToken(value) {
17781
+ return value.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
17782
+ }
17783
+ function uniqueTokens(values) {
17784
+ const result = [];
17785
+ const seen = /* @__PURE__ */ new Set();
17786
+ for (const value of values) {
17787
+ const trimmed = value.trim();
17788
+ if (!trimmed) continue;
17789
+ const key = trimmed.toLowerCase();
17790
+ if (seen.has(key)) continue;
17791
+ seen.add(key);
17792
+ result.push(trimmed);
17793
+ }
17794
+ return result;
17795
+ }
17796
+
17797
+ // src/context/analyzers/python-ast-analyzer.ts
17798
+ var PYTHON_EXTENSIONS = /* @__PURE__ */ new Set([".py"]);
17799
+ var PythonAstAnalyzer = class {
17800
+ name = "python-ast-analyzer";
17801
+ ecosystems = /* @__PURE__ */ new Set(["pip", "poetry", "uv"]);
17619
17802
  supports(ecosystem) {
17620
17803
  return this.ecosystems.has(ecosystem);
17621
17804
  }
17622
17805
  async analyze(context) {
17623
- const packages = context.packages.map((pkg) => ({
17624
- packageName: pkg.packageName,
17625
- ecosystem: pkg.ecosystem,
17626
- status: "unsupported",
17627
- snippets: [],
17628
- notes: this.note
17629
- }));
17806
+ const state = initState(context.packages);
17807
+ const packagePatterns = context.packages.map(
17808
+ (pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem)
17809
+ );
17810
+ let files;
17811
+ try {
17812
+ files = await collectSourceFiles2(context.projectPath, PYTHON_EXTENSIONS);
17813
+ } catch (err) {
17814
+ return errorResultFromMessage(
17815
+ context,
17816
+ this.name,
17817
+ err instanceof Error ? err.message : String(err),
17818
+ "Failed to enumerate Python source files"
17819
+ );
17820
+ }
17821
+ for (const filePath of files) {
17822
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
17823
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
17824
+ if (sourceText === null) continue;
17825
+ const aliasToPackage = /* @__PURE__ */ new Map();
17826
+ const lines = sourceText.split(/\r?\n/);
17827
+ for (let idx = 0; idx < lines.length; idx++) {
17828
+ const line = lines[idx];
17829
+ const trimmed = stripInlineComment(line).trim();
17830
+ const lineNumber = idx + 1;
17831
+ if (!trimmed) continue;
17832
+ const importMatch = trimmed.match(/^import\s+(.+)$/);
17833
+ if (importMatch) {
17834
+ const segments = importMatch[1].split(",").map((part) => part.trim()).filter(Boolean);
17835
+ for (const segment of segments) {
17836
+ const parsed = segment.match(/^([A-Za-z_][A-Za-z0-9_\.]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$/);
17837
+ if (!parsed) continue;
17838
+ const moduleName = parsed[1];
17839
+ const alias = parsed[2] ?? moduleName.split(".").at(0) ?? moduleName;
17840
+ this.addImportForModule(
17841
+ context,
17842
+ state,
17843
+ filePath,
17844
+ sourceText,
17845
+ packagePatterns,
17846
+ aliasToPackage,
17847
+ moduleName,
17848
+ alias,
17849
+ lineNumber
17850
+ );
17851
+ }
17852
+ continue;
17853
+ }
17854
+ const fromMatch = trimmed.match(
17855
+ /^from\s+([A-Za-z_][A-Za-z0-9_\.]*)\s+import\s+(.+)$/
17856
+ );
17857
+ if (fromMatch) {
17858
+ const moduleName = fromMatch[1];
17859
+ const imported = fromMatch[2].split(",").map((part) => part.trim()).filter(Boolean);
17860
+ for (const part of imported) {
17861
+ const parsed = part.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$/);
17862
+ if (!parsed) continue;
17863
+ const name = parsed[1];
17864
+ const alias = parsed[2] ?? name;
17865
+ this.addImportForModule(
17866
+ context,
17867
+ state,
17868
+ filePath,
17869
+ sourceText,
17870
+ packagePatterns,
17871
+ aliasToPackage,
17872
+ moduleName,
17873
+ alias,
17874
+ lineNumber
17875
+ );
17876
+ }
17877
+ continue;
17878
+ }
17879
+ for (const [alias, pkgKey] of aliasToPackage.entries()) {
17880
+ const escapedAlias = escapeRegex(alias);
17881
+ const directCall = new RegExp(`\\b${escapedAlias}\\s*\\(`);
17882
+ if (directCall.test(trimmed)) {
17883
+ addCandidate(context, state, filePath, sourceText, {
17884
+ packageKey: pkgKey,
17885
+ line: lineNumber,
17886
+ matchKind: "call",
17887
+ calledSymbol: alias,
17888
+ confidence: 0.78
17889
+ });
17890
+ }
17891
+ const memberCall = new RegExp(`\\b${escapedAlias}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`);
17892
+ const match = trimmed.match(memberCall);
17893
+ if (match) {
17894
+ addCandidate(context, state, filePath, sourceText, {
17895
+ packageKey: pkgKey,
17896
+ line: lineNumber,
17897
+ matchKind: "call",
17898
+ calledSymbol: `${alias}.${match[1]}`,
17899
+ confidence: 0.8
17900
+ });
17901
+ }
17902
+ }
17903
+ }
17904
+ }
17905
+ return toAnalyzerResult(state);
17906
+ }
17907
+ addImportForModule(context, state, filePath, sourceText, packagePatterns, aliasToPackage, moduleName, alias, lineNumber) {
17908
+ const normalizedModule = moduleName.toLowerCase();
17909
+ for (const pkg of packagePatterns) {
17910
+ const matched = pkg.modules.some(
17911
+ (candidate) => normalizedModule === candidate || normalizedModule.startsWith(`${candidate}.`)
17912
+ );
17913
+ if (!matched) continue;
17914
+ aliasToPackage.set(alias, pkg.key);
17915
+ aliasToPackage.set(moduleName.split(".").at(0) ?? moduleName, pkg.key);
17916
+ addCandidate(context, state, filePath, sourceText, {
17917
+ packageKey: pkg.key,
17918
+ line: lineNumber,
17919
+ matchKind: "import",
17920
+ confidence: 0.95
17921
+ });
17922
+ }
17923
+ }
17924
+ patternForPackage(packageName, ecosystem) {
17925
+ const normalized = toIdentifierToken(packageName).replace(/_/g, "-");
17926
+ const base = toIdentifierToken(basePackageName(packageName));
17927
+ const packageModules = [
17928
+ normalized.replace(/-/g, "_"),
17929
+ base.replace(/-/g, "_"),
17930
+ base.replace(/_/g, "")
17931
+ ];
17932
+ if (normalized === "pyyaml" || base === "pyyaml") {
17933
+ packageModules.push("yaml");
17934
+ }
17630
17935
  return {
17631
- packages,
17632
- errors: [],
17633
- snippetsProduced: 0
17936
+ key: packageKey(ecosystem, packageName),
17937
+ modules: uniqueTokens(packageModules.map((v) => v.toLowerCase()))
17634
17938
  };
17635
17939
  }
17636
17940
  };
17941
+ function stripInlineComment(line) {
17942
+ const hashIndex = line.indexOf("#");
17943
+ return hashIndex === -1 ? line : line.slice(0, hashIndex);
17944
+ }
17945
+ function escapeRegex(value) {
17946
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17947
+ }
17948
+
17949
+ // src/context/analyzers/java-ast-analyzer.ts
17950
+ var JAVA_EXTENSIONS = /* @__PURE__ */ new Set([".java"]);
17951
+ var JavaAstAnalyzer = class {
17952
+ name = "java-ast-analyzer";
17953
+ ecosystems = /* @__PURE__ */ new Set(["maven"]);
17954
+ supports(ecosystem) {
17955
+ return this.ecosystems.has(ecosystem);
17956
+ }
17957
+ async analyze(context) {
17958
+ const state = initState(context.packages);
17959
+ const packagePatterns = context.packages.map(
17960
+ (pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem)
17961
+ );
17962
+ let files;
17963
+ try {
17964
+ files = await collectSourceFiles2(context.projectPath, JAVA_EXTENSIONS);
17965
+ } catch (err) {
17966
+ return errorResultFromMessage(
17967
+ context,
17968
+ this.name,
17969
+ err instanceof Error ? err.message : String(err),
17970
+ "Failed to enumerate Java source files"
17971
+ );
17972
+ }
17973
+ for (const filePath of files) {
17974
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
17975
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
17976
+ if (sourceText === null) continue;
17977
+ const lines = sourceText.split(/\r?\n/);
17978
+ const symbolToPackage = /* @__PURE__ */ new Map();
17979
+ for (let idx = 0; idx < lines.length; idx++) {
17980
+ const line = stripLineComment(lines[idx]).trim();
17981
+ const lineNumber = idx + 1;
17982
+ if (!line) continue;
17983
+ const importMatch = line.match(/^import\s+(?:static\s+)?([A-Za-z0-9_.*]+)\s*;/);
17984
+ if (importMatch) {
17985
+ const importPath = importMatch[1].replace(/\.\*$/, "");
17986
+ const simpleName = importPath.split(".").at(-1) ?? importPath;
17987
+ for (const pkg of packagePatterns) {
17988
+ if (!pkg.candidates.some((candidate) => importPath.startsWith(candidate))) continue;
17989
+ addCandidate(context, state, filePath, sourceText, {
17990
+ packageKey: pkg.key,
17991
+ line: lineNumber,
17992
+ matchKind: "import",
17993
+ confidence: 0.94
17994
+ });
17995
+ if (simpleName && /^[A-Za-z_][A-Za-z0-9_]*$/.test(simpleName)) {
17996
+ symbolToPackage.set(simpleName, pkg.key);
17997
+ }
17998
+ }
17999
+ continue;
18000
+ }
18001
+ const varDecl = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)/);
18002
+ if (varDecl) {
18003
+ const typeName = varDecl[1];
18004
+ const variableName = varDecl[2];
18005
+ const pkgKey2 = symbolToPackage.get(typeName);
18006
+ if (pkgKey2) {
18007
+ symbolToPackage.set(variableName, pkgKey2);
18008
+ }
18009
+ }
18010
+ const callMatch = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18011
+ if (!callMatch) continue;
18012
+ const lhs = callMatch[1];
18013
+ const member = callMatch[2];
18014
+ const pkgKey = symbolToPackage.get(lhs);
18015
+ if (!pkgKey) continue;
18016
+ addCandidate(context, state, filePath, sourceText, {
18017
+ packageKey: pkgKey,
18018
+ line: lineNumber,
18019
+ matchKind: "call",
18020
+ calledSymbol: `${lhs}.${member}`,
18021
+ confidence: 0.8
18022
+ });
18023
+ }
18024
+ }
18025
+ return toAnalyzerResult(state);
18026
+ }
18027
+ patternForPackage(packageName, ecosystem) {
18028
+ const [groupIdRaw, artifactIdRaw] = packageName.split(":");
18029
+ const groupId = (groupIdRaw ?? "").trim();
18030
+ const artifactId = (artifactIdRaw ?? "").trim();
18031
+ const candidates = uniqueTokens([
18032
+ groupId,
18033
+ groupId && artifactId ? `${groupId}.${artifactId.replace(/-/g, ".")}` : "",
18034
+ artifactId ? artifactId.replace(/-/g, ".") : "",
18035
+ artifactId ? toIdentifierToken(artifactId).replace(/_/g, ".") : ""
18036
+ ]);
18037
+ return {
18038
+ key: packageKey(ecosystem, packageName),
18039
+ candidates
18040
+ };
18041
+ }
18042
+ };
18043
+ function stripLineComment(line) {
18044
+ const idx = line.indexOf("//");
18045
+ return idx === -1 ? line : line.slice(0, idx);
18046
+ }
18047
+
18048
+ // src/context/analyzers/dotnet-ast-analyzer.ts
18049
+ var CSHARP_EXTENSIONS = /* @__PURE__ */ new Set([".cs"]);
18050
+ var DotnetAstAnalyzer = class {
18051
+ name = "dotnet-ast-analyzer";
18052
+ ecosystems = /* @__PURE__ */ new Set(["nuget"]);
18053
+ supports(ecosystem) {
18054
+ return this.ecosystems.has(ecosystem);
18055
+ }
18056
+ async analyze(context) {
18057
+ const state = initState(context.packages);
18058
+ const packagePatterns = context.packages.map(
18059
+ (pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem)
18060
+ );
18061
+ let files;
18062
+ try {
18063
+ files = await collectSourceFiles2(context.projectPath, CSHARP_EXTENSIONS);
18064
+ } catch (err) {
18065
+ return errorResultFromMessage(
18066
+ context,
18067
+ this.name,
18068
+ err instanceof Error ? err.message : String(err),
18069
+ "Failed to enumerate C# source files"
18070
+ );
18071
+ }
18072
+ for (const filePath of files) {
18073
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18074
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18075
+ if (sourceText === null) continue;
18076
+ const lines = sourceText.split(/\r?\n/);
18077
+ const symbolToPackage = /* @__PURE__ */ new Map();
18078
+ for (let idx = 0; idx < lines.length; idx++) {
18079
+ const line = stripLineComment2(lines[idx]).trim();
18080
+ const lineNumber = idx + 1;
18081
+ if (!line) continue;
18082
+ const aliasUsing = line.match(/^using\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([A-Za-z0-9_.]+)\s*;/);
18083
+ if (aliasUsing) {
18084
+ const alias = aliasUsing[1];
18085
+ const targetNamespace = aliasUsing[2];
18086
+ for (const pkg of packagePatterns) {
18087
+ if (!pkg.namespaceCandidates.some((candidate) => targetNamespace.startsWith(candidate))) continue;
18088
+ symbolToPackage.set(alias, pkg.key);
18089
+ addCandidate(context, state, filePath, sourceText, {
18090
+ packageKey: pkg.key,
18091
+ line: lineNumber,
18092
+ matchKind: "import",
18093
+ confidence: 0.93
18094
+ });
18095
+ }
18096
+ continue;
18097
+ }
18098
+ const usingMatch = line.match(/^using\s+([A-Za-z0-9_.]+)\s*;/);
18099
+ if (usingMatch) {
18100
+ const namespaceName = usingMatch[1];
18101
+ const tailSymbol = namespaceName.split(".").at(-1) ?? namespaceName;
18102
+ for (const pkg of packagePatterns) {
18103
+ if (!pkg.namespaceCandidates.some((candidate) => namespaceName.startsWith(candidate))) continue;
18104
+ symbolToPackage.set(tailSymbol, pkg.key);
18105
+ addCandidate(context, state, filePath, sourceText, {
18106
+ packageKey: pkg.key,
18107
+ line: lineNumber,
18108
+ matchKind: "import",
18109
+ confidence: 0.93
18110
+ });
18111
+ }
18112
+ continue;
18113
+ }
18114
+ const varDecl = line.match(/^([A-Za-z_][A-Za-z0-9_.]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=|;)/);
18115
+ if (varDecl) {
18116
+ const typeName = varDecl[1].split(".").at(-1) ?? varDecl[1];
18117
+ const variableName = varDecl[2];
18118
+ const pkgKey = symbolToPackage.get(typeName);
18119
+ if (pkgKey) {
18120
+ symbolToPackage.set(variableName, pkgKey);
18121
+ }
18122
+ }
18123
+ const memberCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18124
+ if (memberCall) {
18125
+ const lhs = memberCall[1];
18126
+ const method = memberCall[2];
18127
+ const pkgKey = symbolToPackage.get(lhs) ?? this.findSymbolCandidatePackage(lhs, packagePatterns);
18128
+ if (pkgKey) {
18129
+ addCandidate(context, state, filePath, sourceText, {
18130
+ packageKey: pkgKey,
18131
+ line: lineNumber,
18132
+ matchKind: "call",
18133
+ calledSymbol: `${lhs}.${method}`,
18134
+ confidence: 0.79
18135
+ });
18136
+ }
18137
+ }
18138
+ }
18139
+ }
18140
+ return toAnalyzerResult(state);
18141
+ }
18142
+ patternForPackage(packageName, ecosystem) {
18143
+ const dotted = packageName.replace(/-/g, ".");
18144
+ const segments = dotted.split(".");
18145
+ const symbolCandidates = uniqueTokens([
18146
+ segments.at(-1) ?? "",
18147
+ segments.at(-2) ?? "",
18148
+ packageName.split(".").at(-1) ?? ""
18149
+ ]);
18150
+ const namespaceCandidates = uniqueTokens([
18151
+ dotted,
18152
+ segments.slice(0, -1).join("."),
18153
+ segments.slice(0, Math.min(2, segments.length)).join(".")
18154
+ ]);
18155
+ return {
18156
+ key: packageKey(ecosystem, packageName),
18157
+ namespaceCandidates,
18158
+ symbolCandidates
18159
+ };
18160
+ }
18161
+ findSymbolCandidatePackage(symbol, packagePatterns) {
18162
+ for (const pkg of packagePatterns) {
18163
+ if (pkg.symbolCandidates.includes(symbol)) {
18164
+ return pkg.key;
18165
+ }
18166
+ }
18167
+ return null;
18168
+ }
18169
+ };
18170
+ function stripLineComment2(line) {
18171
+ const idx = line.indexOf("//");
18172
+ return idx === -1 ? line : line.slice(0, idx);
18173
+ }
18174
+
18175
+ // src/context/analyzers/rust-ast-analyzer.ts
18176
+ var RUST_EXTENSIONS = /* @__PURE__ */ new Set([".rs"]);
18177
+ var RustAstAnalyzer = class {
18178
+ name = "rust-ast-analyzer";
18179
+ ecosystems = /* @__PURE__ */ new Set(["cargo"]);
18180
+ supports(ecosystem) {
18181
+ return this.ecosystems.has(ecosystem);
18182
+ }
18183
+ async analyze(context) {
18184
+ const state = initState(context.packages);
18185
+ const packagePatterns = context.packages.map(
18186
+ (pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem)
18187
+ );
18188
+ let files;
18189
+ try {
18190
+ files = await collectSourceFiles2(context.projectPath, RUST_EXTENSIONS);
18191
+ } catch (err) {
18192
+ return errorResultFromMessage(
18193
+ context,
18194
+ this.name,
18195
+ err instanceof Error ? err.message : String(err),
18196
+ "Failed to enumerate Rust source files"
18197
+ );
18198
+ }
18199
+ for (const filePath of files) {
18200
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18201
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18202
+ if (sourceText === null) continue;
18203
+ const lines = sourceText.split(/\r?\n/);
18204
+ const symbolToPackage = /* @__PURE__ */ new Map();
18205
+ for (let idx = 0; idx < lines.length; idx++) {
18206
+ const line = stripLineComment3(lines[idx]).trim();
18207
+ const lineNumber = idx + 1;
18208
+ if (!line) continue;
18209
+ const useMatch = line.match(/^use\s+([A-Za-z_][A-Za-z0-9_:]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/);
18210
+ if (useMatch) {
18211
+ const pathExpr = useMatch[1];
18212
+ const alias = useMatch[2] ?? pathExpr.split("::").at(0) ?? pathExpr;
18213
+ const crate = pathExpr.split("::").at(0) ?? pathExpr;
18214
+ for (const pkg of packagePatterns) {
18215
+ if (!pkg.crateNames.includes(crate)) continue;
18216
+ symbolToPackage.set(alias, pkg.key);
18217
+ symbolToPackage.set(crate, pkg.key);
18218
+ addCandidate(context, state, filePath, sourceText, {
18219
+ packageKey: pkg.key,
18220
+ line: lineNumber,
18221
+ matchKind: "import",
18222
+ confidence: 0.94
18223
+ });
18224
+ }
18225
+ continue;
18226
+ }
18227
+ 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*;/);
18228
+ if (externMatch) {
18229
+ const crate = externMatch[1];
18230
+ const alias = externMatch[2] ?? crate;
18231
+ for (const pkg of packagePatterns) {
18232
+ if (!pkg.crateNames.includes(crate)) continue;
18233
+ symbolToPackage.set(alias, pkg.key);
18234
+ symbolToPackage.set(crate, pkg.key);
18235
+ addCandidate(context, state, filePath, sourceText, {
18236
+ packageKey: pkg.key,
18237
+ line: lineNumber,
18238
+ matchKind: "import",
18239
+ confidence: 0.94
18240
+ });
18241
+ }
18242
+ continue;
18243
+ }
18244
+ const scopedCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18245
+ if (scopedCall) {
18246
+ const lhs = scopedCall[1];
18247
+ const func = scopedCall[2];
18248
+ const pkgKey = symbolToPackage.get(lhs);
18249
+ if (pkgKey) {
18250
+ addCandidate(context, state, filePath, sourceText, {
18251
+ packageKey: pkgKey,
18252
+ line: lineNumber,
18253
+ matchKind: "call",
18254
+ calledSymbol: `${lhs}::${func}`,
18255
+ confidence: 0.8
18256
+ });
18257
+ }
18258
+ }
18259
+ const methodCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18260
+ if (methodCall) {
18261
+ const lhs = methodCall[1];
18262
+ const method = methodCall[2];
18263
+ const pkgKey = symbolToPackage.get(lhs);
18264
+ if (pkgKey) {
18265
+ addCandidate(context, state, filePath, sourceText, {
18266
+ packageKey: pkgKey,
18267
+ line: lineNumber,
18268
+ matchKind: "call",
18269
+ calledSymbol: `${lhs}.${method}`,
18270
+ confidence: 0.78
18271
+ });
18272
+ }
18273
+ }
18274
+ }
18275
+ }
18276
+ return toAnalyzerResult(state);
18277
+ }
18278
+ patternForPackage(packageName, ecosystem) {
18279
+ const crateName = packageName.replace(/-/g, "_");
18280
+ return {
18281
+ key: packageKey(ecosystem, packageName),
18282
+ crateNames: uniqueTokens([crateName, packageName])
18283
+ };
18284
+ }
18285
+ };
18286
+ function stripLineComment3(line) {
18287
+ const idx = line.indexOf("//");
18288
+ return idx === -1 ? line : line.slice(0, idx);
18289
+ }
18290
+
18291
+ // src/context/analyzers/go-ast-analyzer.ts
18292
+ var GO_EXTENSIONS = /* @__PURE__ */ new Set([".go"]);
18293
+ var GoAstAnalyzer = class {
18294
+ name = "go-ast-analyzer";
18295
+ ecosystems = /* @__PURE__ */ new Set(["go"]);
18296
+ supports(ecosystem) {
18297
+ return this.ecosystems.has(ecosystem);
18298
+ }
18299
+ async analyze(context) {
18300
+ const state = initState(context.packages);
18301
+ const packagePatterns = context.packages.map((pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem));
18302
+ let files;
18303
+ try {
18304
+ files = await collectSourceFiles2(context.projectPath, GO_EXTENSIONS);
18305
+ } catch (err) {
18306
+ return errorResultFromMessage(
18307
+ context,
18308
+ this.name,
18309
+ err instanceof Error ? err.message : String(err),
18310
+ "Failed to enumerate Go source files"
18311
+ );
18312
+ }
18313
+ for (const filePath of files) {
18314
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18315
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18316
+ if (sourceText === null) continue;
18317
+ const aliasesByPackage = /* @__PURE__ */ new Map();
18318
+ for (const pkg of packagePatterns) {
18319
+ aliasesByPackage.set(pkg.key, new Set(pkg.aliases));
18320
+ }
18321
+ const lines = sourceText.split(/\r?\n/);
18322
+ let inImportBlock = false;
18323
+ for (let idx = 0; idx < lines.length; idx++) {
18324
+ const lineNumber = idx + 1;
18325
+ const line = lines[idx];
18326
+ const trimmed = line.trim();
18327
+ if (trimmed.startsWith("import (")) {
18328
+ inImportBlock = true;
18329
+ continue;
18330
+ }
18331
+ if (inImportBlock && trimmed === ")") {
18332
+ inImportBlock = false;
18333
+ continue;
18334
+ }
18335
+ const match = this.extractImport(trimmed, inImportBlock);
18336
+ if (match) {
18337
+ for (const pkg of packagePatterns) {
18338
+ if (match.importPath !== pkg.packageName) continue;
18339
+ const alias = match.alias && match.alias !== "_" && match.alias !== "." ? toIdentifierToken(match.alias) : pkg.aliases[0];
18340
+ if (alias) {
18341
+ aliasesByPackage.get(pkg.key)?.add(alias);
18342
+ }
18343
+ addCandidate(context, state, filePath, sourceText, {
18344
+ packageKey: pkg.key,
18345
+ line: lineNumber,
18346
+ matchKind: "import",
18347
+ confidence: 0.95
18348
+ });
18349
+ }
18350
+ }
18351
+ for (const pkg of packagePatterns) {
18352
+ const aliases = aliasesByPackage.get(pkg.key);
18353
+ if (!aliases) continue;
18354
+ for (const alias of aliases) {
18355
+ if (!alias) continue;
18356
+ const escapedAlias = escapeRegex2(alias);
18357
+ const callRegex = new RegExp(`\\b${escapedAlias}\\s*\\.\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\(`);
18358
+ const callMatch = line.match(callRegex);
18359
+ if (!callMatch) continue;
18360
+ addCandidate(context, state, filePath, sourceText, {
18361
+ packageKey: pkg.key,
18362
+ line: lineNumber,
18363
+ matchKind: "call",
18364
+ calledSymbol: `${alias}.${callMatch[1]}`,
18365
+ confidence: 0.8
18366
+ });
18367
+ }
18368
+ }
18369
+ }
18370
+ }
18371
+ return toAnalyzerResult(state);
18372
+ }
18373
+ patternForPackage(packageName, ecosystem) {
18374
+ const baseName = basePackageName(packageName);
18375
+ const identifierBase = toIdentifierToken(baseName.replace(/^v[0-9]+$/, ""));
18376
+ const shortened = identifierBase.replace(/go$/i, "") || identifierBase;
18377
+ return {
18378
+ key: packageKey(ecosystem, packageName),
18379
+ packageName,
18380
+ aliases: uniqueTokens([identifierBase, shortened])
18381
+ };
18382
+ }
18383
+ extractImport(trimmedLine, inImportBlock) {
18384
+ if (!inImportBlock && !trimmedLine.startsWith("import ")) return null;
18385
+ if (inImportBlock) {
18386
+ const blockMatch = trimmedLine.match(/^(?:(\.|_|[A-Za-z_][A-Za-z0-9_]*)\s+)?\"([^\"]+)\"/);
18387
+ if (!blockMatch) return null;
18388
+ return {
18389
+ alias: blockMatch[1] ?? null,
18390
+ importPath: blockMatch[2]
18391
+ };
18392
+ }
18393
+ const singleMatch = trimmedLine.match(/^import\s+(?:(\.|_|[A-Za-z_][A-Za-z0-9_]*)\s+)?\"([^\"]+)\"/);
18394
+ if (!singleMatch) return null;
18395
+ return {
18396
+ alias: singleMatch[1] ?? null,
18397
+ importPath: singleMatch[2]
18398
+ };
18399
+ }
18400
+ };
18401
+ function escapeRegex2(value) {
18402
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18403
+ }
18404
+
18405
+ // src/context/analyzers/ruby-ast-analyzer.ts
18406
+ var RUBY_EXTENSIONS = /* @__PURE__ */ new Set([".rb"]);
18407
+ var RubyAstAnalyzer = class {
18408
+ name = "ruby-ast-analyzer";
18409
+ ecosystems = /* @__PURE__ */ new Set(["ruby"]);
18410
+ supports(ecosystem) {
18411
+ return this.ecosystems.has(ecosystem);
18412
+ }
18413
+ async analyze(context) {
18414
+ const state = initState(context.packages);
18415
+ const packagePatterns = context.packages.map(
18416
+ (pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem)
18417
+ );
18418
+ let files;
18419
+ try {
18420
+ files = await collectSourceFiles2(context.projectPath, RUBY_EXTENSIONS);
18421
+ } catch (err) {
18422
+ return errorResultFromMessage(
18423
+ context,
18424
+ this.name,
18425
+ err instanceof Error ? err.message : String(err),
18426
+ "Failed to enumerate Ruby source files"
18427
+ );
18428
+ }
18429
+ for (const filePath of files) {
18430
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18431
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18432
+ if (sourceText === null) continue;
18433
+ const lines = sourceText.split(/\r?\n/);
18434
+ const symbolToPackage = /* @__PURE__ */ new Map();
18435
+ for (let idx = 0; idx < lines.length; idx++) {
18436
+ const line = stripInlineComment2(lines[idx]).trim();
18437
+ const lineNumber = idx + 1;
18438
+ if (!line) continue;
18439
+ const requireMatch = line.match(/^require(?:_relative)?\s+['"]([^'"]+)['"]/);
18440
+ if (requireMatch) {
18441
+ const requiredPath = requireMatch[1];
18442
+ for (const pkg of packagePatterns) {
18443
+ if (!pkg.requireCandidates.some((candidate) => requiredPath.includes(candidate))) continue;
18444
+ addCandidate(context, state, filePath, sourceText, {
18445
+ packageKey: pkg.key,
18446
+ line: lineNumber,
18447
+ matchKind: "require",
18448
+ confidence: 0.93
18449
+ });
18450
+ for (const constant of pkg.constantCandidates) {
18451
+ symbolToPackage.set(constant, pkg.key);
18452
+ }
18453
+ }
18454
+ continue;
18455
+ }
18456
+ const includeMatch = line.match(/^include\s+([A-Za-z_][A-Za-z0-9_:]*)/);
18457
+ if (includeMatch) {
18458
+ const moduleName = includeMatch[1];
18459
+ for (const pkg of packagePatterns) {
18460
+ if (!pkg.constantCandidates.some((candidate) => moduleName.startsWith(candidate))) continue;
18461
+ symbolToPackage.set(moduleName.split("::").at(0) ?? moduleName, pkg.key);
18462
+ addCandidate(context, state, filePath, sourceText, {
18463
+ packageKey: pkg.key,
18464
+ line: lineNumber,
18465
+ matchKind: "import",
18466
+ confidence: 0.9
18467
+ });
18468
+ }
18469
+ continue;
18470
+ }
18471
+ const namespacedCall = line.match(/\b([A-Z][A-Za-z0-9_:]*)\s*(?:\.|::)\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18472
+ if (namespacedCall) {
18473
+ const lhs = namespacedCall[1].split("::").at(0) ?? namespacedCall[1];
18474
+ const method = namespacedCall[2];
18475
+ const pkgKey = symbolToPackage.get(lhs) ?? this.findConstantCandidatePackage(lhs, packagePatterns);
18476
+ if (pkgKey) {
18477
+ addCandidate(context, state, filePath, sourceText, {
18478
+ packageKey: pkgKey,
18479
+ line: lineNumber,
18480
+ matchKind: "call",
18481
+ calledSymbol: `${lhs}.${method}`,
18482
+ confidence: 0.78
18483
+ });
18484
+ }
18485
+ }
18486
+ }
18487
+ }
18488
+ return toAnalyzerResult(state);
18489
+ }
18490
+ patternForPackage(packageName, ecosystem) {
18491
+ const base = basePackageName(packageName);
18492
+ const normalized = base.toLowerCase();
18493
+ const requireCandidates = uniqueTokens([
18494
+ normalized,
18495
+ normalized.replace(/-/g, "/"),
18496
+ normalized.replace(/-/g, "_")
18497
+ ]);
18498
+ const constantParts = normalized.replace(/[^a-z0-9_/-]/g, "").split(/[\/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1));
18499
+ const constant = constantParts.join("::");
18500
+ const collapsedConstant = constantParts.join("");
18501
+ return {
18502
+ key: packageKey(ecosystem, packageName),
18503
+ requireCandidates,
18504
+ constantCandidates: uniqueTokens([constant, collapsedConstant, constantParts.at(0) ?? ""])
18505
+ };
18506
+ }
18507
+ findConstantCandidatePackage(constant, packagePatterns) {
18508
+ for (const pkg of packagePatterns) {
18509
+ if (pkg.constantCandidates.includes(constant)) {
18510
+ return pkg.key;
18511
+ }
18512
+ }
18513
+ return null;
18514
+ }
18515
+ };
18516
+ function stripInlineComment2(line) {
18517
+ const idx = line.indexOf("#");
18518
+ return idx === -1 ? line : line.slice(0, idx);
18519
+ }
18520
+
18521
+ // src/context/analyzers/php-ast-analyzer.ts
18522
+ var PHP_EXTENSIONS = /* @__PURE__ */ new Set([".php"]);
18523
+ var PhpAstAnalyzer = class {
18524
+ name = "php-ast-analyzer";
18525
+ ecosystems = /* @__PURE__ */ new Set(["composer"]);
18526
+ supports(ecosystem) {
18527
+ return this.ecosystems.has(ecosystem);
18528
+ }
18529
+ async analyze(context) {
18530
+ const state = initState(context.packages);
18531
+ const packagePatterns = context.packages.map(
18532
+ (pkg) => this.patternForPackage(pkg.packageName, pkg.ecosystem)
18533
+ );
18534
+ let files;
18535
+ try {
18536
+ files = await collectSourceFiles2(context.projectPath, PHP_EXTENSIONS);
18537
+ } catch (err) {
18538
+ return errorResultFromMessage(
18539
+ context,
18540
+ this.name,
18541
+ err instanceof Error ? err.message : String(err),
18542
+ "Failed to enumerate PHP source files"
18543
+ );
18544
+ }
18545
+ for (const filePath of files) {
18546
+ if (state.snippetsProduced >= context.maxSnippetsTotal) break;
18547
+ const sourceText = await readSourceFile(this.name, context.ecosystem, filePath, state.errors);
18548
+ if (sourceText === null) continue;
18549
+ const lines = sourceText.split(/\r?\n/);
18550
+ const symbolToPackage = /* @__PURE__ */ new Map();
18551
+ for (let idx = 0; idx < lines.length; idx++) {
18552
+ const line = stripLineComment4(lines[idx]).trim();
18553
+ const lineNumber = idx + 1;
18554
+ if (!line) continue;
18555
+ const useMatch = line.match(/^use\s+([A-Za-z0-9_\\]+)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?\s*;/i);
18556
+ if (useMatch) {
18557
+ const namespacePath = useMatch[1];
18558
+ const alias = useMatch[2] ?? namespacePath.split("\\").at(-1) ?? namespacePath;
18559
+ for (const pkg of packagePatterns) {
18560
+ const normalizedNamespace = namespacePath.toLowerCase();
18561
+ if (!pkg.namespaceCandidates.some((candidate) => normalizedNamespace.startsWith(candidate.toLowerCase()))) continue;
18562
+ symbolToPackage.set(alias, pkg.key);
18563
+ addCandidate(context, state, filePath, sourceText, {
18564
+ packageKey: pkg.key,
18565
+ line: lineNumber,
18566
+ matchKind: "import",
18567
+ confidence: 0.94
18568
+ });
18569
+ }
18570
+ continue;
18571
+ }
18572
+ const requireMatch = line.match(/^require(?:_once)?\s*\(?\s*['"]([^'"]+)['"]\s*\)?\s*;/i);
18573
+ if (requireMatch) {
18574
+ const pathExpr = requireMatch[1].toLowerCase();
18575
+ for (const pkg of packagePatterns) {
18576
+ const hasVendor = pathExpr.includes(pkg.vendor.toLowerCase());
18577
+ const hasPackage = pathExpr.includes(pkg.package.toLowerCase());
18578
+ if (!hasVendor && !hasPackage) continue;
18579
+ addCandidate(context, state, filePath, sourceText, {
18580
+ packageKey: pkg.key,
18581
+ line: lineNumber,
18582
+ matchKind: "require",
18583
+ confidence: 0.9
18584
+ });
18585
+ }
18586
+ continue;
18587
+ }
18588
+ const staticCall = line.match(/\b([A-Za-z_][A-Za-z0-9_]*)::([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
18589
+ if (staticCall) {
18590
+ const lhs = staticCall[1];
18591
+ const method = staticCall[2];
18592
+ const pkgKey = symbolToPackage.get(lhs) ?? this.findSymbolCandidatePackage(lhs, packagePatterns);
18593
+ if (pkgKey) {
18594
+ addCandidate(context, state, filePath, sourceText, {
18595
+ packageKey: pkgKey,
18596
+ line: lineNumber,
18597
+ matchKind: "call",
18598
+ calledSymbol: `${lhs}::${method}`,
18599
+ confidence: 0.8
18600
+ });
18601
+ }
18602
+ }
18603
+ const constructorMatch = line.match(/new\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
18604
+ if (constructorMatch) {
18605
+ const className = constructorMatch[1];
18606
+ const pkgKey = symbolToPackage.get(className) ?? this.findSymbolCandidatePackage(className, packagePatterns);
18607
+ if (pkgKey) {
18608
+ addCandidate(context, state, filePath, sourceText, {
18609
+ packageKey: pkgKey,
18610
+ line: lineNumber,
18611
+ matchKind: "call",
18612
+ calledSymbol: `new ${className}`,
18613
+ confidence: 0.76
18614
+ });
18615
+ }
18616
+ }
18617
+ }
18618
+ }
18619
+ return toAnalyzerResult(state);
18620
+ }
18621
+ patternForPackage(packageName, ecosystem) {
18622
+ const [vendorRaw, packageRaw] = packageName.split("/");
18623
+ const vendor = vendorRaw ?? packageName;
18624
+ const packagePart = packageRaw ?? packageName;
18625
+ const namespaceCandidates = uniqueTokens([
18626
+ pascalize(vendor),
18627
+ pascalize(packagePart),
18628
+ `${pascalize(vendor)}\\${pascalize(packagePart)}`,
18629
+ `${pascalize(vendor)}\\${pascalize(packagePart.replace(/-/g, "_"))}`
18630
+ ]);
18631
+ const symbolCandidates = uniqueTokens([
18632
+ pascalize(packagePart),
18633
+ pascalize(vendor),
18634
+ pascalize(packagePart).split("\\").at(-1) ?? ""
18635
+ ]);
18636
+ return {
18637
+ key: packageKey(ecosystem, packageName),
18638
+ vendor,
18639
+ package: packagePart,
18640
+ namespaceCandidates,
18641
+ symbolCandidates
18642
+ };
18643
+ }
18644
+ findSymbolCandidatePackage(symbol, packagePatterns) {
18645
+ const normalized = symbol.toLowerCase();
18646
+ for (const pkg of packagePatterns) {
18647
+ if (pkg.symbolCandidates.some((candidate) => candidate.toLowerCase() === normalized)) {
18648
+ return pkg.key;
18649
+ }
18650
+ }
18651
+ return null;
18652
+ }
18653
+ };
18654
+ function pascalize(input) {
18655
+ return input.split(/[\/_.-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("\\");
18656
+ }
18657
+ function stripLineComment4(line) {
18658
+ const slashIdx = line.indexOf("//");
18659
+ if (slashIdx !== -1) return line.slice(0, slashIdx);
18660
+ const hashIdx = line.indexOf("#");
18661
+ return hashIdx === -1 ? line : line.slice(0, hashIdx);
18662
+ }
17637
18663
 
17638
18664
  // src/context/usage-context-engine.ts
17639
18665
  var DEFAULT_MAX_SNIPPETS_PER_PACKAGE = 20;
@@ -17643,41 +18669,13 @@ var UsageContextEngine = class {
17643
18669
  constructor(analyzers) {
17644
18670
  this.analyzers = analyzers ?? [
17645
18671
  new JsAstAnalyzer(),
17646
- new UnsupportedAnalyzer(
17647
- "python-ast-analyzer",
17648
- ["pip", "poetry", "uv"],
17649
- "Python AST analyzer is not yet implemented in this release"
17650
- ),
17651
- new UnsupportedAnalyzer(
17652
- "java-ast-analyzer",
17653
- ["maven"],
17654
- "Java AST analyzer is not yet implemented in this release"
17655
- ),
17656
- new UnsupportedAnalyzer(
17657
- "dotnet-ast-analyzer",
17658
- ["nuget"],
17659
- "NuGet/C# AST analyzer is not yet implemented in this release"
17660
- ),
17661
- new UnsupportedAnalyzer(
17662
- "rust-ast-analyzer",
17663
- ["cargo"],
17664
- "Rust AST analyzer is not yet implemented in this release"
17665
- ),
17666
- new UnsupportedAnalyzer(
17667
- "go-ast-analyzer",
17668
- ["go"],
17669
- "Go AST analyzer is not yet implemented in this release"
17670
- ),
17671
- new UnsupportedAnalyzer(
17672
- "ruby-ast-analyzer",
17673
- ["ruby"],
17674
- "Ruby AST analyzer is not yet implemented in this release"
17675
- ),
17676
- new UnsupportedAnalyzer(
17677
- "php-ast-analyzer",
17678
- ["composer"],
17679
- "PHP AST analyzer is not yet implemented in this release"
17680
- )
18672
+ new PythonAstAnalyzer(),
18673
+ new JavaAstAnalyzer(),
18674
+ new DotnetAstAnalyzer(),
18675
+ new RustAstAnalyzer(),
18676
+ new GoAstAnalyzer(),
18677
+ new RubyAstAnalyzer(),
18678
+ new PhpAstAnalyzer()
17681
18679
  ];
17682
18680
  }
17683
18681
  async analyze(input) {
@@ -17737,7 +18735,7 @@ var UsageContextEngine = class {
17737
18735
  try {
17738
18736
  const result = await analyzer.analyze(runContext);
17739
18737
  const resultByKey = new Map(
17740
- result.packages.map((pkg) => [packageKey(pkg.ecosystem, pkg.packageName), pkg])
18738
+ result.packages.map((pkg) => [packageKey2(pkg.ecosystem, pkg.packageName), pkg])
17741
18739
  );
17742
18740
  errors.push(...result.errors);
17743
18741
  remainingSnippets = Math.max(0, remainingSnippets - result.snippetsProduced);
@@ -17745,7 +18743,7 @@ var UsageContextEngine = class {
17745
18743
  let unsupportedCount = 0;
17746
18744
  let analysisErrorCount = 0;
17747
18745
  for (const pkg of packages) {
17748
- const analyzed = resultByKey.get(packageKey(pkg.ecosystem, pkg.packageName)) ?? {
18746
+ const analyzed = resultByKey.get(packageKey2(pkg.ecosystem, pkg.packageName)) ?? {
17749
18747
  packageName: pkg.packageName,
17750
18748
  ecosystem: pkg.ecosystem,
17751
18749
  status: "analysis_error",
@@ -17850,13 +18848,13 @@ var UsageContextEngine = class {
17850
18848
  buildVulnerablePackages(vulnerabilities, dependencies) {
17851
18849
  const directMap = /* @__PURE__ */ new Map();
17852
18850
  for (const dependency of dependencies) {
17853
- const key = packageKey(dependency.ecosystem, dependency.name);
18851
+ const key = packageKey2(dependency.ecosystem, dependency.name);
17854
18852
  const existing = directMap.get(key) ?? false;
17855
18853
  directMap.set(key, existing || dependency.direct);
17856
18854
  }
17857
18855
  const grouped = /* @__PURE__ */ new Map();
17858
18856
  for (const vulnerability of vulnerabilities) {
17859
- const key = packageKey(vulnerability.ecosystem, vulnerability.packageName);
18857
+ const key = packageKey2(vulnerability.ecosystem, vulnerability.packageName);
17860
18858
  const existing = grouped.get(key);
17861
18859
  if (existing) {
17862
18860
  existing.vulnerabilities.push(vulnerability);
@@ -17880,7 +18878,7 @@ var UsageContextEngine = class {
17880
18878
  return n > 0 ? n : fallback;
17881
18879
  }
17882
18880
  };
17883
- function packageKey(ecosystem, packageName) {
18881
+ function packageKey2(ecosystem, packageName) {
17884
18882
  return `${ecosystem}::${packageName}`;
17885
18883
  }
17886
18884
  function groupByEcosystem(packages) {
@@ -17907,9 +18905,9 @@ async function scan(config) {
17907
18905
  const sbom = artifacts.cyclonedx;
17908
18906
  const outputPaths = deriveArtifactOutputPaths(sbomOutput);
17909
18907
  await Promise.all([
17910
- (0, import_promises15.writeFile)(outputPaths.cyclonedx, artifacts.cyclonedx.content, "utf-8"),
17911
- (0, import_promises15.writeFile)(outputPaths.spdx, artifacts.spdx.content, "utf-8"),
17912
- (0, import_promises15.writeFile)(outputPaths.swid, artifacts.swid.content, "utf-8")
18908
+ (0, import_promises16.writeFile)(outputPaths.cyclonedx, artifacts.cyclonedx.content, "utf-8"),
18909
+ (0, import_promises16.writeFile)(outputPaths.spdx, artifacts.spdx.content, "utf-8"),
18910
+ (0, import_promises16.writeFile)(outputPaths.swid, artifacts.swid.content, "utf-8")
17913
18911
  ]);
17914
18912
  let cveCheck;
17915
18913
  if (skipCveCheck) {
@@ -17951,7 +18949,7 @@ async function scan(config) {
17951
18949
  };
17952
18950
  }
17953
18951
  usageContext.artifactPath = outputPaths.usageContext;
17954
- await (0, import_promises15.writeFile)(outputPaths.usageContext, JSON.stringify(usageContext, null, 2), "utf-8");
18952
+ await (0, import_promises16.writeFile)(outputPaths.usageContext, JSON.stringify(usageContext, null, 2), "utf-8");
17955
18953
  }
17956
18954
  const summary = {
17957
18955
  totalDependencies: scanResult.dependencies.length,
@@ -17989,7 +18987,7 @@ async function uploadToVerimu(report, config) {
17989
18987
  throw new Error("API key required for upload");
17990
18988
  }
17991
18989
  const client = new VerimuApiClient(config.apiKey, config.apiBaseUrl);
17992
- const projectName = (0, import_path16.basename)(config.projectPath);
18990
+ const projectName = (0, import_path17.basename)(config.projectPath);
17993
18991
  const upsertRes = await client.upsertProject({
17994
18992
  name: projectName,
17995
18993
  ecosystem: report.project.ecosystem
@@ -18023,16 +19021,16 @@ function printReport(report) {
18023
19021
  console.log(reporter.report(report));
18024
19022
  }
18025
19023
  function deriveArtifactOutputPaths(cycloneDxOutput) {
18026
- const parsed = (0, import_path16.parse)(cycloneDxOutput);
19024
+ const parsed = (0, import_path17.parse)(cycloneDxOutput);
18027
19025
  let baseName = parsed.name;
18028
19026
  if (parsed.ext === ".json" && baseName.endsWith(".cdx")) {
18029
19027
  baseName = baseName.slice(0, -4);
18030
19028
  }
18031
19029
  return {
18032
19030
  cyclonedx: cycloneDxOutput,
18033
- spdx: (0, import_path16.join)(parsed.dir, `${baseName}.spdx.json`),
18034
- swid: (0, import_path16.join)(parsed.dir, `${baseName}.swid.xml`),
18035
- usageContext: (0, import_path16.join)(parsed.dir, `${baseName}.usage-context.json`)
19031
+ spdx: (0, import_path17.join)(parsed.dir, `${baseName}.spdx.json`),
19032
+ swid: (0, import_path17.join)(parsed.dir, `${baseName}.swid.xml`),
19033
+ usageContext: (0, import_path17.join)(parsed.dir, `${baseName}.usage-context.json`)
18036
19034
  };
18037
19035
  }
18038
19036
  function buildUploadPayload(report) {