@rigour-labs/core 5.0.0 → 5.1.0
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 +51 -22
- 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 +1 -3
- 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 +6 -1
- package/dist/hooks/templates.js +6 -1
- package/dist/hooks/types.d.ts +1 -2
- package/dist/hooks/types.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rust Language Adapter
|
|
3
|
+
*
|
|
4
|
+
* Handles Rust-specific code analysis:
|
|
5
|
+
* - Function extraction (fn, pub fn, pub async fn)
|
|
6
|
+
* - Import parsing (use statements)
|
|
7
|
+
* - Error handling (match, unwrap_or_else)
|
|
8
|
+
* - Naming conventions (snake_case functions, PascalCase types)
|
|
9
|
+
*/
|
|
10
|
+
import { classifyCasing } from './types.js';
|
|
11
|
+
export class RustAdapter {
|
|
12
|
+
id = 'rust';
|
|
13
|
+
name = 'Rust';
|
|
14
|
+
extensions = ['.rs'];
|
|
15
|
+
extractFunctions(source, filePath) {
|
|
16
|
+
const functions = [];
|
|
17
|
+
const lines = source.split('\n');
|
|
18
|
+
// Match: fn name(...), pub fn, pub async fn, impl blocks with methods
|
|
19
|
+
const fnRegex = /^\s*(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*\(/;
|
|
20
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21
|
+
const match = lines[i].match(fnRegex);
|
|
22
|
+
if (!match)
|
|
23
|
+
continue;
|
|
24
|
+
const name = match[1];
|
|
25
|
+
const startLine = i + 1;
|
|
26
|
+
const body = this.extractBraceBody(lines, i);
|
|
27
|
+
const bodyStr = body.join('\n');
|
|
28
|
+
const isAsync = /\basync\s+fn\b/.test(lines[i]);
|
|
29
|
+
functions.push({
|
|
30
|
+
name,
|
|
31
|
+
startLine,
|
|
32
|
+
endLine: startLine + body.length,
|
|
33
|
+
body: bodyStr,
|
|
34
|
+
isAsync,
|
|
35
|
+
isExported: /\bpub\b/.test(lines[i]),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return functions;
|
|
39
|
+
}
|
|
40
|
+
extractImports(source) {
|
|
41
|
+
const imports = [];
|
|
42
|
+
const lines = source.split('\n');
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
const line = lines[i].trim();
|
|
45
|
+
// use std::foo;
|
|
46
|
+
// use crate::bar;
|
|
47
|
+
// extern crate baz;
|
|
48
|
+
let match = line.match(/^\s*use\s+([\w:]+)(?:\s*::\s*\{([^}]+)\})?;/);
|
|
49
|
+
if (match) {
|
|
50
|
+
const module = match[1];
|
|
51
|
+
const names = match[2] ? match[2].split(',').map(n => n.trim()) : [];
|
|
52
|
+
imports.push({
|
|
53
|
+
module,
|
|
54
|
+
names,
|
|
55
|
+
line: i + 1,
|
|
56
|
+
isDynamic: false,
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
match = line.match(/^\s*extern\s+crate\s+(\w+);/);
|
|
61
|
+
if (match) {
|
|
62
|
+
imports.push({
|
|
63
|
+
module: match[1],
|
|
64
|
+
names: [],
|
|
65
|
+
line: i + 1,
|
|
66
|
+
isDynamic: false,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return imports;
|
|
71
|
+
}
|
|
72
|
+
extractErrorHandlers(source) {
|
|
73
|
+
const handlers = [];
|
|
74
|
+
const lines = source.split('\n');
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
const line = lines[i];
|
|
77
|
+
// Match: match result { Err(e) =>
|
|
78
|
+
if (/match\s+/.test(line)) {
|
|
79
|
+
const body = this.extractBraceBody(lines, i).join('\n');
|
|
80
|
+
const strategy = this.classifyRustErrorStrategy(body);
|
|
81
|
+
handlers.push({
|
|
82
|
+
type: 'match',
|
|
83
|
+
strategy,
|
|
84
|
+
startLine: i + 1,
|
|
85
|
+
body,
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Match: .unwrap_or_else(|e| ...)
|
|
90
|
+
if (/\.unwrap_or_else\s*\(\s*\|/.test(line)) {
|
|
91
|
+
const body = this.extractCallbackBody(lines, i);
|
|
92
|
+
const strategy = this.classifyRustErrorStrategy(body);
|
|
93
|
+
handlers.push({
|
|
94
|
+
type: 'unwrap_or_else',
|
|
95
|
+
strategy,
|
|
96
|
+
startLine: i + 1,
|
|
97
|
+
body,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return handlers;
|
|
102
|
+
}
|
|
103
|
+
extractNamingPatterns(source) {
|
|
104
|
+
const patterns = [];
|
|
105
|
+
const lines = source.split('\n');
|
|
106
|
+
for (let i = 0; i < lines.length; i++) {
|
|
107
|
+
const line = lines[i];
|
|
108
|
+
// Function definitions
|
|
109
|
+
let match = line.match(/^\s*(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)/);
|
|
110
|
+
if (match) {
|
|
111
|
+
const name = match[1];
|
|
112
|
+
patterns.push({
|
|
113
|
+
name,
|
|
114
|
+
kind: 'function',
|
|
115
|
+
convention: classifyCasing(name),
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Struct definitions
|
|
120
|
+
match = line.match(/^\s*(?:pub\s+)?struct\s+(\w+)/);
|
|
121
|
+
if (match) {
|
|
122
|
+
const name = match[1];
|
|
123
|
+
patterns.push({
|
|
124
|
+
name,
|
|
125
|
+
kind: 'class',
|
|
126
|
+
convention: classifyCasing(name),
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// Enum definitions
|
|
131
|
+
match = line.match(/^\s*(?:pub\s+)?enum\s+(\w+)/);
|
|
132
|
+
if (match) {
|
|
133
|
+
const name = match[1];
|
|
134
|
+
patterns.push({
|
|
135
|
+
name,
|
|
136
|
+
kind: 'class',
|
|
137
|
+
convention: classifyCasing(name),
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Const declarations
|
|
142
|
+
match = line.match(/^\s*(?:pub\s+)?const\s+(\w+)\s*:/);
|
|
143
|
+
if (match) {
|
|
144
|
+
const name = match[1];
|
|
145
|
+
patterns.push({
|
|
146
|
+
name,
|
|
147
|
+
kind: 'constant',
|
|
148
|
+
convention: classifyCasing(name),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return patterns;
|
|
153
|
+
}
|
|
154
|
+
stripComments(source) {
|
|
155
|
+
// Remove line comments: //
|
|
156
|
+
let result = source.replace(/\/\/.*/g, '');
|
|
157
|
+
// Remove block comments: /* */
|
|
158
|
+
result = result.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
extractComparisonOps(source) {
|
|
162
|
+
const ops = [];
|
|
163
|
+
const cleaned = this.stripComments(source);
|
|
164
|
+
const matches = cleaned.matchAll(/(==|!=|>=|<=|>(?!=)|<(?!=))/g);
|
|
165
|
+
for (const m of matches) {
|
|
166
|
+
ops.push(m[1]);
|
|
167
|
+
}
|
|
168
|
+
return ops;
|
|
169
|
+
}
|
|
170
|
+
countBranches(source) {
|
|
171
|
+
let count = 0;
|
|
172
|
+
const cleaned = this.stripComments(source);
|
|
173
|
+
count += (cleaned.match(/\bif\s+/g) || []).length;
|
|
174
|
+
count += (cleaned.match(/\belse\s+if\s+/g) || []).length;
|
|
175
|
+
count += (cleaned.match(/\belse\s*\{/g) || []).length;
|
|
176
|
+
count += (cleaned.match(/\bmatch\s+/g) || []).length;
|
|
177
|
+
count += (cleaned.match(/=>\s*/g) || []).length;
|
|
178
|
+
return count;
|
|
179
|
+
}
|
|
180
|
+
countReturns(source) {
|
|
181
|
+
// Count explicit return + ? operator (early returns)
|
|
182
|
+
let count = (source.match(/\breturn\b/g) || []).length;
|
|
183
|
+
count += (source.match(/\?\s*(?:;|,|\))/g) || []).length;
|
|
184
|
+
return count;
|
|
185
|
+
}
|
|
186
|
+
extractBraceBody(lines, startIndex) {
|
|
187
|
+
let braceDepth = 0;
|
|
188
|
+
let started = false;
|
|
189
|
+
const body = [];
|
|
190
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
191
|
+
const line = lines[i];
|
|
192
|
+
for (const ch of line) {
|
|
193
|
+
if (ch === '{') {
|
|
194
|
+
braceDepth++;
|
|
195
|
+
started = true;
|
|
196
|
+
}
|
|
197
|
+
if (ch === '}')
|
|
198
|
+
braceDepth--;
|
|
199
|
+
}
|
|
200
|
+
if (started)
|
|
201
|
+
body.push(line);
|
|
202
|
+
if (started && braceDepth === 0)
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
return body;
|
|
206
|
+
}
|
|
207
|
+
extractCallbackBody(lines, startIndex) {
|
|
208
|
+
// Extract the closure/callback body: |e| ...
|
|
209
|
+
const line = lines[startIndex];
|
|
210
|
+
const pipeStart = line.indexOf('|');
|
|
211
|
+
const pipeEnd = line.indexOf('|', pipeStart + 1);
|
|
212
|
+
if (pipeEnd === -1) {
|
|
213
|
+
return line.substring(pipeStart);
|
|
214
|
+
}
|
|
215
|
+
// Body is after the second pipe
|
|
216
|
+
return line.substring(pipeEnd + 1);
|
|
217
|
+
}
|
|
218
|
+
classifyRustErrorStrategy(body) {
|
|
219
|
+
const trimmed = body.trim();
|
|
220
|
+
if (!trimmed)
|
|
221
|
+
return 'swallow';
|
|
222
|
+
if (/\bpanic!\b|\?/.test(trimmed))
|
|
223
|
+
return 'rethrow';
|
|
224
|
+
if (/\beprintln!\b|\blog::\w+\b/.test(trimmed))
|
|
225
|
+
return 'log';
|
|
226
|
+
if (/\.map_err|map\s*\(/.test(trimmed))
|
|
227
|
+
return 'wrap';
|
|
228
|
+
if (/\breturn\s+Err|Err\s*\(/.test(trimmed))
|
|
229
|
+
return 'return-error';
|
|
230
|
+
if (/\bunwrap\b|\bexpect\b/.test(trimmed))
|
|
231
|
+
return 'unwrap';
|
|
232
|
+
return 'other';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export const rustAdapter = new RustAdapter();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* Unified interface for language-specific code analysis.
|
|
5
|
+
* Gates use these adapters instead of inline `if ext === '.py'` chains.
|
|
6
|
+
*/
|
|
7
|
+
export interface LanguageAdapter {
|
|
8
|
+
/** Language identifier (e.g., 'js', 'python', 'go') */
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** Human-readable name */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** File extensions this adapter handles (e.g., ['.py', '.pyw']) */
|
|
13
|
+
readonly extensions: string[];
|
|
14
|
+
/** Extract function declarations from source */
|
|
15
|
+
extractFunctions(source: string, filePath?: string): FunctionFact[];
|
|
16
|
+
/** Extract import statements */
|
|
17
|
+
extractImports(source: string): ImportFact[];
|
|
18
|
+
/** Extract error handling patterns */
|
|
19
|
+
extractErrorHandlers(source: string): ErrorHandlerFact[];
|
|
20
|
+
/** Extract naming patterns (variables, functions, classes) */
|
|
21
|
+
extractNamingPatterns(source: string): NamingPattern[];
|
|
22
|
+
/** Strip comments from source (for analysis accuracy) */
|
|
23
|
+
stripComments(source: string): string;
|
|
24
|
+
/** Detect comparison operators in a code block */
|
|
25
|
+
extractComparisonOps(source: string): string[];
|
|
26
|
+
/** Count branch points (if/else/switch/match) */
|
|
27
|
+
countBranches(source: string): number;
|
|
28
|
+
/** Count return points */
|
|
29
|
+
countReturns(source: string): number;
|
|
30
|
+
}
|
|
31
|
+
export interface FunctionFact {
|
|
32
|
+
name: string;
|
|
33
|
+
startLine: number;
|
|
34
|
+
endLine: number;
|
|
35
|
+
body: string;
|
|
36
|
+
isAsync: boolean;
|
|
37
|
+
isExported: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ImportFact {
|
|
40
|
+
module: string;
|
|
41
|
+
names: string[];
|
|
42
|
+
line: number;
|
|
43
|
+
isDynamic: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface ErrorHandlerFact {
|
|
46
|
+
type: string;
|
|
47
|
+
strategy: string;
|
|
48
|
+
startLine: number;
|
|
49
|
+
body: string;
|
|
50
|
+
}
|
|
51
|
+
export interface NamingPattern {
|
|
52
|
+
name: string;
|
|
53
|
+
kind: 'function' | 'class' | 'variable' | 'constant' | 'method';
|
|
54
|
+
convention: NamingConvention;
|
|
55
|
+
}
|
|
56
|
+
export type NamingConvention = 'camelCase' | 'PascalCase' | 'snake_case' | 'SCREAMING_SNAKE' | 'kebab-case' | 'other';
|
|
57
|
+
/**
|
|
58
|
+
* Classify a name into its casing convention.
|
|
59
|
+
*/
|
|
60
|
+
export declare function classifyCasing(name: string): NamingConvention;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* Unified interface for language-specific code analysis.
|
|
5
|
+
* Gates use these adapters instead of inline `if ext === '.py'` chains.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Classify a name into its casing convention.
|
|
9
|
+
*/
|
|
10
|
+
export function classifyCasing(name) {
|
|
11
|
+
if (/^[A-Z][A-Z0-9_]*$/.test(name))
|
|
12
|
+
return 'SCREAMING_SNAKE';
|
|
13
|
+
if (/^[A-Z][a-zA-Z0-9]*$/.test(name))
|
|
14
|
+
return 'PascalCase';
|
|
15
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(name))
|
|
16
|
+
return 'camelCase';
|
|
17
|
+
if (/^[a-z][a-z0-9_]*$/.test(name))
|
|
18
|
+
return 'snake_case';
|
|
19
|
+
if (/^[a-z][a-z0-9-]*$/.test(name))
|
|
20
|
+
return 'kebab-case';
|
|
21
|
+
return 'other';
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logic Drift Gate — Drift-Specific Analysis Helpers
|
|
3
|
+
*
|
|
4
|
+
* Language-specific extraction (stripComments, countBranches, etc.) now
|
|
5
|
+
* delegates to the language adapter layer. This module retains only the
|
|
6
|
+
* drift-detection logic that is specific to the gate.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract ordered sequence of function calls
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractCallSequence(body: string): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Classify whether an operator change is "dangerous" (likely unintentional)
|
|
14
|
+
*/
|
|
15
|
+
export declare function isDangerousMutation(from: string, to: string): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logic Drift Gate — Drift-Specific Analysis Helpers
|
|
3
|
+
*
|
|
4
|
+
* Language-specific extraction (stripComments, countBranches, etc.) now
|
|
5
|
+
* delegates to the language adapter layer. This module retains only the
|
|
6
|
+
* drift-detection logic that is specific to the gate.
|
|
7
|
+
*/
|
|
8
|
+
// ─── Logic Drift-Specific Functions ───────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Extract ordered sequence of function calls
|
|
11
|
+
*/
|
|
12
|
+
export function extractCallSequence(body) {
|
|
13
|
+
const calls = [];
|
|
14
|
+
const matches = body.matchAll(/\b(\w+)\s*\(/g);
|
|
15
|
+
const keywords = new Set(['if', 'for', 'while', 'switch', 'catch', 'function', 'async', 'await', 'return', 'new', 'typeof', 'instanceof']);
|
|
16
|
+
for (const m of matches) {
|
|
17
|
+
if (!keywords.has(m[1])) {
|
|
18
|
+
calls.push(m[1]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return calls;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Classify whether an operator change is "dangerous" (likely unintentional)
|
|
25
|
+
*/
|
|
26
|
+
export function isDangerousMutation(from, to) {
|
|
27
|
+
const dangerous = new Set([
|
|
28
|
+
'>=:>', '>:>=',
|
|
29
|
+
'<=:<', '<:<=',
|
|
30
|
+
'===:==', '==:===',
|
|
31
|
+
'!==:!=', '!=:!==',
|
|
32
|
+
]);
|
|
33
|
+
return dangerous.has(`${from}:${to}`);
|
|
34
|
+
}
|
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
* Strategy: Collect baselines for critical functions, then detect
|
|
16
16
|
* mutations between scans. This foundation enables future LLM-powered
|
|
17
17
|
* deeper analysis (feeding baselines into DriftBench training).
|
|
18
|
-
*
|
|
19
|
-
* @since v5.1.0
|
|
20
18
|
*/
|
|
21
19
|
import { Gate, GateContext } from './base.js';
|
|
22
20
|
import { Failure, Provenance } from '../types/index.js';
|
|
@@ -33,38 +31,10 @@ export declare class LogicDriftGate extends Gate {
|
|
|
33
31
|
protected get provenance(): Provenance;
|
|
34
32
|
run(context: GateContext): Promise<Failure[]>;
|
|
35
33
|
private extractFunctionBaselines;
|
|
36
|
-
private extractBody;
|
|
37
|
-
/**
|
|
38
|
-
* Extract all comparison operators from function body in order.
|
|
39
|
-
* These are the most critical mutations: >= to > causes off-by-one.
|
|
40
|
-
*/
|
|
41
|
-
private extractComparisonOps;
|
|
42
|
-
private countBranches;
|
|
43
|
-
private countReturns;
|
|
44
|
-
/**
|
|
45
|
-
* Extract ordered sequence of function calls.
|
|
46
|
-
* Useful for detecting when AI reorders operations.
|
|
47
|
-
*/
|
|
48
|
-
private extractCallSequence;
|
|
49
34
|
/**
|
|
50
35
|
* Detect specific operator mutations between two ordered operator lists.
|
|
51
36
|
* Only reports CHANGED operators, not added/removed ones (those are
|
|
52
37
|
* covered by branch count changes).
|
|
53
|
-
*
|
|
54
|
-
* Example:
|
|
55
|
-
* prev: ['>=', '===', '!==']
|
|
56
|
-
* curr: ['>', '===', '!==']
|
|
57
|
-
* → [{from: '>=', to: '>'}]
|
|
58
38
|
*/
|
|
59
39
|
private detectOperatorMutations;
|
|
60
|
-
/**
|
|
61
|
-
* Classify whether an operator change is "dangerous" (likely unintentional).
|
|
62
|
-
*
|
|
63
|
-
* Dangerous mutations:
|
|
64
|
-
* - >= to > (boundary change, off-by-one)
|
|
65
|
-
* - <= to < (boundary change)
|
|
66
|
-
* - === to == (type coercion change)
|
|
67
|
-
* - !== to != (type coercion change)
|
|
68
|
-
*/
|
|
69
|
-
private isDangerousMutation;
|
|
70
40
|
}
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
* Strategy: Collect baselines for critical functions, then detect
|
|
16
16
|
* mutations between scans. This foundation enables future LLM-powered
|
|
17
17
|
* deeper analysis (feeding baselines into DriftBench training).
|
|
18
|
-
*
|
|
19
|
-
* @since v5.1.0
|
|
20
18
|
*/
|
|
21
19
|
import { Gate } from './base.js';
|
|
22
20
|
import { FileScanner } from '../utils/scanner.js';
|
|
23
21
|
import { Logger } from '../utils/logger.js';
|
|
22
|
+
import { languageAdapters } from './language-adapters/index.js';
|
|
23
|
+
import { extractCallSequence, isDangerousMutation } from './logic-drift-extractors.js';
|
|
24
24
|
import fs from 'fs-extra';
|
|
25
25
|
import path from 'path';
|
|
26
26
|
import crypto from 'crypto';
|
|
@@ -42,10 +42,10 @@ export class LogicDriftGate extends Gate {
|
|
|
42
42
|
return [];
|
|
43
43
|
const failures = [];
|
|
44
44
|
const baselinePath = path.join(context.cwd, this.config.baseline_path);
|
|
45
|
-
// Find source files
|
|
45
|
+
// Find source files using adapter-supported patterns
|
|
46
46
|
const files = await FileScanner.findFiles({
|
|
47
47
|
cwd: context.cwd,
|
|
48
|
-
patterns: context.patterns ||
|
|
48
|
+
patterns: context.patterns || languageAdapters.getScanPatterns(),
|
|
49
49
|
ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/*.test.*', '**/*.spec.*', '**/*.d.ts'],
|
|
50
50
|
});
|
|
51
51
|
if (files.length === 0)
|
|
@@ -61,7 +61,14 @@ export class LogicDriftGate extends Gate {
|
|
|
61
61
|
let previousBaseline = null;
|
|
62
62
|
if (await fs.pathExists(baselinePath)) {
|
|
63
63
|
try {
|
|
64
|
-
|
|
64
|
+
const raw = await fs.readJson(baselinePath);
|
|
65
|
+
// Validate baseline has required structure
|
|
66
|
+
if (raw && Array.isArray(raw.functions)) {
|
|
67
|
+
previousBaseline = raw;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
Logger.debug('Invalid or empty logic baseline format, will recreate');
|
|
71
|
+
}
|
|
65
72
|
}
|
|
66
73
|
catch {
|
|
67
74
|
Logger.debug('Failed to load logic baseline');
|
|
@@ -133,148 +140,51 @@ export class LogicDriftGate extends Gate {
|
|
|
133
140
|
// ─── Function Baseline Extraction ────────────────────────────────
|
|
134
141
|
extractFunctionBaselines(content, file) {
|
|
135
142
|
const baselines = [];
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|(\w+))\s*=>/,
|
|
140
|
-
/^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/,
|
|
141
|
-
];
|
|
142
|
-
for (let i = 0; i < lines.length; i++) {
|
|
143
|
-
const line = lines[i];
|
|
144
|
-
for (const pattern of fnPatterns) {
|
|
145
|
-
const match = line.match(pattern);
|
|
146
|
-
if (match) {
|
|
147
|
-
const name = match[1];
|
|
148
|
-
if (['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(name))
|
|
149
|
-
continue;
|
|
150
|
-
const body = this.extractBody(lines, i);
|
|
151
|
-
if (body.length < 3)
|
|
152
|
-
continue; // Skip trivial functions
|
|
153
|
-
const bodyText = body.join('\n');
|
|
154
|
-
const normalized = bodyText
|
|
155
|
-
.replace(/\/\/.*/g, '')
|
|
156
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
157
|
-
.replace(/\s+/g, ' ')
|
|
158
|
-
.trim();
|
|
159
|
-
baselines.push({
|
|
160
|
-
name,
|
|
161
|
-
file,
|
|
162
|
-
line: i + 1,
|
|
163
|
-
comparisonOps: this.extractComparisonOps(bodyText),
|
|
164
|
-
branchCount: this.countBranches(bodyText),
|
|
165
|
-
returnCount: this.countReturns(bodyText),
|
|
166
|
-
callSequence: this.extractCallSequence(bodyText),
|
|
167
|
-
bodyHash: crypto.createHash('md5').update(normalized).digest('hex'),
|
|
168
|
-
});
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return baselines;
|
|
174
|
-
}
|
|
175
|
-
extractBody(lines, startIndex) {
|
|
176
|
-
let braceDepth = 0;
|
|
177
|
-
let started = false;
|
|
178
|
-
const body = [];
|
|
179
|
-
for (let i = startIndex; i < lines.length; i++) {
|
|
180
|
-
const line = lines[i];
|
|
181
|
-
for (const ch of line) {
|
|
182
|
-
if (ch === '{') {
|
|
183
|
-
braceDepth++;
|
|
184
|
-
started = true;
|
|
185
|
-
}
|
|
186
|
-
if (ch === '}')
|
|
187
|
-
braceDepth--;
|
|
188
|
-
}
|
|
189
|
-
if (started)
|
|
190
|
-
body.push(line);
|
|
191
|
-
if (started && braceDepth === 0)
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
return body;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Extract all comparison operators from function body in order.
|
|
198
|
-
* These are the most critical mutations: >= to > causes off-by-one.
|
|
199
|
-
*/
|
|
200
|
-
extractComparisonOps(body) {
|
|
201
|
-
const ops = [];
|
|
202
|
-
const matches = body.matchAll(/(===|!==|==|!=|>=|<=|>(?!=)|<(?!=))/g);
|
|
203
|
-
for (const m of matches) {
|
|
204
|
-
ops.push(m[1]);
|
|
143
|
+
const adapter = languageAdapters.getAdapter(file);
|
|
144
|
+
if (!adapter) {
|
|
145
|
+
return baselines; // Unsupported language
|
|
205
146
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const matches = body.matchAll(/\b(\w+)\s*\(/g);
|
|
229
|
-
const keywords = new Set(['if', 'for', 'while', 'switch', 'catch', 'function', 'async', 'await', 'return', 'new', 'typeof', 'instanceof']);
|
|
230
|
-
for (const m of matches) {
|
|
231
|
-
if (!keywords.has(m[1])) {
|
|
232
|
-
calls.push(m[1]);
|
|
233
|
-
}
|
|
147
|
+
const skipKeywords = new Set(['if', 'for', 'while', 'switch', 'catch', 'constructor', 'else', 'elif', 'elsif', 'rescue']);
|
|
148
|
+
const functions = adapter.extractFunctions(content, file);
|
|
149
|
+
for (const fn of functions) {
|
|
150
|
+
// Skip control flow keywords
|
|
151
|
+
if (skipKeywords.has(fn.name))
|
|
152
|
+
continue;
|
|
153
|
+
// Skip very small functions
|
|
154
|
+
if (fn.body.length < 3)
|
|
155
|
+
continue;
|
|
156
|
+
const normalized = adapter.stripComments(fn.body)
|
|
157
|
+
.replace(/\s+/g, ' ')
|
|
158
|
+
.trim();
|
|
159
|
+
baselines.push({
|
|
160
|
+
name: fn.name,
|
|
161
|
+
file,
|
|
162
|
+
line: fn.startLine,
|
|
163
|
+
comparisonOps: adapter.extractComparisonOps(fn.body),
|
|
164
|
+
branchCount: adapter.countBranches(fn.body),
|
|
165
|
+
returnCount: adapter.countReturns(fn.body),
|
|
166
|
+
callSequence: extractCallSequence(fn.body),
|
|
167
|
+
bodyHash: crypto.createHash('md5').update(normalized).digest('hex'),
|
|
168
|
+
});
|
|
234
169
|
}
|
|
235
|
-
return
|
|
170
|
+
return baselines;
|
|
236
171
|
}
|
|
237
172
|
// ─── Mutation Detection ──────────────────────────────────────────
|
|
238
173
|
/**
|
|
239
174
|
* Detect specific operator mutations between two ordered operator lists.
|
|
240
175
|
* Only reports CHANGED operators, not added/removed ones (those are
|
|
241
176
|
* covered by branch count changes).
|
|
242
|
-
*
|
|
243
|
-
* Example:
|
|
244
|
-
* prev: ['>=', '===', '!==']
|
|
245
|
-
* curr: ['>', '===', '!==']
|
|
246
|
-
* → [{from: '>=', to: '>'}]
|
|
247
177
|
*/
|
|
248
178
|
detectOperatorMutations(prev, curr) {
|
|
249
179
|
const mutations = [];
|
|
250
|
-
// Use LCS-like alignment for best matching
|
|
251
180
|
const minLen = Math.min(prev.length, curr.length);
|
|
252
181
|
for (let i = 0; i < minLen; i++) {
|
|
253
182
|
if (prev[i] !== curr[i]) {
|
|
254
|
-
|
|
255
|
-
if (this.isDangerousMutation(prev[i], curr[i])) {
|
|
183
|
+
if (isDangerousMutation(prev[i], curr[i])) {
|
|
256
184
|
mutations.push({ from: prev[i], to: curr[i] });
|
|
257
185
|
}
|
|
258
186
|
}
|
|
259
187
|
}
|
|
260
188
|
return mutations;
|
|
261
189
|
}
|
|
262
|
-
/**
|
|
263
|
-
* Classify whether an operator change is "dangerous" (likely unintentional).
|
|
264
|
-
*
|
|
265
|
-
* Dangerous mutations:
|
|
266
|
-
* - >= to > (boundary change, off-by-one)
|
|
267
|
-
* - <= to < (boundary change)
|
|
268
|
-
* - === to == (type coercion change)
|
|
269
|
-
* - !== to != (type coercion change)
|
|
270
|
-
*/
|
|
271
|
-
isDangerousMutation(from, to) {
|
|
272
|
-
const dangerous = new Set([
|
|
273
|
-
'>=:>', '>:>=', // Boundary mutations
|
|
274
|
-
'<=:<', '<:<=', // Boundary mutations
|
|
275
|
-
'===:==', '==:===', // Type coercion mutations
|
|
276
|
-
'!==:!=', '!=:!==', // Type coercion mutations
|
|
277
|
-
]);
|
|
278
|
-
return dangerous.has(`${from}:${to}`);
|
|
279
|
-
}
|
|
280
190
|
}
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
* C# — Common .NET hallucinated APIs (LINQ, File I/O, string methods)
|
|
17
17
|
* Java — Common hallucinated JDK APIs (Collections, String, Stream, Files)
|
|
18
18
|
*
|
|
19
|
-
* @since v3.0.0
|
|
20
|
-
* @since v3.0.3 — Go, C#, Java pattern-based detection added
|
|
21
19
|
*/
|
|
22
20
|
import { Gate, GateContext } from './base.js';
|
|
23
21
|
import { Failure, Provenance } from '../types/index.js';
|