@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
@@ -0,0 +1,388 @@
1
+ import { Logger } from '../utils/logger.js';
2
+ /**
3
+ * Verify LLM findings against AST-extracted facts.
4
+ * Returns only findings that pass verification.
5
+ */
6
+ export function verifyFindings(findings, facts) {
7
+ const factsByPath = new Map();
8
+ for (const f of facts) {
9
+ factsByPath.set(f.path, f);
10
+ }
11
+ const verified = [];
12
+ for (const finding of findings) {
13
+ const result = verifyFinding(finding, factsByPath);
14
+ if (result.verified) {
15
+ verified.push(result);
16
+ }
17
+ else {
18
+ Logger.debug(`Dropped unverified finding: ${finding.category} in ${finding.file} — ${result.verificationNotes}`);
19
+ }
20
+ }
21
+ return verified;
22
+ }
23
+ function verifyFinding(finding, factsByPath) {
24
+ // Check 1: Does the referenced file exist in facts?
25
+ const fileFacts = findFile(finding.file, factsByPath);
26
+ if (!fileFacts) {
27
+ return {
28
+ ...finding,
29
+ verified: false,
30
+ verificationNotes: `File not found in analyzed files: ${finding.file}`,
31
+ };
32
+ }
33
+ // Check 2: Category-specific verification
34
+ switch (finding.category) {
35
+ // ── Class/Struct-based categories ──
36
+ case 'god_class':
37
+ case 'srp_violation':
38
+ case 'ocp_violation':
39
+ case 'lsp_violation':
40
+ case 'isp_violation':
41
+ case 'dip_violation':
42
+ return verifyClassOrStructFinding(finding, fileFacts);
43
+ // ── Function-based categories ──
44
+ case 'god_function':
45
+ case 'long_params':
46
+ case 'complex_conditional':
47
+ return verifyFunctionFinding(finding, fileFacts);
48
+ // ── Error handling categories ──
49
+ case 'empty_catch':
50
+ case 'error_inconsistency':
51
+ case 'error_swallowing':
52
+ case 'missing_error_check':
53
+ case 'panic_in_library':
54
+ return verifyErrorHandlingFinding(finding, fileFacts);
55
+ // ── Interface categories (Go-specific) ──
56
+ case 'isp_violation_interface':
57
+ return verifyInterfaceFinding(finding, fileFacts);
58
+ // ── Concurrency categories ──
59
+ case 'race_condition':
60
+ case 'goroutine_leak':
61
+ case 'missing_context':
62
+ case 'channel_misuse':
63
+ case 'mutex_scope':
64
+ return verifyConcurrencyFinding(finding, fileFacts);
65
+ // ── Test categories ──
66
+ case 'test_quality':
67
+ case 'test_coupling':
68
+ case 'test_duplication':
69
+ case 'missing_test':
70
+ return verifyTestFinding(finding, fileFacts);
71
+ // ── File-level categories (verified by file existence + basic checks) ──
72
+ case 'long_file':
73
+ return {
74
+ ...finding,
75
+ verified: fileFacts.lineCount > 300,
76
+ verificationNotes: fileFacts.lineCount > 300
77
+ ? `File is ${fileFacts.lineCount} lines`
78
+ : `File is only ${fileFacts.lineCount} lines`,
79
+ };
80
+ case 'magic_number':
81
+ return {
82
+ ...finding,
83
+ verified: (fileFacts.magicNumbers || 0) > 3,
84
+ verificationNotes: `${fileFacts.magicNumbers || 0} magic numbers detected`,
85
+ };
86
+ case 'resource_leak':
87
+ // For Go: check defers vs resource operations
88
+ if (fileFacts.language === 'go') {
89
+ const hasResources = fileFacts.imports.some(i => i.includes('os') || i.includes('net') || i.includes('http') || i.includes('io') || i.includes('sql'));
90
+ return {
91
+ ...finding,
92
+ verified: hasResources && finding.confidence >= 0.4,
93
+ verificationNotes: hasResources ? 'File imports resource packages' : 'No resource imports found',
94
+ };
95
+ }
96
+ return { ...finding, verified: finding.confidence >= 0.4, verificationNotes: 'Accepted on confidence' };
97
+ // ── Categories verified by file existence + reasonable confidence ──
98
+ case 'dry_violation':
99
+ case 'copy_paste_code':
100
+ case 'data_clump':
101
+ case 'feature_envy':
102
+ case 'shotgun_surgery':
103
+ case 'inappropriate_intimacy':
104
+ case 'primitive_obsession':
105
+ case 'lazy_class':
106
+ case 'speculative_generality':
107
+ case 'refused_bequest':
108
+ case 'architecture':
109
+ case 'circular_dependency':
110
+ case 'package_cohesion':
111
+ case 'api_design':
112
+ case 'missing_abstraction':
113
+ case 'language_idiom':
114
+ case 'naming_convention':
115
+ case 'dead_code':
116
+ case 'code_smell':
117
+ case 'performance':
118
+ case 'hardcoded_config':
119
+ return {
120
+ ...finding,
121
+ verified: finding.confidence >= 0.3,
122
+ verificationNotes: finding.confidence < 0.3 ? 'Low confidence' : 'File exists, accepted',
123
+ };
124
+ default:
125
+ // Unknown category — accept if file exists and confidence is reasonable
126
+ return {
127
+ ...finding,
128
+ verified: finding.confidence >= 0.3,
129
+ verificationNotes: 'Unknown category, accepted on confidence',
130
+ };
131
+ }
132
+ }
133
+ /**
134
+ * Verify class OR struct-based findings.
135
+ * For Go: uses structs instead of classes.
136
+ */
137
+ function verifyClassOrStructFinding(finding, facts) {
138
+ // Combine classes and structs for verification
139
+ const entities = [
140
+ ...facts.classes.map(c => ({ name: c.name, methodCount: c.methodCount, lineCount: c.lineCount, methods: c.methods })),
141
+ ...(facts.structs || []).map(s => ({ name: s.name, methodCount: s.methodCount, lineCount: s.lineCount, methods: s.methods })),
142
+ ];
143
+ if (entities.length === 0) {
144
+ // No classes or structs but file exists — for Go, check if file has many functions
145
+ // which effectively makes it a "god module"
146
+ if (facts.language === 'go' && facts.functions.length >= 8) {
147
+ return {
148
+ ...finding,
149
+ verified: true,
150
+ verificationNotes: `Go file with ${facts.functions.length} functions — module-level issue accepted`,
151
+ };
152
+ }
153
+ return {
154
+ ...finding,
155
+ verified: false,
156
+ verificationNotes: 'No classes or structs found in file',
157
+ };
158
+ }
159
+ // Try to find the referenced entity name
160
+ const entityName = extractEntityName(finding.description, entities.map(e => e.name));
161
+ if (!entityName) {
162
+ // Entity not named but file has classes/structs — accept if reasonable
163
+ return {
164
+ ...finding,
165
+ verified: entities.length > 0 && finding.confidence >= 0.4,
166
+ verificationNotes: entities.length > 0 ? 'File has entities, accepted' : 'No entities found',
167
+ };
168
+ }
169
+ const entity = entities.find(e => e.name === entityName);
170
+ if (!entity) {
171
+ return {
172
+ ...finding,
173
+ verified: false,
174
+ verificationNotes: `Entity "${entityName}" not found in ${facts.path}`,
175
+ };
176
+ }
177
+ // Category-specific thresholds
178
+ if (finding.category === 'god_class' || finding.category === 'srp_violation') {
179
+ if (entity.methodCount < 5 && entity.lineCount < 200) {
180
+ return {
181
+ ...finding,
182
+ verified: false,
183
+ verificationNotes: `"${entityName}" has ${entity.methodCount} methods, ${entity.lineCount} lines — below god class threshold`,
184
+ };
185
+ }
186
+ }
187
+ return {
188
+ ...finding,
189
+ verified: true,
190
+ verificationNotes: `"${entityName}" verified (${entity.methodCount} methods, ${entity.lineCount} lines)`,
191
+ };
192
+ }
193
+ function verifyFunctionFinding(finding, facts) {
194
+ const funcName = extractEntityName(finding.description, facts.functions.map(f => f.name));
195
+ if (!funcName) {
196
+ return {
197
+ ...finding,
198
+ verified: facts.functions.length > 0 && finding.confidence >= 0.4,
199
+ verificationNotes: facts.functions.length > 0 ? 'File has functions, accepted' : 'No functions found',
200
+ };
201
+ }
202
+ const func = facts.functions.find(f => f.name === funcName);
203
+ if (!func) {
204
+ return {
205
+ ...finding,
206
+ verified: false,
207
+ verificationNotes: `Function "${funcName}" not found in ${facts.path}`,
208
+ };
209
+ }
210
+ if (finding.category === 'god_function') {
211
+ if (func.lineCount < 30) {
212
+ return {
213
+ ...finding,
214
+ verified: false,
215
+ verificationNotes: `Function "${funcName}" is only ${func.lineCount} lines — not a god function`,
216
+ };
217
+ }
218
+ }
219
+ if (finding.category === 'long_params') {
220
+ if (func.paramCount < 4) {
221
+ return {
222
+ ...finding,
223
+ verified: false,
224
+ verificationNotes: `Function "${funcName}" only has ${func.paramCount} params`,
225
+ };
226
+ }
227
+ }
228
+ if (finding.category === 'complex_conditional') {
229
+ if (func.maxNesting < 3) {
230
+ return {
231
+ ...finding,
232
+ verified: false,
233
+ verificationNotes: `Function "${funcName}" max nesting is only ${func.maxNesting}`,
234
+ };
235
+ }
236
+ }
237
+ return {
238
+ ...finding,
239
+ verified: true,
240
+ verificationNotes: `Function "${funcName}" verified (${func.lineCount} lines, ${func.paramCount} params, nesting:${func.maxNesting})`,
241
+ };
242
+ }
243
+ function verifyErrorHandlingFinding(finding, facts) {
244
+ if (finding.category === 'empty_catch') {
245
+ if (facts.errorHandling.length === 0) {
246
+ return { ...finding, verified: false, verificationNotes: 'No error handling found' };
247
+ }
248
+ const hasEmpty = facts.errorHandling.some(e => e.isEmpty);
249
+ return {
250
+ ...finding,
251
+ verified: hasEmpty,
252
+ verificationNotes: hasEmpty ? 'Empty catch blocks confirmed' : 'No empty catches found',
253
+ };
254
+ }
255
+ if (finding.category === 'error_inconsistency') {
256
+ const strategies = new Set(facts.errorHandling.map(e => e.strategy));
257
+ return {
258
+ ...finding,
259
+ verified: strategies.size >= 2,
260
+ verificationNotes: `${strategies.size} error strategies: ${[...strategies].join(', ')}`,
261
+ };
262
+ }
263
+ // missing_error_check, error_swallowing, panic_in_library
264
+ // These are harder to verify mechanically — accept on confidence + file existence
265
+ return {
266
+ ...finding,
267
+ verified: finding.confidence >= 0.4,
268
+ verificationNotes: 'Accepted on confidence',
269
+ };
270
+ }
271
+ function verifyInterfaceFinding(finding, facts) {
272
+ const interfaces = facts.interfaces || [];
273
+ if (interfaces.length === 0) {
274
+ return { ...finding, verified: false, verificationNotes: 'No interfaces found' };
275
+ }
276
+ const ifaceName = extractEntityName(finding.description, interfaces.map(i => i.name));
277
+ if (ifaceName) {
278
+ const iface = interfaces.find(i => i.name === ifaceName);
279
+ if (iface && iface.methodCount > 5) {
280
+ return {
281
+ ...finding,
282
+ verified: true,
283
+ verificationNotes: `Interface "${ifaceName}" has ${iface.methodCount} methods — ISP violation confirmed`,
284
+ };
285
+ }
286
+ }
287
+ return {
288
+ ...finding,
289
+ verified: finding.confidence >= 0.5,
290
+ verificationNotes: 'Accepted on confidence',
291
+ };
292
+ }
293
+ function verifyConcurrencyFinding(finding, facts) {
294
+ const hasConcurrency = (facts.goroutines || 0) > 0
295
+ || (facts.channels || 0) > 0
296
+ || (facts.mutexes || 0) > 0
297
+ || facts.functions.some(f => f.isAsync);
298
+ if (!hasConcurrency) {
299
+ return {
300
+ ...finding,
301
+ verified: false,
302
+ verificationNotes: 'No concurrency constructs found in file',
303
+ };
304
+ }
305
+ // goroutine_leak: must have goroutines
306
+ if (finding.category === 'goroutine_leak' && (facts.goroutines || 0) === 0) {
307
+ return { ...finding, verified: false, verificationNotes: 'No goroutines found' };
308
+ }
309
+ // channel_misuse: must have channels
310
+ if (finding.category === 'channel_misuse' && (facts.channels || 0) === 0) {
311
+ return { ...finding, verified: false, verificationNotes: 'No channels found' };
312
+ }
313
+ // mutex_scope: must have mutexes
314
+ if (finding.category === 'mutex_scope' && (facts.mutexes || 0) === 0) {
315
+ return { ...finding, verified: false, verificationNotes: 'No mutex usage found' };
316
+ }
317
+ return {
318
+ ...finding,
319
+ verified: finding.confidence >= 0.4,
320
+ verificationNotes: `Concurrency constructs present: goroutines:${facts.goroutines || 0}, channels:${facts.channels || 0}, mutexes:${facts.mutexes || 0}`,
321
+ };
322
+ }
323
+ function verifyTestFinding(finding, facts) {
324
+ if (finding.category === 'missing_test') {
325
+ // The finding says a file needs tests — verify the file is substantial enough
326
+ return {
327
+ ...finding,
328
+ verified: !facts.hasTests && facts.lineCount > 50 && facts.functions.length > 1,
329
+ verificationNotes: facts.hasTests
330
+ ? 'File already has tests'
331
+ : `File has ${facts.lineCount} lines, ${facts.functions.length} functions — needs tests`,
332
+ };
333
+ }
334
+ if (finding.category === 'test_quality' && facts.hasTests) {
335
+ return {
336
+ ...finding,
337
+ verified: finding.confidence >= 0.3,
338
+ verificationNotes: `Test file with ${facts.testAssertions} assertions`,
339
+ };
340
+ }
341
+ return {
342
+ ...finding,
343
+ verified: finding.confidence >= 0.3,
344
+ verificationNotes: 'Accepted on confidence',
345
+ };
346
+ }
347
+ /**
348
+ * Try to find a known entity name referenced in a description string.
349
+ */
350
+ function extractEntityName(description, knownNames) {
351
+ const sorted = [...knownNames].sort((a, b) => b.length - a.length);
352
+ for (const name of sorted) {
353
+ if (description.includes(name)) {
354
+ return name;
355
+ }
356
+ }
357
+ return null;
358
+ }
359
+ /**
360
+ * Find a file in the facts map, handling path normalization.
361
+ */
362
+ function findFile(filePath, factsByPath) {
363
+ if (factsByPath.has(filePath))
364
+ return factsByPath.get(filePath);
365
+ const normalized = filePath.replace(/^\.\//, '');
366
+ if (factsByPath.has(normalized))
367
+ return factsByPath.get(normalized);
368
+ const parts = normalized.split('/');
369
+ if (parts.length >= 2) {
370
+ for (const [key, value] of factsByPath) {
371
+ if (key.endsWith('/' + normalized) || key === normalized) {
372
+ return value;
373
+ }
374
+ }
375
+ }
376
+ else {
377
+ const fileName = parts[0];
378
+ const matches = [];
379
+ for (const [key, value] of factsByPath) {
380
+ if (key.endsWith('/' + fileName) || key === fileName) {
381
+ matches.push(value);
382
+ }
383
+ }
384
+ if (matches.length === 1)
385
+ return matches[0];
386
+ }
387
+ return null;
388
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Deep Analysis Gate — LLM-powered code quality analysis.
3
+ *
4
+ * Three-step pipeline:
5
+ * 1. AST extracts facts → "UserService has 8 public methods touching 4 domains"
6
+ * 2. LLM interprets facts → "UserService violates Single Responsibility"
7
+ * 3. AST verifies LLM → Does UserService actually have those methods? ✓
8
+ *
9
+ * AST grounds LLM. LLM interprets AST. Neither works alone.
10
+ */
11
+ import { Gate, GateContext } from './base.js';
12
+ import { Failure, Provenance, DeepOptions } from '../types/index.js';
13
+ export interface DeepGateConfig {
14
+ options: DeepOptions;
15
+ checks?: Record<string, boolean>;
16
+ threads?: number;
17
+ maxTokens?: number;
18
+ temperature?: number;
19
+ timeoutMs?: number;
20
+ onProgress?: (message: string) => void;
21
+ }
22
+ export declare class DeepAnalysisGate extends Gate {
23
+ private config;
24
+ private provider;
25
+ constructor(config: DeepGateConfig);
26
+ protected get provenance(): Provenance;
27
+ run(context: GateContext): Promise<Failure[]>;
28
+ }