@rigour-labs/core 5.0.1 → 5.1.1
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/README.md +9 -1
- package/dist/gates/agent-team.d.ts +0 -1
- package/dist/gates/agent-team.js +0 -1
- package/dist/gates/checkpoint.d.ts +0 -2
- package/dist/gates/checkpoint.js +0 -2
- package/dist/gates/context-window-artifacts.d.ts +6 -2
- package/dist/gates/context-window-artifacts.js +107 -31
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +41 -11
- package/dist/gates/dependency.d.ts +0 -2
- package/dist/gates/dependency.js +23 -5
- package/dist/gates/deprecated-apis.d.ts +0 -2
- package/dist/gates/deprecated-apis.js +33 -20
- package/dist/gates/duplication-drift/index.d.ts +61 -0
- package/dist/gates/duplication-drift/index.js +240 -0
- package/dist/gates/duplication-drift/similarity.d.ts +68 -0
- package/dist/gates/duplication-drift/similarity.js +177 -0
- package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
- package/dist/gates/duplication-drift/tokenizer.js +195 -0
- package/dist/gates/frontend-secret-exposure.d.ts +0 -3
- package/dist/gates/frontend-secret-exposure.js +1 -114
- package/dist/gates/frontend-secret-patterns.d.ts +33 -0
- package/dist/gates/frontend-secret-patterns.js +119 -0
- package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
- package/dist/gates/hallucinated-imports/index.js +174 -0
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
- package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
- package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
- package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
- package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
- package/dist/gates/hallucinated-imports-lang.js +269 -34
- package/dist/gates/hallucinated-imports.test.js +1 -2
- package/dist/gates/inconsistent-error-handling.d.ts +0 -5
- package/dist/gates/inconsistent-error-handling.js +15 -144
- package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/csharp-adapter.js +211 -0
- package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/go-adapter.js +195 -0
- package/dist/gates/language-adapters/index.d.ts +15 -0
- package/dist/gates/language-adapters/index.js +16 -0
- package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/java-adapter.js +237 -0
- package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/js-adapter.js +279 -0
- package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/python-adapter.js +183 -0
- package/dist/gates/language-adapters/registry.d.ts +26 -0
- package/dist/gates/language-adapters/registry.js +65 -0
- package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/ruby-adapter.js +217 -0
- package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
- package/dist/gates/language-adapters/rust-adapter.js +235 -0
- package/dist/gates/language-adapters/types.d.ts +60 -0
- package/dist/gates/language-adapters/types.js +22 -0
- package/dist/gates/logic-drift-extractors.d.ts +15 -0
- package/dist/gates/logic-drift-extractors.js +34 -0
- package/dist/gates/logic-drift.d.ts +0 -30
- package/dist/gates/logic-drift.js +39 -129
- package/dist/gates/phantom-apis.d.ts +0 -2
- package/dist/gates/phantom-apis.js +49 -20
- package/dist/gates/promise-safety.d.ts +0 -1
- package/dist/gates/promise-safety.js +14 -2
- package/dist/gates/runner.js +52 -23
- package/dist/gates/runner.test.js +1 -1
- package/dist/gates/security-patterns-data.d.ts +14 -0
- package/dist/gates/security-patterns-data.js +235 -0
- package/dist/gates/security-patterns.d.ts +17 -3
- package/dist/gates/security-patterns.js +80 -211
- package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
- package/dist/gates/side-effect-analysis/categorizer.js +83 -0
- package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
- package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
- package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
- package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
- package/dist/gates/side-effect-helpers/index.d.ts +4 -0
- package/dist/gates/side-effect-helpers/index.js +4 -0
- package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
- package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
- package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
- package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
- package/dist/gates/side-effect-helpers/types.d.ts +38 -0
- package/dist/gates/side-effect-helpers/types.js +41 -0
- package/dist/gates/side-effect-rules.d.ts +0 -1
- package/dist/gates/side-effect-rules.js +0 -1
- package/dist/gates/style-drift-rules.d.ts +86 -0
- package/dist/gates/style-drift-rules.js +103 -0
- package/dist/gates/style-drift.d.ts +7 -16
- package/dist/gates/style-drift.js +101 -119
- package/dist/gates/test-quality-matchers.d.ts +53 -0
- package/dist/gates/test-quality-matchers.js +86 -0
- package/dist/gates/test-quality.d.ts +0 -3
- package/dist/gates/test-quality.js +47 -44
- package/dist/hooks/checker.d.ts +0 -1
- package/dist/hooks/checker.js +0 -2
- package/dist/hooks/dlp-templates.d.ts +0 -1
- package/dist/hooks/dlp-templates.js +0 -4
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/input-validator.d.ts +0 -1
- package/dist/hooks/input-validator.js +0 -1
- package/dist/hooks/input-validator.test.js +0 -1
- package/dist/hooks/standalone-checker.d.ts +0 -1
- package/dist/hooks/standalone-checker.js +0 -1
- package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
- package/dist/hooks/standalone-dlp-checker.js +0 -1
- package/dist/hooks/templates.d.ts +0 -1
- package/dist/hooks/templates.js +0 -1
- package/dist/hooks/types.d.ts +0 -1
- package/dist/hooks/types.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/index.js +1 -1
- package/dist/services/adaptive-thresholds.d.ts +0 -2
- package/dist/services/adaptive-thresholds.js +0 -2
- package/dist/services/filesystem-cache.d.ts +0 -1
- package/dist/services/filesystem-cache.js +0 -1
- package/dist/services/score-history.d.ts +0 -1
- package/dist/services/score-history.js +0 -1
- package/dist/services/temporal-drift.d.ts +1 -2
- package/dist/services/temporal-drift.js +7 -8
- package/dist/storage/db.d.ts +23 -7
- package/dist/storage/db.js +116 -55
- package/dist/storage/findings.d.ts +4 -3
- package/dist/storage/findings.js +13 -20
- package/dist/storage/local-memory.d.ts +4 -4
- package/dist/storage/local-memory.js +20 -22
- package/dist/storage/patterns.d.ts +5 -5
- package/dist/storage/patterns.js +20 -26
- package/dist/storage/scans.d.ts +6 -6
- package/dist/storage/scans.js +12 -21
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/scanner.js +1 -1
- package/package.json +7 -8
- package/dist/gates/duplication-drift.d.ts +0 -128
- package/dist/gates/duplication-drift.js +0 -585
- package/dist/gates/hallucinated-imports.js +0 -641
- package/dist/gates/side-effect-helpers.d.ts +0 -260
|
@@ -9,8 +9,8 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
9
9
|
const lines = content.split('\n');
|
|
10
10
|
let inImportBlock = false;
|
|
11
11
|
// Find go.mod by walking up from the Go file's directory (monorepo support).
|
|
12
|
-
// A monorepo may have go.mod in subdirectories like kubernetes/go.mod, server/go.mod, etc.
|
|
13
12
|
let modulePath = null;
|
|
13
|
+
const replaceModules = new Set(); // Modules with local replace directives
|
|
14
14
|
const fileAbsDir = path.dirname(path.resolve(cwd, file));
|
|
15
15
|
const rootDir = path.resolve(cwd);
|
|
16
16
|
let searchDir = fileAbsDir;
|
|
@@ -22,8 +22,26 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
22
22
|
const moduleMatch = goMod.match(/^module\s+(\S+)/m);
|
|
23
23
|
if (moduleMatch) {
|
|
24
24
|
modulePath = moduleMatch[1];
|
|
25
|
-
break;
|
|
26
25
|
}
|
|
26
|
+
// Parse replace directives: replace github.com/foo/bar => ../local-bar
|
|
27
|
+
// These modules are valid even without being in go.sum
|
|
28
|
+
const replacePattern = /^replace\s+(\S+)\s+=>\s+/gm;
|
|
29
|
+
let rm;
|
|
30
|
+
while ((rm = replacePattern.exec(goMod)) !== null) {
|
|
31
|
+
replaceModules.add(rm[1]);
|
|
32
|
+
}
|
|
33
|
+
// Also handle replace blocks: replace ( ... )
|
|
34
|
+
const replaceBlockMatch = goMod.match(/^replace\s*\(([\s\S]*?)\)/m);
|
|
35
|
+
if (replaceBlockMatch) {
|
|
36
|
+
const blockLines = replaceBlockMatch[1].split('\n');
|
|
37
|
+
for (const bl of blockLines) {
|
|
38
|
+
const blMatch = bl.trim().match(/^(\S+)\s+=>/);
|
|
39
|
+
if (blMatch)
|
|
40
|
+
replaceModules.add(blMatch[1]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (modulePath)
|
|
44
|
+
break;
|
|
27
45
|
}
|
|
28
46
|
}
|
|
29
47
|
catch { /* skip */ }
|
|
@@ -32,6 +50,30 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
32
50
|
break;
|
|
33
51
|
searchDir = parent;
|
|
34
52
|
}
|
|
53
|
+
// Check for go.work file (Go workspace mode)
|
|
54
|
+
const goWorkModules = new Set();
|
|
55
|
+
try {
|
|
56
|
+
const goWorkPath = path.join(rootDir, 'go.work');
|
|
57
|
+
if (fs.pathExistsSync(goWorkPath)) {
|
|
58
|
+
const goWork = fs.readFileSync(goWorkPath, 'utf-8');
|
|
59
|
+
// use directives: use ./service-a or use ( ./service-a ./service-b )
|
|
60
|
+
const usePattern = /use\s+(\.\S+)/g;
|
|
61
|
+
let um;
|
|
62
|
+
while ((um = usePattern.exec(goWork)) !== null) {
|
|
63
|
+
// Read go.mod from each workspace member
|
|
64
|
+
const memberModPath = path.join(rootDir, um[1], 'go.mod');
|
|
65
|
+
if (fs.pathExistsSync(memberModPath)) {
|
|
66
|
+
const memberMod = fs.readFileSync(memberModPath, 'utf-8');
|
|
67
|
+
const memberModMatch = memberMod.match(/^module\s+(\S+)/m);
|
|
68
|
+
if (memberModMatch)
|
|
69
|
+
goWorkModules.add(memberModMatch[1]);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch { /* skip */ }
|
|
75
|
+
// Check for vendor/ directory — if it exists, all vendored modules are valid
|
|
76
|
+
const hasVendor = fs.pathExistsSync(path.join(rootDir, 'vendor'));
|
|
35
77
|
for (let i = 0; i < lines.length; i++) {
|
|
36
78
|
const line = lines[i].trim();
|
|
37
79
|
// Detect import block: import ( ... )
|
|
@@ -53,7 +95,6 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
53
95
|
if (isGoStdlib(importPath))
|
|
54
96
|
continue;
|
|
55
97
|
// 2. If we have a module path, check project-relative imports FIRST
|
|
56
|
-
// (project imports like github.com/myorg/project/pkg also have dots)
|
|
57
98
|
if (modulePath && importPath.startsWith(modulePath + '/')) {
|
|
58
99
|
const relPath = importPath.slice(modulePath.length + 1);
|
|
59
100
|
const hasMatchingFile = [...projectFiles].some(f => f.endsWith('.go') && f.startsWith(relPath));
|
|
@@ -65,6 +106,29 @@ export function checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
|
65
106
|
}
|
|
66
107
|
continue;
|
|
67
108
|
}
|
|
109
|
+
// 2b. Check if import matches a go.work workspace member module
|
|
110
|
+
let matchesWorkspace = false;
|
|
111
|
+
for (const wm of goWorkModules) {
|
|
112
|
+
if (importPath === wm || importPath.startsWith(wm + '/')) {
|
|
113
|
+
matchesWorkspace = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (matchesWorkspace)
|
|
118
|
+
continue;
|
|
119
|
+
// 2c. Check if import matches a replace directive (local replacement)
|
|
120
|
+
let matchesReplace = false;
|
|
121
|
+
for (const rm of replaceModules) {
|
|
122
|
+
if (importPath === rm || importPath.startsWith(rm + '/')) {
|
|
123
|
+
matchesReplace = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (matchesReplace)
|
|
128
|
+
continue;
|
|
129
|
+
// 2d. If vendor/ exists, all external imports are valid (vendored)
|
|
130
|
+
if (hasVendor && importPath.includes('.'))
|
|
131
|
+
continue;
|
|
68
132
|
// 3. Skip external modules — any import containing a dot is a domain
|
|
69
133
|
// e.g. github.com/*, google.golang.org/*, go.uber.org/*
|
|
70
134
|
if (importPath.includes('.'))
|
|
@@ -128,36 +192,60 @@ export function checkRubyImports(content, file, cwd, projectFiles, hallucinated)
|
|
|
128
192
|
}
|
|
129
193
|
}
|
|
130
194
|
}
|
|
131
|
-
export function loadRubyGems(cwd) {
|
|
195
|
+
export function loadRubyGems(cwd, projectFiles) {
|
|
132
196
|
const gems = new Set();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
197
|
+
const parseGemfile = (filePath) => {
|
|
198
|
+
try {
|
|
199
|
+
if (!fs.pathExistsSync(filePath))
|
|
200
|
+
return;
|
|
201
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
137
202
|
const gemPattern = /gem\s+['"]([^'"]+)['"]/g;
|
|
138
203
|
let m;
|
|
139
204
|
while ((m = gemPattern.exec(content)) !== null) {
|
|
140
205
|
gems.add(m[1]);
|
|
141
206
|
}
|
|
142
207
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
208
|
+
catch { /* skip */ }
|
|
209
|
+
};
|
|
210
|
+
const parseGemspec = (filePath) => {
|
|
211
|
+
try {
|
|
212
|
+
if (!fs.pathExistsSync(filePath))
|
|
213
|
+
return;
|
|
214
|
+
const spec = fs.readFileSync(filePath, 'utf-8');
|
|
215
|
+
// add_runtime_dependency, add_dependency, add_development_dependency
|
|
216
|
+
const depPattern = /add_(?:runtime_|development_)?dependency\s+['"]([^'"]+)['"]/g;
|
|
217
|
+
let dm;
|
|
218
|
+
while ((dm = depPattern.exec(spec)) !== null) {
|
|
219
|
+
gems.add(dm[1]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch { /* skip */ }
|
|
223
|
+
};
|
|
224
|
+
// Parse root Gemfile
|
|
225
|
+
parseGemfile(path.join(cwd, 'Gemfile'));
|
|
226
|
+
// Discover ALL Gemfiles and .gemspec files in the project (monorepo support)
|
|
227
|
+
if (projectFiles) {
|
|
228
|
+
for (const f of projectFiles) {
|
|
229
|
+
if (f.endsWith('Gemfile') || f.endsWith('/Gemfile')) {
|
|
230
|
+
parseGemfile(path.join(cwd, f));
|
|
231
|
+
}
|
|
232
|
+
if (f.endsWith('.gemspec')) {
|
|
233
|
+
parseGemspec(path.join(cwd, f));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Fallback: check root directory only
|
|
239
|
+
try {
|
|
240
|
+
const files = fs.readdirSync?.(cwd) || [];
|
|
241
|
+
for (const f of files) {
|
|
242
|
+
if (typeof f === 'string' && f.endsWith('.gemspec')) {
|
|
243
|
+
parseGemspec(path.join(cwd, f));
|
|
155
244
|
}
|
|
156
|
-
catch { /* skip */ }
|
|
157
245
|
}
|
|
158
246
|
}
|
|
247
|
+
catch { /* skip */ }
|
|
159
248
|
}
|
|
160
|
-
catch { /* no Gemfile */ }
|
|
161
249
|
return gems;
|
|
162
250
|
}
|
|
163
251
|
export function checkCSharpImports(content, file, cwd, projectFiles, hallucinated) {
|
|
@@ -218,16 +306,57 @@ export function loadNuGetPackages(cwd) {
|
|
|
218
306
|
if (typeof f === 'string' && f.endsWith('.csproj')) {
|
|
219
307
|
try {
|
|
220
308
|
const content = fs.readFileSync(path.join(cwd, f), 'utf-8');
|
|
309
|
+
// PackageReference — NuGet packages
|
|
221
310
|
const pkgPattern = /PackageReference\s+Include="([^"]+)"/g;
|
|
222
311
|
let m;
|
|
223
312
|
while ((m = pkgPattern.exec(content)) !== null) {
|
|
224
313
|
packages.add(m[1]);
|
|
225
|
-
// Also add top-level namespace (e.g. Newtonsoft.Json → Newtonsoft)
|
|
226
314
|
packages.add(m[1].split('.')[0]);
|
|
227
315
|
}
|
|
316
|
+
// ProjectReference — sibling projects (their namespaces become valid)
|
|
317
|
+
const projRefPattern = /ProjectReference\s+Include="([^"]+)"/g;
|
|
318
|
+
let pr;
|
|
319
|
+
while ((pr = projRefPattern.exec(content)) !== null) {
|
|
320
|
+
// Extract project name from path: "../MyLib/MyLib.csproj" → "MyLib"
|
|
321
|
+
const projName = path.basename(pr[1], '.csproj');
|
|
322
|
+
packages.add(projName);
|
|
323
|
+
}
|
|
324
|
+
// RootNamespace — project's own root namespace
|
|
325
|
+
const nsMatch = content.match(/<RootNamespace>([^<]+)<\/RootNamespace>/);
|
|
326
|
+
if (nsMatch) {
|
|
327
|
+
packages.add(nsMatch[1]);
|
|
328
|
+
packages.add(nsMatch[1].split('.')[0]);
|
|
329
|
+
}
|
|
228
330
|
}
|
|
229
331
|
catch { /* skip */ }
|
|
230
332
|
}
|
|
333
|
+
// Legacy packages.config format
|
|
334
|
+
if (typeof f === 'string' && f === 'packages.config') {
|
|
335
|
+
try {
|
|
336
|
+
const content = fs.readFileSync(path.join(cwd, f), 'utf-8');
|
|
337
|
+
const pkgPattern = /id="([^"]+)"/g;
|
|
338
|
+
let m;
|
|
339
|
+
while ((m = pkgPattern.exec(content)) !== null) {
|
|
340
|
+
packages.add(m[1]);
|
|
341
|
+
packages.add(m[1].split('.')[0]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch { /* skip */ }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Check Directory.Build.props for shared package references
|
|
348
|
+
const buildPropsPath = path.join(cwd, 'Directory.Build.props');
|
|
349
|
+
if (fs.pathExistsSync(buildPropsPath)) {
|
|
350
|
+
try {
|
|
351
|
+
const content = fs.readFileSync(buildPropsPath, 'utf-8');
|
|
352
|
+
const pkgPattern = /PackageReference\s+Include="([^"]+)"/g;
|
|
353
|
+
let m;
|
|
354
|
+
while ((m = pkgPattern.exec(content)) !== null) {
|
|
355
|
+
packages.add(m[1]);
|
|
356
|
+
packages.add(m[1].split('.')[0]);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch { /* skip */ }
|
|
231
360
|
}
|
|
232
361
|
}
|
|
233
362
|
catch { /* no .csproj */ }
|
|
@@ -294,31 +423,85 @@ export function checkRustImports(content, file, cwd, projectFiles, hallucinated)
|
|
|
294
423
|
}
|
|
295
424
|
export function loadCargoDeps(cwd) {
|
|
296
425
|
const deps = new Set();
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
426
|
+
const parseCargoToml = (filePath) => {
|
|
427
|
+
try {
|
|
428
|
+
if (!fs.pathExistsSync(filePath))
|
|
429
|
+
return;
|
|
430
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
431
|
+
// Parse ALL dependency sections: [dependencies], [dev-dependencies], [build-dependencies]
|
|
432
|
+
// Also handle [target.'cfg(...)'.dependencies]
|
|
303
433
|
let inDeps = false;
|
|
304
434
|
for (const line of content.split('\n')) {
|
|
305
|
-
|
|
435
|
+
const trimmed = line.trim();
|
|
436
|
+
// Match any dependencies section header
|
|
437
|
+
if (/^\[(?:(?:dev|build)-)?dependencies(?:\.]|\])/.test(trimmed) ||
|
|
438
|
+
/^\[target\.[^\]]*\.(?:(?:dev|build)-)?dependencies\]/.test(trimmed) ||
|
|
439
|
+
/^\[workspace\.dependencies\]/.test(trimmed)) {
|
|
306
440
|
inDeps = true;
|
|
307
441
|
continue;
|
|
308
442
|
}
|
|
309
|
-
if (/^\[/.test(
|
|
443
|
+
if (/^\[/.test(trimmed) && inDeps) {
|
|
310
444
|
inDeps = false;
|
|
311
445
|
continue;
|
|
312
446
|
}
|
|
313
447
|
if (inDeps) {
|
|
314
|
-
const m =
|
|
448
|
+
const m = trimmed.match(/^([\w][\w-]*)\s*=/);
|
|
315
449
|
if (m)
|
|
316
450
|
deps.add(m[1].replace(/-/g, '_')); // Rust uses _ in code for - in Cargo
|
|
317
451
|
}
|
|
318
452
|
}
|
|
453
|
+
// Handle [dependencies.foo] format (inline table)
|
|
454
|
+
const inlineDepPattern = /^\[(?:(?:dev|build)-)?dependencies\.([\w][\w-]*)\]/gm;
|
|
455
|
+
let dm;
|
|
456
|
+
while ((dm = inlineDepPattern.exec(content)) !== null) {
|
|
457
|
+
deps.add(dm[1].replace(/-/g, '_'));
|
|
458
|
+
}
|
|
459
|
+
// Handle [features] section — optional deps are valid when feature is enabled
|
|
460
|
+
let inFeatures = false;
|
|
461
|
+
for (const line of content.split('\n')) {
|
|
462
|
+
const trimmed = line.trim();
|
|
463
|
+
if (trimmed === '[features]') {
|
|
464
|
+
inFeatures = true;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (/^\[/.test(trimmed) && inFeatures) {
|
|
468
|
+
inFeatures = false;
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (inFeatures) {
|
|
472
|
+
// Features reference dep names: feature = ["dep:foo", "bar/feature"]
|
|
473
|
+
const depRefs = trimmed.match(/"dep:(\w[\w-]*)"/g);
|
|
474
|
+
if (depRefs) {
|
|
475
|
+
for (const ref of depRefs) {
|
|
476
|
+
const name = ref.match(/"dep:(\w[\w-]*)"/)?.[1];
|
|
477
|
+
if (name)
|
|
478
|
+
deps.add(name.replace(/-/g, '_'));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
319
483
|
}
|
|
484
|
+
catch { /* skip */ }
|
|
485
|
+
};
|
|
486
|
+
// Parse the Cargo.toml in the working directory
|
|
487
|
+
parseCargoToml(path.join(cwd, 'Cargo.toml'));
|
|
488
|
+
// Walk up to find workspace Cargo.toml
|
|
489
|
+
let searchDir = cwd;
|
|
490
|
+
const rootDir = path.parse(cwd).root;
|
|
491
|
+
while (searchDir !== rootDir) {
|
|
492
|
+
const parentCargoPath = path.join(searchDir, 'Cargo.toml');
|
|
493
|
+
if (fs.pathExistsSync(parentCargoPath)) {
|
|
494
|
+
const content = fs.readFileSync(parentCargoPath, 'utf-8');
|
|
495
|
+
if (content.includes('[workspace]')) {
|
|
496
|
+
parseCargoToml(parentCargoPath);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
const parent = path.dirname(searchDir);
|
|
501
|
+
if (parent === searchDir)
|
|
502
|
+
break;
|
|
503
|
+
searchDir = parent;
|
|
320
504
|
}
|
|
321
|
-
catch { /* no Cargo.toml */ }
|
|
322
505
|
return deps;
|
|
323
506
|
}
|
|
324
507
|
export function checkJavaKotlinImports(content, file, ext, cwd, projectFiles, hallucinated) {
|
|
@@ -375,11 +558,45 @@ export function loadJavaDeps(cwd) {
|
|
|
375
558
|
if (fs.pathExistsSync(gradlePath)) {
|
|
376
559
|
const content = fs.readFileSync(gradlePath, 'utf-8');
|
|
377
560
|
// Match: implementation 'group:artifact:version' or "group:artifact:version"
|
|
378
|
-
const depPattern = /(?:implementation|api|compile|testImplementation|runtimeOnly)\s*[('"]([^:'"]+)/g;
|
|
561
|
+
const depPattern = /(?:implementation|api|compile|testImplementation|testCompile|runtimeOnly|compileOnly|annotationProcessor)\s*[('"]([^:'"]+)/g;
|
|
379
562
|
let m;
|
|
380
563
|
while ((m = depPattern.exec(content)) !== null) {
|
|
381
564
|
deps.add(m[1]); // group ID like "com.google.guava"
|
|
382
565
|
}
|
|
566
|
+
// Also match Kotlin DSL format: implementation("group:artifact:version")
|
|
567
|
+
const kotlinDslPattern = /(?:implementation|api|testImplementation|runtimeOnly|compileOnly)\s*\(\s*"([^:"]+)/g;
|
|
568
|
+
while ((m = kotlinDslPattern.exec(content)) !== null) {
|
|
569
|
+
deps.add(m[1]);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// Gradle settings.gradle(.kts) — find included modules
|
|
574
|
+
for (const settingsFile of ['settings.gradle', 'settings.gradle.kts']) {
|
|
575
|
+
const settingsPath = path.join(cwd, settingsFile);
|
|
576
|
+
if (fs.pathExistsSync(settingsPath)) {
|
|
577
|
+
try {
|
|
578
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
579
|
+
// include ':module-a', ':module-b'
|
|
580
|
+
const includePattern = /include\s*[('"]([^'"]+)/g;
|
|
581
|
+
let im;
|
|
582
|
+
while ((im = includePattern.exec(content)) !== null) {
|
|
583
|
+
const moduleName = im[1].replace(/^:/, '');
|
|
584
|
+
deps.add(moduleName);
|
|
585
|
+
}
|
|
586
|
+
// includeBuild('../composite-project')
|
|
587
|
+
const compositePat = /includeBuild\s*\(\s*['"]([^'"]+)/g;
|
|
588
|
+
while ((im = compositePat.exec(content)) !== null) {
|
|
589
|
+
// Read the included build's group
|
|
590
|
+
const compositePath = path.resolve(cwd, im[1], 'build.gradle');
|
|
591
|
+
if (fs.pathExistsSync(compositePath)) {
|
|
592
|
+
const compositeContent = fs.readFileSync(compositePath, 'utf-8');
|
|
593
|
+
const groupMatch = compositeContent.match(/group\s*=\s*['"]([^'"]+)/);
|
|
594
|
+
if (groupMatch)
|
|
595
|
+
deps.add(groupMatch[1]);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
catch { /* skip */ }
|
|
383
600
|
}
|
|
384
601
|
}
|
|
385
602
|
// Maven: pom.xml
|
|
@@ -391,6 +608,24 @@ export function loadJavaDeps(cwd) {
|
|
|
391
608
|
while ((m = groupPattern.exec(content)) !== null) {
|
|
392
609
|
deps.add(m[1]);
|
|
393
610
|
}
|
|
611
|
+
// Maven <modules> — multi-module projects
|
|
612
|
+
const modulePattern = /<module>([^<]+)<\/module>/g;
|
|
613
|
+
while ((m = modulePattern.exec(content)) !== null) {
|
|
614
|
+
deps.add(m[1]);
|
|
615
|
+
// Also load deps from submodule pom.xml
|
|
616
|
+
const subPomPath = path.join(cwd, m[1], 'pom.xml');
|
|
617
|
+
if (fs.pathExistsSync(subPomPath)) {
|
|
618
|
+
try {
|
|
619
|
+
const subContent = fs.readFileSync(subPomPath, 'utf-8');
|
|
620
|
+
const subGroupPattern = /<groupId>([^<]+)<\/groupId>/g;
|
|
621
|
+
let sm;
|
|
622
|
+
while ((sm = subGroupPattern.exec(subContent)) !== null) {
|
|
623
|
+
deps.add(sm[1]);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
catch { /* skip */ }
|
|
627
|
+
}
|
|
628
|
+
}
|
|
394
629
|
}
|
|
395
630
|
}
|
|
396
631
|
catch { /* no build files */ }
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
* Tests the fix for Go stdlib false positives (PicoClaw regression)
|
|
7
7
|
* and validates all 8 language checkers for false positive/negative accuracy.
|
|
8
8
|
*
|
|
9
|
-
* @since v3.0.1
|
|
10
9
|
*/
|
|
11
10
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
|
-
import { HallucinatedImportsGate } from './hallucinated-imports.js';
|
|
11
|
+
import { HallucinatedImportsGate } from './hallucinated-imports/index.js';
|
|
13
12
|
import path from 'path';
|
|
14
13
|
// Mock fs-extra — vi.hoisted ensures these are available when vi.mock runs (hoisted)
|
|
15
14
|
const { mockPathExists, mockPathExistsSync, mockReadFile, mockReadFileSync, mockReadJson, mockReaddirSync } = vi.hoisted(() => ({
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
* - File C: catch(e) { return null }
|
|
18
18
|
* - File D: catch(e) { [empty] }
|
|
19
19
|
*
|
|
20
|
-
* @since v2.16.0
|
|
21
20
|
*/
|
|
22
21
|
import { Gate, GateContext } from './base.js';
|
|
23
22
|
import { Failure, Provenance } from '../types/index.js';
|
|
@@ -32,9 +31,5 @@ export declare class InconsistentErrorHandlingGate extends Gate {
|
|
|
32
31
|
constructor(config?: InconsistentErrorHandlingConfig);
|
|
33
32
|
protected get provenance(): Provenance;
|
|
34
33
|
run(context: GateContext): Promise<Failure[]>;
|
|
35
|
-
private extractErrorHandlers;
|
|
36
|
-
private classifyStrategy;
|
|
37
|
-
private extractCatchBody;
|
|
38
|
-
private extractCatchCallbackBody;
|
|
39
34
|
private shouldSkipFile;
|
|
40
35
|
}
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
* - File C: catch(e) { return null }
|
|
18
18
|
* - File D: catch(e) { [empty] }
|
|
19
19
|
*
|
|
20
|
-
* @since v2.16.0
|
|
21
20
|
*/
|
|
22
21
|
import { Gate } from './base.js';
|
|
23
22
|
import { FileScanner } from '../utils/scanner.js';
|
|
24
23
|
import { Logger } from '../utils/logger.js';
|
|
24
|
+
import { languageAdapters } from './language-adapters/index.js';
|
|
25
25
|
import fs from 'fs-extra';
|
|
26
26
|
import path from 'path';
|
|
27
27
|
export class InconsistentErrorHandlingGate extends Gate {
|
|
@@ -41,7 +41,7 @@ export class InconsistentErrorHandlingGate extends Gate {
|
|
|
41
41
|
return [];
|
|
42
42
|
const failures = [];
|
|
43
43
|
const handlers = [];
|
|
44
|
-
const scanPatterns = context.patterns ||
|
|
44
|
+
const scanPatterns = context.patterns || languageAdapters.getScanPatterns();
|
|
45
45
|
const files = await FileScanner.findFiles({
|
|
46
46
|
cwd: context.cwd,
|
|
47
47
|
patterns: scanPatterns,
|
|
@@ -53,7 +53,19 @@ export class InconsistentErrorHandlingGate extends Gate {
|
|
|
53
53
|
continue;
|
|
54
54
|
try {
|
|
55
55
|
const content = await fs.readFile(path.join(context.cwd, file), 'utf-8');
|
|
56
|
-
|
|
56
|
+
const adapter = languageAdapters.getAdapter(file);
|
|
57
|
+
if (!adapter)
|
|
58
|
+
continue;
|
|
59
|
+
const errorHandlerFacts = adapter.extractErrorHandlers(content);
|
|
60
|
+
for (const fact of errorHandlerFacts) {
|
|
61
|
+
handlers.push({
|
|
62
|
+
file,
|
|
63
|
+
line: fact.startLine,
|
|
64
|
+
errorType: fact.type,
|
|
65
|
+
strategy: fact.strategy,
|
|
66
|
+
rawPattern: fact.body.split('\n')[0]?.trim() || '',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
57
69
|
}
|
|
58
70
|
catch (e) { }
|
|
59
71
|
}
|
|
@@ -96,147 +108,6 @@ export class InconsistentErrorHandlingGate extends Gate {
|
|
|
96
108
|
}
|
|
97
109
|
return failures;
|
|
98
110
|
}
|
|
99
|
-
extractErrorHandlers(content, file, handlers) {
|
|
100
|
-
const lines = content.split('\n');
|
|
101
|
-
for (let i = 0; i < lines.length; i++) {
|
|
102
|
-
// Match catch clauses: catch (e), catch (error: Error), catch (e: TypeError)
|
|
103
|
-
const catchMatch = lines[i].match(/\bcatch\s*\(\s*(\w+)(?:\s*:\s*(\w+))?\s*\)/);
|
|
104
|
-
if (!catchMatch)
|
|
105
|
-
continue;
|
|
106
|
-
const varName = catchMatch[1];
|
|
107
|
-
const explicitType = catchMatch[2] || 'any';
|
|
108
|
-
// Extract catch body (up to next closing brace at same level)
|
|
109
|
-
const body = this.extractCatchBody(lines, i);
|
|
110
|
-
if (!body)
|
|
111
|
-
continue;
|
|
112
|
-
const strategy = this.classifyStrategy(body, varName);
|
|
113
|
-
const rawPattern = body.split('\n')[0]?.trim() || '';
|
|
114
|
-
handlers.push({
|
|
115
|
-
file,
|
|
116
|
-
line: i + 1,
|
|
117
|
-
errorType: explicitType,
|
|
118
|
-
strategy,
|
|
119
|
-
rawPattern,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
// Also detect .catch() promise patterns
|
|
123
|
-
for (let i = 0; i < lines.length; i++) {
|
|
124
|
-
const catchMatch = lines[i].match(/\.catch\s*\(\s*(?:async\s+)?(?:\(\s*)?(\w+)/);
|
|
125
|
-
if (!catchMatch)
|
|
126
|
-
continue;
|
|
127
|
-
const varName = catchMatch[1];
|
|
128
|
-
// Extract the callback body
|
|
129
|
-
const body = this.extractCatchCallbackBody(lines, i);
|
|
130
|
-
if (!body)
|
|
131
|
-
continue;
|
|
132
|
-
const strategy = this.classifyStrategy(body, varName);
|
|
133
|
-
handlers.push({
|
|
134
|
-
file,
|
|
135
|
-
line: i + 1,
|
|
136
|
-
errorType: 'Promise',
|
|
137
|
-
strategy,
|
|
138
|
-
rawPattern: body.split('\n')[0]?.trim() || '',
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
classifyStrategy(body, varName) {
|
|
143
|
-
const trimmed = body.trim();
|
|
144
|
-
// Empty catch (swallow)
|
|
145
|
-
if (!trimmed || trimmed === '{}' || trimmed === '') {
|
|
146
|
-
return 'swallow';
|
|
147
|
-
}
|
|
148
|
-
// Re-throw
|
|
149
|
-
if (/\bthrow\b/.test(trimmed)) {
|
|
150
|
-
if (/\bthrow\s+new\b/.test(trimmed))
|
|
151
|
-
return 'wrap-and-throw';
|
|
152
|
-
if (new RegExp(`\\bthrow\\s+${varName}\\b`).test(trimmed))
|
|
153
|
-
return 'rethrow';
|
|
154
|
-
return 'throw-new';
|
|
155
|
-
}
|
|
156
|
-
// Return patterns
|
|
157
|
-
if (/\breturn\s+null\b/.test(trimmed))
|
|
158
|
-
return 'return-null';
|
|
159
|
-
if (/\breturn\s+undefined\b/.test(trimmed) || /\breturn\s*;/.test(trimmed))
|
|
160
|
-
return 'return-undefined';
|
|
161
|
-
if (/\breturn\s+\[\s*\]/.test(trimmed))
|
|
162
|
-
return 'return-empty-array';
|
|
163
|
-
if (/\breturn\s+\{\s*\}/.test(trimmed))
|
|
164
|
-
return 'return-empty-object';
|
|
165
|
-
if (/\breturn\s+false\b/.test(trimmed))
|
|
166
|
-
return 'return-false';
|
|
167
|
-
if (/\breturn\s+/.test(trimmed))
|
|
168
|
-
return 'return-value';
|
|
169
|
-
// Logging patterns
|
|
170
|
-
if (/console\.(error|warn)\b/.test(trimmed))
|
|
171
|
-
return 'log-error';
|
|
172
|
-
if (/console\.log\b/.test(trimmed))
|
|
173
|
-
return 'log-info';
|
|
174
|
-
if (/\blogger\b/i.test(trimmed) || /\blog\b/i.test(trimmed))
|
|
175
|
-
return 'log-custom';
|
|
176
|
-
// Process.exit
|
|
177
|
-
if (/process\.exit\b/.test(trimmed))
|
|
178
|
-
return 'exit';
|
|
179
|
-
// Response patterns (Express/HTTP)
|
|
180
|
-
if (/\bres\s*\.\s*status\b/.test(trimmed) || /\bres\s*\.\s*json\b/.test(trimmed))
|
|
181
|
-
return 'http-response';
|
|
182
|
-
// Notification patterns
|
|
183
|
-
if (/\bnotif|toast|alert|modal\b/i.test(trimmed))
|
|
184
|
-
return 'user-notification';
|
|
185
|
-
return 'other';
|
|
186
|
-
}
|
|
187
|
-
extractCatchBody(lines, catchLine) {
|
|
188
|
-
let braceDepth = 0;
|
|
189
|
-
let started = false;
|
|
190
|
-
const body = [];
|
|
191
|
-
for (let i = catchLine; i < lines.length; i++) {
|
|
192
|
-
for (const ch of lines[i]) {
|
|
193
|
-
if (ch === '{') {
|
|
194
|
-
braceDepth++;
|
|
195
|
-
started = true;
|
|
196
|
-
}
|
|
197
|
-
if (ch === '}')
|
|
198
|
-
braceDepth--;
|
|
199
|
-
}
|
|
200
|
-
if (started && i > catchLine)
|
|
201
|
-
body.push(lines[i]);
|
|
202
|
-
if (started && braceDepth === 0)
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
return body.length > 0 ? body.join('\n') : null;
|
|
206
|
-
}
|
|
207
|
-
extractCatchCallbackBody(lines, startLine) {
|
|
208
|
-
// Detect if this is an arrow function (.catch(e => { ... }))
|
|
209
|
-
const hasArrow = lines[startLine]?.includes('=>');
|
|
210
|
-
let braceDepth = 0;
|
|
211
|
-
let started = false;
|
|
212
|
-
const body = [];
|
|
213
|
-
for (let i = startLine; i < Math.min(startLine + 20, lines.length); i++) {
|
|
214
|
-
for (const ch of lines[i]) {
|
|
215
|
-
// For arrow functions, only track braces (not parens) for body extraction
|
|
216
|
-
if (hasArrow) {
|
|
217
|
-
if (ch === '{') {
|
|
218
|
-
braceDepth++;
|
|
219
|
-
started = true;
|
|
220
|
-
}
|
|
221
|
-
if (ch === '}')
|
|
222
|
-
braceDepth--;
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
if (ch === '{' || ch === '(') {
|
|
226
|
-
braceDepth++;
|
|
227
|
-
started = true;
|
|
228
|
-
}
|
|
229
|
-
if (ch === '}' || ch === ')')
|
|
230
|
-
braceDepth--;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (started && i > startLine)
|
|
234
|
-
body.push(lines[i]);
|
|
235
|
-
if (started && braceDepth <= 0)
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
return body.length > 0 ? body.join('\n') : null;
|
|
239
|
-
}
|
|
240
111
|
shouldSkipFile(file) {
|
|
241
112
|
const normalized = file.replace(/\\/g, '/');
|
|
242
113
|
return (normalized.includes('/examples/') ||
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LanguageAdapter, FunctionFact, ImportFact, ErrorHandlerFact, NamingPattern } from './types.js';
|
|
2
|
+
declare class CSharpAdapter implements LanguageAdapter {
|
|
3
|
+
readonly id = "csharp";
|
|
4
|
+
readonly name = "C#";
|
|
5
|
+
readonly extensions: string[];
|
|
6
|
+
extractFunctions(source: string): FunctionFact[];
|
|
7
|
+
extractImports(source: string): ImportFact[];
|
|
8
|
+
extractErrorHandlers(source: string): ErrorHandlerFact[];
|
|
9
|
+
extractNamingPatterns(source: string): NamingPattern[];
|
|
10
|
+
stripComments(source: string): string;
|
|
11
|
+
extractComparisonOps(source: string): string[];
|
|
12
|
+
countBranches(source: string): number;
|
|
13
|
+
countReturns(source: string): number;
|
|
14
|
+
}
|
|
15
|
+
export declare const csharpAdapter: CSharpAdapter;
|
|
16
|
+
export {};
|