@eddacraft/anvil-core 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/antipattern/index.d.ts +11 -0
- package/dist/antipattern/index.d.ts.map +1 -0
- package/dist/antipattern/index.js +31 -0
- package/dist/antipattern/patterns-css.d.ts +17 -0
- package/dist/antipattern/patterns-css.d.ts.map +1 -0
- package/dist/antipattern/patterns-css.js +72 -0
- package/dist/antipattern/patterns-html.d.ts +21 -0
- package/dist/antipattern/patterns-html.d.ts.map +1 -0
- package/dist/antipattern/patterns-html.js +139 -0
- package/dist/antipattern/patterns.d.ts +72 -0
- package/dist/antipattern/patterns.d.ts.map +1 -0
- package/dist/antipattern/patterns.js +301 -0
- package/dist/antipattern/scanner.d.ts +32 -0
- package/dist/antipattern/scanner.d.ts.map +1 -0
- package/dist/antipattern/scanner.js +89 -0
- package/dist/antipattern/types.d.ts +318 -0
- package/dist/antipattern/types.d.ts.map +1 -0
- package/dist/antipattern/types.js +278 -0
- package/dist/architecture/analyzer.d.ts +123 -0
- package/dist/architecture/analyzer.d.ts.map +1 -0
- package/dist/architecture/analyzer.js +321 -0
- package/dist/architecture/baseline.d.ts +112 -0
- package/dist/architecture/baseline.d.ts.map +1 -0
- package/dist/architecture/baseline.js +245 -0
- package/dist/architecture/compiler.d.ts +24 -0
- package/dist/architecture/compiler.d.ts.map +1 -0
- package/dist/architecture/compiler.js +57 -0
- package/dist/architecture/context.d.ts +129 -0
- package/dist/architecture/context.d.ts.map +1 -0
- package/dist/architecture/context.js +116 -0
- package/dist/architecture/dc-generator.d.ts +9 -0
- package/dist/architecture/dc-generator.d.ts.map +1 -0
- package/dist/architecture/dc-generator.js +220 -0
- package/dist/architecture/definition-schema.d.ts +128 -0
- package/dist/architecture/definition-schema.d.ts.map +1 -0
- package/dist/architecture/definition-schema.js +94 -0
- package/dist/architecture/edge-detector-html.d.ts +6 -0
- package/dist/architecture/edge-detector-html.d.ts.map +1 -0
- package/dist/architecture/edge-detector-html.js +5 -0
- package/dist/architecture/edge-detector-web.d.ts +32 -0
- package/dist/architecture/edge-detector-web.d.ts.map +1 -0
- package/dist/architecture/edge-detector-web.js +133 -0
- package/dist/architecture/edge-detector.d.ts +116 -0
- package/dist/architecture/edge-detector.d.ts.map +1 -0
- package/dist/architecture/edge-detector.js +229 -0
- package/dist/architecture/entry-detector.d.ts +44 -0
- package/dist/architecture/entry-detector.d.ts.map +1 -0
- package/dist/architecture/entry-detector.js +263 -0
- package/dist/architecture/index.d.ts +21 -0
- package/dist/architecture/index.d.ts.map +1 -0
- package/dist/architecture/index.js +48 -0
- package/dist/architecture/layer-detector.d.ts +60 -0
- package/dist/architecture/layer-detector.d.ts.map +1 -0
- package/dist/architecture/layer-detector.js +331 -0
- package/dist/architecture/rego-generator.d.ts +25 -0
- package/dist/architecture/rego-generator.d.ts.map +1 -0
- package/dist/architecture/rego-generator.js +229 -0
- package/dist/architecture/templates/index.d.ts +39 -0
- package/dist/architecture/templates/index.d.ts.map +1 -0
- package/dist/architecture/templates/index.js +124 -0
- package/dist/architecture/types.d.ts +280 -0
- package/dist/architecture/types.d.ts.map +1 -0
- package/dist/architecture/types.js +269 -0
- package/dist/architecture/yaml-parser.d.ts +13 -0
- package/dist/architecture/yaml-parser.d.ts.map +1 -0
- package/dist/architecture/yaml-parser.js +234 -0
- package/dist/config/constants.d.ts +9 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +20 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +8 -0
- package/dist/config/loader.d.ts +41 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +76 -0
- package/dist/config/nudge-config.d.ts +35 -0
- package/dist/config/nudge-config.d.ts.map +1 -0
- package/dist/config/nudge-config.js +34 -0
- package/dist/config/types.d.ts +30 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +4 -0
- package/dist/contracts/index.d.ts +14 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +13 -0
- package/dist/contracts/schemas/aps.schema.d.ts +269 -0
- package/dist/contracts/schemas/aps.schema.d.ts.map +1 -0
- package/dist/contracts/schemas/aps.schema.js +183 -0
- package/dist/contracts/schemas/index.d.ts +12 -0
- package/dist/contracts/schemas/index.d.ts.map +1 -0
- package/dist/contracts/schemas/index.js +14 -0
- package/dist/contracts/schemas/json-schema.d.ts +14 -0
- package/dist/contracts/schemas/json-schema.d.ts.map +1 -0
- package/dist/contracts/schemas/json-schema.js +31 -0
- package/dist/contracts/schemas/warning.schema.d.ts +171 -0
- package/dist/contracts/schemas/warning.schema.d.ts.map +1 -0
- package/dist/contracts/schemas/warning.schema.js +123 -0
- package/dist/contracts/types/gate.types.d.ts +194 -0
- package/dist/contracts/types/gate.types.d.ts.map +1 -0
- package/dist/contracts/types/gate.types.js +19 -0
- package/dist/contracts/types/index.d.ts +9 -0
- package/dist/contracts/types/index.d.ts.map +1 -0
- package/dist/contracts/types/index.js +8 -0
- package/dist/crypto/hash.d.ts +47 -0
- package/dist/crypto/hash.d.ts.map +1 -0
- package/dist/crypto/hash.js +110 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +6 -0
- package/dist/drift/index.d.ts +6 -0
- package/dist/drift/index.d.ts.map +1 -0
- package/dist/drift/index.js +5 -0
- package/dist/drift/report-generator.d.ts +21 -0
- package/dist/drift/report-generator.d.ts.map +1 -0
- package/dist/drift/report-generator.js +240 -0
- package/dist/drift/snapshot-capture.d.ts +26 -0
- package/dist/drift/snapshot-capture.d.ts.map +1 -0
- package/dist/drift/snapshot-capture.js +195 -0
- package/dist/drift/snapshot-compare.d.ts +50 -0
- package/dist/drift/snapshot-compare.d.ts.map +1 -0
- package/dist/drift/snapshot-compare.js +142 -0
- package/dist/drift/snapshot-schema.d.ts +197 -0
- package/dist/drift/snapshot-schema.d.ts.map +1 -0
- package/dist/drift/snapshot-schema.js +193 -0
- package/dist/drift/snapshot-storage.d.ts +25 -0
- package/dist/drift/snapshot-storage.d.ts.map +1 -0
- package/dist/drift/snapshot-storage.js +179 -0
- package/dist/explain/antipattern-explainer.d.ts +4 -0
- package/dist/explain/antipattern-explainer.d.ts.map +1 -0
- package/dist/explain/antipattern-explainer.js +196 -0
- package/dist/explain/boundary-explainer.d.ts +5 -0
- package/dist/explain/boundary-explainer.d.ts.map +1 -0
- package/dist/explain/boundary-explainer.js +261 -0
- package/dist/explain/explain-service.d.ts +19 -0
- package/dist/explain/explain-service.d.ts.map +1 -0
- package/dist/explain/explain-service.js +106 -0
- package/dist/explain/index.d.ts +7 -0
- package/dist/explain/index.d.ts.map +1 -0
- package/dist/explain/index.js +5 -0
- package/dist/explain/template-loader.d.ts +9 -0
- package/dist/explain/template-loader.d.ts.map +1 -0
- package/dist/explain/template-loader.js +51 -0
- package/dist/explain/types.d.ts +46 -0
- package/dist/explain/types.d.ts.map +1 -0
- package/dist/explain/types.js +31 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/provenance/collector.d.ts +86 -0
- package/dist/provenance/collector.d.ts.map +1 -0
- package/dist/provenance/collector.js +425 -0
- package/dist/provenance/git-ai-standard/git-notes.d.ts +85 -0
- package/dist/provenance/git-ai-standard/git-notes.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/git-notes.js +292 -0
- package/dist/provenance/git-ai-standard/index.d.ts +44 -0
- package/dist/provenance/git-ai-standard/index.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/index.js +47 -0
- package/dist/provenance/git-ai-standard/serializer.d.ts +54 -0
- package/dist/provenance/git-ai-standard/serializer.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/serializer.js +224 -0
- package/dist/provenance/git-ai-standard/session.d.ts +51 -0
- package/dist/provenance/git-ai-standard/session.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/session.js +118 -0
- package/dist/provenance/git-ai-standard/types.d.ts +173 -0
- package/dist/provenance/git-ai-standard/types.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/types.js +109 -0
- package/dist/provenance/index.d.ts +5 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/provenance/index.js +6 -0
- package/dist/provenance/store.d.ts +83 -0
- package/dist/provenance/store.d.ts.map +1 -0
- package/dist/provenance/store.js +248 -0
- package/dist/provenance/types.d.ts +160 -0
- package/dist/provenance/types.d.ts.map +1 -0
- package/dist/provenance/types.js +112 -0
- package/dist/suppression/index.d.ts +4 -0
- package/dist/suppression/index.d.ts.map +1 -0
- package/dist/suppression/index.js +3 -0
- package/dist/suppression/parser.d.ts +31 -0
- package/dist/suppression/parser.d.ts.map +1 -0
- package/dist/suppression/parser.js +219 -0
- package/dist/suppression/service.d.ts +29 -0
- package/dist/suppression/service.d.ts.map +1 -0
- package/dist/suppression/service.js +132 -0
- package/dist/suppression/store.d.ts +61 -0
- package/dist/suppression/store.d.ts.map +1 -0
- package/dist/suppression/store.js +169 -0
- package/dist/utils/debug.d.ts +48 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +100 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/path-safety.d.ts +21 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +49 -0
- package/dist/utils/severity.d.ts +37 -0
- package/dist/utils/severity.d.ts.map +1 -0
- package/dist/utils/severity.js +22 -0
- package/dist/validation/aps-validator.d.ts +66 -0
- package/dist/validation/aps-validator.d.ts.map +1 -0
- package/dist/validation/aps-validator.js +173 -0
- package/dist/validation/errors.d.ts +52 -0
- package/dist/validation/errors.d.ts.map +1 -0
- package/dist/validation/errors.js +115 -0
- package/dist/validation/index.d.ts +8 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +13 -0
- package/dist/warnings/index.d.ts +2 -0
- package/dist/warnings/index.d.ts.map +1 -0
- package/dist/warnings/index.js +1 -0
- package/dist/warnings/warning-id.d.ts +180 -0
- package/dist/warnings/warning-id.d.ts.map +1 -0
- package/dist/warnings/warning-id.js +257 -0
- package/package.json +79 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createDebugger } from '../utils/debug.js';
|
|
3
|
+
const debug = createDebugger('suppression');
|
|
4
|
+
export const ParsedSuppressionSchema = z.object({
|
|
5
|
+
warningId: z
|
|
6
|
+
.string()
|
|
7
|
+
.regex(/^(AP|ARCH|BOUND)-\d{3}$/)
|
|
8
|
+
.describe('Warning ID (e.g., AP-001, ARCH-001)'),
|
|
9
|
+
reason: z.string().min(1).describe('Human-provided reason for suppression'),
|
|
10
|
+
expiresAt: z.date().optional().describe('Expiry date for time-boxed suppressions'),
|
|
11
|
+
line: z.number().int().positive().describe('Line number of the comment (1-based)'),
|
|
12
|
+
column: z.number().int().nonnegative().optional().describe('Column number (0-based)'),
|
|
13
|
+
scope: z.enum(['line', 'statement', 'file']).describe('Suppression scope'),
|
|
14
|
+
raw: z.string().describe('Raw comment text'),
|
|
15
|
+
});
|
|
16
|
+
// Pattern: @anvil-ignore <ID>: <reason> (reason can be empty for validation)
|
|
17
|
+
const IGNORE_PATTERN = /@anvil-ignore\s+((?:AP|ARCH|BOUND)-\d{3}):\s*(.*)/;
|
|
18
|
+
// Pattern: @anvil-ignore-until YYYY-MM-DD <ID>: <reason>
|
|
19
|
+
const IGNORE_UNTIL_PATTERN = /@anvil-ignore-until\s+(\d{4}-\d{2}-\d{2})\s+((?:AP|ARCH|BOUND)-\d{3}):\s*(.*)/;
|
|
20
|
+
function determineScope(line, column, lineContent, previousLineHasCode) {
|
|
21
|
+
if (line <= 5 && !previousLineHasCode) {
|
|
22
|
+
const trimmed = lineContent.trim();
|
|
23
|
+
if (trimmed.startsWith('//') ||
|
|
24
|
+
trimmed.startsWith('/*') ||
|
|
25
|
+
trimmed.startsWith('/**') ||
|
|
26
|
+
trimmed.startsWith('<!--')) {
|
|
27
|
+
return 'file';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const beforeComment = lineContent.substring(0, column).trim();
|
|
31
|
+
if (beforeComment.length > 0 && !beforeComment.startsWith('//')) {
|
|
32
|
+
return 'line';
|
|
33
|
+
}
|
|
34
|
+
return 'statement';
|
|
35
|
+
}
|
|
36
|
+
function hasCode(line, insideHtmlComment) {
|
|
37
|
+
if (insideHtmlComment)
|
|
38
|
+
return false;
|
|
39
|
+
const trimmed = line.trim();
|
|
40
|
+
if (trimmed.length === 0)
|
|
41
|
+
return false;
|
|
42
|
+
if (trimmed.startsWith('//'))
|
|
43
|
+
return false;
|
|
44
|
+
if (trimmed.startsWith('/*') && trimmed.endsWith('*/'))
|
|
45
|
+
return false;
|
|
46
|
+
if (trimmed.startsWith('/**') && trimmed.endsWith('*/'))
|
|
47
|
+
return false;
|
|
48
|
+
if (trimmed.startsWith('*') && !trimmed.startsWith('*/'))
|
|
49
|
+
return false;
|
|
50
|
+
if (trimmed.startsWith('<!--') && trimmed.includes('-->'))
|
|
51
|
+
return false;
|
|
52
|
+
if (trimmed.startsWith('<!--'))
|
|
53
|
+
return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function updateHtmlCommentState(line, insideHtmlComment) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (insideHtmlComment) {
|
|
59
|
+
return !trimmed.includes('-->');
|
|
60
|
+
}
|
|
61
|
+
if (trimmed.includes('<!--') && !trimmed.includes('-->')) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
function extractSuppressionComment(line) {
|
|
67
|
+
const singleMatch = line.match(/\/\/\s*(@anvil-ignore[^\n]*)/);
|
|
68
|
+
if (singleMatch) {
|
|
69
|
+
return {
|
|
70
|
+
comment: singleMatch[1],
|
|
71
|
+
column: line.indexOf('//'),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const blockMatch = line.match(/\/\*\*?\s*(@anvil-ignore[^*]*)\s*\*\//);
|
|
75
|
+
if (blockMatch) {
|
|
76
|
+
return {
|
|
77
|
+
comment: blockMatch[1],
|
|
78
|
+
column: line.indexOf('/*'),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const htmlMatch = line.match(/<!--\s*(@anvil-ignore.*?)\s*-->/);
|
|
82
|
+
if (htmlMatch) {
|
|
83
|
+
return {
|
|
84
|
+
comment: htmlMatch[1],
|
|
85
|
+
column: line.indexOf('<!--'),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function parseSuppression(comment, line, column, scope, raw) {
|
|
91
|
+
const untilMatch = comment.match(IGNORE_UNTIL_PATTERN);
|
|
92
|
+
if (untilMatch) {
|
|
93
|
+
const [, dateStr, warningId, reason] = untilMatch;
|
|
94
|
+
const trimmedReason = reason.trim();
|
|
95
|
+
if (!trimmedReason) {
|
|
96
|
+
return {
|
|
97
|
+
line,
|
|
98
|
+
column,
|
|
99
|
+
message: 'Suppression requires a non-empty reason',
|
|
100
|
+
raw,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const expiresAt = new Date(dateStr);
|
|
104
|
+
if (isNaN(expiresAt.getTime())) {
|
|
105
|
+
return {
|
|
106
|
+
line,
|
|
107
|
+
column,
|
|
108
|
+
message: `Invalid date format: ${dateStr}. Use YYYY-MM-DD`,
|
|
109
|
+
raw,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
warningId,
|
|
114
|
+
reason: trimmedReason,
|
|
115
|
+
expiresAt,
|
|
116
|
+
line,
|
|
117
|
+
column,
|
|
118
|
+
scope,
|
|
119
|
+
raw,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const ignoreMatch = comment.match(IGNORE_PATTERN);
|
|
123
|
+
if (ignoreMatch) {
|
|
124
|
+
const [, warningId, reason] = ignoreMatch;
|
|
125
|
+
const trimmedReason = reason.trim();
|
|
126
|
+
if (!trimmedReason) {
|
|
127
|
+
return {
|
|
128
|
+
line,
|
|
129
|
+
column,
|
|
130
|
+
message: 'Suppression requires a non-empty reason',
|
|
131
|
+
raw,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
warningId,
|
|
136
|
+
reason: trimmedReason,
|
|
137
|
+
line,
|
|
138
|
+
column,
|
|
139
|
+
scope,
|
|
140
|
+
raw,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
line,
|
|
145
|
+
column,
|
|
146
|
+
message: 'Invalid suppression format. Expected: @anvil-ignore <ID>: <reason> or @anvil-ignore-until <DATE> <ID>: <reason>',
|
|
147
|
+
raw,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export function parseSuppressions(content, _filePath) {
|
|
151
|
+
debug('parsing suppressions', { filePath: _filePath, contentLength: content.length });
|
|
152
|
+
const lines = content.split('\n');
|
|
153
|
+
const suppressions = [];
|
|
154
|
+
const errors = [];
|
|
155
|
+
let previousLineHasCode = false;
|
|
156
|
+
let insideHtmlComment = false;
|
|
157
|
+
for (let i = 0; i < lines.length; i++) {
|
|
158
|
+
const lineNumber = i + 1;
|
|
159
|
+
const lineContent = lines[i];
|
|
160
|
+
if (!lineContent.includes('@anvil-ignore')) {
|
|
161
|
+
previousLineHasCode = previousLineHasCode || hasCode(lineContent, insideHtmlComment);
|
|
162
|
+
insideHtmlComment = updateHtmlCommentState(lineContent, insideHtmlComment);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const extracted = extractSuppressionComment(lineContent);
|
|
166
|
+
if (!extracted) {
|
|
167
|
+
previousLineHasCode = previousLineHasCode || hasCode(lineContent, insideHtmlComment);
|
|
168
|
+
insideHtmlComment = updateHtmlCommentState(lineContent, insideHtmlComment);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const { comment, column } = extracted;
|
|
172
|
+
const scope = determineScope(lineNumber, column, lineContent, previousLineHasCode);
|
|
173
|
+
const result = parseSuppression(comment, lineNumber, column, scope, lineContent.trim());
|
|
174
|
+
if ('message' in result) {
|
|
175
|
+
errors.push(result);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
suppressions.push(result);
|
|
179
|
+
}
|
|
180
|
+
previousLineHasCode = previousLineHasCode || hasCode(lineContent, insideHtmlComment);
|
|
181
|
+
insideHtmlComment = updateHtmlCommentState(lineContent, insideHtmlComment);
|
|
182
|
+
}
|
|
183
|
+
if (suppressions.length > 0 || errors.length > 0) {
|
|
184
|
+
debug('suppressions parsed', { found: suppressions.length, errors: errors.length });
|
|
185
|
+
}
|
|
186
|
+
return { suppressions, errors };
|
|
187
|
+
}
|
|
188
|
+
export function isExpired(suppression, now = new Date()) {
|
|
189
|
+
if (!suppression.expiresAt) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return suppression.expiresAt < now;
|
|
193
|
+
}
|
|
194
|
+
export function suppressionMatches(suppression, warningId, warningLine) {
|
|
195
|
+
if (suppression.warningId !== warningId) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
switch (suppression.scope) {
|
|
199
|
+
case 'file':
|
|
200
|
+
return true;
|
|
201
|
+
case 'line':
|
|
202
|
+
return suppression.line === warningLine;
|
|
203
|
+
case 'statement':
|
|
204
|
+
return warningLine === suppression.line + 1;
|
|
205
|
+
default:
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function findMatchingSuppression(suppressions, warningId, warningLine, now = new Date()) {
|
|
210
|
+
for (const suppression of suppressions) {
|
|
211
|
+
if (isExpired(suppression, now)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (suppressionMatches(suppression, warningId, warningLine)) {
|
|
215
|
+
return suppression;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ParsedSuppression } from './parser.js';
|
|
2
|
+
import { SuppressionStore } from './store.js';
|
|
3
|
+
import type { Warning } from '../antipattern/types.js';
|
|
4
|
+
export interface SuppressionStats {
|
|
5
|
+
total: number;
|
|
6
|
+
active: number;
|
|
7
|
+
expired: number;
|
|
8
|
+
appliedThisRun: number;
|
|
9
|
+
}
|
|
10
|
+
export interface FileSuppressions {
|
|
11
|
+
file: string;
|
|
12
|
+
suppressions: ParsedSuppression[];
|
|
13
|
+
}
|
|
14
|
+
export declare class SuppressionService {
|
|
15
|
+
private store;
|
|
16
|
+
private fileCache;
|
|
17
|
+
private workspaceRoot;
|
|
18
|
+
constructor(workspaceRoot: string, store?: SuppressionStore);
|
|
19
|
+
private normalizeToRelative;
|
|
20
|
+
initialize(): Promise<void>;
|
|
21
|
+
parseFileSuppressions(filePath: string): Promise<ParsedSuppression[]>;
|
|
22
|
+
applyToWarnings(warnings: Warning[], filePath: string, now?: Date): Warning[];
|
|
23
|
+
processFiles(files: string[]): Promise<FileSuppressions[]>;
|
|
24
|
+
applyToAllWarnings(warnings: Warning[], now?: Date): Warning[];
|
|
25
|
+
getStats(warnings: Warning[], now?: Date): SuppressionStats;
|
|
26
|
+
clearCache(): void;
|
|
27
|
+
getStore(): SuppressionStore;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/suppression/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAe,MAAM,yBAAyB,CAAC;AAKpE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,iBAAiB,EAAE,CAAC;CACnC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,aAAa,CAAS;gBAElB,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,gBAAgB;IAM3D,OAAO,CAAC,mBAAmB;IAarB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAsB3E,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,EAAE;IAgCnF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAehE,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,EAAE;IAoB1E,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,GAAE,IAAiB,GAAG,gBAAgB;IA2BvE,UAAU,IAAI,IAAI;IAIlB,QAAQ,IAAI,gBAAgB;CAG7B"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { parseSuppressions, findMatchingSuppression } from './parser.js';
|
|
4
|
+
import { SuppressionStore } from './store.js';
|
|
5
|
+
import { createDebugger } from '../utils/debug.js';
|
|
6
|
+
const debug = createDebugger('suppression');
|
|
7
|
+
export class SuppressionService {
|
|
8
|
+
store;
|
|
9
|
+
fileCache = new Map();
|
|
10
|
+
workspaceRoot;
|
|
11
|
+
constructor(workspaceRoot, store) {
|
|
12
|
+
this.workspaceRoot = workspaceRoot;
|
|
13
|
+
const anvilDir = path.join(workspaceRoot, '.anvil');
|
|
14
|
+
this.store = store ?? new SuppressionStore(anvilDir);
|
|
15
|
+
}
|
|
16
|
+
normalizeToRelative(filePath) {
|
|
17
|
+
if (path.isAbsolute(filePath)) {
|
|
18
|
+
const withSep = this.workspaceRoot.endsWith(path.sep)
|
|
19
|
+
? this.workspaceRoot
|
|
20
|
+
: this.workspaceRoot + path.sep;
|
|
21
|
+
if (filePath.startsWith(withSep)) {
|
|
22
|
+
return filePath.slice(withSep.length);
|
|
23
|
+
}
|
|
24
|
+
return path.relative(this.workspaceRoot, filePath);
|
|
25
|
+
}
|
|
26
|
+
return filePath;
|
|
27
|
+
}
|
|
28
|
+
async initialize() {
|
|
29
|
+
debug('initializing SuppressionService', { workspaceRoot: this.workspaceRoot });
|
|
30
|
+
await this.store.load();
|
|
31
|
+
}
|
|
32
|
+
async parseFileSuppressions(filePath) {
|
|
33
|
+
const normalizedPath = this.normalizeToRelative(filePath);
|
|
34
|
+
const cached = this.fileCache.get(normalizedPath);
|
|
35
|
+
if (cached) {
|
|
36
|
+
return cached;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
40
|
+
? filePath
|
|
41
|
+
: path.join(this.workspaceRoot, filePath);
|
|
42
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
43
|
+
const result = parseSuppressions(content, normalizedPath);
|
|
44
|
+
this.fileCache.set(normalizedPath, result.suppressions);
|
|
45
|
+
return result.suppressions;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
applyToWarnings(warnings, filePath, now = new Date()) {
|
|
52
|
+
const normalizedPath = this.normalizeToRelative(filePath);
|
|
53
|
+
const suppressions = this.fileCache.get(normalizedPath) ?? [];
|
|
54
|
+
return warnings.map((warning) => {
|
|
55
|
+
if (warning.suppressed) {
|
|
56
|
+
return warning;
|
|
57
|
+
}
|
|
58
|
+
const normalizedWarningFile = this.normalizeToRelative(warning.location.file);
|
|
59
|
+
if (normalizedWarningFile !== normalizedPath) {
|
|
60
|
+
return warning;
|
|
61
|
+
}
|
|
62
|
+
const match = findMatchingSuppression(suppressions, warning.id, warning.location.line, now);
|
|
63
|
+
if (!match) {
|
|
64
|
+
return warning;
|
|
65
|
+
}
|
|
66
|
+
const suppression = {
|
|
67
|
+
reason: match.reason,
|
|
68
|
+
scope: match.scope,
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
...warning,
|
|
72
|
+
suppressed: suppression,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async processFiles(files) {
|
|
77
|
+
debug('processing files for suppressions', { fileCount: files.length });
|
|
78
|
+
const results = [];
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const normalizedFile = this.normalizeToRelative(file);
|
|
81
|
+
const suppressions = await this.parseFileSuppressions(file);
|
|
82
|
+
if (suppressions.length > 0) {
|
|
83
|
+
results.push({ file: normalizedFile, suppressions });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
applyToAllWarnings(warnings, now = new Date()) {
|
|
89
|
+
const fileGroups = new Map();
|
|
90
|
+
for (const warning of warnings) {
|
|
91
|
+
const normalizedFile = this.normalizeToRelative(warning.location.file);
|
|
92
|
+
const group = fileGroups.get(normalizedFile) ?? [];
|
|
93
|
+
group.push(warning);
|
|
94
|
+
fileGroups.set(normalizedFile, group);
|
|
95
|
+
}
|
|
96
|
+
const result = [];
|
|
97
|
+
for (const [file, fileWarnings] of fileGroups) {
|
|
98
|
+
const processed = this.applyToWarnings(fileWarnings, file, now);
|
|
99
|
+
result.push(...processed);
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
getStats(warnings, now = new Date()) {
|
|
104
|
+
const allSuppressions = [];
|
|
105
|
+
for (const suppressions of this.fileCache.values()) {
|
|
106
|
+
allSuppressions.push(...suppressions);
|
|
107
|
+
}
|
|
108
|
+
let expired = 0;
|
|
109
|
+
let active = 0;
|
|
110
|
+
for (const s of allSuppressions) {
|
|
111
|
+
if (s.expiresAt && s.expiresAt < now) {
|
|
112
|
+
expired++;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
active++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const appliedThisRun = warnings.filter((w) => w.suppressed).length;
|
|
119
|
+
return {
|
|
120
|
+
total: allSuppressions.length,
|
|
121
|
+
active,
|
|
122
|
+
expired,
|
|
123
|
+
appliedThisRun,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
clearCache() {
|
|
127
|
+
this.fileCache.clear();
|
|
128
|
+
}
|
|
129
|
+
getStore() {
|
|
130
|
+
return this.store;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { SuppressionRecordSchema } from '../antipattern/types.js';
|
|
3
|
+
import type { ParsedSuppression } from './parser.js';
|
|
4
|
+
export type SuppressionRecord = z.infer<typeof SuppressionRecordSchema>;
|
|
5
|
+
export declare const SuppressionStoreDataSchema: z.ZodObject<{
|
|
6
|
+
version: z.ZodLiteral<1>;
|
|
7
|
+
suppressions: z.ZodArray<z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
pattern_id: z.ZodString;
|
|
10
|
+
file: z.ZodString;
|
|
11
|
+
line: z.ZodNumber;
|
|
12
|
+
reason: z.ZodString;
|
|
13
|
+
author: z.ZodOptional<z.ZodString>;
|
|
14
|
+
timestamp: z.ZodString;
|
|
15
|
+
commit: z.ZodOptional<z.ZodString>;
|
|
16
|
+
scope: z.ZodEnum<{
|
|
17
|
+
file: "file";
|
|
18
|
+
line: "line";
|
|
19
|
+
statement: "statement";
|
|
20
|
+
import: "import";
|
|
21
|
+
}>;
|
|
22
|
+
expires_at: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
lastUpdated: z.ZodString;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export type SuppressionStoreData = z.infer<typeof SuppressionStoreDataSchema>;
|
|
27
|
+
export interface SuppressionMatch {
|
|
28
|
+
record: SuppressionRecord;
|
|
29
|
+
isExpired: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare class SuppressionStore {
|
|
32
|
+
private data;
|
|
33
|
+
private filePath;
|
|
34
|
+
private loaded;
|
|
35
|
+
constructor(anvilDir: string);
|
|
36
|
+
load(): Promise<void>;
|
|
37
|
+
save(): Promise<void>;
|
|
38
|
+
add(record: SuppressionRecord): void;
|
|
39
|
+
remove(id: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a warning is suppressed at the given location.
|
|
42
|
+
* Returns null if no matching suppression OR if the matching suppression is expired.
|
|
43
|
+
* Use findSuppressionMatch() if you need to know about expired matches.
|
|
44
|
+
*/
|
|
45
|
+
isSuppressed(warningId: string, file: string, line: number, now?: Date): SuppressionMatch | null;
|
|
46
|
+
/**
|
|
47
|
+
* Find a suppression match, including expired ones.
|
|
48
|
+
* Use this when you need to report on expired suppressions.
|
|
49
|
+
*/
|
|
50
|
+
findSuppressionMatch(warningId: string, file: string, line: number, now?: Date): SuppressionMatch | null;
|
|
51
|
+
private matchesScope;
|
|
52
|
+
private isRecordExpired;
|
|
53
|
+
getAll(): SuppressionRecord[];
|
|
54
|
+
getByFile(file: string): SuppressionRecord[];
|
|
55
|
+
getExpired(now?: Date): SuppressionRecord[];
|
|
56
|
+
pruneExpired(now?: Date): number;
|
|
57
|
+
createRecordFromParsed(parsed: ParsedSuppression, file: string, gitCommit?: string): SuppressionRecord;
|
|
58
|
+
get isLoaded(): boolean;
|
|
59
|
+
get count(): number;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/suppression/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;iBAIrC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;CACpB;AAMD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,QAAQ,EAAE,MAAM;IAStB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAUpC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAS3B;;;;OAIG;IACH,YAAY,CACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,IAAiB,GACrB,gBAAgB,GAAG,IAAI;IAW1B;;;OAGG;IACH,oBAAoB,CAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,IAAiB,GACrB,gBAAgB,GAAG,IAAI;IAgB1B,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,eAAe;IASvB,MAAM,IAAI,iBAAiB,EAAE;IAI7B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAI5C,UAAU,CAAC,GAAG,GAAE,IAAiB,GAAG,iBAAiB,EAAE;IAIvD,YAAY,CAAC,GAAG,GAAE,IAAiB,GAAG,MAAM;IAQ5C,sBAAsB,CACpB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,iBAAiB;IAwBpB,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;CACF"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { SuppressionRecordSchema } from '../antipattern/types.js';
|
|
5
|
+
import { createDebugger } from '../utils/debug.js';
|
|
6
|
+
const debug = createDebugger('suppression');
|
|
7
|
+
export const SuppressionStoreDataSchema = z.object({
|
|
8
|
+
version: z.literal(1),
|
|
9
|
+
suppressions: z.array(SuppressionRecordSchema),
|
|
10
|
+
lastUpdated: z.string().datetime(),
|
|
11
|
+
});
|
|
12
|
+
function generateSuppressionId(file, line, patternId) {
|
|
13
|
+
return `${file}:${line}:${patternId}`;
|
|
14
|
+
}
|
|
15
|
+
export class SuppressionStore {
|
|
16
|
+
data;
|
|
17
|
+
filePath;
|
|
18
|
+
loaded = false;
|
|
19
|
+
constructor(anvilDir) {
|
|
20
|
+
this.filePath = path.join(anvilDir, 'suppressions.json');
|
|
21
|
+
this.data = {
|
|
22
|
+
version: 1,
|
|
23
|
+
suppressions: [],
|
|
24
|
+
lastUpdated: new Date().toISOString(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async load() {
|
|
28
|
+
debug('loading suppression store from', this.filePath);
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(this.filePath, 'utf-8');
|
|
31
|
+
const parsed = JSON.parse(content);
|
|
32
|
+
const result = SuppressionStoreDataSchema.safeParse(parsed);
|
|
33
|
+
if (result.success) {
|
|
34
|
+
debug('suppression store loaded', { count: result.data.suppressions.length });
|
|
35
|
+
this.data = result.data;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.data = {
|
|
39
|
+
version: 1,
|
|
40
|
+
suppressions: [],
|
|
41
|
+
lastUpdated: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
this.data = {
|
|
47
|
+
version: 1,
|
|
48
|
+
suppressions: [],
|
|
49
|
+
lastUpdated: new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
this.loaded = true;
|
|
53
|
+
}
|
|
54
|
+
async save() {
|
|
55
|
+
debug('saving suppression store', { count: this.data.suppressions.length });
|
|
56
|
+
this.data.lastUpdated = new Date().toISOString();
|
|
57
|
+
const dir = path.dirname(this.filePath);
|
|
58
|
+
await fs.mkdir(dir, { recursive: true });
|
|
59
|
+
await fs.writeFile(this.filePath, JSON.stringify(this.data, null, 2), 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
add(record) {
|
|
62
|
+
const existingIndex = this.data.suppressions.findIndex((s) => s.id === record.id);
|
|
63
|
+
if (existingIndex >= 0) {
|
|
64
|
+
this.data.suppressions[existingIndex] = record;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.data.suppressions.push(record);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
remove(id) {
|
|
71
|
+
const index = this.data.suppressions.findIndex((s) => s.id === id);
|
|
72
|
+
if (index >= 0) {
|
|
73
|
+
this.data.suppressions.splice(index, 1);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a warning is suppressed at the given location.
|
|
80
|
+
* Returns null if no matching suppression OR if the matching suppression is expired.
|
|
81
|
+
* Use findSuppressionMatch() if you need to know about expired matches.
|
|
82
|
+
*/
|
|
83
|
+
isSuppressed(warningId, file, line, now = new Date()) {
|
|
84
|
+
const match = this.findSuppressionMatch(warningId, file, line, now);
|
|
85
|
+
// Per acceptance criteria: expired suppressions should NOT suppress warnings
|
|
86
|
+
if (match && match.isExpired) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return match;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Find a suppression match, including expired ones.
|
|
93
|
+
* Use this when you need to report on expired suppressions.
|
|
94
|
+
*/
|
|
95
|
+
findSuppressionMatch(warningId, file, line, now = new Date()) {
|
|
96
|
+
for (const record of this.data.suppressions) {
|
|
97
|
+
if (record.file !== file)
|
|
98
|
+
continue;
|
|
99
|
+
if (record.pattern_id !== warningId)
|
|
100
|
+
continue;
|
|
101
|
+
const isExpired = this.isRecordExpired(record, now);
|
|
102
|
+
const matches = this.matchesScope(record, line);
|
|
103
|
+
if (matches) {
|
|
104
|
+
return { record, isExpired };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
matchesScope(record, warningLine) {
|
|
110
|
+
switch (record.scope) {
|
|
111
|
+
case 'file':
|
|
112
|
+
return true;
|
|
113
|
+
case 'line':
|
|
114
|
+
return record.line === warningLine;
|
|
115
|
+
case 'statement':
|
|
116
|
+
return warningLine === record.line + 1;
|
|
117
|
+
case 'import':
|
|
118
|
+
return warningLine === record.line + 1;
|
|
119
|
+
default:
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
isRecordExpired(record, now) {
|
|
124
|
+
if (!record.expires_at) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const expiresAt = new Date(record.expires_at);
|
|
128
|
+
return expiresAt < now;
|
|
129
|
+
}
|
|
130
|
+
getAll() {
|
|
131
|
+
return [...this.data.suppressions];
|
|
132
|
+
}
|
|
133
|
+
getByFile(file) {
|
|
134
|
+
return this.data.suppressions.filter((s) => s.file === file);
|
|
135
|
+
}
|
|
136
|
+
getExpired(now = new Date()) {
|
|
137
|
+
return this.data.suppressions.filter((record) => this.isRecordExpired(record, now));
|
|
138
|
+
}
|
|
139
|
+
pruneExpired(now = new Date()) {
|
|
140
|
+
const before = this.data.suppressions.length;
|
|
141
|
+
this.data.suppressions = this.data.suppressions.filter((record) => !this.isRecordExpired(record, now));
|
|
142
|
+
return before - this.data.suppressions.length;
|
|
143
|
+
}
|
|
144
|
+
createRecordFromParsed(parsed, file, gitCommit) {
|
|
145
|
+
const id = generateSuppressionId(file, parsed.line, parsed.warningId);
|
|
146
|
+
const record = {
|
|
147
|
+
id,
|
|
148
|
+
pattern_id: parsed.warningId,
|
|
149
|
+
file,
|
|
150
|
+
line: parsed.line,
|
|
151
|
+
reason: parsed.reason,
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
scope: parsed.scope,
|
|
154
|
+
};
|
|
155
|
+
if (gitCommit) {
|
|
156
|
+
record.commit = gitCommit;
|
|
157
|
+
}
|
|
158
|
+
if (parsed.expiresAt) {
|
|
159
|
+
record.expires_at = parsed.expiresAt.toISOString();
|
|
160
|
+
}
|
|
161
|
+
return record;
|
|
162
|
+
}
|
|
163
|
+
get isLoaded() {
|
|
164
|
+
return this.loaded;
|
|
165
|
+
}
|
|
166
|
+
get count() {
|
|
167
|
+
return this.data.suppressions.length;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug logging utility for Anvil
|
|
3
|
+
*
|
|
4
|
+
* Enables debug output when ANVIL_DEBUG or DEBUG environment variable is set.
|
|
5
|
+
* This provides visibility into error handling without cluttering production output.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { debug } from './utils/debug.js';
|
|
9
|
+
* debug('provenance', 'Failed to parse index', error);
|
|
10
|
+
*
|
|
11
|
+
* Enable with:
|
|
12
|
+
* ANVIL_DEBUG=1 anvil gate plan.md
|
|
13
|
+
* DEBUG=anvil:* anvil gate plan.md
|
|
14
|
+
*/
|
|
15
|
+
type DebugNamespace = 'provenance' | 'cache' | 'gate' | 'validation' | 'adapter' | 'architecture' | 'edge-detector' | 'entry-detector' | 'drift' | 'policy' | 'git-ai-notes' | 'agent' | 'atomic' | 'git-agent' | 'lock' | 'queue' | 'check' | 'watch' | 'cli' | 'kindling' | 'api' | 'service' | 'export' | 'explain' | 'suppression' | 'config' | 'secret' | 'compiler';
|
|
16
|
+
/**
|
|
17
|
+
* Check if debug logging is enabled
|
|
18
|
+
*/
|
|
19
|
+
export declare function isDebugEnabled(namespace?: DebugNamespace): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Log a debug message if debug mode is enabled
|
|
22
|
+
*
|
|
23
|
+
* @param namespace - The component namespace (e.g., 'provenance', 'gate')
|
|
24
|
+
* @param message - The debug message
|
|
25
|
+
* @param data - Optional additional data to log
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Redact values that look like tokens, keys, or secrets before logging.
|
|
29
|
+
*
|
|
30
|
+
* Patterns redacted:
|
|
31
|
+
* - Hex tokens (40+ hex characters, e.g. SHA tokens, API keys)
|
|
32
|
+
* - Base64 tokens (20+ chars of base64 alphabet)
|
|
33
|
+
* - Common secret prefixes: sk-, ghp_, ghu_, Bearer
|
|
34
|
+
*
|
|
35
|
+
* @param value - The string to sanitize
|
|
36
|
+
* @returns The sanitized string with secrets replaced by [REDACTED]
|
|
37
|
+
*/
|
|
38
|
+
export declare function sanitizeForLog(value: string): string;
|
|
39
|
+
export declare function debug(namespace: DebugNamespace, message: string, data?: unknown): void;
|
|
40
|
+
/**
|
|
41
|
+
* Create a namespaced debug logger
|
|
42
|
+
*
|
|
43
|
+
* @param namespace - The component namespace
|
|
44
|
+
* @returns A debug function bound to the namespace
|
|
45
|
+
*/
|
|
46
|
+
export declare function createDebugger(namespace: DebugNamespace): (message: string, data?: unknown) => void;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,KAAK,cAAc,GACf,YAAY,GACZ,OAAO,GACP,MAAM,GACN,YAAY,GACZ,SAAS,GACT,cAAc,GACd,eAAe,GACf,gBAAgB,GAChB,OAAO,GACP,QAAQ,GACR,cAAc,GACd,OAAO,GACP,QAAQ,GACR,WAAW,GACX,MAAM,GACN,OAAO,GACP,OAAO,GACP,OAAO,GACP,KAAK,GACL,UAAU,GACV,KAAK,GACL,SAAS,GACT,QAAQ,GACR,SAAS,GACT,aAAa,GACb,QAAQ,GACR,QAAQ,GACR,UAAU,CAAC;AAEf;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,OAAO,CAoBlE;AAED;;;;;;GAMG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcpD;AAED,wBAAgB,KAAK,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAyBtF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,cAAc,GACxB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAE3C"}
|