@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
@@ -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;
@@ -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
+ }