@unrdf/diataxis-kit 26.4.2

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 (49) hide show
  1. package/README.md +425 -0
  2. package/bin/report.mjs +529 -0
  3. package/bin/run.mjs +114 -0
  4. package/bin/verify.mjs +356 -0
  5. package/capability-map.md +92 -0
  6. package/package.json +42 -0
  7. package/src/classify.mjs +584 -0
  8. package/src/diataxis-schema.mjs +425 -0
  9. package/src/evidence.mjs +268 -0
  10. package/src/hash.mjs +37 -0
  11. package/src/inventory.mjs +280 -0
  12. package/src/reference-extractor.mjs +324 -0
  13. package/src/scaffold.mjs +458 -0
  14. package/src/stable-json.mjs +113 -0
  15. package/src/verify-implementation.mjs +131 -0
  16. package/test/determinism.test.mjs +321 -0
  17. package/test/evidence.test.mjs +145 -0
  18. package/test/fixtures/scaffold-det1/explanation/explanation.md +35 -0
  19. package/test/fixtures/scaffold-det1/index.md +29 -0
  20. package/test/fixtures/scaffold-det1/reference/reference.md +34 -0
  21. package/test/fixtures/scaffold-det1/tutorials/tutorial-test-tutorial.md +37 -0
  22. package/test/fixtures/scaffold-det2/explanation/explanation.md +35 -0
  23. package/test/fixtures/scaffold-det2/index.md +29 -0
  24. package/test/fixtures/scaffold-det2/reference/reference.md +34 -0
  25. package/test/fixtures/scaffold-det2/tutorials/tutorial-test-tutorial.md +37 -0
  26. package/test/fixtures/scaffold-empty/explanation/explanation.md +35 -0
  27. package/test/fixtures/scaffold-empty/index.md +25 -0
  28. package/test/fixtures/scaffold-empty/reference/reference.md +34 -0
  29. package/test/fixtures/scaffold-escape/explanation/explanation.md +35 -0
  30. package/test/fixtures/scaffold-escape/index.md +29 -0
  31. package/test/fixtures/scaffold-escape/reference/reference.md +36 -0
  32. package/test/fixtures/scaffold-output/explanation/explanation.md +39 -0
  33. package/test/fixtures/scaffold-output/how-to/howto-configure-options.md +39 -0
  34. package/test/fixtures/scaffold-output/index.md +41 -0
  35. package/test/fixtures/scaffold-output/reference/reference.md +36 -0
  36. package/test/fixtures/scaffold-output/tutorials/tutorial-getting-started.md +41 -0
  37. package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-1.inventory.json +115 -0
  38. package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-2.inventory.json +93 -0
  39. package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-3.inventory.json +97 -0
  40. package/test/fixtures/test-package/LICENSE +1 -0
  41. package/test/fixtures/test-package/README.md +15 -0
  42. package/test/fixtures/test-package/docs/guide.md +3 -0
  43. package/test/fixtures/test-package/examples/basic.mjs +3 -0
  44. package/test/fixtures/test-package/src/index.mjs +3 -0
  45. package/test/inventory.test.mjs +199 -0
  46. package/test/reference-extractor.test.mjs +187 -0
  47. package/test/report.test.mjs +503 -0
  48. package/test/scaffold.test.mjs +242 -0
  49. package/test/verify-gate.test.mjs +634 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * @file scaffold.test.mjs
