@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.
- 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-owasp.test.d.ts +1 -0
- package/dist/gates/security-patterns-owasp.test.js +171 -0
- package/dist/gates/security-patterns.d.ts +6 -1
- package/dist/gates/security-patterns.js +101 -0
- package/dist/gates/structure.js +1 -1
- package/dist/hooks/checker.d.ts +23 -0
- package/dist/hooks/checker.js +222 -0
- package/dist/hooks/checker.test.d.ts +1 -0
- package/dist/hooks/checker.test.js +132 -0
- package/dist/hooks/index.d.ts +9 -0
- package/dist/hooks/index.js +8 -0
- package/dist/hooks/standalone-checker.d.ts +15 -0
- package/dist/hooks/standalone-checker.js +106 -0
- package/dist/hooks/templates.d.ts +22 -0
- package/dist/hooks/templates.js +232 -0
- package/dist/hooks/types.d.ts +34 -0
- package/dist/hooks/types.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -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 +176 -0
- package/dist/types/fix-packet.d.ts +5 -5
- package/dist/types/fix-packet.js +1 -1
- package/dist/types/index.d.ts +207 -0
- package/dist/types/index.js +32 -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
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adaptive Thresholds Service
|
|
3
|
-
*
|
|
4
|
-
* Dynamically adjusts quality gate thresholds based on:
|
|
5
|
-
* - Project maturity (age, commit count, file count)
|
|
6
|
-
* - Historical failure rates
|
|
7
|
-
* - Complexity tier (hobby/startup/enterprise)
|
|
8
|
-
* - Recent trends (improving/degrading)
|
|
9
|
-
*
|
|
10
|
-
* This enables Rigour to be "strict but fair" - new projects get
|
|
11
|
-
* more lenient thresholds while mature codebases are held to higher standards.
|
|
12
|
-
*
|
|
13
|
-
* @since v2.14.0
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import * as fs from 'fs';
|
|
17
|
-
import * as path from 'path';
|
|
18
|
-
import { Logger } from '../utils/logger.js';
|
|
19
|
-
|
|
20
|
-
export type ComplexityTier = 'hobby' | 'startup' | 'enterprise';
|
|
21
|
-
export type QualityTrend = 'improving' | 'stable' | 'degrading';
|
|
22
|
-
|
|
23
|
-
export interface ProjectMetrics {
|
|
24
|
-
fileCount: number;
|
|
25
|
-
commitCount?: number;
|
|
26
|
-
ageInDays?: number;
|
|
27
|
-
testCoverage?: number;
|
|
28
|
-
recentFailureRate?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface AdaptiveConfig {
|
|
32
|
-
enabled?: boolean;
|
|
33
|
-
base_coverage_threshold?: number;
|
|
34
|
-
base_quality_threshold?: number;
|
|
35
|
-
auto_detect_tier?: boolean;
|
|
36
|
-
forced_tier?: ComplexityTier;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface ThresholdAdjustments {
|
|
40
|
-
tier: ComplexityTier;
|
|
41
|
-
trend: QualityTrend;
|
|
42
|
-
coverageThreshold: number;
|
|
43
|
-
qualityThreshold: number;
|
|
44
|
-
securityBlockLevel: 'critical' | 'high' | 'medium' | 'low';
|
|
45
|
-
leniencyFactor: number; // 0.0 = strict, 1.0 = lenient
|
|
46
|
-
reasoning: string[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Historical failure data (persisted to .rigour/adaptive-history.json)
|
|
50
|
-
interface FailureHistory {
|
|
51
|
-
runs: {
|
|
52
|
-
timestamp: string;
|
|
53
|
-
passedGates: number;
|
|
54
|
-
failedGates: number;
|
|
55
|
-
totalFailures: number;
|
|
56
|
-
}[];
|
|
57
|
-
lastUpdated: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let cachedHistory: FailureHistory | null = null;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Load failure history from disk
|
|
64
|
-
*/
|
|
65
|
-
function loadHistory(cwd: string): FailureHistory {
|
|
66
|
-
if (cachedHistory) return cachedHistory;
|
|
67
|
-
|
|
68
|
-
const historyPath = path.join(cwd, '.rigour', 'adaptive-history.json');
|
|
69
|
-
try {
|
|
70
|
-
if (fs.existsSync(historyPath)) {
|
|
71
|
-
cachedHistory = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
|
|
72
|
-
return cachedHistory!;
|
|
73
|
-
}
|
|
74
|
-
} catch (e) {
|
|
75
|
-
Logger.debug('Failed to load adaptive history, starting fresh');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
cachedHistory = { runs: [], lastUpdated: new Date().toISOString() };
|
|
79
|
-
return cachedHistory;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Save failure history to disk
|
|
84
|
-
*/
|
|
85
|
-
function saveHistory(cwd: string, history: FailureHistory): void {
|
|
86
|
-
const rigourDir = path.join(cwd, '.rigour');
|
|
87
|
-
if (!fs.existsSync(rigourDir)) {
|
|
88
|
-
fs.mkdirSync(rigourDir, { recursive: true });
|
|
89
|
-
}
|
|
90
|
-
const historyPath = path.join(rigourDir, 'adaptive-history.json');
|
|
91
|
-
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
|
|
92
|
-
cachedHistory = history;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Record a gate run for historical tracking
|
|
97
|
-
*/
|
|
98
|
-
export function recordGateRun(
|
|
99
|
-
cwd: string,
|
|
100
|
-
passedGates: number,
|
|
101
|
-
failedGates: number,
|
|
102
|
-
totalFailures: number
|
|
103
|
-
): void {
|
|
104
|
-
const history = loadHistory(cwd);
|
|
105
|
-
history.runs.push({
|
|
106
|
-
timestamp: new Date().toISOString(),
|
|
107
|
-
passedGates,
|
|
108
|
-
failedGates,
|
|
109
|
-
totalFailures,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Keep last 100 runs
|
|
113
|
-
if (history.runs.length > 100) {
|
|
114
|
-
history.runs = history.runs.slice(-100);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
history.lastUpdated = new Date().toISOString();
|
|
118
|
-
saveHistory(cwd, history);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Get quality trend from historical data
|
|
123
|
-
*/
|
|
124
|
-
export function getQualityTrend(cwd: string): QualityTrend {
|
|
125
|
-
const history = loadHistory(cwd);
|
|
126
|
-
if (history.runs.length < 5) return 'stable';
|
|
127
|
-
|
|
128
|
-
const recent = history.runs.slice(-10);
|
|
129
|
-
const older = history.runs.slice(-20, -10);
|
|
130
|
-
|
|
131
|
-
if (older.length === 0) return 'stable';
|
|
132
|
-
|
|
133
|
-
const recentFailRate = recent.reduce((sum, r) => sum + r.totalFailures, 0) / recent.length;
|
|
134
|
-
const olderFailRate = older.reduce((sum, r) => sum + r.totalFailures, 0) / older.length;
|
|
135
|
-
|
|
136
|
-
const delta = recentFailRate - olderFailRate;
|
|
137
|
-
|
|
138
|
-
if (delta < -2) return 'improving';
|
|
139
|
-
if (delta > 2) return 'degrading';
|
|
140
|
-
return 'stable';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Detect project complexity tier based on metrics
|
|
145
|
-
*/
|
|
146
|
-
export function detectComplexityTier(metrics: ProjectMetrics): ComplexityTier {
|
|
147
|
-
// Enterprise: Large teams, many files, mature codebase
|
|
148
|
-
if (metrics.fileCount > 500 || (metrics.commitCount && metrics.commitCount > 1000)) {
|
|
149
|
-
return 'enterprise';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Startup: Growing codebase, active development
|
|
153
|
-
if (metrics.fileCount > 50 || (metrics.commitCount && metrics.commitCount > 100)) {
|
|
154
|
-
return 'startup';
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Hobby: Small projects, early stage
|
|
158
|
-
return 'hobby';
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Calculate adaptive thresholds based on project state
|
|
163
|
-
*/
|
|
164
|
-
export function calculateAdaptiveThresholds(
|
|
165
|
-
cwd: string,
|
|
166
|
-
metrics: ProjectMetrics,
|
|
167
|
-
config: AdaptiveConfig = {}
|
|
168
|
-
): ThresholdAdjustments {
|
|
169
|
-
const reasoning: string[] = [];
|
|
170
|
-
|
|
171
|
-
// Determine tier
|
|
172
|
-
const tier = config.forced_tier ??
|
|
173
|
-
(config.auto_detect_tier !== false ? detectComplexityTier(metrics) : 'startup');
|
|
174
|
-
reasoning.push(`Complexity tier: ${tier} (files: ${metrics.fileCount})`);
|
|
175
|
-
|
|
176
|
-
// Get trend
|
|
177
|
-
const trend = getQualityTrend(cwd);
|
|
178
|
-
reasoning.push(`Quality trend: ${trend}`);
|
|
179
|
-
|
|
180
|
-
// Base thresholds
|
|
181
|
-
let coverageThreshold = config.base_coverage_threshold ?? 80;
|
|
182
|
-
let qualityThreshold = config.base_quality_threshold ?? 80;
|
|
183
|
-
let securityBlockLevel: 'critical' | 'high' | 'medium' | 'low' = 'high';
|
|
184
|
-
let leniencyFactor = 0.5;
|
|
185
|
-
|
|
186
|
-
// Adjust by tier
|
|
187
|
-
switch (tier) {
|
|
188
|
-
case 'hobby':
|
|
189
|
-
// Lenient for small/new projects
|
|
190
|
-
coverageThreshold = Math.max(50, coverageThreshold - 30);
|
|
191
|
-
qualityThreshold = Math.max(60, qualityThreshold - 20);
|
|
192
|
-
securityBlockLevel = 'critical'; // Only block on critical
|
|
193
|
-
leniencyFactor = 0.8;
|
|
194
|
-
reasoning.push('Hobby tier: relaxed thresholds, only critical security blocks');
|
|
195
|
-
break;
|
|
196
|
-
|
|
197
|
-
case 'startup':
|
|
198
|
-
// Moderate strictness
|
|
199
|
-
coverageThreshold = Math.max(60, coverageThreshold - 15);
|
|
200
|
-
qualityThreshold = Math.max(70, qualityThreshold - 10);
|
|
201
|
-
securityBlockLevel = 'high';
|
|
202
|
-
leniencyFactor = 0.5;
|
|
203
|
-
reasoning.push('Startup tier: moderate thresholds, high+ security blocks');
|
|
204
|
-
break;
|
|
205
|
-
|
|
206
|
-
case 'enterprise':
|
|
207
|
-
// Strict standards
|
|
208
|
-
coverageThreshold = coverageThreshold;
|
|
209
|
-
qualityThreshold = qualityThreshold;
|
|
210
|
-
securityBlockLevel = 'medium';
|
|
211
|
-
leniencyFactor = 0.2;
|
|
212
|
-
reasoning.push('Enterprise tier: strict thresholds, medium+ security blocks');
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Adjust by trend
|
|
217
|
-
if (trend === 'improving') {
|
|
218
|
-
// Reward improvement with slightly relaxed thresholds
|
|
219
|
-
coverageThreshold = Math.max(50, coverageThreshold - 5);
|
|
220
|
-
qualityThreshold = Math.max(60, qualityThreshold - 5);
|
|
221
|
-
leniencyFactor = Math.min(1, leniencyFactor + 0.1);
|
|
222
|
-
reasoning.push('Improving trend: bonus threshold relaxation (+5%)');
|
|
223
|
-
} else if (trend === 'degrading') {
|
|
224
|
-
// Tighten thresholds to encourage recovery
|
|
225
|
-
coverageThreshold = Math.min(95, coverageThreshold + 5);
|
|
226
|
-
qualityThreshold = Math.min(95, qualityThreshold + 5);
|
|
227
|
-
leniencyFactor = Math.max(0, leniencyFactor - 0.1);
|
|
228
|
-
reasoning.push('Degrading trend: tightened thresholds (-5%)');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Recent failure rate adjustment
|
|
232
|
-
if (metrics.recentFailureRate !== undefined) {
|
|
233
|
-
if (metrics.recentFailureRate > 50) {
|
|
234
|
-
// High failure rate - be more lenient to avoid discouragement
|
|
235
|
-
leniencyFactor = Math.min(1, leniencyFactor + 0.2);
|
|
236
|
-
reasoning.push(`High failure rate (${metrics.recentFailureRate.toFixed(0)}%): increased leniency`);
|
|
237
|
-
} else if (metrics.recentFailureRate < 10) {
|
|
238
|
-
// Low failure rate - team is mature, can handle stricter gates
|
|
239
|
-
leniencyFactor = Math.max(0, leniencyFactor - 0.1);
|
|
240
|
-
reasoning.push(`Low failure rate (${metrics.recentFailureRate.toFixed(0)}%): stricter enforcement`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return {
|
|
245
|
-
tier,
|
|
246
|
-
trend,
|
|
247
|
-
coverageThreshold: Math.round(coverageThreshold),
|
|
248
|
-
qualityThreshold: Math.round(qualityThreshold),
|
|
249
|
-
securityBlockLevel,
|
|
250
|
-
leniencyFactor: Math.round(leniencyFactor * 100) / 100,
|
|
251
|
-
reasoning,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Clear adaptive history (for testing)
|
|
257
|
-
*/
|
|
258
|
-
export function clearAdaptiveHistory(cwd: string): void {
|
|
259
|
-
cachedHistory = null;
|
|
260
|
-
const historyPath = path.join(cwd, '.rigour', 'adaptive-history.json');
|
|
261
|
-
if (fs.existsSync(historyPath)) {
|
|
262
|
-
fs.unlinkSync(historyPath);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Get summary of adaptive thresholds for logging
|
|
268
|
-
*/
|
|
269
|
-
export function getAdaptiveSummary(adjustments: ThresholdAdjustments): string {
|
|
270
|
-
return `[${adjustments.tier.toUpperCase()}] ` +
|
|
271
|
-
`Coverage: ${adjustments.coverageThreshold}%, ` +
|
|
272
|
-
`Quality: ${adjustments.qualityThreshold}%, ` +
|
|
273
|
-
`Security: ${adjustments.securityBlockLevel}+, ` +
|
|
274
|
-
`Trend: ${adjustments.trend}`;
|
|
275
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { FileScanner } from '../utils/scanner.js';
|
|
2
|
-
import { Config } from '../types/index.js';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
|
|
6
|
-
export interface ProjectAnchor {
|
|
7
|
-
id: string;
|
|
8
|
-
type: 'env' | 'naming' | 'import';
|
|
9
|
-
pattern: string;
|
|
10
|
-
confidence: number;
|
|
11
|
-
occurrences: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface GoldenRecord {
|
|
15
|
-
anchors: ProjectAnchor[];
|
|
16
|
-
metadata: {
|
|
17
|
-
scannedFiles: number;
|
|
18
|
-
detectedCasing: 'camelCase' | 'snake_case' | 'PascalCase' | 'unknown';
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ContextEngine {
|
|
23
|
-
constructor(private config: Config) { }
|
|
24
|
-
|
|
25
|
-
async discover(cwd: string): Promise<GoldenRecord> {
|
|
26
|
-
const anchors: ProjectAnchor[] = [];
|
|
27
|
-
const files = await FileScanner.findFiles({
|
|
28
|
-
cwd,
|
|
29
|
-
patterns: [
|
|
30
|
-
'**/*.{ts,js,py,yaml,yml,json}',
|
|
31
|
-
'.env*',
|
|
32
|
-
'**/.env*',
|
|
33
|
-
'**/package.json',
|
|
34
|
-
'**/Dockerfile',
|
|
35
|
-
'**/*.tf'
|
|
36
|
-
]
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const limit = this.config.gates.context?.mining_depth || 100;
|
|
40
|
-
const samples = files.slice(0, limit);
|
|
41
|
-
|
|
42
|
-
const envVars = new Map<string, number>();
|
|
43
|
-
let scannedFiles = 0;
|
|
44
|
-
|
|
45
|
-
for (const file of samples) {
|
|
46
|
-
try {
|
|
47
|
-
const content = await fs.readFile(path.join(cwd, file), 'utf-8');
|
|
48
|
-
scannedFiles++;
|
|
49
|
-
this.mineEnvVars(content, file, envVars);
|
|
50
|
-
} catch (e) { }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Logs removed to avoid stdout pollution in JSON mode
|
|
54
|
-
|
|
55
|
-
// Convert envVars to anchors
|
|
56
|
-
for (const [name, count] of envVars.entries()) {
|
|
57
|
-
const confidence = count >= 2 ? 1 : 0.5;
|
|
58
|
-
anchors.push({
|
|
59
|
-
id: name,
|
|
60
|
-
type: 'env',
|
|
61
|
-
pattern: name,
|
|
62
|
-
occurrences: count,
|
|
63
|
-
confidence
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
anchors,
|
|
69
|
-
metadata: {
|
|
70
|
-
scannedFiles,
|
|
71
|
-
detectedCasing: 'unknown', // TODO: Implement casing discovery
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private mineEnvVars(content: string, file: string, registry: Map<string, number>) {
|
|
77
|
-
const isAnchorSource = file.includes('.env') || file.includes('yml') || file.includes('yaml');
|
|
78
|
-
|
|
79
|
-
if (isAnchorSource) {
|
|
80
|
-
const matches = content.matchAll(/^\s*([A-Z0-9_]+)\s*=/gm);
|
|
81
|
-
for (const match of matches) {
|
|
82
|
-
// Anchors from .env count for more initially
|
|
83
|
-
registry.set(match[1], (registry.get(match[1]) || 0) + 2);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Source code matches (process.env.VAR or process.env['VAR'])
|
|
88
|
-
const tsJsMatches = content.matchAll(/process\.env(?:\.([A-Z0-9_]+)|\[['"]([A-Z0-9_]+)['"]\])/g);
|
|
89
|
-
for (const match of tsJsMatches) {
|
|
90
|
-
const name = match[1] || match[2];
|
|
91
|
-
this.incrementRegistry(registry, name);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Python matches (os.environ.get('VAR') or os.environ['VAR'])
|
|
95
|
-
const pyMatches = content.matchAll(/os\.environ(?:\.get\(|\[)['"]([A-Z0-9_]+)['"]/g);
|
|
96
|
-
for (const match of pyMatches) {
|
|
97
|
-
this.incrementRegistry(registry, match[1]);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private incrementRegistry(registry: Map<string, number>, key: string) {
|
|
102
|
-
registry.set(key, (registry.get(key) || 0) + 1);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { Report, Failure, Config } from '../types/index.js';
|
|
2
|
-
import { FixPacketV2, FixPacketV2Schema } from '../types/fix-packet.js';
|
|
3
|
-
|
|
4
|
-
export class FixPacketService {
|
|
5
|
-
generate(report: Report, config: Config): FixPacketV2 {
|
|
6
|
-
const violations = report.failures.map(f => ({
|
|
7
|
-
id: f.id,
|
|
8
|
-
gate: f.id,
|
|
9
|
-
severity: this.inferSeverity(f),
|
|
10
|
-
title: f.title,
|
|
11
|
-
details: f.details,
|
|
12
|
-
files: f.files,
|
|
13
|
-
hint: f.hint,
|
|
14
|
-
instructions: f.hint ? [f.hint] : [], // Use hint as first instruction
|
|
15
|
-
metrics: (f as any).metrics,
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
const packet: FixPacketV2 = {
|
|
19
|
-
version: 2,
|
|
20
|
-
goal: "Achieve PASS state by resolving all listed engineering violations.",
|
|
21
|
-
violations,
|
|
22
|
-
constraints: {
|
|
23
|
-
paradigm: config.paradigm,
|
|
24
|
-
protected_paths: config.gates.safety?.protected_paths,
|
|
25
|
-
do_not_touch: config.gates.safety?.protected_paths,
|
|
26
|
-
max_files_changed: config.gates.safety?.max_files_changed_per_cycle,
|
|
27
|
-
no_new_deps: true,
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return FixPacketV2Schema.parse(packet);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private inferSeverity(f: Failure): "low" | "medium" | "high" | "critical" {
|
|
35
|
-
// High complexity or God objects are usually High severity
|
|
36
|
-
if (f.id === 'ast-analysis') return 'high';
|
|
37
|
-
// Unit test or Lint failures are Medium
|
|
38
|
-
if (f.id === 'test' || f.id === 'lint') return 'medium';
|
|
39
|
-
// Documentation or small file size issues are Low
|
|
40
|
-
return 'medium';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Local Calibration State Service
|
|
7
|
-
*
|
|
8
|
-
* DOCTRINE COMPLIANCE (§6):
|
|
9
|
-
* - Local-only, deletable, optional
|
|
10
|
-
* - Used ONLY for prioritization, ordering, early warnings
|
|
11
|
-
* - NEVER stores: source code, file contents, raw paths, prompts, user identifiers
|
|
12
|
-
* - State NEVER changes PASS/FAIL results (only ordering/messaging)
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
interface ViolationStats {
|
|
16
|
-
count: number;
|
|
17
|
-
lastSeen: string; // ISO timestamp
|
|
18
|
-
coOccurs: Record<string, number>; // Rule ID -> co-occurrence count
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface LocalState {
|
|
22
|
-
version: number;
|
|
23
|
-
createdAt: string;
|
|
24
|
-
violations: Record<string, ViolationStats>; // Rule ID -> stats
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const STATE_DIR = '.rigour';
|
|
28
|
-
const STATE_FILE = 'state.json';
|
|
29
|
-
const CURRENT_VERSION = 1;
|
|
30
|
-
|
|
31
|
-
export class StateService {
|
|
32
|
-
private statePath: string;
|
|
33
|
-
private state: LocalState;
|
|
34
|
-
|
|
35
|
-
constructor(cwd: string) {
|
|
36
|
-
this.statePath = path.join(cwd, STATE_DIR, STATE_FILE);
|
|
37
|
-
this.state = this.load();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private load(): LocalState {
|
|
41
|
-
try {
|
|
42
|
-
if (fs.existsSync(this.statePath)) {
|
|
43
|
-
const content = fs.readFileSync(this.statePath, 'utf-8');
|
|
44
|
-
return JSON.parse(content);
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// Corrupted or missing state - reset
|
|
48
|
-
}
|
|
49
|
-
return this.createEmpty();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private createEmpty(): LocalState {
|
|
53
|
-
return {
|
|
54
|
-
version: CURRENT_VERSION,
|
|
55
|
-
createdAt: new Date().toISOString(),
|
|
56
|
-
violations: {},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Record violation occurrences for prioritization.
|
|
62
|
-
* PRIVACY: Only stores rule IDs and counts, never file contents or paths.
|
|
63
|
-
*/
|
|
64
|
-
recordViolations(ruleIds: string[]): void {
|
|
65
|
-
const now = new Date().toISOString();
|
|
66
|
-
|
|
67
|
-
for (const ruleId of ruleIds) {
|
|
68
|
-
if (!this.state.violations[ruleId]) {
|
|
69
|
-
this.state.violations[ruleId] = { count: 0, lastSeen: now, coOccurs: {} };
|
|
70
|
-
}
|
|
71
|
-
this.state.violations[ruleId].count++;
|
|
72
|
-
this.state.violations[ruleId].lastSeen = now;
|
|
73
|
-
|
|
74
|
-
// Track co-occurrences for pattern detection
|
|
75
|
-
for (const otherId of ruleIds) {
|
|
76
|
-
if (otherId !== ruleId) {
|
|
77
|
-
this.state.violations[ruleId].coOccurs[otherId] =
|
|
78
|
-
(this.state.violations[ruleId].coOccurs[otherId] || 0) + 1;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Prioritize violations for Fix Packet ordering.
|
|
86
|
-
* Most frequent + recent violations appear first.
|
|
87
|
-
*/
|
|
88
|
-
prioritize(ruleIds: string[]): string[] {
|
|
89
|
-
return [...ruleIds].sort((a, b) => {
|
|
90
|
-
const statsA = this.state.violations[a];
|
|
91
|
-
const statsB = this.state.violations[b];
|
|
92
|
-
|
|
93
|
-
if (!statsA && !statsB) return 0;
|
|
94
|
-
if (!statsA) return 1;
|
|
95
|
-
if (!statsB) return -1;
|
|
96
|
-
|
|
97
|
-
// Higher count = higher priority
|
|
98
|
-
return statsB.count - statsA.count;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get repeat violation hints for agent feedback.
|
|
104
|
-
*/
|
|
105
|
-
getRepeatHints(ruleIds: string[]): Record<string, string> {
|
|
106
|
-
const hints: Record<string, string> = {};
|
|
107
|
-
|
|
108
|
-
for (const ruleId of ruleIds) {
|
|
109
|
-
const stats = this.state.violations[ruleId];
|
|
110
|
-
if (stats && stats.count > 2) {
|
|
111
|
-
hints[ruleId] = `This violation has occurred ${stats.count} times. Consider root-cause analysis.`;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return hints;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
save(): void {
|
|
119
|
-
try {
|
|
120
|
-
fs.ensureDirSync(path.dirname(this.statePath));
|
|
121
|
-
fs.writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
|
|
122
|
-
} catch {
|
|
123
|
-
// Silent fail - state is optional
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Clear all state (user privacy action).
|
|
129
|
-
*/
|
|
130
|
-
clear(): void {
|
|
131
|
-
this.state = this.createEmpty();
|
|
132
|
-
try {
|
|
133
|
-
fs.removeSync(this.statePath);
|
|
134
|
-
} catch {
|
|
135
|
-
// Silent fail
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
package/src/smoke.test.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { GateRunner } from '../src/gates/runner.js';
|
|
3
|
-
|
|
4
|
-
describe('GateRunner Smoke Test', () => {
|
|
5
|
-
it('should initialize with empty config', async () => {
|
|
6
|
-
const config = {
|
|
7
|
-
version: 1,
|
|
8
|
-
commands: {},
|
|
9
|
-
gates: {
|
|
10
|
-
max_file_lines: 500,
|
|
11
|
-
forbid_todos: true,
|
|
12
|
-
forbid_fixme: true,
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
const runner = new GateRunner(config as any);
|
|
16
|
-
expect(runner).toBeDefined();
|
|
17
|
-
});
|
|
18
|
-
});
|