@rigour-labs/core 3.0.5 → 4.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 (85) hide show
  1. package/dist/deep/fact-extractor.d.ts +80 -0
  2. package/dist/deep/fact-extractor.js +626 -0
  3. package/dist/deep/index.d.ts +14 -0
  4. package/dist/deep/index.js +12 -0
  5. package/dist/deep/prompts.d.ts +22 -0
  6. package/dist/deep/prompts.js +374 -0
  7. package/dist/deep/verifier.d.ts +16 -0
  8. package/dist/deep/verifier.js +388 -0
  9. package/dist/gates/deep-analysis.d.ts +28 -0
  10. package/dist/gates/deep-analysis.js +302 -0
  11. package/dist/gates/deprecated-apis-rules-lang.d.ts +21 -0
  12. package/dist/gates/deprecated-apis-rules-lang.js +311 -0
  13. package/dist/gates/deprecated-apis-rules-node.d.ts +19 -0
  14. package/dist/gates/deprecated-apis-rules-node.js +199 -0
  15. package/dist/gates/deprecated-apis-rules.d.ts +6 -0
  16. package/dist/gates/deprecated-apis-rules.js +6 -0
  17. package/dist/gates/deprecated-apis.js +1 -502
  18. package/dist/gates/hallucinated-imports-lang.d.ts +16 -0
  19. package/dist/gates/hallucinated-imports-lang.js +374 -0
  20. package/dist/gates/hallucinated-imports-stdlib.d.ts +12 -0
  21. package/dist/gates/hallucinated-imports-stdlib.js +228 -0
  22. package/dist/gates/hallucinated-imports.d.ts +0 -98
  23. package/dist/gates/hallucinated-imports.js +10 -678
  24. package/dist/gates/phantom-apis-data.d.ts +33 -0
  25. package/dist/gates/phantom-apis-data.js +398 -0
  26. package/dist/gates/phantom-apis.js +1 -393
  27. package/dist/gates/phantom-apis.test.js +52 -0
  28. package/dist/gates/promise-safety-helpers.d.ts +19 -0
  29. package/dist/gates/promise-safety-helpers.js +101 -0
  30. package/dist/gates/promise-safety-rules.d.ts +7 -0
  31. package/dist/gates/promise-safety-rules.js +19 -0
  32. package/dist/gates/promise-safety.d.ts +1 -21
  33. package/dist/gates/promise-safety.js +51 -257
  34. package/dist/gates/runner.d.ts +4 -2
  35. package/dist/gates/runner.js +46 -1
  36. package/dist/gates/test-quality-lang.d.ts +30 -0
  37. package/dist/gates/test-quality-lang.js +188 -0
  38. package/dist/gates/test-quality.d.ts +0 -14
  39. package/dist/gates/test-quality.js +13 -186
  40. package/dist/index.d.ts +10 -0
  41. package/dist/index.js +12 -2
  42. package/dist/inference/cloud-provider.d.ts +34 -0
  43. package/dist/inference/cloud-provider.js +126 -0
  44. package/dist/inference/index.d.ts +17 -0
  45. package/dist/inference/index.js +23 -0
  46. package/dist/inference/model-manager.d.ts +26 -0
  47. package/dist/inference/model-manager.js +106 -0
  48. package/dist/inference/sidecar-provider.d.ts +15 -0
  49. package/dist/inference/sidecar-provider.js +153 -0
  50. package/dist/inference/types.d.ts +77 -0
  51. package/dist/inference/types.js +19 -0
  52. package/dist/pattern-index/indexer-helpers.d.ts +38 -0
  53. package/dist/pattern-index/indexer-helpers.js +111 -0
  54. package/dist/pattern-index/indexer-lang.d.ts +13 -0
  55. package/dist/pattern-index/indexer-lang.js +244 -0
  56. package/dist/pattern-index/indexer-ts.d.ts +22 -0
  57. package/dist/pattern-index/indexer-ts.js +258 -0
  58. package/dist/pattern-index/indexer.d.ts +4 -106
  59. package/dist/pattern-index/indexer.js +58 -707
  60. package/dist/pattern-index/staleness-data.d.ts +6 -0
  61. package/dist/pattern-index/staleness-data.js +262 -0
  62. package/dist/pattern-index/staleness.js +1 -258
  63. package/dist/settings.d.ts +104 -0
  64. package/dist/settings.js +186 -0
  65. package/dist/storage/db.d.ts +16 -0
  66. package/dist/storage/db.js +132 -0
  67. package/dist/storage/findings.d.ts +14 -0
  68. package/dist/storage/findings.js +38 -0
  69. package/dist/storage/index.d.ts +9 -0
  70. package/dist/storage/index.js +8 -0
  71. package/dist/storage/patterns.d.ts +35 -0
  72. package/dist/storage/patterns.js +62 -0
  73. package/dist/storage/scans.d.ts +42 -0
  74. package/dist/storage/scans.js +55 -0
  75. package/dist/templates/index.d.ts +12 -16
  76. package/dist/templates/index.js +11 -527
  77. package/dist/templates/paradigms.d.ts +2 -0
  78. package/dist/templates/paradigms.js +46 -0
  79. package/dist/templates/presets.d.ts +14 -0
  80. package/dist/templates/presets.js +227 -0
  81. package/dist/templates/universal-config.d.ts +2 -0
  82. package/dist/templates/universal-config.js +190 -0
  83. package/dist/types/index.d.ts +438 -15
  84. package/dist/types/index.js +41 -1
  85. package/package.json +6 -2
