@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.
Files changed (141) hide show
  1. package/README.md +9 -1
  2. package/dist/gates/agent-team.d.ts +0 -1
  3. package/dist/gates/agent-team.js +0 -1
  4. package/dist/gates/checkpoint.d.ts +0 -2
  5. package/dist/gates/checkpoint.js +0 -2
  6. package/dist/gates/context-window-artifacts.d.ts +6 -2
  7. package/dist/gates/context-window-artifacts.js +107 -31
  8. package/dist/gates/deep-analysis.d.ts +2 -0
  9. package/dist/gates/deep-analysis.js +41 -11
  10. package/dist/gates/dependency.d.ts +0 -2
  11. package/dist/gates/dependency.js +23 -5
  12. package/dist/gates/deprecated-apis.d.ts +0 -2
  13. package/dist/gates/deprecated-apis.js +33 -20
  14. package/dist/gates/duplication-drift/index.d.ts +61 -0
  15. package/dist/gates/duplication-drift/index.js +240 -0
  16. package/dist/gates/duplication-drift/similarity.d.ts +68 -0
  17. package/dist/gates/duplication-drift/similarity.js +177 -0
  18. package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
  19. package/dist/gates/duplication-drift/tokenizer.js +195 -0
  20. package/dist/gates/frontend-secret-exposure.d.ts +0 -3
  21. package/dist/gates/frontend-secret-exposure.js +1 -114
  22. package/dist/gates/frontend-secret-patterns.d.ts +33 -0
  23. package/dist/gates/frontend-secret-patterns.js +119 -0
  24. package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
  25. package/dist/gates/hallucinated-imports/index.js +174 -0
  26. package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
  27. package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
  28. package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
  29. package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
  30. package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
  31. package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
  32. package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
  33. package/dist/gates/hallucinated-imports-lang.js +269 -34
  34. package/dist/gates/hallucinated-imports.test.js +1 -2
  35. package/dist/gates/inconsistent-error-handling.d.ts +0 -5
  36. package/dist/gates/inconsistent-error-handling.js +15 -144
  37. package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
  38. package/dist/gates/language-adapters/csharp-adapter.js +211 -0
  39. package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
  40. package/dist/gates/language-adapters/go-adapter.js +195 -0
  41. package/dist/gates/language-adapters/index.d.ts +15 -0
  42. package/dist/gates/language-adapters/index.js +16 -0
  43. package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
  44. package/dist/gates/language-adapters/java-adapter.js +237 -0
  45. package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
  46. package/dist/gates/language-adapters/js-adapter.js +279 -0
  47. package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
  48. package/dist/gates/language-adapters/python-adapter.js +183 -0
  49. package/dist/gates/language-adapters/registry.d.ts +26 -0
  50. package/dist/gates/language-adapters/registry.js +65 -0
  51. package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
  52. package/dist/gates/language-adapters/ruby-adapter.js +217 -0
  53. package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
  54. package/dist/gates/language-adapters/rust-adapter.js +235 -0
  55. package/dist/gates/language-adapters/types.d.ts +60 -0
  56. package/dist/gates/language-adapters/types.js +22 -0
  57. package/dist/gates/logic-drift-extractors.d.ts +15 -0
  58. package/dist/gates/logic-drift-extractors.js +34 -0
  59. package/dist/gates/logic-drift.d.ts +0 -30
  60. package/dist/gates/logic-drift.js +39 -129
  61. package/dist/gates/phantom-apis.d.ts +0 -2
  62. package/dist/gates/phantom-apis.js +49 -20
  63. package/dist/gates/promise-safety.d.ts +0 -1
  64. package/dist/gates/promise-safety.js +14 -2
  65. package/dist/gates/runner.js +52 -23
  66. package/dist/gates/runner.test.js +1 -1
  67. package/dist/gates/security-patterns-data.d.ts +14 -0
  68. package/dist/gates/security-patterns-data.js +235 -0
  69. package/dist/gates/security-patterns.d.ts +17 -3
  70. package/dist/gates/security-patterns.js +80 -211
  71. package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
  72. package/dist/gates/side-effect-analysis/categorizer.js +83 -0
  73. package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
  74. package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
  75. package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
  76. package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
  77. package/dist/gates/side-effect-helpers/index.d.ts +4 -0
  78. package/dist/gates/side-effect-helpers/index.js +4 -0
  79. package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
  80. package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
  81. package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
  82. package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
  83. package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
  84. package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
  85. package/dist/gates/side-effect-helpers/types.d.ts +38 -0
  86. package/dist/gates/side-effect-helpers/types.js +41 -0
  87. package/dist/gates/side-effect-rules.d.ts +0 -1
  88. package/dist/gates/side-effect-rules.js +0 -1
  89. package/dist/gates/style-drift-rules.d.ts +86 -0
  90. package/dist/gates/style-drift-rules.js +103 -0
  91. package/dist/gates/style-drift.d.ts +7 -16
  92. package/dist/gates/style-drift.js +101 -119
  93. package/dist/gates/test-quality-matchers.d.ts +53 -0
  94. package/dist/gates/test-quality-matchers.js +86 -0
  95. package/dist/gates/test-quality.d.ts +0 -3
  96. package/dist/gates/test-quality.js +47 -44
  97. package/dist/hooks/checker.d.ts +0 -1
  98. package/dist/hooks/checker.js +0 -2
  99. package/dist/hooks/dlp-templates.d.ts +0 -1
  100. package/dist/hooks/dlp-templates.js +0 -4
  101. package/dist/hooks/index.d.ts +0 -2
  102. package/dist/hooks/index.js +0 -2
  103. package/dist/hooks/input-validator.d.ts +0 -1
  104. package/dist/hooks/input-validator.js +0 -1
  105. package/dist/hooks/input-validator.test.js +0 -1
  106. package/dist/hooks/standalone-checker.d.ts +0 -1
  107. package/dist/hooks/standalone-checker.js +0 -1
  108. package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
  109. package/dist/hooks/standalone-dlp-checker.js +0 -1
  110. package/dist/hooks/templates.d.ts +0 -1
  111. package/dist/hooks/templates.js +0 -1
  112. package/dist/hooks/types.d.ts +0 -1
  113. package/dist/hooks/types.js +0 -1
  114. package/dist/index.d.ts +1 -1
  115. package/dist/index.js +1 -1
  116. package/dist/inference/index.js +1 -1
  117. package/dist/services/adaptive-thresholds.d.ts +0 -2
  118. package/dist/services/adaptive-thresholds.js +0 -2
  119. package/dist/services/filesystem-cache.d.ts +0 -1
  120. package/dist/services/filesystem-cache.js +0 -1
  121. package/dist/services/score-history.d.ts +0 -1
  122. package/dist/services/score-history.js +0 -1
  123. package/dist/services/temporal-drift.d.ts +1 -2
  124. package/dist/services/temporal-drift.js +7 -8
  125. package/dist/storage/db.d.ts +23 -7
  126. package/dist/storage/db.js +116 -55
  127. package/dist/storage/findings.d.ts +4 -3
  128. package/dist/storage/findings.js +13 -20
  129. package/dist/storage/local-memory.d.ts +4 -4
  130. package/dist/storage/local-memory.js +20 -22
  131. package/dist/storage/patterns.d.ts +5 -5
  132. package/dist/storage/patterns.js +20 -26
  133. package/dist/storage/scans.d.ts +6 -6
  134. package/dist/storage/scans.js +12 -21
  135. package/dist/types/index.d.ts +1 -0
  136. package/dist/utils/scanner.js +1 -1
  137. package/package.json +7 -8
  138. package/dist/gates/duplication-drift.d.ts +0 -128
  139. package/dist/gates/duplication-drift.js +0 -585
  140. package/dist/gates/hallucinated-imports.js +0 -641
  141. 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 || ['**/*.{ts,tsx,js,jsx}'],
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
- previousBaseline = await fs.readJson(baselinePath);
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 lines = content.split('\n');
137
- const fnPatterns = [
138
- /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
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
- return ops;
207
- }
208
- countBranches(body) {
209
- let count = 0;
210
- // Count if, else if, else, switch, case, ternary
211
- count += (body.match(/\bif\s*\(/g) || []).length;
212
- count += (body.match(/\belse\s+if\s*\(/g) || []).length;
213
- count += (body.match(/\belse\s*\{/g) || []).length;
214
- count += (body.match(/\bswitch\s*\(/g) || []).length;
215
- count += (body.match(/\bcase\s+/g) || []).length;
216
- count += (body.match(/\?\s*[^?]/g) || []).length; // ternary (approximate)
217
- return count;
218
- }
219
- countReturns(body) {
220
- return (body.match(/\breturn\b/g) || []).length;
221
- }
222
- /**
223
- * Extract ordered sequence of function calls.
224
- * Useful for detecting when AI reorders operations.
225
- */
226
- extractCallSequence(body) {
227
- const calls = [];
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 calls;
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
- // Check if this is a "dangerous" mutation (same family, different strictness)
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';