@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
@@ -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
- });