@rigour-labs/core 2.21.2 → 3.0.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 +58 -0
- package/dist/context.test.js +2 -3
- package/dist/environment.test.js +2 -1
- package/dist/gates/agent-team.d.ts +2 -1
- package/dist/gates/agent-team.js +1 -0
- package/dist/gates/base.d.ts +4 -2
- package/dist/gates/base.js +5 -1
- package/dist/gates/checkpoint.d.ts +2 -1
- package/dist/gates/checkpoint.js +3 -2
- package/dist/gates/content.js +1 -1
- package/dist/gates/context-window-artifacts.d.ts +34 -0
- package/dist/gates/context-window-artifacts.js +214 -0
- package/dist/gates/context.d.ts +2 -1
- package/dist/gates/context.js +4 -3
- package/dist/gates/coverage.js +3 -1
- package/dist/gates/dependency.js +5 -5
- package/dist/gates/duplication-drift.d.ts +33 -0
- package/dist/gates/duplication-drift.js +190 -0
- package/dist/gates/environment.js +4 -4
- package/dist/gates/file.js +1 -1
- package/dist/gates/hallucinated-imports.d.ts +63 -0
- package/dist/gates/hallucinated-imports.js +406 -0
- package/dist/gates/inconsistent-error-handling.d.ts +39 -0
- package/dist/gates/inconsistent-error-handling.js +236 -0
- package/dist/gates/promise-safety.d.ts +68 -0
- package/dist/gates/promise-safety.js +509 -0
- package/dist/gates/retry-loop-breaker.d.ts +2 -1
- package/dist/gates/retry-loop-breaker.js +2 -1
- package/dist/gates/runner.js +62 -1
- package/dist/gates/safety.d.ts +2 -1
- package/dist/gates/safety.js +2 -1
- package/dist/gates/security-patterns.d.ts +2 -1
- package/dist/gates/security-patterns.js +2 -1
- package/dist/gates/structure.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/fix-packet-service.d.ts +0 -1
- package/dist/services/fix-packet-service.js +9 -14
- package/dist/services/score-history.d.ts +54 -0
- package/dist/services/score-history.js +122 -0
- package/dist/templates/index.js +195 -0
- package/dist/types/fix-packet.d.ts +5 -5
- package/dist/types/fix-packet.js +1 -1
- package/dist/types/index.d.ts +430 -0
- package/dist/types/index.js +57 -0
- package/package.json +21 -1
- package/src/context.test.ts +0 -256
- package/src/discovery.test.ts +0 -88
- package/src/discovery.ts +0 -112
- package/src/environment.test.ts +0 -115
- package/src/gates/agent-team.test.ts +0 -134
- package/src/gates/agent-team.ts +0 -210
- package/src/gates/ast-handlers/base.ts +0 -13
- package/src/gates/ast-handlers/python.ts +0 -145
- package/src/gates/ast-handlers/python_parser.py +0 -181
- package/src/gates/ast-handlers/typescript.ts +0 -264
- package/src/gates/ast-handlers/universal.ts +0 -184
- package/src/gates/ast.ts +0 -54
- package/src/gates/base.ts +0 -27
- package/src/gates/checkpoint.test.ts +0 -135
- package/src/gates/checkpoint.ts +0 -311
- package/src/gates/content.ts +0 -50
- package/src/gates/context.ts +0 -267
- package/src/gates/coverage.ts +0 -74
- package/src/gates/dependency.ts +0 -108
- package/src/gates/environment.ts +0 -94
- package/src/gates/file.ts +0 -42
- package/src/gates/retry-loop-breaker.ts +0 -151
- package/src/gates/runner.ts +0 -156
- package/src/gates/safety.ts +0 -56
- package/src/gates/security-patterns.test.ts +0 -162
- package/src/gates/security-patterns.ts +0 -305
- package/src/gates/structure.ts +0 -36
- package/src/index.ts +0 -13
- package/src/pattern-index/embeddings.ts +0 -84
- package/src/pattern-index/index.ts +0 -59
- package/src/pattern-index/indexer.test.ts +0 -276
- package/src/pattern-index/indexer.ts +0 -1023
- package/src/pattern-index/matcher.test.ts +0 -293
- package/src/pattern-index/matcher.ts +0 -493
- package/src/pattern-index/overrides.ts +0 -235
- package/src/pattern-index/security.ts +0 -151
- package/src/pattern-index/staleness.test.ts +0 -313
- package/src/pattern-index/staleness.ts +0 -568
- package/src/pattern-index/types.ts +0 -339
- package/src/safety.test.ts +0 -53
- package/src/services/adaptive-thresholds.test.ts +0 -189
- package/src/services/adaptive-thresholds.ts +0 -275
- package/src/services/context-engine.ts +0 -104
- package/src/services/fix-packet-service.ts +0 -42
- package/src/services/state-service.ts +0 -138
- package/src/smoke.test.ts +0 -18
- package/src/templates/index.ts +0 -312
- package/src/types/fix-packet.ts +0 -32
- package/src/types/index.ts +0 -159
- package/src/utils/logger.ts +0 -43
- package/src/utils/scanner.test.ts +0 -37
- package/src/utils/scanner.ts +0 -43
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -7
- package/vitest.setup.ts +0 -30
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inconsistent Error Handling Gate
|
|
3
|
+
*
|
|
4
|
+
* Detects when the same error type is handled differently across the codebase.
|
|
5
|
+
* This is an AI-specific failure mode — each agent session writes error handling
|
|
6
|
+
* from scratch, leading to 4 different patterns for the same error type.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Extract all try-catch blocks and error handling patterns
|
|
10
|
+
* 2. Cluster by caught error type (e.g. "Error", "TypeError", custom errors)
|
|
11
|
+
* 3. Compare handling strategies within each cluster
|
|
12
|
+
* 4. Flag types with >2 distinct handling patterns across files
|
|
13
|
+
*
|
|
14
|
+
* Examples of inconsistency:
|
|
15
|
+
* - File A: catch(e) { console.log(e) }
|
|
16
|
+
* - File B: catch(e) { throw new AppError(e) }
|
|
17
|
+
* - File C: catch(e) { return null }
|
|
18
|
+
* - File D: catch(e) { [empty] }
|
|
19
|
+
*
|
|
20
|
+
* @since v2.16.0
|
|
21
|
+
*/
|
|
22
|
+
import { Gate } from './base.js';
|
|
23
|
+
import { FileScanner } from '../utils/scanner.js';
|
|
24
|
+
import { Logger } from '../utils/logger.js';
|
|
25
|
+
import fs from 'fs-extra';
|
|
26
|
+
import path from 'path';
|
|
27
|
+
export class InconsistentErrorHandlingGate extends Gate {
|
|
28
|
+
config;
|
|
29
|
+
constructor(config = {}) {
|
|
30
|
+
super('inconsistent-error-handling', 'Inconsistent Error Handling Detection');
|
|
31
|
+
this.config = {
|
|
32
|
+
enabled: config.enabled ?? true,
|
|
33
|
+
max_strategies_per_type: config.max_strategies_per_type ?? 2,
|
|
34
|
+
min_occurrences: config.min_occurrences ?? 3,
|
|
35
|
+
ignore_empty_catches: config.ignore_empty_catches ?? false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
get provenance() { return 'ai-drift'; }
|
|
39
|
+
async run(context) {
|
|
40
|
+
if (!this.config.enabled)
|
|
41
|
+
return [];
|
|
42
|
+
const failures = [];
|
|
43
|
+
const handlers = [];
|
|
44
|
+
const files = await FileScanner.findFiles({
|
|
45
|
+
cwd: context.cwd,
|
|
46
|
+
patterns: ['**/*.{ts,js,tsx,jsx}'],
|
|
47
|
+
ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/*.test.*', '**/*.spec.*'],
|
|
48
|
+
});
|
|
49
|
+
Logger.info(`Inconsistent Error Handling: Scanning ${files.length} files`);
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
try {
|
|
52
|
+
const content = await fs.readFile(path.join(context.cwd, file), 'utf-8');
|
|
53
|
+
this.extractErrorHandlers(content, file, handlers);
|
|
54
|
+
}
|
|
55
|
+
catch (e) { }
|
|
56
|
+
}
|
|
57
|
+
// Group by error type
|
|
58
|
+
const byType = new Map();
|
|
59
|
+
for (const handler of handlers) {
|
|
60
|
+
const existing = byType.get(handler.errorType) || [];
|
|
61
|
+
existing.push(handler);
|
|
62
|
+
byType.set(handler.errorType, existing);
|
|
63
|
+
}
|
|
64
|
+
// Analyze each error type for inconsistent handling
|
|
65
|
+
for (const [errorType, typeHandlers] of byType) {
|
|
66
|
+
if (typeHandlers.length < this.config.min_occurrences)
|
|
67
|
+
continue;
|
|
68
|
+
// Count unique strategies
|
|
69
|
+
const strategies = new Map();
|
|
70
|
+
for (const handler of typeHandlers) {
|
|
71
|
+
const existing = strategies.get(handler.strategy) || [];
|
|
72
|
+
existing.push(handler);
|
|
73
|
+
strategies.set(handler.strategy, existing);
|
|
74
|
+
}
|
|
75
|
+
// Filter out empty catches if configured
|
|
76
|
+
const activeStrategies = this.config.ignore_empty_catches
|
|
77
|
+
? new Map([...strategies].filter(([k]) => k !== 'swallow'))
|
|
78
|
+
: strategies;
|
|
79
|
+
if (activeStrategies.size > this.config.max_strategies_per_type) {
|
|
80
|
+
// Only flag if handlers span multiple files
|
|
81
|
+
const uniqueFiles = new Set(typeHandlers.map(h => h.file));
|
|
82
|
+
if (uniqueFiles.size < 2)
|
|
83
|
+
continue;
|
|
84
|
+
const strategyBreakdown = [...activeStrategies.entries()]
|
|
85
|
+
.map(([strategy, handlers]) => {
|
|
86
|
+
const files = [...new Set(handlers.map(h => h.file))].slice(0, 3);
|
|
87
|
+
return ` • ${strategy} (${handlers.length}x): ${files.join(', ')}`;
|
|
88
|
+
})
|
|
89
|
+
.join('\n');
|
|
90
|
+
failures.push(this.createFailure(`Inconsistent error handling for '${errorType}': ${activeStrategies.size} different strategies found across ${uniqueFiles.size} files:\n${strategyBreakdown}`, [...uniqueFiles].slice(0, 5), `Standardize error handling for '${errorType}'. Create a shared error handler or establish a project convention. AI agents often write error handling from scratch each session, leading to divergent patterns.`, 'Inconsistent Error Handling', typeHandlers[0].line, undefined, 'high'));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return failures;
|
|
94
|
+
}
|
|
95
|
+
extractErrorHandlers(content, file, handlers) {
|
|
96
|
+
const lines = content.split('\n');
|
|
97
|
+
for (let i = 0; i < lines.length; i++) {
|
|
98
|
+
// Match catch clauses: catch (e), catch (error: Error), catch (e: TypeError)
|
|
99
|
+
const catchMatch = lines[i].match(/\bcatch\s*\(\s*(\w+)(?:\s*:\s*(\w+))?\s*\)/);
|
|
100
|
+
if (!catchMatch)
|
|
101
|
+
continue;
|
|
102
|
+
const varName = catchMatch[1];
|
|
103
|
+
const explicitType = catchMatch[2] || 'any';
|
|
104
|
+
// Extract catch body (up to next closing brace at same level)
|
|
105
|
+
const body = this.extractCatchBody(lines, i);
|
|
106
|
+
if (!body)
|
|
107
|
+
continue;
|
|
108
|
+
const strategy = this.classifyStrategy(body, varName);
|
|
109
|
+
const rawPattern = body.split('\n')[0]?.trim() || '';
|
|
110
|
+
handlers.push({
|
|
111
|
+
file,
|
|
112
|
+
line: i + 1,
|
|
113
|
+
errorType: explicitType,
|
|
114
|
+
strategy,
|
|
115
|
+
rawPattern,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Also detect .catch() promise patterns
|
|
119
|
+
for (let i = 0; i < lines.length; i++) {
|
|
120
|
+
const catchMatch = lines[i].match(/\.catch\s*\(\s*(?:async\s+)?(?:\(\s*)?(\w+)/);
|
|
121
|
+
if (!catchMatch)
|
|
122
|
+
continue;
|
|
123
|
+
const varName = catchMatch[1];
|
|
124
|
+
// Extract the callback body
|
|
125
|
+
const body = this.extractCatchCallbackBody(lines, i);
|
|
126
|
+
if (!body)
|
|
127
|
+
continue;
|
|
128
|
+
const strategy = this.classifyStrategy(body, varName);
|
|
129
|
+
handlers.push({
|
|
130
|
+
file,
|
|
131
|
+
line: i + 1,
|
|
132
|
+
errorType: 'Promise',
|
|
133
|
+
strategy,
|
|
134
|
+
rawPattern: body.split('\n')[0]?.trim() || '',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
classifyStrategy(body, varName) {
|
|
139
|
+
const trimmed = body.trim();
|
|
140
|
+
// Empty catch (swallow)
|
|
141
|
+
if (!trimmed || trimmed === '{}' || trimmed === '') {
|
|
142
|
+
return 'swallow';
|
|
143
|
+
}
|
|
144
|
+
// Re-throw
|
|
145
|
+
if (/\bthrow\b/.test(trimmed)) {
|
|
146
|
+
if (/\bthrow\s+new\b/.test(trimmed))
|
|
147
|
+
return 'wrap-and-throw';
|
|
148
|
+
if (new RegExp(`\\bthrow\\s+${varName}\\b`).test(trimmed))
|
|
149
|
+
return 'rethrow';
|
|
150
|
+
return 'throw-new';
|
|
151
|
+
}
|
|
152
|
+
// Return patterns
|
|
153
|
+
if (/\breturn\s+null\b/.test(trimmed))
|
|
154
|
+
return 'return-null';
|
|
155
|
+
if (/\breturn\s+undefined\b/.test(trimmed) || /\breturn\s*;/.test(trimmed))
|
|
156
|
+
return 'return-undefined';
|
|
157
|
+
if (/\breturn\s+\[\s*\]/.test(trimmed))
|
|
158
|
+
return 'return-empty-array';
|
|
159
|
+
if (/\breturn\s+\{\s*\}/.test(trimmed))
|
|
160
|
+
return 'return-empty-object';
|
|
161
|
+
if (/\breturn\s+false\b/.test(trimmed))
|
|
162
|
+
return 'return-false';
|
|
163
|
+
if (/\breturn\s+/.test(trimmed))
|
|
164
|
+
return 'return-value';
|
|
165
|
+
// Logging patterns
|
|
166
|
+
if (/console\.(error|warn)\b/.test(trimmed))
|
|
167
|
+
return 'log-error';
|
|
168
|
+
if (/console\.log\b/.test(trimmed))
|
|
169
|
+
return 'log-info';
|
|
170
|
+
if (/\blogger\b/i.test(trimmed) || /\blog\b/i.test(trimmed))
|
|
171
|
+
return 'log-custom';
|
|
172
|
+
// Process.exit
|
|
173
|
+
if (/process\.exit\b/.test(trimmed))
|
|
174
|
+
return 'exit';
|
|
175
|
+
// Response patterns (Express/HTTP)
|
|
176
|
+
if (/\bres\s*\.\s*status\b/.test(trimmed) || /\bres\s*\.\s*json\b/.test(trimmed))
|
|
177
|
+
return 'http-response';
|
|
178
|
+
// Notification patterns
|
|
179
|
+
if (/\bnotif|toast|alert|modal\b/i.test(trimmed))
|
|
180
|
+
return 'user-notification';
|
|
181
|
+
return 'other';
|
|
182
|
+
}
|
|
183
|
+
extractCatchBody(lines, catchLine) {
|
|
184
|
+
let braceDepth = 0;
|
|
185
|
+
let started = false;
|
|
186
|
+
const body = [];
|
|
187
|
+
for (let i = catchLine; i < lines.length; i++) {
|
|
188
|
+
for (const ch of lines[i]) {
|
|
189
|
+
if (ch === '{') {
|
|
190
|
+
braceDepth++;
|
|
191
|
+
started = true;
|
|
192
|
+
}
|
|
193
|
+
if (ch === '}')
|
|
194
|
+
braceDepth--;
|
|
195
|
+
}
|
|
196
|
+
if (started && i > catchLine)
|
|
197
|
+
body.push(lines[i]);
|
|
198
|
+
if (started && braceDepth === 0)
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
return body.length > 0 ? body.join('\n') : null;
|
|
202
|
+
}
|
|
203
|
+
extractCatchCallbackBody(lines, startLine) {
|
|
204
|
+
// Detect if this is an arrow function (.catch(e => { ... }))
|
|
205
|
+
const hasArrow = lines[startLine]?.includes('=>');
|
|
206
|
+
let braceDepth = 0;
|
|
207
|
+
let started = false;
|
|
208
|
+
const body = [];
|
|
209
|
+
for (let i = startLine; i < Math.min(startLine + 20, lines.length); i++) {
|
|
210
|
+
for (const ch of lines[i]) {
|
|
211
|
+
// For arrow functions, only track braces (not parens) for body extraction
|
|
212
|
+
if (hasArrow) {
|
|
213
|
+
if (ch === '{') {
|
|
214
|
+
braceDepth++;
|
|
215
|
+
started = true;
|
|
216
|
+
}
|
|
217
|
+
if (ch === '}')
|
|
218
|
+
braceDepth--;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (ch === '{' || ch === '(') {
|
|
222
|
+
braceDepth++;
|
|
223
|
+
started = true;
|
|
224
|
+
}
|
|
225
|
+
if (ch === '}' || ch === ')')
|
|
226
|
+
braceDepth--;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (started && i > startLine)
|
|
230
|
+
body.push(lines[i]);
|
|
231
|
+
if (started && braceDepth <= 0)
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
return body.length > 0 ? body.join('\n') : null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async & Error Safety Gate (Multi-Language)
|
|
3
|
+
*
|
|
4
|
+
* Detects unsafe async/promise/error patterns that AI code generators commonly produce.
|
|
5
|
+
* LLMs understand synchronous control flow well but frequently produce incomplete
|
|
6
|
+
* async and error-handling patterns across all languages.
|
|
7
|
+
*
|
|
8
|
+
* Supported languages:
|
|
9
|
+
* - JS/TS: .then() without .catch(), JSON.parse without try/catch, async without await, fetch without error handling
|
|
10
|
+
* - Python: json.loads without try/except, async def without await, requests/httpx without error handling, bare except
|
|
11
|
+
* - Go: ignored error returns (_, err pattern), json.Unmarshal without error check, http calls without error check
|
|
12
|
+
* - Ruby: JSON.parse without begin/rescue, Net::HTTP without begin/rescue
|
|
13
|
+
* - C#/.NET: JsonSerializer without try/catch, HttpClient without try/catch, async without await, .Result/.Wait() deadlocks
|
|
14
|
+
*
|
|
15
|
+
* @since v2.17.0
|
|
16
|
+
*/
|
|
17
|
+
import { Gate, GateContext } from './base.js';
|
|
18
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
19
|
+
export interface PromiseSafetyConfig {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
check_unhandled_then?: boolean;
|
|
22
|
+
check_unsafe_parse?: boolean;
|
|
23
|
+
check_async_without_await?: boolean;
|
|
24
|
+
check_unsafe_fetch?: boolean;
|
|
25
|
+
ignore_patterns?: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare class PromiseSafetyGate extends Gate {
|
|
28
|
+
private config;
|
|
29
|
+
constructor(config?: PromiseSafetyConfig);
|
|
30
|
+
protected get provenance(): Provenance;
|
|
31
|
+
run(context: GateContext): Promise<Failure[]>;
|
|
32
|
+
private scanFile;
|
|
33
|
+
private scanJS;
|
|
34
|
+
private detectUnhandledThen;
|
|
35
|
+
private detectUnsafeParseJS;
|
|
36
|
+
private detectAsyncWithoutAwaitJS;
|
|
37
|
+
private detectUnsafeFetchJS;
|
|
38
|
+
private scanPython;
|
|
39
|
+
private detectUnsafeParsePython;
|
|
40
|
+
private detectAsyncWithoutAwaitPython;
|
|
41
|
+
private detectUnsafeFetchPython;
|
|
42
|
+
private detectBareExceptPython;
|
|
43
|
+
private scanGo;
|
|
44
|
+
private detectUnsafeParseGo;
|
|
45
|
+
private detectUnsafeFetchGo;
|
|
46
|
+
private detectIgnoredErrorsGo;
|
|
47
|
+
private scanRuby;
|
|
48
|
+
private detectUnsafeParseRuby;
|
|
49
|
+
private detectUnsafeFetchRuby;
|
|
50
|
+
private scanCSharp;
|
|
51
|
+
private detectUnsafeParseCSharp;
|
|
52
|
+
private detectUnsafeFetchCSharp;
|
|
53
|
+
private detectAsyncWithoutAwaitCSharp;
|
|
54
|
+
private detectDeadlockRiskCSharp;
|
|
55
|
+
private extractBraceBody;
|
|
56
|
+
/** Extract Python indented body after a colon */
|
|
57
|
+
private extractIndentedBody;
|
|
58
|
+
/** Check if line is inside try block (JS/TS/C# — brace-based) */
|
|
59
|
+
private isInsideTryBlock;
|
|
60
|
+
/** Check if line is inside Python try block (indent-based) */
|
|
61
|
+
private isInsidePythonTry;
|
|
62
|
+
/** Check if line is inside Ruby begin/rescue block */
|
|
63
|
+
private isInsideRubyRescue;
|
|
64
|
+
private hasCatchAhead;
|
|
65
|
+
private hasStatusCheckAhead;
|
|
66
|
+
private stripStrings;
|
|
67
|
+
private buildFailures;
|
|
68
|
+
}
|