vibe-fabric 0.3.3 → 0.4.1

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 (56) hide show
  1. package/dist/cli/commands/analyze.d.ts +33 -0
  2. package/dist/cli/commands/analyze.d.ts.map +1 -0
  3. package/dist/cli/commands/analyze.js +243 -0
  4. package/dist/cli/commands/analyze.js.map +1 -0
  5. package/dist/cli/commands/config/get.d.ts +9 -0
  6. package/dist/cli/commands/config/get.d.ts.map +1 -0
  7. package/dist/cli/commands/config/get.js +69 -0
  8. package/dist/cli/commands/config/get.js.map +1 -0
  9. package/dist/cli/commands/config/list.d.ts +24 -0
  10. package/dist/cli/commands/config/list.d.ts.map +1 -0
  11. package/dist/cli/commands/config/list.js +146 -0
  12. package/dist/cli/commands/config/list.js.map +1 -0
  13. package/dist/cli/commands/config/set.d.ts +14 -0
  14. package/dist/cli/commands/config/set.d.ts.map +1 -0
  15. package/dist/cli/commands/config/set.js +111 -0
  16. package/dist/cli/commands/config/set.js.map +1 -0
  17. package/dist/cli/commands/repo/list.d.ts +26 -0
  18. package/dist/cli/commands/repo/list.d.ts.map +1 -0
  19. package/dist/cli/commands/repo/list.js +197 -0
  20. package/dist/cli/commands/repo/list.js.map +1 -0
  21. package/dist/cli/commands/repo/remove.d.ts +29 -0
  22. package/dist/cli/commands/repo/remove.d.ts.map +1 -0
  23. package/dist/cli/commands/repo/remove.js +219 -0
  24. package/dist/cli/commands/repo/remove.js.map +1 -0
  25. package/dist/cli/commands/report.d.ts +16 -0
  26. package/dist/cli/commands/report.d.ts.map +1 -0
  27. package/dist/cli/commands/report.js +160 -0
  28. package/dist/cli/commands/report.js.map +1 -0
  29. package/dist/cli/index.js +14 -0
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/core/config.d.ts +25 -0
  32. package/dist/core/config.d.ts.map +1 -1
  33. package/dist/core/config.js +77 -0
  34. package/dist/core/config.js.map +1 -1
  35. package/dist/core/repo/templates/claude-agents.d.ts.map +1 -1
  36. package/dist/core/repo/templates/claude-agents.js +136 -28
  37. package/dist/core/repo/templates/claude-agents.js.map +1 -1
  38. package/dist/core/repo/templates/claude-prompts.d.ts +1 -1
  39. package/dist/core/repo/templates/claude-prompts.d.ts.map +1 -1
  40. package/dist/core/repo/templates/claude-prompts.js +412 -157
  41. package/dist/core/repo/templates/claude-prompts.js.map +1 -1
  42. package/dist/core/repo/templates/claude-scripts.d.ts.map +1 -1
  43. package/dist/core/repo/templates/claude-scripts.js +555 -94
  44. package/dist/core/repo/templates/claude-scripts.js.map +1 -1
  45. package/dist/core/repo/templates/vibe-readme.d.ts.map +1 -1
  46. package/dist/core/repo/templates/vibe-readme.js +177 -10
  47. package/dist/core/repo/templates/vibe-readme.js.map +1 -1
  48. package/dist/core/report.d.ts +25 -0
  49. package/dist/core/report.d.ts.map +1 -0
  50. package/dist/core/report.js +702 -0
  51. package/dist/core/report.js.map +1 -0
  52. package/dist/types/report.d.ts +158 -0
  53. package/dist/types/report.d.ts.map +1 -0
  54. package/dist/types/report.js +7 -0
  55. package/dist/types/report.js.map +1 -0
  56. package/package.json +1 -1
