@rigour-labs/core 2.22.0 → 3.0.1

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 (117) 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 +3 -1
  7. package/dist/gates/base.js +3 -0
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/context-window-artifacts.d.ts +2 -1
  11. package/dist/gates/context-window-artifacts.js +6 -3
  12. package/dist/gates/context.d.ts +2 -1
  13. package/dist/gates/context.js +1 -0
  14. package/dist/gates/coverage.js +3 -1
  15. package/dist/gates/dependency.js +5 -5
  16. package/dist/gates/duplication-drift.d.ts +2 -1
  17. package/dist/gates/duplication-drift.js +4 -1
  18. package/dist/gates/environment.js +4 -4
  19. package/dist/gates/hallucinated-imports.d.ts +21 -2
  20. package/dist/gates/hallucinated-imports.js +116 -2
  21. package/dist/gates/inconsistent-error-handling.d.ts +2 -1
  22. package/dist/gates/inconsistent-error-handling.js +21 -7
  23. package/dist/gates/promise-safety.d.ts +68 -0
  24. package/dist/gates/promise-safety.js +509 -0
  25. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  26. package/dist/gates/retry-loop-breaker.js +2 -1
  27. package/dist/gates/runner.js +34 -1
  28. package/dist/gates/safety.d.ts +2 -1
  29. package/dist/gates/safety.js +2 -1
  30. package/dist/gates/security-patterns-owasp.test.d.ts +1 -0
  31. package/dist/gates/security-patterns-owasp.test.js +171 -0
  32. package/dist/gates/security-patterns.d.ts +6 -1
  33. package/dist/gates/security-patterns.js +101 -0
  34. package/dist/gates/structure.js +1 -1
  35. package/dist/hooks/checker.d.ts +23 -0
  36. package/dist/hooks/checker.js +222 -0
  37. package/dist/hooks/checker.test.d.ts +1 -0
  38. package/dist/hooks/checker.test.js +132 -0
  39. package/dist/hooks/index.d.ts +9 -0
  40. package/dist/hooks/index.js +8 -0
  41. package/dist/hooks/standalone-checker.d.ts +15 -0
  42. package/dist/hooks/standalone-checker.js +106 -0
  43. package/dist/hooks/templates.d.ts +22 -0
  44. package/dist/hooks/templates.js +232 -0
  45. package/dist/hooks/types.d.ts +34 -0
  46. package/dist/hooks/types.js +21 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +2 -0
  49. package/dist/services/fix-packet-service.d.ts +0 -1
  50. package/dist/services/fix-packet-service.js +9 -14
  51. package/dist/services/score-history.d.ts +54 -0
  52. package/dist/services/score-history.js +122 -0
  53. package/dist/templates/index.js +176 -0
  54. package/dist/types/fix-packet.d.ts +5 -5
  55. package/dist/types/fix-packet.js +1 -1
  56. package/dist/types/index.d.ts +207 -0
  57. package/dist/types/index.js +32 -0
  58. package/package.json +21 -1
  59. package/src/context.test.ts +0 -256
  60. package/src/discovery.test.ts +0 -88
  61. package/src/discovery.ts +0 -112
  62. package/src/environment.test.ts +0 -115
  63. package/src/gates/agent-team.test.ts +0 -134
  64. package/src/gates/agent-team.ts +0 -210
  65. package/src/gates/ast-handlers/base.ts +0 -13
  66. package/src/gates/ast-handlers/python.ts +0 -145
  67. package/src/gates/ast-handlers/python_parser.py +0 -181
  68. package/src/gates/ast-handlers/typescript.ts +0 -264
  69. package/src/gates/ast-handlers/universal.ts +0 -184
  70. package/src/gates/ast.ts +0 -54
  71. package/src/gates/base.ts +0 -28
  72. package/src/gates/checkpoint.test.ts +0 -135
  73. package/src/gates/checkpoint.ts +0 -311
  74. package/src/gates/content.ts +0 -51
  75. package/src/gates/context-window-artifacts.ts +0 -277
  76. package/src/gates/context.ts +0 -270
  77. package/src/gates/coverage.ts +0 -74
  78. package/src/gates/dependency.ts +0 -108
  79. package/src/gates/duplication-drift.ts +0 -231
  80. package/src/gates/environment.ts +0 -94
  81. package/src/gates/file.ts +0 -46
  82. package/src/gates/hallucinated-imports.ts +0 -361
  83. package/src/gates/inconsistent-error-handling.ts +0 -254
  84. package/src/gates/retry-loop-breaker.ts +0 -151
  85. package/src/gates/runner.ts +0 -188
  86. package/src/gates/safety.ts +0 -56
  87. package/src/gates/security-patterns.test.ts +0 -162
  88. package/src/gates/security-patterns.ts +0 -306
  89. package/src/gates/structure.ts +0 -36
  90. package/src/index.ts +0 -13
  91. package/src/pattern-index/embeddings.ts +0 -84
  92. package/src/pattern-index/index.ts +0 -59
  93. package/src/pattern-index/indexer.test.ts +0 -276
  94. package/src/pattern-index/indexer.ts +0 -1023
  95. package/src/pattern-index/matcher.test.ts +0 -293
  96. package/src/pattern-index/matcher.ts +0 -493
  97. package/src/pattern-index/overrides.ts +0 -235
  98. package/src/pattern-index/security.ts +0 -151
  99. package/src/pattern-index/staleness.test.ts +0 -313
  100. package/src/pattern-index/staleness.ts +0 -568
  101. package/src/pattern-index/types.ts +0 -339
  102. package/src/safety.test.ts +0 -53
  103. package/src/services/adaptive-thresholds.test.ts +0 -189
  104. package/src/services/adaptive-thresholds.ts +0 -275
  105. package/src/services/context-engine.ts +0 -104
  106. package/src/services/fix-packet-service.ts +0 -42
  107. package/src/services/state-service.ts +0 -138
  108. package/src/smoke.test.ts +0 -18
  109. package/src/templates/index.ts +0 -338
  110. package/src/types/fix-packet.ts +0 -32
  111. package/src/types/index.ts +0 -200
  112. package/src/utils/logger.ts +0 -43
  113. package/src/utils/scanner.test.ts +0 -37
  114. package/src/utils/scanner.ts +0 -43
  115. package/tsconfig.json +0 -10
  116. package/vitest.config.ts +0 -7
  117. package/vitest.setup.ts +0 -30
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @rigour-labs/core
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@rigour-labs/core?color=cyan)](https://www.npmjs.com/package/@rigour-labs/core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)
@@ -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 { fileURLToPath } from 'url';
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
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);
@@ -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(process.cwd(), 'temp-test-env');
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
  }
@@ -120,6 +120,7 @@ export class AgentTeamGate extends Gate {
120
120
  task_ownership: config.task_ownership ?? 'strict',
121
121
  };
122
122
  }
123
+ get provenance() { return 'governance'; }
123
124
  async run(context) {
124
125
  if (!this.config.enabled) {
125
126
  return [];
@@ -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
  }
@@ -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
  }
@@ -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('#') || 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;
@@ -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
  /**
@@ -18,6 +18,7 @@ export class ContextGate extends Gate {
18
18
  max_cross_file_depth: 50,
19
19
  };
20
20
  }
21
+ get provenance() { return 'ai-drift'; }
21
22
  async run(context) {
22
23
  const failures = [];
23
24
  const record = context.record;
@@ -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
  }
@@ -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(/['"`]/g, '"') // normalize quotes
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/**', '**/.venv/**'],
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;