@rigour-labs/core 2.21.1 → 2.22.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.
@@ -1,5 +1,5 @@
1
1
  import { Gate } from './base.js';
2
- import { Failure, Config, Report, Status } from '../types/index.js';
2
+ import { Failure, Config, Report, Status, Severity, SEVERITY_WEIGHTS } from '../types/index.js';
3
3
  import { FileGate } from './file.js';
4
4
  import { ContentGate } from './content.js';
5
5
  import { StructureGate } from './structure.js';
@@ -14,6 +14,10 @@ import { RetryLoopBreakerGate } from './retry-loop-breaker.js';
14
14
  import { AgentTeamGate } from './agent-team.js';
15
15
  import { CheckpointGate } from './checkpoint.js';
16
16
  import { SecurityPatternsGate } from './security-patterns.js';
17
+ import { DuplicationDriftGate } from './duplication-drift.js';
18
+ import { HallucinatedImportsGate } from './hallucinated-imports.js';
19
+ import { InconsistentErrorHandlingGate } from './inconsistent-error-handling.js';
20
+ import { ContextWindowArtifactsGate } from './context-window-artifacts.js';
17
21
  import { execa } from 'execa';
18
22
  import { Logger } from '../utils/logger.js';
19
23
 
@@ -66,6 +70,23 @@ export class GateRunner {
66
70
  this.gates.push(new SecurityPatternsGate(this.config.gates.security));
67
71
  }
68
72
 
73
+ // v2.16+ AI-Native Drift Detection Gates (enabled by default)
74
+ if (this.config.gates.duplication_drift?.enabled !== false) {
75
+ this.gates.push(new DuplicationDriftGate(this.config.gates.duplication_drift));
76
+ }
77
+
78
+ if (this.config.gates.hallucinated_imports?.enabled !== false) {
79
+ this.gates.push(new HallucinatedImportsGate(this.config.gates.hallucinated_imports));
80
+ }
81
+
82
+ if (this.config.gates.inconsistent_error_handling?.enabled !== false) {
83
+ this.gates.push(new InconsistentErrorHandlingGate(this.config.gates.inconsistent_error_handling));
84
+ }
85
+
86
+ if (this.config.gates.context_window_artifacts?.enabled !== false) {
87
+ this.gates.push(new ContextWindowArtifactsGate(this.config.gates.context_window_artifacts));
88
+ }
89
+
69
90
  // Environment Alignment Gate (Should be prioritized)
70
91
  if (this.config.gates.environment?.enabled) {
71
92
  this.gates.unshift(new EnvironmentGate(this.config.gates));
@@ -141,7 +162,17 @@ export class GateRunner {
141
162
  }
142
163
 
143
164
  const status: Status = failures.length > 0 ? 'FAIL' : 'PASS';
144
- const score = Math.max(0, 100 - (failures.length * 5)); // Basic SME scoring logic
165
+
166
+ // Severity-weighted scoring: each failure deducts based on its severity
167
+ // critical=20, high=10, medium=5, low=2, info=0
168
+ const severityBreakdown: Record<string, number> = {};
169
+ let totalDeduction = 0;
170
+ for (const f of failures) {
171
+ const sev = (f.severity || 'medium') as Severity;
172
+ severityBreakdown[sev] = (severityBreakdown[sev] || 0) + 1;
173
+ totalDeduction += SEVERITY_WEIGHTS[sev] ?? 5;
174
+ }
175
+ const score = Math.max(0, 100 - totalDeduction);
145
176
 
146
177
  return {
147
178
  status,
@@ -150,6 +181,7 @@ export class GateRunner {
150
181
  stats: {
151
182
  duration_ms: Date.now() - start,
152
183
  score,
184
+ severity_breakdown: severityBreakdown,
153
185
  },
154
186
  };
155
187
  }
@@ -236,7 +236,8 @@ export class SecurityPatternsGate extends Gate {
236
236
  `Found: "${vuln.match.slice(0, 60)}..." - Use parameterized queries/sanitization.`,
237
237
  `Security: ${vuln.type.replace('_', ' ').toUpperCase()}`,
238
238
  vuln.line,
239
- vuln.line
239
+ vuln.line,
240
+ vuln.severity
240
241
  ));
241
242
  }
242
243
  }
