@eddacraft/anvil-runtime 0.1.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.
- package/LICENSE +14 -0
- package/dist/cache/cache-key.d.ts +45 -0
- package/dist/cache/cache-key.d.ts.map +1 -0
- package/dist/cache/cache-key.js +135 -0
- package/dist/cache/index.d.ts +27 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +38 -0
- package/dist/cache/providers/file-cache.d.ts +63 -0
- package/dist/cache/providers/file-cache.d.ts.map +1 -0
- package/dist/cache/providers/file-cache.js +369 -0
- package/dist/cache/providers/memory-cache.d.ts +52 -0
- package/dist/cache/providers/memory-cache.d.ts.map +1 -0
- package/dist/cache/providers/memory-cache.js +197 -0
- package/dist/cache/providers/null-cache.d.ts +26 -0
- package/dist/cache/providers/null-cache.d.ts.map +1 -0
- package/dist/cache/providers/null-cache.js +50 -0
- package/dist/cache/types.d.ts +114 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +4 -0
- package/dist/concurrency/agent.d.ts +137 -0
- package/dist/concurrency/agent.d.ts.map +1 -0
- package/dist/concurrency/agent.js +440 -0
- package/dist/concurrency/atomic.d.ts +93 -0
- package/dist/concurrency/atomic.d.ts.map +1 -0
- package/dist/concurrency/atomic.js +281 -0
- package/dist/concurrency/git-agent.d.ts +114 -0
- package/dist/concurrency/git-agent.d.ts.map +1 -0
- package/dist/concurrency/git-agent.js +313 -0
- package/dist/concurrency/index.d.ts +95 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +127 -0
- package/dist/concurrency/lock-manager.d.ts +170 -0
- package/dist/concurrency/lock-manager.d.ts.map +1 -0
- package/dist/concurrency/lock-manager.js +525 -0
- package/dist/concurrency/queue-manager.d.ts +166 -0
- package/dist/concurrency/queue-manager.d.ts.map +1 -0
- package/dist/concurrency/queue-manager.js +442 -0
- package/dist/concurrency/types.d.ts +382 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +204 -0
- package/dist/export/constraint-collector.d.ts +175 -0
- package/dist/export/constraint-collector.d.ts.map +1 -0
- package/dist/export/constraint-collector.js +203 -0
- package/dist/export/formatters/llms-txt-formatter.d.ts +89 -0
- package/dist/export/formatters/llms-txt-formatter.d.ts.map +1 -0
- package/dist/export/formatters/llms-txt-formatter.js +249 -0
- package/dist/export/formatters/mcp-resource-formatter.d.ts +186 -0
- package/dist/export/formatters/mcp-resource-formatter.d.ts.map +1 -0
- package/dist/export/formatters/mcp-resource-formatter.js +139 -0
- package/dist/export/formatters/prompt-formatter.d.ts +83 -0
- package/dist/export/formatters/prompt-formatter.d.ts.map +1 -0
- package/dist/export/formatters/prompt-formatter.js +256 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +9 -0
- package/dist/gate/check.interface.d.ts +15 -0
- package/dist/gate/check.interface.d.ts.map +1 -0
- package/dist/gate/check.interface.js +18 -0
- package/dist/gate/checks/antipattern.check.d.ts +27 -0
- package/dist/gate/checks/antipattern.check.d.ts.map +1 -0
- package/dist/gate/checks/antipattern.check.js +140 -0
- package/dist/gate/checks/architecture/circular-detector.d.ts +33 -0
- package/dist/gate/checks/architecture/circular-detector.d.ts.map +1 -0
- package/dist/gate/checks/architecture/circular-detector.js +71 -0
- package/dist/gate/checks/architecture/dependency-analyzer.d.ts +81 -0
- package/dist/gate/checks/architecture/dependency-analyzer.d.ts.map +1 -0
- package/dist/gate/checks/architecture/dependency-analyzer.js +136 -0
- package/dist/gate/checks/architecture/layer-validator.d.ts +75 -0
- package/dist/gate/checks/architecture/layer-validator.d.ts.map +1 -0
- package/dist/gate/checks/architecture/layer-validator.js +193 -0
- package/dist/gate/checks/architecture.check.d.ts +56 -0
- package/dist/gate/checks/architecture.check.d.ts.map +1 -0
- package/dist/gate/checks/architecture.check.js +394 -0
- package/dist/gate/checks/command-safety.check.d.ts +12 -0
- package/dist/gate/checks/command-safety.check.d.ts.map +1 -0
- package/dist/gate/checks/command-safety.check.js +230 -0
- package/dist/gate/checks/coverage.check.d.ts +9 -0
- package/dist/gate/checks/coverage.check.d.ts.map +1 -0
- package/dist/gate/checks/coverage.check.js +81 -0
- package/dist/gate/checks/dependency.check.d.ts +17 -0
- package/dist/gate/checks/dependency.check.d.ts.map +1 -0
- package/dist/gate/checks/dependency.check.js +342 -0
- package/dist/gate/checks/eslint.check.d.ts +14 -0
- package/dist/gate/checks/eslint.check.d.ts.map +1 -0
- package/dist/gate/checks/eslint.check.js +79 -0
- package/dist/gate/checks/policy.check.d.ts +78 -0
- package/dist/gate/checks/policy.check.d.ts.map +1 -0
- package/dist/gate/checks/policy.check.js +457 -0
- package/dist/gate/checks/secret/entropy-detector.d.ts +44 -0
- package/dist/gate/checks/secret/entropy-detector.d.ts.map +1 -0
- package/dist/gate/checks/secret/entropy-detector.js +76 -0
- package/dist/gate/checks/secret/git-scanner.d.ts +36 -0
- package/dist/gate/checks/secret/git-scanner.d.ts.map +1 -0
- package/dist/gate/checks/secret/git-scanner.js +90 -0
- package/dist/gate/checks/secret/secret-patterns.d.ts +42 -0
- package/dist/gate/checks/secret/secret-patterns.d.ts.map +1 -0
- package/dist/gate/checks/secret/secret-patterns.js +137 -0
- package/dist/gate/checks/secret.check.d.ts +56 -0
- package/dist/gate/checks/secret.check.d.ts.map +1 -0
- package/dist/gate/checks/secret.check.js +245 -0
- package/dist/gate/config/command-safety-config.d.ts +5 -0
- package/dist/gate/config/command-safety-config.d.ts.map +1 -0
- package/dist/gate/config/command-safety-config.js +69 -0
- package/dist/gate/config/index.d.ts +2 -0
- package/dist/gate/config/index.d.ts.map +1 -0
- package/dist/gate/config/index.js +1 -0
- package/dist/gate/formatters/command-safety-formatter.d.ts +10 -0
- package/dist/gate/formatters/command-safety-formatter.d.ts.map +1 -0
- package/dist/gate/formatters/command-safety-formatter.js +64 -0
- package/dist/gate/formatters/index.d.ts +2 -0
- package/dist/gate/formatters/index.d.ts.map +1 -0
- package/dist/gate/formatters/index.js +1 -0
- package/dist/gate/gate-config.d.ts +44 -0
- package/dist/gate/gate-config.d.ts.map +1 -0
- package/dist/gate/gate-config.js +334 -0
- package/dist/gate/gate-runner.d.ts +160 -0
- package/dist/gate/gate-runner.d.ts.map +1 -0
- package/dist/gate/gate-runner.js +531 -0
- package/dist/gate/index.d.ts +20 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/index.js +14 -0
- package/dist/gate/parsers/command-parser.d.ts +18 -0
- package/dist/gate/parsers/command-parser.d.ts.map +1 -0
- package/dist/gate/parsers/command-parser.js +363 -0
- package/dist/gate/parsers/index.d.ts +2 -0
- package/dist/gate/parsers/index.d.ts.map +1 -0
- package/dist/gate/parsers/index.js +1 -0
- package/dist/gate/policy/index.d.ts +12 -0
- package/dist/gate/policy/index.d.ts.map +1 -0
- package/dist/gate/policy/index.js +10 -0
- package/dist/gate/rules/default-filesystem-rules.d.ts +3 -0
- package/dist/gate/rules/default-filesystem-rules.d.ts.map +1 -0
- package/dist/gate/rules/default-filesystem-rules.js +201 -0
- package/dist/gate/rules/default-git-rules.d.ts +3 -0
- package/dist/gate/rules/default-git-rules.d.ts.map +1 -0
- package/dist/gate/rules/default-git-rules.js +192 -0
- package/dist/gate/rules/index.d.ts +5 -0
- package/dist/gate/rules/index.d.ts.map +1 -0
- package/dist/gate/rules/index.js +3 -0
- package/dist/gate/rules/rule-matcher.d.ts +27 -0
- package/dist/gate/rules/rule-matcher.d.ts.map +1 -0
- package/dist/gate/rules/rule-matcher.js +228 -0
- package/dist/gate/rules/types.d.ts +250 -0
- package/dist/gate/rules/types.d.ts.map +1 -0
- package/dist/gate/rules/types.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/types/gate.types.d.ts +42 -0
- package/dist/types/gate.types.d.ts.map +1 -0
- package/dist/types/gate.types.js +94 -0
- package/dist/watch/debouncer.d.ts +90 -0
- package/dist/watch/debouncer.d.ts.map +1 -0
- package/dist/watch/debouncer.js +135 -0
- package/dist/watch/file-watcher.d.ts +73 -0
- package/dist/watch/file-watcher.d.ts.map +1 -0
- package/dist/watch/file-watcher.js +121 -0
- package/dist/watch/git-status.d.ts +98 -0
- package/dist/watch/git-status.d.ts.map +1 -0
- package/dist/watch/git-status.js +266 -0
- package/dist/watch/index.d.ts +16 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +15 -0
- package/dist/watch/orchestrator.d.ts +113 -0
- package/dist/watch/orchestrator.d.ts.map +1 -0
- package/dist/watch/orchestrator.js +409 -0
- package/dist/watch/types.d.ts +190 -0
- package/dist/watch/types.d.ts.map +1 -0
- package/dist/watch/types.js +76 -0
- package/package.json +60 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture Check - Validate architectural constraints using dependency-cruiser
|
|
3
|
+
*
|
|
4
|
+
* Detects:
|
|
5
|
+
* - Circular dependencies
|
|
6
|
+
* - Layer/boundary violations
|
|
7
|
+
* - Orphaned modules (optional)
|
|
8
|
+
*/
|
|
9
|
+
import { BaseCheck } from '../check.interface.js';
|
|
10
|
+
import { getFilesFromContext } from '../../types/gate.types.js';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { minimatch } from 'minimatch';
|
|
14
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
15
|
+
import { loadBaseline } from '@eddacraft/anvil-core/architecture';
|
|
16
|
+
const log = createDebugger('check');
|
|
17
|
+
import { createContextBuilder, } from '@eddacraft/anvil-core/architecture';
|
|
18
|
+
import { createWarningResult, createWarningFingerprint, } from '@eddacraft/anvil-core/antipattern';
|
|
19
|
+
import { DependencyAnalyzer } from './architecture/dependency-analyzer.js';
|
|
20
|
+
import { CircularDetector } from './architecture/circular-detector.js';
|
|
21
|
+
import { LayerValidator } from './architecture/layer-validator.js';
|
|
22
|
+
const ARCH_VIOLATION_IDS = {
|
|
23
|
+
circular: 'ARCH-001',
|
|
24
|
+
orphan: 'ARCH-002',
|
|
25
|
+
layer: 'ARCH-003',
|
|
26
|
+
other: 'ARCH-004',
|
|
27
|
+
};
|
|
28
|
+
const ARCH_VIOLATION_TITLES = {
|
|
29
|
+
'ARCH-001': 'Circular dependency detected',
|
|
30
|
+
'ARCH-002': 'Orphaned module',
|
|
31
|
+
'ARCH-003': 'Layer/boundary violation',
|
|
32
|
+
'ARCH-004': 'Architecture violation',
|
|
33
|
+
};
|
|
34
|
+
const ARCH_VIOLATION_EXPLANATIONS = {
|
|
35
|
+
'ARCH-001': 'Circular dependencies make code harder to understand and can cause issues with module loading.',
|
|
36
|
+
'ARCH-002': 'This module has no dependents or dependencies, which may indicate dead code or missing integration.',
|
|
37
|
+
'ARCH-003': 'This import crosses an architectural boundary that should not be crossed.',
|
|
38
|
+
'ARCH-004': 'This import violates a configured architecture rule.',
|
|
39
|
+
};
|
|
40
|
+
const ARCH_VIOLATION_SUGGESTIONS = {
|
|
41
|
+
'ARCH-001': 'Break the cycle by extracting shared code into a separate module or using dependency injection.',
|
|
42
|
+
'ARCH-002': 'Either connect this module to your application or remove it if unused.',
|
|
43
|
+
'ARCH-003': 'Move the import target to an appropriate layer or adjust the boundary definition.',
|
|
44
|
+
'ARCH-004': 'Review the dependency-cruiser rule and adjust your code or configuration.',
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Score penalties per severity
|
|
48
|
+
*/
|
|
49
|
+
const SEVERITY_PENALTIES = {
|
|
50
|
+
error: 15,
|
|
51
|
+
warn: 5,
|
|
52
|
+
info: 1,
|
|
53
|
+
ignore: 0,
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Architecture check that validates project structure using dependency-cruiser
|
|
57
|
+
*/
|
|
58
|
+
export class ArchitectureCheck extends BaseCheck {
|
|
59
|
+
name = 'architecture';
|
|
60
|
+
description = 'Validate architectural constraints using dependency-cruiser';
|
|
61
|
+
analyzer = new DependencyAnalyzer();
|
|
62
|
+
detector = new CircularDetector();
|
|
63
|
+
validator = new LayerValidator();
|
|
64
|
+
async run(context) {
|
|
65
|
+
log(`architecture check starting, workspace=${context.workspace_root}, fullScan=${context.fullScan}`);
|
|
66
|
+
const config = this.validator.parseConfig(context.check_config);
|
|
67
|
+
log(`architecture config: scope=${config.scope}, severity_threshold=${config.severity_threshold}, fail_on_circular=${config.fail_on_circular}`);
|
|
68
|
+
try {
|
|
69
|
+
// Step 1: Load dependency-cruiser
|
|
70
|
+
const loadResult = await this.analyzer.loadCruiser();
|
|
71
|
+
if (!loadResult.success) {
|
|
72
|
+
log('architecture check: dependency-cruiser not installed, skipping');
|
|
73
|
+
return this.createSuccess('dependency-cruiser not installed. Run `npm install -D dependency-cruiser` to enable architecture checks.', 100, {
|
|
74
|
+
skipped: true,
|
|
75
|
+
reason: 'dependency-cruiser not available',
|
|
76
|
+
warnings: createWarningResult([], []),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Step 2: Load dependency-cruiser config or use defaults
|
|
80
|
+
const configPath = join(context.workspace_root, config.config_file);
|
|
81
|
+
let cruiseOptions = await this.analyzer.loadConfig(context.workspace_root, config.config_file);
|
|
82
|
+
if (!cruiseOptions) {
|
|
83
|
+
if (existsSync(configPath)) {
|
|
84
|
+
log(`architecture check: config file exists but failed to load: ${config.config_file}`);
|
|
85
|
+
return this.createFailure(`Failed to load dependency-cruiser config: ${config.config_file}`, 'Config file exists but could not be loaded');
|
|
86
|
+
}
|
|
87
|
+
log('architecture check: no config file found, using built-in defaults');
|
|
88
|
+
// Use default rules for circular dependency and orphan detection
|
|
89
|
+
cruiseOptions = this.analyzer.getDefaultCruiseOptions();
|
|
90
|
+
}
|
|
91
|
+
// Step 3: Determine files to analyse
|
|
92
|
+
const effectiveConfig = context.fullScan ? { ...config, scope: 'full' } : config;
|
|
93
|
+
const filesToCruise = this.getFilesToCruise(context, effectiveConfig);
|
|
94
|
+
if (filesToCruise.length === 0) {
|
|
95
|
+
log('architecture check: no files to analyse');
|
|
96
|
+
return this.createSuccess('No files to analyse for architecture violations', 100, {
|
|
97
|
+
warnings: createWarningResult([], []),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
log(`architecture check: analysing ${filesToCruise.length} files/patterns`);
|
|
101
|
+
// Step 4: Run dependency analysis
|
|
102
|
+
const analysisResult = await this.analyzer.analyze(filesToCruise, cruiseOptions);
|
|
103
|
+
if (!analysisResult.success || !analysisResult.result) {
|
|
104
|
+
if (analysisResult.skipped) {
|
|
105
|
+
log(`architecture check: analysis skipped, reason=${analysisResult.reason}`);
|
|
106
|
+
return this.createSuccess(analysisResult.reason || 'Analysis skipped', 100, {
|
|
107
|
+
skipped: true,
|
|
108
|
+
reason: analysisResult.reason,
|
|
109
|
+
warnings: createWarningResult([], []),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
log(`architecture check: analysis failed, error=${analysisResult.error}`);
|
|
113
|
+
return this.createFailure('Dependency analysis failed', analysisResult.error || 'Unknown error');
|
|
114
|
+
}
|
|
115
|
+
const output = analysisResult.result;
|
|
116
|
+
// Step 5: Load baseline if exists (for new-only mode)
|
|
117
|
+
const baseline = loadBaseline(context.workspace_root);
|
|
118
|
+
log(`architecture check: baseline loaded=${baseline !== null}, totalCruised=${output.summary.totalCruised}, violations=${output.summary.violations.length}`);
|
|
119
|
+
// Step 6: Convert ALL violations to Warning format with drift info
|
|
120
|
+
const allViolations = output.summary.violations;
|
|
121
|
+
const allWarnings = allViolations.map((v) => this.convertViolationToWarning(v, baseline));
|
|
122
|
+
// Step 7: In new-only mode (when baseline exists), filter to NEW violations only
|
|
123
|
+
const warnings = baseline ? allWarnings.filter((w) => w.drift?.isNew !== false) : allWarnings;
|
|
124
|
+
// Step 8: Calculate score/passed from the effective warnings
|
|
125
|
+
const effectiveViolations = this.detector.filterNewViolations(allViolations, baseline);
|
|
126
|
+
const { score, passed, violationsByType } = this.validator.calculateScore(effectiveViolations, config);
|
|
127
|
+
const warningResult = this.createArchWarningResult(warnings, violationsByType);
|
|
128
|
+
const message = this.validator.buildMessage(effectiveViolations, output.summary.totalCruised, passed);
|
|
129
|
+
const architectureContext = this.buildArchitectureContext(allViolations, effectiveViolations, output.summary, violationsByType, baseline, config);
|
|
130
|
+
log('architecture check result', {
|
|
131
|
+
passed,
|
|
132
|
+
score,
|
|
133
|
+
totalViolations: allViolations.length,
|
|
134
|
+
newViolations: effectiveViolations.length,
|
|
135
|
+
violationsByType,
|
|
136
|
+
});
|
|
137
|
+
return this.createResult(passed, message, score, {
|
|
138
|
+
warnings: warningResult,
|
|
139
|
+
totalModulesCruised: output.summary.totalCruised,
|
|
140
|
+
violationCount: allViolations.length,
|
|
141
|
+
newViolationCount: effectiveViolations.length,
|
|
142
|
+
errorCount: output.summary.error,
|
|
143
|
+
warnCount: output.summary.warn,
|
|
144
|
+
infoCount: output.summary.info,
|
|
145
|
+
violations: allViolations.map((v) => ({
|
|
146
|
+
from: v.from,
|
|
147
|
+
to: v.to,
|
|
148
|
+
rule: v.rule.name,
|
|
149
|
+
severity: v.rule.severity,
|
|
150
|
+
cycle: v.cycle,
|
|
151
|
+
})),
|
|
152
|
+
violationsByType,
|
|
153
|
+
configFile: existsSync(configPath) ? config.config_file : 'built-in defaults',
|
|
154
|
+
scope: config.scope,
|
|
155
|
+
baselineLoaded: baseline !== null,
|
|
156
|
+
architectureContext,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
log(`architecture check error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
161
|
+
return this.createFailure('Architecture check failed unexpectedly', error instanceof Error ? error.message : 'Unknown error');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
convertViolationToWarning(violation, baseline) {
|
|
165
|
+
const violationType = this.detector.categoriseViolation(violation);
|
|
166
|
+
const id = ARCH_VIOLATION_IDS[violationType];
|
|
167
|
+
const severity = this.mapCruiserSeverity(violation.rule.severity);
|
|
168
|
+
const warning = {
|
|
169
|
+
id,
|
|
170
|
+
category: 'architecture',
|
|
171
|
+
severity,
|
|
172
|
+
confidence: 'high',
|
|
173
|
+
title: ARCH_VIOLATION_TITLES[id],
|
|
174
|
+
message: `${violation.from} → ${violation.to} (${violation.rule.name})`,
|
|
175
|
+
explanation: ARCH_VIOLATION_EXPLANATIONS[id],
|
|
176
|
+
suggestion: ARCH_VIOLATION_SUGGESTIONS[id],
|
|
177
|
+
location: {
|
|
178
|
+
file: violation.from,
|
|
179
|
+
line: 1,
|
|
180
|
+
},
|
|
181
|
+
pattern: violation.rule.name,
|
|
182
|
+
};
|
|
183
|
+
warning.fingerprint = createWarningFingerprint(warning);
|
|
184
|
+
if (baseline) {
|
|
185
|
+
const isNew = this.detector.isNewViolation(violation, baseline);
|
|
186
|
+
warning.drift = {
|
|
187
|
+
isNew,
|
|
188
|
+
existingCount: baseline.baseline_snapshot.violations.length,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return warning;
|
|
192
|
+
}
|
|
193
|
+
mapCruiserSeverity(severity) {
|
|
194
|
+
if (severity === 'error')
|
|
195
|
+
return 'error';
|
|
196
|
+
if (severity === 'warn')
|
|
197
|
+
return 'warning';
|
|
198
|
+
return 'info';
|
|
199
|
+
}
|
|
200
|
+
createArchWarningResult(warnings, violationsByType) {
|
|
201
|
+
const patternsChecked = Object.keys(violationsByType).map((type) => ARCH_VIOLATION_IDS[type]);
|
|
202
|
+
return createWarningResult(warnings, patternsChecked);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get files to cruise based on scope
|
|
206
|
+
*/
|
|
207
|
+
getFilesToCruise(context, config) {
|
|
208
|
+
if (config.scope === 'full') {
|
|
209
|
+
// Return the include patterns for full scan
|
|
210
|
+
return config.include_patterns;
|
|
211
|
+
}
|
|
212
|
+
// Use unified helper for both planless and plan-based modes
|
|
213
|
+
const files = getFilesFromContext(context, {
|
|
214
|
+
filter: (f) => this.validator.isAnalysableFile(f, config),
|
|
215
|
+
});
|
|
216
|
+
// If no files match, fall back to include patterns
|
|
217
|
+
if (files.length === 0) {
|
|
218
|
+
return config.include_patterns;
|
|
219
|
+
}
|
|
220
|
+
return files;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if a file should be analysed
|
|
224
|
+
* @internal Reserved for future use
|
|
225
|
+
*/
|
|
226
|
+
// @ts-expect-error Reserved for future use
|
|
227
|
+
_isAnalysableFile(filePath, config) {
|
|
228
|
+
const analysableExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
229
|
+
const hasValidExtension = analysableExtensions.some((ext) => filePath.endsWith(ext));
|
|
230
|
+
if (!hasValidExtension)
|
|
231
|
+
return false;
|
|
232
|
+
// Check exclusions
|
|
233
|
+
for (const pattern of config.exclude_patterns) {
|
|
234
|
+
if (this.matchesGlobPattern(filePath, pattern)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
matchesGlobPattern(filePath, pattern) {
|
|
241
|
+
// Normalise to forward slashes for consistent cross-platform matching
|
|
242
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
243
|
+
return minimatch(normalizedPath, pattern, { dot: true });
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get default cruise options when no config file exists
|
|
247
|
+
* @internal Reserved for future use
|
|
248
|
+
*/
|
|
249
|
+
// @ts-expect-error Reserved for future use
|
|
250
|
+
_getDefaultCruiseOptions() {
|
|
251
|
+
return {
|
|
252
|
+
validate: true,
|
|
253
|
+
ruleSet: {
|
|
254
|
+
forbidden: [
|
|
255
|
+
{
|
|
256
|
+
name: 'no-circular',
|
|
257
|
+
severity: 'error',
|
|
258
|
+
comment: 'Circular dependencies are not allowed',
|
|
259
|
+
from: {},
|
|
260
|
+
to: {
|
|
261
|
+
circular: true,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'no-orphans',
|
|
266
|
+
severity: 'warn',
|
|
267
|
+
comment: 'Modules without dependents or dependencies',
|
|
268
|
+
from: {
|
|
269
|
+
orphan: true,
|
|
270
|
+
pathNot: [
|
|
271
|
+
'\\.d\\.ts$',
|
|
272
|
+
'\\.test\\.(ts|js)$',
|
|
273
|
+
'\\.spec\\.(ts|js)$',
|
|
274
|
+
'index\\.(ts|js)$',
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
to: {},
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Calculate score based on violations
|
|
285
|
+
* @internal Reserved for future use
|
|
286
|
+
*/
|
|
287
|
+
// @ts-expect-error Reserved for future use
|
|
288
|
+
_calculateScore(violations, config) {
|
|
289
|
+
const violationsByType = {
|
|
290
|
+
circular: 0,
|
|
291
|
+
orphan: 0,
|
|
292
|
+
layer: 0,
|
|
293
|
+
other: 0,
|
|
294
|
+
};
|
|
295
|
+
let totalPenalty = 0;
|
|
296
|
+
let hasBlockingViolation = false;
|
|
297
|
+
for (const v of violations) {
|
|
298
|
+
// Categorise violation
|
|
299
|
+
if (v.cycle && v.cycle.length > 0) {
|
|
300
|
+
violationsByType.circular++;
|
|
301
|
+
if (config.fail_on_circular &&
|
|
302
|
+
this.isBlockingSeverity(v.rule.severity, config.severity_threshold)) {
|
|
303
|
+
hasBlockingViolation = true;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (v.rule.name.includes('orphan')) {
|
|
307
|
+
violationsByType.orphan++;
|
|
308
|
+
if (config.fail_on_orphan &&
|
|
309
|
+
this.isBlockingSeverity(v.rule.severity, config.severity_threshold)) {
|
|
310
|
+
hasBlockingViolation = true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else if (v.rule.name.includes('layer') || v.rule.name.includes('boundary')) {
|
|
314
|
+
violationsByType.layer++;
|
|
315
|
+
if (this.isBlockingSeverity(v.rule.severity, config.severity_threshold)) {
|
|
316
|
+
hasBlockingViolation = true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
violationsByType.other++;
|
|
321
|
+
if (this.isBlockingSeverity(v.rule.severity, config.severity_threshold)) {
|
|
322
|
+
hasBlockingViolation = true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Calculate penalty
|
|
326
|
+
totalPenalty += SEVERITY_PENALTIES[v.rule.severity] || 0;
|
|
327
|
+
}
|
|
328
|
+
const score = Math.max(0, 100 - totalPenalty);
|
|
329
|
+
const passed = !hasBlockingViolation;
|
|
330
|
+
return { score, passed, violationsByType };
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Check if a severity level should block the check
|
|
334
|
+
*/
|
|
335
|
+
isBlockingSeverity(severity, threshold) {
|
|
336
|
+
const levels = { error: 3, warn: 2, info: 1, ignore: 0 };
|
|
337
|
+
return levels[severity] >= levels[threshold];
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Build human-readable message
|
|
341
|
+
* @internal Reserved for future use
|
|
342
|
+
*/
|
|
343
|
+
// @ts-expect-error Reserved for future use
|
|
344
|
+
_buildMessage(violations, totalCruised, passed) {
|
|
345
|
+
if (violations.length === 0) {
|
|
346
|
+
return `Architecture check passed: ${totalCruised} modules analysed, no violations`;
|
|
347
|
+
}
|
|
348
|
+
const errorCount = violations.filter((v) => v.rule.severity === 'error').length;
|
|
349
|
+
const warnCount = violations.filter((v) => v.rule.severity === 'warn').length;
|
|
350
|
+
const infoCount = violations.filter((v) => v.rule.severity === 'info').length;
|
|
351
|
+
const parts = [];
|
|
352
|
+
if (errorCount > 0)
|
|
353
|
+
parts.push(`${errorCount} error${errorCount > 1 ? 's' : ''}`);
|
|
354
|
+
if (warnCount > 0)
|
|
355
|
+
parts.push(`${warnCount} warning${warnCount > 1 ? 's' : ''}`);
|
|
356
|
+
if (infoCount > 0)
|
|
357
|
+
parts.push(`${infoCount} info`);
|
|
358
|
+
const status = passed ? 'passed with issues' : 'failed';
|
|
359
|
+
return `Architecture check ${status}: ${parts.join(', ')} (${totalCruised} modules analysed)`;
|
|
360
|
+
}
|
|
361
|
+
buildArchitectureContext(allViolations, effectiveViolations, summary, violationsByType, baseline, config) {
|
|
362
|
+
const builder = createContextBuilder();
|
|
363
|
+
const archViolations = allViolations.map((v) => ({
|
|
364
|
+
from: v.from,
|
|
365
|
+
to: v.to,
|
|
366
|
+
rule: v.rule.name,
|
|
367
|
+
severity: v.rule.severity,
|
|
368
|
+
is_circular: v.cycle !== undefined && v.cycle.length > 0,
|
|
369
|
+
cycle: v.cycle,
|
|
370
|
+
is_new: baseline ? this.detector.isNewViolation(v, baseline) : true,
|
|
371
|
+
from_layer: null,
|
|
372
|
+
to_layer: null,
|
|
373
|
+
}));
|
|
374
|
+
builder.setViolations(archViolations);
|
|
375
|
+
builder.setSummary({
|
|
376
|
+
total_modules: summary.totalCruised,
|
|
377
|
+
total_violations: allViolations.length,
|
|
378
|
+
new_violations: effectiveViolations.length,
|
|
379
|
+
error_count: summary.error,
|
|
380
|
+
warn_count: summary.warn,
|
|
381
|
+
info_count: summary.info,
|
|
382
|
+
circular_count: violationsByType.circular ?? 0,
|
|
383
|
+
orphan_count: violationsByType.orphan ?? 0,
|
|
384
|
+
layer_violation_count: violationsByType.layer ?? 0,
|
|
385
|
+
baseline_loaded: baseline !== null,
|
|
386
|
+
});
|
|
387
|
+
builder.setConfig({
|
|
388
|
+
config_file: config.config_file,
|
|
389
|
+
scope: config.scope,
|
|
390
|
+
severity_threshold: config.severity_threshold,
|
|
391
|
+
});
|
|
392
|
+
return builder.build();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCheck } from '../check.interface.js';
|
|
2
|
+
import type { CheckContext, GateResult } from '../../types/gate.types.js';
|
|
3
|
+
export declare class CommandSafetyCheck extends BaseCheck {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
private parser;
|
|
7
|
+
constructor();
|
|
8
|
+
run(context: CheckContext): Promise<GateResult>;
|
|
9
|
+
private getConfig;
|
|
10
|
+
private calculateScore;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=command-safety.check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-safety.check.d.ts","sourceRoot":"","sources":["../../../src/gate/checks/command-safety.check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AA4I1E,qBAAa,kBAAmB,SAAQ,SAAS;IAC/C,IAAI,SAAoB;IACxB,WAAW,SAAyD;IAEpE,OAAO,CAAC,MAAM,CAAgB;;IAOxB,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAsHrD,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,cAAc;CAWvB"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { BaseCheck } from '../check.interface.js';
|
|
2
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
3
|
+
import { CommandParser } from '../parsers/command-parser.js';
|
|
4
|
+
import { findMatchingRule, DEFAULT_GIT_RULES, DEFAULT_FILESYSTEM_RULES } from '../rules/index.js';
|
|
5
|
+
const log = createDebugger('check');
|
|
6
|
+
function extractCommandsFromPlan(context) {
|
|
7
|
+
const commands = [];
|
|
8
|
+
if (!context.plan) {
|
|
9
|
+
return commands;
|
|
10
|
+
}
|
|
11
|
+
for (const change of context.plan.proposed_changes) {
|
|
12
|
+
if (change.type === 'script_execute' && change.description) {
|
|
13
|
+
const codeBlockMatch = change.description.match(/```(?:bash|sh|shell)?\n([\s\S]*?)```/);
|
|
14
|
+
if (codeBlockMatch) {
|
|
15
|
+
const lines = codeBlockMatch[1]
|
|
16
|
+
.split('\n')
|
|
17
|
+
.filter((line) => line.trim() && !line.startsWith('#'));
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
commands.push({ command: line.trim(), source: change.path || 'script_execute' });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (!change.description.includes('\n')) {
|
|
23
|
+
commands.push({ command: change.description, source: change.path || 'script_execute' });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return commands;
|
|
28
|
+
}
|
|
29
|
+
function loadRules(config) {
|
|
30
|
+
let rules = [...DEFAULT_GIT_RULES, ...DEFAULT_FILESYSTEM_RULES];
|
|
31
|
+
if (config.rules?.disabled && config.rules.disabled.length > 0) {
|
|
32
|
+
const disabledSet = new Set(config.rules.disabled);
|
|
33
|
+
rules = rules.filter((r) => !disabledSet.has(r.id));
|
|
34
|
+
}
|
|
35
|
+
if (config.rules?.overrides && config.rules.overrides.length > 0) {
|
|
36
|
+
for (const override of config.rules.overrides) {
|
|
37
|
+
const ruleIndex = rules.findIndex((r) => r.id === override.id);
|
|
38
|
+
if (ruleIndex !== -1) {
|
|
39
|
+
if (override.action === 'disable') {
|
|
40
|
+
rules.splice(ruleIndex, 1);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
rules[ruleIndex] = {
|
|
44
|
+
...rules[ruleIndex],
|
|
45
|
+
...(override.action && { action: override.action }),
|
|
46
|
+
...(override.severity && { severity: override.severity }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (config.rules?.custom && config.rules.custom.length > 0) {
|
|
53
|
+
rules.push(...config.rules.custom);
|
|
54
|
+
}
|
|
55
|
+
return rules;
|
|
56
|
+
}
|
|
57
|
+
function formatBlockedMessage(blocked, outputConfig) {
|
|
58
|
+
if (blocked.length === 0) {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
const lines = [`Blocked ${blocked.length} dangerous command(s):`, ''];
|
|
62
|
+
for (let i = 0; i < blocked.length; i++) {
|
|
63
|
+
const finding = blocked[i];
|
|
64
|
+
lines.push(`${i + 1}. ${finding.command}`);
|
|
65
|
+
if (outputConfig.verbose) {
|
|
66
|
+
lines.push(` Reason: ${finding.reason}`);
|
|
67
|
+
}
|
|
68
|
+
if (outputConfig.showSuggestions && finding.suggestion) {
|
|
69
|
+
lines.push(` Suggestion: ${finding.suggestion}`);
|
|
70
|
+
}
|
|
71
|
+
if (outputConfig.showReferences && finding.references && finding.references.length > 0) {
|
|
72
|
+
lines.push(` Reference: ${finding.references[0]}`);
|
|
73
|
+
}
|
|
74
|
+
lines.push('');
|
|
75
|
+
}
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
function formatWarningMessage(warnings, outputConfig) {
|
|
79
|
+
if (warnings.length === 0) {
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
const lines = [`Found ${warnings.length} potentially dangerous command(s):`, ''];
|
|
83
|
+
for (let i = 0; i < warnings.length; i++) {
|
|
84
|
+
const finding = warnings[i];
|
|
85
|
+
lines.push(`${i + 1}. ${finding.command}`);
|
|
86
|
+
if (outputConfig.verbose) {
|
|
87
|
+
lines.push(` Reason: ${finding.reason}`);
|
|
88
|
+
}
|
|
89
|
+
if (outputConfig.showSuggestions && finding.suggestion) {
|
|
90
|
+
lines.push(` Suggestion: ${finding.suggestion}`);
|
|
91
|
+
}
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
export class CommandSafetyCheck extends BaseCheck {
|
|
97
|
+
name = 'command-safety';
|
|
98
|
+
description = 'Validates shell commands for destructive operations';
|
|
99
|
+
parser;
|
|
100
|
+
constructor() {
|
|
101
|
+
super();
|
|
102
|
+
this.parser = new CommandParser();
|
|
103
|
+
}
|
|
104
|
+
async run(context) {
|
|
105
|
+
log(`command-safety check starting, workspace=${context.workspace_root}`);
|
|
106
|
+
const config = this.getConfig(context);
|
|
107
|
+
if (!config.enabled) {
|
|
108
|
+
log('command-safety check disabled via config');
|
|
109
|
+
return this.createResult(true, 'Command safety check disabled', 100, { skipped: true });
|
|
110
|
+
}
|
|
111
|
+
const commandSources = extractCommandsFromPlan(context);
|
|
112
|
+
if (commandSources.length === 0) {
|
|
113
|
+
log('command-safety check: no commands found in plan');
|
|
114
|
+
return this.createSuccess('No commands to analyse', 100, {
|
|
115
|
+
summary: { total: 0, blocked: 0, warned: 0, allowed: 0 },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
log(`command-safety check: analysing ${commandSources.length} command source(s), strict=${config.strict}`);
|
|
119
|
+
const rules = loadRules(config);
|
|
120
|
+
log(`command-safety check: loaded ${rules.length} rules`);
|
|
121
|
+
const blocked = [];
|
|
122
|
+
const warnings = [];
|
|
123
|
+
let allowed = 0;
|
|
124
|
+
let totalAnalysed = 0;
|
|
125
|
+
for (const { command: rawCommand, source } of commandSources) {
|
|
126
|
+
const compoundResult = this.parser.parseCompound(rawCommand);
|
|
127
|
+
for (const parsed of compoundResult.commands) {
|
|
128
|
+
if (!parsed.command) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
totalAnalysed++;
|
|
132
|
+
const matchedRule = findMatchingRule(parsed, rules, {
|
|
133
|
+
strict: config.strict,
|
|
134
|
+
workingDirectory: config.workingDirectory,
|
|
135
|
+
cwd: context.workspace_root,
|
|
136
|
+
});
|
|
137
|
+
if (!matchedRule) {
|
|
138
|
+
allowed++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (matchedRule.action === 'allow') {
|
|
142
|
+
allowed++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const finding = {
|
|
146
|
+
command: compoundResult.isCompound ? `${parsed.raw} (from: ${rawCommand})` : parsed.raw,
|
|
147
|
+
ruleId: matchedRule.id,
|
|
148
|
+
category: matchedRule.category,
|
|
149
|
+
action: matchedRule.action,
|
|
150
|
+
severity: matchedRule.severity,
|
|
151
|
+
reason: matchedRule.reason,
|
|
152
|
+
suggestion: matchedRule.suggestion,
|
|
153
|
+
references: matchedRule.references,
|
|
154
|
+
source,
|
|
155
|
+
};
|
|
156
|
+
if (matchedRule.action === 'block') {
|
|
157
|
+
blocked.push(finding);
|
|
158
|
+
}
|
|
159
|
+
else if (matchedRule.action === 'warn') {
|
|
160
|
+
warnings.push(finding);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const summary = {
|
|
165
|
+
total: totalAnalysed,
|
|
166
|
+
blocked: blocked.length,
|
|
167
|
+
warned: warnings.length,
|
|
168
|
+
allowed,
|
|
169
|
+
};
|
|
170
|
+
const passed = blocked.length === 0;
|
|
171
|
+
const score = this.calculateScore(summary);
|
|
172
|
+
log('command-safety check result', {
|
|
173
|
+
passed,
|
|
174
|
+
score,
|
|
175
|
+
total: totalAnalysed,
|
|
176
|
+
blocked: blocked.length,
|
|
177
|
+
warned: warnings.length,
|
|
178
|
+
allowed,
|
|
179
|
+
});
|
|
180
|
+
let message;
|
|
181
|
+
if (passed && warnings.length === 0) {
|
|
182
|
+
message = `All ${summary.total} command(s) passed safety check`;
|
|
183
|
+
}
|
|
184
|
+
else if (passed) {
|
|
185
|
+
message = `${summary.total} command(s) analysed: ${warnings.length} warning(s)`;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
message = `Command safety check failed: ${blocked.length} blocked, ${warnings.length} warning(s)`;
|
|
189
|
+
}
|
|
190
|
+
return this.createResult(passed, message, score, {
|
|
191
|
+
blocked,
|
|
192
|
+
warnings,
|
|
193
|
+
summary,
|
|
194
|
+
config: {
|
|
195
|
+
strict: config.strict ?? false,
|
|
196
|
+
rulesCount: rules.length,
|
|
197
|
+
customRulesCount: config.rules?.custom?.length ?? 0,
|
|
198
|
+
disabledRulesCount: config.rules?.disabled?.length ?? 0,
|
|
199
|
+
},
|
|
200
|
+
formattedBlockedMessage: formatBlockedMessage(blocked, config.output),
|
|
201
|
+
formattedWarningMessage: formatWarningMessage(warnings, config.output),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
getConfig(context) {
|
|
205
|
+
const checkConfig = (context.check_config ?? {});
|
|
206
|
+
return {
|
|
207
|
+
enabled: checkConfig.enabled ?? true,
|
|
208
|
+
strict: checkConfig.strict ?? false,
|
|
209
|
+
rules: checkConfig.rules ?? {},
|
|
210
|
+
workingDirectory: {
|
|
211
|
+
allowDeleteInCwd: checkConfig.workingDirectory?.allowDeleteInCwd ?? false,
|
|
212
|
+
tempDirPatterns: checkConfig.workingDirectory?.tempDirPatterns ?? ['/tmp', '/var/tmp'],
|
|
213
|
+
},
|
|
214
|
+
output: {
|
|
215
|
+
verbose: checkConfig.output?.verbose ?? true,
|
|
216
|
+
showSuggestions: checkConfig.output?.showSuggestions ?? true,
|
|
217
|
+
showReferences: checkConfig.output?.showReferences ?? true,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
calculateScore(summary) {
|
|
222
|
+
if (summary.total === 0) {
|
|
223
|
+
return 100;
|
|
224
|
+
}
|
|
225
|
+
const blockedPenalty = summary.blocked * 25;
|
|
226
|
+
const warnedPenalty = summary.warned * 5;
|
|
227
|
+
const totalPenalty = blockedPenalty + warnedPenalty;
|
|
228
|
+
return Math.max(0, 100 - totalPenalty);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCheck } from '../check.interface.js';
|
|
2
|
+
import { CheckContext, GateResult } from '../../types/gate.types.js';
|
|
3
|
+
export declare class CoverageCheck extends BaseCheck {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
run(context: CheckContext): Promise<GateResult>;
|
|
7
|
+
private analyzeCoverage;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=coverage.check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.check.d.ts","sourceRoot":"","sources":["../../../src/gate/checks/coverage.check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAsCrE,qBAAa,aAAc,SAAQ,SAAS;IAC1C,IAAI,SAAc;IAClB,WAAW,SAAoC;IAEzC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAoDrD,OAAO,CAAC,eAAe;CAyCxB"}
|