@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.
- package/README.md +58 -0
- package/dist/context.test.js +2 -3
- package/dist/environment.test.js +2 -1
- package/dist/gates/agent-team.d.ts +2 -1
- package/dist/gates/agent-team.js +1 -0
- package/dist/gates/base.d.ts +4 -2
- package/dist/gates/base.js +5 -1
- package/dist/gates/checkpoint.d.ts +2 -1
- package/dist/gates/checkpoint.js +3 -2
- package/dist/gates/content.js +1 -1
- package/dist/gates/context-window-artifacts.d.ts +34 -0
- package/dist/gates/context-window-artifacts.js +214 -0
- package/dist/gates/context.d.ts +2 -1
- package/dist/gates/context.js +4 -3
- package/dist/gates/coverage.js +3 -1
- package/dist/gates/dependency.js +5 -5
- package/dist/gates/duplication-drift.d.ts +33 -0
- package/dist/gates/duplication-drift.js +190 -0
- package/dist/gates/environment.js +4 -4
- package/dist/gates/file.js +1 -1
- package/dist/gates/hallucinated-imports.d.ts +63 -0
- package/dist/gates/hallucinated-imports.js +406 -0
- package/dist/gates/inconsistent-error-handling.d.ts +39 -0
- package/dist/gates/inconsistent-error-handling.js +236 -0
- package/dist/gates/promise-safety.d.ts +68 -0
- package/dist/gates/promise-safety.js +509 -0
- package/dist/gates/retry-loop-breaker.d.ts +2 -1
- package/dist/gates/retry-loop-breaker.js +2 -1
- package/dist/gates/runner.js +62 -1
- package/dist/gates/safety.d.ts +2 -1
- package/dist/gates/safety.js +2 -1
- package/dist/gates/security-patterns.d.ts +2 -1
- package/dist/gates/security-patterns.js +2 -1
- package/dist/gates/structure.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/fix-packet-service.d.ts +0 -1
- package/dist/services/fix-packet-service.js +9 -14
- package/dist/services/score-history.d.ts +54 -0
- package/dist/services/score-history.js +122 -0
- package/dist/templates/index.js +195 -0
- package/dist/types/fix-packet.d.ts +5 -5
- package/dist/types/fix-packet.js +1 -1
- package/dist/types/index.d.ts +430 -0
- package/dist/types/index.js +57 -0
- package/package.json +21 -1
- package/src/context.test.ts +0 -256
- package/src/discovery.test.ts +0 -88
- package/src/discovery.ts +0 -112
- package/src/environment.test.ts +0 -115
- package/src/gates/agent-team.test.ts +0 -134
- package/src/gates/agent-team.ts +0 -210
- package/src/gates/ast-handlers/base.ts +0 -13
- package/src/gates/ast-handlers/python.ts +0 -145
- package/src/gates/ast-handlers/python_parser.py +0 -181
- package/src/gates/ast-handlers/typescript.ts +0 -264
- package/src/gates/ast-handlers/universal.ts +0 -184
- package/src/gates/ast.ts +0 -54
- package/src/gates/base.ts +0 -27
- package/src/gates/checkpoint.test.ts +0 -135
- package/src/gates/checkpoint.ts +0 -311
- package/src/gates/content.ts +0 -50
- package/src/gates/context.ts +0 -267
- package/src/gates/coverage.ts +0 -74
- package/src/gates/dependency.ts +0 -108
- package/src/gates/environment.ts +0 -94
- package/src/gates/file.ts +0 -42
- package/src/gates/retry-loop-breaker.ts +0 -151
- package/src/gates/runner.ts +0 -156
- package/src/gates/safety.ts +0 -56
- package/src/gates/security-patterns.test.ts +0 -162
- package/src/gates/security-patterns.ts +0 -305
- package/src/gates/structure.ts +0 -36
- package/src/index.ts +0 -13
- package/src/pattern-index/embeddings.ts +0 -84
- package/src/pattern-index/index.ts +0 -59
- package/src/pattern-index/indexer.test.ts +0 -276
- package/src/pattern-index/indexer.ts +0 -1023
- package/src/pattern-index/matcher.test.ts +0 -293
- package/src/pattern-index/matcher.ts +0 -493
- package/src/pattern-index/overrides.ts +0 -235
- package/src/pattern-index/security.ts +0 -151
- package/src/pattern-index/staleness.test.ts +0 -313
- package/src/pattern-index/staleness.ts +0 -568
- package/src/pattern-index/types.ts +0 -339
- package/src/safety.test.ts +0 -53
- package/src/services/adaptive-thresholds.test.ts +0 -189
- package/src/services/adaptive-thresholds.ts +0 -275
- package/src/services/context-engine.ts +0 -104
- package/src/services/fix-packet-service.ts +0 -42
- package/src/services/state-service.ts +0 -138
- package/src/smoke.test.ts +0 -18
- package/src/templates/index.ts +0 -312
- package/src/types/fix-packet.ts +0 -32
- package/src/types/index.ts +0 -159
- package/src/utils/logger.ts +0 -43
- package/src/utils/scanner.test.ts +0 -37
- package/src/utils/scanner.ts +0 -43
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -7
- package/vitest.setup.ts +0 -30
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Index - Types
|
|
3
|
-
*
|
|
4
|
-
* Core type definitions for the Pattern Index system.
|
|
5
|
-
* Rigour's Pattern Index prevents AI from reinventing existing code.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* All supported pattern types that can be indexed.
|
|
10
|
-
* Organized by category for clarity.
|
|
11
|
-
*/
|
|
12
|
-
export type PatternType =
|
|
13
|
-
// === CODE PATTERNS ===
|
|
14
|
-
| 'function' // Standalone functions, utilities
|
|
15
|
-
| 'class' // Classes
|
|
16
|
-
| 'method' // Class methods (indexed separately for reuse)
|
|
17
|
-
| 'component' // React/Vue/Svelte components
|
|
18
|
-
| 'hook' // React hooks (useX)
|
|
19
|
-
| 'decorator' // Python/TS decorators
|
|
20
|
-
| 'middleware' // Express/FastAPI middleware
|
|
21
|
-
|
|
22
|
-
// === DATA PATTERNS ===
|
|
23
|
-
| 'type' // TypeScript types
|
|
24
|
-
| 'interface' // TypeScript interfaces
|
|
25
|
-
| 'schema' // Zod, Yup, JSON Schema validators
|
|
26
|
-
| 'model' // Database models (Prisma, SQLAlchemy, etc.)
|
|
27
|
-
| 'enum' // Enumerations
|
|
28
|
-
|
|
29
|
-
// === CONFIGURATION ===
|
|
30
|
-
| 'constant' // Constants, magic values
|
|
31
|
-
| 'config' // Configuration objects
|
|
32
|
-
| 'env' // Environment variable patterns
|
|
33
|
-
|
|
34
|
-
// === API PATTERNS ===
|
|
35
|
-
| 'route' // API routes/endpoints
|
|
36
|
-
| 'handler' // Route handlers
|
|
37
|
-
| 'resolver' // GraphQL resolvers
|
|
38
|
-
| 'rpc' // tRPC procedures
|
|
39
|
-
|
|
40
|
-
// === STATE PATTERNS ===
|
|
41
|
-
| 'store' // State stores (Zustand, Redux)
|
|
42
|
-
| 'reducer' // Redux reducers
|
|
43
|
-
| 'action' // Actions/mutations
|
|
44
|
-
| 'selector' // State selectors
|
|
45
|
-
|
|
46
|
-
// === ERROR HANDLING ===
|
|
47
|
-
| 'error' // Custom error classes
|
|
48
|
-
| 'exception' // Exception types
|
|
49
|
-
|
|
50
|
-
// === TESTING ===
|
|
51
|
-
| 'mock' // Mock objects/functions
|
|
52
|
-
| 'fixture' // Test fixtures
|
|
53
|
-
| 'factory' // Test factories
|
|
54
|
-
|
|
55
|
-
// === INFRASTRUCTURE ===
|
|
56
|
-
| 'command' // CLI commands
|
|
57
|
-
| 'task' // Background tasks/jobs
|
|
58
|
-
| 'event' // Event types/handlers
|
|
59
|
-
| 'protocol'; // Python protocols, abstract classes
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* A single indexed pattern entry.
|
|
63
|
-
*/
|
|
64
|
-
export interface PatternEntry {
|
|
65
|
-
/** Unique identifier (hash of file + name) */
|
|
66
|
-
id: string;
|
|
67
|
-
|
|
68
|
-
/** The type of pattern */
|
|
69
|
-
type: PatternType;
|
|
70
|
-
|
|
71
|
-
/** Name of the pattern (e.g., "formatDate") */
|
|
72
|
-
name: string;
|
|
73
|
-
|
|
74
|
-
/** Relative path to the file */
|
|
75
|
-
file: string;
|
|
76
|
-
|
|
77
|
-
/** Line number where the pattern is defined */
|
|
78
|
-
line: number;
|
|
79
|
-
|
|
80
|
-
/** End line number */
|
|
81
|
-
endLine: number;
|
|
82
|
-
|
|
83
|
-
/** Function/method signature if applicable */
|
|
84
|
-
signature: string;
|
|
85
|
-
|
|
86
|
-
/** Description from JSDoc/docstring */
|
|
87
|
-
description: string;
|
|
88
|
-
|
|
89
|
-
/** Extracted semantic keywords for matching */
|
|
90
|
-
keywords: string[];
|
|
91
|
-
|
|
92
|
-
/** Content hash for change detection */
|
|
93
|
-
hash: string;
|
|
94
|
-
|
|
95
|
-
/** Is this pattern exported? */
|
|
96
|
-
exported: boolean;
|
|
97
|
-
|
|
98
|
-
/** How many files import this pattern */
|
|
99
|
-
usageCount: number; // How many files import this?
|
|
100
|
-
|
|
101
|
-
/** User-defined category/grouping */
|
|
102
|
-
category?: string; // User-defined grouping
|
|
103
|
-
embedding?: number[]; // Vector embedding for semantic search
|
|
104
|
-
|
|
105
|
-
/** Last indexed timestamp */
|
|
106
|
-
indexedAt: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* The complete pattern index structure.
|
|
111
|
-
*/
|
|
112
|
-
export interface PatternIndex {
|
|
113
|
-
/** Index format version */
|
|
114
|
-
version: string;
|
|
115
|
-
|
|
116
|
-
/** When the index was last updated */
|
|
117
|
-
lastUpdated: string;
|
|
118
|
-
|
|
119
|
-
/** Root directory that was indexed */
|
|
120
|
-
rootDir: string;
|
|
121
|
-
|
|
122
|
-
/** All indexed patterns */
|
|
123
|
-
patterns: PatternEntry[];
|
|
124
|
-
|
|
125
|
-
/** Index statistics */
|
|
126
|
-
stats: PatternIndexStats;
|
|
127
|
-
|
|
128
|
-
/** Files that were indexed */
|
|
129
|
-
files: IndexedFile[];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Statistics about the pattern index.
|
|
134
|
-
*/
|
|
135
|
-
export interface PatternIndexStats {
|
|
136
|
-
totalPatterns: number;
|
|
137
|
-
totalFiles: number;
|
|
138
|
-
byType: Record<PatternType, number>;
|
|
139
|
-
indexDurationMs: number;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Information about an indexed file.
|
|
144
|
-
*/
|
|
145
|
-
export interface IndexedFile {
|
|
146
|
-
path: string;
|
|
147
|
-
hash: string;
|
|
148
|
-
patternCount: number;
|
|
149
|
-
indexedAt: string;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Configuration for the pattern indexer.
|
|
154
|
-
*/
|
|
155
|
-
export interface PatternIndexConfig {
|
|
156
|
-
/** Directories to index (defaults to src/) */
|
|
157
|
-
include: string[];
|
|
158
|
-
|
|
159
|
-
/** Directories to exclude */
|
|
160
|
-
exclude: string[];
|
|
161
|
-
|
|
162
|
-
/** File extensions to index */
|
|
163
|
-
extensions: string[];
|
|
164
|
-
|
|
165
|
-
/** Whether to index test files */
|
|
166
|
-
indexTests: boolean;
|
|
167
|
-
|
|
168
|
-
/** Whether to index node_modules */
|
|
169
|
-
indexNodeModules: boolean;
|
|
170
|
-
|
|
171
|
-
/** Minimum pattern name length to index */
|
|
172
|
-
minNameLength: number;
|
|
173
|
-
|
|
174
|
-
/** Custom categories for patterns */
|
|
175
|
-
categories: Record<string, string[]>;
|
|
176
|
-
|
|
177
|
-
/** Whether to generate semantic embeddings for patterns */
|
|
178
|
-
useEmbeddings?: boolean;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Result from matching against the pattern index.
|
|
183
|
-
*/
|
|
184
|
-
export interface PatternMatchResult {
|
|
185
|
-
/** The query that was matched */
|
|
186
|
-
query: string;
|
|
187
|
-
|
|
188
|
-
/** All matches found */
|
|
189
|
-
matches: PatternMatch[];
|
|
190
|
-
|
|
191
|
-
/** Suggestion for what to do */
|
|
192
|
-
suggestion: string;
|
|
193
|
-
|
|
194
|
-
/** Whether human override is available */
|
|
195
|
-
canOverride: boolean;
|
|
196
|
-
|
|
197
|
-
/** Overall status */
|
|
198
|
-
status: 'FOUND_SIMILAR' | 'NO_MATCH' | 'OVERRIDE_ALLOWED';
|
|
199
|
-
|
|
200
|
-
/** Recommended action */
|
|
201
|
-
action: 'BLOCK' | 'WARN' | 'ALLOW';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* A single pattern match.
|
|
206
|
-
*/
|
|
207
|
-
export interface PatternMatch {
|
|
208
|
-
/** The matched pattern */
|
|
209
|
-
pattern: PatternEntry;
|
|
210
|
-
|
|
211
|
-
/** How the match was determined */
|
|
212
|
-
matchType: 'exact' | 'fuzzy' | 'signature' | 'semantic';
|
|
213
|
-
|
|
214
|
-
/** Confidence score 0-100 */
|
|
215
|
-
confidence: number;
|
|
216
|
-
|
|
217
|
-
/** Human-readable reason for the match */
|
|
218
|
-
reason: string;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Human override entry.
|
|
223
|
-
*/
|
|
224
|
-
export interface PatternOverride {
|
|
225
|
-
/** Pattern name or glob */
|
|
226
|
-
pattern: string;
|
|
227
|
-
|
|
228
|
-
/** Why the override was granted */
|
|
229
|
-
reason: string;
|
|
230
|
-
|
|
231
|
-
/** When the override expires */
|
|
232
|
-
expiresAt?: string;
|
|
233
|
-
|
|
234
|
-
/** Who approved the override */
|
|
235
|
-
approvedBy?: string;
|
|
236
|
-
|
|
237
|
-
/** When the override was created */
|
|
238
|
-
createdAt: string;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Staleness detection result.
|
|
243
|
-
*/
|
|
244
|
-
export interface StalenessResult {
|
|
245
|
-
/** Overall status */
|
|
246
|
-
status: 'FRESH' | 'STALE' | 'DEPRECATED';
|
|
247
|
-
|
|
248
|
-
/** All staleness issues found */
|
|
249
|
-
issues: StalenessIssue[];
|
|
250
|
-
|
|
251
|
-
/** Project context (versions) */
|
|
252
|
-
projectContext: Record<string, string>;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* A single staleness issue.
|
|
257
|
-
*/
|
|
258
|
-
export interface StalenessIssue {
|
|
259
|
-
/** Line number */
|
|
260
|
-
line: number;
|
|
261
|
-
|
|
262
|
-
/** The stale pattern */
|
|
263
|
-
pattern: string;
|
|
264
|
-
|
|
265
|
-
/** Severity */
|
|
266
|
-
severity: 'error' | 'warning' | 'info';
|
|
267
|
-
|
|
268
|
-
/** Why it's stale */
|
|
269
|
-
reason: string;
|
|
270
|
-
|
|
271
|
-
/** What to use instead */
|
|
272
|
-
replacement: string;
|
|
273
|
-
|
|
274
|
-
/** Link to documentation */
|
|
275
|
-
docs?: string;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Security/CVE entry for a package.
|
|
280
|
-
*/
|
|
281
|
-
export interface SecurityEntry {
|
|
282
|
-
/** CVE Identifier (e.g., CVE-2021-1234) */
|
|
283
|
-
cveId: string;
|
|
284
|
-
|
|
285
|
-
/** Package name */
|
|
286
|
-
packageName: string;
|
|
287
|
-
|
|
288
|
-
/** Vulnerable version range (semver) */
|
|
289
|
-
vulnerableRange: string;
|
|
290
|
-
|
|
291
|
-
/** Severity level */
|
|
292
|
-
severity: 'critical' | 'high' | 'moderate' | 'low';
|
|
293
|
-
|
|
294
|
-
/** Brief description of the vulnerability */
|
|
295
|
-
title: string;
|
|
296
|
-
|
|
297
|
-
/** Link to advisory */
|
|
298
|
-
url: string;
|
|
299
|
-
|
|
300
|
-
/** The version of the package in the current project */
|
|
301
|
-
currentVersion?: string;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Result from security check.
|
|
306
|
-
*/
|
|
307
|
-
export interface SecurityResult {
|
|
308
|
-
/** Overall security status */
|
|
309
|
-
status: 'SECURE' | 'VULNERABLE';
|
|
310
|
-
|
|
311
|
-
/** All CVEs/vulnerabilities found */
|
|
312
|
-
vulnerabilities: SecurityEntry[];
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Deprecation entry in the deprecation database.
|
|
317
|
-
*/
|
|
318
|
-
export interface DeprecationEntry {
|
|
319
|
-
/** Pattern to match (can be regex) */
|
|
320
|
-
pattern: string;
|
|
321
|
-
|
|
322
|
-
/** Library this belongs to */
|
|
323
|
-
library?: string;
|
|
324
|
-
|
|
325
|
-
/** Version when deprecated */
|
|
326
|
-
deprecatedIn: string;
|
|
327
|
-
|
|
328
|
-
/** Suggested replacement */
|
|
329
|
-
replacement: string;
|
|
330
|
-
|
|
331
|
-
/** Severity level */
|
|
332
|
-
severity: 'error' | 'warning' | 'info';
|
|
333
|
-
|
|
334
|
-
/** Additional context */
|
|
335
|
-
reason?: string;
|
|
336
|
-
|
|
337
|
-
/** Documentation link */
|
|
338
|
-
docs?: string;
|
|
339
|
-
}
|
package/src/safety.test.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { FileGuardGate } from './gates/safety.js';
|
|
3
|
-
import { Gates } from './types/index.js';
|
|
4
|
-
import { execa } from 'execa';
|
|
5
|
-
|
|
6
|
-
vi.mock('execa');
|
|
7
|
-
|
|
8
|
-
describe('FileGuardGate', () => {
|
|
9
|
-
const config: Gates = {
|
|
10
|
-
safety: {
|
|
11
|
-
protected_paths: ['docs/'],
|
|
12
|
-
max_files_changed_per_cycle: 10
|
|
13
|
-
}
|
|
14
|
-
} as any;
|
|
15
|
-
|
|
16
|
-
it('should flag modified (M) protected files', async () => {
|
|
17
|
-
const gate = new FileGuardGate(config);
|
|
18
|
-
vi.mocked(execa).mockResolvedValueOnce({ stdout: ' M docs/SPEC.md\n' } as any);
|
|
19
|
-
|
|
20
|
-
const failures = await gate.run({ cwd: '/test', record: {} as any });
|
|
21
|
-
expect(failures).toHaveLength(1);
|
|
22
|
-
expect(failures[0].title).toContain("Protected file 'docs/SPEC.md' was modified.");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should flag added (A) protected files', async () => {
|
|
26
|
-
const gate = new FileGuardGate(config);
|
|
27
|
-
vi.mocked(execa).mockResolvedValueOnce({ stdout: 'A docs/NEW.md\n' } as any);
|
|
28
|
-
|
|
29
|
-
const failures = await gate.run({ cwd: '/test', record: {} as any });
|
|
30
|
-
expect(failures).toHaveLength(1);
|
|
31
|
-
expect(failures[0].title).toContain("Protected file 'docs/NEW.md' was modified.");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should NOT flag untracked (??) protected files', async () => {
|
|
35
|
-
const gate = new FileGuardGate(config);
|
|
36
|
-
vi.mocked(execa).mockResolvedValueOnce({ stdout: '?? docs/UNTRAKED.md\n' } as any);
|
|
37
|
-
|
|
38
|
-
const failures = await gate.run({ cwd: '/test', record: {} as any });
|
|
39
|
-
expect(failures).toHaveLength(0);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should correctly handle multiple mixed statuses', async () => {
|
|
43
|
-
const gate = new FileGuardGate(config);
|
|
44
|
-
vi.mocked(execa).mockResolvedValueOnce({
|
|
45
|
-
stdout: ' M docs/MODIFIED.md\n?? docs/NEW_UNTRACKED.md\n D docs/DELETED.md\n'
|
|
46
|
-
} as any);
|
|
47
|
-
|
|
48
|
-
const failures = await gate.run({ cwd: '/test', record: {} as any });
|
|
49
|
-
expect(failures).toHaveLength(2);
|
|
50
|
-
expect(failures.map(f => f.title)).toContain("Protected file 'docs/MODIFIED.md' was modified.");
|
|
51
|
-
expect(failures.map(f => f.title)).toContain("Protected file 'docs/DELETED.md' was modified.");
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
detectComplexityTier,
|
|
4
|
-
calculateAdaptiveThresholds,
|
|
5
|
-
recordGateRun,
|
|
6
|
-
getQualityTrend,
|
|
7
|
-
clearAdaptiveHistory,
|
|
8
|
-
getAdaptiveSummary,
|
|
9
|
-
} from './adaptive-thresholds.js';
|
|
10
|
-
import * as fs from 'fs';
|
|
11
|
-
import * as path from 'path';
|
|
12
|
-
import * as os from 'os';
|
|
13
|
-
|
|
14
|
-
describe('AdaptiveThresholds', () => {
|
|
15
|
-
let testDir: string;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adaptive-test-'));
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
clearAdaptiveHistory(testDir);
|
|
23
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('detectComplexityTier', () => {
|
|
27
|
-
it('should detect hobby tier for small projects', () => {
|
|
28
|
-
const tier = detectComplexityTier({ fileCount: 20 });
|
|
29
|
-
expect(tier).toBe('hobby');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should detect startup tier for medium projects', () => {
|
|
33
|
-
const tier = detectComplexityTier({ fileCount: 100 });
|
|
34
|
-
expect(tier).toBe('startup');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should detect enterprise tier for large projects', () => {
|
|
38
|
-
const tier = detectComplexityTier({ fileCount: 600 });
|
|
39
|
-
expect(tier).toBe('enterprise');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should consider commit count for tier detection', () => {
|
|
43
|
-
const tier = detectComplexityTier({ fileCount: 50, commitCount: 1500 });
|
|
44
|
-
expect(tier).toBe('enterprise');
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('calculateAdaptiveThresholds', () => {
|
|
49
|
-
it('should return lenient thresholds for hobby tier', () => {
|
|
50
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
51
|
-
testDir,
|
|
52
|
-
{ fileCount: 20 }
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
expect(adjustments.tier).toBe('hobby');
|
|
56
|
-
expect(adjustments.coverageThreshold).toBeLessThan(80);
|
|
57
|
-
expect(adjustments.securityBlockLevel).toBe('critical');
|
|
58
|
-
expect(adjustments.leniencyFactor).toBeGreaterThan(0.5);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should return strict thresholds for enterprise tier', () => {
|
|
62
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
63
|
-
testDir,
|
|
64
|
-
{ fileCount: 600 }
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
expect(adjustments.tier).toBe('enterprise');
|
|
68
|
-
expect(adjustments.coverageThreshold).toBe(80);
|
|
69
|
-
expect(adjustments.securityBlockLevel).toBe('medium');
|
|
70
|
-
expect(adjustments.leniencyFactor).toBeLessThan(0.5);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should respect forced_tier config', () => {
|
|
74
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
75
|
-
testDir,
|
|
76
|
-
{ fileCount: 20 },
|
|
77
|
-
{ forced_tier: 'enterprise' }
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(adjustments.tier).toBe('enterprise');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should include reasoning for adjustments', () => {
|
|
84
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
85
|
-
testDir,
|
|
86
|
-
{ fileCount: 100 }
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
expect(adjustments.reasoning.length).toBeGreaterThan(0);
|
|
90
|
-
expect(adjustments.reasoning.some(r => r.includes('tier'))).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('historical tracking', () => {
|
|
95
|
-
it('should record gate runs', () => {
|
|
96
|
-
recordGateRun(testDir, 5, 2, 10);
|
|
97
|
-
recordGateRun(testDir, 6, 1, 5);
|
|
98
|
-
|
|
99
|
-
const historyPath = path.join(testDir, '.rigour', 'adaptive-history.json');
|
|
100
|
-
expect(fs.existsSync(historyPath)).toBe(true);
|
|
101
|
-
|
|
102
|
-
const history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
|
|
103
|
-
expect(history.runs).toHaveLength(2);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should return stable trend for new projects', () => {
|
|
107
|
-
const trend = getQualityTrend(testDir);
|
|
108
|
-
expect(trend).toBe('stable');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should detect improving trend', () => {
|
|
112
|
-
// Record 20 runs: older ones with high failures, recent with low
|
|
113
|
-
for (let i = 0; i < 10; i++) {
|
|
114
|
-
recordGateRun(testDir, 3, 5, 20);
|
|
115
|
-
}
|
|
116
|
-
for (let i = 0; i < 10; i++) {
|
|
117
|
-
recordGateRun(testDir, 7, 1, 5);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const trend = getQualityTrend(testDir);
|
|
121
|
-
expect(trend).toBe('improving');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should detect degrading trend', () => {
|
|
125
|
-
// Record 20 runs: older ones with low failures, recent with high
|
|
126
|
-
for (let i = 0; i < 10; i++) {
|
|
127
|
-
recordGateRun(testDir, 7, 1, 3);
|
|
128
|
-
}
|
|
129
|
-
for (let i = 0; i < 10; i++) {
|
|
130
|
-
recordGateRun(testDir, 3, 5, 20);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const trend = getQualityTrend(testDir);
|
|
134
|
-
expect(trend).toBe('degrading');
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe('trend-based adjustments', () => {
|
|
139
|
-
it('should relax thresholds for improving trend', () => {
|
|
140
|
-
// Create improving history
|
|
141
|
-
for (let i = 0; i < 10; i++) {
|
|
142
|
-
recordGateRun(testDir, 3, 5, 20);
|
|
143
|
-
}
|
|
144
|
-
for (let i = 0; i < 10; i++) {
|
|
145
|
-
recordGateRun(testDir, 7, 1, 5);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
149
|
-
testDir,
|
|
150
|
-
{ fileCount: 100 }
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
expect(adjustments.trend).toBe('improving');
|
|
154
|
-
expect(adjustments.reasoning.some(r => r.includes('bonus'))).toBe(true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should tighten thresholds for degrading trend', () => {
|
|
158
|
-
// Create degrading history
|
|
159
|
-
for (let i = 0; i < 10; i++) {
|
|
160
|
-
recordGateRun(testDir, 7, 1, 3);
|
|
161
|
-
}
|
|
162
|
-
for (let i = 0; i < 10; i++) {
|
|
163
|
-
recordGateRun(testDir, 3, 5, 20);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
167
|
-
testDir,
|
|
168
|
-
{ fileCount: 100 }
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
expect(adjustments.trend).toBe('degrading');
|
|
172
|
-
expect(adjustments.reasoning.some(r => r.includes('tightened'))).toBe(true);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe('getAdaptiveSummary', () => {
|
|
177
|
-
it('should return formatted summary string', () => {
|
|
178
|
-
const adjustments = calculateAdaptiveThresholds(
|
|
179
|
-
testDir,
|
|
180
|
-
{ fileCount: 100 }
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
const summary = getAdaptiveSummary(adjustments);
|
|
184
|
-
expect(summary).toContain('STARTUP');
|
|
185
|
-
expect(summary).toContain('Coverage:');
|
|
186
|
-
expect(summary).toContain('Quality:');
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
});
|