@planu/cli 0.54.2 → 0.56.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.
Files changed (178) hide show
  1. package/dist/config/license-plans.json +1 -0
  2. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts +7 -1
  3. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.d.ts.map +1 -1
  4. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js +16 -3
  5. package/dist/engine/doc-generator/executive-summary/executive-summary-generator.js.map +1 -1
  6. package/dist/engine/doc-generator/executive-summary/html-template.d.ts +5 -2
  7. package/dist/engine/doc-generator/executive-summary/html-template.d.ts.map +1 -1
  8. package/dist/engine/doc-generator/executive-summary/html-template.js +7 -3
  9. package/dist/engine/doc-generator/executive-summary/html-template.js.map +1 -1
  10. package/dist/engine/doc-generator/per-spec-report/executive-report.d.ts +3 -1
  11. package/dist/engine/doc-generator/per-spec-report/executive-report.d.ts.map +1 -1
  12. package/dist/engine/doc-generator/per-spec-report/executive-report.js +22 -2
  13. package/dist/engine/doc-generator/per-spec-report/executive-report.js.map +1 -1
  14. package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts +5 -3
  15. package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts.map +1 -1
  16. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js +27 -6
  17. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js.map +1 -1
  18. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.d.ts +1 -0
  19. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.d.ts.map +1 -1
  20. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.js +29 -0
  21. package/dist/engine/doc-generator/per-spec-report/spec-data-extractor.js.map +1 -1
  22. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.d.ts +8 -1
  23. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.d.ts.map +1 -1
  24. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js +188 -0
  25. package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js.map +1 -1
  26. package/dist/engine/doc-generator/per-spec-report/technical-report.d.ts +3 -1
  27. package/dist/engine/doc-generator/per-spec-report/technical-report.d.ts.map +1 -1
  28. package/dist/engine/doc-generator/per-spec-report/technical-report.js +24 -7
  29. package/dist/engine/doc-generator/per-spec-report/technical-report.js.map +1 -1
  30. package/dist/engine/doc-generator/portal/analytics-page.d.ts +8 -0
  31. package/dist/engine/doc-generator/portal/analytics-page.d.ts.map +1 -0
  32. package/dist/engine/doc-generator/portal/analytics-page.js +265 -0
  33. package/dist/engine/doc-generator/portal/analytics-page.js.map +1 -0
  34. package/dist/engine/doc-generator/portal/architecture-page.d.ts +6 -0
  35. package/dist/engine/doc-generator/portal/architecture-page.d.ts.map +1 -0
  36. package/dist/engine/doc-generator/portal/architecture-page.js +250 -0
  37. package/dist/engine/doc-generator/portal/architecture-page.js.map +1 -0
  38. package/dist/engine/doc-generator/portal/changelog-page.d.ts +6 -0
  39. package/dist/engine/doc-generator/portal/changelog-page.d.ts.map +1 -0
  40. package/dist/engine/doc-generator/portal/changelog-page.js +204 -0
  41. package/dist/engine/doc-generator/portal/changelog-page.js.map +1 -0
  42. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts +11 -0
  43. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts.map +1 -0
  44. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js +24 -0
  45. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js.map +1 -0
  46. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts +9 -0
  47. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts.map +1 -0
  48. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js +31 -0
  49. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js.map +1 -0
  50. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts +10 -0
  51. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts.map +1 -0
  52. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js +77 -0
  53. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js.map +1 -0
  54. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts +9 -0
  55. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts.map +1 -0
  56. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js +31 -0
  57. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js.map +1 -0
  58. package/dist/engine/doc-generator/portal/decision-page.d.ts +6 -0
  59. package/dist/engine/doc-generator/portal/decision-page.d.ts.map +1 -0
  60. package/dist/engine/doc-generator/portal/decision-page.js +169 -0
  61. package/dist/engine/doc-generator/portal/decision-page.js.map +1 -0
  62. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts +8 -0
  63. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts.map +1 -0
  64. package/dist/engine/doc-generator/portal/decision-timeline-builder.js +131 -0
  65. package/dist/engine/doc-generator/portal/decision-timeline-builder.js.map +1 -0
  66. package/dist/engine/doc-generator/portal/index.d.ts +19 -0
  67. package/dist/engine/doc-generator/portal/index.d.ts.map +1 -0
  68. package/dist/engine/doc-generator/portal/index.js +21 -0
  69. package/dist/engine/doc-generator/portal/index.js.map +1 -0
  70. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts +8 -0
  71. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts.map +1 -0
  72. package/dist/engine/doc-generator/portal/kanban-view-builder.js +140 -0
  73. package/dist/engine/doc-generator/portal/kanban-view-builder.js.map +1 -0
  74. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts +27 -0
  75. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts.map +1 -0
  76. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +139 -0
  77. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js.map +1 -0
  78. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts +8 -0
  79. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts.map +1 -0
  80. package/dist/engine/doc-generator/portal/portal-landing-cards.js +177 -0
  81. package/dist/engine/doc-generator/portal/portal-landing-cards.js.map +1 -0
  82. package/dist/engine/doc-generator/portal/portal-navbar.d.ts +15 -0
  83. package/dist/engine/doc-generator/portal/portal-navbar.d.ts.map +1 -0
  84. package/dist/engine/doc-generator/portal/portal-navbar.js +143 -0
  85. package/dist/engine/doc-generator/portal/portal-navbar.js.map +1 -0
  86. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +22 -0
  87. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts.map +1 -0
  88. package/dist/engine/doc-generator/portal/portal-page-detector.js +58 -0
  89. package/dist/engine/doc-generator/portal/portal-page-detector.js.map +1 -0
  90. package/dist/engine/doc-generator/portal/portal-theme.d.ts +7 -0
  91. package/dist/engine/doc-generator/portal/portal-theme.d.ts.map +1 -0
  92. package/dist/engine/doc-generator/portal/portal-theme.js +53 -0
  93. package/dist/engine/doc-generator/portal/portal-theme.js.map +1 -0
  94. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts +5 -0
  95. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts.map +1 -0
  96. package/dist/engine/doc-generator/portal/risk-gauge-svg.js +80 -0
  97. package/dist/engine/doc-generator/portal/risk-gauge-svg.js.map +1 -0
  98. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +11 -0
  99. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts.map +1 -0
  100. package/dist/engine/doc-generator/portal/risk-matrix-svg.js +181 -0
  101. package/dist/engine/doc-generator/portal/risk-matrix-svg.js.map +1 -0
  102. package/dist/engine/doc-generator/portal/risk-page.d.ts +7 -0
  103. package/dist/engine/doc-generator/portal/risk-page.d.ts.map +1 -0
  104. package/dist/engine/doc-generator/portal/risk-page.js +219 -0
  105. package/dist/engine/doc-generator/portal/risk-page.js.map +1 -0
  106. package/dist/engine/doc-generator/portal/roadmap-page.d.ts +7 -0
  107. package/dist/engine/doc-generator/portal/roadmap-page.d.ts.map +1 -0
  108. package/dist/engine/doc-generator/portal/roadmap-page.js +204 -0
  109. package/dist/engine/doc-generator/portal/roadmap-page.js.map +1 -0
  110. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts +4 -0
  111. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts.map +1 -0
  112. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js +50 -0
  113. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js.map +1 -0
  114. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts +16 -0
  115. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts.map +1 -0
  116. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js +45 -0
  117. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js.map +1 -0
  118. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts +16 -0
  119. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts.map +1 -0
  120. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js +159 -0
  121. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js.map +1 -0
  122. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts +7 -0
  123. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts.map +1 -0
  124. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js +52 -0
  125. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js.map +1 -0
  126. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts +8 -0
  127. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts.map +1 -0
  128. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +126 -0
  129. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js.map +1 -0
  130. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +10 -0
  131. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts.map +1 -0
  132. package/dist/engine/doc-generator/proposal/proposal-generator.js +93 -0
  133. package/dist/engine/doc-generator/proposal/proposal-generator.js.map +1 -0
  134. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts +7 -0
  135. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts.map +1 -0
  136. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js +31 -0
  137. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js.map +1 -0
  138. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts +12 -0
  139. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts.map +1 -0
  140. package/dist/engine/doc-generator/proposal/proposal-i18n.js +230 -0
  141. package/dist/engine/doc-generator/proposal/proposal-i18n.js.map +1 -0
  142. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts +8 -0
  143. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts.map +1 -0
  144. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +32 -0
  145. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js.map +1 -0
  146. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts +14 -0
  147. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts.map +1 -0
  148. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +248 -0
  149. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js.map +1 -0
  150. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts +13 -0
  151. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts.map +1 -0
  152. package/dist/engine/doc-generator/proposal/proposal-section-builders.js +423 -0
  153. package/dist/engine/doc-generator/proposal/proposal-section-builders.js.map +1 -0
  154. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts +4 -0
  155. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts.map +1 -0
  156. package/dist/engine/doc-generator/proposal/proposal-themes.js +194 -0
  157. package/dist/engine/doc-generator/proposal/proposal-themes.js.map +1 -0
  158. package/dist/engine/spec-summary-html.d.ts.map +1 -1
  159. package/dist/engine/spec-summary-html.js +17 -5
  160. package/dist/engine/spec-summary-html.js.map +1 -1
  161. package/dist/index.js +2 -0
  162. package/dist/index.js.map +1 -1
  163. package/dist/tools/generate-proposal.d.ts +3 -0
  164. package/dist/tools/generate-proposal.d.ts.map +1 -0
  165. package/dist/tools/generate-proposal.js +166 -0
  166. package/dist/tools/generate-proposal.js.map +1 -0
  167. package/dist/types/docs.d.ts +75 -0
  168. package/dist/types/docs.d.ts.map +1 -1
  169. package/dist/types/portal.d.ts +128 -0
  170. package/dist/types/portal.d.ts.map +1 -0
  171. package/dist/types/portal.js +3 -0
  172. package/dist/types/portal.js.map +1 -0
  173. package/dist/types/proposal.d.ts +57 -0
  174. package/dist/types/proposal.d.ts.map +1 -0
  175. package/dist/types/proposal.js +2 -0
  176. package/dist/types/proposal.js.map +1 -0
  177. package/package.json +1 -1
  178. package/src/config/license-plans.json +1 -0
