@unrdf/kgn 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/package.json +90 -0
- package/src/MIGRATION_COMPLETE.md +186 -0
- package/src/PORT-MAP.md +302 -0
- package/src/base/filter-templates.js +479 -0
- package/src/base/index.js +92 -0
- package/src/base/injection-targets.js +583 -0
- package/src/base/macro-templates.js +298 -0
- package/src/base/macro-templates.js.bak +461 -0
- package/src/base/shacl-templates.js +617 -0
- package/src/base/template-base.js +388 -0
- package/src/core/attestor.js +381 -0
- package/src/core/filters.js +518 -0
- package/src/core/index.js +21 -0
- package/src/core/kgen-engine.js +372 -0
- package/src/core/parser.js +447 -0
- package/src/core/post-processor.js +313 -0
- package/src/core/renderer.js +469 -0
- package/src/doc-generator/cli.mjs +122 -0
- package/src/doc-generator/index.mjs +28 -0
- package/src/doc-generator/mdx-generator.mjs +71 -0
- package/src/doc-generator/nav-generator.mjs +136 -0
- package/src/doc-generator/parser.mjs +291 -0
- package/src/doc-generator/rdf-builder.mjs +306 -0
- package/src/doc-generator/scanner.mjs +189 -0
- package/src/engine/index.js +42 -0
- package/src/engine/pipeline.js +448 -0
- package/src/engine/renderer.js +604 -0
- package/src/engine/template-engine.js +566 -0
- package/src/filters/array.js +436 -0
- package/src/filters/data.js +479 -0
- package/src/filters/index.js +270 -0
- package/src/filters/rdf.js +264 -0
- package/src/filters/text.js +369 -0
- package/src/index.js +109 -0
- package/src/inheritance/index.js +40 -0
- package/src/injection/api.js +260 -0
- package/src/injection/atomic-writer.js +327 -0
- package/src/injection/constants.js +136 -0
- package/src/injection/idempotency-manager.js +295 -0
- package/src/injection/index.js +28 -0
- package/src/injection/injection-engine.js +378 -0
- package/src/injection/integration.js +339 -0
- package/src/injection/modes/index.js +341 -0
- package/src/injection/rollback-manager.js +373 -0
- package/src/injection/target-resolver.js +323 -0
- package/src/injection/tests/atomic-writer.test.js +382 -0
- package/src/injection/tests/injection-engine.test.js +611 -0
- package/src/injection/tests/integration.test.js +392 -0
- package/src/injection/tests/run-tests.js +283 -0
- package/src/injection/validation-engine.js +547 -0
- package/src/linter/determinism-linter.js +473 -0
- package/src/linter/determinism.js +410 -0
- package/src/linter/index.js +6 -0
- package/src/linter/test-doubles.js +475 -0
- package/src/parser/frontmatter.js +228 -0
- package/src/parser/variables.js +344 -0
- package/src/renderer/deterministic.js +245 -0
- package/src/renderer/index.js +6 -0
- package/src/templates/latex/academic-paper.njk +186 -0
- package/src/templates/latex/index.js +104 -0
- package/src/templates/nextjs/app-page.njk +66 -0
- package/src/templates/nextjs/index.js +80 -0
- package/src/templates/office/docx/document.njk +368 -0
- package/src/templates/office/index.js +79 -0
- package/src/templates/office/word-report.njk +129 -0
- package/src/utils/template-utils.js +426 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGEN Determinism Linter - ZERO TOLERANCE ENFORCEMENT
|
|
3
|
+
*
|
|
4
|
+
* Mission: Detect and eliminate ALL sources of nondeterminism in KGEN templates
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Real-time violation detection
|
|
8
|
+
* - Template depth analysis
|
|
9
|
+
* - Output size validation
|
|
10
|
+
* - Deterministic pattern enforcement
|
|
11
|
+
* - Cross-platform consistency checking
|
|
12
|
+
*
|
|
13
|
+
* Generated by: Determinism Sentinel Agent
|
|
14
|
+
* Authority: ZERO TOLERANCE for nondeterminism
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync, statSync, readdirSync } from 'fs';
|
|
18
|
+
import { resolve, relative, extname } from 'path';
|
|
19
|
+
import { createHash } from 'crypto';
|
|
20
|
+
import { glob } from 'glob';
|
|
21
|
+
import consola from 'consola';
|
|
22
|
+
|
|
23
|
+
export class DeterminismLinter {
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.config = {
|
|
26
|
+
// CRITICAL LIMITS (ZERO TOLERANCE)
|
|
27
|
+
maxTemplateDepth: 10,
|
|
28
|
+
maxOutputSize: 10 * 1024 * 1024, // 10MB
|
|
29
|
+
maxIterations: 10000,
|
|
30
|
+
maxFileSize: 1 * 1024 * 1024, // 1MB per template
|
|
31
|
+
|
|
32
|
+
// ENFORCEMENT SETTINGS
|
|
33
|
+
strictMode: true,
|
|
34
|
+
zeroTolerance: true,
|
|
35
|
+
failFast: true,
|
|
36
|
+
|
|
37
|
+
// VIOLATION TRACKING
|
|
38
|
+
trackViolations: true,
|
|
39
|
+
generateReport: true,
|
|
40
|
+
|
|
41
|
+
...config
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.logger = consola.withTag('determinism-linter');
|
|
45
|
+
this.violations = [];
|
|
46
|
+
this.statistics = {
|
|
47
|
+
filesScanned: 0,
|
|
48
|
+
violationsFound: 0,
|
|
49
|
+
criticalViolations: 0,
|
|
50
|
+
templateDepthExceeded: 0,
|
|
51
|
+
outputSizeExceeded: 0
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// CRITICAL VIOLATION PATTERNS (ZERO TOLERANCE)
|
|
55
|
+
this.criticalPatterns = [
|
|
56
|
+
// Temporal nondeterminism
|
|
57
|
+
{
|
|
58
|
+
pattern: /new\s+Date\s*\(/g,
|
|
59
|
+
severity: 'CRITICAL',
|
|
60
|
+
message: 'new Date() constructor detected - FORBIDDEN',
|
|
61
|
+
replacement: 'context.metadata.buildTime'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
pattern: /Date\.now\s*\(/g,
|
|
65
|
+
severity: 'CRITICAL',
|
|
66
|
+
message: 'Date.now() detected - FORBIDDEN',
|
|
67
|
+
replacement: 'context.metadata.buildTime'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
pattern: /Math\.random\s*\(/g,
|
|
71
|
+
severity: 'CRITICAL',
|
|
72
|
+
message: 'Math.random() detected - FORBIDDEN',
|
|
73
|
+
replacement: 'generateDeterministicId(inputData)'
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Environment dependencies
|
|
77
|
+
{
|
|
78
|
+
pattern: /process\.env\./g,
|
|
79
|
+
severity: 'CRITICAL',
|
|
80
|
+
message: 'process.env access in template - FORBIDDEN',
|
|
81
|
+
replacement: 'config.environment'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
pattern: /os\.hostname\s*\(/g,
|
|
85
|
+
severity: 'CRITICAL',
|
|
86
|
+
message: 'os.hostname() detected - FORBIDDEN',
|
|
87
|
+
replacement: 'config.targetHost'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
pattern: /process\.platform/g,
|
|
91
|
+
severity: 'CRITICAL',
|
|
92
|
+
message: 'process.platform access - FORBIDDEN',
|
|
93
|
+
replacement: 'config.targetPlatform'
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Nondeterministic iteration
|
|
97
|
+
{
|
|
98
|
+
pattern: /for\s*\(\s*\w+\s+in\s+/g,
|
|
99
|
+
severity: 'CRITICAL',
|
|
100
|
+
message: 'for...in loop detected - key order undefined',
|
|
101
|
+
replacement: 'for (key of Object.keys(obj).sort())'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
pattern: /Object\.keys\([^)]+\)\.forEach/g,
|
|
105
|
+
severity: 'HIGH',
|
|
106
|
+
message: 'Object.keys().forEach() without sorting',
|
|
107
|
+
replacement: 'Object.keys(obj).sort().forEach'
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
// Random/UUID generation
|
|
111
|
+
{
|
|
112
|
+
pattern: /crypto\.randomBytes/g,
|
|
113
|
+
severity: 'CRITICAL',
|
|
114
|
+
message: 'crypto.randomBytes() detected - FORBIDDEN',
|
|
115
|
+
replacement: 'generateDeterministicBytes(seed, length)'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
pattern: /uuid\(\)|uuidv4\(\)/g,
|
|
119
|
+
severity: 'CRITICAL',
|
|
120
|
+
message: 'UUID generation detected - FORBIDDEN',
|
|
121
|
+
replacement: 'generateDeterministicId(content)'
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// HIGH PRIORITY PATTERNS
|
|
126
|
+
this.highPriorityPatterns = [
|
|
127
|
+
{
|
|
128
|
+
pattern: /fs\.readdir\([^)]+\)(?!\.sort\(\))/g,
|
|
129
|
+
severity: 'HIGH',
|
|
130
|
+
message: 'fs.readdir() without sorting - order undefined',
|
|
131
|
+
replacement: 'fs.readdir(path).then(files => files.sort())'
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
pattern: /glob\([^)]+\)(?!\.sort\(\))/g,
|
|
135
|
+
severity: 'HIGH',
|
|
136
|
+
message: 'glob() without sorting - order undefined',
|
|
137
|
+
replacement: 'glob(pattern).then(files => files.sort())'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
pattern: /require\([^)]*\$\{[^}]+\}[^)]*\)/g,
|
|
141
|
+
severity: 'HIGH',
|
|
142
|
+
message: 'Dynamic require() with template literals',
|
|
143
|
+
replacement: 'Static import paths only'
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Lint a single file for determinism violations
|
|
150
|
+
*/
|
|
151
|
+
async lintFile(filePath) {
|
|
152
|
+
this.logger.info(`Linting ${filePath} for determinism violations`);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const content = readFileSync(filePath, 'utf8');
|
|
156
|
+
const fileViolations = [];
|
|
157
|
+
|
|
158
|
+
// Check file size limits
|
|
159
|
+
const stats = statSync(filePath);
|
|
160
|
+
if (stats.size > this.config.maxFileSize) {
|
|
161
|
+
fileViolations.push({
|
|
162
|
+
file: filePath,
|
|
163
|
+
line: 1,
|
|
164
|
+
column: 1,
|
|
165
|
+
severity: 'HIGH',
|
|
166
|
+
message: `File size ${stats.size} exceeds limit ${this.config.maxFileSize}`,
|
|
167
|
+
type: 'file-size-exceeded'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Scan for critical patterns
|
|
172
|
+
await this.scanPatterns(filePath, content, this.criticalPatterns, fileViolations);
|
|
173
|
+
await this.scanPatterns(filePath, content, this.highPriorityPatterns, fileViolations);
|
|
174
|
+
|
|
175
|
+
// Template-specific checks
|
|
176
|
+
if (this.isTemplateFile(filePath)) {
|
|
177
|
+
await this.validateTemplateStructure(filePath, content, fileViolations);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// JavaScript-specific checks
|
|
181
|
+
if (this.isJavaScriptFile(filePath)) {
|
|
182
|
+
await this.validateJavaScriptCode(filePath, content, fileViolations);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.violations.push(...fileViolations);
|
|
186
|
+
this.statistics.filesScanned++;
|
|
187
|
+
this.statistics.violationsFound += fileViolations.length;
|
|
188
|
+
this.statistics.criticalViolations += fileViolations.filter(v => v.severity === 'CRITICAL').length;
|
|
189
|
+
|
|
190
|
+
return fileViolations;
|
|
191
|
+
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger.error(`Failed to lint ${filePath}:`, error);
|
|
194
|
+
return [{
|
|
195
|
+
file: filePath,
|
|
196
|
+
line: 1,
|
|
197
|
+
column: 1,
|
|
198
|
+
severity: 'ERROR',
|
|
199
|
+
message: `Linting failed: ${error.message}`,
|
|
200
|
+
type: 'linting-error'
|
|
201
|
+
}];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Scan content for violation patterns
|
|
207
|
+
*/
|
|
208
|
+
async scanPatterns(filePath, content, patterns, violations) {
|
|
209
|
+
const lines = content.split('\n');
|
|
210
|
+
|
|
211
|
+
for (const patternConfig of patterns) {
|
|
212
|
+
const matches = [...content.matchAll(patternConfig.pattern)];
|
|
213
|
+
|
|
214
|
+
for (const match of matches) {
|
|
215
|
+
const position = this.getLineAndColumn(content, match.index);
|
|
216
|
+
|
|
217
|
+
violations.push({
|
|
218
|
+
file: filePath,
|
|
219
|
+
line: position.line,
|
|
220
|
+
column: position.column,
|
|
221
|
+
severity: patternConfig.severity,
|
|
222
|
+
message: patternConfig.message,
|
|
223
|
+
pattern: patternConfig.pattern.source,
|
|
224
|
+
replacement: patternConfig.replacement,
|
|
225
|
+
type: 'pattern-violation',
|
|
226
|
+
matchedText: match[0]
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Validate template structure for determinism
|
|
234
|
+
*/
|
|
235
|
+
async validateTemplateStructure(filePath, content, violations) {
|
|
236
|
+
// Check template depth
|
|
237
|
+
const depth = this.calculateTemplateDepth(content);
|
|
238
|
+
if (depth > this.config.maxTemplateDepth) {
|
|
239
|
+
violations.push({
|
|
240
|
+
file: filePath,
|
|
241
|
+
line: 1,
|
|
242
|
+
column: 1,
|
|
243
|
+
severity: 'HIGH',
|
|
244
|
+
message: `Template depth ${depth} exceeds maximum ${this.config.maxTemplateDepth}`,
|
|
245
|
+
type: 'template-depth-exceeded'
|
|
246
|
+
});
|
|
247
|
+
this.statistics.templateDepthExceeded++;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check for dynamic includes
|
|
251
|
+
const dynamicIncludes = content.match(/{%\s*include\s+[^'"][^%]*%}/g);
|
|
252
|
+
if (dynamicIncludes) {
|
|
253
|
+
dynamicIncludes.forEach((include, index) => {
|
|
254
|
+
const position = this.getLineAndColumn(content, content.indexOf(include));
|
|
255
|
+
violations.push({
|
|
256
|
+
file: filePath,
|
|
257
|
+
line: position.line,
|
|
258
|
+
column: position.column,
|
|
259
|
+
severity: 'HIGH',
|
|
260
|
+
message: 'Dynamic template include detected - path must be static',
|
|
261
|
+
type: 'dynamic-include',
|
|
262
|
+
matchedText: include
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check for nondeterministic filters
|
|
268
|
+
const nondeterministicFilters = [
|
|
269
|
+
'timestamp', 'random', 'uuid', 'now', 'hostname', 'platform'
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
for (const filter of nondeterministicFilters) {
|
|
273
|
+
const filterPattern = new RegExp(`\\|\\s*${filter}\\s*(?:\\([^)]*\\))?`, 'g');
|
|
274
|
+
const matches = [...content.matchAll(filterPattern)];
|
|
275
|
+
|
|
276
|
+
for (const match of matches) {
|
|
277
|
+
const position = this.getLineAndColumn(content, match.index);
|
|
278
|
+
violations.push({
|
|
279
|
+
file: filePath,
|
|
280
|
+
line: position.line,
|
|
281
|
+
column: position.column,
|
|
282
|
+
severity: 'CRITICAL',
|
|
283
|
+
message: `Nondeterministic filter '${filter}' detected`,
|
|
284
|
+
type: 'nondeterministic-filter',
|
|
285
|
+
matchedText: match[0]
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Validate JavaScript code for determinism
|
|
293
|
+
*/
|
|
294
|
+
async validateJavaScriptCode(filePath, content, violations) {
|
|
295
|
+
// Check for unsorted object processing
|
|
296
|
+
const objectIterationPatterns = [
|
|
297
|
+
/Object\.keys\([^)]+\)(?!\.sort\(\))/g,
|
|
298
|
+
/Object\.values\([^)]+\)(?!\[Object\.keys\([^)]+\)\.sort\(\)\])/g,
|
|
299
|
+
/Object\.entries\([^)]+\)(?!\.sort\(\))/g
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
for (const pattern of objectIterationPatterns) {
|
|
303
|
+
const matches = [...content.matchAll(pattern)];
|
|
304
|
+
for (const match of matches) {
|
|
305
|
+
const position = this.getLineAndColumn(content, match.index);
|
|
306
|
+
violations.push({
|
|
307
|
+
file: filePath,
|
|
308
|
+
line: position.line,
|
|
309
|
+
column: position.column,
|
|
310
|
+
severity: 'HIGH',
|
|
311
|
+
message: 'Object iteration without deterministic ordering',
|
|
312
|
+
type: 'unsorted-iteration',
|
|
313
|
+
matchedText: match[0]
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check for array sort without stable comparator
|
|
319
|
+
const unstableSortPattern = /\.sort\(\)(?!\s*\/\/\s*stable|\.sort\([^)]+\))/g;
|
|
320
|
+
const sortMatches = [...content.matchAll(unstableSortPattern)];
|
|
321
|
+
|
|
322
|
+
for (const match of sortMatches) {
|
|
323
|
+
const position = this.getLineAndColumn(content, match.index);
|
|
324
|
+
violations.push({
|
|
325
|
+
file: filePath,
|
|
326
|
+
line: position.line,
|
|
327
|
+
column: position.column,
|
|
328
|
+
severity: 'MEDIUM',
|
|
329
|
+
message: 'Array.sort() without explicit comparator - may be unstable',
|
|
330
|
+
type: 'unstable-sort',
|
|
331
|
+
matchedText: match[0]
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Lint entire directory recursively
|
|
338
|
+
*/
|
|
339
|
+
async lintDirectory(directoryPath, extensions = ['.js', '.ts', '.njk', '.ejs']) {
|
|
340
|
+
this.logger.info(`Scanning directory ${directoryPath} for determinism violations`);
|
|
341
|
+
|
|
342
|
+
const pattern = `${directoryPath}/**/*{${extensions.join(',')}}`;
|
|
343
|
+
const files = await glob(pattern, { ignore: ['**/node_modules/**', '**/dist/**'] });
|
|
344
|
+
|
|
345
|
+
const allViolations = [];
|
|
346
|
+
|
|
347
|
+
for (const file of files) {
|
|
348
|
+
const fileViolations = await this.lintFile(file);
|
|
349
|
+
allViolations.push(...fileViolations);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return allViolations;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Generate comprehensive violation report
|
|
357
|
+
*/
|
|
358
|
+
generateReport() {
|
|
359
|
+
const criticalViolations = this.violations.filter(v => v.severity === 'CRITICAL');
|
|
360
|
+
const highViolations = this.violations.filter(v => v.severity === 'HIGH');
|
|
361
|
+
|
|
362
|
+
const report = {
|
|
363
|
+
summary: {
|
|
364
|
+
totalFiles: this.statistics.filesScanned,
|
|
365
|
+
totalViolations: this.statistics.violationsFound,
|
|
366
|
+
criticalViolations: criticalViolations.length,
|
|
367
|
+
highViolations: highViolations.length,
|
|
368
|
+
status: criticalViolations.length > 0 ? 'FAILED' : 'PASSED'
|
|
369
|
+
},
|
|
370
|
+
violations: {
|
|
371
|
+
critical: criticalViolations,
|
|
372
|
+
high: highViolations,
|
|
373
|
+
medium: this.violations.filter(v => v.severity === 'MEDIUM')
|
|
374
|
+
},
|
|
375
|
+
statistics: this.statistics,
|
|
376
|
+
enforcement: {
|
|
377
|
+
zeroToleranceMode: this.config.zeroTolerance,
|
|
378
|
+
strictMode: this.config.strictMode,
|
|
379
|
+
maxTemplateDepth: this.config.maxTemplateDepth,
|
|
380
|
+
maxOutputSize: this.config.maxOutputSize
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
if (this.config.generateReport) {
|
|
385
|
+
this.logger.info('Determinism Linting Report:', report);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return report;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Utility methods
|
|
393
|
+
*/
|
|
394
|
+
calculateTemplateDepth(content) {
|
|
395
|
+
const includePattern = /{%\s*include\s+/g;
|
|
396
|
+
const extendPattern = /{%\s*extends\s+/g;
|
|
397
|
+
const blockPattern = /{%\s*block\s+/g;
|
|
398
|
+
|
|
399
|
+
const includes = (content.match(includePattern) || []).length;
|
|
400
|
+
const extends_ = (content.match(extendPattern) || []).length;
|
|
401
|
+
const blocks = (content.match(blockPattern) || []).length;
|
|
402
|
+
|
|
403
|
+
return Math.max(includes, extends_, blocks);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
getLineAndColumn(content, index) {
|
|
407
|
+
const lines = content.substring(0, index).split('\n');
|
|
408
|
+
return {
|
|
409
|
+
line: lines.length,
|
|
410
|
+
column: lines[lines.length - 1].length + 1
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
isTemplateFile(filePath) {
|
|
415
|
+
const templateExtensions = ['.njk', '.ejs', '.hbs', '.mustache', '.liquid'];
|
|
416
|
+
return templateExtensions.some(ext => filePath.endsWith(ext));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
isJavaScriptFile(filePath) {
|
|
420
|
+
return filePath.endsWith('.js') || filePath.endsWith('.ts');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* ZERO TOLERANCE enforcement
|
|
425
|
+
*/
|
|
426
|
+
enforceZeroTolerance() {
|
|
427
|
+
const criticalViolations = this.violations.filter(v => v.severity === 'CRITICAL');
|
|
428
|
+
|
|
429
|
+
if (criticalViolations.length > 0 && this.config.zeroTolerance) {
|
|
430
|
+
this.logger.error(`ZERO TOLERANCE VIOLATION: ${criticalViolations.length} critical determinism violations detected`);
|
|
431
|
+
|
|
432
|
+
criticalViolations.forEach(violation => {
|
|
433
|
+
this.logger.error(`${violation.file}:${violation.line}:${violation.column} - ${violation.message}`);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (this.config.failFast) {
|
|
437
|
+
throw new Error(`DETERMINISM ENFORCEMENT FAILED: ${criticalViolations.length} critical violations detected`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return criticalViolations.length === 0;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* CLI interface for determinism linting
|
|
447
|
+
*/
|
|
448
|
+
export async function runDeterminismLinter(targetPath, options = {}) {
|
|
449
|
+
const linter = new DeterminismLinter(options);
|
|
450
|
+
|
|
451
|
+
try {
|
|
452
|
+
const violations = await linter.lintDirectory(targetPath);
|
|
453
|
+
const report = linter.generateReport();
|
|
454
|
+
|
|
455
|
+
// ZERO TOLERANCE enforcement
|
|
456
|
+
const passed = linter.enforceZeroTolerance();
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
passed,
|
|
460
|
+
report,
|
|
461
|
+
violations,
|
|
462
|
+
exitCode: passed ? 0 : 1
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
} catch (error) {
|
|
466
|
+
consola.error('Determinism linting failed:', error);
|
|
467
|
+
return {
|
|
468
|
+
passed: false,
|
|
469
|
+
error: error.message,
|
|
470
|
+
exitCode: 2
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|