@rigour-labs/core 2.22.0 → 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 +3 -1
- package/dist/gates/base.js +3 -0
- package/dist/gates/checkpoint.d.ts +2 -1
- package/dist/gates/checkpoint.js +3 -2
- package/dist/gates/context-window-artifacts.d.ts +2 -1
- package/dist/gates/context-window-artifacts.js +6 -3
- package/dist/gates/context.d.ts +2 -1
- package/dist/gates/context.js +1 -0
- package/dist/gates/coverage.js +3 -1
- package/dist/gates/dependency.js +5 -5
- package/dist/gates/duplication-drift.d.ts +2 -1
- package/dist/gates/duplication-drift.js +4 -1
- package/dist/gates/environment.js +4 -4
- package/dist/gates/hallucinated-imports.d.ts +21 -2
- package/dist/gates/hallucinated-imports.js +116 -2
- package/dist/gates/inconsistent-error-handling.d.ts +2 -1
- package/dist/gates/inconsistent-error-handling.js +21 -7
- 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 +34 -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 +1 -0
- 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 +169 -0
- package/dist/types/fix-packet.d.ts +5 -5
- package/dist/types/fix-packet.js +1 -1
- package/dist/types/index.d.ts +153 -0
- package/dist/types/index.js +19 -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 -28
- package/src/gates/checkpoint.test.ts +0 -135
- package/src/gates/checkpoint.ts +0 -311
- package/src/gates/content.ts +0 -51
- package/src/gates/context-window-artifacts.ts +0 -277
- package/src/gates/context.ts +0 -270
- package/src/gates/coverage.ts +0 -74
- package/src/gates/dependency.ts +0 -108
- package/src/gates/duplication-drift.ts +0 -231
- package/src/gates/environment.ts +0 -94
- package/src/gates/file.ts +0 -46
- package/src/gates/hallucinated-imports.ts +0 -361
- package/src/gates/inconsistent-error-handling.ts +0 -254
- package/src/gates/retry-loop-breaker.ts +0 -151
- package/src/gates/runner.ts +0 -188
- package/src/gates/safety.ts +0 -56
- package/src/gates/security-patterns.test.ts +0 -162
- package/src/gates/security-patterns.ts +0 -306
- 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 -338
- package/src/types/fix-packet.ts +0 -32
- package/src/types/index.ts +0 -200
- 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
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @rigour-labs/core
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@rigour-labs/core)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**Deterministic quality gate engine for AI-generated code.**
|
|
7
|
+
|
|
8
|
+
The core library powering [Rigour](https://rigour.run) — AST analysis, AI drift detection, security scanning, and Fix Packet generation across TypeScript, JavaScript, Python, Go, Ruby, and C#/.NET.
|
|
9
|
+
|
|
10
|
+
> This package is the engine. For the CLI, use [`@rigour-labs/cli`](https://www.npmjs.com/package/@rigour-labs/cli). For MCP integration, use [`@rigour-labs/mcp`](https://www.npmjs.com/package/@rigour-labs/mcp).
|
|
11
|
+
|
|
12
|
+
## What's Inside
|
|
13
|
+
|
|
14
|
+
### 23 Quality Gates
|
|
15
|
+
|
|
16
|
+
**Structural:** File size, cyclomatic complexity, method count, parameter count, nesting depth, required docs, content hygiene.
|
|
17
|
+
|
|
18
|
+
**Security:** Hardcoded secrets, SQL injection, XSS, command injection, path traversal.
|
|
19
|
+
|
|
20
|
+
**AI-Native Drift Detection:** Duplication drift, hallucinated imports, inconsistent error handling, context window artifacts, async & error safety (promise safety).
|
|
21
|
+
|
|
22
|
+
**Agent Governance:** Multi-agent scope isolation, checkpoint supervision, context drift, retry loop breaker.
|
|
23
|
+
|
|
24
|
+
### Multi-Language Support
|
|
25
|
+
|
|
26
|
+
All gates support: TypeScript, JavaScript, Python, Go, Ruby, and C#/.NET.
|
|
27
|
+
|
|
28
|
+
### Two-Score System
|
|
29
|
+
|
|
30
|
+
Every failure carries a **provenance tag** (`ai-drift`, `traditional`, `security`, `governance`) and contributes to two sub-scores:
|
|
31
|
+
|
|
32
|
+
- **AI Health Score** (0–100) — AI-specific failures
|
|
33
|
+
- **Structural Score** (0–100) — Traditional code quality
|
|
34
|
+
|
|
35
|
+
### Fix Packets (v2)
|
|
36
|
+
|
|
37
|
+
Machine-readable JSON diagnostics with severity, provenance, file, line number, and step-by-step remediation instructions that AI agents can consume directly.
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { GateRunner } from '@rigour-labs/core';
|
|
43
|
+
|
|
44
|
+
const runner = new GateRunner(config, projectRoot);
|
|
45
|
+
const report = await runner.run();
|
|
46
|
+
|
|
47
|
+
console.log(report.pass); // true or false
|
|
48
|
+
console.log(report.score); // 0-100
|
|
49
|
+
console.log(report.failures); // Failure[]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
**[Full docs at docs.rigour.run](https://docs.rigour.run)**
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT © [Rigour Labs](https://github.com/rigour-labs)
|
package/dist/context.test.js
CHANGED
|
@@ -2,9 +2,8 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
|
2
2
|
import { GateRunner } from '../src/gates/runner.js';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import
|
|
6
|
-
const
|
|
7
|
-
const TEST_CWD = path.join(__dirname, '../temp-test-context');
|
|
5
|
+
import os from 'os';
|
|
6
|
+
const TEST_CWD = path.join(os.tmpdir(), 'rigour-temp-test-context-' + process.pid);
|
|
8
7
|
describe('Context Awareness Engine', () => {
|
|
9
8
|
beforeAll(async () => {
|
|
10
9
|
await fs.ensureDir(TEST_CWD);
|
package/dist/environment.test.js
CHANGED
|
@@ -3,8 +3,9 @@ import { GateRunner } from './gates/runner.js';
|
|
|
3
3
|
import { ConfigSchema } from './types/index.js';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
6
7
|
describe('Environment Alignment Gate', () => {
|
|
7
|
-
const testDir = path.join(
|
|
8
|
+
const testDir = path.join(os.tmpdir(), 'rigour-temp-test-env-' + process.pid);
|
|
8
9
|
beforeEach(async () => {
|
|
9
10
|
await fs.ensureDir(testDir);
|
|
10
11
|
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @since v2.14.0
|
|
13
13
|
*/
|
|
14
14
|
import { Gate, GateContext } from './base.js';
|
|
15
|
-
import { Failure } from '../types/index.js';
|
|
15
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
16
16
|
export interface AgentRegistration {
|
|
17
17
|
agentId: string;
|
|
18
18
|
taskScope: string[];
|
|
@@ -46,5 +46,6 @@ export declare function clearSession(cwd: string): void;
|
|
|
46
46
|
export declare class AgentTeamGate extends Gate {
|
|
47
47
|
private config;
|
|
48
48
|
constructor(config?: AgentTeamConfig);
|
|
49
|
+
protected get provenance(): Provenance;
|
|
49
50
|
run(context: GateContext): Promise<Failure[]>;
|
|
50
51
|
}
|
package/dist/gates/agent-team.js
CHANGED
package/dist/gates/base.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GoldenRecord } from '../services/context-engine.js';
|
|
2
|
-
import { Failure, Severity } from '../types/index.js';
|
|
2
|
+
import { Failure, Severity, Provenance } from '../types/index.js';
|
|
3
3
|
export interface GateContext {
|
|
4
4
|
cwd: string;
|
|
5
5
|
record?: GoldenRecord;
|
|
@@ -11,5 +11,7 @@ export declare abstract class Gate {
|
|
|
11
11
|
readonly title: string;
|
|
12
12
|
constructor(id: string, title: string);
|
|
13
13
|
abstract run(context: GateContext): Promise<Failure[]>;
|
|
14
|
+
/** Default provenance for this gate — subclasses override */
|
|
15
|
+
protected get provenance(): Provenance;
|
|
14
16
|
protected createFailure(details: string, files?: string[], hint?: string, title?: string, line?: number, endLine?: number, severity?: Severity): Failure;
|
|
15
17
|
}
|
package/dist/gates/base.js
CHANGED
|
@@ -5,12 +5,15 @@ export class Gate {
|
|
|
5
5
|
this.id = id;
|
|
6
6
|
this.title = title;
|
|
7
7
|
}
|
|
8
|
+
/** Default provenance for this gate — subclasses override */
|
|
9
|
+
get provenance() { return 'traditional'; }
|
|
8
10
|
createFailure(details, files, hint, title, line, endLine, severity) {
|
|
9
11
|
return {
|
|
10
12
|
id: this.id,
|
|
11
13
|
title: title || this.title,
|
|
12
14
|
details,
|
|
13
15
|
severity: severity || 'medium',
|
|
16
|
+
provenance: this.provenance,
|
|
14
17
|
files,
|
|
15
18
|
line,
|
|
16
19
|
endLine,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @since v2.14.0
|
|
14
14
|
*/
|
|
15
15
|
import { Gate, GateContext } from './base.js';
|
|
16
|
-
import { Failure } from '../types/index.js';
|
|
16
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
17
17
|
export interface CheckpointEntry {
|
|
18
18
|
checkpointId: string;
|
|
19
19
|
timestamp: Date;
|
|
@@ -68,5 +68,6 @@ export declare function clearCheckpointSession(cwd: string): void;
|
|
|
68
68
|
export declare class CheckpointGate extends Gate {
|
|
69
69
|
private config;
|
|
70
70
|
constructor(config?: CheckpointConfig);
|
|
71
|
+
protected get provenance(): Provenance;
|
|
71
72
|
run(context: GateContext): Promise<Failure[]>;
|
|
72
73
|
}
|
package/dist/gates/checkpoint.js
CHANGED
|
@@ -198,6 +198,7 @@ export class CheckpointGate extends Gate {
|
|
|
198
198
|
auto_save_on_failure: config.auto_save_on_failure ?? true,
|
|
199
199
|
};
|
|
200
200
|
}
|
|
201
|
+
get provenance() { return 'governance'; }
|
|
201
202
|
async run(context) {
|
|
202
203
|
if (!this.config.enabled) {
|
|
203
204
|
return [];
|
|
@@ -217,13 +218,13 @@ export class CheckpointGate extends Gate {
|
|
|
217
218
|
// Check 2: Quality threshold
|
|
218
219
|
const lastCheckpoint = session.checkpoints[session.checkpoints.length - 1];
|
|
219
220
|
if (lastCheckpoint.qualityScore < (this.config.quality_threshold ?? 80)) {
|
|
220
|
-
failures.push(this.createFailure(`Quality score ${lastCheckpoint.qualityScore}% is below threshold ${this.config.quality_threshold}%`, lastCheckpoint.filesChanged, 'Review recent changes and address quality issues before continuing', 'Quality Below Threshold'));
|
|
221
|
+
failures.push(this.createFailure(`Quality score ${lastCheckpoint.qualityScore}% is below threshold ${this.config.quality_threshold}%`, lastCheckpoint.filesChanged, 'Review recent changes and address quality issues before continuing', 'Quality Below Threshold', undefined, undefined, 'high'));
|
|
221
222
|
}
|
|
222
223
|
// Check 3: Drift detection
|
|
223
224
|
if (this.config.drift_detection) {
|
|
224
225
|
const { hasDrift, trend } = detectDrift(session.checkpoints);
|
|
225
226
|
if (hasDrift && trend === 'degrading') {
|
|
226
|
-
failures.push(this.createFailure(`Quality drift detected: scores are degrading over time`, undefined, 'Agent performance is declining. Consider pausing and reviewing recent work.', 'Quality Drift Detected'));
|
|
227
|
+
failures.push(this.createFailure(`Quality drift detected: scores are degrading over time`, undefined, 'Agent performance is declining. Consider pausing and reviewing recent work.', 'Quality Drift Detected', undefined, undefined, 'high'));
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
230
|
return failures;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @since v2.16.0
|
|
17
17
|
*/
|
|
18
18
|
import { Gate, GateContext } from './base.js';
|
|
19
|
-
import { Failure } from '../types/index.js';
|
|
19
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
20
20
|
export interface ContextWindowArtifactsConfig {
|
|
21
21
|
enabled?: boolean;
|
|
22
22
|
min_file_lines?: number;
|
|
@@ -26,6 +26,7 @@ export interface ContextWindowArtifactsConfig {
|
|
|
26
26
|
export declare class ContextWindowArtifactsGate extends Gate {
|
|
27
27
|
private config;
|
|
28
28
|
constructor(config?: ContextWindowArtifactsConfig);
|
|
29
|
+
protected get provenance(): Provenance;
|
|
29
30
|
run(context: GateContext): Promise<Failure[]>;
|
|
30
31
|
private analyzeFile;
|
|
31
32
|
private measureHalf;
|
|
@@ -31,6 +31,7 @@ export class ContextWindowArtifactsGate extends Gate {
|
|
|
31
31
|
signals_required: config.signals_required ?? 2,
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
+
get provenance() { return 'ai-drift'; }
|
|
34
35
|
async run(context) {
|
|
35
36
|
if (!this.config.enabled)
|
|
36
37
|
return [];
|
|
@@ -68,8 +69,8 @@ export class ContextWindowArtifactsGate extends Gate {
|
|
|
68
69
|
const bottomMetrics = this.measureHalf(bottomContent);
|
|
69
70
|
const signals = [];
|
|
70
71
|
let degradationScore = 0;
|
|
71
|
-
// Signal 1: Comment density drops
|
|
72
|
-
if (topMetrics.commentDensity > 0) {
|
|
72
|
+
// Signal 1: Comment density drops (use threshold to avoid tiny-denominator noise)
|
|
73
|
+
if (topMetrics.commentDensity > 0.01) {
|
|
73
74
|
const commentRatio = bottomMetrics.commentDensity / topMetrics.commentDensity;
|
|
74
75
|
if (commentRatio < 0.5) {
|
|
75
76
|
signals.push(`Comment density drops ${((1 - commentRatio) * 100).toFixed(0)}% in bottom half`);
|
|
@@ -130,9 +131,11 @@ export class ContextWindowArtifactsGate extends Gate {
|
|
|
130
131
|
measureHalf(content) {
|
|
131
132
|
const lines = content.split('\n');
|
|
132
133
|
const codeLines = lines.filter(l => l.trim() && !l.trim().startsWith('//') && !l.trim().startsWith('#') && !l.trim().startsWith('*'));
|
|
134
|
+
// Only count inline comments (//), not JSDoc/block comments (/** ... */ or * ...)
|
|
135
|
+
// JSDoc tends to cluster at file top, skewing "degradation" unfairly
|
|
133
136
|
const commentLines = lines.filter(l => {
|
|
134
137
|
const trimmed = l.trim();
|
|
135
|
-
return trimmed.startsWith('//') || trimmed.startsWith('#')
|
|
138
|
+
return trimmed.startsWith('//') || trimmed.startsWith('#');
|
|
136
139
|
});
|
|
137
140
|
// Comment density
|
|
138
141
|
const commentDensity = codeLines.length > 0 ? commentLines.length / codeLines.length : 0;
|
package/dist/gates/context.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Gate, GateContext } from './base.js';
|
|
2
|
-
import { Failure, Gates } from '../types/index.js';
|
|
2
|
+
import { Failure, Gates, Provenance } from '../types/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Extended Context Configuration (v2.14+)
|
|
5
5
|
* For 1M token frontier models like Opus 4.6
|
|
@@ -17,6 +17,7 @@ export declare class ContextGate extends Gate {
|
|
|
17
17
|
private config;
|
|
18
18
|
private extendedConfig;
|
|
19
19
|
constructor(config: Gates);
|
|
20
|
+
protected get provenance(): Provenance;
|
|
20
21
|
run(context: GateContext): Promise<Failure[]>;
|
|
21
22
|
private checkEnvDrift;
|
|
22
23
|
/**
|
package/dist/gates/context.js
CHANGED
package/dist/gates/coverage.js
CHANGED
|
@@ -35,7 +35,9 @@ export class CoverageGate extends Gate {
|
|
|
35
35
|
title: `Low coverage for high-risk file: ${file}`,
|
|
36
36
|
details: `Current coverage: ${coverage.toFixed(2)}%. Required: ${threshold}% due to structural risk.`,
|
|
37
37
|
files: [file],
|
|
38
|
-
hint: `Add dynamic tests to cover complex logical branches in this file
|
|
38
|
+
hint: `Add dynamic tests to cover complex logical branches in this file.`,
|
|
39
|
+
severity: 'medium',
|
|
40
|
+
provenance: 'traditional'
|
|
39
41
|
});
|
|
40
42
|
}
|
|
41
43
|
}
|
package/dist/gates/dependency.js
CHANGED
|
@@ -25,7 +25,7 @@ export class DependencyGate extends Gate {
|
|
|
25
25
|
};
|
|
26
26
|
for (const dep of forbidden) {
|
|
27
27
|
if (allDeps[dep]) {
|
|
28
|
-
failures.push(this.createFailure(`The package '${dep}' is forbidden by project standards.`, ['package.json'], `Remove '${dep}' from package.json and use approved alternatives.`, 'Forbidden Dependency'));
|
|
28
|
+
failures.push(this.createFailure(`The package '${dep}' is forbidden by project standards.`, ['package.json'], `Remove '${dep}' from package.json and use approved alternatives.`, 'Forbidden Dependency', undefined, undefined, 'medium'));
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -37,7 +37,7 @@ export class DependencyGate extends Gate {
|
|
|
37
37
|
const content = await fs.readFile(reqPath, 'utf-8');
|
|
38
38
|
for (const dep of forbidden) {
|
|
39
39
|
if (new RegExp(`^${dep}([=<>! ]|$)`, 'm').test(content)) {
|
|
40
|
-
failures.push(this.createFailure(`The Python package '${dep}' is forbidden.`, ['requirements.txt'], `Remove '${dep}' from requirements.txt.`, 'Forbidden Dependency'));
|
|
40
|
+
failures.push(this.createFailure(`The Python package '${dep}' is forbidden.`, ['requirements.txt'], `Remove '${dep}' from requirements.txt.`, 'Forbidden Dependency', undefined, undefined, 'medium'));
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -46,7 +46,7 @@ export class DependencyGate extends Gate {
|
|
|
46
46
|
const content = await fs.readFile(pyprojPath, 'utf-8');
|
|
47
47
|
for (const dep of forbidden) {
|
|
48
48
|
if (new RegExp(`^${dep}\\s*=`, 'm').test(content)) {
|
|
49
|
-
failures.push(this.createFailure(`The Python package '${dep}' is forbidden in pyproject.toml.`, ['pyproject.toml'], `Remove '${dep}' from pyproject.toml dependencies.`, 'Forbidden Dependency'));
|
|
49
|
+
failures.push(this.createFailure(`The Python package '${dep}' is forbidden in pyproject.toml.`, ['pyproject.toml'], `Remove '${dep}' from pyproject.toml dependencies.`, 'Forbidden Dependency', undefined, undefined, 'medium'));
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -56,7 +56,7 @@ export class DependencyGate extends Gate {
|
|
|
56
56
|
const content = await fs.readFile(goModPath, 'utf-8');
|
|
57
57
|
for (const dep of forbidden) {
|
|
58
58
|
if (content.includes(dep)) {
|
|
59
|
-
failures.push(this.createFailure(`The Go module '${dep}' is forbidden.`, ['go.mod'], `Remove '${dep}' from go.mod.`, 'Forbidden Dependency'));
|
|
59
|
+
failures.push(this.createFailure(`The Go module '${dep}' is forbidden.`, ['go.mod'], `Remove '${dep}' from go.mod.`, 'Forbidden Dependency', undefined, undefined, 'medium'));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -66,7 +66,7 @@ export class DependencyGate extends Gate {
|
|
|
66
66
|
const content = await fs.readFile(pomPath, 'utf-8');
|
|
67
67
|
for (const dep of forbidden) {
|
|
68
68
|
if (content.includes(`<artifactId>${dep}</artifactId>`)) {
|
|
69
|
-
failures.push(this.createFailure(`The Java artifact '${dep}' is forbidden.`, ['pom.xml'], `Remove '${dep}' from pom.xml.`, 'Forbidden Dependency'));
|
|
69
|
+
failures.push(this.createFailure(`The Java artifact '${dep}' is forbidden.`, ['pom.xml'], `Remove '${dep}' from pom.xml.`, 'Forbidden Dependency', undefined, undefined, 'medium'));
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @since v2.16.0
|
|
14
14
|
*/
|
|
15
15
|
import { Gate, GateContext } from './base.js';
|
|
16
|
-
import { Failure } from '../types/index.js';
|
|
16
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
17
17
|
export interface DuplicationDriftConfig {
|
|
18
18
|
enabled?: boolean;
|
|
19
19
|
similarity_threshold?: number;
|
|
@@ -22,6 +22,7 @@ export interface DuplicationDriftConfig {
|
|
|
22
22
|
export declare class DuplicationDriftGate extends Gate {
|
|
23
23
|
private config;
|
|
24
24
|
constructor(config?: DuplicationDriftConfig);
|
|
25
|
+
protected get provenance(): Provenance;
|
|
25
26
|
run(context: GateContext): Promise<Failure[]>;
|
|
26
27
|
private extractJSFunctions;
|
|
27
28
|
private extractPyFunctions;
|
|
@@ -27,6 +27,7 @@ export class DuplicationDriftGate extends Gate {
|
|
|
27
27
|
min_body_lines: config.min_body_lines ?? 5,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
get provenance() { return 'ai-drift'; }
|
|
30
31
|
async run(context) {
|
|
31
32
|
if (!this.config.enabled)
|
|
32
33
|
return [];
|
|
@@ -157,8 +158,10 @@ export class DuplicationDriftGate extends Gate {
|
|
|
157
158
|
.replace(/\/\/.*/g, '') // strip single-line comments
|
|
158
159
|
.replace(/\/\*[\s\S]*?\*\//g, '') // strip multi-line comments
|
|
159
160
|
.replace(/#.*/g, '') // strip Python comments
|
|
161
|
+
.replace(/`[^`]*`/g, '"STR"') // normalize template literals to placeholder
|
|
162
|
+
.replace(/\basync\s+/g, '') // normalize async modifier
|
|
160
163
|
.replace(/\s+/g, ' ') // collapse whitespace
|
|
161
|
-
.replace(/['"
|
|
164
|
+
.replace(/['"]/g, '"') // normalize single/double quotes (NOT backticks)
|
|
162
165
|
.trim();
|
|
163
166
|
}
|
|
164
167
|
hash(text) {
|
|
@@ -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;
|
|
@@ -11,16 +11,20 @@
|
|
|
11
11
|
* 2. For relative imports: verify the target file exists
|
|
12
12
|
* 3. For package imports: verify the package exists in node_modules or package.json
|
|
13
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#
|
|
14
18
|
*
|
|
15
19
|
* @since v2.16.0
|
|
16
20
|
*/
|
|
17
21
|
import { Gate, GateContext } from './base.js';
|
|
18
|
-
import { Failure } from '../types/index.js';
|
|
22
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
19
23
|
export interface HallucinatedImport {
|
|
20
24
|
file: string;
|
|
21
25
|
line: number;
|
|
22
26
|
importPath: string;
|
|
23
|
-
type: 'relative' | 'package' | 'python';
|
|
27
|
+
type: 'relative' | 'package' | 'python' | 'go' | 'ruby' | 'csharp';
|
|
24
28
|
reason: string;
|
|
25
29
|
}
|
|
26
30
|
export interface HallucinatedImportsConfig {
|
|
@@ -32,6 +36,7 @@ export interface HallucinatedImportsConfig {
|
|
|
32
36
|
export declare class HallucinatedImportsGate extends Gate {
|
|
33
37
|
private config;
|
|
34
38
|
constructor(config?: HallucinatedImportsConfig);
|
|
39
|
+
protected get provenance(): Provenance;
|
|
35
40
|
run(context: GateContext): Promise<Failure[]>;
|
|
36
41
|
private checkJSImports;
|
|
37
42
|
private checkPyImports;
|
|
@@ -40,5 +45,19 @@ export declare class HallucinatedImportsGate extends Gate {
|
|
|
40
45
|
private shouldIgnore;
|
|
41
46
|
private isNodeBuiltin;
|
|
42
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;
|
|
43
62
|
private loadPackageJson;
|
|
44
63
|
}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* 2. For relative imports: verify the target file exists
|
|
12
12
|
* 3. For package imports: verify the package exists in node_modules or package.json
|
|
13
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#
|
|
14
18
|
*
|
|
15
19
|
* @since v2.16.0
|
|
16
20
|
*/
|
|
@@ -33,6 +37,7 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
33
37
|
],
|
|
34
38
|
};
|
|
35
39
|
}
|
|
40
|
+
get provenance() { return 'ai-drift'; }
|
|
36
41
|
async run(context) {
|
|
37
42
|
if (!this.config.enabled)
|
|
38
43
|
return [];
|
|
@@ -40,8 +45,9 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
40
45
|
const hallucinated = [];
|
|
41
46
|
const files = await FileScanner.findFiles({
|
|
42
47
|
cwd: context.cwd,
|
|
43
|
-
patterns: ['**/*.{ts,js,tsx,jsx,py}'],
|
|
44
|
-
ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/build/**',
|
|
48
|
+
patterns: ['**/*.{ts,js,tsx,jsx,py,go,rb,cs}'],
|
|
49
|
+
ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/build/**',
|
|
50
|
+
'**/.venv/**', '**/venv/**', '**/vendor/**', '**/bin/Debug/**', '**/bin/Release/**', '**/obj/**'],
|
|
45
51
|
});
|
|
46
52
|
Logger.info(`Hallucinated Imports: Scanning ${files.length} files`);
|
|
47
53
|
// Build lookup sets for fast resolution
|
|
@@ -65,6 +71,15 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
65
71
|
else if (ext === '.py') {
|
|
66
72
|
await this.checkPyImports(content, file, context.cwd, projectFiles, hallucinated);
|
|
67
73
|
}
|
|
74
|
+
else if (ext === '.go') {
|
|
75
|
+
this.checkGoImports(content, file, context.cwd, projectFiles, hallucinated);
|
|
76
|
+
}
|
|
77
|
+
else if (ext === '.rb') {
|
|
78
|
+
this.checkRubyImports(content, file, projectFiles, hallucinated);
|
|
79
|
+
}
|
|
80
|
+
else if (ext === '.cs') {
|
|
81
|
+
this.checkCSharpImports(content, file, projectFiles, hallucinated);
|
|
82
|
+
}
|
|
68
83
|
}
|
|
69
84
|
catch (e) { }
|
|
70
85
|
}
|
|
@@ -279,6 +294,105 @@ export class HallucinatedImportsGate extends Gate {
|
|
|
279
294
|
]);
|
|
280
295
|
return stdlibs.has(topLevel);
|
|
281
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Check Go imports — verify relative/project package paths exist
|
|
299
|
+
* Go stdlib packages are skipped; only project-relative imports are checked
|
|
300
|
+
*/
|
|
301
|
+
checkGoImports(content, file, cwd, projectFiles, hallucinated) {
|
|
302
|
+
const lines = content.split('\n');
|
|
303
|
+
let inImportBlock = false;
|
|
304
|
+
for (let i = 0; i < lines.length; i++) {
|
|
305
|
+
const line = lines[i].trim();
|
|
306
|
+
// Detect import block: import ( ... )
|
|
307
|
+
if (/^import\s*\(/.test(line)) {
|
|
308
|
+
inImportBlock = true;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (inImportBlock && line === ')') {
|
|
312
|
+
inImportBlock = false;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
// Single import: import "path"
|
|
316
|
+
const singleMatch = line.match(/^import\s+"([^"]+)"/);
|
|
317
|
+
const blockMatch = inImportBlock ? line.match(/^\s*"([^"]+)"/) : null;
|
|
318
|
+
const importPath = singleMatch?.[1] || blockMatch?.[1];
|
|
319
|
+
if (!importPath)
|
|
320
|
+
continue;
|
|
321
|
+
// Skip Go stdlib (no dots in path = stdlib or well-known)
|
|
322
|
+
if (!importPath.includes('.') && !importPath.includes('/'))
|
|
323
|
+
continue;
|
|
324
|
+
// Project-relative imports (contain module path from go.mod)
|
|
325
|
+
// We only flag imports that look like project paths but don't resolve
|
|
326
|
+
if (importPath.includes('/') && !importPath.startsWith('github.com') && !importPath.startsWith('golang.org')) {
|
|
327
|
+
// Check if the path maps to a directory in the project
|
|
328
|
+
const dirPath = importPath.split('/').slice(-2).join('/');
|
|
329
|
+
const hasMatchingFile = [...projectFiles].some(f => f.includes(dirPath));
|
|
330
|
+
if (!hasMatchingFile) {
|
|
331
|
+
hallucinated.push({
|
|
332
|
+
file, line: i + 1, importPath, type: 'go',
|
|
333
|
+
reason: `Go import '${importPath}' — package path not found in project`,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Check Ruby imports — verify require_relative paths exist
|
|
341
|
+
*/
|
|
342
|
+
checkRubyImports(content, file, projectFiles, hallucinated) {
|
|
343
|
+
const lines = content.split('\n');
|
|
344
|
+
for (let i = 0; i < lines.length; i++) {
|
|
345
|
+
const line = lines[i].trim();
|
|
346
|
+
// require_relative 'path' — should resolve to a real file
|
|
347
|
+
const relMatch = line.match(/require_relative\s+['"]([^'"]+)['"]/);
|
|
348
|
+
if (relMatch) {
|
|
349
|
+
const reqPath = relMatch[1];
|
|
350
|
+
const dir = path.dirname(file);
|
|
351
|
+
const resolved = path.join(dir, reqPath).replace(/\\/g, '/');
|
|
352
|
+
const candidates = [resolved + '.rb', resolved];
|
|
353
|
+
if (!candidates.some(c => projectFiles.has(c))) {
|
|
354
|
+
hallucinated.push({
|
|
355
|
+
file, line: i + 1, importPath: reqPath, type: 'ruby',
|
|
356
|
+
reason: `require_relative '${reqPath}' — file not found in project`,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Check C# imports — verify relative using paths match project namespaces
|
|
364
|
+
* (C# uses namespaces, not file paths — we check for obviously wrong namespaces)
|
|
365
|
+
*/
|
|
366
|
+
checkCSharpImports(content, file, projectFiles, hallucinated) {
|
|
367
|
+
const lines = content.split('\n');
|
|
368
|
+
for (let i = 0; i < lines.length; i++) {
|
|
369
|
+
const line = lines[i].trim();
|
|
370
|
+
// using ProjectName.Something — check if namespace maps to project files
|
|
371
|
+
const usingMatch = line.match(/^using\s+([\w.]+)\s*;/);
|
|
372
|
+
if (!usingMatch)
|
|
373
|
+
continue;
|
|
374
|
+
const namespace = usingMatch[1];
|
|
375
|
+
// Skip System.* and Microsoft.* and common framework namespaces
|
|
376
|
+
if (/^(?:System|Microsoft|Newtonsoft|NUnit|Xunit|Moq|AutoMapper)\b/.test(namespace))
|
|
377
|
+
continue;
|
|
378
|
+
// Check if the namespace maps to any .cs file path in the project
|
|
379
|
+
const nsPath = namespace.replace(/\./g, '/');
|
|
380
|
+
const hasMatch = [...projectFiles].some(f => f.endsWith('.cs') && (f.includes(nsPath) || f.includes(namespace.split('.')[0])));
|
|
381
|
+
// Only flag if the project has NO files that could match this namespace
|
|
382
|
+
if (!hasMatch && namespace.includes('.')) {
|
|
383
|
+
// Could be a NuGet package — we can't verify without .csproj parsing
|
|
384
|
+
// Only flag obvious project-relative namespaces
|
|
385
|
+
const topLevel = namespace.split('.')[0];
|
|
386
|
+
const hasProjectFiles = [...projectFiles].some(f => f.endsWith('.cs') && f.includes(topLevel));
|
|
387
|
+
if (hasProjectFiles) {
|
|
388
|
+
hallucinated.push({
|
|
389
|
+
file, line: i + 1, importPath: namespace, type: 'csharp',
|
|
390
|
+
reason: `Namespace '${namespace}' — no matching files found in project`,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
282
396
|
async loadPackageJson(cwd) {
|
|
283
397
|
try {
|
|
284
398
|
const pkgPath = path.join(cwd, 'package.json');
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* @since v2.16.0
|
|
21
21
|
*/
|
|
22
22
|
import { Gate, GateContext } from './base.js';
|
|
23
|
-
import { Failure } from '../types/index.js';
|
|
23
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
24
24
|
export interface InconsistentErrorHandlingConfig {
|
|
25
25
|
enabled?: boolean;
|
|
26
26
|
max_strategies_per_type?: number;
|
|
@@ -30,6 +30,7 @@ export interface InconsistentErrorHandlingConfig {
|
|
|
30
30
|
export declare class InconsistentErrorHandlingGate extends Gate {
|
|
31
31
|
private config;
|
|
32
32
|
constructor(config?: InconsistentErrorHandlingConfig);
|
|
33
|
+
protected get provenance(): Provenance;
|
|
33
34
|
run(context: GateContext): Promise<Failure[]>;
|
|
34
35
|
private extractErrorHandlers;
|
|
35
36
|
private classifyStrategy;
|