@planu/cli 0.55.0 → 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 (162) 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/html-wrapper.d.ts +5 -3
  11. package/dist/engine/doc-generator/per-spec-report/html-wrapper.d.ts.map +1 -1
  12. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js +27 -6
  13. package/dist/engine/doc-generator/per-spec-report/html-wrapper.js.map +1 -1
  14. package/dist/engine/doc-generator/portal/analytics-page.d.ts +8 -0
  15. package/dist/engine/doc-generator/portal/analytics-page.d.ts.map +1 -0
  16. package/dist/engine/doc-generator/portal/analytics-page.js +265 -0
  17. package/dist/engine/doc-generator/portal/analytics-page.js.map +1 -0
  18. package/dist/engine/doc-generator/portal/architecture-page.d.ts +6 -0
  19. package/dist/engine/doc-generator/portal/architecture-page.d.ts.map +1 -0
  20. package/dist/engine/doc-generator/portal/architecture-page.js +250 -0
  21. package/dist/engine/doc-generator/portal/architecture-page.js.map +1 -0
  22. package/dist/engine/doc-generator/portal/changelog-page.d.ts +6 -0
  23. package/dist/engine/doc-generator/portal/changelog-page.d.ts.map +1 -0
  24. package/dist/engine/doc-generator/portal/changelog-page.js +204 -0
  25. package/dist/engine/doc-generator/portal/changelog-page.js.map +1 -0
  26. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts +11 -0
  27. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.d.ts.map +1 -0
  28. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js +24 -0
  29. package/dist/engine/doc-generator/portal/data-aggregators/accuracy-aggregator.js.map +1 -0
  30. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts +9 -0
  31. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.d.ts.map +1 -0
  32. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js +31 -0
  33. package/dist/engine/doc-generator/portal/data-aggregators/bottleneck-detector.js.map +1 -0
  34. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts +10 -0
  35. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.d.ts.map +1 -0
  36. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js +77 -0
  37. package/dist/engine/doc-generator/portal/data-aggregators/burndown-aggregator.js.map +1 -0
  38. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts +9 -0
  39. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.d.ts.map +1 -0
  40. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js +31 -0
  41. package/dist/engine/doc-generator/portal/data-aggregators/velocity-aggregator.js.map +1 -0
  42. package/dist/engine/doc-generator/portal/decision-page.d.ts +6 -0
  43. package/dist/engine/doc-generator/portal/decision-page.d.ts.map +1 -0
  44. package/dist/engine/doc-generator/portal/decision-page.js +169 -0
  45. package/dist/engine/doc-generator/portal/decision-page.js.map +1 -0
  46. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts +8 -0
  47. package/dist/engine/doc-generator/portal/decision-timeline-builder.d.ts.map +1 -0
  48. package/dist/engine/doc-generator/portal/decision-timeline-builder.js +131 -0
  49. package/dist/engine/doc-generator/portal/decision-timeline-builder.js.map +1 -0
  50. package/dist/engine/doc-generator/portal/index.d.ts +19 -0
  51. package/dist/engine/doc-generator/portal/index.d.ts.map +1 -0
  52. package/dist/engine/doc-generator/portal/index.js +21 -0
  53. package/dist/engine/doc-generator/portal/index.js.map +1 -0
  54. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts +8 -0
  55. package/dist/engine/doc-generator/portal/kanban-view-builder.d.ts.map +1 -0
  56. package/dist/engine/doc-generator/portal/kanban-view-builder.js +140 -0
  57. package/dist/engine/doc-generator/portal/kanban-view-builder.js.map +1 -0
  58. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts +27 -0
  59. package/dist/engine/doc-generator/portal/portal-breadcrumbs.d.ts.map +1 -0
  60. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +139 -0
  61. package/dist/engine/doc-generator/portal/portal-breadcrumbs.js.map +1 -0
  62. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts +8 -0
  63. package/dist/engine/doc-generator/portal/portal-landing-cards.d.ts.map +1 -0
  64. package/dist/engine/doc-generator/portal/portal-landing-cards.js +177 -0
  65. package/dist/engine/doc-generator/portal/portal-landing-cards.js.map +1 -0
  66. package/dist/engine/doc-generator/portal/portal-navbar.d.ts +15 -0
  67. package/dist/engine/doc-generator/portal/portal-navbar.d.ts.map +1 -0
  68. package/dist/engine/doc-generator/portal/portal-navbar.js +143 -0
  69. package/dist/engine/doc-generator/portal/portal-navbar.js.map +1 -0
  70. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +22 -0
  71. package/dist/engine/doc-generator/portal/portal-page-detector.d.ts.map +1 -0
  72. package/dist/engine/doc-generator/portal/portal-page-detector.js +58 -0
  73. package/dist/engine/doc-generator/portal/portal-page-detector.js.map +1 -0
  74. package/dist/engine/doc-generator/portal/portal-theme.d.ts +7 -0
  75. package/dist/engine/doc-generator/portal/portal-theme.d.ts.map +1 -0
  76. package/dist/engine/doc-generator/portal/portal-theme.js +53 -0
  77. package/dist/engine/doc-generator/portal/portal-theme.js.map +1 -0
  78. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts +5 -0
  79. package/dist/engine/doc-generator/portal/risk-gauge-svg.d.ts.map +1 -0
  80. package/dist/engine/doc-generator/portal/risk-gauge-svg.js +80 -0
  81. package/dist/engine/doc-generator/portal/risk-gauge-svg.js.map +1 -0
  82. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts +11 -0
  83. package/dist/engine/doc-generator/portal/risk-matrix-svg.d.ts.map +1 -0
  84. package/dist/engine/doc-generator/portal/risk-matrix-svg.js +181 -0
  85. package/dist/engine/doc-generator/portal/risk-matrix-svg.js.map +1 -0
  86. package/dist/engine/doc-generator/portal/risk-page.d.ts +7 -0
  87. package/dist/engine/doc-generator/portal/risk-page.d.ts.map +1 -0
  88. package/dist/engine/doc-generator/portal/risk-page.js +219 -0
  89. package/dist/engine/doc-generator/portal/risk-page.js.map +1 -0
  90. package/dist/engine/doc-generator/portal/roadmap-page.d.ts +7 -0
  91. package/dist/engine/doc-generator/portal/roadmap-page.d.ts.map +1 -0
  92. package/dist/engine/doc-generator/portal/roadmap-page.js +204 -0
  93. package/dist/engine/doc-generator/portal/roadmap-page.js.map +1 -0
  94. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts +4 -0
  95. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.d.ts.map +1 -0
  96. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js +50 -0
  97. package/dist/engine/doc-generator/portal/svg-charts/bar-chart.js.map +1 -0
  98. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts +16 -0
  99. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.d.ts.map +1 -0
  100. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js +45 -0
  101. package/dist/engine/doc-generator/portal/svg-charts/chart-utils.js.map +1 -0
  102. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts +16 -0
  103. package/dist/engine/doc-generator/portal/svg-charts/line-chart.d.ts.map +1 -0
  104. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js +159 -0
  105. package/dist/engine/doc-generator/portal/svg-charts/line-chart.js.map +1 -0
  106. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts +7 -0
  107. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.d.ts.map +1 -0
  108. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js +52 -0
  109. package/dist/engine/doc-generator/portal/svg-charts/pie-chart.js.map +1 -0
  110. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts +8 -0
  111. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.d.ts.map +1 -0
  112. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +126 -0
  113. package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js.map +1 -0
  114. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts +10 -0
  115. package/dist/engine/doc-generator/proposal/proposal-generator.d.ts.map +1 -0
  116. package/dist/engine/doc-generator/proposal/proposal-generator.js +93 -0
  117. package/dist/engine/doc-generator/proposal/proposal-generator.js.map +1 -0
  118. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts +7 -0
  119. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.d.ts.map +1 -0
  120. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js +31 -0
  121. package/dist/engine/doc-generator/proposal/proposal-html-wrapper.js.map +1 -0
  122. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts +12 -0
  123. package/dist/engine/doc-generator/proposal/proposal-i18n.d.ts.map +1 -0
  124. package/dist/engine/doc-generator/proposal/proposal-i18n.js +230 -0
  125. package/dist/engine/doc-generator/proposal/proposal-i18n.js.map +1 -0
  126. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts +8 -0
  127. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.d.ts.map +1 -0
  128. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +32 -0
  129. package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js.map +1 -0
  130. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts +14 -0
  131. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.d.ts.map +1 -0
  132. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +248 -0
  133. package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js.map +1 -0
  134. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts +13 -0
  135. package/dist/engine/doc-generator/proposal/proposal-section-builders.d.ts.map +1 -0
  136. package/dist/engine/doc-generator/proposal/proposal-section-builders.js +423 -0
  137. package/dist/engine/doc-generator/proposal/proposal-section-builders.js.map +1 -0
  138. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts +4 -0
  139. package/dist/engine/doc-generator/proposal/proposal-themes.d.ts.map +1 -0
  140. package/dist/engine/doc-generator/proposal/proposal-themes.js +194 -0
  141. package/dist/engine/doc-generator/proposal/proposal-themes.js.map +1 -0
  142. package/dist/engine/spec-summary-html.d.ts.map +1 -1
  143. package/dist/engine/spec-summary-html.js +17 -5
  144. package/dist/engine/spec-summary-html.js.map +1 -1
  145. package/dist/index.js +2 -0
  146. package/dist/index.js.map +1 -1
  147. package/dist/tools/generate-proposal.d.ts +3 -0
  148. package/dist/tools/generate-proposal.d.ts.map +1 -0
  149. package/dist/tools/generate-proposal.js +166 -0
  150. package/dist/tools/generate-proposal.js.map +1 -0
  151. package/dist/types/docs.d.ts +41 -0
  152. package/dist/types/docs.d.ts.map +1 -1
  153. package/dist/types/portal.d.ts +128 -0
  154. package/dist/types/portal.d.ts.map +1 -0
  155. package/dist/types/portal.js +3 -0
  156. package/dist/types/portal.js.map +1 -0
  157. package/dist/types/proposal.d.ts +57 -0
  158. package/dist/types/proposal.d.ts.map +1 -0
  159. package/dist/types/proposal.js +2 -0
  160. package/dist/types/proposal.js.map +1 -0
  161. package/package.json +1 -1
  162. package/src/config/license-plans.json +1 -0
