@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,30 @@
1
+ /**
2
+ * Language-specific test quality checks for Go and Java/Kotlin.
3
+ * Extracted from test-quality.ts to keep it under 500 lines.
4
+ */
5
+ export interface TestQualityIssue {
6
+ file: string;
7
+ line: number;
8
+ pattern: string;
9
+ reason: string;
10
+ }
11
+ export interface TestQualityConfig {
12
+ check_empty_tests?: boolean;
13
+ check_tautological?: boolean;
14
+ check_mock_heavy?: boolean;
15
+ max_mocks_per_test?: number;
16
+ }
17
+ /**
18
+ * Go test quality checks.
19
+ * Go tests use func TestXxx(t *testing.T) pattern.
20
+ * Assertions via t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow.
21
+ * Also checks for t.Run subtests and table-driven patterns.
22
+ */
23
+ export declare function checkGoTestQuality(content: string, file: string, issues: TestQualityIssue[], config: TestQualityConfig): void;
24
+ /**
25
+ * Java/Kotlin test quality checks.
26
+ * JUnit 4: @Test + Assert.assertEquals, assertTrue, etc.
27
+ * JUnit 5: @Test + Assertions.assertEquals, assertThrows, etc.
28
+ * Kotlin: @Test + kotlin.test assertEquals, etc.
29
+ */
30
+ export declare function checkJavaKotlinTestQuality(content: string, file: string, ext: string, issues: TestQualityIssue[], config: TestQualityConfig): void;
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Language-specific test quality checks for Go and Java/Kotlin.
3
+ * Extracted from test-quality.ts to keep it under 500 lines.
4
+ */
5
+ /**
6
+ * Go test quality checks.
7
+ * Go tests use func TestXxx(t *testing.T) pattern.
8
+ * Assertions via t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow.
9
+ * Also checks for t.Run subtests and table-driven patterns.
10
+ */
11
+ export function checkGoTestQuality(content, file, issues, config) {
12
+ const lines = content.split('\n');
13
+ let inTestFunc = false;
14
+ let testStartLine = 0;
15
+ let braceDepth = 0;
16
+ let hasAssertion = false;
17
+ let testContent = '';
18
+ for (let i = 0; i < lines.length; i++) {
19
+ const line = lines[i];
20
+ const trimmed = line.trim();
21
+ // Detect test function: func TestXxx(t *testing.T) {
22
+ const testMatch = trimmed.match(/^func\s+(Test\w+|Benchmark\w+)\s*\(/);
23
+ if (testMatch && !inTestFunc) {
24
+ inTestFunc = true;
25
+ testStartLine = i + 1;
26
+ braceDepth = 0;
27
+ hasAssertion = false;
28
+ testContent = '';
29
+ // Count braces on this line
30
+ for (const ch of line) {
31
+ if (ch === '{')
32
+ braceDepth++;
33
+ if (ch === '}')
34
+ braceDepth--;
35
+ }
36
+ continue;
37
+ }
38
+ if (inTestFunc) {
39
+ testContent += line + '\n';
40
+ for (const ch of line) {
41
+ if (ch === '{')
42
+ braceDepth++;
43
+ if (ch === '}')
44
+ braceDepth--;
45
+ }
46
+ // Go test assertions: t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow
47
+ // Also: assert/require from testify, t.Run for subtests
48
+ if (/\bt\.\s*(?:Fatal|Error|Fatalf|Errorf|Fail|FailNow|Log|Logf|Skip|Skipf|Helper)\s*\(/.test(line) ||
49
+ /\bt\.Run\s*\(/.test(line) ||
50
+ /\bassert\.\w+\s*\(/.test(line) || /\brequire\.\w+\s*\(/.test(line) ||
51
+ /\bif\b.*\bt\./.test(line)) {
52
+ hasAssertion = true;
53
+ }
54
+ // Tautological: if true { t.Fatal... } or assert.True(t, true)
55
+ if (config.check_tautological) {
56
+ if (/assert\.True\s*\(\s*\w+\s*,\s*true\s*\)/.test(line) ||
57
+ /assert\.Equal\s*\(\s*\w+\s*,\s*(\d+|"[^"]*")\s*,\s*\1\s*\)/.test(line)) {
58
+ issues.push({
59
+ file, line: i + 1, pattern: 'tautological-assertion',
60
+ reason: 'Tautological assertion — comparing a constant to itself proves nothing',
61
+ });
62
+ }
63
+ }
64
+ // End of function
65
+ if (braceDepth === 0 && testContent.trim()) {
66
+ const meaningful = testContent.split('\n').filter(l => {
67
+ const t = l.trim();
68
+ return t && t !== '{' && t !== '}' && !t.startsWith('//');
69
+ });
70
+ if (config.check_empty_tests && meaningful.length === 0) {
71
+ issues.push({
72
+ file, line: testStartLine, pattern: 'empty-test',
73
+ reason: 'Empty test function — no test logic',
74
+ });
75
+ }
76
+ else if (config.check_empty_tests && !hasAssertion && meaningful.length > 0) {
77
+ issues.push({
78
+ file, line: testStartLine, pattern: 'no-assertion',
79
+ reason: 'Test has no assertions (t.Error, t.Fatal, assert.*) — executes code but never verifies',
80
+ });
81
+ }
82
+ inTestFunc = false;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Java/Kotlin test quality checks.
89
+ * JUnit 4: @Test + Assert.assertEquals, assertTrue, etc.
90
+ * JUnit 5: @Test + Assertions.assertEquals, assertThrows, etc.
91
+ * Kotlin: @Test + kotlin.test assertEquals, etc.
92
+ */
93
+ export function checkJavaKotlinTestQuality(content, file, ext, issues, config) {
94
+ const lines = content.split('\n');
95
+ const isKotlin = ext === '.kt';
96
+ let inTestMethod = false;
97
+ let testStartLine = 0;
98
+ let braceDepth = 0;
99
+ let hasAssertion = false;
100
+ let mockCount = 0;
101
+ for (let i = 0; i < lines.length; i++) {
102
+ const line = lines[i];
103
+ const trimmed = line.trim();
104
+ // Detect @Test annotation (next non-empty line is the method)
105
+ if (trimmed === '@Test' || /^@Test\s*(\(|$)/.test(trimmed)) {
106
+ // Look for the method signature on this or next lines
107
+ for (let j = i + 1; j < Math.min(i + 3, lines.length); j++) {
108
+ const methodLine = lines[j].trim();
109
+ const methodMatch = isKotlin
110
+ ? methodLine.match(/^(?:fun|suspend\s+fun)\s+(\w+)\s*\(/)
111
+ : methodLine.match(/^(?:public\s+|private\s+|protected\s+)?(?:static\s+)?void\s+(\w+)\s*\(/);
112
+ if (methodMatch) {
113
+ inTestMethod = true;
114
+ testStartLine = j + 1;
115
+ braceDepth = 0;
116
+ hasAssertion = false;
117
+ mockCount = 0;
118
+ // Count braces
119
+ for (const ch of lines[j]) {
120
+ if (ch === '{')
121
+ braceDepth++;
122
+ if (ch === '}')
123
+ braceDepth--;
124
+ }
125
+ i = j;
126
+ break;
127
+ }
128
+ }
129
+ continue;
130
+ }
131
+ if (inTestMethod) {
132
+ for (const ch of line) {
133
+ if (ch === '{')
134
+ braceDepth++;
135
+ if (ch === '}')
136
+ braceDepth--;
137
+ }
138
+ // JUnit 4/5 assertions
139
+ if (/\b(?:assert(?:Equals|True|False|NotNull|Null|That|Throws|DoesNotThrow|Same|NotSame|ArrayEquals)|assertEquals|assertTrue|assertFalse|assertNotNull|assertNull|assertThrows)\s*\(/.test(line)) {
140
+ hasAssertion = true;
141
+ }
142
+ // Kotlin test assertions
143
+ if (isKotlin && /\b(?:assertEquals|assertTrue|assertFalse|assertNotNull|assertNull|assertFailsWith|assertIs|assertContains|expect)\s*[({]/.test(line)) {
144
+ hasAssertion = true;
145
+ }
146
+ // Hamcrest / AssertJ
147
+ if (/\bassertThat\s*\(/.test(line) || /\.should\w*\(/.test(line)) {
148
+ hasAssertion = true;
149
+ }
150
+ // Verify (Mockito)
151
+ if (/\bverify\s*\(/.test(line)) {
152
+ hasAssertion = true;
153
+ }
154
+ // Mock counting
155
+ if (/\b(?:mock|spy|when|doReturn|doThrow|doNothing|Mockito\.\w+)\s*\(/.test(line) ||
156
+ /@Mock\b/.test(line) || /@InjectMocks\b/.test(line)) {
157
+ mockCount++;
158
+ }
159
+ // Tautological
160
+ if (config.check_tautological) {
161
+ if (/assertEquals\s*\(\s*true\s*,\s*true\s*\)/.test(line) ||
162
+ /assertTrue\s*\(\s*true\s*\)/.test(line) ||
163
+ /assertEquals\s*\(\s*(\d+)\s*,\s*\1\s*\)/.test(line)) {
164
+ issues.push({
165
+ file, line: i + 1, pattern: 'tautological-assertion',
166
+ reason: 'Tautological assertion — comparing a constant to itself proves nothing',
167
+ });
168
+ }
169
+ }
170
+ // End of method
171
+ if (braceDepth === 0) {
172
+ if (config.check_empty_tests && !hasAssertion) {
173
+ issues.push({
174
+ file, line: testStartLine, pattern: 'no-assertion',
175
+ reason: 'Test has no assertions — executes code but never verifies results',
176
+ });
177
+ }
178
+ if (config.check_mock_heavy && mockCount > config.max_mocks_per_test) {
179
+ issues.push({
180
+ file, line: testStartLine, pattern: 'mock-heavy',
181
+ reason: `Test uses ${mockCount} mocks (max: ${config.max_mocks_per_test}) — may be testing mocks, not real behavior`,
182
+ });
183
+ }
184
+ inTestMethod = false;
185
+ }
186
+ }
187
+ }
188
+ }
@@ -50,18 +50,4 @@ export declare class TestQualityGate extends Gate {
50
50
  private analyzeJSTestBlock;
51
51
  private checkPythonTestQuality;
52
52
  private analyzePythonTestBlock;
53
- /**
54
- * Go test quality checks.
55
- * Go tests use func TestXxx(t *testing.T) pattern.
56
- * Assertions via t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow.
57
- * Also checks for t.Run subtests and table-driven patterns.
58
- */
59
- private checkGoTestQuality;
60
- /**
61
- * Java/Kotlin test quality checks.
62
- * JUnit 4: @Test + Assert.assertEquals, assertTrue, etc.
63
- * JUnit 5: @Test + Assertions.assertEquals, assertThrows, etc.
64
- * Kotlin: @Test + kotlin.test assertEquals, etc.
65
- */
66
- private checkJavaKotlinTestQuality;
67
53
  }
@@ -26,6 +26,7 @@
26
26
  import { Gate } from './base.js';
27
27
  import { FileScanner } from '../utils/scanner.js';
28
28
  import { Logger } from '../utils/logger.js';
29
+ import { checkGoTestQuality, checkJavaKotlinTestQuality } from './test-quality-lang.js';
29
30
  import fs from 'fs-extra';
30
31
  import path from 'path';
31
32
  export class TestQualityGate extends Gate {
@@ -76,10 +77,20 @@ export class TestQualityGate extends Gate {
76
77
  this.checkPythonTestQuality(content, file, issues);
77
78
  }
78
79
  else if (ext === '.go') {
79
- this.checkGoTestQuality(content, file, issues);
80
+ checkGoTestQuality(content, file, issues, {
81
+ check_empty_tests: this.config.check_empty_tests,
82
+ check_tautological: this.config.check_tautological,
83
+ check_mock_heavy: this.config.check_mock_heavy,
84
+ max_mocks_per_test: this.config.max_mocks_per_test,
85
+ });
80
86
  }
81
87
  else if (ext === '.java' || ext === '.kt') {
82
- this.checkJavaKotlinTestQuality(content, file, ext, issues);
88
+ checkJavaKotlinTestQuality(content, file, ext, issues, {
89
+ check_empty_tests: this.config.check_empty_tests,
90
+ check_tautological: this.config.check_tautological,
91
+ check_mock_heavy: this.config.check_mock_heavy,
92
+ max_mocks_per_test: this.config.max_mocks_per_test,
93
+ });
83
94
  }
84
95
  }
85
96
  catch { /* skip */ }
@@ -325,188 +336,4 @@ export class TestQualityGate extends Gate {
325
336
  });
326
337
  }
327
338
  }
328
- /**
329
- * Go test quality checks.
330
- * Go tests use func TestXxx(t *testing.T) pattern.
331
- * Assertions via t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow.
332
- * Also checks for t.Run subtests and table-driven patterns.
333
- */
334
- checkGoTestQuality(content, file, issues) {
335
- const lines = content.split('\n');
336
- let inTestFunc = false;
337
- let testStartLine = 0;
338
- let braceDepth = 0;
339
- let hasAssertion = false;
340
- let testContent = '';
341
- for (let i = 0; i < lines.length; i++) {
342
- const line = lines[i];
343
- const trimmed = line.trim();
344
- // Detect test function: func TestXxx(t *testing.T) {
345
- const testMatch = trimmed.match(/^func\s+(Test\w+|Benchmark\w+)\s*\(/);
346
- if (testMatch && !inTestFunc) {
347
- inTestFunc = true;
348
- testStartLine = i + 1;
349
- braceDepth = 0;
350
- hasAssertion = false;
351
- testContent = '';
352
- // Count braces on this line
353
- for (const ch of line) {
354
- if (ch === '{')
355
- braceDepth++;
356
- if (ch === '}')
357
- braceDepth--;
358
- }
359
- continue;
360
- }
361
- if (inTestFunc) {
362
- testContent += line + '\n';
363
- for (const ch of line) {
364
- if (ch === '{')
365
- braceDepth++;
366
- if (ch === '}')
367
- braceDepth--;
368
- }
369
- // Go test assertions: t.Fatal, t.Error, t.Fatalf, t.Errorf, t.Fail, t.FailNow
370
- // Also: assert/require from testify, t.Run for subtests
371
- if (/\bt\.\s*(?:Fatal|Error|Fatalf|Errorf|Fail|FailNow|Log|Logf|Skip|Skipf|Helper)\s*\(/.test(line) ||
372
- /\bt\.Run\s*\(/.test(line) ||
373
- /\bassert\.\w+\s*\(/.test(line) || /\brequire\.\w+\s*\(/.test(line) ||
374
- /\bif\b.*\bt\./.test(line)) {
375
- hasAssertion = true;
376
- }
377
- // Tautological: if true { t.Fatal... } or assert.True(t, true)
378
- if (this.config.check_tautological) {
379
- if (/assert\.True\s*\(\s*\w+\s*,\s*true\s*\)/.test(line) ||
380
- /assert\.Equal\s*\(\s*\w+\s*,\s*(\d+|"[^"]*")\s*,\s*\1\s*\)/.test(line)) {
381
- issues.push({
382
- file, line: i + 1, pattern: 'tautological-assertion',
383
- reason: 'Tautological assertion — comparing a constant to itself proves nothing',
384
- });
385
- }
386
- }
387
- // End of function
388
- if (braceDepth === 0 && testContent.trim()) {
389
- const meaningful = testContent.split('\n').filter(l => {
390
- const t = l.trim();
391
- return t && t !== '{' && t !== '}' && !t.startsWith('//');
392
- });
393
- if (this.config.check_empty_tests && meaningful.length === 0) {
394
- issues.push({
395
- file, line: testStartLine, pattern: 'empty-test',
396
- reason: 'Empty test function — no test logic',
397
- });
398
- }
399
- else if (this.config.check_empty_tests && !hasAssertion && meaningful.length > 0) {
400
- issues.push({
401
- file, line: testStartLine, pattern: 'no-assertion',
402
- reason: 'Test has no assertions (t.Error, t.Fatal, assert.*) — executes code but never verifies',
403
- });
404
- }
405
- inTestFunc = false;
406
- }
407
- }
408
- }
409
- }
410
- /**
411
- * Java/Kotlin test quality checks.
412
- * JUnit 4: @Test + Assert.assertEquals, assertTrue, etc.
413
- * JUnit 5: @Test + Assertions.assertEquals, assertThrows, etc.
414
- * Kotlin: @Test + kotlin.test assertEquals, etc.
415
- */
416
- checkJavaKotlinTestQuality(content, file, ext, issues) {
417
- const lines = content.split('\n');
418
- const isKotlin = ext === '.kt';
419
- let inTestMethod = false;
420
- let testStartLine = 0;
421
- let braceDepth = 0;
422
- let hasAssertion = false;
423
- let mockCount = 0;
424
- for (let i = 0; i < lines.length; i++) {
425
- const line = lines[i];
426
- const trimmed = line.trim();
427
- // Detect @Test annotation (next non-empty line is the method)
428
- if (trimmed === '@Test' || /^@Test\s*(\(|$)/.test(trimmed)) {
429
- // Look for the method signature on this or next lines
430
- for (let j = i + 1; j < Math.min(i + 3, lines.length); j++) {
431
- const methodLine = lines[j].trim();
432
- const methodMatch = isKotlin
433
- ? methodLine.match(/^(?:fun|suspend\s+fun)\s+(\w+)\s*\(/)
434
- : methodLine.match(/^(?:public\s+|private\s+|protected\s+)?(?:static\s+)?void\s+(\w+)\s*\(/);
435
- if (methodMatch) {
436
- inTestMethod = true;
437
- testStartLine = j + 1;
438
- braceDepth = 0;
439
- hasAssertion = false;
440
- mockCount = 0;
441
- // Count braces
442
- for (const ch of lines[j]) {
443
- if (ch === '{')
444
- braceDepth++;
445
- if (ch === '}')
446
- braceDepth--;
447
- }
448
- i = j;
449
- break;
450
- }
451
- }
452
- continue;
453
- }
454
- if (inTestMethod) {
455
- for (const ch of line) {
456
- if (ch === '{')
457
- braceDepth++;
458
- if (ch === '}')
459
- braceDepth--;
460
- }
461
- // JUnit 4/5 assertions
462
- if (/\b(?:assert(?:Equals|True|False|NotNull|Null|That|Throws|DoesNotThrow|Same|NotSame|ArrayEquals)|assertEquals|assertTrue|assertFalse|assertNotNull|assertNull|assertThrows)\s*\(/.test(line)) {
463
- hasAssertion = true;
464
- }
465
- // Kotlin test assertions
466
- if (isKotlin && /\b(?:assertEquals|assertTrue|assertFalse|assertNotNull|assertNull|assertFailsWith|assertIs|assertContains|expect)\s*[({]/.test(line)) {
467
- hasAssertion = true;
468
- }
469
- // Hamcrest / AssertJ
470
- if (/\bassertThat\s*\(/.test(line) || /\.should\w*\(/.test(line)) {
471
- hasAssertion = true;
472
- }
473
- // Verify (Mockito)
474
- if (/\bverify\s*\(/.test(line)) {
475
- hasAssertion = true;
476
- }
477
- // Mock counting
478
- if (/\b(?:mock|spy|when|doReturn|doThrow|doNothing|Mockito\.\w+)\s*\(/.test(line) ||
479
- /@Mock\b/.test(line) || /@InjectMocks\b/.test(line)) {
480
- mockCount++;
481
- }
482
- // Tautological
483
- if (this.config.check_tautological) {
484
- if (/assertEquals\s*\(\s*true\s*,\s*true\s*\)/.test(line) ||
485
- /assertTrue\s*\(\s*true\s*\)/.test(line) ||
486
- /assertEquals\s*\(\s*(\d+)\s*,\s*\1\s*\)/.test(line)) {
487
- issues.push({
488
- file, line: i + 1, pattern: 'tautological-assertion',
489
- reason: 'Tautological assertion — comparing a constant to itself proves nothing',
490
- });
491
- }
492
- }
493
- // End of method
494
- if (braceDepth === 0) {
495
- if (this.config.check_empty_tests && !hasAssertion) {
496
- issues.push({
497
- file, line: testStartLine, pattern: 'no-assertion',
498
- reason: 'Test has no assertions — executes code but never verifies results',
499
- });
500
- }
501
- if (this.config.check_mock_heavy && mockCount > this.config.max_mocks_per_test) {
502
- issues.push({
503
- file, line: testStartLine, pattern: 'mock-heavy',
504
- reason: `Test uses ${mockCount} mocks (max: ${this.config.max_mocks_per_test}) — may be testing mocks, not real behavior`,
505
- });
506
- }
507
- inTestMethod = false;
508
- }
509
- }
510
- }
511
- }
512
339
  }
package/dist/index.d.ts CHANGED
@@ -9,3 +9,13 @@ export { RetryLoopBreakerGate } from './gates/retry-loop-breaker.js';
9
9
  export * from './utils/logger.js';
10
10
  export * from './services/score-history.js';
11
11
  export * from './hooks/index.js';
12
+ export { loadSettings, saveSettings, getSettingsPath, resolveDeepOptions, getProviderKey, getAgentConfig, getCliPreferences, updateProviderKey, removeProviderKey } from './settings.js';
13
+ export type { RigourSettings, ResolvedDeepOptions, CLIDeepOptions } from './settings.js';
14
+ export { DeepAnalysisGate } from './gates/deep-analysis.js';
15
+ export { createProvider } from './inference/index.js';
16
+ export type { InferenceProvider, DeepFinding, DeepAnalysisResult, ModelTier } from './inference/types.js';
17
+ export { MODELS } from './inference/types.js';
18
+ export { isModelCached, getModelsDir, getModelInfo } from './inference/model-manager.js';
19
+ export { extractFacts, factsToPromptString } from './deep/fact-extractor.js';
20
+ export { openDatabase, isSQLiteAvailable, insertScan, insertFindings, getRecentScans, getScoreTrendFromDB, getTopIssues, reinforcePattern, getStrongPatterns } from './storage/index.js';
21
+ export type { RigourDB } from './storage/index.js';
package/dist/index.js CHANGED
@@ -9,7 +9,17 @@ export { RetryLoopBreakerGate } from './gates/retry-loop-breaker.js';
9
9
  export * from './utils/logger.js';
10
10
  export * from './services/score-history.js';
11
11
  export * from './hooks/index.js';
12
+ // Settings Module (Global user config at ~/.rigour/settings.json)
13
+ export { loadSettings, saveSettings, getSettingsPath, resolveDeepOptions, getProviderKey, getAgentConfig, getCliPreferences, updateProviderKey, removeProviderKey } from './settings.js';
14
+ // Deep Analysis Pipeline (v4.0+)
15
+ export { DeepAnalysisGate } from './gates/deep-analysis.js';
16
+ export { createProvider } from './inference/index.js';
17
+ export { MODELS } from './inference/types.js';
18
+ export { isModelCached, getModelsDir, getModelInfo } from './inference/model-manager.js';
19
+ export { extractFacts, factsToPromptString } from './deep/fact-extractor.js';
20
+ // Storage (SQLite Brain)
21
+ export { openDatabase, isSQLiteAvailable, insertScan, insertFindings, getRecentScans, getScoreTrendFromDB, getTopIssues, reinforcePattern, getStrongPatterns } from './storage/index.js';
12
22
  // Pattern Index is intentionally NOT exported here to prevent
13
- // native dependency issues (sharp/transformers) from leaking into
14
- // non-AI parts of the system.
23
+ // native dependency issues (sharp/transformers) from leaking into
24
+ // non-AI parts of the system.
15
25
  // Import from @rigour-labs/core/pattern-index instead.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cloud API Provider — runs inference via ANY cloud LLM API.
3
+ *
4
+ * The moat is local-first. But if a user brings their own key,
5
+ * we don't block them. No limitations. Support EVERY provider:
6
+ *
7
+ * - 'claude'/'anthropic' → Anthropic SDK (native)
8
+ * - Everything else → OpenAI-compatible SDK (works with OpenAI, Gemini, Groq,
9
+ * Mistral, Together, Fireworks, Perplexity, DeepSeek, self-hosted vLLM,
10
+ * Ollama, LM Studio, any OpenAI-compatible endpoint)
11
+ *
12
+ * User provides: api_key + provider name + optional base_url + optional model_name
13
+ * We figure out the rest. Their key, their choice.
14
+ */
15
+ import type { InferenceProvider, InferenceOptions } from './types.js';
16
+ export declare class CloudProvider implements InferenceProvider {
17
+ readonly name: string;
18
+ private client;
19
+ private providerName;
20
+ private apiKey;
21
+ private baseUrl?;
22
+ private modelName;
23
+ private isClaude;
24
+ constructor(providerName: string, apiKey: string, options?: {
25
+ baseUrl?: string;
26
+ modelName?: string;
27
+ });
28
+ isAvailable(): Promise<boolean>;
29
+ setup(onProgress?: (message: string) => void): Promise<void>;
30
+ analyze(prompt: string, options?: InferenceOptions): Promise<string>;
31
+ private analyzeClaude;
32
+ private analyzeOpenAICompat;
33
+ dispose(): void;
34
+ }
@@ -0,0 +1,126 @@
1
+ /** Default models per provider (user can override via model_name) */
2
+ const DEFAULT_MODELS = {
3
+ claude: 'claude-opus-4-6',
4
+ anthropic: 'claude-sonnet-4-6',
5
+ openai: 'gpt-4o-mini',
6
+ gemini: 'gemini-3-flash',
7
+ groq: 'llama-3.1-70b-versatile',
8
+ mistral: 'mistral-large-latest',
9
+ together: 'meta-llama/Llama-3.1-70B-Instruct-Turbo',
10
+ fireworks: 'accounts/fireworks/models/llama-v3p1-70b-instruct',
11
+ deepseek: 'deepseek-coder',
12
+ perplexity: 'llama-3.1-sonar-large-128k-online',
13
+ ollama: 'qwen2.5-coder:7b',
14
+ lmstudio: 'qwen2.5-coder-7b-instruct',
15
+ };
16
+ /** Default base URLs per provider */
17
+ const DEFAULT_BASE_URLS = {
18
+ openai: 'https://api.openai.com/v1',
19
+ gemini: 'https://generativelanguage.googleapis.com/v1beta/openai',
20
+ groq: 'https://api.groq.com/openai/v1',
21
+ mistral: 'https://api.mistral.ai/v1',
22
+ together: 'https://api.together.xyz/v1',
23
+ fireworks: 'https://api.fireworks.ai/inference/v1',
24
+ deepseek: 'https://api.deepseek.com/v1',
25
+ perplexity: 'https://api.perplexity.ai',
26
+ ollama: 'http://localhost:11434/v1',
27
+ lmstudio: 'http://localhost:1234/v1',
28
+ };
29
+ export class CloudProvider {
30
+ name;
31
+ client = null;
32
+ providerName;
33
+ apiKey;
34
+ baseUrl;
35
+ modelName;
36
+ isClaude;
37
+ constructor(providerName, apiKey, options) {
38
+ if (!apiKey || apiKey.trim().length === 0) {
39
+ throw new Error(`API key cannot be empty for provider "${providerName}"`);
40
+ }
41
+ this.providerName = providerName.toLowerCase();
42
+ this.apiKey = apiKey.trim();
43
+ this.baseUrl = options?.baseUrl;
44
+ this.modelName = options?.modelName || DEFAULT_MODELS[this.providerName] || 'gpt-4o-mini';
45
+ this.isClaude = this.providerName === 'claude' || this.providerName === 'anthropic';
46
+ this.name = `cloud-${this.providerName}`;
47
+ }
48
+ async isAvailable() {
49
+ return !!this.apiKey;
50
+ }
51
+ async setup(onProgress) {
52
+ if (this.isClaude) {
53
+ try {
54
+ const { default: Anthropic } = await import('@anthropic-ai/sdk');
55
+ this.client = new Anthropic({ apiKey: this.apiKey });
56
+ onProgress?.(`✓ ${this.providerName} API connected (model: ${this.modelName})`);
57
+ }
58
+ catch {
59
+ throw new Error('Claude API SDK not installed. Run: npm install @anthropic-ai/sdk');
60
+ }
61
+ }
62
+ else {
63
+ // OpenAI-compatible SDK — works with literally everything.
64
+ // OpenAI, Groq, Mistral, Together, Fireworks, DeepSeek, Perplexity,
65
+ // Gemini, Ollama, LM Studio, vLLM, any OpenAI-compatible endpoint.
66
+ // No limitations. User's key, user's choice.
67
+ try {
68
+ const { default: OpenAI } = await import('openai');
69
+ const baseURL = this.baseUrl || DEFAULT_BASE_URLS[this.providerName] || undefined;
70
+ this.client = new OpenAI({
71
+ apiKey: this.apiKey,
72
+ ...(baseURL ? { baseURL } : {}),
73
+ });
74
+ onProgress?.(`✓ ${this.providerName} API connected (model: ${this.modelName})`);
75
+ }
76
+ catch {
77
+ throw new Error('OpenAI SDK not installed (used for all OpenAI-compatible APIs). Run: npm install openai');
78
+ }
79
+ }
80
+ }
81
+ async analyze(prompt, options) {
82
+ if (!this.client) {
83
+ throw new Error('Provider not set up. Call setup() first.');
84
+ }
85
+ if (this.isClaude) {
86
+ return this.analyzeClaude(prompt, options);
87
+ }
88
+ else {
89
+ return this.analyzeOpenAICompat(prompt, options);
90
+ }
91
+ }
92
+ async analyzeClaude(prompt, options) {
93
+ const response = await this.client.messages.create({
94
+ model: this.modelName,
95
+ max_tokens: options?.maxTokens || 2048,
96
+ temperature: options?.temperature || 0.1,
97
+ messages: [
98
+ { role: 'user', content: prompt }
99
+ ],
100
+ });
101
+ const textBlock = response.content.find((b) => b.type === 'text');
102
+ if (!textBlock?.text) {
103
+ throw new Error(`Empty response from ${this.providerName} API (model: ${this.modelName}). Response had ${response.content.length} blocks but no text.`);
104
+ }
105
+ return textBlock.text;
106
+ }
107
+ async analyzeOpenAICompat(prompt, options) {
108
+ const response = await this.client.chat.completions.create({
109
+ model: this.modelName,
110
+ max_tokens: options?.maxTokens || 2048,
111
+ temperature: options?.temperature || 0.1,
112
+ messages: [
113
+ { role: 'user', content: prompt }
114
+ ],
115
+ ...(options?.jsonMode ? { response_format: { type: 'json_object' } } : {}),
116
+ });
117
+ const content = response.choices[0]?.message?.content;
118
+ if (!content) {
119
+ throw new Error(`Empty response from ${this.providerName} API (model: ${this.modelName}). No content in choices.`);
120
+ }
121
+ return content;
122
+ }
123
+ dispose() {
124
+ this.client = null;
125
+ }
126
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Inference provider factory and exports.
3
+ */
4
+ export type { InferenceProvider, InferenceOptions, DeepFinding, DeepAnalysisResult, ModelTier, ModelInfo } from './types.js';
5
+ export { MODELS } from './types.js';
6
+ export { SidecarProvider } from './sidecar-provider.js';
7
+ export { CloudProvider } from './cloud-provider.js';
8
+ export { ensureModel, isModelCached, getModelPath, getModelInfo, downloadModel, getModelsDir } from './model-manager.js';
9
+ import type { InferenceProvider } from './types.js';
10
+ import type { DeepOptions } from '../types/index.js';
11
+ /**
12
+ * Create the appropriate inference provider based on options.
13
+ *
14
+ * - No API key → SidecarProvider (local llama.cpp binary)
15
+ * - API key + any provider → CloudProvider (no restrictions, user's key, user's choice)
16
+ */
17
+ export declare function createProvider(options: DeepOptions): InferenceProvider;