@@ -2,15 +2,7 @@
2
2
  * Async & Error Safety Gate (Multi-Language)
3
3
  *
4
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
5
+ * Supports: JS/TS, Python, Go, Ruby, C#/.NET
14
6
  *
15
7
  * @since v2.17.0
16
8
  */
@@ -52,17 +44,5 @@ export declare class PromiseSafetyGate extends Gate {
52
44
  private detectUnsafeFetchCSharp;
53
45
  private detectAsyncWithoutAwaitCSharp;
54
46
  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
47
  private buildFailures;
68
48
  }
@@ -2,39 +2,17 @@
2
2
  * Async & Error Safety Gate (Multi-Language)
3
3
  *
4
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
5
+ * Supports: JS/TS, Python, Go, Ruby, C#/.NET
14
6
  *
15
7
  * @since v2.17.0
16
8
  */
17
9
  import { Gate } from './base.js';
18
10
  import { FileScanner } from '../utils/scanner.js';
19
11
  import { Logger } from '../utils/logger.js';
12
+ import { LANG_EXTENSIONS, LANG_GLOBS } from './promise-safety-rules.js';
13
+ import { extractBraceBody, extractIndentedBody, isInsideTryBlock, isInsidePythonTry, isInsideRubyRescue, hasCatchAhead, hasStatusCheckAhead } from './promise-safety-helpers.js';
20
14
  import fs from 'fs-extra';
21
15
  import path from 'path';