@@ -0,0 +1,250 @@
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
+ const TECH_ICONS = {
11
+ typescript: '🔷',
12
+ javascript: '🟡',
13
+ python: '🐍',
14
+ rust: '🦀',
15
+ go: '🐹',
16
+ java: '☕',
17
+ ruby: '💎',
18
+ php: '🐘',
19
+ swift: '🍎',
20
+ kotlin: '🟣',
21
+ 'c#': '🔵',
22
+ elixir: '💧',
23
+ next: '▲',
24
+ nextjs: '▲',
25
+ react: '⚛️',
26
+ vue: '💚',
27
+ angular: '🔴',
28
+ svelte: '🔥',
29
+ express: '🚂',
30
+ fastapi: '⚡',
31
+ django: '🟢',
32
+ rails: '💎',
33
+ laravel: '🔴',
34
+ spring: '🌿',
35
+ postgres: '🐘',
36
+ postgresql: '🐘',
37
+ mysql: '🐬',
38
+ mongodb: '🍃',
39
+ redis: '🔴',
40
+ sqlite: '📦',
41
+ prisma: '⬟',
42
+ drizzle: '💧',
43
+ pnpm: '📦',
44
+ npm: '📦',
45
+ yarn: '🧶',
46
+ bun: '🐰',
47
+ vite: '⚡',
48
+ webpack: '📦',
49
+ esbuild: '⚡',
50
+ docker: '🐋',
51
+ kubernetes: '☸️',
52
+ node: '🟢',
53
+ };
54
+ function techIcon(name) {
55
+ const key = name.toLowerCase();
56
+ return TECH_ICONS[key] ?? '🔧';
57
+ }
58
+ function buildStackCards(knowledge) {
59
+ const entries = [
60
+ { key: 'language', label: 'Language', value: knowledge.language },
61
+ { key: 'framework', label: 'Framework', value: knowledge.framework },
62
+ { key: 'database', label: 'Database', value: knowledge.database },
63
+ { key: 'orm', label: 'ORM', value: knowledge.orm },
64
+ { key: 'packageManager', label: 'Package Manager', value: knowledge.packageManager },
65
+ ];
66
+ const buildToolName = knowledge.buildTool?.buildTool ?? null;
67
+ if (buildToolName && buildToolName !== 'unknown') {
68
+ entries.push({ key: 'buildTool', label: 'Build Tool', value: buildToolName });
69
+ }
70
+ const cards = entries
71
+ .filter((e) => e.value !== null && e.value !== 'none' && e.value !== 'null')
72
+ .map((e) => {
73
+ const icon = techIcon(e.value);
74
+ return `<div class="stack-card planu-card">
75
+ <div class="stack-icon">${icon}</div>
76
+ <div class="stack-info">
77
+ <div class="stack-label">${escapeHtml(e.label)}</div>
78
+ <div class="stack-value">${escapeHtml(e.value)}</div>
79
+ </div>
80
+ </div>`;
81
+ })
82
+ .join('\n');
83
+ if (!cards) {
84
+ return '<p class="empty-message">No stack information detected.</p>';
85
+ }
86
+ return `<div class="stack-grid">${cards}</div>`;
87
+ }
88
+ function buildLayersSection(knowledge) {
89
+ const layers = knowledge.architecture.layers;
90
+ if (layers.length === 0) {
91
+ return '';
92
+ }
93
+ const boxes = layers
94
+ .map((layer) => `<div class="layer-box">
95
+ <div class="layer-name">${escapeHtml(layer.name)}</div>
96
+ <div class="layer-resp">${escapeHtml(layer.responsibility)}</div>
97
+ ${layer.directories.length > 0 ? `<div class="layer-dirs">${layer.directories.map((d) => `<code>${escapeHtml(d)}</code>`).join(' ')}</div>` : ''}
98
+ </div>`)
99
+ .join('\n <div class="layer-arrow">↓</div>\n ');
100
+ return `<section class="planu-card section-card">
101
+ <h2>Architecture Layers</h2>
102
+ <div class="layers-stack">
103
+ ${boxes}
104
+ </div>
105
+ </section>`;
106
+ }
107
+ function appTypeIcon(type) {
108
+ const icons = {
109
+ frontend: '🖥️',
110
+ backend: '⚙️',
111
+ mobile: '📱',
112
+ ios: '🍎',
113
+ android: '🤖',
114
+ desktop: '💻',
115
+ shared: '📦',
116
+ worker: '⚡',
117
+ docs: '📄',
118
+ };
119
+ return icons[type] ?? '📦';
120
+ }
121
+ function buildAppsSection(knowledge) {
122
+ const { apps } = knowledge;
123
+ if (apps.length === 0) {
124
+ return '';
125
+ }
126
+ const cards = apps
127
+ .map((app) => `<div class="app-card planu-card">
128
+ <div class="app-header">
129
+ <span class="app-icon">${appTypeIcon(app.type)}</span>
130
+ <span class="app-name">${escapeHtml(app.name)}</span>
131
+ <span class="app-type-badge">${escapeHtml(app.type)}</span>
132
+ </div>
133
+ <div class="app-details">
134
+ <span>${escapeHtml(app.language)}</span>
135
+ ${app.framework ? `<span class="sep">·</span><span>${escapeHtml(app.framework)}</span>` : ''}
136
+ ${app.database ? `<span class="sep">·</span><span>DB: ${escapeHtml(app.database)}</span>` : ''}
137
+ </div>
138
+ ${app.path ? `<div class="app-path"><code>${escapeHtml(app.path)}</code></div>` : ''}
139
+ </div>`)
140
+ .join('\n');
141
+ return `<section class="planu-card section-card">
142
+ <h2>Applications</h2>
143
+ <div class="apps-grid">${cards}</div>
144
+ </section>`;
145
+ }
146
+ function buildConventionsSection(knowledge) {
147
+ const entries = Object.entries(knowledge.conventions);
148
+ if (entries.length === 0) {
149
+ return '';
150
+ }
151
+ const rows = entries
152
+ .map(([k, v]) => `<tr>
153
+ <td class="conv-key">${escapeHtml(k)}</td>
154
+ <td>${escapeHtml(v)}</td>
155
+ </tr>`)
156
+ .join('\n');
157
+ return `<section class="planu-card section-card">
158
+ <h2>Conventions</h2>
159
+ <table class="simple-table">
160
+ <thead><tr><th>Convention</th><th>Value</th></tr></thead>
161
+ <tbody>${rows}</tbody>
162
+ </table>
163
+ </section>`;
164
+ }
165
+ function buildExternalServicesSection(knowledge) {
166
+ const services = knowledge.externalServices;
167
+ if (services.length === 0) {
168
+ return '';
169
+ }
170
+ const items = services.map((s) => `<li class="service-item">🔌 ${escapeHtml(s)}</li>`).join('\n');
171
+ return `<section class="planu-card section-card">
172
+ <h2>External Services</h2>
173
+ <ul class="services-list">${items}</ul>
174
+ </section>`;
175
+ }
176
+ function buildPageCSS() {
177
+ return `
178
+ body { font-family: system-ui, sans-serif; margin: 0; padding: 0 16px 48px; background: var(--planu-bg); color: var(--planu-text); }
179
+ .page-title { font-size: 1.6rem; font-weight: 700; margin: 0 0 6px; }
180
+ .page-subtitle { color: #6b7280; margin: 0 0 24px; font-size: 0.95rem; }
181
+ .section-card { margin-bottom: 24px; }
182
+ h2 { font-size: 1.1rem; font-weight: 600; margin: 0 0 16px; color: var(--planu-primary); }
183
+ .stack-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }
184
+ .stack-card { display: flex; align-items: center; gap: 10px; padding: 12px; }
185
+ .stack-icon { font-size: 1.8rem; }
186
+ .stack-label { font-size: 0.75rem; color: #6b7280; }
187
+ .stack-value { font-weight: 600; font-size: 0.9rem; }
188
+ .layers-stack { display: flex; flex-direction: column; align-items: center; gap: 0; }
189
+ .layer-box { width: 100%; max-width: 560px; padding: 12px 16px; background: white; border: 1px solid var(--planu-border); border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
190
+ .layer-name { font-weight: 700; color: var(--planu-primary); margin-bottom: 2px; }
191
+ .layer-resp { font-size: 0.84rem; color: #4b5563; }
192
+ .layer-dirs { margin-top: 6px; font-size: 0.78rem; color: #6b7280; }
193
+ .layer-dirs code { background: #f3f4f6; padding: 1px 4px; border-radius: 3px; margin-right: 4px; }
194
+ .layer-arrow { font-size: 1.2rem; color: #9ca3af; }
195
+ .apps-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
196
+ .app-card { padding: 12px 14px; }
197
+ .app-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
198
+ .app-icon { font-size: 1.4rem; }
199
+ .app-name { font-weight: 700; }
200
+ .app-type-badge { font-size: 0.75rem; padding: 2px 6px; border-radius: 4px; background: #f3f4f6; color: #374151; }
201
+ .app-details { font-size: 0.82rem; color: #6b7280; }
202
+ .sep { margin: 0 4px; color: #d1d5db; }
203
+ .app-path { margin-top: 6px; font-size: 0.75rem; color: #9ca3af; }
204
+ .app-path code { background: #f9fafb; padding: 1px 4px; border-radius: 3px; }
205
+ .simple-table { width: 100%; border-collapse: collapse; font-size: 0.83rem; }
206
+ .simple-table th { background: #f3f4f6; padding: 8px 10px; text-align: left; border-bottom: 2px solid var(--planu-border); }
207
+ .simple-table td { padding: 8px 10px; border-bottom: 1px solid var(--planu-border); }
208
+ .conv-key { font-weight: 600; color: var(--planu-primary); }
209
+ .services-list { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; }
210
+ .service-item { padding: 8px 12px; background: #f9fafb; border-radius: 6px; font-size: 0.86rem; }
211
+ .empty-state { text-align: center; padding: 64px 24px; color: #6b7280; }
212
+ .empty-message { color: #9ca3af; font-style: italic; }`;
213
+ }
214
+ /**
215
+ * Generate the architecture.html portal page.
216
+ */
217
+ export function generateArchitecturePage(knowledge) {
218
+ const navbar = buildNavbar('architecture', '');
219
+ const themeCSS = getPortalThemeCSS();
220
+ const pageCSS = buildPageCSS();
221
+ const hasContent = knowledge.language !== '' || knowledge.framework !== null || knowledge.apps.length > 0;
222
+ const bodyContent = hasContent
223
+ ? `<section class="planu-card section-card">
224
+ <h2>Tech Stack</h2>
225
+ ${buildStackCards(knowledge)}
226
+ </section>
227
+ ${buildLayersSection(knowledge)}
228
+ ${buildAppsSection(knowledge)}
229
+ ${buildConventionsSection(knowledge)}
230
+ ${buildExternalServicesSection(knowledge)}`
231
+ : `<div class="empty-state"><h3>No architecture data available</h3><p>Run <code>scan_project</code> to populate this page.</p></div>`;
232
+ return `<!DOCTYPE html>
233
+ <html lang="en">
234
+ <head>
235
+ <meta charset="UTF-8">
236
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
237
+ <title>Architecture — Planu Portal</title>
238
+ <style>${themeCSS}${pageCSS}</style>
239
+ </head>
240
+ <body>
241
+ ${navbar}
242
+ <main>
243
+ <h1 class="page-title">🔧 Architecture</h1>
244
+ <p class="page-subtitle">Tech stack, layers, apps, and conventions for this project.</p>
245
+ ${bodyContent}
246
+ </main>
247
+ </body>
248
+ </html>`;
249
+ }
250
+ //# sourceMappingURL=architecture-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"architecture-page.js","sourceRoot":"","sources":["../../../../src/engine/doc-generator/portal/architecture-page.ts"],"names":[],"mappings":"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"}
@@ -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, '&amp;')
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"}