@hongmaple0820/scale-engine 0.12.2 → 0.13.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 (116) hide show
  1. package/README.md +30 -2
  2. package/dist/adapters/ClaudeCodeAdapter.d.ts +1 -0
  3. package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
  4. package/dist/adapters/KiroAdapter.d.ts +14 -0
  5. package/dist/adapters/KiroAdapter.js +180 -0
  6. package/dist/adapters/KiroAdapter.js.map +1 -0
  7. package/dist/adapters/index.d.ts +1 -0
  8. package/dist/adapters/index.js +3 -0
  9. package/dist/adapters/index.js.map +1 -1
  10. package/dist/api/cli.js +396 -5
  11. package/dist/api/cli.js.map +1 -1
  12. package/dist/api/doctor.d.ts +12 -0
  13. package/dist/api/doctor.js +232 -5
  14. package/dist/api/doctor.js.map +1 -1
  15. package/dist/api/quickstart.d.ts +19 -1
  16. package/dist/api/quickstart.js +103 -2
  17. package/dist/api/quickstart.js.map +1 -1
  18. package/dist/artifact/types.d.ts +16 -2
  19. package/dist/artifact/types.js.map +1 -1
  20. package/dist/cli/phaseCommands.d.ts +61 -0
  21. package/dist/cli/phaseCommands.js +559 -39
  22. package/dist/cli/phaseCommands.js.map +1 -1
  23. package/dist/cli/vibeCommands.d.ts +44 -0
  24. package/dist/cli/vibeCommands.js +244 -0
  25. package/dist/cli/vibeCommands.js.map +1 -0
  26. package/dist/guardrails/detectors.d.ts +9 -0
  27. package/dist/guardrails/detectors.js +102 -0
  28. package/dist/guardrails/detectors.js.map +1 -1
  29. package/dist/hooks/HookGeneratorEnhanced.js +29 -0
  30. package/dist/hooks/HookGeneratorEnhanced.js.map +1 -1
  31. package/dist/hooks/WorkflowHooksManager.js +20 -1
  32. package/dist/hooks/WorkflowHooksManager.js.map +1 -1
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +4 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/output/BrandThemeLoader.d.ts +54 -0
  37. package/dist/output/BrandThemeLoader.js +340 -0
  38. package/dist/output/BrandThemeLoader.js.map +1 -0
  39. package/dist/output/HTMLDocumentRenderer.d.ts +83 -0
  40. package/dist/output/HTMLDocumentRenderer.js +717 -0
  41. package/dist/output/HTMLDocumentRenderer.js.map +1 -0
  42. package/dist/output/UIPrototypeRenderer.d.ts +61 -0
  43. package/dist/output/UIPrototypeRenderer.js +500 -0
  44. package/dist/output/UIPrototypeRenderer.js.map +1 -0
  45. package/dist/output/index.d.ts +6 -0
  46. package/dist/output/index.js +6 -0
  47. package/dist/output/index.js.map +1 -0
  48. package/dist/prompts/PhasePromptRegistry.d.ts +53 -0
  49. package/dist/prompts/PhasePromptRegistry.js +517 -0
  50. package/dist/prompts/PhasePromptRegistry.js.map +1 -0
  51. package/dist/skills/SkillDiscovery.js +2 -1
  52. package/dist/skills/SkillDiscovery.js.map +1 -1
  53. package/dist/skills/index.d.ts +1 -0
  54. package/dist/skills/index.js +1 -0
  55. package/dist/skills/index.js.map +1 -1
  56. package/dist/skills/routing/SkillGate.d.ts +11 -0
  57. package/dist/skills/routing/SkillGate.js +76 -0
  58. package/dist/skills/routing/SkillGate.js.map +1 -0
  59. package/dist/skills/routing/SkillPlanner.d.ts +8 -0
  60. package/dist/skills/routing/SkillPlanner.js +91 -0
  61. package/dist/skills/routing/SkillPlanner.js.map +1 -0
  62. package/dist/skills/routing/SkillPolicy.d.ts +6 -0
  63. package/dist/skills/routing/SkillPolicy.js +146 -0
  64. package/dist/skills/routing/SkillPolicy.js.map +1 -0
  65. package/dist/skills/routing/SkillRoutingTypes.d.ts +72 -0
  66. package/dist/skills/routing/SkillRoutingTypes.js +2 -0
  67. package/dist/skills/routing/SkillRoutingTypes.js.map +1 -0
  68. package/dist/skills/routing/TaskIntentClassifier.d.ts +6 -0
  69. package/dist/skills/routing/TaskIntentClassifier.js +79 -0
  70. package/dist/skills/routing/TaskIntentClassifier.js.map +1 -0
  71. package/dist/skills/routing/index.d.ts +5 -0
  72. package/dist/skills/routing/index.js +6 -0
  73. package/dist/skills/routing/index.js.map +1 -0
  74. package/dist/workflow/GovernanceTemplates.d.ts +12 -0
  75. package/dist/workflow/GovernanceTemplates.js +515 -0
  76. package/dist/workflow/GovernanceTemplates.js.map +1 -0
  77. package/dist/workflow/PhaseMarkerTracker.d.ts +63 -0
  78. package/dist/workflow/PhaseMarkerTracker.js +291 -0
  79. package/dist/workflow/PhaseMarkerTracker.js.map +1 -0
  80. package/dist/workflow/SessionStateTracker.d.ts +74 -0
  81. package/dist/workflow/SessionStateTracker.js +270 -0
  82. package/dist/workflow/SessionStateTracker.js.map +1 -0
  83. package/dist/workflow/TaskArtifactScaffolder.d.ts +47 -0
  84. package/dist/workflow/TaskArtifactScaffolder.js +237 -0
  85. package/dist/workflow/TaskArtifactScaffolder.js.map +1 -0
  86. package/dist/workflow/TaskMetricsStore.d.ts +49 -0
  87. package/dist/workflow/TaskMetricsStore.js +149 -0
  88. package/dist/workflow/TaskMetricsStore.js.map +1 -0
  89. package/dist/workflow/VerificationCommands.d.ts +2 -0
  90. package/dist/workflow/VerificationCommands.js +7 -4
  91. package/dist/workflow/VerificationCommands.js.map +1 -1
  92. package/dist/workflow/VerificationProfile.d.ts +55 -0
  93. package/dist/workflow/VerificationProfile.js +133 -0
  94. package/dist/workflow/VerificationProfile.js.map +1 -0
  95. package/dist/workflow/WorkflowArtifactWriter.d.ts +113 -0
  96. package/dist/workflow/WorkflowArtifactWriter.js +241 -0
  97. package/dist/workflow/WorkflowArtifactWriter.js.map +1 -0
  98. package/dist/workflow/WorkflowEngine.d.ts +20 -2
  99. package/dist/workflow/WorkflowEngine.js +37 -8
  100. package/dist/workflow/WorkflowEngine.js.map +1 -1
  101. package/dist/workflow/autonomous/AutonomousDevLoop.d.ts +88 -0
  102. package/dist/workflow/autonomous/AutonomousDevLoop.js +381 -0
  103. package/dist/workflow/autonomous/AutonomousDevLoop.js.map +1 -0
  104. package/dist/workflow/autonomous/WorklogManager.d.ts +50 -0
  105. package/dist/workflow/autonomous/WorklogManager.js +264 -0
  106. package/dist/workflow/autonomous/WorklogManager.js.map +1 -0
  107. package/dist/workflow/autonomous/index.d.ts +2 -0
  108. package/dist/workflow/autonomous/index.js +4 -0
  109. package/dist/workflow/autonomous/index.js.map +1 -0
  110. package/dist/workflow/gates/GateSystem.d.ts +12 -3
  111. package/dist/workflow/gates/GateSystem.js +185 -41
  112. package/dist/workflow/gates/GateSystem.js.map +1 -1
  113. package/dist/workflow/index.d.ts +7 -0
  114. package/dist/workflow/index.js +7 -0
  115. package/dist/workflow/index.js.map +1 -1
  116. package/package.json +3 -3
