@rigour-labs/core 2.21.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +58 -0
  2. package/dist/context.test.js +2 -3
  3. package/dist/environment.test.js +2 -1
  4. package/dist/gates/agent-team.d.ts +2 -1
  5. package/dist/gates/agent-team.js +1 -0
  6. package/dist/gates/base.d.ts +4 -2
  7. package/dist/gates/base.js +5 -1
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/content.js +1 -1
  11. package/dist/gates/context-window-artifacts.d.ts +34 -0
  12. package/dist/gates/context-window-artifacts.js +214 -0
  13. package/dist/gates/context.d.ts +2 -1
  14. package/dist/gates/context.js +4 -3
  15. package/dist/gates/coverage.js +3 -1
  16. package/dist/gates/dependency.js +5 -5
  17. package/dist/gates/duplication-drift.d.ts +33 -0
  18. package/dist/gates/duplication-drift.js +190 -0
  19. package/dist/gates/environment.js +4 -4
  20. package/dist/gates/file.js +1 -1
  21. package/dist/gates/hallucinated-imports.d.ts +63 -0
  22. package/dist/gates/hallucinated-imports.js +406 -0
  23. package/dist/gates/inconsistent-error-handling.d.ts +39 -0
  24. package/dist/gates/inconsistent-error-handling.js +236 -0
  25. package/dist/gates/promise-safety.d.ts +68 -0
  26. package/dist/gates/promise-safety.js +509 -0
  27. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  28. package/dist/gates/retry-loop-breaker.js +2 -1
  29. package/dist/gates/runner.js +62 -1
  30. package/dist/gates/safety.d.ts +2 -1
  31. package/dist/gates/safety.js +2 -1
  32. package/dist/gates/security-patterns.d.ts +2 -1
  33. package/dist/gates/security-patterns.js +2 -1
  34. package/dist/gates/structure.js +1 -1
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/services/fix-packet-service.d.ts +0 -1
  38. package/dist/services/fix-packet-service.js +9 -14
  39. package/dist/services/score-history.d.ts +54 -0
  40. package/dist/services/score-history.js +122 -0
  41. package/dist/templates/index.js +195 -0
  42. package/dist/types/fix-packet.d.ts +5 -5
  43. package/dist/types/fix-packet.js +1 -1
  44. package/dist/types/index.d.ts +430 -0
  45. package/dist/types/index.js +57 -0
  46. package/package.json +21 -1
  47. package/src/context.test.ts +0 -256
  48. package/src/discovery.test.ts +0 -88
  49. package/src/discovery.ts +0 -112
  50. package/src/environment.test.ts +0 -115
  51. package/src/gates/agent-team.test.ts +0 -134
  52. package/src/gates/agent-team.ts +0 -210
  53. package/src/gates/ast-handlers/base.ts +0 -13
  54. package/src/gates/ast-handlers/python.ts +0 -145
  55. package/src/gates/ast-handlers/python_parser.py +0 -181
  56. package/src/gates/ast-handlers/typescript.ts +0 -264
  57. package/src/gates/ast-handlers/universal.ts +0 -184
  58. package/src/gates/ast.ts +0 -54
  59. package/src/gates/base.ts +0 -27
  60. package/src/gates/checkpoint.test.ts +0 -135
  61. package/src/gates/checkpoint.ts +0 -311
  62. package/src/gates/content.ts +0 -50
  63. package/src/gates/context.ts +0 -267
  64. package/src/gates/coverage.ts +0 -74
  65. package/src/gates/dependency.ts +0 -108
  66. package/src/gates/environment.ts +0 -94
  67. package/src/gates/file.ts +0 -42
  68. package/src/gates/retry-loop-breaker.ts +0 -151
  69. package/src/gates/runner.ts +0 -156
  70. package/src/gates/safety.ts +0 -56
  71. package/src/gates/security-patterns.test.ts +0 -162
  72. package/src/gates/security-patterns.ts +0 -305
  73. package/src/gates/structure.ts +0 -36
  74. package/src/index.ts +0 -13
  75. package/src/pattern-index/embeddings.ts +0 -84
  76. package/src/pattern-index/index.ts +0 -59
  77. package/src/pattern-index/indexer.test.ts +0 -276
  78. package/src/pattern-index/indexer.ts +0 -1023
  79. package/src/pattern-index/matcher.test.ts +0 -293
  80. package/src/pattern-index/matcher.ts +0 -493
  81. package/src/pattern-index/overrides.ts +0 -235
  82. package/src/pattern-index/security.ts +0 -151
  83. package/src/pattern-index/staleness.test.ts +0 -313
  84. package/src/pattern-index/staleness.ts +0 -568
  85. package/src/pattern-index/types.ts +0 -339
  86. package/src/safety.test.ts +0 -53
  87. package/src/services/adaptive-thresholds.test.ts +0 -189
  88. package/src/services/adaptive-thresholds.ts +0 -275
  89. package/src/services/context-engine.ts +0 -104
  90. package/src/services/fix-packet-service.ts +0 -42
  91. package/src/services/state-service.ts +0 -138
  92. package/src/smoke.test.ts +0 -18
  93. package/src/templates/index.ts +0 -312
  94. package/src/types/fix-packet.ts +0 -32
  95. package/src/types/index.ts +0 -159
  96. package/src/utils/logger.ts +0 -43
  97. package/src/utils/scanner.test.ts +0 -37
  98. package/src/utils/scanner.ts +0 -43
  99. package/tsconfig.json +0 -10
  100. package/vitest.config.ts +0 -7
  101. package/vitest.setup.ts +0 -30
@@ -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
- });