vibe-fabric 0.3.3 → 0.4.0
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.
- package/dist/cli/commands/analyze.d.ts +33 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +243 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/config/get.d.ts +9 -0
- package/dist/cli/commands/config/get.d.ts.map +1 -0
- package/dist/cli/commands/config/get.js +69 -0
- package/dist/cli/commands/config/get.js.map +1 -0
- package/dist/cli/commands/config/list.d.ts +24 -0
- package/dist/cli/commands/config/list.d.ts.map +1 -0
- package/dist/cli/commands/config/list.js +146 -0
- package/dist/cli/commands/config/list.js.map +1 -0
- package/dist/cli/commands/config/set.d.ts +14 -0
- package/dist/cli/commands/config/set.d.ts.map +1 -0
- package/dist/cli/commands/config/set.js +111 -0
- package/dist/cli/commands/config/set.js.map +1 -0
- package/dist/cli/commands/repo/list.d.ts +26 -0
- package/dist/cli/commands/repo/list.d.ts.map +1 -0
- package/dist/cli/commands/repo/list.js +197 -0
- package/dist/cli/commands/repo/list.js.map +1 -0
- package/dist/cli/commands/repo/remove.d.ts +29 -0
- package/dist/cli/commands/repo/remove.d.ts.map +1 -0
- package/dist/cli/commands/repo/remove.js +219 -0
- package/dist/cli/commands/repo/remove.js.map +1 -0
- package/dist/cli/commands/report.d.ts +16 -0
- package/dist/cli/commands/report.d.ts.map +1 -0
- package/dist/cli/commands/report.js +160 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +25 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +77 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/repo/templates/claude-agents.d.ts.map +1 -1
- package/dist/core/repo/templates/claude-agents.js +136 -28
- package/dist/core/repo/templates/claude-agents.js.map +1 -1
- package/dist/core/repo/templates/claude-prompts.d.ts +1 -1
- package/dist/core/repo/templates/claude-prompts.d.ts.map +1 -1
- package/dist/core/repo/templates/claude-prompts.js +412 -157
- package/dist/core/repo/templates/claude-prompts.js.map +1 -1
- package/dist/core/repo/templates/claude-scripts.d.ts.map +1 -1
- package/dist/core/repo/templates/claude-scripts.js +555 -94
- package/dist/core/repo/templates/claude-scripts.js.map +1 -1
- package/dist/core/report.d.ts +25 -0
- package/dist/core/report.d.ts.map +1 -0
- package/dist/core/report.js +702 -0
- package/dist/core/report.js.map +1 -0
- package/dist/types/report.d.ts +158 -0
- package/dist/types/report.d.ts.map +1 -0
- package/dist/types/report.js +7 -0
- package/dist/types/report.js.map +1 -0
- 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, '&')
|
|
697
|
+
.replace(/</g, '<')
|
|
698
|
+
.replace(/>/g, '>')
|
|
699
|
+
.replace(/"/g, '"')
|
|
700
|
+
.replace(/'/g, ''');
|
|
701
|
+
}
|
|
702
|
+
//# sourceMappingURL=report.js.map
|