@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,584 @@
1
+ /**
2
+ * @file Diátaxis classifier - evidence-driven documentation classification
3
+ * @module classify
4
+ * @description Consumes InventoryEntry + EvidenceSnapshot and produces DiataxisEntry
5
+ * with stubs for tutorials, how-tos, reference, and explanation.
6
+ */
7
+
8
+ import { createDiataxisEntry, validateDiataxisEntry, ensureMinimumDiataxis } from './diataxis-schema.mjs';
9
+ import { hashEvidence } from './evidence.mjs';
10
+
11
+ /**
12
+ * Generate a stable ID from a title string with type prefix
13
+ * @param {string} title - Title to convert
14
+ * @param {string} type - Type prefix (tutorial, howto, reference, explanation)
15
+ * @returns {string} Prefixed kebab-case ID
16
+ */
17
+ function generateId(title, type) {
18
+ const slug = title
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, '-')
21
+ .replace(/^-+|-+$/g, '');
22
+ return `${type}-${slug}`;
23
+ }
24
+
25
+ /**
26
+ * Calculate confidence score based on evidence strength
27
+ * @param {Object} evidence - Evidence object containing various sources
28
+ * @param {string} type - Type of documentation (tutorials, howtos, reference, explanation)
29
+ * @returns {number} Confidence score 0-1
30
+ */
31
+ function confidenceScore(evidence, type) {
32
+ let score = 0;
33
+
34
+ switch (type) {
35
+ case 'tutorials':
36
+ // +0.3 for examples/, +0.3 for README, +0.1 for keywords
37
+ if (evidence.examplesFiles && evidence.examplesFiles.length > 0) {
38
+ score += 0.3;
39
+ }
40
+ if (evidence.readmeHeadings && evidence.readmeHeadings.some(h =>
41
+ /tutorial|getting started|quick start|first steps/i.test(h)
42
+ )) {
43
+ score += 0.3;
44
+ }
45
+ if (evidence.keywords && evidence.keywords.length > 0) {
46
+ score += 0.1;
47
+ }
48
+ // Additional boost for README content with intro sections
49
+ if (evidence.readmeContent && /## (tutorial|getting started|quick start)/i.test(evidence.readmeContent)) {
50
+ score += 0.3;
51
+ }
52
+ break;
53
+
54
+ case 'howtos':
55
+ // +0.25 README sections, +0.25 bin, +0.25 keywords, +0.25 tests
56
+ if (evidence.readmeHeadings && evidence.readmeHeadings.some(h =>
57
+ /usage|api|options|configuration|how to/i.test(h)
58
+ )) {
59
+ score += 0.25;
60
+ }
61
+ if (evidence.binEntries && Object.keys(evidence.binEntries).length > 0) {
62
+ score += 0.25;
63
+ }
64
+ if (evidence.keywords && evidence.keywords.length > 2) {
65
+ score += 0.25;
66
+ }
67
+ if (evidence.testFileCount && evidence.testFileCount > 0) {
68
+ score += 0.25;
69
+ }
70
+ break;
71
+
72
+ case 'reference':
73
+ // +0.5 exports, +0.3 bin, +0.2 README API
74
+ if (evidence.exportSurface && Object.keys(evidence.exportSurface).length > 0) {
75
+ score += 0.5;
76
+ }
77
+ if (evidence.binEntries && Object.keys(evidence.binEntries).length > 0) {
78
+ score += 0.3;
79
+ }
80
+ if (evidence.readmeHeadings && evidence.readmeHeadings.some(h =>
81
+ /api|reference|exports|methods/i.test(h)
82
+ )) {
83
+ score += 0.2;
84
+ }
85
+ break;
86
+
87
+ case 'explanation':
88
+ // +0.3 README, +0.3 docs/, +0.4 keywords
89
+ if (evidence.readmeContent && evidence.readmeContent.length > 100) {
90
+ score += 0.3;
91
+ }
92
+ if (evidence.docsFiles && evidence.docsFiles.length > 0) {
93
+ score += 0.3;
94
+ }
95
+ if (evidence.keywords && evidence.keywords.length > 0) {
96
+ score += 0.4;
97
+ }
98
+ break;
99
+
100
+ default:
101
+ return 0;
102
+ }
103
+
104
+ return Math.min(score, 1.0);
105
+ }
106
+
107
+ /**
108
+ * Extract API section content from README (first 500 chars after API heading)
109
+ * @param {string|null} readmeContent - README content
110
+ * @returns {string} API section snippet or empty string
111
+ */
112
+ function extractApiSection(readmeContent) {
113
+ if (!readmeContent) return '';
114
+
115
+ const apiMatch = readmeContent.match(/##\s+(API|Reference|Exports|Methods)\s*\n([\s\S]{0,500})/i);
116
+ return apiMatch ? apiMatch[2].trim() : '';
117
+ }
118
+
119
+ /**
120
+ * Extract first sentence from README intro (architecture description)
121
+ * @param {string|null} readmeContent - README content
122
+ * @returns {string} First sentence or empty string
123
+ */
124
+ function extractFirstSentence(readmeContent) {
125
+ if (!readmeContent) return '';
126
+
127
+ // Skip over title and find first paragraph
128
+ const paragraphMatch = readmeContent.match(/^#[^\n]*\n+([^#\n][^\n]*\.)/m);
129
+ return paragraphMatch ? paragraphMatch[1].trim() : '';
130
+ }
131
+
132
+ /**
133
+ * Check if README contains tradeoff/comparison language
134
+ * @param {string|null} readmeContent - README content
135
+ * @returns {boolean} True if tradeoff language detected
136
+ */
137
+ function hasTradeoffLanguage(readmeContent) {
138
+ if (!readmeContent) return false;
139
+ return /\b(trade[-\s]?off|vs\.?|versus|compared to|alternative|choice)\b/i.test(readmeContent);
140
+ }
141
+
142
+ /**
143
+ * Generate tutorial stubs based on evidence
144
+ * @param {string} packageName - Package name
145
+ * @param {Object} evidence - Evidence snapshot
146
+ * @returns {Array<{id: string, title: string, goal: string, prerequisites: string[], stepsOutline: string[], confidenceScore: number, source: string[]}>} Tutorial stubs
147
+ */
148
+ function generateTutorials(packageName, evidence) {
149
+ const tutorials = [];
150
+ const sources = [];
151
+
152
+ // Determine confidence and sources
153
+ let confidence = 0;
154
+ const hasExamples = evidence.examplesFiles && evidence.examplesFiles.length > 0;
155
+ const hasReadmeIntro = evidence.readmeHeadings && evidence.readmeHeadings.some(h =>
156
+ /tutorial|getting started|quick start/i.test(h)
157
+ );
158
+
159
+ if (hasExamples) {
160
+ sources.push('examples');
161
+ confidence += 0.6;
162
+ }
163
+ if (hasReadmeIntro) {
164
+ sources.push('readme');
165
+ confidence += 0.3;
166
+ }
167
+ if (evidence.keywords && evidence.keywords.length > 0) {
168
+ sources.push('keywords');
169
+ confidence += 0.1;
170
+ }
171
+
172
+ confidence = Math.min(confidence, 1.0);
173
+
174
+ // Always generate "Getting Started" tutorial
175
+ const prerequisites = [];
176
+ if (evidence.keywords) {
177
+ // Infer prerequisites from keywords
178
+ if (evidence.keywords.some(k => /rdf|semantic|triple/i.test(k))) {
179
+ prerequisites.push('Basic RDF knowledge');
180
+ }
181
+ if (evidence.keywords.some(k => /graph|query|sparql/i.test(k))) {
182
+ prerequisites.push('Understanding of graph databases');
183
+ }
184
+ if (evidence.keywords.some(k => /node|javascript|npm/i.test(k))) {
185
+ prerequisites.push('Node.js environment');
186
+ }
187
+ }
188
+
189
+ // Generate steps from evidence headings or use generic
190
+ let stepsOutline = [];
191
+ if (hasReadmeIntro) {
192
+ // Try to extract steps from README headings
193
+ const stepHeadings = evidence.readmeHeadings.filter(h =>
194
+ /install|setup|usage|example|run/i.test(h)
195
+ ).slice(0, 5);
196
+
197
+ if (stepHeadings.length >= 3) {
198
+ stepsOutline = stepHeadings;
199
+ }
200
+ }
201
+
202
+ if (stepsOutline.length === 0) {
203
+ // Use generic steps
204
+ stepsOutline = [
205
+ 'Install the package',
206
+ 'Import required modules',
207
+ 'Create a basic example',
208
+ 'Run and verify output'
209
+ ];
210
+ }
211
+
212
+ tutorials.push({
213
+ id: generateId(`Getting Started with ${packageName}`, 'tutorial'),
214
+ title: `Getting Started with ${packageName}`,
215
+ goal: `Learn how to set up and use ${packageName} in your project`,
216
+ prerequisites,
217
+ stepsOutline,
218
+ confidenceScore: confidence,
219
+ source: sources.length > 0 ? sources : ['inferred']
220
+ });
221
+
222
+ // Generate "First Steps" if multiple examples exist
223
+ if (hasExamples && evidence.examplesFiles.length > 2) {
224
+ tutorials.push({
225
+ id: generateId('First Steps', 'tutorial'),
226
+ title: 'First Steps',
227
+ goal: 'Complete your first practical examples',
228
+ prerequisites: [`Completed "Getting Started with ${packageName}"`],
229
+ stepsOutline: evidence.examplesFiles.slice(0, 5).map(file => `Work through ${file}`),
230
+ confidenceScore: 0.8,
231
+ source: ['examples']
232
+ });
233
+ }
234
+
235
+ // Sort by id for stable ordering
236
+ tutorials.sort((a, b) => a.id.localeCompare(b.id));
237
+
238
+ return tutorials;
239
+ }
240
+
241
+ /**
242
+ * Generate how-to stubs based on evidence (minimum 2 required)
243
+ * @param {string} packageName - Package name
244
+ * @param {Object} evidence - Evidence snapshot
245
+ * @returns {Array<{id: string, title: string, task: string, context: string, steps: string[], confidenceScore: number, source: string[]}>} How-to stubs
246
+ */
247
+ function generateHowTos(packageName, evidence) {
248
+ const howtos = [];
249
+
250
+ // Check for Configuration section
251
+ if (evidence.readmeHeadings && evidence.readmeHeadings.some(h => /configuration|config|options/i.test(h))) {
252
+ howtos.push({
253
+ id: generateId(`Configure ${packageName}`, 'howto'),
254
+ title: `Configure ${packageName}`,
255
+ task: `Set up custom configuration for ${packageName}`,
256
+ context: 'When you need to customize package behavior',
257
+ steps: [
258
+ 'Create configuration file',
259
+ 'Define configuration options',
260
+ 'Apply configuration to package'
261
+ ],
262
+ confidenceScore: 1.0,
263
+ source: ['readme']
264
+ });
265
+ }
266
+
267
+ // Check for CLI usage (bin entries)
268
+ if (evidence.binEntries && Object.keys(evidence.binEntries).length > 0) {
269
+ const binNames = Object.keys(evidence.binEntries);
270
+ howtos.push({
271
+ id: generateId('Use the CLI', 'howto'),
272
+ title: 'Use the CLI',
273
+ task: `Execute ${binNames[0]} command-line interface`,
274
+ context: 'When you need to use the package from the command line',
275
+ steps: [
276
+ 'Install package globally or locally',
277
+ 'Run CLI command with options',
278
+ 'Process command output'
279
+ ],
280
+ confidenceScore: 1.0,
281
+ source: ['bin']
282
+ });
283
+ }
284
+
285
+ // Check for integration opportunities (keywords)
286
+ if (evidence.keywords && evidence.keywords.length > 2) {
287
+ const keyword = evidence.keywords[0];
288
+ howtos.push({
289
+ id: generateId(`Integrate with ${keyword}`, 'howto'),
290
+ title: `Integrate with ${keyword}`,
291
+ task: `Connect ${packageName} with ${keyword}`,
292
+ context: `When working with ${keyword} in your project`,
293
+ steps: [
294
+ 'Install dependencies',
295
+ 'Set up integration',
296
+ 'Test integration'
297
+ ],
298
+ confidenceScore: 0.7,
299
+ source: ['keywords', 'inferred']
300
+ });
301
+ }
302
+
303
+ // Check for error handling (test files exist)
304
+ if (evidence.testFileCount && evidence.testFileCount > 0) {
305
+ howtos.push({
306
+ id: generateId('Handle Errors', 'howto'),
307
+ title: 'Handle Errors',
308
+ task: `Implement error handling for ${packageName}`,
309
+ context: 'When you need robust error handling in production',
310
+ steps: [
311
+ 'Set up try-catch blocks',
312
+ 'Handle common error types',
313
+ 'Log errors appropriately'
314
+ ],
315
+ confidenceScore: 0.5,
316
+ source: ['tests']
317
+ });
318
+ }
319
+
320
+ // Ensure minimum 2 how-tos (add generic ones if needed)
321
+ if (howtos.length === 0) {
322
+ howtos.push({
323
+ id: generateId(`Use ${packageName}`, 'howto'),
324
+ title: `Use ${packageName}`,
325
+ task: `Perform basic operations with ${packageName}`,
326
+ context: 'When you need to accomplish common tasks',
327
+ steps: [
328
+ 'Import the package',
329
+ 'Initialize with options',
330
+ 'Execute operations'
331
+ ],
332
+ confidenceScore: 0.4,
333
+ source: ['inferred']
334
+ });
335
+ }
336
+
337
+ if (howtos.length === 1) {
338
+ howtos.push({
339
+ id: generateId('Troubleshoot Common Issues', 'howto'),
340
+ title: 'Troubleshoot Common Issues',
341
+ task: 'Resolve common problems and errors',
342
+ context: 'When encountering issues during usage',
343
+ steps: [
344
+ 'Check configuration',
345
+ 'Review error messages',
346
+ 'Apply fixes'
347
+ ],
348
+ confidenceScore: 0.4,
349
+ source: ['inferred']
350
+ });
351
+ }
352
+
353
+ // Sort by id for stable ordering
354
+ howtos.sort((a, b) => a.id.localeCompare(b.id));
355
+
356
+ return howtos;
357
+ }
358
+
359
+ /**
360
+ * Generate reference documentation based on exports and bin
361
+ * @param {string} packageName - Package name
362
+ * @param {Object} evidence - Evidence snapshot
363
+ * @returns {{id: string, title: string, items: Array, confidenceScore: number, source: string[]}} Reference object
364
+ */
365
+ function generateReference(packageName, evidence) {
366
+ const items = [];
367
+ const sources = [];
368
+ let confidence = 0;
369
+
370
+ // Extract from exports field
371
+ if (evidence.exportSurface && typeof evidence.exportSurface === 'object') {
372
+ const exportKeys = Object.keys(evidence.exportSurface);
373
+ if (exportKeys.length > 0) {
374
+ sources.push('exports');
375
+ confidence = 1.0;
376
+
377
+ for (const key of exportKeys.sort()) {
378
+ const exportPath = evidence.exportSurface[key];
379
+ items.push({
380
+ name: key === '.' ? packageName : key,
381
+ type: 'export',
382
+ description: `Export: ${key}`,
383
+ example: typeof exportPath === 'string' ? `import from "${exportPath}"` : null
384
+ });
385
+ }
386
+ }
387
+ }
388
+
389
+ // Extract from bin field
390
+ if (evidence.binEntries && typeof evidence.binEntries === 'object') {
391
+ const binKeys = Object.keys(evidence.binEntries);
392
+ if (binKeys.length > 0) {
393
+ sources.push('bin');
394
+ if (confidence === 0) confidence = 0.8;
395
+
396
+ for (const binName of binKeys.sort()) {
397
+ const binPath = evidence.binEntries[binName];
398
+ items.push({
399
+ name: binName,
400
+ type: 'bin',
401
+ description: `CLI command: ${binName}`,
402
+ example: `${binName} [options]`
403
+ });
404
+ }
405
+ }
406
+ }
407
+
408
+ // Parse README for API section
409
+ const apiSection = extractApiSection(evidence.readmeContent);
410
+ if (apiSection) {
411
+ sources.push('readme');
412
+ if (confidence === 0) confidence = 0.6;
413
+ }
414
+
415
+ // Add placeholder if no exports or bin found
416
+ if (items.length === 0) {
417
+ items.push({
418
+ name: 'unknown',
419
+ type: 'unknown',
420
+ description: 'Reference documentation to be added',
421
+ example: null
422
+ });
423
+ sources.push('inferred');
424
+ confidence = 0.5;
425
+ }
426
+
427
+ // Sort items by name for stable ordering
428
+ items.sort((a, b) => a.name.localeCompare(b.name));
429
+
430
+ return {
431
+ id: generateId(`${packageName} Reference`, 'reference'),
432
+ title: `${packageName} Reference`,
433
+ items,
434
+ confidenceScore: confidence,
435
+ source: sources
436
+ };
437
+ }
438
+
439
+ /**
440
+ * Generate explanation documentation based on README and docs
441
+ * @param {string} packageName - Package name
442
+ * @param {Object} evidence - Evidence snapshot
443
+ * @returns {{id: string, title: string, concepts: string[], architecture: string, tradeoffs: string[], confidenceScore: number, source: string[]}} Explanation object
444
+ */
445
+ function generateExplanation(packageName, evidence) {
446
+ const concepts = [];
447
+ const tradeoffs = [];
448
+ const sources = [];
449
+ let confidence = 0;
450
+
451
+ // Extract concepts from keywords
452
+ if (evidence.keywords && evidence.keywords.length > 0) {
453
+ concepts.push(...evidence.keywords.slice(0, 5));
454
+ sources.push('keywords');
455
+ confidence += 0.4;
456
+ }
457
+
458
+ // Generate architecture description from README intro
459
+ let architecture = '';
460
+ if (evidence.readmeContent) {
461
+ architecture = extractFirstSentence(evidence.readmeContent);
462
+ if (architecture) {
463
+ sources.push('readme');
464
+ confidence += 0.3;
465
+ }
466
+ }
467
+
468
+ if (!architecture) {
469
+ architecture = `${packageName} is a package in the UNRDF ecosystem`;
470
+ }
471
+
472
+ // Check for docs directory
473
+ if (evidence.docsFiles && evidence.docsFiles.length > 0) {
474
+ sources.push('docs');
475
+ confidence += 0.3;
476
+ }
477
+
478
+ // List tradeoffs if detected
479
+ if (hasTradeoffLanguage(evidence.readmeContent)) {
480
+ tradeoffs.push('Performance vs. flexibility tradeoffs discussed in README');
481
+ if (!sources.includes('readme')) {
482
+ sources.push('readme');
483
+ }
484
+ }
485
+
486
+ // Generate default tradeoff stubs if none found
487
+ if (tradeoffs.length === 0) {
488
+ tradeoffs.push(
489
+ 'Ease of use vs. advanced configuration options',
490
+ 'Memory usage vs. processing speed',
491
+ 'Bundle size vs. feature completeness'
492
+ );
493
+ if (!sources.includes('inferred')) {
494
+ sources.push('inferred');
495
+ }
496
+ }
497
+
498
+ confidence = Math.min(confidence, 1.0);
499
+
500
+ return {
501
+ id: generateId(`${packageName} Explanation`, 'explanation'),
502
+ title: `Understanding ${packageName}`,
503
+ concepts: concepts.length > 0 ? concepts : ['core functionality'],
504
+ architecture,
505
+ tradeoffs,
506
+ confidenceScore: confidence,
507
+ source: sources.length > 0 ? sources : ['inferred']
508
+ };
509
+ }
510
+
511
+ /**
512
+ * Classify a package and produce DiataxisEntry with stubs
513
+ * @param {import('./inventory.mjs').PackageEntry} packageEntry - Package metadata from inventory
514
+ * @param {import('./evidence.mjs').EvidenceSnapshot} evidenceSnapshot - Evidence collected from package
515
+ * @returns {Promise<import('./diataxis-schema.mjs').DiataxisEntry>} Complete Diátaxis entry
516
+ * @throws {Error} If packageEntry is invalid
517
+ */
518
+ export async function classifyPackage(packageEntry, evidenceSnapshot) {
519
+ // Validate packageEntry
520
+ if (!packageEntry || typeof packageEntry !== 'object') {
521
+ throw new Error('Invalid packageEntry: must be an object');
522
+ }
523
+ if (!packageEntry.name || typeof packageEntry.name !== 'string') {
524
+ throw new Error('Invalid packageEntry: name must be a non-empty string');
525
+ }
526
+ if (!packageEntry.version || typeof packageEntry.version !== 'string') {
527
+ throw new Error('Invalid packageEntry: version must be a non-empty string');
528
+ }
529
+
530
+ // Use empty snapshot if evidenceSnapshot is missing
531
+ const evidence = evidenceSnapshot || {
532
+ readmeContent: null,
533
+ readmeHeadings: [],
534
+ examplesFiles: [],
535
+ examplesSnippets: {},
536
+ docsFiles: [],
537
+ docsSnippets: {},
538
+ srcFiles: [],
539
+ testFileCount: 0,
540
+ binEntries: {},
541
+ exportSurface: {},
542
+ keywords: [],
543
+ hasLicense: false,
544
+ hasTsConfig: false,
545
+ fingerprint: ''
546
+ };
547
+
548
+ // Generate all Diátaxis sections
549
+ const tutorials = generateTutorials(packageEntry.name, evidence);
550
+ const howtos = generateHowTos(packageEntry.name, evidence);
551
+ const reference = generateReference(packageEntry.name, evidence);
552
+ const explanation = generateExplanation(packageEntry.name, evidence);
553
+
554
+ // Calculate overall confidence scores
555
+ const confidence = {
556
+ tutorials: confidenceScore(evidence, 'tutorials'),
557
+ howtos: confidenceScore(evidence, 'howtos'),
558
+ reference: confidenceScore(evidence, 'reference'),
559
+ explanation: confidenceScore(evidence, 'explanation')
560
+ };
561
+
562
+ // Create Diátaxis entry
563
+ const entry = createDiataxisEntry(packageEntry.name, packageEntry.version, {
564
+ readmeHeadings: evidence.readmeHeadings || [],
565
+ docsFiles: evidence.docsFiles || [],
566
+ examplesFiles: evidence.examplesFiles || [],
567
+ tutorials,
568
+ howtos,
569
+ reference,
570
+ explanation,
571
+ confidence
572
+ });
573
+
574
+ // Validate the entry
575
+ const validation = validateDiataxisEntry(entry);
576
+ if (!validation.valid) {
577
+ throw new Error(`Generated invalid DiataxisEntry: ${validation.errors.join(', ')}`);
578
+ }
579
+
580
+ // Ensure minimum structure
581
+ const finalEntry = ensureMinimumDiataxis(entry);
582
+
583
+ return finalEntry;
584
+ }