@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
package/bin/verify.mjs ADDED
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file Diátaxis coverage verification gate
4
+ * @description Verifies that all workspace packages meet Diátaxis coverage requirements
5
+ *
6
+ * Requirements per package:
7
+ * - At least 1 tutorial stub (tutorials.length >= 1)
8
+ * - At least 2 how-to stubs (howtos.length >= 2)
9
+ * - Reference page exists (reference.items.length > 0)
10
+ * - Explanation page exists (explanation with content)
11
+ */
12
+
13
+ import { readFile, readdir } from 'node:fs/promises';
14
+ import { join, resolve } from 'node:path';
15
+ import { existsSync } from 'node:fs';
16
+ import { stableStringify } from '../src/stable-json.mjs';
17
+
18
+ /**
19
+ * Parse CLI arguments manually (no external dependencies)
20
+ * @param {string[]} argv - Process arguments
21
+ * @returns {Object} Parsed options
22
+ */
23
+ function parseArgs(argv) {
24
+ const options = {
25
+ json: false,
26
+ failFast: false,
27
+ threshold: 0,
28
+ help: false
29
+ };
30
+
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const arg = argv[i];
33
+
34
+ if (arg === '--json') {
35
+ options.json = true;
36
+ } else if (arg === '--fail-fast') {
37
+ options.failFast = true;
38
+ } else if (arg === '--threshold') {
39
+ const value = argv[i + 1];
40
+ if (value === undefined || isNaN(parseInt(value, 10))) {
41
+ console.error('Error: --threshold requires a numeric value');
42
+ process.exit(1);
43
+ }
44
+ options.threshold = parseInt(value, 10);
45
+ i++; // Skip next arg
46
+ } else if (arg === '--help' || arg === '-h') {
47
+ options.help = true;
48
+ }
49
+ }
50
+
51
+ return options;
52
+ }
53
+
54
+ /**
55
+ * Display help message
56
+ */
57
+ function showHelp() {
58
+ console.log(`
59
+ Diátaxis Coverage Verification Gate
60
+
61
+ Usage: node bin/verify.mjs [options]
62
+
63
+ Options:
64
+ --json Output JSON instead of human-readable format
65
+ --fail-fast Exit on first failure (don't check all packages)
66
+ --threshold <num> Only fail if more than NUM packages fail (default: 0)
67
+ --help, -h Show this help message
68
+
69
+ Requirements per package:
70
+ - At least 1 tutorial stub
71
+ - At least 2 how-to stubs
72
+ - Reference page exists (with items)
73
+ - Explanation page exists (with content)
74
+
75
+ Exit codes:
76
+ 0 - All packages pass (or failures <= threshold)
77
+ 1 - One or more packages fail verification
78
+ `.trim());
79
+ }
80
+
81
+ /**
82
+ * Check if reference is populated
83
+ * @param {Object} reference - Reference object
84
+ * @returns {boolean} True if reference has content
85
+ */
86
+ function isReferencPopulated(reference) {
87
+ if (!reference || typeof reference !== 'object') {
88
+ return false;
89
+ }
90
+
91
+ // Reference is populated if it has items
92
+ if (Array.isArray(reference.items) && reference.items.length > 0) {
93
+ return true;
94
+ }
95
+
96
+ // Or if it has a confidence score > 0
97
+ if (reference.confidenceScore > 0) {
98
+ return true;
99
+ }
100
+
101
+ return false;
102
+ }
103
+
104
+ /**
105
+ * Check if explanation is populated
106
+ * @param {Object} explanation - Explanation object
107
+ * @returns {boolean} True if explanation has content
108
+ */
109
+ function isExplanationPopulated(explanation) {
110
+ if (!explanation || typeof explanation !== 'object') {
111
+ return false;
112
+ }
113
+
114
+ // Explanation is populated if it has concepts, architecture, or tradeoffs
115
+ if (Array.isArray(explanation.concepts) && explanation.concepts.length > 0) {
116
+ return true;
117
+ }
118
+
119
+ if (explanation.architecture && explanation.architecture.trim() !== '') {
120
+ return true;
121
+ }
122
+
123
+ if (Array.isArray(explanation.tradeoffs) && explanation.tradeoffs.length > 0) {
124
+ return true;
125
+ }
126
+
127
+ // Or if it has a confidence score > 0
128
+ if (explanation.confidenceScore > 0) {
129
+ return true;
130
+ }
131
+
132
+ return false;
133
+ }
134
+
135
+ /**
136
+ * Verify a single package's Diátaxis coverage
137
+ * @param {string} packageName - Package name
138
+ * @param {Object} entry - Diátaxis entry
139
+ * @returns {Object} Verification result
140
+ */
141
+ function verifyPackage(packageName, entry) {
142
+ const failures = [];
143
+
144
+ // Check tutorials (need at least 1)
145
+ if (!Array.isArray(entry.tutorials) || entry.tutorials.length < 1) {
146
+ failures.push(`Missing tutorials (${entry.tutorials?.length || 0}/1 required)`);
147
+ }
148
+
149
+ // Check how-tos (need at least 2)
150
+ if (!Array.isArray(entry.howtos) || entry.howtos.length < 2) {
151
+ failures.push(`Missing how-tos (${entry.howtos?.length || 0}/2 required)`);
152
+ }
153
+
154
+ // Check reference
155
+ if (!isReferencPopulated(entry.reference)) {
156
+ failures.push('Missing reference');
157
+ }
158
+
159
+ // Check explanation
160
+ if (!isExplanationPopulated(entry.explanation)) {
161
+ failures.push('Missing explanation');
162
+ }
163
+
164
+ return {
165
+ packageName,
166
+ passing: failures.length === 0,
167
+ failures
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Main verification logic
173
+ * @param {Object} options - CLI options
174
+ * @returns {Promise<number>} Exit code
175
+ */
176
+ async function main(options) {
177
+ const workspaceRoot = resolve(process.cwd());
178
+ const artifactsDir = join(workspaceRoot, 'ARTIFACTS', 'diataxis');
179
+ const inventoryPath = join(artifactsDir, 'inventory.json');
180
+
181
+ // Check if ARTIFACTS/diataxis exists
182
+ if (!existsSync(artifactsDir)) {
183
+ if (options.json) {
184
+ console.log(stableStringify({
185
+ error: 'ARTIFACTS/diataxis directory not found',
186
+ path: artifactsDir
187
+ }));
188
+ } else {
189
+ console.error(`Error: ARTIFACTS/diataxis directory not found at ${artifactsDir}`);
190
+ }
191
+ return 1;
192
+ }
193
+
194
+ // Check if inventory.json exists
195
+ if (!existsSync(inventoryPath)) {
196
+ if (options.json) {
197
+ console.log(stableStringify({
198
+ error: 'inventory.json not found',
199
+ path: inventoryPath
200
+ }));
201
+ } else {
202
+ console.error(`Error: inventory.json not found at ${inventoryPath}`);
203
+ }
204
+ return 1;
205
+ }
206
+
207
+ // Load inventory
208
+ let inventory;
209
+ try {
210
+ const inventoryContent = await readFile(inventoryPath, 'utf-8');
211
+ inventory = JSON.parse(inventoryContent);
212
+ } catch (error) {
213
+ if (options.json) {
214
+ console.log(stableStringify({
215
+ error: 'Failed to parse inventory.json',
216
+ message: error.message
217
+ }));
218
+ } else {
219
+ console.error(`Error: Failed to parse inventory.json: ${error.message}`);
220
+ }
221
+ return 1;
222
+ }
223
+
224
+ // Get list of packages from inventory
225
+ const packageNames = Array.isArray(inventory.packages)
226
+ ? inventory.packages.map(p => p.name)
227
+ : [];
228
+
229
+ if (packageNames.length === 0) {
230
+ if (options.json) {
231
+ console.log(stableStringify({
232
+ error: 'No packages found in inventory',
233
+ total: 0,
234
+ passing: 0,
235
+ failing: 0,
236
+ failures: []
237
+ }));
238
+ } else {
239
+ console.error('Error: No packages found in inventory.json');
240
+ }
241
+ return 1;
242
+ }
243
+
244
+ // Sort package names for deterministic output
245
+ packageNames.sort((a, b) => a.localeCompare(b));
246
+
247
+ const results = [];
248
+
249
+ // Verify each package
250
+ for (const packageName of packageNames) {
251
+ const packageDir = join(artifactsDir, packageName);
252
+ const diataxisPath = join(packageDir, 'diataxis.json');
253
+
254
+ let entry;
255
+ let verificationResult;
256
+
257
+ // Check if package diataxis.json exists
258
+ if (!existsSync(diataxisPath)) {
259
+ verificationResult = {
260
+ packageName,
261
+ passing: false,
262
+ failures: ['diataxis.json not found']
263
+ };
264
+ } else {
265
+ // Try to load and parse diataxis.json
266
+ try {
267
+ const content = await readFile(diataxisPath, 'utf-8');
268
+ entry = JSON.parse(content);
269
+ verificationResult = verifyPackage(packageName, entry);
270
+ } catch (error) {
271
+ verificationResult = {
272
+ packageName,
273
+ passing: false,
274
+ failures: [`Failed to parse diataxis.json: ${error.message}`]
275
+ };
276
+ }
277
+ }
278
+
279
+ results.push(verificationResult);
280
+
281
+ // Fail fast if requested and we have a failure
282
+ if (options.failFast && !verificationResult.passing) {
283
+ break;
284
+ }
285
+ }
286
+
287
+ // Calculate summary
288
+ const totalPackages = options.failFast ? results.length : packageNames.length;
289
+ const passingPackages = results.filter(r => r.passing).length;
290
+ const failingPackages = results.filter(r => !r.passing).length;
291
+ const failures = results.filter(r => !r.passing);
292
+
293
+ // Determine exit code based on threshold
294
+ const shouldFail = failingPackages > options.threshold;
295
+
296
+ // Output results
297
+ if (options.json) {
298
+ console.log(stableStringify({
299
+ total: totalPackages,
300
+ passing: passingPackages,
301
+ failing: failingPackages,
302
+ threshold: options.threshold,
303
+ exitCode: shouldFail ? 1 : 0,
304
+ failures: failures.map(f => ({
305
+ package: f.packageName,
306
+ failures: f.failures
307
+ }))
308
+ }));
309
+ } else {
310
+ console.log('Diátaxis Coverage Verification');
311
+ console.log('==============================\n');
312
+ console.log(`Total packages: ${totalPackages}`);
313
+ console.log(`Passing: ${passingPackages}`);
314
+ console.log(`Failing: ${failingPackages}`);
315
+
316
+ if (failures.length > 0) {
317
+ console.log('\nFAILURES:');
318
+ console.log('--------');
319
+ for (const failure of failures) {
320
+ console.log(`❌ ${failure.packageName}`);
321
+ for (const msg of failure.failures) {
322
+ console.log(` - ${msg}`);
323
+ }
324
+ console.log('');
325
+ }
326
+ }
327
+
328
+ if (shouldFail) {
329
+ console.log(`Exit code: 1 (${failingPackages} failures > threshold ${options.threshold})`);
330
+ } else {
331
+ console.log(`Exit code: 0 (${failingPackages} failures <= threshold ${options.threshold})`);
332
+ }
333
+ }
334
+
335
+ return shouldFail ? 1 : 0;
336
+ }
337
+
338
+ // Run main if executed directly
339
+ if (import.meta.url === `file://${process.argv[1]}`) {
340
+ const args = process.argv.slice(2);
341
+ const options = parseArgs(args);
342
+
343
+ if (options.help) {
344
+ showHelp();
345
+ process.exit(0);
346
+ }
347
+
348
+ main(options)
349
+ .then(exitCode => process.exit(exitCode))
350
+ .catch(error => {
351
+ console.error('Fatal error:', error);
352
+ process.exit(1);
353
+ });
354
+ }
355
+
356
+ export { main, verifyPackage, isReferencPopulated, isExplanationPopulated };
@@ -0,0 +1,92 @@
1
+ # Capability Map: @unrdf/diataxis-kit
2
+
3
+ **Generated:** 2025-12-28
4
+ **Package:** @unrdf/diataxis-kit
5
+ **Version:** 1.0.0
6
+
7
+ ---
8
+
9
+ ## Description
10
+
11
+ Diátaxis documentation kit for monorepo package inventory and deterministic doc scaffold generation
12
+
13
+ ---
14
+
15
+ ## Capability Atoms
16
+
17
+ ### A52: Documentation Generation
18
+
19
+ **Runtime:** Node.js
20
+ **Invariants:** structured, evidence-based
21
+ **Evidence:** `packages/diataxis-kit/src/index.mjs`
22
+
23
+
24
+
25
+ ---
26
+
27
+ ## Package Metadata
28
+
29
+ ### Dependencies
30
+
31
+ No dependencies
32
+
33
+ ### Exports
34
+
35
+ - `.`: `./src/index.mjs`
36
+ - `./inventory`: `./src/inventory.mjs`
37
+ - `./evidence`: `./src/evidence.mjs`
38
+ - `./classify`: `./src/classify.mjs`
39
+ - `./scaffold`: `./src/scaffold.mjs`
40
+ - `./stable-json`: `./src/stable-json.mjs`
41
+ - `./hash`: `./src/hash.mjs`
42
+
43
+ ---
44
+
45
+ ## Integration Patterns
46
+
47
+ ### Primary Use Cases
48
+
49
+ 1. **Documentation Generation**
50
+ - Import: `import { /* exports */ } from '@unrdf/diataxis-kit'`
51
+ - Use for: Documentation Generation operations
52
+ - Runtime: Node.js
53
+
54
+
55
+ ### Composition Examples
56
+
57
+ ```javascript
58
+ import { createStore } from '@unrdf/oxigraph';
59
+ import { /* functions */ } from '@unrdf/diataxis-kit';
60
+
61
+ const store = createStore();
62
+ // Use diataxis-kit capabilities with store
63
+ ```
64
+
65
+
66
+ ---
67
+
68
+ ## Evidence Trail
69
+
70
+ - **A52**: `packages/diataxis-kit/src/index.mjs`
71
+
72
+ ---
73
+
74
+ ## Next Steps
75
+
76
+ 1. **Explore API Surface**
77
+ - Review exports in package.json
78
+ - Read source files in `src/` directory
79
+
80
+ 2. **Integration Testing**
81
+ - Create test cases using package capabilities
82
+ - Verify compatibility with dependent packages
83
+
84
+ 3. **Performance Profiling**
85
+ - Benchmark key operations
86
+ - Measure runtime characteristics
87
+
88
+ ---
89
+
90
+ **Status:** GENERATED
91
+ **Method:** Systematic extraction from capability-basis.md + package.json analysis
92
+ **Confidence:** 95% (evidence-based)
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@unrdf/diataxis-kit",
3
+ "version": "26.4.2",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Diátaxis documentation kit for monorepo package inventory and deterministic doc scaffold generation",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": "./src/index.mjs",
11
+ "./inventory": "./src/inventory.mjs",
12
+ "./evidence": "./src/evidence.mjs",
13
+ "./classify": "./src/classify.mjs",
14
+ "./scaffold": "./src/scaffold.mjs",
15
+ "./stable-json": "./src/stable-json.mjs",
16
+ "./hash": "./src/hash.mjs"
17
+ },
18
+ "bin": {
19
+ "diataxis-run": "./bin/run.mjs",
20
+ "diataxis-verify": "./bin/verify.mjs",
21
+ "diataxis-report": "./bin/report.mjs"
22
+ },
23
+ "keywords": [
24
+ "diataxis",
25
+ "documentation",
26
+ "monorepo",
27
+ "inventory"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "scripts": {
33
+ "run": "node bin/run.mjs",
34
+ "verify": "node bin/verify.mjs",
35
+ "report": "node bin/report.mjs",
36
+ "test": "node test/determinism.test.mjs && node test/inventory.test.mjs && node test/verify-gate.test.mjs",
37
+ "test:determinism": "node test/determinism.test.mjs",
38
+ "test:inventory": "node test/inventory.test.mjs",
39
+ "test:verify": "node test/verify-gate.test.mjs",
40
+ "clean": "rm -rf ARTIFACTS OUT"
41
+ }
42
+ }