@@ -0,0 +1,6 @@
1
+ import type { Spec } from '../../../types/spec/core.js';
2
+ /**
3
+ * Generate the changelog.html portal page.
4
+ */
5
+ export declare function generateChangelogPage(specs: Spec[]): string;
6
+ //# sourceMappingURL=changelog-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changelog-page.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/changelog-page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AA+LxD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAuC3D"}
@@ -0,0 +1,204 @@
1
+ import { buildNavbar } from './portal-navbar.js';
2
+ import { getPortalThemeCSS } from './portal-theme.js';
3
+ function escapeHtml(s) {
4
+ return s
5
+ .replace(/&/g, '&')
6
+ .replace(/</g, '&lt;')
7
+ .replace(/>/g, '&gt;')
8
+ .replace(/"/g, '&quot;');
9
+ }
10
+ function formatDate(dateStr) {
11
+ try {
12
+ const d = new Date(dateStr);
13
+ return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
14
+ }
15
+ catch {
16
+ return escapeHtml(dateStr);
17
+ }
18
+ }
19
+ function getYearMonth(dateStr) {
20
+ try {
21
+ const d = new Date(dateStr);
22
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
23
+ }
24
+ catch {
25
+ return 'unknown';
26
+ }
27
+ }
28
+ function accuracyClass(estimated, actual) {
29
+ if (estimated <= 0) {
30
+ return { label: 'N/A', cls: 'planu-badge-muted' };
31
+ }
32
+ const ratio = Math.abs(actual - estimated) / estimated;
33
+ if (ratio <= 0.2) {
34
+ return { label: `±${Math.round(ratio * 100)}%`, cls: 'planu-badge-success' };
35
+ }
36
+ if (ratio <= 0.5) {
37
+ return { label: `±${Math.round(ratio * 100)}%`, cls: 'planu-badge-warning' };
38
+ }
39
+ return { label: `±${Math.round(ratio * 100)}%`, cls: 'planu-badge-danger' };
40
+ }
41
+ function buildSpecCard(spec) {
42
+ const completedAt = spec.actuals?.completedAt ?? '';
43
+ const dateDisplay = completedAt ? formatDate(completedAt) : 'Unknown date';
44
+ const estimatedHours = spec.estimation.devHours + spec.estimation.reviewHours;
45
+ const actualHours = spec.actuals ? spec.actuals.devHours + spec.actuals.reviewHours : 0;
46
+ const { label: accLabel, cls: accCls } = accuracyClass(estimatedHours, actualHours);
47
+ const actualDisplay = spec.actuals !== null
48
+ ? `<span class="hours-actual">${actualHours.toFixed(1)}h actual</span>`
49
+ : '';
50
+ const tagBadges = spec.tags.length > 0
51
+ ? spec.tags
52
+ .slice(0, 3)
53
+ .map((t) => `<span class="tag-chip">${escapeHtml(t)}</span>`)
54
+ .join('')
55
+ : '';
56
+ return `<div class="cl-card planu-card">
57
+ <div class="cl-header">
58
+ <span class="cl-id">${escapeHtml(spec.id)}</span>
59
+ <span class="cl-title">${escapeHtml(spec.title)}</span>
60
+ <span class="planu-badge ${accCls}">${accLabel}</span>
61
+ <span class="cl-date">${dateDisplay}</span>
62
+ </div>
63
+ <div class="cl-meta">
64
+ <span class="hours-est">${estimatedHours.toFixed(1)}h est</span>
65
+ ${actualDisplay}
66
+ ${tagBadges}
67
+ </div>
68
+ </div>`;
69
+ }
70
+ function buildFeed(doneSpecs) {
71
+ if (doneSpecs.length === 0) {
72
+ return '';
73
+ }
74
+ // Group by year-month
75
+ const groups = new Map();
76
+ for (const spec of doneSpecs) {
77
+ const key = spec.actuals?.completedAt ? getYearMonth(spec.actuals.completedAt) : 'unknown';
78
+ const arr = groups.get(key) ?? [];
79
+ arr.push(spec);
80
+ groups.set(key, arr);
81
+ }
82
+ // Sort keys descending
83
+ const sortedKeys = [...groups.keys()].sort((a, b) => b.localeCompare(a));
84
+ return sortedKeys
85
+ .map((key) => {
86
+ const specs = groups.get(key) ?? [];
87
+ const label = key === 'unknown' ? 'Unknown Date' : key;
88
+ const cards = specs.map(buildSpecCard).join('\n');
89
+ return `<div class="month-group">
90
+ <div class="month-header">${escapeHtml(label)}</div>
91
+ ${cards}
92
+ </div>`;
93
+ })
94
+ .join('\n');
95
+ }
96
+ function buildSummarySection(doneSpecs) {
97
+ if (doneSpecs.length === 0) {
98
+ return '';
99
+ }
100
+ const now = new Date();
101
+ const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
102
+ const thisMonth = doneSpecs.filter((s) => s.actuals?.completedAt && getYearMonth(s.actuals.completedAt) === currentMonth);
103
+ const totalHours = doneSpecs.reduce((sum, s) => {
104
+ if (!s.actuals) {
105
+ return sum;
106
+ }
107
+ return sum + s.actuals.devHours + s.actuals.reviewHours;
108
+ }, 0);
109
+ const specsWithActuals = doneSpecs.filter((s) => s.actuals !== null && s.estimation.devHours > 0);
110
+ const avgAccuracy = specsWithActuals.length > 0
111
+ ? specsWithActuals.reduce((sum, s) => {
112
+ const est = s.estimation.devHours + s.estimation.reviewHours;
113
+ const act = (s.actuals?.devHours ?? 0) + (s.actuals?.reviewHours ?? 0);
114
+ return sum + Math.abs(act - est) / est;
115
+ }, 0) / specsWithActuals.length
116
+ : null;
117
+ const accDisplay = avgAccuracy !== null ? `${Math.round(avgAccuracy * 100)}%` : 'N/A';
118
+ return `<section class="planu-card section-card summary-section">
119
+ <h2>Summary</h2>
120
+ <div class="summary-grid">
121
+ <div class="sum-item">
122
+ <span class="sum-num">${doneSpecs.length}</span>
123
+ <span class="sum-lbl">Total Done</span>
124
+ </div>
125
+ <div class="sum-item">
126
+ <span class="sum-num">${thisMonth.length}</span>
127
+ <span class="sum-lbl">This Month</span>
128
+ </div>
129
+ <div class="sum-item">
130
+ <span class="sum-num">${totalHours.toFixed(0)}h</span>
131
+ <span class="sum-lbl">Total Hours</span>
132
+ </div>
133
+ <div class="sum-item">
134
+ <span class="sum-num">${accDisplay}</span>
135
+ <span class="sum-lbl">Avg Deviation</span>
136
+ </div>
137
+ </div>
138
+ </section>`;
139
+ }
140
+ function buildPageCSS() {
141
+ return `
142
+ body { font-family: system-ui, sans-serif; margin: 0; padding: 0 16px 48px; background: var(--planu-bg); color: var(--planu-text); }
143
+ .page-title { font-size: 1.6rem; font-weight: 700; margin: 0 0 6px; }
144
+ .page-subtitle { color: #6b7280; margin: 0 0 24px; font-size: 0.95rem; }
145
+ .section-card { margin-bottom: 24px; }
146
+ h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 16px; color: var(--planu-primary); }
147
+ .summary-grid { display: flex; gap: 16px; flex-wrap: wrap; }
148
+ .sum-item { text-align: center; padding: 12px 20px; background: #f9fafb; border-radius: 8px; min-width: 80px; }
149
+ .sum-num { display: block; font-size: 1.8rem; font-weight: 700; color: var(--planu-primary); }
150
+ .sum-lbl { font-size: 0.78rem; color: #6b7280; }
151
+ .month-group { margin-bottom: 24px; }
152
+ .month-header { font-size: 0.85rem; font-weight: 700; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 10px; padding-left: 4px; }
153
+ .cl-card { margin-bottom: 10px; padding: 12px 14px; }
154
+ .cl-header { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
155
+ .cl-id { font-weight: 700; color: #6b7280; font-size: 0.82rem; }
156
+ .cl-title { font-weight: 600; flex: 1; }
157
+ .cl-date { font-size: 0.78rem; color: #9ca3af; margin-left: auto; }
158
+ .cl-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.78rem; }
159
+ .hours-est { color: #6b7280; }
160
+ .hours-actual { color: var(--planu-primary); font-weight: 600; }
161
+ .tag-chip { padding: 2px 6px; border-radius: 4px; background: #f3f4f6; color: #374151; }
162
+ .empty-state { text-align: center; padding: 64px 24px; color: #6b7280; }
163
+ .empty-state h3 { font-size: 1.2rem; }`;
164
+ }
165
+ /**
166
+ * Generate the changelog.html portal page.
167
+ */
168
+ export function generateChangelogPage(specs) {
169
+ const doneSpecs = specs
170
+ .filter((s) => s.status === 'done')
171
+ .sort((a, b) => {
172
+ const aDate = a.actuals?.completedAt ?? '';
173
+ const bDate = b.actuals?.completedAt ?? '';
174
+ return bDate.localeCompare(aDate);
175
+ });
176
+ const navbar = buildNavbar('changelog', '');
177
+ const themeCSS = getPortalThemeCSS();
178
+ const pageCSS = buildPageCSS();
179
+ const bodyContent = doneSpecs.length === 0
180
+ ? `<div class="empty-state"><h3>No completed specs yet.</h3><p>Specs marked as <code>done</code> will appear here.</p></div>`
181
+ : `${buildSummarySection(doneSpecs)}
182
+ <section class="section-card">
183
+ <h2>Completion Feed</h2>
184
+ ${buildFeed(doneSpecs)}
185
+ </section>`;
186
+ return `<!DOCTYPE html>
187
+ <html lang="en">
188
+ <head>
189
+ <meta charset="UTF-8">
190
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
191
+ <title>Changelog — Planu Portal</title>
192
+ <style>${themeCSS}${pageCSS}</style>
193
+ </head>
194
+ <body>
195
+ ${navbar}
196
+ <main>
197
+ <h1 class="page-title">📝 Changelog</h1>
198
+ <p class="page-subtitle">Completed specs in reverse chronological order with estimation accuracy.</p>
199
+ ${bodyContent}
200
+ </main>
201
+ </body>
202
+ </html>`;
203
+ }
204
+ //# sourceMappingURL=changelog-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changelog-page.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/changelog-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Spec } from '../../../../types/index.js';
2
+ import type { AccuracyRecord } from '../../../../types/portal.js';
3
+ /**
4
+ * Compare estimation.devHours vs actuals.devHours for every done spec
5
+ * that has recorded actuals.
6
+ *
7
+ * accuracy = 100 - |estimated - actual| / estimated * 100
8
+ * Clamped to [0, 100].
9
+ */
10
+ export declare function computeAccuracy(specs: Spec[]): AccuracyRecord[];
11
+ //# sourceMappingURL=accuracy-aggregator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accuracy-aggregator.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAmB/D"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Compare estimation.devHours vs actuals.devHours for every done spec
3
+ * that has recorded actuals.
4
+ *
5
+ * accuracy = 100 - |estimated - actual| / estimated * 100
6
+ * Clamped to [0, 100].
7
+ */
8
+ export function computeAccuracy(specs) {
9
+ const records = [];
10
+ for (const spec of specs) {
11
+ if (spec.status !== 'done' || !spec.actuals) {
12
+ continue;
13
+ }
14
+ const estimated = spec.estimation.devHours;
15
+ const actual = spec.actuals.devHours;
16
+ if (estimated <= 0) {
17
+ continue;
18
+ }
19
+ const accuracy = Math.max(0, 100 - (Math.abs(estimated - actual) / estimated) * 100);
20
+ records.push({ specId: spec.id, estimated, actual, accuracy: Math.round(accuracy * 10) / 10 });
21
+ }
22
+ return records;
23
+ }
24
+ //# sourceMappingURL=accuracy-aggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accuracy-aggregator.js","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5C,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;QACrC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACjG,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Spec } from '../../../../types/index.js';
2
+ import type { BottleneckSpec } from '../../../../types/portal.js';
3
+ /**
4
+ * Find specs that have been in the same non-done status for more than 7 days.
5
+ * Uses updatedAt as the reference timestamp.
6
+ * Returns results sorted by staleDays descending (worst first).
7
+ */
8
+ export declare function detectBottlenecks(specs: Spec[]): BottleneckSpec[];
9
+ //# sourceMappingURL=bottleneck-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bottleneck-detector.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/bottleneck-detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAKlE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CA0BjE"}
@@ -0,0 +1,31 @@
1
+ const STALE_THRESHOLD_DAYS = 7;
2
+ const MS_PER_DAY = 86_400_000;
3
+ /**
4
+ * Find specs that have been in the same non-done status for more than 7 days.
5
+ * Uses updatedAt as the reference timestamp.
6
+ * Returns results sorted by staleDays descending (worst first).
7
+ */
8
+ export function detectBottlenecks(specs) {
9
+ const now = Date.now();
10
+ const bottlenecks = [];
11
+ for (const spec of specs) {
12
+ if (spec.status === 'done') {
13
+ continue;
14
+ }
15
+ const updatedMs = new Date(spec.updatedAt).getTime();
16
+ if (isNaN(updatedMs)) {
17
+ continue;
18
+ }
19
+ const staleDays = Math.floor((now - updatedMs) / MS_PER_DAY);
20
+ if (staleDays >= STALE_THRESHOLD_DAYS) {
21
+ bottlenecks.push({
22
+ specId: spec.id,
23
+ title: spec.title,
24
+ status: spec.status,
25
+ staleDays,
26
+ });
27
+ }
28
+ }
29
+ return bottlenecks.sort((a, b) => b.staleDays - a.staleDays);
30
+ }
31
+ //# sourceMappingURL=bottleneck-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bottleneck-detector.js","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/bottleneck-detector.ts"],"names":[],"mappings":"AAIA,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,WAAW,GAAqB,EAAE,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;QACrD,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YACrB,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC,CAAC;QAC7D,IAAI,SAAS,IAAI,oBAAoB,EAAE,CAAC;YACtC,WAAW,CAAC,IAAI,CAAC;gBACf,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,SAAS;aACV,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Spec } from '../../../../types/index.js';
2
+ import type { BurndownPoint } from '../../../../types/portal.js';
3
+ /**
4
+ * Compute a sprint-level burndown from specs.
5
+ * Uses createdAt to determine when work started and completedAt for done dates.
6
+ * Returns daily data points sorted by date.
7
+ * Returns empty array when no specs have dates.
8
+ */
9
+ export declare function computeBurndown(specs: Spec[]): BurndownPoint[];
10
+ //# sourceMappingURL=burndown-aggregator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burndown-aggregator.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/burndown-aggregator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,CA0D9D"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Compute a sprint-level burndown from specs.
3
+ * Uses createdAt to determine when work started and completedAt for done dates.
4
+ * Returns daily data points sorted by date.
5
+ * Returns empty array when no specs have dates.
6
+ */
7
+ export function computeBurndown(specs) {
8
+ if (specs.length === 0) {
9
+ return [];
10
+ }
11
+ const datedSpecs = specs.filter((s) => Boolean(s.createdAt));
12
+ if (datedSpecs.length === 0) {
13
+ return [];
14
+ }
15
+ const startMs = Math.min(...datedSpecs.map((s) => toMs(s.createdAt)));
16
+ const endMs = computeEndMs(datedSpecs, startMs);
17
+ if (endMs <= startMs) {
18
+ return [];
19
+ }
20
+ const total = specs.length;
21
+ const spanDays = Math.round((endMs - startMs) / MS_PER_DAY);
22
+ if (spanDays === 0) {
23
+ return [];
24
+ }
25
+ // Build completion map: date string -> cumulative done count
26
+ const doneByDate = new Map();
27
+ for (const spec of specs) {
28
+ if (spec.status === 'done' && spec.actuals?.completedAt) {
29
+ const dateStr = toDateStr(spec.actuals.completedAt);
30
+ if (dateStr) {
31
+ doneByDate.set(dateStr, (doneByDate.get(dateStr) ?? 0) + 1);
32
+ }
33
+ }
34
+ }
35
+ const points = [];
36
+ let cumulativeDone = 0;
37
+ const stepDays = Math.max(1, Math.ceil(spanDays / 20)); // max 20 points
38
+ for (let day = 0; day <= spanDays; day += stepDays) {
39
+ const currentMs = startMs + day * MS_PER_DAY;
40
+ const dateStr = toDateStr(new Date(currentMs).toISOString());
41
+ if (!dateStr) {
42
+ continue;
43
+ }
44
+ // Accumulate done specs up to current date
45
+ for (const [dDate, cnt] of doneByDate) {
46
+ if (dDate <= dateStr) {
47
+ cumulativeDone += cnt;
48
+ doneByDate.delete(dDate); // prevent double-counting
49
+ }
50
+ }
51
+ const remaining = Math.max(0, total - cumulativeDone);
52
+ const ideal = Math.round(total * (1 - day / spanDays));
53
+ points.push({ date: dateStr, remaining, ideal });
54
+ }
55
+ return points;
56
+ }
57
+ const MS_PER_DAY = 86_400_000;
58
+ function toMs(dateStr) {
59
+ const d = new Date(dateStr);
60
+ return isNaN(d.getTime()) ? 0 : d.getTime();
61
+ }
62
+ function toDateStr(isoStr) {
63
+ const d = new Date(isoStr);
64
+ if (isNaN(d.getTime())) {
65
+ return null;
66
+ }
67
+ return d.toISOString().slice(0, 10);
68
+ }
69
+ function computeEndMs(specs, startMs) {
70
+ const completedSpecs = specs.filter((s) => Boolean(s.actuals?.completedAt));
71
+ if (completedSpecs.length > 0) {
72
+ return Math.max(...completedSpecs.map((s) => toMs(s.actuals?.completedAt ?? '')));
73
+ }
74
+ // Default: start + 4 weeks
75
+ return startMs + 28 * MS_PER_DAY;
76
+ }
77
+ //# sourceMappingURL=burndown-aggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"burndown-aggregator.js","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/burndown-aggregator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Spec } from '../../../../types/index.js';
2
+ import type { VelocityPeriod } from '../../../../types/portal.js';
3
+ /**
4
+ * Group done specs by the month of their completedAt date.
5
+ * Returns periods sorted chronologically.
6
+ * Specs without actuals or completedAt are ignored.
7
+ */
8
+ export declare function computeVelocity(specs: Spec[]): VelocityPeriod[];
9
+ //# sourceMappingURL=velocity-aggregator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"velocity-aggregator.d.ts","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/velocity-aggregator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAiB/D"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Group done specs by the month of their completedAt date.
3
+ * Returns periods sorted chronologically.
4
+ * Specs without actuals or completedAt are ignored.
5
+ */
6
+ export function computeVelocity(specs) {
7
+ const counts = new Map();
8
+ for (const spec of specs) {
9
+ if (spec.status !== 'done' || !spec.actuals?.completedAt) {
10
+ continue;
11
+ }
12
+ const period = toYearMonth(spec.actuals.completedAt);
13
+ if (!period) {
14
+ continue;
15
+ }
16
+ counts.set(period, (counts.get(period) ?? 0) + 1);
17
+ }
18
+ return Array.from(counts.entries())
19
+ .sort(([a], [b]) => a.localeCompare(b))
20
+ .map(([period, count]) => ({ period, count }));
21
+ }
22
+ function toYearMonth(dateStr) {
23
+ const d = new Date(dateStr);
24
+ if (isNaN(d.getTime())) {
25
+ return null;
26
+ }
27
+ const y = d.getUTCFullYear();
28
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
29
+ return `${y}-${m}`;
30
+ }
31
+ //# sourceMappingURL=velocity-aggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"velocity-aggregator.js","sourceRoot":"","sources":["../../../../../src/engine/doc-generator/portal/data-aggregators/velocity-aggregator.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;YACzD,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,SAAS;QACX,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;SAChC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Decision } from '../../../types/decisions.js';
2
+ /**
3
+ * Generate the decisions.html portal page.
4
+ */
5
+ export declare function generateDecisionPage(decisions: Decision[]): string;
6
+ //# sourceMappingURL=decision-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-page.d.ts","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/decision-page.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,6BAA6B,CAAC;AAgK9E;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAgClE"}