@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
@@ -1,256 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { GateRunner } from '../src/gates/runner.js';
3
- import fs from 'fs-extra';
4
- import path from 'path';
5
- import { fileURLToPath } from 'url';
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const TEST_CWD = path.join(__dirname, '../temp-test-context');
9
-
10
- describe('Context Awareness Engine', () => {
11
- beforeAll(async () => {
12
- await fs.ensureDir(TEST_CWD);
13
- });
14
-
15
- afterAll(async () => {
16
- await fs.remove(TEST_CWD);
17
- });
18
-
19
- it('should detect context drift for redundant env suffixes (Golden Example)', async () => {
20
- // Setup: Define standard GCP_PROJECT_ID
21
- await fs.writeFile(path.join(TEST_CWD, '.env.example'), 'GCP_PROJECT_ID=my-project\n');
22
-
23
- // Setup: Use drifted GCP_PROJECT_ID_PRODUCTION
24
- await fs.writeFile(path.join(TEST_CWD, 'feature.js'), `
25
- const id = process.env.GCP_PROJECT_ID_PRODUCTION;
26
- console.log(id);
27
- `);
28
-
29
- const config = {
30
- version: 1,
31
- commands: {},
32
- gates: {
33
- context: {
34
- enabled: true,
35
- sensitivity: 0.8,
36
- mining_depth: 10,
37
- ignored_patterns: [],
38
- cross_file_patterns: true,
39
- naming_consistency: true,
40
- import_relationships: true,
41
- max_cross_file_depth: 50,
42
- },
43
- },
44
- output: { report_path: 'rigour-report.json' }
45
- };
46
-
47
- const runner = new GateRunner(config as any);
48
- const report = await runner.run(TEST_CWD);
49
-
50
- const driftFailures = report.failures.filter(f => f.id === 'context-drift');
51
- expect(driftFailures.length).toBeGreaterThan(0);
52
- expect(driftFailures[0].details).toContain('GCP_PROJECT_ID_PRODUCTION');
53
- expect(driftFailures[0].hint).toContain('GCP_PROJECT_ID');
54
- });
55
-
56
- it('should not flag valid environment variables', async () => {
57
- await fs.writeFile(path.join(TEST_CWD, 'valid.js'), `
58
- const id = process.env.GCP_PROJECT_ID;
59
- `);
60
-
61
- const config = {
62
- version: 1,
63
- commands: {},
64
- gates: {
65
- context: {
66
- enabled: true,
67
- sensitivity: 0.8,
68
- mining_depth: 100,
69
- ignored_patterns: [],
70
- cross_file_patterns: true,
71
- naming_consistency: true,
72
- import_relationships: true,
73
- max_cross_file_depth: 50,
74
- },
75
- },
76
- output: { report_path: 'rigour-report.json' }
77
- };
78
-
79
- const runner = new GateRunner(config as any);
80
- const report = await runner.run(TEST_CWD);
81
-
82
- const driftFailures = report.failures.filter(f => f.id === 'context-drift');
83
- // Filter out failures from other files if they still exist in TEST_CWD
84
- const specificFailures = driftFailures.filter(f => f.files?.includes('valid.js'));
85
- expect(specificFailures.length).toBe(0);
86
- });
87
- it('should classify arrow function exports as camelCase, not unknown', async () => {
88
- // Create files with arrow function patterns that previously returned 'unknown'
89
- await fs.writeFile(path.join(TEST_CWD, 'api.ts'), `
90
- export const fetchData = async () => { return []; };
91
- export const getUserProfile = async (id: string) => { return {}; };
92
- export const use = () => {};
93
- export const get = async () => {};
94
- const handleClick = (e: Event) => {};
95
- let processItem = async (item: any) => {};
96
- `);
97
-
98
- // Create a second file with consistent arrow function naming
99
- await fs.writeFile(path.join(TEST_CWD, 'service.ts'), `
100
- export const createUser = async (data: any) => {};
101
- export const deleteUser = async (id: string) => {};
102
- export const updateUser = async (id: string, data: any) => {};
103
- `);
104
-
105
- const config = {
106
- version: 1,
107
- commands: {},
108
- gates: {
109
- context: {
110
- enabled: true,
111
- sensitivity: 0.8,
112
- mining_depth: 10,
113
- ignored_patterns: [],
114
- cross_file_patterns: true,
115
- naming_consistency: true,
116
- import_relationships: true,
117
- max_cross_file_depth: 50,
118
- },
119
- },
120
- output: { report_path: 'rigour-report.json' }
121
- };
122
-
123
- const runner = new GateRunner(config as any);
124
- const report = await runner.run(TEST_CWD);
125
-
126
- // Should NOT have any "unknown" naming convention failures
127
- const namingFailures = report.failures.filter(f =>
128
- f.id === 'context-drift' && f.details?.includes('unknown')
129
- );
130
- expect(namingFailures.length).toBe(0);
131
- });
132
-
133
- it('should not classify plain variable declarations as function patterns', async () => {
134
- // Create file with non-function const declarations
135
- await fs.writeFile(path.join(TEST_CWD, 'constants.ts'), `
136
- export const API_URL = 'https://api.example.com';
137
- export const MAX_RETRIES = 3;
138
- const config = { timeout: 5000 };
139
- let count = 0;
140
- `);
141
-
142
- // Create file with actual functions for a dominant pattern
143
- await fs.writeFile(path.join(TEST_CWD, 'utils.ts'), `
144
- function getData() { return []; }
145
- function setData(d: any) { return d; }
146
- function processRequest(req: any) { return req; }
147
- `);
148
-
149
- const config = {
150
- version: 1,
151
- commands: {},
152
- gates: {
153
- context: {
154
- enabled: true,
155
- sensitivity: 0.8,
156
- mining_depth: 10,
157
- ignored_patterns: [],
158
- cross_file_patterns: true,
159
- naming_consistency: true,
160
- import_relationships: true,
161
- max_cross_file_depth: 50,
162
- },
163
- },
164
- output: { report_path: 'rigour-report.json' }
165
- };
166
-
167
- const runner = new GateRunner(config as any);
168
- const report = await runner.run(TEST_CWD);
169
-
170
- // SCREAMING_SNAKE constants should NOT create naming drift failures
171
- // because they should not be in the 'function' pattern bucket at all
172
- const namingFailures = report.failures.filter(f =>
173
- f.id === 'context-drift' && f.details?.includes('SCREAMING_SNAKE')
174
- );
175
- expect(namingFailures.length).toBe(0);
176
- });
177
- });
178
-
179
- /**
180
- * Direct unit tests for detectCasing logic
181
- */
182
- describe('detectCasing classification', () => {
183
- // We test the regex rules directly since detectCasing is private
184
- function detectCasing(name: string): string {
185
- if (/^[A-Z][a-z]/.test(name) && /[a-z][A-Z]/.test(name)) return 'PascalCase';
186
- if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name)) return 'camelCase';
187
- if (/^[a-z][a-zA-Z0-9]*$/.test(name)) return 'camelCase'; // single-word lowercase
188
- if (/^[a-z]+(_[a-z]+)+$/.test(name)) return 'snake_case';
189
- if (/^[A-Z]+(_[A-Z]+)*$/.test(name)) return 'SCREAMING_SNAKE';
190
- if (/^[A-Z][a-zA-Z]*$/.test(name)) return 'PascalCase';
191
- return 'unknown';
192
- }
193
-
194
- // Multi-word camelCase
195
- it('classifies multi-word camelCase', () => {
196
- expect(detectCasing('fetchData')).toBe('camelCase');
197
- expect(detectCasing('getUserProfile')).toBe('camelCase');
198
- expect(detectCasing('handleClick')).toBe('camelCase');
199
- expect(detectCasing('processItem')).toBe('camelCase');
200
- expect(detectCasing('createNewUser')).toBe('camelCase');
201
- });
202
-
203
- // Single-word lowercase (the bug fix)
204
- it('classifies single-word lowercase as camelCase', () => {
205
- expect(detectCasing('fetch')).toBe('camelCase');
206
- expect(detectCasing('use')).toBe('camelCase');
207
- expect(detectCasing('get')).toBe('camelCase');
208
- expect(detectCasing('set')).toBe('camelCase');
209
- expect(detectCasing('run')).toBe('camelCase');
210
- expect(detectCasing('a')).toBe('camelCase');
211
- expect(detectCasing('x')).toBe('camelCase');
212
- expect(detectCasing('id')).toBe('camelCase');
213
- });
214
-
215
- // Single-word lowercase with digits
216
- it('classifies lowercase with digits as camelCase', () => {
217
- expect(detectCasing('handler2')).toBe('camelCase');
218
- expect(detectCasing('config3')).toBe('camelCase');
219
- expect(detectCasing('v2')).toBe('camelCase');
220
- });
221
-
222
- // PascalCase
223
- it('classifies PascalCase', () => {
224
- expect(detectCasing('MyComponent')).toBe('PascalCase');
225
- expect(detectCasing('UserService')).toBe('PascalCase');
226
- expect(detectCasing('App')).toBe('PascalCase');
227
- expect(detectCasing('A')).toBe('SCREAMING_SNAKE'); // single uppercase letter
228
- });
229
-
230
- // snake_case
231
- it('classifies snake_case', () => {
232
- expect(detectCasing('my_func')).toBe('snake_case');
233
- expect(detectCasing('get_data')).toBe('snake_case');
234
- expect(detectCasing('process_all_items')).toBe('snake_case');
235
- });
236
-
237
- // SCREAMING_SNAKE
238
- it('classifies SCREAMING_SNAKE_CASE', () => {
239
- expect(detectCasing('API_URL')).toBe('SCREAMING_SNAKE');
240
- expect(detectCasing('MAX_RETRIES')).toBe('SCREAMING_SNAKE');
241
- expect(detectCasing('A')).toBe('SCREAMING_SNAKE');
242
- expect(detectCasing('DB')).toBe('SCREAMING_SNAKE');
243
- });
244
-
245
- // Edge cases that should NOT be unknown
246
- it('does not return unknown for valid identifiers', () => {
247
- const validIdentifiers = [
248
- 'fetch', 'getData', 'MyClass', 'my_func', 'API_KEY',
249
- 'use', 'run', 'a', 'x', 'id', 'App', 'handler2',
250
- 'processItem', 'UserProfile', 'get_all_data', 'MAX_SIZE',
251
- ];
252
- for (const name of validIdentifiers) {
253
- expect(detectCasing(name)).not.toBe('unknown');
254
- }
255
- });
256
- });
@@ -1,88 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { DiscoveryService } from './discovery.js';
3
- import fs from 'fs-extra';
4
- import path from 'path';
5
-
6
- vi.mock('fs-extra');
7
-
8
- describe('DiscoveryService', () => {
9
- beforeEach(() => {
10
- vi.resetAllMocks();
11
- });
12
-
13
- it('should discover project marker in root directory', async () => {
14
- const service = new DiscoveryService();
15
- vi.mocked(fs.pathExists).mockImplementation(async (p: string) => p.includes('package.json'));
16
- vi.mocked(fs.readdir).mockResolvedValue(['package.json'] as any);
17
- vi.mocked(fs.readFile).mockResolvedValue('{}' as any);
18
-
19
- const result = await service.discover('/test');
20
- // If package.json doesn't match a specific role marker, it stays Universal.
21
- // Let's mock a specific one like 'express'
22
- vi.mocked(fs.pathExists).mockImplementation(async (p: string) => p.includes('express'));
23
- const result2 = await service.discover('/test');
24
- expect(result2.matches.preset?.name).toBe('api');
25
- });
26
-
27
- it('should discover project marker in src/ directory (Deep Detection)', async () => {
28
- const service = new DiscoveryService();
29
- vi.mocked(fs.pathExists).mockImplementation((async (p: string) => {
30
- if (p.endsWith('src')) return true;
31
- if (p.includes('src/index.ts')) return true;
32
- return false;
33
- }) as any);
34
- vi.mocked(fs.readdir).mockImplementation((async (p: string) => {
35
- if (p.toString().endsWith('/test')) return ['src'] as any;
36
- if (p.toString().endsWith('src')) return ['index.ts'] as any;
37
- return [] as any;
38
- }) as any);
39
- vi.mocked(fs.readFile).mockResolvedValue('export const x = 1;' as any);
40
-
41
- const result = await service.discover('/test');
42
- // Since UNIVERSAL_CONFIG has a default, we check if it found something extra or matches expectation
43
- // Default is universal, but detecting .ts should tilt it towards node or similar if configured
44
- // In our current templates, package.json is the node marker.
45
- // Let's check for paradigm detection which uses content
46
- expect(result.config).toBeDefined();
47
- });
48
-
49
- it('should identify OOP paradigm from content in subfolder', async () => {
50
- const service = new DiscoveryService();
51
- vi.mocked(fs.pathExists).mockImplementation((async (p: string) => p.endsWith('src') || p.endsWith('src/Service.ts')) as any);
52
- vi.mocked(fs.readdir).mockImplementation((async (p: string) => {
53
- if (p.toString().endsWith('src')) return ['Service.ts'] as any;
54
- return ['src'] as any;
55
- }) as any);
56
- vi.mocked(fs.readFile).mockResolvedValue('class MyService {}' as any);
57
-
58
- const result = await service.discover('/test');
59
- expect(result.matches.paradigm?.name).toBe('oop');
60
- });
61
-
62
- it('should include project-type-aware ignore patterns for API preset', async () => {
63
- const service = new DiscoveryService();
64
- // Mock finding requirements.txt (Python API marker)
65
- vi.mocked(fs.pathExists).mockImplementation(async (p: string) => p.includes('requirements.txt'));
66
- vi.mocked(fs.readdir).mockResolvedValue(['requirements.txt'] as any);
67
- vi.mocked(fs.readFile).mockResolvedValue('flask==2.0.0' as any);
68
-
69
- const result = await service.discover('/test');
70
- expect(result.matches.preset?.name).toBe('api');
71
- expect(result.config.ignore).toContain('venv/**');
72
- expect(result.config.ignore).toContain('__pycache__/**');
73
- expect(result.config.ignore).toContain('*.pyc');
74
- });
75
-
76
- it('should include project-type-aware ignore patterns for UI preset', async () => {
77
- const service = new DiscoveryService();
78
- // Mock finding next.config.js (UI marker)
79
- vi.mocked(fs.pathExists).mockImplementation(async (p: string) => p.includes('next.config.js'));
80
- vi.mocked(fs.readdir).mockResolvedValue(['next.config.js'] as any);
81
- vi.mocked(fs.readFile).mockResolvedValue('module.exports = {}' as any);
82
-
83
- const result = await service.discover('/test');
84
- expect(result.matches.preset?.name).toBe('ui');
85
- expect(result.config.ignore).toContain('node_modules/**');
86
- expect(result.config.ignore).toContain('.next/**');
87
- });
88
- });
package/src/discovery.ts DELETED
@@ -1,112 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { Config, Gates } from './types/index.js';
4
- import { TEMPLATES, PARADIGM_TEMPLATES, UNIVERSAL_CONFIG } from './templates/index.js';
5
-
6
- export interface DiscoveryResult {
7
- config: Config;
8
- matches: {
9
- preset?: { name: string; marker: string };
10
- paradigm?: { name: string; marker: string };
11
- };
12
- }
13
-
14
- export class DiscoveryService {
15
- async discover(cwd: string): Promise<DiscoveryResult> {
16
- let config = { ...UNIVERSAL_CONFIG };
17
- const matches: DiscoveryResult['matches'] = {};
18
-
19
- // 1. Detect Role (ui, api, infra, data)
20
- for (const template of TEMPLATES) {
21
- const marker = await this.findFirstMarker(cwd, template.markers, true); // Search content for roles too
22
- if (marker) {
23
- config = this.mergeConfig(config, template.config);
24
- matches.preset = { name: template.name, marker };
25
- break; // Only one role for now
26
- }
27
- }
28
-
29
- // 2. Detect Paradigm (oop, functional)
30
- for (const template of PARADIGM_TEMPLATES) {
31
- const marker = await this.findFirstMarker(cwd, template.markers, true); // Search content
32
- if (marker) {
33
- config = this.mergeConfig(config, template.config);
34
- matches.paradigm = { name: template.name, marker };
35
- break;
36
- }
37
- }
38
-
39
- return { config, matches };
40
- }
41
-
42
- private mergeConfig(base: Config, extension: any): Config {
43
- // Deep merge for gates to preserve defaults when overrides are partial
44
- const mergedGates = { ...base.gates };
45
- if (extension.gates) {
46
- for (const [key, value] of Object.entries(extension.gates)) {
47
- if (typeof value === 'object' && value !== null && !Array.isArray(value) && (mergedGates as any)[key]) {
48
- (mergedGates as any)[key] = { ...(mergedGates as any)[key], ...value };
49
- } else {
50
- (mergedGates as any)[key] = value;
51
- }
52
- }
53
- }
54
-
55
- return {
56
- ...base,
57
- preset: extension.preset || base.preset,
58
- paradigm: extension.paradigm || base.paradigm,
59
- commands: { ...base.commands, ...extension.commands },
60
- gates: mergedGates as Gates,
61
- ignore: [...new Set([...(base.ignore || []), ...(extension.ignore || [])])],
62
- };
63
- }
64
-
65
- private async findFirstMarker(cwd: string, markers: string[], searchContent: boolean = false): Promise<string | null> {
66
- for (const marker of markers) {
67
- const fullPath = path.join(cwd, marker);
68
-
69
- // File/Directory existence check
70
- if (await fs.pathExists(fullPath)) {
71
- return marker;
72
- }
73
-
74
- // Deep content check for paradigms
75
- if (searchContent) {
76
- const match = await this.existsInContent(cwd, marker);
77
- if (match) return `content:${marker}`;
78
- }
79
- }
80
- return null;
81
- }
82
-
83
- private async existsInContent(cwd: string, pattern: string): Promise<boolean> {
84
- // Simple heuristic: search in top 5 source files
85
- const files = await this.findSourceFiles(cwd);
86
- for (const file of files) {
87
- const content = await fs.readFile(file, 'utf-8');
88
- if (content.includes(pattern)) return true;
89
- }
90
- return false;
91
- }
92
-
93
- private async findSourceFiles(cwd: string): Promise<string[]> {
94
- const extensions = ['.ts', '.js', '.py', '.go', '.java', '.tf', 'package.json'];
95
- const samples: string[] = [];
96
- const commonDirs = ['.', 'src', 'app', 'lib', 'api', 'pkg'];
97
-
98
- for (const dir of commonDirs) {
99
- const fullDir = path.join(cwd, dir);
100
- if (!(await fs.pathExists(fullDir))) continue;
101
-
102
- const files = await fs.readdir(fullDir);
103
- for (const file of files) {
104
- if (extensions.some(ext => file.endsWith(ext))) {
105
- samples.push(path.join(fullDir, file));
106
- if (samples.length >= 5) return samples;
107
- }
108
- }
109
- }
110
- return samples;
111
- }
112
- }
@@ -1,115 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { GateRunner } from './gates/runner.js';
3
- import { Config, RawConfig, ConfigSchema } from './types/index.js';
4
- import fs from 'fs-extra';
5
- import path from 'path';
6
-
7
- describe('Environment Alignment Gate', () => {
8
- const testDir = path.join(process.cwd(), 'temp-test-env');
9
-
10
- beforeEach(async () => {
11
- await fs.ensureDir(testDir);
12
- });
13
-
14
- afterEach(async () => {
15
- await fs.remove(testDir);
16
- });
17
-
18
- it('should detect tool version mismatch (Explicit)', async () => {
19
- const rawConfig: RawConfig = {
20
- version: 1,
21
- gates: {
22
- environment: {
23
- enabled: true,
24
- enforce_contracts: false,
25
- tools: {
26
- node: ">=99.0.0" // Guaranteed to fail
27
- }
28
- }
29
- }
30
- };
31
-
32
- const config = ConfigSchema.parse(rawConfig);
33
- const runner = new GateRunner(config);
34
- const report = await runner.run(testDir);
35
-
36
- expect(report.status).toBe('FAIL');
37
- const envFailure = report.failures.find(f => f.id === 'environment-alignment');
38
- expect(envFailure).toBeDefined();
39
- expect(envFailure?.details).toContain('node');
40
- expect(envFailure?.details).toContain('version mismatch');
41
- });
42
-
43
- it('should detect missing environment variables', async () => {
44
- const rawConfig: RawConfig = {
45
- version: 1,
46
- gates: {
47
- environment: {
48
- enabled: true,
49
- required_env: ["RIGOUR_TEST_VAR_MISSING"]
50
- }
51
- }
52
- };
53
-
54
- const config = ConfigSchema.parse(rawConfig);
55
- const runner = new GateRunner(config);
56
- const report = await runner.run(testDir);
57
-
58
- expect(report.status).toBe('FAIL');
59
- expect(report.failures[0].details).toContain('RIGOUR_TEST_VAR_MISSING');
60
- });
61
-
62
- it('should discover contracts from pyproject.toml', async () => {
63
- // Create mock pyproject.toml with a version that will surely fail
64
- await fs.writeFile(path.join(testDir, 'pyproject.toml'), `
65
- [tool.ruff]
66
- ruff = ">=99.14.0"
67
- `);
68
-
69
- const rawConfig: RawConfig = {
70
- version: 1,
71
- gates: {
72
- environment: {
73
- enabled: true,
74
- enforce_contracts: true,
75
- tools: {} // Should discover ruff from file
76
- }
77
- }
78
- };
79
-
80
- const config = ConfigSchema.parse(rawConfig);
81
- const runner = new GateRunner(config);
82
- const report = await runner.run(testDir);
83
-
84
- // This might pass or fail depending on the local ruff version,
85
- // but we want to check if the gate attempted to check ruff.
86
- // If ruff is missing, it will fail with "is missing".
87
- const ruffFailure = report.failures.find(f => f.details.includes('ruff'));
88
- expect(ruffFailure).toBeDefined();
89
- });
90
-
91
- it('should prioritize environment gate and run it first', async () => {
92
- const rawConfig: RawConfig = {
93
- version: 1,
94
- gates: {
95
- max_file_lines: 1,
96
- environment: {
97
- enabled: true,
98
- required_env: ["MANDATORY_SECRET_MISSING"]
99
- }
100
- }
101
- };
102
-
103
- const config = ConfigSchema.parse(rawConfig);
104
-
105
- // Create a file that would fail max_file_lines
106
- await fs.writeFile(path.join(testDir, 'large.js'), 'line1\nline2\nline3');
107
-
108
- const runner = new GateRunner(config);
109
- const report = await runner.run(testDir);
110
-
111
- // In a real priority system, we might want to stop after environment failure.
112
- // Currently GateRunner runs all, but environment-alignment has been unshifted.
113
- expect(Object.keys(report.summary)[0]).toBe('environment-alignment');
114
- });
115
- });