@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,97 @@
1
+ {
2
+ "packageName": "@unrdf/test-pkg-3",
3
+ "version": "2.1.0",
4
+ "generatedAt": "2025-01-01T00:00:00.000Z",
5
+ "confidence": {
6
+ "tutorials": 0.6,
7
+ "howtos": 0.7,
8
+ "reference": 0.8,
9
+ "explanation": 0.65
10
+ },
11
+ "tutorials": [
12
+ {
13
+ "id": "tutorial-start",
14
+ "title": "Start Here",
15
+ "goal": "Get started",
16
+ "prerequisites": [],
17
+ "stepsOutline": [
18
+ "Install",
19
+ "Use"
20
+ ],
21
+ "confidenceScore": 0.6,
22
+ "source": [
23
+ "readme"
24
+ ]
25
+ }
26
+ ],
27
+ "howtos": [
28
+ {
29
+ "id": "howto-config",
30
+ "title": "Configure",
31
+ "task": "Configure",
32
+ "context": "Setup",
33
+ "steps": [
34
+ "Edit config"
35
+ ],
36
+ "confidenceScore": 0.7,
37
+ "source": [
38
+ "readme"
39
+ ]
40
+ },
41
+ {
42
+ "id": "howto-integrate",
43
+ "title": "Integrate",
44
+ "task": "Integrate",
45
+ "context": "Integration",
46
+ "steps": [
47
+ "Setup"
48
+ ],
49
+ "confidenceScore": 0.7,
50
+ "source": [
51
+ "keywords"
52
+ ]
53
+ }
54
+ ],
55
+ "reference": {
56
+ "id": "reference-api",
57
+ "title": "API Reference",
58
+ "items": [
59
+ {
60
+ "name": "export1",
61
+ "type": "export",
62
+ "description": "Export",
63
+ "example": "import"
64
+ }
65
+ ],
66
+ "confidenceScore": 0.8,
67
+ "source": [
68
+ "exports"
69
+ ]
70
+ },
71
+ "explanation": {
72
+ "id": "explanation-overview",
73
+ "title": "Understanding Package",
74
+ "concepts": [
75
+ "concept"
76
+ ],
77
+ "architecture": "Architecture description",
78
+ "tradeoffs": [
79
+ "Tradeoff"
80
+ ],
81
+ "confidenceScore": 0.65,
82
+ "source": [
83
+ "readme"
84
+ ]
85
+ },
86
+ "evidence": {
87
+ "readmeHeadings": [
88
+ "Getting Started",
89
+ "Usage"
90
+ ],
91
+ "docsFiles": [],
92
+ "examplesFiles": [
93
+ "example.mjs"
94
+ ],
95
+ "fingerprint": "ghi789"
96
+ }
97
+ }
@@ -0,0 +1 @@
1
+ MIT License
@@ -0,0 +1,15 @@
1
+ # Test Package
2
+
3
+ This is a test package.
4
+
5
+ ## Installation
6
+
7
+ Install with npm.
8
+
9
+ ## Usage
10
+
11
+ Use the package like this.
12
+
13
+ ## API Reference
14
+
15
+ See the API docs.
@@ -0,0 +1,3 @@
1
+ # User Guide
2
+
3
+ This guide explains how to use the test package.
@@ -0,0 +1,3 @@
1
+ import { testFunction } from '../src/index.mjs';
2
+
3
+ console.log(testFunction());
@@ -0,0 +1,3 @@
1
+ export function testFunction() {
2
+ return 'test';
3
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * @file Inventory discovery tests
3
+ * @description Validates package discovery and validation functions
4
+ */
5
+
6
+ import assert from 'node:assert/strict';
7
+ import { resolve } from 'node:path';
8
+ import { discoverPackages, validatePackageEntry } from '../src/inventory.mjs';
9
+ import { existsSync } from 'node:fs';
10
+
11
+ let passCount = 0;
12
+ let failCount = 0;
13
+
14
+ /**
15
+ * Test runner
16
+ * @param {string} name - Test name
17
+ * @param {Function} fn - Async test function
18
+ */
19
+ async function test(name, fn) {
20
+ try {
21
+ await fn();
22
+ console.log(`✅ ${name}`);
23
+ passCount++;
24
+ } catch (err) {
25
+ console.log(`❌ ${name}: ${err.message}`);
26
+ if (err.stack && process.env.VERBOSE) {
27
+ console.log(err.stack);
28
+ }
29
+ failCount++;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Test: discoverPackages finds at least 42 packages
35
+ */
36
+ async function testDiscoverPackagesCount() {
37
+ const workspaceRoot = resolve(process.cwd(), '../..');
38
+ const packages = await discoverPackages(workspaceRoot);
39
+
40
+ if (packages.length < 42) {
41
+ throw new Error(`Expected >= 42 packages, found ${packages.length}`);
42
+ }
43
+
44
+ console.log(` Found ${packages.length} packages`);
45
+ }
46
+
47
+ /**
48
+ * Test: All packages have required fields
49
+ */
50
+ async function testRequiredFields() {
51
+ const workspaceRoot = resolve(process.cwd(), '../..');
52
+ const packages = await discoverPackages(workspaceRoot);
53
+
54
+ const requiredFields = ['name', 'dir', 'version', 'description'];
55
+ const missingFields = [];
56
+
57
+ for (const pkg of packages) {
58
+ for (const field of requiredFields) {
59
+ if (typeof pkg[field] !== 'string') {
60
+ missingFields.push(`${pkg.name || 'unknown'}: missing or invalid field '${field}'`);
61
+ }
62
+ }
63
+ }
64
+
65
+ if (missingFields.length > 0) {
66
+ throw new Error(`Missing fields:\n${missingFields.join('\n')}`);
67
+ }
68
+
69
+ console.log(` Verified required fields for ${packages.length} packages`);
70
+ }
71
+
72
+ /**
73
+ * Test: Package names are unique
74
+ */
75
+ async function testUniqueNames() {
76
+ const workspaceRoot = resolve(process.cwd(), '../..');
77
+ const packages = await discoverPackages(workspaceRoot);
78
+
79
+ const nameSet = new Set();
80
+ const duplicates = [];
81
+
82
+ for (const pkg of packages) {
83
+ if (nameSet.has(pkg.name)) {
84
+ duplicates.push(pkg.name);
85
+ }
86
+ nameSet.add(pkg.name);
87
+ }
88
+
89
+ if (duplicates.length > 0) {
90
+ throw new Error(`Duplicate package names: ${duplicates.join(', ')}`);
91
+ }
92
+
93
+ console.log(` Verified ${packages.length} unique package names`);
94
+ }
95
+
96
+ /**
97
+ * Test: All package directories exist and are readable
98
+ */
99
+ async function testDirectoriesExist() {
100
+ const workspaceRoot = resolve(process.cwd(), '../..');
101
+ const packages = await discoverPackages(workspaceRoot);
102
+
103
+ const missingDirs = [];
104
+
105
+ for (const pkg of packages) {
106
+ if (!existsSync(pkg.dir)) {
107
+ missingDirs.push(`${pkg.name}: directory not found at ${pkg.dir}`);
108
+ }
109
+ }
110
+
111
+ if (missingDirs.length > 0) {
112
+ throw new Error(`Missing directories:\n${missingDirs.join('\n')}`);
113
+ }
114
+
115
+ console.log(` Verified all ${packages.length} directories exist`);
116
+ }
117
+
118
+ /**
119
+ * Test: validatePackageEntry works correctly
120
+ */
121
+ async function testValidatePackageEntry() {
122
+ const workspaceRoot = resolve(process.cwd(), '../..');
123
+ const packages = await discoverPackages(workspaceRoot);
124
+
125
+ const validationErrors = [];
126
+
127
+ for (const pkg of packages) {
128
+ const result = validatePackageEntry(pkg);
129
+ if (!result.valid) {
130
+ validationErrors.push(`${pkg.name}: ${result.errors.join(', ')}`);
131
+ }
132
+ }
133
+
134
+ if (validationErrors.length > 0) {
135
+ throw new Error(`Validation errors:\n${validationErrors.join('\n')}`);
136
+ }
137
+
138
+ console.log(` Validated ${packages.length} package entries`);
139
+ }
140
+
141
+ /**
142
+ * Test: Invalid entries are rejected
143
+ */
144
+ async function testValidateRejectsInvalid() {
145
+ const invalidEntries = [
146
+ null,
147
+ undefined,
148
+ {},
149
+ { name: 'test' }, // Missing required fields
150
+ { name: 'test', dir: '/path', version: 123 }, // Wrong type
151
+ { name: 'test', dir: '/path', version: '1.0.0', description: 'test', exports: null }, // Invalid exports
152
+ ];
153
+
154
+ for (const entry of invalidEntries) {
155
+ const result = validatePackageEntry(entry);
156
+ if (result.valid) {
157
+ throw new Error(`Expected validation to fail for: ${JSON.stringify(entry)}`);
158
+ }
159
+ }
160
+
161
+ console.log(` Verified validation rejects ${invalidEntries.length} invalid entries`);
162
+ }
163
+
164
+ /**
165
+ * Test: Packages are sorted by name
166
+ */
167
+ async function testPackagesSorted() {
168
+ const workspaceRoot = resolve(process.cwd(), '../..');
169
+ const packages = await discoverPackages(workspaceRoot);
170
+
171
+ const names = packages.map(p => p.name);
172
+ const sortedNames = [...names].sort((a, b) => a.localeCompare(b));
173
+
174
+ for (let i = 0; i < names.length; i++) {
175
+ if (names[i] !== sortedNames[i]) {
176
+ throw new Error(`Packages not sorted: ${names[i]} at index ${i}, expected ${sortedNames[i]}`);
177
+ }
178
+ }
179
+
180
+ console.log(` Verified ${packages.length} packages sorted alphabetically`);
181
+ }
182
+
183
+ // Run all tests
184
+ async function runTests() {
185
+ console.log('Running inventory tests...\n');
186
+
187
+ await test('inventory: discovers >= 42 packages', testDiscoverPackagesCount);
188
+ await test('inventory: all packages have required fields', testRequiredFields);
189
+ await test('inventory: package names are unique', testUniqueNames);
190
+ await test('inventory: all directories exist', testDirectoriesExist);
191
+ await test('inventory: validatePackageEntry accepts valid entries', testValidatePackageEntry);
192
+ await test('inventory: validatePackageEntry rejects invalid entries', testValidateRejectsInvalid);
193
+ await test('inventory: packages sorted by name', testPackagesSorted);
194
+
195
+ console.log(`\n${passCount}/${passCount + failCount} tests passed`);
196
+ process.exit(failCount > 0 ? 1 : 0);
197
+ }
198
+
199
+ runTests();
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @fileoverview Tests for reference-extractor.mjs
3
+ */
4
+
5
+ import { describe, it } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { extractReference } from '../src/reference-extractor.mjs';
8
+
9
+ describe('extractReference', () => {
10
+ it('should extract from exports field (string)', () => {
11
+ const pkg = {
12
+ name: 'test-pkg',
13
+ exports: './index.js'
14
+ };
15
+ const evidence = { readme: '' };
16
+
17
+ const result = extractReference(pkg, evidence);
18
+
19
+ assert.equal(result.id, 'reference');
20
+ assert.equal(result.title, 'test-pkg API Reference');
21
+ assert.equal(result.items.length, 1);
22
+ assert.equal(result.items[0].name, '.');
23
+ assert.equal(result.items[0].type, 'export');
24
+ assert.equal(result.items[0].description, './index.js');
25
+ assert.equal(result.confidenceScore, 1.0);
26
+ assert.deepEqual(result.source, ['exports']);
27
+ });
28
+
29
+ it('should extract from exports field (object)', () => {
30
+ const pkg = {
31
+ name: 'test-pkg',
32
+ exports: {
33
+ '.': './index.js',
34
+ './utils': './utils.js'
35
+ }
36
+ };
37
+ const evidence = { readme: '' };
38
+
39
+ const result = extractReference(pkg, evidence);
40
+
41
+ assert.equal(result.items.length, 2);
42
+ assert.equal(result.items[0].name, '.');
43
+ assert.equal(result.items[1].name, './utils');
44
+ assert.equal(result.confidenceScore, 1.0);
45
+ });
46
+
47
+ it('should extract from bin field (string)', () => {
48
+ const pkg = {
49
+ name: 'cli-tool',
50
+ bin: './cli.js'
51
+ };
52
+ const evidence = { readme: '' };
53
+
54
+ const result = extractReference(pkg, evidence);
55
+
56
+ assert.equal(result.items.length, 1);
57
+ assert.equal(result.items[0].name, 'cli-tool');
58
+ assert.equal(result.items[0].type, 'bin');
59
+ assert.ok(result.items[0].description.includes('CLI entry point'));
60
+ assert.equal(result.confidenceScore, 0.8);
61
+ assert.deepEqual(result.source, ['bin']);
62
+ });
63
+
64
+ it('should extract from bin field (object)', () => {
65
+ const pkg = {
66
+ name: 'multi-cli',
67
+ bin: {
68
+ 'cmd1': './cmd1.js',
69
+ 'cmd2': './cmd2.js'
70
+ }
71
+ };
72
+ const evidence = { readme: '' };
73
+
74
+ const result = extractReference(pkg, evidence);
75
+
76
+ assert.equal(result.items.length, 2);
77
+ assert.equal(result.items[0].name, 'cmd1');
78
+ assert.equal(result.items[1].name, 'cmd2');
79
+ assert.equal(result.confidenceScore, 0.8);
80
+ });
81
+
82
+ it('should extract from README API section', () => {
83
+ const pkg = { name: 'readme-pkg' };
84
+ const evidence = {
85
+ readme: `
86
+ # Package
87
+
88
+ ## API
89
+
90
+ - foo: Does something cool
91
+ - bar: Does another thing
92
+ `
93
+ };
94
+
95
+ const result = extractReference(pkg, evidence);
96
+
97
+ assert.equal(result.items.length, 2);
98
+ assert.equal(result.items[0].name, 'bar');
99
+ assert.equal(result.items[0].type, 'api');
100
+ assert.equal(result.items[0].description, 'Does another thing');
101
+ assert.equal(result.items[1].name, 'foo');
102
+ assert.equal(result.confidenceScore, 0.6);
103
+ assert.deepEqual(result.source, ['readme']);
104
+ });
105
+
106
+ it('should combine multiple sources', () => {
107
+ const pkg = {
108
+ name: 'combo-pkg',
109
+ exports: { '.': './index.js' },
110
+ bin: './cli.js'
111
+ };
112
+ const evidence = {
113
+ readme: '## API\n\n- method: A method'
114
+ };
115
+
116
+ const result = extractReference(pkg, evidence);
117
+
118
+ assert.ok(result.items.length >= 3);
119
+ assert.equal(result.confidenceScore, 1.0); // exports = highest
120
+ assert.ok(result.source.includes('exports'));
121
+ assert.ok(result.source.includes('bin'));
122
+ assert.ok(result.source.includes('readme'));
123
+ });
124
+
125
+ it('should return unknown fallback when no data found', () => {
126
+ const pkg = { name: 'empty-pkg' };
127
+ const evidence = { readme: '' };
128
+
129
+ const result = extractReference(pkg, evidence);
130
+
131
+ assert.equal(result.items.length, 1);
132
+ assert.equal(result.items[0].name, 'unknown');
133
+ assert.equal(result.items[0].type, 'unknown');
134
+ assert.equal(result.items[0].description, 'API reference not found in documentation');
135
+ assert.equal(result.confidenceScore, 0.1);
136
+ assert.deepEqual(result.source, ['inferred']);
137
+ });
138
+
139
+ it('should sort items by name deterministically', () => {
140
+ const pkg = {
141
+ name: 'sort-test',
142
+ exports: {
143
+ './zebra': './z.js',
144
+ './alpha': './a.js',
145
+ './beta': './b.js'
146
+ }
147
+ };
148
+ const evidence = { readme: '' };
149
+
150
+ const result = extractReference(pkg, evidence);
151
+
152
+ assert.equal(result.items[0].name, './alpha');
153
+ assert.equal(result.items[1].name, './beta');
154
+ assert.equal(result.items[2].name, './zebra');
155
+ });
156
+
157
+ it('should handle conditional exports', () => {
158
+ const pkg = {
159
+ name: 'conditional-pkg',
160
+ exports: {
161
+ '.': {
162
+ import: './esm/index.js',
163
+ require: './cjs/index.js'
164
+ }
165
+ }
166
+ };
167
+ const evidence = { readme: '' };
168
+
169
+ const result = extractReference(pkg, evidence);
170
+
171
+ assert.equal(result.items.length, 1);
172
+ assert.equal(result.items[0].description, './esm/index.js'); // import first
173
+ });
174
+
175
+ it('should handle README with backticks in method names', () => {
176
+ const pkg = { name: 'backtick-pkg' };
177
+ const evidence = {
178
+ readme: '## Methods\n\n- `createStore()`: Creates a new store\n- `query()`: Runs a query'
179
+ };
180
+
181
+ const result = extractReference(pkg, evidence);
182
+
183
+ assert.equal(result.items.length, 2);
184
+ assert.equal(result.items[0].name, 'createStore()');
185
+ assert.equal(result.items[1].name, 'query()');
186
+ });
187
+ });