22
- // ─── Language Detection ───────────────────────────────────────────
23
- const LANG_EXTENSIONS = {
24
- '.ts': 'js', '.tsx': 'js', '.js': 'js', '.jsx': 'js', '.mjs': 'js', '.cjs': 'js',
25
- '.py': 'python', '.pyw': 'python',
26
- '.go': 'go',
27
- '.rb': 'ruby', '.rake': 'ruby',
28
- '.cs': 'csharp',
29
- };
30
- const LANG_GLOBS = {
31
- js: ['**/*.{ts,js,tsx,jsx,mjs,cjs}'],
32
- python: ['**/*.py'],
33
- go: ['**/*.go'],
34
- ruby: ['**/*.rb'],
35
- csharp: ['**/*.cs'],
36
- unknown: [],
37
- };
38
16
  function detectLang(filePath) {
39
17
  const ext = path.extname(filePath).toLowerCase();
40
18
  return LANG_EXTENSIONS[ext] || 'unknown';
@@ -57,7 +35,6 @@ export class PromiseSafetyGate extends Gate {
57
35
  if (!this.config.enabled)
58
36
  return [];
59
37
  const violations = [];
60
- // Scan all supported languages
61
38
  const allPatterns = Object.values(LANG_GLOBS).flat();
62
39
  const files = await FileScanner.findFiles({
63
40
  cwd: context.cwd,
@@ -79,33 +56,19 @@ export class PromiseSafetyGate extends Gate {
79
56
  const lines = content.split('\n');
80
57
  this.scanFile(lang, lines, content, file, violations);
81
58
  }
82
- catch { /* skip unreadable files */ }
59
+ catch { /* skip */ }
83
60
  }
84
61
  return this.buildFailures(violations);
85
62
  }
86
- // ─── Multi-Language Dispatcher ────────────────────────
87
63
  scanFile(lang, lines, content, file, violations) {
88
64
  switch (lang) {
89
- case 'js':
90
- this.scanJS(lines, content, file, violations);
91
- break;
92
- case 'python':
93
- this.scanPython(lines, content, file, violations);
94
- break;
95
- case 'go':
96
- this.scanGo(lines, content, file, violations);
97
- break;
98
- case 'ruby':
99
- this.scanRuby(lines, content, file, violations);
100
- break;
101
- case 'csharp':
102
- this.scanCSharp(lines, content, file, violations);
103
- break;
65
+ case 'js': return this.scanJS(lines, content, file, violations);
66
+ case 'python': return this.scanPython(lines, content, file, violations);
67
+ case 'go': return this.scanGo(lines, content, file, violations);
68
+ case 'ruby': return this.scanRuby(lines, content, file, violations);
69
+ case 'csharp': return this.scanCSharp(lines, content, file, violations);
104
70
  }
105
71
  }
106
- // ═══════════════════════════════════════════════════════
107
- // JS/TS Checks
108
- // ═══════════════════════════════════════════════════════
109
72
  scanJS(lines, content, file, violations) {
110
73
  if (this.config.check_unhandled_then)
111
74
  this.detectUnhandledThen(lines, file, violations);
@@ -129,36 +92,29 @@ export class PromiseSafetyGate extends Gate {
129
92
  if (j > i && /^(?:const|let|var|function|class|export|import|if|for|while|return)\b/.test(lines[j].trim()))
130
93
  break;
131
94
  }
132
- if (!hasCatch)
133
- hasCatch = this.isInsideTryBlock(lines, i);
134
- const isStored = /(?:const|let|var)\s+\w+\s*=/.test(lines[i]);
135
- if (!hasCatch && !isStored) {
95
+ if (!hasCatch && !isInsideTryBlock(lines, i) && !/(?:const|let|var)\s+\w+\s*=/.test(lines[i])) {
136
96
  violations.push({ file, line: i + 1, type: 'unhandled-then', code: lines[i].trim().substring(0, 80), reason: `.then() chain without .catch() — unhandled promise rejection` });
137
97
  }
138
98
  }
139
99
  }
140
100
  detectUnsafeParseJS(lines, file, violations) {
141
101
  for (let i = 0; i < lines.length; i++) {
142
- if (/JSON\.parse\s*\(/.test(lines[i]) && !this.isInsideTryBlock(lines, i)) {
102
+ if (/JSON\.parse\s*\(/.test(lines[i]) && !isInsideTryBlock(lines, i)) {
143
103
  violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON.parse() without try/catch — crashes on malformed input` });
144
104
  }
145
105
  }
146
106
  }
147
107
  detectAsyncWithoutAwaitJS(content, file, violations) {
148
- const patterns = [
149
- /async\s+function\s+(\w+)\s*\([^)]*\)\s*\{/g,
150
- /(?:const|let|var)\s+(\w+)\s*=\s*async\s+(?:\([^)]*\)|[a-zA-Z_$]\w*)\s*=>\s*\{/g,
151
- /async\s+(\w+)\s*\([^)]*\)\s*(?::\s*[^{]+)?\s*\{/g,
152
- ];
108
+ const patterns = [/async\s+function\s+(\w+)\s*\([^)]*\)\s*\{/g, /(?:const|let|var)\s+(\w+)\s*=\s*async\s+(?:\([^)]*\)|[a-zA-Z_$]\w*)\s*=>\s*\{/g];
153
109
  for (const pattern of patterns) {
154
110
  pattern.lastIndex = 0;
155
111
  let match;
156
112
  while ((match = pattern.exec(content)) !== null) {
157
113
  const funcName = match[1];
158
- const body = this.extractBraceBody(content, match.index + match[0].length);
114
+ const body = extractBraceBody(content, match.index + match[0].length);
159
115
  if (body && !/\bawait\b/.test(body) && body.trim().split('\n').length > 2) {
160
116
  const lineNum = content.substring(0, match.index).split('\n').length;
161
- violations.push({ file, line: lineNum, type: 'async-no-await', code: `async ${funcName}()`, reason: `async function '${funcName}' never uses await — unnecessary async or missing await` });
117
+ violations.push({ file, line: lineNum, type: 'async-no-await', code: `async ${funcName}()`, reason: `async function never uses await — unnecessary async or missing await` });
162
118
  }
163
119
  }
164
120
  }
@@ -167,16 +123,11 @@ export class PromiseSafetyGate extends Gate {
167
123
  for (let i = 0; i < lines.length; i++) {
168
124
  if (!/\bfetch\s*\(/.test(lines[i]) && !/\baxios\.\w+\s*\(/.test(lines[i]))
169
125
  continue;
170
- if (this.isInsideTryBlock(lines, i))
171
- continue;
172
- if (this.hasCatchAhead(lines, i) || this.hasStatusCheckAhead(lines, i))
126
+ if (isInsideTryBlock(lines, i) || hasCatchAhead(lines, i) || hasStatusCheckAhead(lines, i))
173
127
  continue;
174
- violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without error handling (no try/catch, no .catch(), no .ok check)` });
128
+ violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without error handling` });
175
129
  }
176
130
  }
177
- // ═══════════════════════════════════════════════════════
178
- // Python Checks
179
- // ═══════════════════════════════════════════════════════
180
131
  scanPython(lines, content, file, violations) {
181
132
  if (this.config.check_unsafe_parse)
182
133
  this.detectUnsafeParsePython(lines, file, violations);
@@ -188,11 +139,8 @@ export class PromiseSafetyGate extends Gate {
188
139
  }
189
140
  detectUnsafeParsePython(lines, file, violations) {
190
141
  for (let i = 0; i < lines.length; i++) {
191
- if (/json\.loads?\s*\(/.test(lines[i]) && !this.isInsidePythonTry(lines, i)) {
192
- violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `json.loads() without try/except — crashes on malformed input` });
193
- }
194
- if (/yaml\.safe_load\s*\(/.test(lines[i]) && !this.isInsidePythonTry(lines, i)) {
195
- violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `yaml.safe_load() without try/except — crashes on malformed input` });
142
+ if (/json\.loads?\s*\(/.test(lines[i]) && !isInsidePythonTry(lines, i)) {
143
+ violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `json.loads() without try/except` });
196
144
  }
197
145
  }
198
146
  }
@@ -201,30 +149,27 @@ export class PromiseSafetyGate extends Gate {
201
149
  let match;
202
150
  while ((match = pattern.exec(content)) !== null) {
203
151
  const funcName = match[1];
204
- const body = this.extractIndentedBody(content, match.index + match[0].length);
152
+ const body = extractIndentedBody(content, match.index + match[0].length);
205
153
  if (body && !/\bawait\b/.test(body) && body.trim().split('\n').length > 2) {
206
154
  const lineNum = content.substring(0, match.index).split('\n').length;
207
- violations.push({ file, line: lineNum, type: 'async-no-await', code: `async def ${funcName}()`, reason: `async def '${funcName}' never uses await — unnecessary async or missing await` });
155
+ violations.push({ file, line: lineNum, type: 'async-no-await', code: `async def ${funcName}()`, reason: `async def never uses await` });
208
156
  }
209
157
  }
210
158
  }
211
159
  detectUnsafeFetchPython(lines, file, violations) {
212
- const httpPatterns = /\b(?:requests\.(?:get|post|put|patch|delete)|httpx\.(?:get|post|put|patch|delete)|aiohttp\.ClientSession|urllib\.request\.urlopen)\s*\(/;
160
+ const httpPatterns = /\b(?:requests|httpx|aiohttp|urllib)\.(?:get|post|ClientSession|urlopen)\s*\(/;
213
161
  for (let i = 0; i < lines.length; i++) {
214
- if (!httpPatterns.test(lines[i]))
162
+ if (!httpPatterns.test(lines[i]) || isInsidePythonTry(lines, i))
215
163
  continue;
216
- if (this.isInsidePythonTry(lines, i))
217
- continue;
218
- // Check for raise_for_status() within 10 lines
219
164
  let hasCheck = false;
220
165
  for (let j = i; j < Math.min(i + 10, lines.length); j++) {
221
- if (/raise_for_status\s*\(/.test(lines[j]) || /\.status_code\b/.test(lines[j])) {
166
+ if (/raise_for_status|status_code/.test(lines[j])) {
222
167
  hasCheck = true;
223
168
  break;
224
169
  }
225
170
  }
226
171
  if (!hasCheck) {
227
- violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without error handling (no try/except, no raise_for_status)` });
172
+ violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without error handling` });
228
173
  }
229
174
  }
230
175
  }
@@ -232,17 +177,13 @@ export class PromiseSafetyGate extends Gate {
232
177
  for (let i = 0; i < lines.length; i++) {
233
178
  const trimmed = lines[i].trim();
234
179
  if (/^except\s*:/.test(trimmed) || /^except\s+Exception\s*:/.test(trimmed)) {
235
- // Check if the except block just passes (swallows errors silently)
236
180
  const nextLine = i + 1 < lines.length ? lines[i + 1].trim() : '';
237
181
  if (nextLine === 'pass' || nextLine === '...') {
238
- violations.push({ file, line: i + 1, type: 'bare-except', code: trimmed, reason: `Bare except with pass — silently swallows all errors` });
182
+ violations.push({ file, line: i + 1, type: 'bare-except', code: trimmed, reason: `Bare except with pass — silently swallows errors` });
239
183
  }
240
184
  }
241
185
  }
242
186
  }
243
- // ═══════════════════════════════════════════════════════
244
- // Go Checks
245
- // ═══════════════════════════════════════════════════════
246
187
  scanGo(lines, content, file, violations) {
247
188
  if (this.config.check_unsafe_parse)
248
189
  this.detectUnsafeParseGo(lines, file, violations);
@@ -252,52 +193,26 @@ export class PromiseSafetyGate extends Gate {
252
193
  }
253
194
  detectUnsafeParseGo(lines, file, violations) {
254
195
  for (let i = 0; i < lines.length; i++) {
255
- // json.Unmarshal returns error check if error is ignored
256
- if (/json\.(?:Unmarshal|NewDecoder)/.test(lines[i])) {
257
- if (/\b_\s*(?:=|:=)/.test(lines[i]) || !/\berr\b/.test(lines[i])) {
258
- violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON decode with ignored error return — crashes on malformed input` });
259
- }
196
+ if (/json\.(?:Unmarshal|NewDecoder)/.test(lines[i]) && (/\b_\s*(?:=|:=)/.test(lines[i]) || !/\berr\b/.test(lines[i]))) {
197
+ violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON decode with ignored error` });
260
198
  }
261
199
  }
262
200
  }
263
201
  detectUnsafeFetchGo(lines, file, violations) {
264
202
  for (let i = 0; i < lines.length; i++) {
265
- if (/http\.(?:Get|Post|Do|Head)\s*\(/.test(lines[i])) {
266
- if (/\b_\s*(?:=|:=)/.test(lines[i]) || !/\berr\b/.test(lines[i])) {
267
- violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call with ignored error return — unhandled network errors` });
268
- }
269
- // Also check if resp.Body is closed (defer resp.Body.Close())
270
- let hasClose = false;
271
- for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
272
- if (/defer\s+.*\.Body\.Close\(\)/.test(lines[j]) || /\.Body\.Close\(\)/.test(lines[j])) {
273
- hasClose = true;
274
- break;
275
- }
276
- }
277
- if (!hasClose && /\berr\b/.test(lines[i])) {
278
- // Don't flag if error IS checked — only flag missing Body.Close
279
- // This is a softer check, skip for now to reduce noise
280
- }
203
+ if (/http\.(?:Get|Post|Do|Head)\s*\(/.test(lines[i]) && (/\b_\s*(?:=|:=)/.test(lines[i]) || !/\berr\b/.test(lines[i]))) {
204
+ violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call with ignored error` });
281
205
  }
282
206
  }
283
207
  }
284
208
  detectIgnoredErrorsGo(lines, file, violations) {
285
209
  for (let i = 0; i < lines.length; i++) {
286
- // Detect: result, _ := someFunc() or _ = someFunc()
287
- // Only flag when the ignored return is likely an error
288
210
  const match = lines[i].match(/(\w+)\s*,\s*_\s*(?::=|=)\s*(\w+)\./);
289
- if (match) {
290
- const funcCall = lines[i].trim();
291
- // Common error-returning functions
292
- if (/\b(?:os\.|io\.|ioutil\.|bufio\.|sql\.|net\.|http\.|json\.|xml\.|yaml\.|strconv\.)/.test(funcCall)) {
293
- violations.push({ file, line: i + 1, type: 'ignored-error', code: funcCall.substring(0, 80), reason: `Error return ignored with _ — unhandled error can cause silent failures` });
294
- }
211
+ if (match && /\b(?:os|io|ioutil|bufio|sql|net|http|json|xml|yaml|strconv)\./.test(lines[i].trim())) {
212
+ violations.push({ file, line: i + 1, type: 'ignored-error', code: lines[i].trim().substring(0, 80), reason: `Error return ignored with _` });
295
213
  }
296
214
  }
297
215
  }
298
- // ═══════════════════════════════════════════════════════
299
- // Ruby Checks
300
- // ═══════════════════════════════════════════════════════
301
216
  scanRuby(lines, content, file, violations) {
302
217
  if (this.config.check_unsafe_parse)
303
218
  this.detectUnsafeParseRuby(lines, file, violations);
@@ -306,26 +221,18 @@ export class PromiseSafetyGate extends Gate {
306
221
  }
307
222
  detectUnsafeParseRuby(lines, file, violations) {
308
223
  for (let i = 0; i < lines.length; i++) {
309
- if (/JSON\.parse\s*\(/.test(lines[i]) && !this.isInsideRubyRescue(lines, i)) {
310
- violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON.parse without begin/rescue — crashes on malformed input` });
311
- }
312
- if (/YAML\.(?:safe_)?load\s*\(/.test(lines[i]) && !this.isInsideRubyRescue(lines, i)) {
313
- violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `YAML.load without begin/rescue — crashes on malformed input` });
224
+ if (/JSON\.parse\s*\(/.test(lines[i]) && !isInsideRubyRescue(lines, i)) {
225
+ violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON.parse without begin/rescue` });
314
226
  }
315
227
  }
316
228
  }
317
229
  detectUnsafeFetchRuby(lines, file, violations) {
318
230
  for (let i = 0; i < lines.length; i++) {
319
- if (/Net::HTTP\.(?:get|post|start)\s*\(/.test(lines[i]) || /HTTParty\.(?:get|post)\s*\(/.test(lines[i]) || /Faraday\.(?:get|post)\s*\(/.test(lines[i])) {
320
- if (!this.isInsideRubyRescue(lines, i)) {
321
- violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without begin/rescue — unhandled network errors` });
322
- }
231
+ if (/(?:Net::HTTP|HTTParty|Faraday)\.(?:get|post|start)\s*\(/.test(lines[i]) && !isInsideRubyRescue(lines, i)) {
232
+ violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without begin/rescue` });
323
233
  }
324
234
  }
325
235
  }
326
- // ═══════════════════════════════════════════════════════
327
- // C# / .NET Checks
328
- // ═══════════════════════════════════════════════════════
329
236
  scanCSharp(lines, content, file, violations) {
330
237
  if (this.config.check_unsafe_parse)
331
238
  this.detectUnsafeParseCSharp(lines, file, violations);
@@ -337,28 +244,24 @@ export class PromiseSafetyGate extends Gate {
337
244
  }
338
245
  detectUnsafeParseCSharp(lines, file, violations) {
339
246
  for (let i = 0; i < lines.length; i++) {
340
- if (/JsonSerializer\.Deserialize/.test(lines[i]) || /JsonConvert\.DeserializeObject/.test(lines[i]) || /JObject\.Parse/.test(lines[i])) {
341
- if (!this.isInsideTryBlock(lines, i)) {
342
- violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON deserialization without try/catch — crashes on malformed input` });
343
- }
247
+ if (/JsonSerializer|JsonConvert|JObject/.test(lines[i]) && !isInsideTryBlock(lines, i)) {
248
+ violations.push({ file, line: i + 1, type: 'unsafe-parse', code: lines[i].trim().substring(0, 80), reason: `JSON deserialization without try/catch` });
344
249
  }