3
+ * @description Tests for scaffold generator module
4
+ */
5
+
6
+ import { describe, it } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import { generateScaffold } from '../src/scaffold.mjs';
9
+ import { createDiataxisEntry } from '../src/diataxis-schema.mjs';
10
+ import { readFile, rm } from 'node:fs/promises';
11
+ import { existsSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { dirname } from 'node:path';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ const testOutputDir = join(__dirname, 'fixtures', 'scaffold-output');
19
+
20
+ describe('scaffold.mjs', () => {
21
+ describe('generateScaffold', () => {
22
+ it('should generate complete scaffold structure', async () => {
23
+ // Clean up test output directory
24
+ if (existsSync(testOutputDir)) {
25
+ await rm(testOutputDir, { recursive: true, force: true });
26
+ }
27
+
28
+ // Create a minimal DiataxisEntry
29
+ const entry = createDiataxisEntry('test-package', '1.0.0', {
30
+ readmeHeadings: ['Installation', 'Usage'],
31
+ docsFiles: ['api.md'],
32
+ examplesFiles: ['example.js'],
33
+ tutorials: [
34
+ {
35
+ id: 'getting-started',
36
+ title: 'Getting Started',
37
+ goal: 'Learn the basics',
38
+ prerequisites: ['Node.js 18+'],
39
+ stepsOutline: ['Install package', 'Create config', 'Run first command'],
40
+ confidenceScore: 0.9,
41
+ source: ['README.md', 'docs/tutorial.md']
42
+ }
43
+ ],
44
+ howtos: [
45
+ {
46
+ id: 'configure-options',
47
+ title: 'Configure Options',
48
+ task: 'Configure advanced options',
49
+ context: 'When you need custom configuration',
50
+ steps: ['Open config file', 'Set options', 'Validate config'],
51
+ confidenceScore: 0.85,
52
+ source: ['docs/config.md']
53
+ }
54
+ ],
55
+ reference: {
56
+ id: 'api-reference',
57
+ title: 'API Reference',
58
+ items: [
59
+ {
60
+ name: 'createClient',
61
+ type: 'export',
62
+ description: 'Creates a new client instance',
63
+ example: 'const client = createClient();'
64
+ }
65
+ ],
66
+ confidenceScore: 0.95,
67
+ source: ['src/index.mjs']
68
+ },
69
+ explanation: {
70
+ id: 'architecture',
71
+ title: 'Architecture',
72
+ concepts: ['Event-driven design', 'Plugin system'],
73
+ architecture: 'The system uses a modular architecture with plugins.',
74
+ tradeoffs: ['Performance vs flexibility', 'Memory vs speed'],
75
+ confidenceScore: 0.8,
76
+ source: ['docs/architecture.md']
77
+ },
78
+ confidence: {
79
+ tutorials: 0.9,
80
+ howtos: 0.85,
81
+ reference: 0.95,
82
+ explanation: 0.8
83
+ }
84
+ });
85
+
86
+ // Generate scaffold
87
+ const result = await generateScaffold(entry, testOutputDir);
88
+
89
+ // Verify result structure
90
+ assert.ok(result);
91
+ assert.equal(result.packageName, 'test-package');
92
+ assert.ok(Array.isArray(result.filesGenerated));
93
+ assert.ok(result.filesGenerated.length > 0);
94
+ assert.ok(typeof result.indexPath === 'string');
95
+ assert.ok(typeof result.filesHash === 'string');
96
+ assert.equal(result.filesHash.length, 64); // SHA256 hex
97
+
98
+ // Verify index.md exists
99
+ assert.ok(existsSync(result.indexPath));
100
+ const indexContent = await readFile(result.indexPath, 'utf8');
101
+ assert.ok(indexContent.includes('test-package'));
102
+ assert.ok(indexContent.includes('**Tutorials**: 1'));
103
+ assert.ok(indexContent.includes('**How-To Guides**: 1'));
104
+
105
+ // Verify tutorial file
106
+ const tutorialPath = join(testOutputDir, 'tutorials', 'tutorial-getting-started.md');
107
+ assert.ok(existsSync(tutorialPath));
108
+ const tutorialContent = await readFile(tutorialPath, 'utf8');
109
+ assert.ok(tutorialContent.includes('---'));
110
+ assert.ok(tutorialContent.includes('title: "Getting Started"'));
111
+ assert.ok(tutorialContent.includes('type: "tutorial"'));
112
+ assert.ok(tutorialContent.includes('confidenceScore: 0.9'));
113
+ assert.ok(tutorialContent.includes('proof:'));
114
+ assert.ok(tutorialContent.includes('## Prerequisites'));
115
+ assert.ok(tutorialContent.includes('Node.js 18+'));
116
+
117
+ // Verify how-to file
118
+ const howtoPath = join(testOutputDir, 'how-to', 'howto-configure-options.md');
119
+ assert.ok(existsSync(howtoPath));
120
+ const howtoContent = await readFile(howtoPath, 'utf8');
121
+ assert.ok(howtoContent.includes('title: "Configure Options"'));
122
+ assert.ok(howtoContent.includes('type: "how-to"'));
123
+ assert.ok(howtoContent.includes('## Task'));
124
+ assert.ok(howtoContent.includes('## Context'));
125
+
126
+ // Verify reference file
127
+ const referencePath = join(testOutputDir, 'reference', 'reference.md');
128
+ assert.ok(existsSync(referencePath));
129
+ const referenceContent = await readFile(referencePath, 'utf8');
130
+ assert.ok(referenceContent.includes('API Reference'));
131
+ assert.ok(referenceContent.includes('| Name | Type | Description |'));
132
+ assert.ok(referenceContent.includes('createClient'));
133
+
134
+ // Verify explanation file
135
+ const explanationPath = join(testOutputDir, 'explanation', 'explanation.md');
136
+ assert.ok(existsSync(explanationPath));
137
+ const explanationContent = await readFile(explanationPath, 'utf8');
138
+ assert.ok(explanationContent.includes('Architecture'));
139
+ assert.ok(explanationContent.includes('Event-driven design'));
140
+ assert.ok(explanationContent.includes('## Tradeoffs'));
141
+
142
+ // Verify proof blocks exist
143
+ assert.ok(tutorialContent.includes('## Proof'));
144
+ assert.ok(tutorialContent.includes('```json'));
145
+ assert.ok(howtoContent.includes('## Proof'));
146
+ assert.ok(referenceContent.includes('## Proof'));
147
+ assert.ok(explanationContent.includes('## Proof'));
148
+ });
149
+
150
+ it('should handle empty entry gracefully', async () => {
151
+ const emptyOutputDir = join(__dirname, 'fixtures', 'scaffold-empty');
152
+ if (existsSync(emptyOutputDir)) {
153
+ await rm(emptyOutputDir, { recursive: true, force: true });
154
+ }
155
+
156
+ const entry = createDiataxisEntry('empty-package', '0.0.1', {});
157
+
158
+ const result = await generateScaffold(entry, emptyOutputDir);
159
+
160
+ assert.ok(result);
161
+ assert.equal(result.packageName, 'empty-package');
162
+ assert.ok(existsSync(result.indexPath));
163
+
164
+ // Should still create reference and explanation files
165
+ assert.ok(existsSync(join(emptyOutputDir, 'reference', 'reference.md')));
166
+ assert.ok(existsSync(join(emptyOutputDir, 'explanation', 'explanation.md')));
167
+ });
168
+
169
+ it('should produce deterministic output', async () => {
170
+ const deterministicDir1 = join(__dirname, 'fixtures', 'scaffold-det1');
171
+ const deterministicDir2 = join(__dirname, 'fixtures', 'scaffold-det2');
172
+
173
+ // Clean up
174
+ for (const dir of [deterministicDir1, deterministicDir2]) {
175
+ if (existsSync(dir)) {
176
+ await rm(dir, { recursive: true, force: true });
177
+ }
178
+ }
179
+
180
+ // Set deterministic mode
181
+ process.env.DETERMINISTIC = '1';
182
+
183
+ const entry = createDiataxisEntry('test-deterministic', '1.0.0', {
184
+ tutorials: [
185
+ {
186
+ title: 'Test Tutorial',
187
+ goal: 'Test',
188
+ prerequisites: [],
189
+ stepsOutline: ['Step 1'],
190
+ confidenceScore: 0.9,
191
+ source: ['test.md']
192
+ }
193
+ ]
194
+ });
195
+
196
+ const result1 = await generateScaffold(entry, deterministicDir1);
197
+ const result2 = await generateScaffold(entry, deterministicDir2);
198
+
199
+ // Hashes should be identical
200
+ assert.equal(result1.filesHash, result2.filesHash);
201
+
202
+ // Content should be identical
203
+ const index1 = await readFile(result1.indexPath, 'utf8');
204
+ const index2 = await readFile(result2.indexPath, 'utf8');
205
+ assert.equal(index1, index2);
206
+
207
+ // Clean up env
208
+ delete process.env.DETERMINISTIC;
209
+ });
210
+
211
+ it('should escape markdown in table cells', async () => {
212
+ const escapeTestDir = join(__dirname, 'fixtures', 'scaffold-escape');
213
+ if (existsSync(escapeTestDir)) {
214
+ await rm(escapeTestDir, { recursive: true, force: true });
215
+ }
216
+
217
+ const entry = createDiataxisEntry('escape-test', '1.0.0', {
218
+ reference: {
219
+ title: 'Reference',
220
+ items: [
221
+ {
222
+ name: 'test|pipe',
223
+ type: 'export',
224
+ description: 'Description with | pipe',
225
+ example: null
226
+ }
227
+ ],
228
+ confidenceScore: 0.9,
229
+ source: ['test.md']
230
+ }
231
+ });
232
+
233
+ const result = await generateScaffold(entry, escapeTestDir);
234
+ const referencePath = join(escapeTestDir, 'reference', 'reference.md');
235
+ const content = await readFile(referencePath, 'utf8');
236
+
237
+ // Pipes should be escaped
238
+ assert.ok(content.includes('test\\|pipe'));
239
+ assert.ok(content.includes('Description with \\| pipe'));
240
+ });
241
+ });
242
+ });