@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.
- package/README.md +425 -0
- package/bin/report.mjs +529 -0
- package/bin/run.mjs +114 -0
- package/bin/verify.mjs +356 -0
- package/capability-map.md +92 -0
- package/package.json +42 -0
- package/src/classify.mjs +584 -0
- package/src/diataxis-schema.mjs +425 -0
- package/src/evidence.mjs +268 -0
- package/src/hash.mjs +37 -0
- package/src/inventory.mjs +280 -0
- package/src/reference-extractor.mjs +324 -0
- package/src/scaffold.mjs +458 -0
- package/src/stable-json.mjs +113 -0
- package/src/verify-implementation.mjs +131 -0
- package/test/determinism.test.mjs +321 -0
- package/test/evidence.test.mjs +145 -0
- package/test/fixtures/scaffold-det1/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-det1/index.md +29 -0
- package/test/fixtures/scaffold-det1/reference/reference.md +34 -0
- package/test/fixtures/scaffold-det1/tutorials/tutorial-test-tutorial.md +37 -0
- package/test/fixtures/scaffold-det2/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-det2/index.md +29 -0
- package/test/fixtures/scaffold-det2/reference/reference.md +34 -0
- package/test/fixtures/scaffold-det2/tutorials/tutorial-test-tutorial.md +37 -0
- package/test/fixtures/scaffold-empty/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-empty/index.md +25 -0
- package/test/fixtures/scaffold-empty/reference/reference.md +34 -0
- package/test/fixtures/scaffold-escape/explanation/explanation.md +35 -0
- package/test/fixtures/scaffold-escape/index.md +29 -0
- package/test/fixtures/scaffold-escape/reference/reference.md +36 -0
- package/test/fixtures/scaffold-output/explanation/explanation.md +39 -0
- package/test/fixtures/scaffold-output/how-to/howto-configure-options.md +39 -0
- package/test/fixtures/scaffold-output/index.md +41 -0
- package/test/fixtures/scaffold-output/reference/reference.md +36 -0
- package/test/fixtures/scaffold-output/tutorials/tutorial-getting-started.md +41 -0
- package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-1.inventory.json +115 -0
- package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-2.inventory.json +93 -0
- package/test/fixtures/test-artifacts/ARTIFACTS/diataxis/test-pkg-3.inventory.json +97 -0
- package/test/fixtures/test-package/LICENSE +1 -0
- package/test/fixtures/test-package/README.md +15 -0
- package/test/fixtures/test-package/docs/guide.md +3 -0
- package/test/fixtures/test-package/examples/basic.mjs +3 -0
- package/test/fixtures/test-package/src/index.mjs +3 -0
- package/test/inventory.test.mjs +199 -0
- package/test/reference-extractor.test.mjs +187 -0
- package/test/report.test.mjs +503 -0
- package/test/scaffold.test.mjs +242 -0
- 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
|
+
});
|