345
250
  }
346
251
  }
347
252
  detectUnsafeFetchCSharp(lines, file, violations) {
348
253
  for (let i = 0; i < lines.length; i++) {
349
- if (/\.GetAsync\s*\(/.test(lines[i]) || /\.PostAsync\s*\(/.test(lines[i]) || /\.SendAsync\s*\(/.test(lines[i]) || /HttpClient\.\w+Async/.test(lines[i])) {
350
- if (!this.isInsideTryBlock(lines, i)) {
351
- let hasCheck = false;
352
- for (let j = i; j < Math.min(i + 10, lines.length); j++) {
353
- if (/EnsureSuccessStatusCode/.test(lines[j]) || /\.IsSuccessStatusCode/.test(lines[j]) || /\.StatusCode/.test(lines[j])) {
354
- hasCheck = true;
355
- break;
356
- }
357
- }
358
- if (!hasCheck) {
359
- violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without error handling (no try/catch, no status check)` });
254
+ if (/\.(?:GetAsync|PostAsync|SendAsync)\s*\(/.test(lines[i]) && !isInsideTryBlock(lines, i)) {
255
+ let hasCheck = false;
256
+ for (let j = i; j < Math.min(i + 10, lines.length); j++) {
257
+ if (/EnsureSuccess|IsSuccessStatusCode|StatusCode/.test(lines[j])) {
258
+ hasCheck = true;
259
+ break;
360
260
  }
361
261
  }
262
+ if (!hasCheck) {
263
+ violations.push({ file, line: i + 1, type: 'unsafe-fetch', code: lines[i].trim().substring(0, 80), reason: `HTTP call without error handling` });
264
+ }
362
265
  }
363
266
  }
364
267
  }
@@ -367,127 +270,20 @@ export class PromiseSafetyGate extends Gate {
367
270
  let match;
368
271
  while ((match = pattern.exec(content)) !== null) {
369
272
  const funcName = match[1];
370
- const body = this.extractBraceBody(content, match.index + match[0].length);
273
+ const body = extractBraceBody(content, match.index + match[0].length);
371
274
  if (body && !/\bawait\b/.test(body) && body.trim().split('\n').length > 2) {
372
275
  const lineNum = content.substring(0, match.index).split('\n').length;
373
- violations.push({ file, line: lineNum, type: 'async-no-await', code: `async Task ${funcName}()`, reason: `async method '${funcName}' never uses await — unnecessary async or missing await` });
276
+ violations.push({ file, line: lineNum, type: 'async-no-await', code: `async Task ${funcName}()`, reason: `async method never uses await` });
374
277
  }
375
278
  }
376
279
  }
377
280
  detectDeadlockRiskCSharp(lines, file, violations) {
378
281
  for (let i = 0; i < lines.length; i++) {
379
- if (/\.Result\b/.test(lines[i]) || /\.Wait\(\)/.test(lines[i]) || /\.GetAwaiter\(\)\.GetResult\(\)/.test(lines[i])) {
380
- // Common AI mistake: using .Result or .Wait() on async tasks causes deadlocks
381
- violations.push({ file, line: i + 1, type: 'deadlock-risk', code: lines[i].trim().substring(0, 80), reason: `.Result/.Wait() on async task — deadlock risk in synchronous context` });
382
- }
383
- }
384
- }
385
- // ═══════════════════════════════════════════════════════
386
- // Shared Helpers
387
- // ═══════════════════════════════════════════════════════
388
- extractBraceBody(content, startIdx) {
389
- let depth = 1;
390
- let idx = startIdx;
391
- while (depth > 0 && idx < content.length) {
392
- if (content[idx] === '{')
393
- depth++;
394
- if (content[idx] === '}')
395
- depth--;
396
- idx++;
397
- }
398
- return depth === 0 ? content.substring(startIdx, idx - 1) : null;
399
- }
400
- /** Extract Python indented body after a colon */
401
- extractIndentedBody(content, startIdx) {
402
- const rest = content.substring(startIdx);
403
- const lines = rest.split('\n');
404
- if (lines.length < 2)
405
- return null;
406
- // Find indent level of first non-empty line after the def
407
- let bodyIndent = -1;
408
- const bodyLines = [];
409
- for (let i = 1; i < lines.length; i++) {
410
- const line = lines[i];
411
- if (line.trim() === '' || line.trim().startsWith('#')) {
412
- bodyLines.push(line);
413
- continue;
414
- }
415
- const indent = line.length - line.trimStart().length;
416
- if (bodyIndent === -1) {
417
- bodyIndent = indent;
282
+ if (/\.Result\b|\.Wait\(\)|\.GetAwaiter\(\)\.GetResult\(\)/.test(lines[i])) {
283
+ violations.push({ file, line: i + 1, type: 'deadlock-risk', code: lines[i].trim().substring(0, 80), reason: `.Result/.Wait() on async task deadlock risk` });
418
284
  }
419
- if (indent < bodyIndent)
420
- break;
421
- bodyLines.push(line);
422
285
  }
423
- return bodyLines.join('\n');
424
286
  }
425
- /** Check if line is inside try block (JS/TS/C# — brace-based) */
426
- isInsideTryBlock(lines, lineIdx) {
427
- let braceDepth = 0;
428
- for (let j = lineIdx - 1; j >= Math.max(0, lineIdx - 30); j--) {
429
- const prevLine = this.stripStrings(lines[j]);
430
- for (const ch of prevLine) {
431
- if (ch === '}')
432
- braceDepth++;
433
- if (ch === '{')
434
- braceDepth--;
435
- }
436
- if (/\btry\s*\{/.test(prevLine) && braceDepth <= 0)
437
- return true;
438
- if (/\}\s*catch\s*\(/.test(prevLine))
439
- return false;
440
- }
441
- return false;
442
- }
443
- /** Check if line is inside Python try block (indent-based) */
444
- isInsidePythonTry(lines, lineIdx) {
445
- const lineIndent = lines[lineIdx].length - lines[lineIdx].trimStart().length;
446
- for (let j = lineIdx - 1; j >= Math.max(0, lineIdx - 30); j--) {
447
- const trimmed = lines[j].trim();
448
- if (trimmed === '')
449
- continue;
450
- const indent = lines[j].length - lines[j].trimStart().length;
451
- if (indent < lineIndent && /^\s*try\s*:/.test(lines[j]))
452
- return true;
453
- if (indent < lineIndent && /^\s*(?:except|finally)\s*/.test(lines[j]))
454
- return false;
455
- if (indent === 0 && /^(?:def|class|async\s+def)\s/.test(trimmed))
456
- break;
457
- }
458
- return false;
459
- }
460
- /** Check if line is inside Ruby begin/rescue block */
461
- isInsideRubyRescue(lines, lineIdx) {
462
- for (let j = lineIdx - 1; j >= Math.max(0, lineIdx - 30); j--) {
463
- const trimmed = lines[j].trim();
464
- if (trimmed === 'begin')
465
- return true;
466
- if (/^rescue\b/.test(trimmed))
467
- return false;
468
- if (/^(?:def|class|module)\s/.test(trimmed))
469
- break;
470
- }
471
- return false;
472
- }
473
- hasCatchAhead(lines, idx) {
474
- for (let j = idx; j < Math.min(idx + 10, lines.length); j++) {
475
- if (/\.catch\s*\(/.test(lines[j]))
476
- return true;
477
- }
478
- return false;
479
- }
480
- hasStatusCheckAhead(lines, idx) {
481
- for (let j = idx; j < Math.min(idx + 10, lines.length); j++) {
482
- if (/\.\s*ok\b/.test(lines[j]) || /\.status(?:Text)?\b/.test(lines[j]))
483
- return true;
484
- }
485
- return false;
486
- }
487
- stripStrings(line) {
488
- return line.replace(/`[^`]*`/g, '""').replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, '""');
489
- }
490
- // ─── Failure Aggregation ──────────────────────────────
491
287
  buildFailures(violations) {
492
288
  const byFile = new Map();
493
289
  for (const v of violations) {
@@ -500,9 +296,7 @@ export class PromiseSafetyGate extends Gate {
500
296
  const details = fileViolations.map(v => ` L${v.line}: [${v.type}] ${v.reason}`).join('\n');
501
297
  const hasHighSev = fileViolations.some(v => v.type !== 'async-no-await');
502
298
  const severity = hasHighSev ? 'high' : 'medium';
503
- const lang = detectLang(file);
504
- const langLabel = lang === 'js' ? 'JS/TS' : lang === 'csharp' ? 'C#' : lang.charAt(0).toUpperCase() + lang.slice(1);
505
- failures.push(this.createFailure(`Unsafe async/error patterns in ${file}:\n${details}`, [file], `AI code generators often produce incomplete error handling in ${langLabel}. Ensure all parse operations are wrapped in error handling, async functions use await, and HTTP calls check for errors.`, 'Async & Error Safety Violation', fileViolations[0].line, undefined, severity));
299
+ failures.push(this.createFailure(`Unsafe async/error patterns in ${file}:\n${details}`, [file], `Review and fix async/error handling patterns.`, 'Async & Error Safety Violation', fileViolations[0].line, undefined, severity));
506
300
  }
507
301
  return failures;
508
302
  }
@@ -1,5 +1,5 @@
1
1
  import { Gate } from './base.js';
2
- import { Config, Report } from '../types/index.js';
2
+ import { Config, Report, DeepOptions } from '../types/index.js';
3
3
  export declare class GateRunner {
4
4
  private config;
5
5
  private gates;
@@ -9,5 +9,7 @@ export declare class GateRunner {
9
9
  * Allows adding custom gates dynamically (SOLID - Open/Closed Principle)
10
10
  */
11
11
  addGate(gate: Gate): void;
12
- run(cwd: string, patterns?: string[]): Promise<Report>;
12
+ run(cwd: string, patterns?: string[], deepOptions?: DeepOptions & {
13
+ onProgress?: (msg: string) => void;
14
+ }): Promise<Report>;
13
15
  }
@@ -1,4 +1,5 @@
1
1
  import { SEVERITY_WEIGHTS } from '../types/index.js';
2
+ import { DeepAnalysisGate } from './deep-analysis.js';
2
3
  import { FileGate } from './file.js';
3
4
  import { ContentGate } from './content.js';
4
5
  import { StructureGate } from './structure.js';
@@ -102,7 +103,7 @@ export class GateRunner {
102
103
  addGate(gate) {
103
104
  this.gates.push(gate);
104
105
  }
105
- async run(cwd, patterns) {
106
+ async run(cwd, patterns, deepOptions) {
106
107
  const start = Date.now();
107
108
  const failures = [];
108
109
  const summary = {};
@@ -164,6 +165,43 @@ export class GateRunner {
164
165
  }
165
166
  }
166
167
  }
168
+ // 3. Run Deep Analysis (if enabled)
169
+ let deepStats = undefined;
170
+ if (deepOptions?.enabled) {
171
+ const deepSetupStart = Date.now();
172
+ const deepGate = new DeepAnalysisGate({
173
+ options: deepOptions,
174
+ checks: this.config.gates.deep?.checks,
175
+ threads: this.config.gates.deep?.threads,
176
+ maxTokens: this.config.gates.deep?.max_tokens,
177
+ temperature: this.config.gates.deep?.temperature,
178
+ timeoutMs: this.config.gates.deep?.timeout_ms,
179
+ onProgress: deepOptions.onProgress,
180
+ });
181
+ try {
182
+ const deepFailures = await deepGate.run({ cwd, ignore, patterns });
183
+ if (deepFailures.length > 0) {
184
+ failures.push(...deepFailures);
185
+ summary['deep-analysis'] = 'FAIL';
186
+ }
187
+ else {
188
+ summary['deep-analysis'] = 'PASS';
189
+ }
190
+ deepStats = {
191
+ enabled: true,
192
+ tier: deepOptions.apiKey ? 'cloud' : (deepOptions.pro ? 'pro' : 'deep'),
193
+ model: deepOptions.apiKey ? (deepOptions.provider || 'cloud') : (deepOptions.pro ? 'Qwen2.5-Coder-1.5B' : 'Qwen2.5-Coder-0.5B'),
194
+ total_ms: Date.now() - deepSetupStart,
195
+ findings_count: deepFailures.length,
196
+ findings_verified: deepFailures.filter((f) => f.verified).length,
197
+ };
198
+ }
199
+ catch (error) {
200
+ Logger.error(`Deep analysis failed: ${error.message}`);
201
+ summary['deep-analysis'] = 'ERROR';
202
+ deepStats = { enabled: true };
203
+ }
204
+ }
167
205
  const status = failures.length > 0 ? 'FAIL' : 'PASS';
168
206
  // Severity-weighted scoring: each failure deducts based on its severity
169
207
  const severityBreakdown = {};
@@ -180,11 +218,13 @@ export class GateRunner {
180
218
  // preventing security criticals from incorrectly zeroing structural_score.
181
219
  let aiDeduction = 0;
182
220
  let structuralDeduction = 0;
221
+ let deepDeduction = 0;
183
222
  const provenanceCounts = {
184
223
  'ai-drift': 0,
185
224
  'traditional': 0,
186
225
  'security': 0,
187
226
  'governance': 0,
227
+ 'deep-analysis': 0,
188
228
  };
189
229
  for (const f of failures) {
190
230
  const sev = (f.severity || 'medium');
@@ -198,6 +238,9 @@ export class GateRunner {
198
238
  case 'traditional':
199
239
  structuralDeduction += weight;
200
240
  break;
241
+ case 'deep-analysis':
242
+ deepDeduction += weight;
243
+ break;
201
244
  // security and governance contribute to overall score (totalDeduction)
202
245
  // but do NOT pollute the sub-scores
203
246
  case 'security':
@@ -214,8 +257,10 @@ export class GateRunner {
214
257
  score,
215
258
  ai_health_score: Math.max(0, 100 - aiDeduction),
216
259
  structural_score: Math.max(0, 100 - structuralDeduction),
260
+ ...(deepOptions?.enabled ? { code_quality_score: Math.max(0, 100 - deepDeduction) } : {}),
217
261
  severity_breakdown: severityBreakdown,
218
262
  provenance_breakdown: provenanceCounts,
263
+ ...(deepStats ? { deep: deepStats } : {}),
219
264
  },
220
265
  };
221
266
  }