@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,344 @@
1
+ /**
2
+ * Variable Extractor - Extract and validate template variables
3
+ * Migrated from ~/unjucks with enhanced pattern matching
4
+ */
5
+
6
+ export class VariableExtractor {
7
+ constructor(options = {}) {
8
+ this.includeFilters = options.includeFilters !== false;
9
+ this.includeFunctions = options.includeFunctions !== false;
10
+ this.strict = options.strict !== false;
11
+ }
12
+
13
+ /**
14
+ * Extract all variables from template content
15
+ */
16
+ extract(content) {
17
+ const variables = new Set();
18
+ const filters = new Set();
19
+ const functions = new Set();
20
+
21
+ // Extract {{ variable }} patterns
22
+ this.extractOutputVariables(content, variables, filters);
23
+
24
+ // Extract {% for variable in ... %} patterns
25
+ this.extractLoopVariables(content, variables);
26
+
27
+ // Extract {% if variable %} patterns
28
+ this.extractConditionalVariables(content, variables);
29
+
30
+ // Extract {% set variable = ... %} patterns
31
+ this.extractAssignmentVariables(content, variables);
32
+
33
+ // Extract function calls
34
+ if (this.includeFunctions) {
35
+ this.extractFunctionCalls(content, functions);
36
+ }
37
+
38
+ return {
39
+ variables: Array.from(variables).sort(),
40
+ filters: this.includeFilters ? Array.from(filters).sort() : [],
41
+ functions: this.includeFunctions ? Array.from(functions).sort() : [],
42
+ totalVariables: variables.size,
43
+ complexity: this.calculateComplexity(content)
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Extract variables from output expressions {{ ... }}
49
+ */
50
+ extractOutputVariables(content, variables, filters) {
51
+ // Match {{ variable | filter1 | filter2 }} patterns
52
+ const outputPattern = /\{\{\s*([^}]+?)\s*\}\}/g;
53
+ let match;
54
+
55
+ while ((match = outputPattern.exec(content)) !== null) {
56
+ const expression = match[1].trim();
57
+ this.parseExpression(expression, variables, filters);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Extract variables from loop constructs
63
+ */
64
+ extractLoopVariables(content, variables) {
65
+ // Match {% for item in collection %} patterns
66
+ const forPattern = /\{\%\s*for\s+(\w+)(?:\s*,\s*(\w+))?\s+in\s+([^%]+?)\s*\%\}/g;
67
+ let match;
68
+
69
+ while ((match = forPattern.exec(content)) !== null) {
70
+ const [, itemVar, indexVar, collection] = match;
71
+
72
+ // Collection is a variable we need
73
+ const collectionVar = collection.trim().split('.')[0].split('|')[0].trim();
74
+ if (collectionVar && this.isValidVariable(collectionVar)) {
75
+ variables.add(collectionVar);
76
+ }
77
+
78
+ // Note: itemVar and indexVar are loop-local, not template variables
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Extract variables from conditional statements
84
+ */
85
+ extractConditionalVariables(content, variables) {
86
+ // Match {% if condition %} patterns
87
+ const ifPattern = /\{\%\s*if\s+([^%]+?)\s*\%\}/g;
88
+ const elifPattern = /\{\%\s*elif\s+([^%]+?)\s*\%\}/g;
89
+
90
+ let match;
91
+
92
+ // Process if statements
93
+ while ((match = ifPattern.exec(content)) !== null) {
94
+ const condition = match[1].trim();
95
+ this.parseCondition(condition, variables);
96
+ }
97
+
98
+ // Process elif statements
99
+ while ((match = elifPattern.exec(content)) !== null) {
100
+ const condition = match[1].trim();
101
+ this.parseCondition(condition, variables);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Extract variables from assignment statements
107
+ */
108
+ extractAssignmentVariables(content, variables) {
109
+ // Match {% set variable = expression %} patterns
110
+ const setPattern = /\{\%\s*set\s+\w+\s*=\s*([^%]+?)\s*\%\}/g;
111
+ let match;
112
+
113
+ while ((match = setPattern.exec(content)) !== null) {
114
+ const expression = match[1].trim();
115
+ this.parseExpression(expression, variables, new Set());
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Extract function calls from template
121
+ */
122
+ extractFunctionCalls(content, functions) {
123
+ // Match function calls like func(args)
124
+ const functionPattern = /(\w+)\s*\(/g;
125
+ let match;
126
+
127
+ while ((match = functionPattern.exec(content)) !== null) {
128
+ const funcName = match[1];
129
+ if (!this.isBuiltinFunction(funcName)) {
130
+ functions.add(funcName);
131
+ }
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Parse expression to extract variables and filters
137
+ */
138
+ parseExpression(expression, variables, filters) {
139
+ // Handle complex expressions with operators
140
+ const parts = expression.split(/[+\-*\/\(\)]/);
141
+
142
+ for (let part of parts) {
143
+ part = part.trim();
144
+
145
+ // Skip empty parts and literals
146
+ if (!part || this.isLiteral(part)) continue;
147
+
148
+ // Check for filter chain: variable | filter1 | filter2
149
+ if (part.includes('|')) {
150
+ const [varPart, ...filterParts] = part.split('|').map(p => p.trim());
151
+
152
+ // Extract variable
153
+ const rootVar = this.extractRootVariable(varPart);
154
+ if (rootVar && this.isValidVariable(rootVar)) {
155
+ variables.add(rootVar);
156
+ }
157
+
158
+ // Extract filters
159
+ filterParts.forEach(filter => {
160
+ const filterName = filter.split('(')[0].trim();
161
+ if (filterName && this.includeFilters) {
162
+ filters.add(filterName);
163
+ }
164
+ });
165
+ } else {
166
+ // Simple variable reference
167
+ const rootVar = this.extractRootVariable(part);
168
+ if (rootVar && this.isValidVariable(rootVar)) {
169
+ variables.add(rootVar);
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Parse conditional expression to extract variables
177
+ */
178
+ parseCondition(condition, variables) {
179
+ // Handle operators: and, or, not, ==, !=, <, >, <=, >=, in, is
180
+ const conditionParts = condition.split(/\s+(?:and|or|not|==|!=|<=|>=|<|>|in|is)\s+/);
181
+
182
+ conditionParts.forEach(part => {
183
+ part = part.trim();
184
+ if (part && !this.isLiteral(part)) {
185
+ const rootVar = this.extractRootVariable(part);
186
+ if (rootVar && this.isValidVariable(rootVar)) {
187
+ variables.add(rootVar);
188
+ }
189
+ }
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Extract root variable from complex expression
195
+ */
196
+ extractRootVariable(expression) {
197
+ // Handle dot notation: user.name -> user
198
+ // Handle array access: users[0] -> users
199
+ // Handle function calls: len(users) -> users
200
+
201
+ let cleaned = expression.trim();
202
+
203
+ // Remove function calls but keep arguments
204
+ cleaned = cleaned.replace(/\w+\(([^)]+)\)/g, '$1');
205
+
206
+ // Extract variable before dot or bracket
207
+ const match = cleaned.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
208
+ return match ? match[1] : null;
209
+ }
210
+
211
+ /**
212
+ * Check if value is a literal (string, number, boolean)
213
+ */
214
+ isLiteral(value) {
215
+ // String literals
216
+ if ((value.startsWith('"') && value.endsWith('"')) ||
217
+ (value.startsWith("'") && value.endsWith("'"))) {
218
+ return true;
219
+ }
220
+
221
+ // Number literals
222
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
223
+ return true;
224
+ }
225
+
226
+ // Boolean literals
227
+ if (['true', 'false', 'True', 'False'].includes(value)) {
228
+ return true;
229
+ }
230
+
231
+ // None/null literals
232
+ if (['none', 'null', 'None', 'Null'].includes(value)) {
233
+ return true;
234
+ }
235
+
236
+ return false;
237
+ }
238
+
239
+ /**
240
+ * Check if variable name is valid
241
+ */
242
+ isValidVariable(name) {
243
+ // Valid variable names: letters, numbers, underscore
244
+ // Must start with letter or underscore
245
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) &&
246
+ !this.isBuiltinVariable(name);
247
+ }
248
+
249
+ /**
250
+ * Check if variable is a builtin
251
+ */
252
+ isBuiltinVariable(name) {
253
+ const builtins = [
254
+ 'loop', 'super', 'self', 'varargs', 'kwargs',
255
+ 'joiner', 'cycler', 'range', 'lipsum'
256
+ ];
257
+ return builtins.includes(name);
258
+ }
259
+
260
+ /**
261
+ * Check if function is a builtin
262
+ */
263
+ isBuiltinFunction(name) {
264
+ const builtins = [
265
+ 'range', 'lipsum', 'dict', 'list', 'tuple', 'set',
266
+ 'len', 'str', 'int', 'float', 'bool'
267
+ ];
268
+ return builtins.includes(name);
269
+ }
270
+
271
+ /**
272
+ * Calculate template complexity
273
+ */
274
+ calculateComplexity(content) {
275
+ const patterns = {
276
+ variables: /\{\{\s*[^}]+?\s*\}\}/g,
277
+ conditions: /\{\%\s*if\s+[^%]+?\s*\%\}/g,
278
+ loops: /\{\%\s*for\s+[^%]+?\s*\%\}/g,
279
+ includes: /\{\%\s*include\s+[^%]+?\s*\%\}/g,
280
+ macros: /\{\%\s*macro\s+[^%]+?\s*\%\}/g,
281
+ blocks: /\{\%\s*block\s+[^%]+?\s*\%\}/g
282
+ };
283
+
284
+ let complexity = 0;
285
+ Object.values(patterns).forEach(pattern => {
286
+ const matches = content.match(pattern);
287
+ complexity += matches ? matches.length : 0;
288
+ });
289
+
290
+ return complexity;
291
+ }
292
+
293
+ /**
294
+ * Validate extracted variables against provided context
295
+ */
296
+ validateContext(extractedVars, context, options = {}) {
297
+ const errors = [];
298
+ const warnings = [];
299
+
300
+ const availableVars = new Set([
301
+ ...Object.keys(context),
302
+ '__meta', // Always available
303
+ ...options.additionalVars || []
304
+ ]);
305
+
306
+ extractedVars.forEach(varName => {
307
+ if (!availableVars.has(varName)) {
308
+ if (options.strictMode) {
309
+ errors.push(`Required variable '${varName}' not found in context`);
310
+ } else {
311
+ warnings.push(`Variable '${varName}' not found in context`);
312
+ }
313
+ }
314
+ });
315
+
316
+ // Check for unused context variables
317
+ if (options.warnUnused) {
318
+ Object.keys(context).forEach(contextVar => {
319
+ if (!extractedVars.includes(contextVar) && !contextVar.startsWith('_')) {
320
+ warnings.push(`Context variable '${contextVar}' is unused in template`);
321
+ }
322
+ });
323
+ }
324
+
325
+ return {
326
+ valid: errors.length === 0,
327
+ errors,
328
+ warnings
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Get extractor statistics
334
+ */
335
+ getStats() {
336
+ return {
337
+ includeFilters: this.includeFilters,
338
+ includeFunctions: this.includeFunctions,
339
+ strict: this.strict
340
+ };
341
+ }
342
+ }
343
+
344
+ export default VariableExtractor;
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Deterministic Renderer - Ensures identical output across template runs
3
+ * Migrated from ~/unjucks with enhanced deterministic guarantees
4
+ */
5
+
6
+ import crypto from 'crypto';
7
+
8
+ export class DeterministicRenderer {
9
+ constructor(options = {}) {
10
+ this.staticBuildTime = options.staticBuildTime || '2024-01-01T00:00:00.000Z';
11
+ this.blockNonDeterministic = options.blockNonDeterministic !== false;
12
+ this.strictMode = options.strictMode !== false;
13
+ }
14
+
15
+ /**
16
+ * Render template with deterministic context
17
+ */
18
+ async render(nunjucksEnv, templateContent, context) {
19
+ try {
20
+ // Create deterministic context
21
+ const deterministicContext = this.createDeterministicContext(context);
22
+
23
+ // Block non-deterministic operations if enabled
24
+ if (this.blockNonDeterministic) {
25
+ this.validateDeterministicContext(deterministicContext);
26
+ }
27
+
28
+ // Render with enhanced error handling
29
+ const rendered = nunjucksEnv.renderString(templateContent, deterministicContext);
30
+
31
+ return this.postProcessOutput(rendered);
32
+
33
+ } catch (error) {
34
+ if (this.strictMode) {
35
+ throw new Error(`Deterministic rendering failed: ${error.message}`);
36
+ }
37
+
38
+ // Fallback to non-deterministic rendering
39
+ console.warn('[DeterministicRenderer] Fallback to non-deterministic rendering:', error.message);
40
+ return nunjucksEnv.renderString(templateContent, context);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Render string template with deterministic context
46
+ */
47
+ renderString(nunjucksEnv, templateContent, context) {
48
+ const deterministicContext = this.createDeterministicContext(context);
49
+ const rendered = nunjucksEnv.renderString(templateContent, deterministicContext);
50
+ return this.postProcessOutput(rendered);
51
+ }
52
+
53
+ /**
54
+ * Create deterministic context by replacing non-deterministic values
55
+ */
56
+ createDeterministicContext(context) {
57
+ const deterministicContext = { ...context };
58
+
59
+ // Add deterministic metadata
60
+ deterministicContext.__deterministic = {
61
+ buildTime: this.staticBuildTime,
62
+ renderTime: this.staticBuildTime, // Use same time for consistency
63
+ hash: this.hashContent(JSON.stringify(context)),
64
+ mode: 'deterministic'
65
+ };
66
+
67
+ // Override potentially non-deterministic built-ins
68
+ this.overrideNonDeterministicValues(deterministicContext);
69
+
70
+ return deterministicContext;
71
+ }
72
+
73
+ /**
74
+ * Override non-deterministic values in context
75
+ */
76
+ overrideNonDeterministicValues(context) {
77
+ // Replace any Date objects with static date
78
+ this.replaceInObject(context, (key, value) => {
79
+ if (value instanceof Date) {
80
+ return new Date(this.staticBuildTime);
81
+ }
82
+
83
+ // Replace current timestamp references
84
+ if (typeof value === 'string' && this.looksLikeTimestamp(value)) {
85
+ return this.staticBuildTime;
86
+ }
87
+
88
+ return value;
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Recursively replace values in object
94
+ */
95
+ replaceInObject(obj, replacer) {
96
+ for (const [key, value] of Object.entries(obj)) {
97
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
98
+ this.replaceInObject(value, replacer);
99
+ } else if (Array.isArray(value)) {
100
+ for (let i = 0; i < value.length; i++) {
101
+ if (typeof value[i] === 'object' && value[i] !== null) {
102
+ this.replaceInObject(value[i], replacer);
103
+ } else {
104
+ value[i] = replacer(i.toString(), value[i]);
105
+ }
106
+ }
107
+ } else {
108
+ obj[key] = replacer(key, value);
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Check if string looks like a timestamp
115
+ */
116
+ looksLikeTimestamp(value) {
117
+ // ISO 8601 format
118
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/.test(value)) {
119
+ return true;
120
+ }
121
+
122
+ // Unix timestamp (seconds or milliseconds)
123
+ if (/^\d{10,13}$/.test(value)) {
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * Validate context doesn't contain non-deterministic operations
132
+ */
133
+ validateDeterministicContext(context) {
134
+ const nonDeterministicPatterns = [
135
+ /Math\.random/,
136
+ /Date\.now/,
137
+ /new Date\(\)/,
138
+ /crypto\.randomUUID/,
139
+ /Math\.floor\(Math\.random/
140
+ ];
141
+
142
+ const contextStr = JSON.stringify(context);
143
+
144
+ nonDeterministicPatterns.forEach(pattern => {
145
+ if (pattern.test(contextStr)) {
146
+ throw new Error(`Non-deterministic operation detected: ${pattern.source}`);
147
+ }
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Post-process rendered output for consistency
153
+ */
154
+ postProcessOutput(output) {
155
+ // Normalize line endings
156
+ let processed = output.replace(/\r\n|\r/g, '\n');
157
+
158
+ // Remove trailing whitespace from lines (but keep empty lines)
159
+ processed = processed.replace(/[^\S\n]+$/gm, '');
160
+
161
+ // Ensure consistent final newline
162
+ if (processed.length > 0 && !processed.endsWith('\n')) {
163
+ processed += '\n';
164
+ }
165
+
166
+ return processed;
167
+ }
168
+
169
+ /**
170
+ * Get deterministic time string
171
+ */
172
+ getDeterministicTime() {
173
+ return this.staticBuildTime;
174
+ }
175
+
176
+ /**
177
+ * Generate deterministic hash
178
+ */
179
+ hashContent(content) {
180
+ return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
181
+ }
182
+
183
+ /**
184
+ * Verify template produces identical output across runs
185
+ */
186
+ async verifyDeterminism(nunjucksEnv, templateContent, context, iterations = 3) {
187
+ const outputs = [];
188
+ const hashes = new Set();
189
+
190
+ for (let i = 0; i < iterations; i++) {
191
+ const output = await this.render(nunjucksEnv, templateContent, context);
192
+ const hash = this.hashContent(output);
193
+
194
+ outputs.push({ iteration: i + 1, output, hash });
195
+ hashes.add(hash);
196
+ }
197
+
198
+ const isDeterministic = hashes.size === 1;
199
+
200
+ return {
201
+ isDeterministic,
202
+ iterations,
203
+ uniqueOutputs: hashes.size,
204
+ outputs: outputs.slice(0, 2), // Include first 2 for comparison
205
+ firstHash: outputs[0]?.hash,
206
+ allHashesSame: isDeterministic
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Create reproducibility report
212
+ */
213
+ async createReproducibilityReport(nunjucksEnv, templateContent, context) {
214
+ const verification = await this.verifyDeterminism(nunjucksEnv, templateContent, context, 5);
215
+
216
+ const report = {
217
+ timestamp: this.getDeterministicTime(),
218
+ templateHash: this.hashContent(templateContent),
219
+ contextHash: this.hashContent(JSON.stringify(context)),
220
+ deterministicSettings: {
221
+ staticBuildTime: this.staticBuildTime,
222
+ blockNonDeterministic: this.blockNonDeterministic,
223
+ strictMode: this.strictMode
224
+ },
225
+ verification,
226
+ reproducible: verification.isDeterministic,
227
+ confidence: verification.isDeterministic ? 'HIGH' : 'LOW'
228
+ };
229
+
230
+ return report;
231
+ }
232
+
233
+ /**
234
+ * Get renderer statistics
235
+ */
236
+ getStats() {
237
+ return {
238
+ staticBuildTime: this.staticBuildTime,
239
+ blockNonDeterministic: this.blockNonDeterministic,
240
+ strictMode: this.strictMode
241
+ };
242
+ }
243
+ }
244
+
245
+ export default DeterministicRenderer;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Renderer Module Exports
3
+ */
4
+
5
+ export { DeterministicRenderer } from './deterministic.js';
6
+ export default { DeterministicRenderer };