@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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duplication Drift Gate
|
|
3
|
+
*
|
|
4
|
+
* Detects when AI generates near-identical functions across files because
|
|
5
|
+
* it doesn't remember what it already wrote. This is an AI-specific failure
|
|
6
|
+
* mode — humans reuse via copy-paste (same file), AI re-invents (cross-file).
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Extract all function bodies (normalized: strip whitespace, comments)
|
|
10
|
+
* 2. Compare function signatures + body hashes across files
|
|
11
|
+
* 3. Flag functions with >80% similarity in different files
|
|
12
|
+
*
|
|
13
|
+
* @since v2.16.0
|
|
14
|
+
*/
|
|
15
|
+
import { Gate } from './base.js';
|
|
16
|
+
import { FileScanner } from '../utils/scanner.js';
|
|
17
|
+
import { Logger } from '../utils/logger.js';
|
|
18
|
+
import crypto from 'crypto';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
export class DuplicationDriftGate extends Gate {
|
|
21
|
+
config;
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
super('duplication-drift', 'AI Duplication Drift Detection');
|
|
24
|
+
this.config = {
|
|
25
|
+
enabled: config.enabled ?? true,
|
|
26
|
+
similarity_threshold: config.similarity_threshold ?? 0.8,
|
|
27
|
+
min_body_lines: config.min_body_lines ?? 5,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
get provenance() { return 'ai-drift'; }
|
|
31
|
+
async run(context) {
|
|
32
|
+
if (!this.config.enabled)
|
|
33
|
+
return [];
|
|
34
|
+
const failures = [];
|
|
35
|
+
const functions = [];
|
|
36
|
+
const files = await FileScanner.findFiles({
|
|
37
|
+
cwd: context.cwd,
|
|
38
|
+
patterns: ['**/*.{ts,js,tsx,jsx,py}'],
|
|
39
|
+
ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/*.test.*', '**/*.spec.*'],
|
|
40
|
+
});
|
|
41
|
+
Logger.info(`Duplication Drift: Scanning ${files.length} files`);
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
try {
|
|
44
|
+
const { readFile } = await import('fs-extra');
|
|
45
|
+
const content = await readFile(path.join(context.cwd, file), 'utf-8');
|
|
46
|
+
const ext = path.extname(file);
|
|
47
|
+
if (['.ts', '.js', '.tsx', '.jsx'].includes(ext)) {
|
|
48
|
+
this.extractJSFunctions(content, file, functions);
|
|
49
|
+
}
|
|
50
|
+
else if (ext === '.py') {
|
|
51
|
+
this.extractPyFunctions(content, file, functions);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e) { }
|
|
55
|
+
}
|
|
56
|
+
// Compare all function pairs across different files
|
|
57
|
+
const duplicateGroups = this.findDuplicateGroups(functions);
|
|
58
|
+
for (const group of duplicateGroups) {
|
|
59
|
+
const files = group.map(f => f.file);
|
|
60
|
+
const locations = group.map(f => `${f.file}:${f.line} (${f.name})`).join(', ');
|
|
61
|
+
failures.push(this.createFailure(`AI Duplication Drift: Function '${group[0].name}' has ${group.length} near-identical copies across files`, [...new Set(files)], `Found duplicate implementations at: ${locations}. Extract to a shared module and import.`, 'Duplication Drift', group[0].line, undefined, 'high'));
|
|
62
|
+
}
|
|
63
|
+
return failures;
|
|
64
|
+
}
|
|
65
|
+
extractJSFunctions(content, file, functions) {
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
// Match function declarations, arrow functions, and method definitions
|
|
68
|
+
const patterns = [
|
|
69
|
+
// function name(...) {
|
|
70
|
+
/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
|
|
71
|
+
// const name = (...) => {
|
|
72
|
+
/^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|(\w+))\s*=>/,
|
|
73
|
+
// name(...) { — class method
|
|
74
|
+
/^\s+(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*\{/,
|
|
75
|
+
];
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
const line = lines[i];
|
|
78
|
+
for (const pattern of patterns) {
|
|
79
|
+
const match = line.match(pattern);
|
|
80
|
+
if (match) {
|
|
81
|
+
const name = match[1];
|
|
82
|
+
const params = match[2] || '';
|
|
83
|
+
const body = this.extractFunctionBody(lines, i);
|
|
84
|
+
if (body.length >= this.config.min_body_lines) {
|
|
85
|
+
const normalized = this.normalizeBody(body.join('\n'));
|
|
86
|
+
functions.push({
|
|
87
|
+
name,
|
|
88
|
+
file,
|
|
89
|
+
line: i + 1,
|
|
90
|
+
paramCount: params ? params.split(',').length : 0,
|
|
91
|
+
bodyHash: this.hash(normalized),
|
|
92
|
+
bodyLength: body.length,
|
|
93
|
+
normalized,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
extractPyFunctions(content, file, functions) {
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
for (let i = 0; i < lines.length; i++) {
|
|
104
|
+
const match = lines[i].match(/^(?:\s*)(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
|
|
105
|
+
if (match) {
|
|
106
|
+
const name = match[1];
|
|
107
|
+
const params = match[2] || '';
|
|
108
|
+
const indent = lines[i].match(/^(\s*)/)?.[1]?.length || 0;
|
|
109
|
+
// Extract body by indentation
|
|
110
|
+
const body = [];
|
|
111
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
112
|
+
const lineIndent = lines[j].match(/^(\s*)/)?.[1]?.length || 0;
|
|
113
|
+
if (lines[j].trim() === '' || lineIndent > indent) {
|
|
114
|
+
body.push(lines[j]);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (body.length >= this.config.min_body_lines) {
|
|
121
|
+
const normalized = this.normalizeBody(body.join('\n'));
|
|
122
|
+
functions.push({
|
|
123
|
+
name,
|
|
124
|
+
file,
|
|
125
|
+
line: i + 1,
|
|
126
|
+
paramCount: params ? params.split(',').length : 0,
|
|
127
|
+
bodyHash: this.hash(normalized),
|
|
128
|
+
bodyLength: body.length,
|
|
129
|
+
normalized,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
extractFunctionBody(lines, startIndex) {
|
|
136
|
+
let braceDepth = 0;
|
|
137
|
+
let started = false;
|
|
138
|
+
const body = [];
|
|
139
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
140
|
+
const line = lines[i];
|
|
141
|
+
for (const ch of line) {
|
|
142
|
+
if (ch === '{') {
|
|
143
|
+
braceDepth++;
|
|
144
|
+
started = true;
|
|
145
|
+
}
|
|
146
|
+
if (ch === '}')
|
|
147
|
+
braceDepth--;
|
|
148
|
+
}
|
|
149
|
+
if (started)
|
|
150
|
+
body.push(line);
|
|
151
|
+
if (started && braceDepth === 0)
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
return body;
|
|
155
|
+
}
|
|
156
|
+
normalizeBody(body) {
|
|
157
|
+
return body
|
|
158
|
+
.replace(/\/\/.*/g, '') // strip single-line comments
|
|
159
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // strip multi-line comments
|
|
160
|
+
.replace(/#.*/g, '') // strip Python comments
|
|
161
|
+
.replace(/`[^`]*`/g, '"STR"') // normalize template literals to placeholder
|
|
162
|
+
.replace(/\basync\s+/g, '') // normalize async modifier
|
|
163
|
+
.replace(/\s+/g, ' ') // collapse whitespace
|
|
164
|
+
.replace(/['"]/g, '"') // normalize single/double quotes (NOT backticks)
|
|
165
|
+
.trim();
|
|
166
|
+
}
|
|
167
|
+
hash(text) {
|
|
168
|
+
return crypto.createHash('md5').update(text).digest('hex');
|
|
169
|
+
}
|
|
170
|
+
findDuplicateGroups(functions) {
|
|
171
|
+
const groups = new Map();
|
|
172
|
+
// Group by body hash (exact duplicates across files)
|
|
173
|
+
for (const fn of functions) {
|
|
174
|
+
const existing = groups.get(fn.bodyHash) || [];
|
|
175
|
+
existing.push(fn);
|
|
176
|
+
groups.set(fn.bodyHash, existing);
|
|
177
|
+
}
|
|
178
|
+
// Filter: only groups with functions from DIFFERENT files, 2+ members
|
|
179
|
+
const duplicates = [];
|
|
180
|
+
for (const group of groups.values()) {
|
|
181
|
+
if (group.length < 2)
|
|
182
|
+
continue;
|
|
183
|
+
const uniqueFiles = new Set(group.map(f => f.file));
|
|
184
|
+
if (uniqueFiles.size >= 2) {
|
|
185
|
+
duplicates.push(group);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return duplicates;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -26,22 +26,22 @@ export class EnvironmentGate extends Gate {
|
|
|
26
26
|
if (versionMatch) {
|
|
27
27
|
const version = versionMatch[1];
|
|
28
28
|
if (!semver.satisfies(version, semverRange)) {
|
|
29
|
-
failures.push(this.createFailure(`Environment Alignment: Tool '${tool}' version mismatch.`, [], `Project requires '${tool} ${semverRange}' (discovered from contract), but found version '${version}'. Please align your local environment to prevent drift
|
|
29
|
+
failures.push(this.createFailure(`Environment Alignment: Tool '${tool}' version mismatch.`, [], `Project requires '${tool} ${semverRange}' (discovered from contract), but found version '${version}'. Please align your local environment to prevent drift.`, undefined, undefined, undefined, 'medium'));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
|
-
failures.push(this.createFailure(`Environment Alignment: Could not determine version for '${tool}'.`, [], `Ensure '${tool} --version' returns a standard SemVer string
|
|
33
|
+
failures.push(this.createFailure(`Environment Alignment: Could not determine version for '${tool}'.`, [], `Ensure '${tool} --version' returns a standard SemVer string.`, undefined, undefined, undefined, 'medium'));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
catch (e) {
|
|
37
|
-
failures.push(this.createFailure(`Environment Alignment: Required tool '${tool}' is missing.`, [], `Install '${tool}' and ensure it is in your $PATH
|
|
37
|
+
failures.push(this.createFailure(`Environment Alignment: Required tool '${tool}' is missing.`, [], `Install '${tool}' and ensure it is in your $PATH.`, undefined, undefined, undefined, 'medium'));
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
// 2. Verify Required Env Vars
|
|
41
41
|
const requiredEnv = envConfig.required_env || [];
|
|
42
42
|
for (const envVar of requiredEnv) {
|
|
43
43
|
if (!process.env[envVar]) {
|
|
44
|
-
failures.push(this.createFailure(`Environment Alignment: Missing required environment variable '${envVar}'.`, [], `Ensure '${envVar}' is defined in your environment or .env file
|
|
44
|
+
failures.push(this.createFailure(`Environment Alignment: Missing required environment variable '${envVar}'.`, [], `Ensure '${envVar}' is defined in your environment or .env file.`, undefined, undefined, undefined, 'medium'));
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
return failures;
|
package/dist/gates/file.js
CHANGED
|
@@ -22,7 +22,7 @@ export class FileGate extends Gate {
|
|
|
22
22
|
}
|
|
23
23
|
if (violations.length > 0) {
|
|
24
24
|
return [
|
|
25
|
-
this.createFailure(`The following files exceed the maximum limit of ${this.config.maxLines} lines:`, violations, 'Break these files into smaller, more modular components to improve maintainability (SOLID - Single Responsibility Principle).'),
|
|
25
|
+
this.createFailure(`The following files exceed the maximum limit of ${this.config.maxLines} lines:`, violations, 'Break these files into smaller, more modular components to improve maintainability (SOLID - Single Responsibility Principle).', undefined, undefined, undefined, 'low'),
|
|
26
26
|
];
|
|
27
27
|
}
|
|
28
28
|
return [];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hallucinated Imports Gate
|
|
3
|
+
*
|
|
4
|
+
* Detects imports that reference modules which don't exist in the project.
|
|
5
|
+
* This is an AI-specific failure mode — LLMs confidently generate import
|
|
6
|
+
* statements for packages, files, or modules that were never installed
|
|
7
|
+
* or created.
|
|
8
|
+
*
|
|
9
|
+
* Detection strategy:
|
|
10
|
+
* 1. Parse all import/require statements
|
|
11
|
+
* 2. For relative imports: verify the target file exists
|
|
12
|
+
* 3. For package imports: verify the package exists in node_modules or package.json
|
|
13
|
+
* 4. For Python imports: verify the module exists in the project or site-packages
|
|
14
|
+
* 5. For Go imports: verify relative package paths exist in the project
|
|
15
|
+
* 6. For Ruby/C#: verify relative require/using paths exist
|
|
16
|
+
*
|
|
17
|
+
* Supported languages: JS/TS, Python, Go, Ruby, C#
|
|
18
|
+
*
|
|
19
|
+
* @since v2.16.0
|
|
20
|
+
*/
|
|
21
|
+
import { Gate, GateContext } from './base.js';
|
|
22
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
23
|
+
export interface HallucinatedImport {
|
|
24
|
+
file: string;
|
|
25
|
+
line: number;
|
|
26
|
+
importPath: string;
|
|
27
|
+
type: 'relative' | 'package' | 'python' | 'go' | 'ruby' | 'csharp';
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
export interface HallucinatedImportsConfig {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
check_relative?: boolean;
|
|
33
|
+
check_packages?: boolean;
|
|
34
|
+
ignore_patterns?: string[];
|
|
35
|
+
}
|
|
36
|
+
export declare class HallucinatedImportsGate extends Gate {
|
|
37
|
+
private config;
|
|
38
|
+
constructor(config?: HallucinatedImportsConfig);
|
|
39
|
+
protected get provenance(): Provenance;
|
|
40
|
+
run(context: GateContext): Promise<Failure[]>;
|
|
41
|
+
private checkJSImports;
|
|
42
|
+
private checkPyImports;
|
|
43
|
+
private resolveRelativeImport;
|
|
44
|
+
private extractPackageName;
|
|
45
|
+
private shouldIgnore;
|
|
46
|
+
private isNodeBuiltin;
|
|
47
|
+
private isPythonStdlib;
|
|
48
|
+
/**
|
|
49
|
+
* Check Go imports — verify relative/project package paths exist
|
|
50
|
+
* Go stdlib packages are skipped; only project-relative imports are checked
|
|
51
|
+
*/
|
|
52
|
+
private checkGoImports;
|
|
53
|
+
/**
|
|
54
|
+
* Check Ruby imports — verify require_relative paths exist
|
|
55
|
+
*/
|
|
56
|
+
private checkRubyImports;
|
|
57
|
+
/**
|
|
58
|
+
* Check C# imports — verify relative using paths match project namespaces
|
|
59
|
+
* (C# uses namespaces, not file paths — we check for obviously wrong namespaces)
|
|
60
|
+
*/
|
|
61
|
+
private checkCSharpImports;
|
|
62
|
+
private loadPackageJson;
|
|
63
|
+
}
|