@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/report.mjs ADDED
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file report.mjs
4
+ * @description CLI tool for generating Diátaxis coverage and confidence reports
5
+ */
6
+
7
+ import { readdir, readFile, stat } from 'node:fs/promises';
8
+ import { join, resolve } from 'node:path';
9
+ import { existsSync } from 'node:fs';
10
+ import { stableStringify } from '../src/stable-json.mjs';
11
+
12
+ /**
13
+ * @typedef {Object} ReportOptions
14
+ * @property {boolean} json - Output as JSON
15
+ * @property {boolean} csv - Output as CSV
16
+ * @property {number} top - Number of top packages to show
17
+ * @property {string|null} filter - Filter packages by keyword
18
+ * @property {string} sort - Sort field (confidence, tutorials, howtos, reference, explanation)
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} PackageStats
23
+ * @property {string} packageName
24
+ * @property {number} tutorialsCount
25
+ * @property {number} howtosCount
26
+ * @property {boolean} hasReference
27
+ * @property {boolean} hasExplanation
28
+ * @property {number} avgConfidence
29
+ * @property {Object} confidence
30
+ * @property {string[]} missingEvidence
31
+ */
32
+
33
+ /**
34
+ * Parse command-line arguments manually (no external dependencies)
35
+ * @param {string[]} args - Process arguments (from process.argv)
36
+ * @returns {ReportOptions} Parsed options
37
+ */
38
+ function parseArgs(args) {
39
+ const options = {
40
+ json: false,
41
+ csv: false,
42
+ top: 5,
43
+ filter: null,
44
+ sort: 'confidence'
45
+ };
46
+
47
+ for (let i = 0; i < args.length; i++) {
48
+ const arg = args[i];
49
+
50
+ if (arg === '--json') {
51
+ options.json = true;
52
+ } else if (arg === '--csv') {
53
+ options.csv = true;
54
+ } else if (arg === '--top' && i + 1 < args.length) {
55
+ options.top = parseInt(args[i + 1], 10);
56
+ i++;
57
+ } else if (arg === '--filter' && i + 1 < args.length) {
58
+ options.filter = args[i + 1];
59
+ i++;
60
+ } else if (arg === '--sort' && i + 1 < args.length) {
61
+ options.sort = args[i + 1];
62
+ i++;
63
+ } else if (arg === '--help' || arg === '-h') {
64
+ console.log(`
65
+ Diátaxis Coverage Report Generator
66
+
67
+ Usage: diataxis-report [options]
68
+
69
+ Options:
70
+ --json Output as JSON structure
71
+ --csv Output as CSV (for spreadsheet import)
72
+ --top N Show top N packages instead of 5 (default: 5)
73
+ --filter <keyword> Only report on packages matching keyword
74
+ --sort <field> Sort by: confidence, tutorials, howtos, reference, explanation
75
+ --help, -h Show this help message
76
+
77
+ Examples:
78
+ diataxis-report
79
+ diataxis-report --json
80
+ diataxis-report --top 10 --sort tutorials
81
+ diataxis-report --filter "@unrdf/oxigraph" --json
82
+ `);
83
+ process.exit(0);
84
+ }
85
+ }
86
+
87
+ return options;
88
+ }
89
+
90
+ /**
91
+ * Load all inventory JSON files from ARTIFACTS/diataxis/
92
+ * @param {string} artifactsPath - Path to artifacts directory
93
+ * @returns {Promise<Object[]>} Array of parsed inventory entries
94
+ */
95
+ async function loadInventories(artifactsPath) {
96
+ const diataxisDir = join(artifactsPath, 'diataxis');
97
+
98
+ if (!existsSync(diataxisDir)) {
99
+ return [];
100
+ }
101
+
102
+ try {
103
+ // Recursively find all diataxis.json files
104
+ const inventories = [];
105
+
106
+ /**
107
+ * Recursively search for diataxis.json files
108
+ */
109
+ async function findDiataxisFiles(dir) {
110
+ try {
111
+ const files = await readdir(dir);
112
+ for (const file of files) {
113
+ const filePath = join(dir, file);
114
+ if (file === 'diataxis.json') {
115
+ try {
116
+ const content = await readFile(filePath, 'utf-8');
117
+ const data = JSON.parse(content);
118
+ inventories.push(data);
119
+ } catch (error) {
120
+ console.warn(`Warning: Failed to parse ${filePath}: ${error.message}`);
121
+ }
122
+ } else {
123
+ try {
124
+ const statInfo = await stat(filePath);
125
+ if (statInfo.isDirectory()) {
126
+ await findDiataxisFiles(filePath);
127
+ }
128
+ } catch (error) {
129
+ // Skip
130
+ }
131
+ }
132
+ }
133
+ } catch (error) {
134
+ // Silently skip directories
135
+ }
136
+ }
137
+
138
+ await findDiataxisFiles(diataxisDir);
139
+ return inventories;
140
+ } catch (error) {
141
+ console.warn(`Warning: Failed to read diataxis directory: ${error.message}`);
142
+ return [];
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Calculate statistics for a single package
148
+ * @param {Object} inventory - DiataxisEntry inventory
149
+ * @returns {PackageStats} Package statistics
150
+ */
151
+ function calculatePackageStats(inventory) {
152
+ const tutorialsCount = inventory.tutorials?.length || 0;
153
+ const howtosCount = inventory.howtos?.length || 0;
154
+ const hasReference = (inventory.reference?.items?.length || 0) > 0;
155
+ const hasExplanation = (
156
+ (inventory.explanation?.concepts?.length || 0) > 0 ||
157
+ (inventory.explanation?.architecture || '').length > 0 ||
158
+ (inventory.explanation?.tradeoffs?.length || 0) > 0
159
+ );
160
+
161
+ const confidence = inventory.confidence || {
162
+ tutorials: 0,
163
+ howtos: 0,
164
+ reference: 0,
165
+ explanation: 0
166
+ };
167
+
168
+ const avgConfidence = (
169
+ confidence.tutorials +
170
+ confidence.howtos +
171
+ confidence.reference +
172
+ confidence.explanation
173
+ ) / 4;
174
+
175
+ // Determine missing evidence
176
+ const missingEvidence = [];
177
+ const evidence = inventory.evidence || {};
178
+
179
+ if (!evidence.examplesFiles || evidence.examplesFiles.length === 0) {
180
+ missingEvidence.push('no examples/');
181
+ }
182
+ if (!evidence.docsFiles || evidence.docsFiles.length === 0) {
183
+ missingEvidence.push('no docs/');
184
+ }
185
+ if (!evidence.readmeHeadings || evidence.readmeHeadings.length === 0) {
186
+ missingEvidence.push('empty README');
187
+ }
188
+
189
+ // Check for bin entries from reference
190
+ const hasBinEntries = inventory.reference?.items?.some(item => item.type === 'bin');
191
+ if (!hasBinEntries) {
192
+ missingEvidence.push('no bin entries');
193
+ }
194
+
195
+ return {
196
+ packageName: inventory.packageName,
197
+ tutorialsCount,
198
+ howtosCount,
199
+ hasReference,
200
+ hasExplanation,
201
+ avgConfidence,
202
+ confidence,
203
+ missingEvidence
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Generate summary statistics from all packages
209
+ * @param {PackageStats[]} stats - Array of package statistics
210
+ * @returns {Object} Summary statistics
211
+ */
212
+ function generateSummary(stats) {
213
+ const total = stats.length;
214
+ const withTutorials = stats.filter(s => s.tutorialsCount > 0).length;
215
+ const with2PlusHowtos = stats.filter(s => s.howtosCount >= 2).length;
216
+ const withReference = stats.filter(s => s.hasReference).length;
217
+ const withExplanation = stats.filter(s => s.hasExplanation).length;
218
+
219
+ return {
220
+ total,
221
+ withTutorials,
222
+ with2PlusHowtos,
223
+ withReference,
224
+ withExplanation,
225
+ withTutorialsPercent: total > 0 ? (withTutorials / total * 100) : 0,
226
+ with2PlusHowtosPercent: total > 0 ? (with2PlusHowtos / total * 100) : 0,
227
+ withReferencePercent: total > 0 ? (withReference / total * 100) : 0,
228
+ withExplanationPercent: total > 0 ? (withExplanation / total * 100) : 0
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Calculate confidence distribution statistics
234
+ * @param {PackageStats[]} stats - Array of package statistics
235
+ * @returns {Object} Confidence statistics
236
+ */
237
+ function calculateConfidenceStats(stats) {
238
+ const fields = ['tutorials', 'howtos', 'reference', 'explanation'];
239
+ const result = {};
240
+
241
+ for (const field of fields) {
242
+ const values = stats.map(s => s.confidence[field]).filter(v => v !== undefined);
243
+ if (values.length === 0) {
244
+ result[field] = { avg: 0, min: 0, max: 0 };
245
+ } else {
246
+ const avg = values.reduce((a, b) => a + b, 0) / values.length;
247
+ const min = Math.min(...values);
248
+ const max = Math.max(...values);
249
+ result[field] = { avg, min, max };
250
+ }
251
+ }
252
+
253
+ return result;
254
+ }
255
+
256
+ /**
257
+ * Find packages with lowest average confidence
258
+ * @param {PackageStats[]} stats - Array of package statistics
259
+ * @param {number} topN - Number of packages to return
260
+ * @returns {PackageStats[]} Top N lowest confidence packages
261
+ */
262
+ function findLowestConfidence(stats, topN) {
263
+ return [...stats]
264
+ .sort((a, b) => a.avgConfidence - b.avgConfidence)
265
+ .slice(0, topN);
266
+ }
267
+
268
+ /**
269
+ * Aggregate missing evidence sources
270
+ * @param {PackageStats[]} stats - Array of package statistics
271
+ * @returns {Object} Map of evidence type to count
272
+ */
273
+ function aggregateMissingEvidence(stats) {
274
+ const evidenceCounts = {};
275
+
276
+ for (const pkg of stats) {
277
+ for (const evidence of pkg.missingEvidence) {
278
+ evidenceCounts[evidence] = (evidenceCounts[evidence] || 0) + 1;
279
+ }
280
+ }
281
+
282
+ // Sort by count descending
283
+ const sorted = Object.entries(evidenceCounts)
284
+ .sort(([, a], [, b]) => b - a)
285
+ .reduce((acc, [key, val]) => {
286
+ acc[key] = val;
287
+ return acc;
288
+ }, {});
289
+
290
+ return sorted;
291
+ }
292
+
293
+ /**
294
+ * Generate text report
295
+ * @param {PackageStats[]} stats - Package statistics
296
+ * @param {ReportOptions} options - Report options
297
+ */
298
+ function generateTextReport(stats, options) {
299
+ const summary = generateSummary(stats);
300
+ const confidenceStats = calculateConfidenceStats(stats);
301
+ const lowestConfidence = findLowestConfidence(stats, options.top);
302
+ const missingEvidence = aggregateMissingEvidence(stats);
303
+
304
+ console.log('Diátaxis Coverage Report');
305
+ console.log('========================\n');
306
+
307
+ // Summary section
308
+ console.log('SUMMARY');
309
+ console.log('-------');
310
+ console.log(`Total packages: ${summary.total}`);
311
+ console.log(`With tutorials: ${summary.withTutorials} (${summary.withTutorialsPercent.toFixed(0)}%)`);
312
+ console.log(`With 2+ how-tos: ${summary.with2PlusHowtos} (${summary.with2PlusHowtosPercent.toFixed(0)}%)`);
313
+ console.log(`With reference: ${summary.withReference} (${summary.withReferencePercent.toFixed(0)}%)`);
314
+ console.log(`With explanation: ${summary.withExplanation} (${summary.withExplanationPercent.toFixed(0)}%)\n`);
315
+
316
+ // Confidence section
317
+ console.log('CONFIDENCE');
318
+ console.log('----------');
319
+ for (const [field, values] of Object.entries(confidenceStats)) {
320
+ const fieldName = field.charAt(0).toUpperCase() + field.slice(1);
321
+ console.log(`${fieldName.padEnd(12)}: avg=${values.avg.toFixed(2)}, min=${values.min.toFixed(2)}, max=${values.max.toFixed(2)}`);
322
+ }
323
+ console.log();
324
+
325
+ // Lowest confidence section
326
+ console.log(`LOWEST CONFIDENCE (${options.top} packages)`);
327
+ console.log('----------------------------');
328
+ if (lowestConfidence.length === 0) {
329
+ console.log('No packages found\n');
330
+ } else {
331
+ lowestConfidence.forEach((pkg, i) => {
332
+ const reasons = pkg.missingEvidence.length > 0 ? ` - ${pkg.missingEvidence.join(', ')}` : '';
333
+ console.log(`${(i + 1).toString().padStart(2)}. ${pkg.packageName.padEnd(30)} (${pkg.avgConfidence.toFixed(2)})${reasons}`);
334
+ });
335
+ console.log();
336
+ }
337
+
338
+ // Missing evidence section
339
+ console.log('MISSING EVIDENCE');
340
+ console.log('----------------');
341
+ if (Object.keys(missingEvidence).length === 0) {
342
+ console.log('All evidence sources present\n');
343
+ } else {
344
+ for (const [evidence, count] of Object.entries(missingEvidence)) {
345
+ const evidenceLabel = evidence.padEnd(25);
346
+ console.log(`${evidenceLabel}: ${count} packages`);
347
+ }
348
+ console.log();
349
+ }
350
+
351
+ // Quick fixes section
352
+ console.log('QUICK FIXES');
353
+ console.log('-----------');
354
+ if (Object.keys(missingEvidence).length === 0) {
355
+ console.log('No immediate improvements needed\n');
356
+ } else {
357
+ console.log('To improve coverage, consider:');
358
+ for (const [evidence, count] of Object.entries(missingEvidence)) {
359
+ const action = evidence.replace('no ', 'Add ').replace('empty ', 'Create ');
360
+ console.log(`- ${action} to ${count} packages`);
361
+ }
362
+ console.log();
363
+ }
364
+
365
+ console.log(`EXIT: 0 (all packages documented)`);
366
+ }
367
+
368
+ /**
369
+ * Generate JSON report
370
+ * @param {PackageStats[]} stats - Package statistics
371
+ * @param {ReportOptions} options - Report options
372
+ */
373
+ function generateJsonReport(stats, options) {
374
+ const summary = generateSummary(stats);
375
+ const confidenceStats = calculateConfidenceStats(stats);
376
+ const lowestConfidence = findLowestConfidence(stats, options.top);
377
+ const missingEvidence = aggregateMissingEvidence(stats);
378
+
379
+ const report = {
380
+ summary: {
381
+ total: summary.total,
382
+ withTutorials: summary.withTutorials,
383
+ withTutorialsPercent: parseFloat(summary.withTutorialsPercent.toFixed(2)),
384
+ with2PlusHowtos: summary.with2PlusHowtos,
385
+ with2PlusHowtosPercent: parseFloat(summary.with2PlusHowtosPercent.toFixed(2)),
386
+ withReference: summary.withReference,
387
+ withReferencePercent: parseFloat(summary.withReferencePercent.toFixed(2)),
388
+ withExplanation: summary.withExplanation,
389
+ withExplanationPercent: parseFloat(summary.withExplanationPercent.toFixed(2))
390
+ },
391
+ confidence: confidenceStats,
392
+ lowestConfidence: lowestConfidence.map(pkg => ({
393
+ packageName: pkg.packageName,
394
+ avgConfidence: parseFloat(pkg.avgConfidence.toFixed(2)),
395
+ missingEvidence: pkg.missingEvidence,
396
+ confidence: {
397
+ tutorials: parseFloat(pkg.confidence.tutorials.toFixed(2)),
398
+ howtos: parseFloat(pkg.confidence.howtos.toFixed(2)),
399
+ reference: parseFloat(pkg.confidence.reference.toFixed(2)),
400
+ explanation: parseFloat(pkg.confidence.explanation.toFixed(2))
401
+ }
402
+ })),
403
+ missingEvidence,
404
+ generatedAt: new Date().toISOString()
405
+ };
406
+
407
+ console.log(stableStringify(report, { indent: 2 }));
408
+ }
409
+
410
+ /**
411
+ * Generate CSV report
412
+ * @param {PackageStats[]} stats - Package statistics
413
+ */
414
+ function generateCsvReport(stats) {
415
+ // Header
416
+ console.log('Package,Tutorials,HowTos,HasReference,HasExplanation,AvgConfidence,TutorialsConf,HowtosConf,ReferenceConf,ExplanationConf,MissingEvidence');
417
+
418
+ // Rows
419
+ for (const pkg of stats) {
420
+ const row = [
421
+ pkg.packageName,
422
+ pkg.tutorialsCount,
423
+ pkg.howtosCount,
424
+ pkg.hasReference ? 'yes' : 'no',
425
+ pkg.hasExplanation ? 'yes' : 'no',
426
+ pkg.avgConfidence.toFixed(2),
427
+ pkg.confidence.tutorials.toFixed(2),
428
+ pkg.confidence.howtos.toFixed(2),
429
+ pkg.confidence.reference.toFixed(2),
430
+ pkg.confidence.explanation.toFixed(2),
431
+ `"${pkg.missingEvidence.join('; ')}"`
432
+ ];
433
+ console.log(row.join(','));
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Filter packages by keyword
439
+ * @param {PackageStats[]} stats - Package statistics
440
+ * @param {string} keyword - Filter keyword
441
+ * @returns {PackageStats[]} Filtered statistics
442
+ */
443
+ function filterPackages(stats, keyword) {
444
+ if (!keyword) return stats;
445
+ return stats.filter(pkg => pkg.packageName.includes(keyword));
446
+ }
447
+
448
+ /**
449
+ * Sort packages by field
450
+ * @param {PackageStats[]} stats - Package statistics
451
+ * @param {string} field - Sort field
452
+ * @returns {PackageStats[]} Sorted statistics
453
+ */
454
+ function sortPackages(stats, field) {
455
+ const sorted = [...stats];
456
+
457
+ switch (field) {
458
+ case 'confidence':
459
+ sorted.sort((a, b) => b.avgConfidence - a.avgConfidence);
460
+ break;
461
+ case 'tutorials':
462
+ sorted.sort((a, b) => b.tutorialsCount - a.tutorialsCount);
463
+ break;
464
+ case 'howtos':
465
+ sorted.sort((a, b) => b.howtosCount - a.howtosCount);
466
+ break;
467
+ case 'reference':
468
+ sorted.sort((a, b) => (b.hasReference ? 1 : 0) - (a.hasReference ? 1 : 0));
469
+ break;
470
+ case 'explanation':
471
+ sorted.sort((a, b) => (b.hasExplanation ? 1 : 0) - (a.hasExplanation ? 1 : 0));
472
+ break;
473
+ default:
474
+ // Keep original order (sorted by package name from loading)
475
+ break;
476
+ }
477
+
478
+ return sorted;
479
+ }
480
+
481
+ /**
482
+ * Main entry point
483
+ */
484
+ async function main() {
485
+ const args = process.argv.slice(2);
486
+ const options = parseArgs(args);
487
+
488
+ // Determine artifacts path (default: ARTIFACTS/ in workspace root)
489
+ const workspaceRoot = resolve(process.cwd());
490
+ const artifactsPath = join(workspaceRoot, 'ARTIFACTS');
491
+
492
+ // Load inventories
493
+ const inventories = await loadInventories(artifactsPath);
494
+
495
+ if (inventories.length === 0) {
496
+ console.log('No inventory generated. Run diataxis-run first.');
497
+ process.exit(0);
498
+ }
499
+
500
+ // Calculate statistics
501
+ let stats = inventories.map(calculatePackageStats);
502
+
503
+ // Apply filter
504
+ stats = filterPackages(stats, options.filter);
505
+
506
+ if (stats.length === 0) {
507
+ console.log('No packages found matching filter.');
508
+ process.exit(0);
509
+ }
510
+
511
+ // Apply sort
512
+ stats = sortPackages(stats, options.sort);
513
+
514
+ // Generate report
515
+ if (options.json) {
516
+ generateJsonReport(stats, options);
517
+ } else if (options.csv) {
518
+ generateCsvReport(stats);
519
+ } else {
520
+ generateTextReport(stats, options);
521
+ }
522
+
523
+ process.exit(0);
524
+ }
525
+
526
+ main().catch(error => {
527
+ console.error(`Error: ${error.message}`);
528
+ process.exit(1);
529
+ });
package/bin/run.mjs ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @file Diátaxis Kit orchestrator - full pipeline execution
4
+ * @description Discovers packages, gathers evidence, classifies, and scaffolds documentation
5
+ */
6
+
7
+ import { mkdir, writeFile, readFile, rm } from 'node:fs/promises';
8
+ import { join, resolve } from 'node:path';
9
+ import { existsSync } from 'node:fs';
10
+ import { discoverPackages } from '../src/inventory.mjs';
11
+ import { collectEvidence } from '../src/evidence.mjs';
12
+ import { classifyPackage } from '../src/classify.mjs';
13
+ import { generateScaffold } from '../src/scaffold.mjs';
14
+ import { stableStringify } from '../src/stable-json.mjs';
15
+
16
+ /**
17
+ * Main orchestration function
18
+ * @returns {Promise<void>}
19
+ */
20
+ async function main() {
21
+ const isDeterministic = process.env.DETERMINISTIC === '1';
22
+ const workspaceRoot = resolve(process.cwd(), '../..');
23
+ const artifactsDir = resolve(process.cwd(), 'ARTIFACTS', 'diataxis');
24
+ const outDir = resolve(process.cwd(), 'OUT');
25
+
26
+ console.log('🚀 Diátaxis Kit - Full Pipeline');
27
+ console.log(` Workspace: ${workspaceRoot}`);
28
+ console.log(` Deterministic: ${isDeterministic ? 'YES' : 'NO'}`);
29
+ console.log('');
30
+
31
+ // Step 1: Discover packages
32
+ console.log('📦 Discovering packages...');
33
+ const packages = await discoverPackages(workspaceRoot);
34
+ console.log(` Found ${packages.length} packages`);
35
+
36
+ // Step 2: Create ARTIFACTS directory structure
37
+ await mkdir(artifactsDir, { recursive: true });
38
+ await mkdir(outDir, { recursive: true });
39
+
40
+ // Step 3: Gather evidence and classify each package
41
+ console.log('');
42
+ console.log('🔍 Gathering evidence and classifying...');
43
+
44
+ const diataxisEntries = [];
45
+ for (const pkg of packages) {
46
+ // Read package.json for evidence collection
47
+ const pkgJsonPath = join(pkg.dir, 'package.json');
48
+ const pkgJsonContent = await readFile(pkgJsonPath, 'utf8');
49
+ const pkgJson = JSON.parse(pkgJsonContent);
50
+
51
+ const evidence = await collectEvidence(pkg.dir, pkgJson);
52
+ const diataxisEntry = await classifyPackage(pkg, evidence);
53
+
54
+ // Override timestamp if deterministic mode
55
+ if (isDeterministic) {
56
+ diataxisEntry.generatedAt = '2000-01-01T00:00:00.000Z';
57
+ }
58
+
59
+ diataxisEntries.push(diataxisEntry);
60
+
61
+ // Save per-package diataxis.json
62
+ const packageDir = join(artifactsDir, pkg.name);
63
+ await mkdir(packageDir, { recursive: true });
64
+ await writeFile(
65
+ join(packageDir, 'diataxis.json'),
66
+ stableStringify(diataxisEntry)
67
+ );
68
+
69
+ console.log(` ✓ ${pkg.name}`);
70
+ }
71
+
72
+ // Step 4: Save inventory.json
73
+ const inventory = {
74
+ generatedAt: isDeterministic ? '2000-01-01T00:00:00.000Z' : new Date().toISOString(),
75
+ packageCount: packages.length,
76
+ packages: packages.map(pkg => ({
77
+ name: pkg.name,
78
+ version: pkg.version,
79
+ dir: pkg.dir
80
+ }))
81
+ };
82
+
83
+ await writeFile(
84
+ join(artifactsDir, 'inventory.json'),
85
+ stableStringify(inventory)
86
+ );
87
+
88
+ // Step 5: Generate scaffolds
89
+ console.log('');
90
+ console.log('📝 Generating scaffolds...');
91
+
92
+ for (const diataxisEntry of diataxisEntries) {
93
+ const packageOutDir = join(outDir, diataxisEntry.packageName);
94
+
95
+ try {
96
+ await generateScaffold(diataxisEntry, packageOutDir);
97
+ console.log(` ✓ ${diataxisEntry.packageName}`);
98
+ } catch (error) {
99
+ console.error(` ✗ ${diataxisEntry.packageName}: ${error.message}`);
100
+ }
101
+ }
102
+
103
+ console.log('');
104
+ console.log('✅ Pipeline complete');
105
+ console.log(` Artifacts: ${artifactsDir}`);
106
+ console.log(` Output: ${outDir}`);
107
+ }
108
+
109
+ // Execute main
110
+ main().catch(error => {
111
+ console.error('❌ Pipeline failed:', error.message);
112
+ console.error(error.stack);
113
+ process.exit(1);
114
+ });