@@ -0,0 +1,717 @@
1
+ // SCALE Engine — HTML Document Renderer
2
+ // Self-contained HTML output for Spec, Plan, Review, and Report documents
3
+ // Dark theme, inline CSS/JS, interactive TOC, print-friendly
4
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
5
+ import { dirname } from 'node:path';
6
+ // ============================================================================
7
+ // 2. Default options
8
+ // ============================================================================
9
+ const DEFAULT_OPTIONS = {
10
+ theme: 'dark',
11
+ printFriendly: true,
12
+ interactive: true,
13
+ title: 'SCALE Engine Document',
14
+ lang: 'zh',
15
+ };
16
+ // ============================================================================
17
+ // 3. CSS Theme Variables
18
+ // ============================================================================
19
+ function getCSSVariables(theme, brand) {
20
+ // Default dark theme — matches dashboard pattern
21
+ if (theme === 'dark' || theme === 'auto') {
22
+ return `
23
+ :root {
24
+ --bg: #0f0f0f;
25
+ --bg-surface: #1a1a1a;
26
+ --bg-elevated: #242424;
27
+ --fg: #e0e0e0;
28
+ --fg-muted: #808080;
29
+ --fg-dim: #606060;
30
+ --accent: #00ff88;
31
+ --accent-dim: #00cc6a;
32
+ --border: #333;
33
+ --danger: #ff4444;
34
+ --warning: #ffaa00;
35
+ --info: #4488ff;
36
+ --success: #00ff88;
37
+ --font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
38
+ --radius: 8px;
39
+ --shadow: 0 2px 8px rgba(0,0,0,0.3);
40
+ }
41
+ ${theme === 'auto' ? `
42
+ @media (prefers-color-scheme: light) {
43
+ :root {
44
+ --bg: #ffffff;
45
+ --bg-surface: #f5f5f5;
46
+ --bg-elevated: #eeeeee;
47
+ --fg: #1a1a1a;
48
+ --fg-muted: #666;
49
+ --fg-dim: #999;
50
+ --accent: #00aa55;
51
+ --accent-dim: #008844;
52
+ --border: #ddd;
53
+ --shadow: 0 2px 8px rgba(0,0,0,0.1);
54
+ }
55
+ }` : ''}
56
+ `;
57
+ }
58
+ // Light theme
59
+ return `
60
+ :root {
61
+ --bg: #ffffff;
62
+ --bg-surface: #f5f5f5;
63
+ --bg-elevated: #eeeeee;
64
+ --fg: #1a1a1a;
65
+ --fg-muted: #666;
66
+ --fg-dim: #999;
67
+ --accent: #00aa55;
68
+ --accent-dim: #008844;
69
+ --border: #ddd;
70
+ --danger: #cc0000;
71
+ --warning: #cc8800;
72
+ --info: #2266cc;
73
+ --success: #00aa55;
74
+ --font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
75
+ --radius: 8px;
76
+ --shadow: 0 2px 8px rgba(0,0,0,0.1);
77
+ }
78
+ `;
79
+ }
80
+ // ============================================================================
81
+ // 4. Base Layout CSS
82
+ // ============================================================================
83
+ function getBaseCSS() {
84
+ return `
85
+ * { box-sizing: border-box; margin: 0; padding: 0; }
86
+ body {
87
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
88
+ background: var(--bg);
89
+ color: var(--fg);
90
+ line-height: 1.6;
91
+ min-height: 100vh;
92
+ }
93
+ /* Navigation */
94
+ .doc-nav {
95
+ background: var(--bg-surface);
96
+ padding: 0.75rem 1.5rem;
97
+ display: flex;
98
+ gap: 1rem;
99
+ align-items: center;
100
+ border-bottom: 1px solid var(--border);
101
+ position: sticky;
102
+ top: 0;
103
+ z-index: 100;
104
+ }
105
+ .doc-nav .brand { color: var(--accent); font-weight: 700; font-size: 0.85rem; letter-spacing: 0.05em; }
106
+ .doc-nav .sep { color: var(--fg-dim); }
107
+ .doc-nav .title { color: var(--fg-muted); font-size: 0.85rem; }
108
+
109
+ /* Layout */
110
+ .doc-layout { display: flex; min-height: calc(100vh - 49px); }
111
+ .doc-toc {
112
+ width: 260px;
113
+ min-width: 260px;
114
+ background: var(--bg-surface);
115
+ padding: 1.5rem 1rem;
116
+ border-right: 1px solid var(--border);
117
+ overflow-y: auto;
118
+ position: sticky;
119
+ top: 49px;
120
+ height: calc(100vh - 49px);
121
+ }
122
+ .doc-toc h3 {
123
+ color: var(--fg-muted);
124
+ font-size: 0.7rem;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.1em;
127
+ margin-bottom: 0.75rem;
128
+ }
129
+ .doc-toc a {
130
+ display: block;
131
+ color: var(--fg-muted);
132
+ text-decoration: none;
133
+ font-size: 0.85rem;
134
+ padding: 0.3rem 0.5rem;
135
+ border-radius: 4px;
136
+ transition: all 0.15s;
137
+ }
138
+ .doc-toc a:hover { color: var(--fg); background: var(--bg-elevated); }
139
+ .doc-toc a.active { color: var(--accent); background: var(--bg-elevated); }
140
+ .doc-toc a.indent-1 { padding-left: 1.25rem; font-size: 0.8rem; }
141
+
142
+ .doc-main {
143
+ flex: 1;
144
+ padding: 2rem 3rem;
145
+ max-width: 900px;
146
+ }
147
+
148
+ /* Header */
149
+ .doc-header {
150
+ margin-bottom: 2.5rem;
151
+ padding-bottom: 1.5rem;
152
+ border-bottom: 1px solid var(--border);
153
+ }
154
+ .doc-header h1 {
155
+ color: var(--accent);
156
+ font-size: 1.75rem;
157
+ margin-bottom: 0.5rem;
158
+ }
159
+ .doc-meta {
160
+ display: flex;
161
+ gap: 1.5rem;
162
+ flex-wrap: wrap;
163
+ margin-top: 0.75rem;
164
+ }
165
+ .doc-meta .tag {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ gap: 0.35rem;
169
+ font-size: 0.75rem;
170
+ color: var(--fg-muted);
171
+ background: var(--bg-elevated);
172
+ padding: 0.25rem 0.6rem;
173
+ border-radius: 4px;
174
+ }
175
+ .doc-meta .tag .dot {
176
+ width: 6px;
177
+ height: 6px;
178
+ border-radius: 50%;
179
+ display: inline-block;
180
+ }
181
+ .doc-meta .tag .dot.green { background: var(--success); }
182
+ .doc-meta .tag .dot.yellow { background: var(--warning); }
183
+ .doc-meta .tag .dot.red { background: var(--danger); }
184
+
185
+ /* Sections */
186
+ .doc-section {
187
+ margin-bottom: 2rem;
188
+ scroll-margin-top: 60px;
189
+ }
190
+ .doc-section h2 {
191
+ color: var(--fg);
192
+ font-size: 1.25rem;
193
+ margin-bottom: 0.75rem;
194
+ padding-bottom: 0.5rem;
195
+ border-bottom: 1px solid var(--border);
196
+ display: flex;
197
+ align-items: center;
198
+ gap: 0.5rem;
199
+ }
200
+ .doc-section h2 .icon { color: var(--accent); font-size: 1rem; }
201
+ .doc-section h3 {
202
+ color: var(--fg);
203
+ font-size: 1rem;
204
+ margin-top: 1rem;
205
+ margin-bottom: 0.5rem;
206
+ }
207
+ .doc-section p { margin-bottom: 0.75rem; }
208
+ .doc-section ul, .doc-section ol {
209
+ padding-left: 1.5rem;
210
+ margin-bottom: 0.75rem;
211
+ }
212
+ .doc-section li { margin-bottom: 0.35rem; }
213
+
214
+ /* Cards */
215
+ .card {
216
+ background: var(--bg-surface);
217
+ border: 1px solid var(--border);
218
+ border-radius: var(--radius);
219
+ padding: 1rem 1.25rem;
220
+ margin-bottom: 0.75rem;
221
+ }
222
+ .card-header {
223
+ display: flex;
224
+ justify-content: space-between;
225
+ align-items: center;
226
+ margin-bottom: 0.5rem;
227
+ }
228
+ .card-title { font-weight: 600; font-size: 0.9rem; }
229
+ .card-badge {
230
+ font-size: 0.7rem;
231
+ padding: 0.15rem 0.5rem;
232
+ border-radius: 4px;
233
+ font-weight: 600;
234
+ }
235
+ .badge-green { background: rgba(0,255,136,0.15); color: var(--success); }
236
+ .badge-yellow { background: rgba(255,170,0,0.15); color: var(--warning); }
237
+ .badge-red { background: rgba(255,68,68,0.15); color: var(--danger); }
238
+ .badge-blue { background: rgba(68,136,255,0.15); color: var(--info); }
239
+
240
+ /* Tables */
241
+ .doc-table {
242
+ width: 100%;
243
+ border-collapse: collapse;
244
+ margin-bottom: 1rem;
245
+ font-size: 0.85rem;
246
+ }
247
+ .doc-table th, .doc-table td {
248
+ padding: 0.6rem 0.75rem;
249
+ text-align: left;
250
+ border-bottom: 1px solid var(--border);
251
+ }
252
+ .doc-table th {
253
+ color: var(--fg-muted);
254
+ font-weight: 600;
255
+ font-size: 0.75rem;
256
+ text-transform: uppercase;
257
+ letter-spacing: 0.05em;
258
+ }
259
+ .doc-table tr:hover td { background: var(--bg-surface); }
260
+
261
+ /* Code */
262
+ code {
263
+ font-family: var(--font-mono);
264
+ font-size: 0.85em;
265
+ background: var(--bg-elevated);
266
+ padding: 0.15rem 0.4rem;
267
+ border-radius: 3px;
268
+ }
269
+ pre {
270
+ background: var(--bg-surface);
271
+ border: 1px solid var(--border);
272
+ border-radius: var(--radius);
273
+ padding: 1rem;
274
+ overflow-x: auto;
275
+ margin-bottom: 1rem;
276
+ }
277
+ pre code {
278
+ background: none;
279
+ padding: 0;
280
+ font-size: 0.8rem;
281
+ line-height: 1.5;
282
+ }
283
+
284
+ /* Checklist */
285
+ .checklist { list-style: none; padding-left: 0; }
286
+ .checklist li {
287
+ padding: 0.4rem 0;
288
+ display: flex;
289
+ align-items: flex-start;
290
+ gap: 0.5rem;
291
+ }
292
+ .checklist li::before {
293
+ content: '\\2610';
294
+ color: var(--fg-muted);
295
+ flex-shrink: 0;
296
+ }
297
+ .checklist li.done::before {
298
+ content: '\\2611';
299
+ color: var(--success);
300
+ }
301
+
302
+ /* Finding rows */
303
+ .finding-row {
304
+ display: flex;
305
+ gap: 0.5rem;
306
+ align-items: flex-start;
307
+ padding: 0.5rem 0;
308
+ border-bottom: 1px solid var(--border);
309
+ font-size: 0.85rem;
310
+ }
311
+ .finding-severity {
312
+ flex-shrink: 0;
313
+ font-weight: 600;
314
+ font-size: 0.7rem;
315
+ padding: 0.1rem 0.4rem;
316
+ border-radius: 3px;
317
+ text-transform: uppercase;
318
+ }
319
+ .severity-critical { background: rgba(255,68,68,0.2); color: var(--danger); }
320
+ .severity-high { background: rgba(255,170,0,0.2); color: var(--warning); }
321
+ .severity-medium { background: rgba(68,136,255,0.2); color: var(--info); }
322
+ .severity-low { background: rgba(128,128,128,0.2); color: var(--fg-muted); }
323
+ .finding-file { color: var(--accent); font-family: var(--font-mono); font-size: 0.8rem; }
324
+ .finding-message { color: var(--fg); }
325
+
326
+ /* Metric grid */
327
+ .metric-grid {
328
+ display: grid;
329
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
330
+ gap: 0.75rem;
331
+ margin-bottom: 1rem;
332
+ }
333
+ .metric-card {
334
+ background: var(--bg-surface);
335
+ border: 1px solid var(--border);
336
+ border-radius: var(--radius);
337
+ padding: 1rem;
338
+ text-align: center;
339
+ }
340
+ .metric-value {
341
+ font-size: 1.5rem;
342
+ font-weight: 700;
343
+ color: var(--accent);
344
+ }
345
+ .metric-label {
346
+ font-size: 0.75rem;
347
+ color: var(--fg-muted);
348
+ margin-top: 0.25rem;
349
+ }
350
+
351
+ /* Collapsible */
352
+ details { margin-bottom: 0.75rem; }
353
+ details summary {
354
+ cursor: pointer;
355
+ font-weight: 600;
356
+ padding: 0.5rem 0;
357
+ color: var(--fg);
358
+ }
359
+ details summary:hover { color: var(--accent); }
360
+ details[open] summary { margin-bottom: 0.5rem; }
361
+
362
+ /* Footer */
363
+ .doc-footer {
364
+ margin-top: 3rem;
365
+ padding-top: 1.5rem;
366
+ border-top: 1px solid var(--border);
367
+ font-size: 0.75rem;
368
+ color: var(--fg-dim);
369
+ text-align: center;
370
+ }
371
+
372
+ /* Print */
373
+ @media print {
374
+ body { background: #fff; color: #000; }
375
+ .doc-nav { display: none; }
376
+ .doc-toc { display: none; }
377
+ .doc-main { max-width: 100%; padding: 0; }
378
+ .doc-header h1 { color: #000; }
379
+ .card { border: 1px solid #ccc; }
380
+ .metric-card { border: 1px solid #ccc; }
381
+ .metric-value { color: #000; }
382
+ a { color: #000; text-decoration: underline; }
383
+ .doc-footer { color: #666; }
384
+ }
385
+
386
+ /* Responsive */
387
+ @media (max-width: 768px) {
388
+ .doc-toc { display: none; }
389
+ .doc-main { padding: 1.5rem 1rem; }
390
+ .doc-meta { gap: 0.75rem; }
391
+ }
392
+ `;
393
+ }
394
+ // ============================================================================
395
+ // 5. Interactive JS
396
+ // ============================================================================
397
+ function getInteractiveScript() {
398
+ return `
399
+ // Active TOC tracking
400
+ (function() {
401
+ const sections = document.querySelectorAll('.doc-section[id]');
402
+ const tocLinks = document.querySelectorAll('.doc-toc a[href^="#"]');
403
+ if (!sections.length || !tocLinks.length) return;
404
+
405
+ const observer = new IntersectionObserver((entries) => {
406
+ entries.forEach(entry => {
407
+ if (entry.isIntersecting) {
408
+ tocLinks.forEach(link => link.classList.remove('active'));
409
+ const id = entry.target.getAttribute('id');
410
+ const active = document.querySelector('.doc-toc a[href="#' + id + '"]');
411
+ if (active) active.classList.add('active');
412
+ }
413
+ });
414
+ }, { rootMargin: '-60px 0px -60% 0px' });
415
+
416
+ sections.forEach(s => observer.observe(s));
417
+ })();
418
+
419
+ // Smooth scroll for TOC links
420
+ document.querySelectorAll('.doc-toc a').forEach(link => {
421
+ link.addEventListener('click', (e) => {
422
+ const href = link.getAttribute('href');
423
+ if (href && href.startsWith('#')) {
424
+ e.preventDefault();
425
+ const target = document.getElementById(href.slice(1));
426
+ if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
427
+ }
428
+ });
429
+ });
430
+
431
+ // Keyboard shortcut: Ctrl+P print hint
432
+ document.addEventListener('keydown', (e) => {
433
+ if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
434
+ // Let browser handle print
435
+ }
436
+ });
437
+ `;
438
+ }
439
+ // ============================================================================
440
+ // 6. HTMLDocumentRenderer
441
+ // ============================================================================
442
+ export class HTMLDocumentRenderer {
443
+ constructor(options) {
444
+ this.options = { ...DEFAULT_OPTIONS, ...options };
445
+ }
446
+ // === Spec rendering ===
447
+ renderSpec(data) {
448
+ const sections = [
449
+ this.renderSection('overview', 'What', `
450
+ <p>${this.escapeHtml(data.what)}</p>
451
+ `),
452
+ this.renderSection('success-criteria', 'Success Criteria', `
453
+ <ul class="checklist">
454
+ ${data.successCriteria.map(c => `<li>${this.escapeHtml(c)}</li>`).join('\n')}
455
+ </ul>
456
+ `),
457
+ data.outOfScope.length > 0 ? this.renderSection('out-of-scope', 'Out of Scope', `
458
+ <ul>${data.outOfScope.map(o => `<li>${this.escapeHtml(o)}</li>`).join('\n')}</ul>
459
+ `) : '',
460
+ data.edgeCases.length > 0 ? this.renderSection('edge-cases', 'Edge Cases', `
461
+ <ul>${data.edgeCases.map(e => `<li>${this.escapeHtml(e)}</li>`).join('\n')}</ul>
462
+ `) : '',
463
+ this.renderSection('north-star', 'North Star', `
464
+ <p>${this.escapeHtml(data.northStar)}</p>
465
+ `),
466
+ ].filter(Boolean).join('\n');
467
+ const meta = this.buildMetaTags([
468
+ { label: 'ID', value: data.id },
469
+ { label: 'Status', value: this.options.status ?? 'FROZEN', dotColor: 'green' },
470
+ data.ambiguityScore !== undefined
471
+ ? { label: 'Ambiguity', value: data.ambiguityScore.toFixed(2), dotColor: data.ambiguityScore > 0.2 ? 'yellow' : 'green' }
472
+ : null,
473
+ { label: 'Criteria', value: `${data.successCriteria.length}` },
474
+ ].filter(Boolean));
475
+ return this.wrapInLayout(data.title, meta, sections, 'DEFINE');
476
+ }
477
+ // === Plan rendering ===
478
+ renderPlan(data) {
479
+ const sections = [
480
+ this.renderSection('approach', 'Approach', `
481
+ <p>${this.escapeHtml(data.approach)}</p>
482
+ `),
483
+ data.techChoices.length > 0 ? this.renderSection('tech-choices', 'Tech Choices', `
484
+ <div class="doc-table-wrap">
485
+ <table class="doc-table">
486
+ <thead><tr><th>Decision</th><th>Rationale</th>${data.techChoices[0]?.alternatives ? '<th>Alternatives</th>' : ''}</tr></thead>
487
+ <tbody>
488
+ ${data.techChoices.map(t => `<tr>
489
+ <td><strong>${this.escapeHtml(t.decision)}</strong></td>
490
+ <td>${this.escapeHtml(t.rationale)}</td>
491
+ ${t.alternatives ? `<td>${t.alternatives.map(a => this.escapeHtml(a)).join(', ')}</td>` : ''}
492
+ </tr>`).join('\n')}
493
+ </tbody>
494
+ </table>
495
+ </div>
496
+ `) : '',
497
+ data.modules.length > 0 ? this.renderSection('modules', 'Modules', `
498
+ <div class="doc-table-wrap">
499
+ <table class="doc-table">
500
+ <thead><tr><th>Action</th><th>Path</th><th>Reason</th></tr></thead>
501
+ <tbody>
502
+ ${data.modules.map(m => `<tr>
503
+ <td><span class="card-badge ${m.action === 'create' ? 'badge-green' : m.action === 'modify' ? 'badge-yellow' : 'badge-red'}">${m.action}</span></td>
504
+ <td><code>${this.escapeHtml(m.path)}</code></td>
505
+ <td>${this.escapeHtml(m.reason)}</td>
506
+ </tr>`).join('\n')}
507
+ </tbody>
508
+ </table>
509
+ </div>
510
+ `) : '',
511
+ this.renderSection('rollback', 'Rollback Strategy', `
512
+ <div class="card">
513
+ <p>${this.escapeHtml(data.rollbackStrategy)}</p>
514
+ </div>
515
+ `),
516
+ data.estimatedComplexity !== undefined ? this.renderSection('complexity', 'Estimated Complexity', `
517
+ <div class="metric-grid">
518
+ <div class="metric-card">
519
+ <div class="metric-value">${data.estimatedComplexity}/10</div>
520
+ <div class="metric-label">Complexity</div>
521
+ </div>
522
+ </div>
523
+ `) : '',
524
+ ].filter(Boolean).join('\n');
525
+ const meta = this.buildMetaTags([
526
+ { label: 'ID', value: data.id },
527
+ { label: 'Spec', value: data.specId },
528
+ { label: 'Status', value: this.options.status ?? 'APPROVED', dotColor: 'green' },
529
+ data.estimatedComplexity !== undefined
530
+ ? { label: 'Complexity', value: `${data.estimatedComplexity}/10` }
531
+ : null,
532
+ ].filter(Boolean));
533
+ return this.wrapInLayout(data.title ?? `Plan ${data.id}`, meta, sections, 'PLAN');
534
+ }
535
+ // === Review rendering ===
536
+ renderReview(data) {
537
+ const severityGroups = this.groupBySeverity(data.findings);
538
+ const sections = [
539
+ // Summary metrics
540
+ `<div class="metric-grid">
541
+ <div class="metric-card">
542
+ <div class="metric-value">${data.findings.length}</div>
543
+ <div class="metric-label">Findings</div>
544
+ </div>
545
+ <div class="metric-card">
546
+ <div class="metric-value" style="color: ${data.passed ? 'var(--success)' : 'var(--danger)'}">${data.passed ? 'PASS' : 'FAIL'}</div>
547
+ <div class="metric-label">Result</div>
548
+ </div>
549
+ ${data.specCoverage !== undefined ? `
550
+ <div class="metric-card">
551
+ <div class="metric-value">${(data.specCoverage * 100).toFixed(0)}%</div>
552
+ <div class="metric-label">Spec Coverage</div>
553
+ </div>` : ''}
554
+ ${Object.entries(severityGroups).map(([sev, items]) => `
555
+ <div class="metric-card">
556
+ <div class="metric-value" style="color: ${sev === 'critical' || sev === 'high' ? 'var(--danger)' : sev === 'medium' ? 'var(--warning)' : 'var(--fg-muted)'}">${items.length}</div>
557
+ <div class="metric-label">${sev}</div>
558
+ </div>`).join('')}
559
+ </div>`,
560
+ // Findings table
561
+ this.renderSection('findings', 'Findings', data.findings.length > 0
562
+ ? data.findings.map(f => `
563
+ <div class="finding-row">
564
+ <span class="finding-severity severity-${f.severity.toLowerCase()}">${f.severity}</span>
565
+ <span class="finding-file">${this.escapeHtml(f.file)}${f.line ? `:${f.line}` : ''}</span>
566
+ <span class="finding-message">${this.escapeHtml(f.message)}</span>
567
+ </div>
568
+ `).join('')
569
+ : '<p style="color: var(--fg-muted)">No findings</p>'),
570
+ // Spec conformance
571
+ data.specFindings && data.specFindings.length > 0
572
+ ? this.renderSection('spec-conformance', 'Spec Conformance', `
573
+ <ul>${data.specFindings.map(f => `<li>${this.escapeHtml(f)}</li>`).join('\n')}</ul>
574
+ `)
575
+ : '',
576
+ ].filter(Boolean).join('\n');
577
+ const meta = this.buildMetaTags([
578
+ { label: 'ID', value: data.id },
579
+ { label: 'Status', value: data.passed ? 'PASS' : 'FAIL', dotColor: data.passed ? 'green' : 'red' },
580
+ { label: 'Time', value: data.timestamp },
581
+ ]);
582
+ return this.wrapInLayout(data.title, meta, sections, 'REVIEW');
583
+ }
584
+ // === Report rendering ===
585
+ renderReport(data) {
586
+ // Metrics grid
587
+ const metricsHtml = data.metrics
588
+ ? `<div class="metric-grid">
589
+ ${Object.entries(data.metrics).map(([key, val]) => `
590
+ <div class="metric-card">
591
+ <div class="metric-value">${this.escapeHtml(String(val))}</div>
592
+ <div class="metric-label">${this.escapeHtml(key)}</div>
593
+ </div>
594
+ `).join('')}
595
+ </div>`
596
+ : '';
597
+ const sections = [
598
+ metricsHtml,
599
+ ...data.sections.map(s => this.renderSection(this.slugify(s.heading), s.heading, s.content)),
600
+ ].filter(Boolean).join('\n');
601
+ const meta = this.buildMetaTags([
602
+ { label: 'Type', value: data.type },
603
+ { label: 'Time', value: data.timestamp },
604
+ ]);
605
+ return this.wrapInLayout(data.title, meta, sections, data.type.toUpperCase());
606
+ }
607
+ // === Generic content rendering ===
608
+ renderContent(title, bodyHtml, phase) {
609
+ const meta = this.buildMetaTags([
610
+ { label: 'Phase', value: phase ?? 'CUSTOM' },
611
+ { label: 'Time', value: new Date().toISOString() },
612
+ ]);
613
+ return this.wrapInLayout(title, meta, bodyHtml, phase);
614
+ }
615
+ // === Write to file ===
616
+ writeToFile(html, filePath) {
617
+ const dir = dirname(filePath);
618
+ if (!existsSync(dir))
619
+ mkdirSync(dir, { recursive: true });
620
+ writeFileSync(filePath, html, 'utf-8');
621
+ return filePath;
622
+ }
623
+ // === Internal: Wrap in full HTML layout ===
624
+ wrapInLayout(title, metaHtml, bodyHtml, phase) {
625
+ const opts = this.options;
626
+ const toc = this.extractTOC(bodyHtml);
627
+ return `<!DOCTYPE html>
628
+ <html lang="${opts.lang}">
629
+ <head>
630
+ <meta charset="UTF-8">
631
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
632
+ <meta name="generator" content="SCALE Engine v${opts.version ?? '0.13.0'}">
633
+ <title>${this.escapeHtml(title)} — SCALE Engine</title>
634
+ <style>
635
+ ${getCSSVariables(opts.theme, opts.brand)}
636
+ ${getBaseCSS()}
637
+ </style>
638
+ </head>
639
+ <body>
640
+ <nav class="doc-nav">
641
+ <span class="brand">SCALE</span>
642
+ <span class="sep">/</span>
643
+ <span class="title">${this.escapeHtml(phase ?? 'DOC')} — ${this.escapeHtml(title)}</span>
644
+ </nav>
645
+ <div class="doc-layout">
646
+ ${opts.interactive && toc.length > 0 ? `
647
+ <aside class="doc-toc">
648
+ <h3>Contents</h3>
649
+ ${toc.map(item => ` <a href="#${item.id}"${item.indent ? ' class="indent-1"' : ''}>${this.escapeHtml(item.label)}</a>`).join('\n')}
650
+ </aside>
651
+ ` : ''}
652
+ <main class="doc-main">
653
+ <header class="doc-header">
654
+ <h1>${this.escapeHtml(title)}</h1>
655
+ ${metaHtml}
656
+ </header>
657
+ ${bodyHtml}
658
+ <footer class="doc-footer">
659
+ Generated by SCALE Engine v${opts.version ?? '0.13.0'} &middot; ${new Date().toISOString().slice(0, 10)}
660
+ ${opts.printFriendly ? ' &middot; <em>Ctrl+P to print as PDF</em>' : ''}
661
+ </footer>
662
+ </main>
663
+ </div>
664
+ ${opts.interactive ? `<script>${getInteractiveScript()}</script>` : ''}
665
+ </body>
666
+ </html>`;
667
+ }
668
+ // === Internal: Render a section with anchor ===
669
+ renderSection(id, heading, content) {
670
+ return `
671
+ <section class="doc-section" id="${id}">
672
+ <h2><span class="icon">#</span> ${this.escapeHtml(heading)}</h2>
673
+ ${content}
674
+ </section>`;
675
+ }
676
+ // === Internal: Build meta tags HTML ===
677
+ buildMetaTags(tags) {
678
+ if (tags.length === 0)
679
+ return '';
680
+ return `<div class="doc-meta">
681
+ ${tags.map(t => ` <span class="tag">${t.dotColor ? `<span class="dot ${t.dotColor}"></span>` : ''}<strong>${this.escapeHtml(t.label)}:</strong> ${this.escapeHtml(t.value)}</span>`).join('\n')}
682
+ </div>`;
683
+ }
684
+ // === Internal: Extract TOC from rendered sections ===
685
+ extractTOC(html) {
686
+ const items = [];
687
+ const regex = /<section[^>]*id="([^"]*)"[^>]*>\s*<h2[^>]*>(?:<[^>]*>)?\s*#\s*(?:<[^>]*>)?\s*([^<]+)/g;
688
+ let match;
689
+ while ((match = regex.exec(html)) !== null) {
690
+ items.push({ id: match[1], label: match[2].trim(), indent: false });
691
+ }
692
+ return items;
693
+ }
694
+ // === Utility ===
695
+ escapeHtml(str) {
696
+ return str
697
+ .replace(/&/g, '&amp;')
698
+ .replace(/</g, '&lt;')
699
+ .replace(/>/g, '&gt;')
700
+ .replace(/"/g, '&quot;')
701
+ .replace(/\n/g, '<br>');
702
+ }
703
+ slugify(text) {
704
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
705
+ }
706
+ groupBySeverity(findings) {
707
+ const groups = {};
708
+ for (const f of findings) {
709
+ const key = f.severity.toLowerCase();
710
+ if (!groups[key])
711
+ groups[key] = [];
712
+ groups[key].push(f);
713
+ }
714
+ return groups;
715
+ }
716
+ }
717
+ //# sourceMappingURL=HTMLDocumentRenderer.js.map