@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,306 @@
1
+ /**
2
+ * @fileoverview RDF Graph Builder
3
+ * Converts parsed JSDoc data to RDF triples
4
+ */
5
+
6
+ import crypto from 'crypto';
7
+
8
+ /**
9
+ * RDF Vocabulary Prefixes
10
+ */
11
+ export const PREFIXES = {
12
+ rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
13
+ rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
14
+ xsd: 'http://www.w3.org/2001/XMLSchema#',
15
+ code: 'http://unrdf.org/vocab/code#',
16
+ fs: 'http://unrdf.org/vocab/fs#',
17
+ doc: 'http://unrdf.org/vocab/doc#',
18
+ ex: 'http://example.org/',
19
+ };
20
+
21
+ /**
22
+ * Generate a deterministic URI for a resource
23
+ * @param {string} path - Resource path
24
+ * @returns {string} URI
25
+ */
26
+ function generateURI(path) {
27
+ // Use package path as namespace
28
+ const namespace = 'http://unrdf.org/packages/';
29
+ // Create deterministic ID from path
30
+ const id = path.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-');
31
+ return namespace + id;
32
+ }
33
+
34
+ /**
35
+ * Generate a blank node ID
36
+ * @param {string} name - Name for blank node
37
+ * @returns {string} Blank node ID
38
+ */
39
+ function blankNode(name) {
40
+ const hash = crypto.createHash('md5').update(name).digest('hex').substring(0, 8);
41
+ return `_:${name}_${hash}`;
42
+ }
43
+
44
+ /**
45
+ * Escape string for Turtle literal
46
+ * @param {string} str - String to escape
47
+ * @returns {string} Escaped string
48
+ */
49
+ function escapeLiteral(str) {
50
+ if (!str) return '""';
51
+ return `"${str
52
+ .replace(/\\/g, '\\\\')
53
+ .replace(/"/g, '\\"')
54
+ .replace(/\n/g, '\\n')
55
+ .replace(/\r/g, '\\r')
56
+ .replace(/\t/g, '\\t')}"`;
57
+ }
58
+
59
+ /**
60
+ * Build RDF graph from parsed file data
61
+ * @param {Object} parsedFile - Output from parser.parseFile()
62
+ * @returns {string} Turtle RDF representation
63
+ */
64
+ export function buildRDFGraph(parsedFile) {
65
+ const triples = [];
66
+
67
+ // File resource
68
+ const fileURI = generateURI(parsedFile.relativePath);
69
+
70
+ triples.push(`# File: ${parsedFile.relativePath}`);
71
+ triples.push(`${fileURI}`);
72
+ triples.push(` a fs:File , code:Module ;`);
73
+ triples.push(` fs:path ${escapeLiteral(parsedFile.relativePath)} ;`);
74
+ triples.push(` fs:absolutePath ${escapeLiteral(parsedFile.file)} ;`);
75
+ triples.push(` code:commentCount ${parsedFile.comments} .`);
76
+ triples.push('');
77
+
78
+ // Exports
79
+ parsedFile.exports.forEach((exp, idx) => {
80
+ const exportURI = `${fileURI}#${exp.name}`;
81
+
82
+ triples.push(`# Export: ${exp.name}`);
83
+ triples.push(`${exportURI}`);
84
+
85
+ if (exp.type === 'function') {
86
+ triples.push(` a code:Function ;`);
87
+ triples.push(` code:name ${escapeLiteral(exp.name)} ;`);
88
+
89
+ if (exp.description) {
90
+ triples.push(` doc:description ${escapeLiteral(exp.description)} ;`);
91
+ }
92
+
93
+ if (exp.async) {
94
+ triples.push(` code:async true ;`);
95
+ }
96
+
97
+ // Parameters
98
+ exp.params?.forEach((param, paramIdx) => {
99
+ const paramNode = blankNode(`${exp.name}_param_${param.name}`);
100
+ triples.push(` code:param ${paramNode} ;`);
101
+
102
+ const paramTriples = [];
103
+ paramTriples.push(`${paramNode}`);
104
+ paramTriples.push(` a code:Parameter ;`);
105
+ paramTriples.push(` code:name ${escapeLiteral(param.name)} ;`);
106
+ paramTriples.push(` code:type ${escapeLiteral(param.type)} ;`);
107
+
108
+ if (param.optional) {
109
+ paramTriples.push(` code:optional true ;`);
110
+ }
111
+
112
+ if (param.description) {
113
+ paramTriples.push(` doc:description ${escapeLiteral(param.description)} ;`);
114
+ }
115
+
116
+ if (param.default !== undefined) {
117
+ paramTriples.push(` code:default ${escapeLiteral(String(param.default))} ;`);
118
+ }
119
+
120
+ paramTriples.push(` code:position ${paramIdx} .`);
121
+ triples.push(...paramTriples);
122
+ triples.push('');
123
+ });
124
+
125
+ // Returns
126
+ if (exp.returns) {
127
+ triples.push(` code:returns ${escapeLiteral(exp.returns.type)} ;`);
128
+ if (exp.returns.description) {
129
+ triples.push(` code:returnsDescription ${escapeLiteral(exp.returns.description)} ;`);
130
+ }
131
+ }
132
+
133
+ // Examples
134
+ exp.examples?.forEach((example, exIdx) => {
135
+ triples.push(` doc:example ${escapeLiteral(example)} ;`);
136
+ });
137
+
138
+ // Remove trailing semicolon from last triple
139
+ const lastLine = triples[triples.length - 1];
140
+ triples[triples.length - 1] = lastLine.replace(/\s;$/, ' .');
141
+
142
+ } else if (exp.type === 'class') {
143
+ triples.push(` a code:Class ;`);
144
+ triples.push(` code:name ${escapeLiteral(exp.name)} ;`);
145
+
146
+ if (exp.description) {
147
+ triples.push(` doc:description ${escapeLiteral(exp.description)} ;`);
148
+ }
149
+
150
+ if (exp.extends) {
151
+ triples.push(` code:extends ${escapeLiteral(exp.extends)} ;`);
152
+ }
153
+
154
+ // Methods
155
+ exp.methods?.forEach(method => {
156
+ const methodNode = blankNode(`${exp.name}_${method.name}`);
157
+ triples.push(` code:method ${methodNode} ;`);
158
+
159
+ const methodTriples = [];
160
+ methodTriples.push(`${methodNode}`);
161
+ methodTriples.push(` a code:Method ;`);
162
+ methodTriples.push(` code:name ${escapeLiteral(method.name)} ;`);
163
+ methodTriples.push(` code:kind ${escapeLiteral(method.kind)} ;`);
164
+
165
+ if (method.static) {
166
+ methodTriples.push(` code:static true ;`);
167
+ }
168
+
169
+ if (method.async) {
170
+ methodTriples.push(` code:async true ;`);
171
+ }
172
+
173
+ methodTriples.push(` code:paramCount ${method.params?.length || 0} .`);
174
+ triples.push(...methodTriples);
175
+ triples.push('');
176
+ });
177
+
178
+ // Remove trailing semicolon
179
+ const lastLine = triples[triples.length - 1];
180
+ if (lastLine.includes(';')) {
181
+ triples[triples.length - 1] = lastLine.replace(/\s;$/, ' .');
182
+ }
183
+
184
+ } else if (exp.type === 'variable') {
185
+ triples.push(` a code:Variable ;`);
186
+ triples.push(` code:name ${escapeLiteral(exp.name)} ;`);
187
+ triples.push(` code:valueType ${escapeLiteral(exp.valueType)} .`);
188
+ }
189
+
190
+ triples.push('');
191
+ });
192
+
193
+ // Imports (for cross-referencing)
194
+ parsedFile.imports?.forEach((imp, idx) => {
195
+ const importNode = blankNode(`import_${idx}`);
196
+
197
+ triples.push(`# Import from: ${imp.source}`);
198
+ triples.push(`${fileURI}`);
199
+ triples.push(` code:imports ${importNode} .`);
200
+ triples.push('');
201
+
202
+ triples.push(`${importNode}`);
203
+ triples.push(` a code:Import ;`);
204
+ triples.push(` code:source ${escapeLiteral(imp.source)} ;`);
205
+
206
+ imp.specifiers.forEach((spec, specIdx) => {
207
+ const specNode = blankNode(`import_${idx}_spec_${specIdx}`);
208
+ triples.push(` code:specifier ${specNode} ;`);
209
+
210
+ triples.push(`${specNode}`);
211
+ triples.push(` a code:ImportSpecifier ;`);
212
+ triples.push(` code:imported ${escapeLiteral(spec.imported)} ;`);
213
+ triples.push(` code:local ${escapeLiteral(spec.local)} ;`);
214
+ triples.push(` code:specifierType ${escapeLiteral(spec.type)} .`);
215
+ triples.push('');
216
+ });
217
+
218
+ triples.push('');
219
+ });
220
+
221
+ // Build complete Turtle document
222
+ const prefixDeclarations = Object.entries(PREFIXES)
223
+ .map(([prefix, uri]) => `@prefix ${prefix}: <${uri}> .`)
224
+ .join('\n');
225
+
226
+ return `${prefixDeclarations}\n\n${triples.join('\n')}`;
227
+ }
228
+
229
+ /**
230
+ * Build RDF graph for multiple files
231
+ * @param {Array} parsedFiles - Array of parsed file data
232
+ * @returns {string} Combined Turtle RDF
233
+ */
234
+ export function buildRDFGraphs(parsedFiles) {
235
+ const graphs = parsedFiles
236
+ .filter(file => !file.error)
237
+ .map(file => buildRDFGraph(file));
238
+
239
+ const prefixDeclarations = Object.entries(PREFIXES)
240
+ .map(([prefix, uri]) => `@prefix ${prefix}: <${uri}> .`)
241
+ .join('\n');
242
+
243
+ // Remove duplicate prefix declarations from individual graphs
244
+ const graphsWithoutPrefixes = graphs.map(graph =>
245
+ graph.replace(/@prefix[^\n]+\n/g, '').trim()
246
+ );
247
+
248
+ return `${prefixDeclarations}\n\n${graphsWithoutPrefixes.join('\n\n')}`;
249
+ }
250
+
251
+ /**
252
+ * Build JSON-LD representation
253
+ * @param {Object} parsedFile - Parsed file data
254
+ * @returns {Object} JSON-LD document
255
+ */
256
+ export function buildJSONLD(parsedFile) {
257
+ const fileURI = generateURI(parsedFile.relativePath);
258
+
259
+ const document = {
260
+ '@context': PREFIXES,
261
+ '@id': fileURI,
262
+ '@type': ['fs:File', 'code:Module'],
263
+ 'fs:path': parsedFile.relativePath,
264
+ 'fs:absolutePath': parsedFile.file,
265
+ 'code:commentCount': parsedFile.comments,
266
+ 'code:exports': [],
267
+ };
268
+
269
+ parsedFile.exports.forEach(exp => {
270
+ const exportObj = {
271
+ '@id': `${fileURI}#${exp.name}`,
272
+ '@type': `code:${exp.type.charAt(0).toUpperCase() + exp.type.slice(1)}`,
273
+ 'code:name': exp.name,
274
+ };
275
+
276
+ if (exp.description) {
277
+ exportObj['doc:description'] = exp.description;
278
+ }
279
+
280
+ if (exp.type === 'function') {
281
+ if (exp.params) {
282
+ exportObj['code:param'] = exp.params.map(param => ({
283
+ '@type': 'code:Parameter',
284
+ 'code:name': param.name,
285
+ 'code:type': param.type,
286
+ 'code:optional': param.optional,
287
+ 'doc:description': param.description,
288
+ }));
289
+ }
290
+
291
+ if (exp.returns) {
292
+ exportObj['code:returns'] = exp.returns.type;
293
+ }
294
+
295
+ if (exp.examples?.length) {
296
+ exportObj['doc:example'] = exp.examples;
297
+ }
298
+ }
299
+
300
+ document['code:exports'].push(exportObj);
301
+ });
302
+
303
+ return document;
304
+ }
305
+
306
+ export default { buildRDFGraph, buildRDFGraphs, buildJSONLD, PREFIXES };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @fileoverview Workspace scanner for batch documentation generation
3
+ * Scans packages and finds all documentable source files
4
+ */
5
+
6
+ import { readdir, stat } from 'fs/promises';
7
+ import { join, relative } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+
12
+ /**
13
+ * Scan workspace for source files
14
+ * @param {string} rootDir - Workspace root directory
15
+ * @param {Object} options - Scanner options
16
+ * @returns {Promise<Array>} Array of file paths grouped by package
17
+ */
18
+ export async function scanWorkspace(rootDir, options = {}) {
19
+ const {
20
+ packagesDir = 'packages',
21
+ include = ['**/*.mjs', '**/*.js'],
22
+ exclude = ['**/node_modules/**', '**/dist/**', '**/test/**', '**/*.test.*', '**/*.spec.*'],
23
+ } = options;
24
+
25
+ const packagesPath = join(rootDir, packagesDir);
26
+ const packages = [];
27
+
28
+ try {
29
+ const packageDirs = await readdir(packagesPath);
30
+
31
+ for (const pkgName of packageDirs) {
32
+ const pkgPath = join(packagesPath, pkgName);
33
+ const pkgStat = await stat(pkgPath);
34
+
35
+ if (!pkgStat.isDirectory()) continue;
36
+
37
+ // Check if package has a src directory
38
+ const srcPath = join(pkgPath, 'src');
39
+ let hasSrc = false;
40
+ try {
41
+ const srcStat = await stat(srcPath);
42
+ hasSrc = srcStat.isDirectory();
43
+ } catch {
44
+ // No src directory
45
+ }
46
+
47
+ if (!hasSrc) continue;
48
+
49
+ // Scan for source files
50
+ const sourceFiles = await scanDirectory(srcPath, {
51
+ include,
52
+ exclude,
53
+ rootDir,
54
+ });
55
+
56
+ if (sourceFiles.length > 0) {
57
+ packages.push({
58
+ name: pkgName,
59
+ path: pkgPath,
60
+ relativePath: relative(rootDir, pkgPath),
61
+ srcPath,
62
+ sourceFiles,
63
+ });
64
+ }
65
+ }
66
+
67
+ return packages;
68
+ } catch (error) {
69
+ console.error(`Failed to scan workspace: ${error.message}`);
70
+ return [];
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Recursively scan directory for source files
76
+ * @param {string} dir - Directory to scan
77
+ * @param {Object} options - Scan options
78
+ * @returns {Promise<Array>} Array of file paths
79
+ */
80
+ async function scanDirectory(dir, options) {
81
+ const { include, exclude, rootDir } = options;
82
+ const files = [];
83
+
84
+ try {
85
+ const entries = await readdir(dir, { withFileTypes: true });
86
+
87
+ for (const entry of entries) {
88
+ const fullPath = join(dir, entry.name);
89
+ const relativePath = relative(rootDir, fullPath);
90
+
91
+ // Check exclusions
92
+ if (shouldExclude(relativePath, exclude)) {
93
+ continue;
94
+ }
95
+
96
+ if (entry.isDirectory()) {
97
+ // Recursively scan subdirectories
98
+ const subFiles = await scanDirectory(fullPath, options);
99
+ files.push(...subFiles);
100
+ } else if (entry.isFile()) {
101
+ // Check if file matches include patterns
102
+ if (shouldInclude(entry.name, include)) {
103
+ files.push(fullPath);
104
+ }
105
+ }
106
+ }
107
+ } catch (error) {
108
+ console.error(`Failed to scan directory ${dir}: ${error.message}`);
109
+ }
110
+
111
+ return files;
112
+ }
113
+
114
+ /**
115
+ * Check if path should be excluded
116
+ * @param {string} path - Path to check
117
+ * @param {Array<string>} patterns - Exclusion patterns
118
+ * @returns {boolean} True if should exclude
119
+ */
120
+ function shouldExclude(path, patterns) {
121
+ return patterns.some(pattern => {
122
+ const regex = patternToRegex(pattern);
123
+ return regex.test(path);
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Check if filename should be included
129
+ * @param {string} filename - Filename to check
130
+ * @param {Array<string>} patterns - Inclusion patterns
131
+ * @returns {boolean} True if should include
132
+ */
133
+ function shouldInclude(filename, patterns) {
134
+ return patterns.some(pattern => {
135
+ const regex = patternToRegex(pattern);
136
+ return regex.test(filename);
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Convert glob pattern to regex
142
+ * @param {string} pattern - Glob pattern
143
+ * @returns {RegExp} Regular expression
144
+ */
145
+ function patternToRegex(pattern) {
146
+ // Simple glob to regex conversion
147
+ const regexPattern = pattern
148
+ .replace(/\*\*/g, '.*')
149
+ .replace(/\*/g, '[^/]*')
150
+ .replace(/\./g, '\\.');
151
+
152
+ return new RegExp(`^${regexPattern}$`);
153
+ }
154
+
155
+ /**
156
+ * Group files by package and module
157
+ * @param {Array} packages - Scanned packages
158
+ * @returns {Object} Grouped file structure
159
+ */
160
+ export function groupFilesByModule(packages) {
161
+ const grouped = {};
162
+
163
+ packages.forEach(pkg => {
164
+ const modules = new Map();
165
+
166
+ pkg.sourceFiles.forEach(filePath => {
167
+ const relativePath = relative(pkg.srcPath, filePath);
168
+ const parts = relativePath.split('/');
169
+
170
+ // Group by top-level directory in src/
171
+ const moduleName = parts.length > 1 ? parts[0] : 'root';
172
+
173
+ if (!modules.has(moduleName)) {
174
+ modules.set(moduleName, []);
175
+ }
176
+
177
+ modules.get(moduleName).push(filePath);
178
+ });
179
+
180
+ grouped[pkg.name] = {
181
+ ...pkg,
182
+ modules: Object.fromEntries(modules),
183
+ };
184
+ });
185
+
186
+ return grouped;
187
+ }
188
+
189
+ export default { scanWorkspace, groupFilesByModule };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * DETERMINISTIC Template Engine - Complete Exports
3
+ *
4
+ * 4-Stage Pipeline: plan → render → post → attest
5
+ * 100% Deterministic guarantees with cryptographic attestation
6
+ */
7
+
8
+ export { TemplateEngine } from './template-engine.js';
9
+ export { DeterministicRenderer } from './renderer.js';
10
+ export { DeterministicPipeline } from './pipeline.js';
11
+
12
+ // Factory for deterministic engine with complete pipeline
13
+ export function createDeterministicEngine(options = {}) {
14
+ const pipeline = new DeterministicPipeline(options);
15
+
16
+ return {
17
+ pipeline,
18
+ renderer: pipeline.renderer,
19
+
20
+ async render(template, data, opts = {}) {
21
+ return await pipeline.execute(template, data, opts);
22
+ },
23
+
24
+ async verifyDeterminism(template, data, iterations = 3) {
25
+ return await pipeline.verifyDeterminism(template, data, iterations);
26
+ },
27
+
28
+ async renderBatch(batch, opts = {}) {
29
+ return await pipeline.executeBatch(batch, opts);
30
+ },
31
+
32
+ getStats() { return pipeline.getStats(); },
33
+ resetStats() { pipeline.resetStats(); }
34
+ };
35
+ }
36
+
37
+ export default {
38
+ TemplateEngine,
39
+ DeterministicRenderer,
40
+ DeterministicPipeline,
41
+ createDeterministicEngine
42
+ };