@@ -0,0 +1,702 @@
1
+ /**
2
+ * Core report module - aggregates data and generates reports
3
+ *
4
+ * Generates exportable reports in Markdown, JSON, and HTML formats
5
+ * using cached project data.
6
+ */
7
+ import { loadLastSyncData, loadProjectStatus, } from './status.js';
8
+ /**
9
+ * Package version (updated during build)
10
+ */
11
+ const VIBE_VERSION = '0.2.0';
12
+ /**
13
+ * Aggregate all data needed for reports
14
+ */
15
+ export async function aggregateReportData(projectPath, config) {
16
+ // Load project status (reuses existing status loading)
17
+ const status = await loadProjectStatus(projectPath, config);
18
+ const lastSync = await loadLastSyncData(projectPath);
19
+ // Build report metadata
20
+ const meta = {
21
+ generated: new Date().toISOString(),
22
+ dataTimestamp: lastSync?.timestamp || null,
23
+ projectName: status.projectName,
24
+ vibeVersion: VIBE_VERSION,
25
+ };
26
+ // Build summary
27
+ const activeRepos = status.repos.filter((r) => r.status === 'active');
28
+ const syncedRepos = activeRepos.filter((r) => r.success);
29
+ const summary = {
30
+ repositories: status.repos.length,
31
+ activeRepositories: activeRepos.length,
32
+ syncedRepositories: syncedRepos.length,
33
+ prdCoverage: status.coverage.coveragePercentage,
34
+ totalRequirements: status.coverage.totalRequirements,
35
+ implementedRequirements: status.coverage.implementedRequirements,
36
+ activeScopes: status.scopes.inProgress + status.scopes.sent,
37
+ totalScopes: status.scopes.drafts +
38
+ status.scopes.ready +
39
+ status.scopes.sent +
40
+ status.scopes.inProgress +
41
+ status.scopes.completed,
42
+ criticalGaps: status.gaps.p1,
43
+ totalGaps: status.gaps.total,
44
+ health: status.health,
45
+ healthReasons: status.healthReasons,
46
+ };
47
+ // Build repository status list
48
+ const repositories = status.repos.map((repo) => ({
49
+ alias: repo.alias,
50
+ name: repo.name,
51
+ status: repo.status,
52
+ lastSync: repo.lastSync,
53
+ synced: repo.success,
54
+ filesScanned: repo.filesScanned,
55
+ kbEntries: repo.kbEntries,
56
+ error: repo.error,
57
+ }));
58
+ // Build coverage data
59
+ const modules = Object.entries(status.coverage.byModule).map(([name, data]) => ({
60
+ name,
61
+ total: data.total,
62
+ implemented: data.implemented,
63
+ percentage: data.percentage,
64
+ }));
65
+ const coverage = {
66
+ percentage: status.coverage.coveragePercentage,
67
+ implemented: status.coverage.implementedRequirements,
68
+ total: status.coverage.totalRequirements,
69
+ modules,
70
+ };
71
+ // Build scope counts
72
+ const scopes = {
73
+ drafts: status.scopes.drafts,
74
+ ready: status.scopes.ready,
75
+ sent: status.scopes.sent,
76
+ inProgress: status.scopes.inProgress,
77
+ completed: status.scopes.completed,
78
+ blocked: status.scopes.blocked,
79
+ total: status.scopes.drafts +
80
+ status.scopes.ready +
81
+ status.scopes.sent +
82
+ status.scopes.inProgress +
83
+ status.scopes.completed,
84
+ };
85
+ // Build gap summary
86
+ const gaps = {
87
+ p1: status.gaps.p1,
88
+ p2: status.gaps.p2,
89
+ p3: status.gaps.p3,
90
+ total: status.gaps.total,
91
+ gaps: status.gapDetails.map((gap) => ({
92
+ id: gap.id,
93
+ type: 'missing',
94
+ priority: gap.priority,
95
+ module: gap.module,
96
+ description: gap.description,
97
+ })),
98
+ };
99
+ return {
100
+ meta,
101
+ summary,
102
+ repositories,
103
+ coverage,
104
+ scopes,
105
+ gaps,
106
+ };
107
+ }
108
+ /**
109
+ * Generate Markdown report
110
+ */
111
+ export function generateMarkdown(data, type) {
112
+ const sections = [];
113
+ // Header
114
+ sections.push(`# Project Report: ${data.meta.projectName}`);
115
+ sections.push('');
116
+ sections.push(`**Generated:** ${formatDate(data.meta.generated)}`);
117
+ if (data.meta.dataTimestamp) {
118
+ sections.push(`**Data as of:** ${formatDate(data.meta.dataTimestamp)}`);
119
+ }
120
+ sections.push('');
121
+ sections.push('---');
122
+ sections.push('');
123
+ // Executive Summary (always included)
124
+ sections.push('## Executive Summary');
125
+ sections.push('');
126
+ sections.push('| Metric | Value |');
127
+ sections.push('|--------|-------|');
128
+ sections.push(`| Health | ${getHealthEmoji(data.summary.health)} ${data.summary.health.toUpperCase()} |`);
129
+ sections.push(`| Repositories | ${data.summary.syncedRepositories}/${data.summary.activeRepositories} synced |`);
130
+ sections.push(`| PRD Coverage | ${data.summary.prdCoverage}% |`);
131
+ sections.push(`| Active Scopes | ${data.summary.activeScopes} of ${data.summary.totalScopes} |`);
132
+ sections.push(`| Critical Gaps | ${data.summary.criticalGaps} P1 issues |`);
133
+ sections.push('');
134
+ if (data.summary.healthReasons.length > 0 && data.summary.health !== 'healthy') {
135
+ sections.push('**Health Issues:**');
136
+ for (const reason of data.summary.healthReasons) {
137
+ sections.push(`- ${reason}`);
138
+ }
139
+ sections.push('');
140
+ }
141
+ // Repository Status (for full or status type)
142
+ if (type === 'full' || type === 'status') {
143
+ sections.push('---');
144
+ sections.push('');
145
+ sections.push('## Repository Status');
146
+ sections.push('');
147
+ if (data.repositories.length === 0) {
148
+ sections.push('*No repositories configured.*');
149
+ }
150
+ else {
151
+ sections.push('| Repository | Status | Last Sync | Files | KB Entries |');
152
+ sections.push('|------------|--------|-----------|-------|------------|');
153
+ for (const repo of data.repositories) {
154
+ const statusIcon = repo.status === 'planned'
155
+ ? '⏳'
156
+ : repo.synced
157
+ ? '✅'
158
+ : '❌';
159
+ const lastSync = repo.status === 'planned'
160
+ ? 'Planned'
161
+ : repo.lastSync
162
+ ? formatDate(repo.lastSync)
163
+ : 'Never';
164
+ sections.push(`| ${repo.alias} | ${statusIcon} ${repo.status} | ${lastSync} | ${repo.filesScanned} | ${repo.kbEntries} |`);
165
+ }
166
+ }
167
+ sections.push('');
168
+ }
169
+ // PRD Coverage (for full or coverage type)
170
+ if (type === 'full' || type === 'coverage') {
171
+ sections.push('---');
172
+ sections.push('');
173
+ sections.push('## PRD Coverage');
174
+ sections.push('');
175
+ sections.push(`**Overall:** ${data.coverage.percentage}% (${data.coverage.implemented}/${data.coverage.total} requirements)`);
176
+ sections.push('');
177
+ if (data.coverage.modules.length > 0) {
178
+ sections.push('### Coverage by Module');
179
+ sections.push('');
180
+ sections.push('| Module | Coverage | Implemented | Total |');
181
+ sections.push('|--------|----------|-------------|-------|');
182
+ for (const module of data.coverage.modules) {
183
+ const bar = getProgressBarMd(module.percentage);
184
+ sections.push(`| ${module.name} | ${bar} ${module.percentage}% | ${module.implemented} | ${module.total} |`);
185
+ }
186
+ }
187
+ else {
188
+ sections.push('*No PRD modules found.*');
189
+ }
190
+ sections.push('');
191
+ }
192
+ // Scopes (for full or status type)
193
+ if (type === 'full' || type === 'status') {
194
+ sections.push('---');
195
+ sections.push('');
196
+ sections.push('## Scope Progress');
197
+ sections.push('');
198
+ sections.push('| Status | Count |');
199
+ sections.push('|--------|-------|');
200
+ sections.push(`| 📝 Drafts | ${data.scopes.drafts} |`);
201
+ sections.push(`| ✅ Ready | ${data.scopes.ready} |`);
202
+ sections.push(`| 📤 Sent | ${data.scopes.sent} |`);
203
+ sections.push(`| 🔄 In Progress | ${data.scopes.inProgress} |`);
204
+ sections.push(`| ✔️ Completed | ${data.scopes.completed} |`);
205
+ if (data.scopes.blocked > 0) {
206
+ sections.push(`| 🚫 Blocked | ${data.scopes.blocked} |`);
207
+ }
208
+ sections.push(`| **Total** | **${data.scopes.total}** |`);
209
+ sections.push('');
210
+ }
211
+ // Gap Analysis (for full or gaps type)
212
+ if (type === 'full' || type === 'gaps') {
213
+ sections.push('---');
214
+ sections.push('');
215
+ sections.push('## Gap Analysis');
216
+ sections.push('');
217
+ if (data.gaps.total === 0) {
218
+ sections.push('✅ **No gaps found!** All requirements are covered.');
219
+ }
220
+ else {
221
+ sections.push('### Summary by Priority');
222
+ sections.push('');
223
+ sections.push('| Priority | Count |');
224
+ sections.push('|----------|-------|');
225
+ if (data.gaps.p1 > 0) {
226
+ sections.push(`| 🔴 P1 (Critical) | ${data.gaps.p1} |`);
227
+ }
228
+ if (data.gaps.p2 > 0) {
229
+ sections.push(`| 🟡 P2 (High) | ${data.gaps.p2} |`);
230
+ }
231
+ if (data.gaps.p3 > 0) {
232
+ sections.push(`| ⚪ P3 (Medium) | ${data.gaps.p3} |`);
233
+ }
234
+ sections.push(`| **Total** | **${data.gaps.total}** |`);
235
+ sections.push('');
236
+ if (data.gaps.gaps.length > 0) {
237
+ sections.push('### Gap Details');
238
+ sections.push('');
239
+ sections.push('| ID | Priority | Module | Description |');
240
+ sections.push('|----|----------|--------|-------------|');
241
+ const displayGaps = data.gaps.gaps.slice(0, 20);
242
+ for (const gap of displayGaps) {
243
+ const priorityIcon = gap.priority === 'P1' ? '🔴' : gap.priority === 'P2' ? '🟡' : '⚪';
244
+ sections.push(`| ${gap.id} | ${priorityIcon} ${gap.priority} | ${gap.module} | ${gap.description} |`);
245
+ }
246
+ if (data.gaps.gaps.length > 20) {
247
+ sections.push('');
248
+ sections.push(`*... and ${data.gaps.gaps.length - 20} more gaps*`);
249
+ }
250
+ }
251
+ }
252
+ sections.push('');
253
+ }
254
+ // Footer
255
+ sections.push('---');
256
+ sections.push('');
257
+ sections.push(`*Report generated by [Vibe-Fabric](https://github.com/anthropics/vibe-fabric) v${data.meta.vibeVersion}*`);
258
+ sections.push('');
259
+ return sections.join('\n');
260
+ }
261
+ /**
262
+ * Generate JSON report
263
+ */
264
+ export function generateJson(data, type) {
265
+ const output = {
266
+ $schema: 'https://vibe-fabric.dev/schemas/report.json',
267
+ meta: data.meta,
268
+ summary: {
269
+ health: data.summary.health,
270
+ healthReasons: data.summary.healthReasons,
271
+ repositories: {
272
+ total: data.summary.repositories,
273
+ active: data.summary.activeRepositories,
274
+ synced: data.summary.syncedRepositories,
275
+ },
276
+ coverage: {
277
+ percentage: data.summary.prdCoverage,
278
+ implemented: data.summary.implementedRequirements,
279
+ total: data.summary.totalRequirements,
280
+ },
281
+ scopes: {
282
+ active: data.summary.activeScopes,
283
+ total: data.summary.totalScopes,
284
+ },
285
+ gaps: {
286
+ critical: data.summary.criticalGaps,
287
+ total: data.summary.totalGaps,
288
+ },
289
+ },
290
+ repositories: type === 'full' || type === 'status' ? data.repositories : [],
291
+ coverage: type === 'full' || type === 'coverage' ? data.coverage : {
292
+ percentage: data.coverage.percentage,
293
+ implemented: data.coverage.implemented,
294
+ total: data.coverage.total,
295
+ modules: [],
296
+ },
297
+ scopes: type === 'full' || type === 'status' ? data.scopes : {
298
+ drafts: 0,
299
+ ready: 0,
300
+ sent: 0,
301
+ inProgress: 0,
302
+ completed: 0,
303
+ blocked: 0,
304
+ total: 0,
305
+ },
306
+ gaps: type === 'full' || type === 'gaps' ? data.gaps : {
307
+ p1: data.gaps.p1,
308
+ p2: data.gaps.p2,
309
+ p3: data.gaps.p3,
310
+ total: data.gaps.total,
311
+ gaps: [],
312
+ },
313
+ };
314
+ return JSON.stringify(output, null, 2);
315
+ }
316
+ /**
317
+ * Generate HTML report
318
+ */
319
+ export function generateHtml(data, type) {
320
+ const healthColor = data.summary.health === 'healthy'
321
+ ? '#4caf50'
322
+ : data.summary.health === 'warning'
323
+ ? '#ff9800'
324
+ : '#f44336';
325
+ const sections = [];
326
+ // Start HTML document
327
+ sections.push(`<!DOCTYPE html>
328
+ <html lang="en">
329
+ <head>
330
+ <meta charset="UTF-8">
331
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
332
+ <title>Project Report: ${escapeHtml(data.meta.projectName)}</title>
333
+ <style>
334
+ * { box-sizing: border-box; }
335
+ body {
336
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
337
+ max-width: 900px;
338
+ margin: 0 auto;
339
+ padding: 2rem;
340
+ line-height: 1.6;
341
+ color: #333;
342
+ background: #fafafa;
343
+ }
344
+ h1, h2, h3 { color: #1a1a1a; margin-top: 2rem; }
345
+ h1 { border-bottom: 3px solid #2196f3; padding-bottom: 0.5rem; }
346
+ h2 { border-bottom: 1px solid #e0e0e0; padding-bottom: 0.3rem; }
347
+ .meta { color: #666; font-size: 0.9rem; margin-bottom: 1.5rem; }
348
+ .summary-grid {
349
+ display: grid;
350
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
351
+ gap: 1rem;
352
+ margin: 1.5rem 0;
353
+ }
354
+ .metric {
355
+ background: white;
356
+ padding: 1.25rem;
357
+ border-radius: 8px;
358
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
359
+ text-align: center;
360
+ }
361
+ .metric-value {
362
+ font-size: 2rem;
363
+ font-weight: bold;
364
+ color: #1976d2;
365
+ }
366
+ .metric-label { color: #666; font-size: 0.85rem; margin-top: 0.25rem; }
367
+ .health-badge {
368
+ display: inline-block;
369
+ padding: 0.5rem 1rem;
370
+ border-radius: 20px;
371
+ color: white;
372
+ font-weight: bold;
373
+ text-transform: uppercase;
374
+ }
375
+ table {
376
+ width: 100%;
377
+ border-collapse: collapse;
378
+ margin: 1rem 0;
379
+ background: white;
380
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
381
+ border-radius: 8px;
382
+ overflow: hidden;
383
+ }
384
+ th, td {
385
+ padding: 0.75rem 1rem;
386
+ text-align: left;
387
+ border-bottom: 1px solid #e0e0e0;
388
+ }
389
+ th { background: #f5f5f5; font-weight: 600; color: #555; }
390
+ tr:last-child td { border-bottom: none; }
391
+ tr:hover td { background: #f9f9f9; }
392
+ .progress-bar {
393
+ height: 8px;
394
+ background: #e0e0e0;
395
+ border-radius: 4px;
396
+ overflow: hidden;
397
+ width: 100px;
398
+ display: inline-block;
399
+ vertical-align: middle;
400
+ margin-right: 0.5rem;
401
+ }
402
+ .progress-fill {
403
+ height: 100%;
404
+ background: linear-gradient(90deg, #4caf50, #8bc34a);
405
+ border-radius: 4px;
406
+ }
407
+ .p1 { color: #f44336; font-weight: bold; }
408
+ .p2 { color: #ff9800; }
409
+ .p3 { color: #9e9e9e; }
410
+ .status-synced { color: #4caf50; }
411
+ .status-failed { color: #f44336; }
412
+ .status-planned { color: #2196f3; }
413
+ .footer {
414
+ margin-top: 3rem;
415
+ padding-top: 1rem;
416
+ border-top: 1px solid #e0e0e0;
417
+ color: #999;
418
+ font-size: 0.85rem;
419
+ text-align: center;
420
+ }
421
+ .no-data { color: #999; font-style: italic; padding: 1rem; }
422
+ .health-reasons { margin: 0.5rem 0; padding-left: 1.5rem; }
423
+ .health-reasons li { color: #666; margin: 0.25rem 0; }
424
+ </style>
425
+ </head>
426
+ <body>
427
+ `);
428
+ // Header
429
+ sections.push(`
430
+ <h1>${escapeHtml(data.meta.projectName)}</h1>
431
+ <div class="meta">
432
+ <strong>Generated:</strong> ${formatDate(data.meta.generated)}<br>
433
+ ${data.meta.dataTimestamp ? `<strong>Data as of:</strong> ${formatDate(data.meta.dataTimestamp)}` : ''}
434
+ </div>
435
+ `);
436
+ // Health Badge
437
+ sections.push(`
438
+ <div style="margin: 1rem 0;">
439
+ <span class="health-badge" style="background: ${healthColor}">
440
+ ${data.summary.health.toUpperCase()}
441
+ </span>
442
+ </div>
443
+ `);
444
+ if (data.summary.healthReasons.length > 0 && data.summary.health !== 'healthy') {
445
+ sections.push('<ul class="health-reasons">');
446
+ for (const reason of data.summary.healthReasons) {
447
+ sections.push(`<li>${escapeHtml(reason)}</li>`);
448
+ }
449
+ sections.push('</ul>');
450
+ }
451
+ // Summary Grid
452
+ sections.push(`
453
+ <h2>Executive Summary</h2>
454
+ <div class="summary-grid">
455
+ <div class="metric">
456
+ <div class="metric-value">${data.summary.syncedRepositories}/${data.summary.activeRepositories}</div>
457
+ <div class="metric-label">Repositories Synced</div>
458
+ </div>
459
+ <div class="metric">
460
+ <div class="metric-value">${data.summary.prdCoverage}%</div>
461
+ <div class="metric-label">PRD Coverage</div>
462
+ </div>
463
+ <div class="metric">
464
+ <div class="metric-value">${data.summary.activeScopes}</div>
465
+ <div class="metric-label">Active Scopes</div>
466
+ </div>
467
+ <div class="metric">
468
+ <div class="metric-value" style="color: ${data.summary.criticalGaps > 0 ? '#f44336' : '#4caf50'}">
469
+ ${data.summary.criticalGaps}
470
+ </div>
471
+ <div class="metric-label">Critical Gaps (P1)</div>
472
+ </div>
473
+ </div>
474
+ `);
475
+ // Repository Status
476
+ if (type === 'full' || type === 'status') {
477
+ sections.push('<h2>Repository Status</h2>');
478
+ if (data.repositories.length === 0) {
479
+ sections.push('<p class="no-data">No repositories configured.</p>');
480
+ }
481
+ else {
482
+ sections.push(`
483
+ <table>
484
+ <thead>
485
+ <tr>
486
+ <th>Repository</th>
487
+ <th>Status</th>
488
+ <th>Last Sync</th>
489
+ <th>Files</th>
490
+ <th>KB Entries</th>
491
+ </tr>
492
+ </thead>
493
+ <tbody>
494
+ `);
495
+ for (const repo of data.repositories) {
496
+ const statusClass = repo.status === 'planned'
497
+ ? 'status-planned'
498
+ : repo.synced
499
+ ? 'status-synced'
500
+ : 'status-failed';
501
+ const statusText = repo.status === 'planned'
502
+ ? 'Planned'
503
+ : repo.synced
504
+ ? 'Synced'
505
+ : 'Failed';
506
+ const lastSync = repo.status === 'planned'
507
+ ? 'N/A'
508
+ : repo.lastSync
509
+ ? formatDate(repo.lastSync)
510
+ : 'Never';
511
+ sections.push(`
512
+ <tr>
513
+ <td><strong>${escapeHtml(repo.alias)}</strong> (${escapeHtml(repo.name)})</td>
514
+ <td class="${statusClass}">${statusText}</td>
515
+ <td>${lastSync}</td>
516
+ <td>${repo.filesScanned}</td>
517
+ <td>${repo.kbEntries}</td>
518
+ </tr>
519
+ `);
520
+ }
521
+ sections.push('</tbody></table>');
522
+ }
523
+ }
524
+ // PRD Coverage
525
+ if (type === 'full' || type === 'coverage') {
526
+ sections.push('<h2>PRD Coverage</h2>');
527
+ sections.push(`
528
+ <p>
529
+ <strong>Overall:</strong> ${data.coverage.percentage}%
530
+ (${data.coverage.implemented}/${data.coverage.total} requirements)
531
+ </p>
532
+ `);
533
+ if (data.coverage.modules.length > 0) {
534
+ sections.push(`
535
+ <table>
536
+ <thead>
537
+ <tr>
538
+ <th>Module</th>
539
+ <th>Coverage</th>
540
+ <th>Implemented</th>
541
+ <th>Total</th>
542
+ </tr>
543
+ </thead>
544
+ <tbody>
545
+ `);
546
+ for (const module of data.coverage.modules) {
547
+ const fillColor = module.percentage >= 75
548
+ ? '#4caf50'
549
+ : module.percentage >= 50
550
+ ? '#ff9800'
551
+ : '#f44336';
552
+ sections.push(`
553
+ <tr>
554
+ <td>${escapeHtml(module.name)}</td>
555
+ <td>
556
+ <span class="progress-bar">
557
+ <span class="progress-fill" style="width: ${module.percentage}%; background: ${fillColor};"></span>
558
+ </span>
559
+ ${module.percentage}%
560
+ </td>
561
+ <td>${module.implemented}</td>
562
+ <td>${module.total}</td>
563
+ </tr>
564
+ `);
565
+ }
566
+ sections.push('</tbody></table>');
567
+ }
568
+ else {
569
+ sections.push('<p class="no-data">No PRD modules found.</p>');
570
+ }
571
+ }
572
+ // Scope Progress
573
+ if (type === 'full' || type === 'status') {
574
+ sections.push('<h2>Scope Progress</h2>');
575
+ sections.push(`
576
+ <table>
577
+ <thead>
578
+ <tr><th>Status</th><th>Count</th></tr>
579
+ </thead>
580
+ <tbody>
581
+ <tr><td>📝 Drafts</td><td>${data.scopes.drafts}</td></tr>
582
+ <tr><td>✅ Ready</td><td>${data.scopes.ready}</td></tr>
583
+ <tr><td>📤 Sent</td><td>${data.scopes.sent}</td></tr>
584
+ <tr><td>🔄 In Progress</td><td>${data.scopes.inProgress}</td></tr>
585
+ <tr><td>✔️ Completed</td><td>${data.scopes.completed}</td></tr>
586
+ ${data.scopes.blocked > 0 ? `<tr><td>🚫 Blocked</td><td>${data.scopes.blocked}</td></tr>` : ''}
587
+ <tr><td><strong>Total</strong></td><td><strong>${data.scopes.total}</strong></td></tr>
588
+ </tbody>
589
+ </table>
590
+ `);
591
+ }
592
+ // Gap Analysis
593
+ if (type === 'full' || type === 'gaps') {
594
+ sections.push('<h2>Gap Analysis</h2>');
595
+ if (data.gaps.total === 0) {
596
+ sections.push('<p style="color: #4caf50; font-weight: bold;">✅ No gaps found! All requirements are covered.</p>');
597
+ }
598
+ else {
599
+ sections.push(`
600
+ <table>
601
+ <thead>
602
+ <tr><th>Priority</th><th>Count</th></tr>
603
+ </thead>
604
+ <tbody>
605
+ ${data.gaps.p1 > 0 ? `<tr><td class="p1">🔴 P1 (Critical)</td><td class="p1">${data.gaps.p1}</td></tr>` : ''}
606
+ ${data.gaps.p2 > 0 ? `<tr><td class="p2">🟡 P2 (High)</td><td class="p2">${data.gaps.p2}</td></tr>` : ''}
607
+ ${data.gaps.p3 > 0 ? `<tr><td class="p3">⚪ P3 (Medium)</td><td class="p3">${data.gaps.p3}</td></tr>` : ''}
608
+ <tr><td><strong>Total</strong></td><td><strong>${data.gaps.total}</strong></td></tr>
609
+ </tbody>
610
+ </table>
611
+ `);
612
+ if (data.gaps.gaps.length > 0) {
613
+ sections.push('<h3>Gap Details</h3>');
614
+ sections.push(`
615
+ <table>
616
+ <thead>
617
+ <tr>
618
+ <th>ID</th>
619
+ <th>Priority</th>
620
+ <th>Module</th>
621
+ <th>Description</th>
622
+ </tr>
623
+ </thead>
624
+ <tbody>
625
+ `);
626
+ const displayGaps = data.gaps.gaps.slice(0, 20);
627
+ for (const gap of displayGaps) {
628
+ const priorityClass = gap.priority === 'P1' ? 'p1' : gap.priority === 'P2' ? 'p2' : 'p3';
629
+ sections.push(`
630
+ <tr>
631
+ <td>${escapeHtml(gap.id)}</td>
632
+ <td class="${priorityClass}">${gap.priority}</td>
633
+ <td>${escapeHtml(gap.module)}</td>
634
+ <td>${escapeHtml(gap.description)}</td>
635
+ </tr>
636
+ `);
637
+ }
638
+ sections.push('</tbody></table>');
639
+ if (data.gaps.gaps.length > 20) {
640
+ sections.push(`<p class="no-data">... and ${data.gaps.gaps.length - 20} more gaps</p>`);
641
+ }
642
+ }
643
+ }
644
+ }
645
+ // Footer
646
+ sections.push(`
647
+ <div class="footer">
648
+ Report generated by <a href="https://github.com/anthropics/vibe-fabric">Vibe-Fabric</a> v${escapeHtml(data.meta.vibeVersion)}
649
+ </div>
650
+ </body>
651
+ </html>
652
+ `);
653
+ return sections.join('');
654
+ }
655
+ /**
656
+ * Format date for display
657
+ */
658
+ function formatDate(isoString) {
659
+ const date = new Date(isoString);
660
+ return date.toLocaleString('en-US', {
661
+ year: 'numeric',
662
+ month: 'short',
663
+ day: 'numeric',
664
+ hour: '2-digit',
665
+ minute: '2-digit',
666
+ });
667
+ }
668
+ /**
669
+ * Get health emoji
670
+ */
671
+ function getHealthEmoji(health) {
672
+ switch (health) {
673
+ case 'healthy':
674
+ return '✅';
675
+ case 'warning':
676
+ return '⚠️';
677
+ case 'critical':
678
+ return '❌';
679
+ default:
680
+ return '❓';
681
+ }
682
+ }
683
+ /**
684
+ * Get markdown progress bar
685
+ */
686
+ function getProgressBarMd(percent) {
687
+ const filled = Math.round(percent / 10);
688
+ const empty = 10 - filled;
689
+ return '█'.repeat(filled) + '░'.repeat(empty);
690
+ }
691
+ /**
692
+ * Escape HTML special characters
693
+ */
694
+ function escapeHtml(text) {
695
+ return text
696
+ .replace(/&/g, '&amp;')
697
+ .replace(/</g, '&lt;')
698
+ .replace(/>/g, '&gt;')
699
+ .replace(/"/g, '&quot;')
700
+ .replace(/'/g, '&#039;');
701
+ }
702
+ //# sourceMappingURL=report.js.map