@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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/package.json +90 -0
  4. package/src/MIGRATION_COMPLETE.md +186 -0
  5. package/src/PORT-MAP.md +302 -0
  6. package/src/base/filter-templates.js +479 -0
  7. package/src/base/index.js +92 -0
  8. package/src/base/injection-targets.js +583 -0
  9. package/src/base/macro-templates.js +298 -0
  10. package/src/base/macro-templates.js.bak +461 -0
  11. package/src/base/shacl-templates.js +617 -0
  12. package/src/base/template-base.js +388 -0
  13. package/src/core/attestor.js +381 -0
  14. package/src/core/filters.js +518 -0
  15. package/src/core/index.js +21 -0
  16. package/src/core/kgen-engine.js +372 -0
  17. package/src/core/parser.js +447 -0
  18. package/src/core/post-processor.js +313 -0
  19. package/src/core/renderer.js +469 -0
  20. package/src/doc-generator/cli.mjs +122 -0
  21. package/src/doc-generator/index.mjs +28 -0
  22. package/src/doc-generator/mdx-generator.mjs +71 -0
  23. package/src/doc-generator/nav-generator.mjs +136 -0
  24. package/src/doc-generator/parser.mjs +291 -0
  25. package/src/doc-generator/rdf-builder.mjs +306 -0
  26. package/src/doc-generator/scanner.mjs +189 -0
  27. package/src/engine/index.js +42 -0
  28. package/src/engine/pipeline.js +448 -0
  29. package/src/engine/renderer.js +604 -0
  30. package/src/engine/template-engine.js +566 -0
  31. package/src/filters/array.js +436 -0
  32. package/src/filters/data.js +479 -0
  33. package/src/filters/index.js +270 -0
  34. package/src/filters/rdf.js +264 -0
  35. package/src/filters/text.js +369 -0
  36. package/src/index.js +109 -0
  37. package/src/inheritance/index.js +40 -0
  38. package/src/injection/api.js +260 -0
  39. package/src/injection/atomic-writer.js +327 -0
  40. package/src/injection/constants.js +136 -0
  41. package/src/injection/idempotency-manager.js +295 -0
  42. package/src/injection/index.js +28 -0
  43. package/src/injection/injection-engine.js +378 -0
  44. package/src/injection/integration.js +339 -0
  45. package/src/injection/modes/index.js +341 -0
  46. package/src/injection/rollback-manager.js +373 -0
  47. package/src/injection/target-resolver.js +323 -0
  48. package/src/injection/tests/atomic-writer.test.js +382 -0
  49. package/src/injection/tests/injection-engine.test.js +611 -0
  50. package/src/injection/tests/integration.test.js +392 -0
  51. package/src/injection/tests/run-tests.js +283 -0
  52. package/src/injection/validation-engine.js +547 -0
  53. package/src/linter/determinism-linter.js +473 -0
  54. package/src/linter/determinism.js +410 -0
  55. package/src/linter/index.js +6 -0
  56. package/src/linter/test-doubles.js +475 -0
  57. package/src/parser/frontmatter.js +228 -0
  58. package/src/parser/variables.js +344 -0
  59. package/src/renderer/deterministic.js +245 -0
  60. package/src/renderer/index.js +6 -0
  61. package/src/templates/latex/academic-paper.njk +186 -0
  62. package/src/templates/latex/index.js +104 -0
  63. package/src/templates/nextjs/app-page.njk +66 -0
  64. package/src/templates/nextjs/index.js +80 -0
  65. package/src/templates/office/docx/document.njk +368 -0
  66. package/src/templates/office/index.js +79 -0
  67. package/src/templates/office/word-report.njk +129 -0
  68. 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
+ }