@@ -109,7 +109,8 @@ export class PatternIndexer {
109
109
 
110
110
  // Generate embeddings in parallel batches if enabled
111
111
  if (this.config.useEmbeddings && patterns.length > 0) {
112
- console.log(`Generating embeddings for ${patterns.length} patterns...`);
112
+ // Use stderr to avoid contaminating JSON output on stdout
113
+ process.stderr.write(`Generating embeddings for ${patterns.length} patterns...\n`);
113
114
  for (let i = 0; i < patterns.length; i += BATCH_SIZE) {
114
115
  const batch = patterns.slice(i, i + BATCH_SIZE);
115
116
  await Promise.all(batch.map(async (pattern) => {
@@ -303,6 +303,32 @@ export const UNIVERSAL_CONFIG: Config = {
303
303
  'prefer-const': false,
304
304
  },
305
305
  },
306
+ duplication_drift: {
307
+ enabled: true,
308
+ similarity_threshold: 0.8,
309
+ min_body_lines: 5,
310
+ },
311
+ hallucinated_imports: {
312
+ enabled: true,
313
+ check_relative: true,
314
+ check_packages: true,
315
+ ignore_patterns: [
316
+ '\\.css$', '\\.scss$', '\\.less$', '\\.svg$', '\\.png$', '\\.jpg$',
317
+ '\\.json$', '\\.wasm$', '\\.graphql$', '\\.gql$',
318
+ ],
319
+ },
320
+ inconsistent_error_handling: {
321
+ enabled: true,
322
+ max_strategies_per_type: 2,
323
+ min_occurrences: 3,
324
+ ignore_empty_catches: false,
325
+ },
326
+ context_window_artifacts: {
327
+ enabled: true,
328
+ min_file_lines: 100,
329
+ degradation_threshold: 0.4,
330
+ signals_required: 2,
331
+ },
306
332
  },
307
333
  output: {
308
334
  report_path: 'rigour-report.json',
@@ -103,6 +103,33 @@ export const GatesSchema = z.object({
103
103
  auto_detect_tier: z.boolean().optional().default(true),
104
104
  forced_tier: z.enum(['hobby', 'startup', 'enterprise']).optional(),
105
105
  }).optional().default({}),
106
+ // v2.16+ AI-Native Drift Detection Gates
107
+ duplication_drift: z.object({
108
+ enabled: z.boolean().optional().default(true),
109
+ similarity_threshold: z.number().min(0).max(1).optional().default(0.8),
110
+ min_body_lines: z.number().optional().default(5),
111
+ }).optional().default({}),
112
+ hallucinated_imports: z.object({
113
+ enabled: z.boolean().optional().default(true),
114
+ check_relative: z.boolean().optional().default(true),
115
+ check_packages: z.boolean().optional().default(true),
116
+ ignore_patterns: z.array(z.string()).optional().default([
117
+ '\\.css$', '\\.scss$', '\\.less$', '\\.svg$', '\\.png$', '\\.jpg$',
118
+ '\\.json$', '\\.wasm$', '\\.graphql$', '\\.gql$',
119
+ ]),
120
+ }).optional().default({}),
121
+ inconsistent_error_handling: z.object({
122
+ enabled: z.boolean().optional().default(true),
123
+ max_strategies_per_type: z.number().optional().default(2),
124
+ min_occurrences: z.number().optional().default(3),
125
+ ignore_empty_catches: z.boolean().optional().default(false),
126
+ }).optional().default({}),
127
+ context_window_artifacts: z.object({
128
+ enabled: z.boolean().optional().default(true),
129
+ min_file_lines: z.number().optional().default(100),
130
+ degradation_threshold: z.number().min(0).max(1).optional().default(0.4),
131
+ signals_required: z.number().optional().default(2),
132
+ }).optional().default({}),
106
133
  });
107
134
 
108
135
  export const CommandsSchema = z.object({
@@ -136,10 +163,23 @@ export type RawConfig = z.input<typeof ConfigSchema>;
136
163
  export const StatusSchema = z.enum(['PASS', 'FAIL', 'SKIP', 'ERROR']);
137
164
  export type Status = z.infer<typeof StatusSchema>;
138
165
 
166
+ export const SeveritySchema = z.enum(['critical', 'high', 'medium', 'low', 'info']);
167
+ export type Severity = z.infer<typeof SeveritySchema>;
168
+
169
+ /** Severity weights for score calculation */
170
+ export const SEVERITY_WEIGHTS: Record<Severity, number> = {
171
+ critical: 20,
172
+ high: 10,
173
+ medium: 5,
174
+ low: 2,
175
+ info: 0,
176
+ };
177
+
139
178
  export const FailureSchema = z.object({
140
179
  id: z.string(),
141
180
  title: z.string(),
142
181
  details: z.string(),
182
+ severity: SeveritySchema.optional(),
143
183
  files: z.array(z.string()).optional(),
144
184
  line: z.number().optional(),
145
185
  endLine: z.number().optional(),
@@ -154,6 +194,7 @@ export const ReportSchema = z.object({
154
194
  stats: z.object({
155
195
  duration_ms: z.number(),
156
196
  score: z.number().optional(),
197
+ severity_breakdown: z.record(z.number()).optional(),
157
198
  }),
158
199
  });
159
200
  export type Report = z.infer<typeof ReportSchema>;