@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.
Files changed (101) hide show
  1. package/README.md +58 -0
  2. package/dist/context.test.js +2 -3
  3. package/dist/environment.test.js +2 -1
  4. package/dist/gates/agent-team.d.ts +2 -1
  5. package/dist/gates/agent-team.js +1 -0
  6. package/dist/gates/base.d.ts +4 -2
  7. package/dist/gates/base.js +5 -1
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/content.js +1 -1
  11. package/dist/gates/context-window-artifacts.d.ts +34 -0
  12. package/dist/gates/context-window-artifacts.js +214 -0
  13. package/dist/gates/context.d.ts +2 -1
  14. package/dist/gates/context.js +4 -3
  15. package/dist/gates/coverage.js +3 -1
  16. package/dist/gates/dependency.js +5 -5
  17. package/dist/gates/duplication-drift.d.ts +33 -0
  18. package/dist/gates/duplication-drift.js +190 -0
  19. package/dist/gates/environment.js +4 -4
  20. package/dist/gates/file.js +1 -1
  21. package/dist/gates/hallucinated-imports.d.ts +63 -0
  22. package/dist/gates/hallucinated-imports.js +406 -0
  23. package/dist/gates/inconsistent-error-handling.d.ts +39 -0
  24. package/dist/gates/inconsistent-error-handling.js +236 -0
  25. package/dist/gates/promise-safety.d.ts +68 -0
  26. package/dist/gates/promise-safety.js +509 -0
  27. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  28. package/dist/gates/retry-loop-breaker.js +2 -1
  29. package/dist/gates/runner.js +62 -1
  30. package/dist/gates/safety.d.ts +2 -1
  31. package/dist/gates/safety.js +2 -1
  32. package/dist/gates/security-patterns.d.ts +2 -1
  33. package/dist/gates/security-patterns.js +2 -1
  34. package/dist/gates/structure.js +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/services/fix-packet-service.d.ts +0 -1
  38. package/dist/services/fix-packet-service.js +9 -14
  39. package/dist/services/score-history.d.ts +54 -0
  40. package/dist/services/score-history.js +122 -0
  41. package/dist/templates/index.js +195 -0
  42. package/dist/types/fix-packet.d.ts +5 -5
  43. package/dist/types/fix-packet.js +1 -1
  44. package/dist/types/index.d.ts +430 -0
  45. package/dist/types/index.js +57 -0
  46. package/package.json +21 -1
  47. package/src/context.test.ts +0 -256
  48. package/src/discovery.test.ts +0 -88
  49. package/src/discovery.ts +0 -112
  50. package/src/environment.test.ts +0 -115
  51. package/src/gates/agent-team.test.ts +0 -134
  52. package/src/gates/agent-team.ts +0 -210
  53. package/src/gates/ast-handlers/base.ts +0 -13
  54. package/src/gates/ast-handlers/python.ts +0 -145
  55. package/src/gates/ast-handlers/python_parser.py +0 -181
  56. package/src/gates/ast-handlers/typescript.ts +0 -264
  57. package/src/gates/ast-handlers/universal.ts +0 -184
  58. package/src/gates/ast.ts +0 -54
  59. package/src/gates/base.ts +0 -27
  60. package/src/gates/checkpoint.test.ts +0 -135
  61. package/src/gates/checkpoint.ts +0 -311
  62. package/src/gates/content.ts +0 -50
  63. package/src/gates/context.ts +0 -267
  64. package/src/gates/coverage.ts +0 -74
  65. package/src/gates/dependency.ts +0 -108
  66. package/src/gates/environment.ts +0 -94
  67. package/src/gates/file.ts +0 -42
  68. package/src/gates/retry-loop-breaker.ts +0 -151
  69. package/src/gates/runner.ts +0 -156
  70. package/src/gates/safety.ts +0 -56
  71. package/src/gates/security-patterns.test.ts +0 -162
  72. package/src/gates/security-patterns.ts +0 -305
  73. package/src/gates/structure.ts +0 -36
  74. package/src/index.ts +0 -13
  75. package/src/pattern-index/embeddings.ts +0 -84
  76. package/src/pattern-index/index.ts +0 -59
  77. package/src/pattern-index/indexer.test.ts +0 -276
  78. package/src/pattern-index/indexer.ts +0 -1023
  79. package/src/pattern-index/matcher.test.ts +0 -293
  80. package/src/pattern-index/matcher.ts +0 -493
  81. package/src/pattern-index/overrides.ts +0 -235
  82. package/src/pattern-index/security.ts +0 -151
  83. package/src/pattern-index/staleness.test.ts +0 -313
  84. package/src/pattern-index/staleness.ts +0 -568
  85. package/src/pattern-index/types.ts +0 -339
  86. package/src/safety.test.ts +0 -53
  87. package/src/services/adaptive-thresholds.test.ts +0 -189
  88. package/src/services/adaptive-thresholds.ts +0 -275
  89. package/src/services/context-engine.ts +0 -104
  90. package/src/services/fix-packet-service.ts +0 -42
  91. package/src/services/state-service.ts +0 -138
  92. package/src/smoke.test.ts +0 -18
  93. package/src/templates/index.ts +0 -312
  94. package/src/types/fix-packet.ts +0 -32
  95. package/src/types/index.ts +0 -159
  96. package/src/utils/logger.ts +0 -43
  97. package/src/utils/scanner.test.ts +0 -37
  98. package/src/utils/scanner.ts +0 -43
  99. package/tsconfig.json +0 -10
  100. package/vitest.config.ts +0 -7
  101. 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
+ }