popeye-cli 1.0.1 → 1.2.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 (216) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +832 -123
  5. package/dist/adapters/claude.d.ts +19 -4
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +908 -42
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/gemini.d.ts +55 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +318 -0
  12. package/dist/adapters/gemini.js.map +1 -0
  13. package/dist/adapters/grok.d.ts +73 -0
  14. package/dist/adapters/grok.d.ts.map +1 -0
  15. package/dist/adapters/grok.js +430 -0
  16. package/dist/adapters/grok.js.map +1 -0
  17. package/dist/adapters/openai.d.ts +1 -1
  18. package/dist/adapters/openai.d.ts.map +1 -1
  19. package/dist/adapters/openai.js +47 -8
  20. package/dist/adapters/openai.js.map +1 -1
  21. package/dist/auth/claude.d.ts +11 -9
  22. package/dist/auth/claude.d.ts.map +1 -1
  23. package/dist/auth/claude.js +107 -71
  24. package/dist/auth/claude.js.map +1 -1
  25. package/dist/auth/gemini.d.ts +58 -0
  26. package/dist/auth/gemini.d.ts.map +1 -0
  27. package/dist/auth/gemini.js +172 -0
  28. package/dist/auth/gemini.js.map +1 -0
  29. package/dist/auth/grok.d.ts +73 -0
  30. package/dist/auth/grok.d.ts.map +1 -0
  31. package/dist/auth/grok.js +211 -0
  32. package/dist/auth/grok.js.map +1 -0
  33. package/dist/auth/index.d.ts +14 -7
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +41 -6
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/keychain.d.ts +20 -7
  38. package/dist/auth/keychain.d.ts.map +1 -1
  39. package/dist/auth/keychain.js +85 -29
  40. package/dist/auth/keychain.js.map +1 -1
  41. package/dist/auth/openai.d.ts +2 -2
  42. package/dist/auth/openai.d.ts.map +1 -1
  43. package/dist/auth/openai.js +30 -32
  44. package/dist/auth/openai.js.map +1 -1
  45. package/dist/cli/commands/auth.d.ts +1 -1
  46. package/dist/cli/commands/auth.d.ts.map +1 -1
  47. package/dist/cli/commands/auth.js +79 -8
  48. package/dist/cli/commands/auth.js.map +1 -1
  49. package/dist/cli/commands/create.d.ts.map +1 -1
  50. package/dist/cli/commands/create.js +15 -4
  51. package/dist/cli/commands/create.js.map +1 -1
  52. package/dist/cli/interactive.d.ts.map +1 -1
  53. package/dist/cli/interactive.js +1494 -114
  54. package/dist/cli/interactive.js.map +1 -1
  55. package/dist/config/defaults.d.ts +9 -1
  56. package/dist/config/defaults.d.ts.map +1 -1
  57. package/dist/config/defaults.js +19 -2
  58. package/dist/config/defaults.js.map +1 -1
  59. package/dist/config/index.d.ts +19 -0
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +33 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/config/schema.d.ts +47 -0
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +29 -1
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/generators/fullstack.d.ts +32 -0
  68. package/dist/generators/fullstack.d.ts.map +1 -0
  69. package/dist/generators/fullstack.js +497 -0
  70. package/dist/generators/fullstack.js.map +1 -0
  71. package/dist/generators/index.d.ts +4 -3
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +15 -1
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/python.d.ts +17 -1
  76. package/dist/generators/python.d.ts.map +1 -1
  77. package/dist/generators/python.js +34 -20
  78. package/dist/generators/python.js.map +1 -1
  79. package/dist/generators/templates/fullstack.d.ts +113 -0
  80. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  81. package/dist/generators/templates/fullstack.js +1004 -0
  82. package/dist/generators/templates/fullstack.js.map +1 -0
  83. package/dist/generators/typescript.d.ts +19 -1
  84. package/dist/generators/typescript.d.ts.map +1 -1
  85. package/dist/generators/typescript.js +37 -20
  86. package/dist/generators/typescript.js.map +1 -1
  87. package/dist/state/index.d.ts +108 -0
  88. package/dist/state/index.d.ts.map +1 -1
  89. package/dist/state/index.js +551 -4
  90. package/dist/state/index.js.map +1 -1
  91. package/dist/state/registry.d.ts +52 -0
  92. package/dist/state/registry.d.ts.map +1 -0
  93. package/dist/state/registry.js +215 -0
  94. package/dist/state/registry.js.map +1 -0
  95. package/dist/types/cli.d.ts +8 -0
  96. package/dist/types/cli.d.ts.map +1 -1
  97. package/dist/types/cli.js.map +1 -1
  98. package/dist/types/consensus.d.ts +186 -4
  99. package/dist/types/consensus.d.ts.map +1 -1
  100. package/dist/types/consensus.js +35 -3
  101. package/dist/types/consensus.js.map +1 -1
  102. package/dist/types/project.d.ts +76 -0
  103. package/dist/types/project.d.ts.map +1 -1
  104. package/dist/types/project.js +1 -1
  105. package/dist/types/project.js.map +1 -1
  106. package/dist/types/workflow.d.ts +217 -16
  107. package/dist/types/workflow.d.ts.map +1 -1
  108. package/dist/types/workflow.js +40 -1
  109. package/dist/types/workflow.js.map +1 -1
  110. package/dist/workflow/auto-fix.d.ts +45 -0
  111. package/dist/workflow/auto-fix.d.ts.map +1 -0
  112. package/dist/workflow/auto-fix.js +274 -0
  113. package/dist/workflow/auto-fix.js.map +1 -0
  114. package/dist/workflow/consensus.d.ts +70 -2
  115. package/dist/workflow/consensus.d.ts.map +1 -1
  116. package/dist/workflow/consensus.js +872 -17
  117. package/dist/workflow/consensus.js.map +1 -1
  118. package/dist/workflow/execution-mode.d.ts +10 -4
  119. package/dist/workflow/execution-mode.d.ts.map +1 -1
  120. package/dist/workflow/execution-mode.js +547 -58
  121. package/dist/workflow/execution-mode.js.map +1 -1
  122. package/dist/workflow/index.d.ts +14 -2
  123. package/dist/workflow/index.d.ts.map +1 -1
  124. package/dist/workflow/index.js +69 -6
  125. package/dist/workflow/index.js.map +1 -1
  126. package/dist/workflow/milestone-workflow.d.ts +34 -0
  127. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  128. package/dist/workflow/milestone-workflow.js +414 -0
  129. package/dist/workflow/milestone-workflow.js.map +1 -0
  130. package/dist/workflow/plan-mode.d.ts +80 -3
  131. package/dist/workflow/plan-mode.d.ts.map +1 -1
  132. package/dist/workflow/plan-mode.js +767 -49
  133. package/dist/workflow/plan-mode.js.map +1 -1
  134. package/dist/workflow/plan-storage.d.ts +386 -0
  135. package/dist/workflow/plan-storage.d.ts.map +1 -0
  136. package/dist/workflow/plan-storage.js +878 -0
  137. package/dist/workflow/plan-storage.js.map +1 -0
  138. package/dist/workflow/project-verification.d.ts +37 -0
  139. package/dist/workflow/project-verification.d.ts.map +1 -0
  140. package/dist/workflow/project-verification.js +381 -0
  141. package/dist/workflow/project-verification.js.map +1 -0
  142. package/dist/workflow/task-workflow.d.ts +37 -0
  143. package/dist/workflow/task-workflow.d.ts.map +1 -0
  144. package/dist/workflow/task-workflow.js +386 -0
  145. package/dist/workflow/task-workflow.js.map +1 -0
  146. package/dist/workflow/test-runner.d.ts +9 -0
  147. package/dist/workflow/test-runner.d.ts.map +1 -1
  148. package/dist/workflow/test-runner.js +101 -5
  149. package/dist/workflow/test-runner.js.map +1 -1
  150. package/dist/workflow/ui-designer.d.ts +82 -0
  151. package/dist/workflow/ui-designer.d.ts.map +1 -0
  152. package/dist/workflow/ui-designer.js +234 -0
  153. package/dist/workflow/ui-designer.js.map +1 -0
  154. package/dist/workflow/ui-setup.d.ts +58 -0
  155. package/dist/workflow/ui-setup.d.ts.map +1 -0
  156. package/dist/workflow/ui-setup.js +685 -0
  157. package/dist/workflow/ui-setup.js.map +1 -0
  158. package/dist/workflow/ui-verification.d.ts +114 -0
  159. package/dist/workflow/ui-verification.d.ts.map +1 -0
  160. package/dist/workflow/ui-verification.js +258 -0
  161. package/dist/workflow/ui-verification.js.map +1 -0
  162. package/dist/workflow/workflow-logger.d.ts +110 -0
  163. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  164. package/dist/workflow/workflow-logger.js +267 -0
  165. package/dist/workflow/workflow-logger.js.map +1 -0
  166. package/dist/workflow/workspace-manager.d.ts +342 -0
  167. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  168. package/dist/workflow/workspace-manager.js +733 -0
  169. package/dist/workflow/workspace-manager.js.map +1 -0
  170. package/package.json +2 -2
  171. package/src/adapters/claude.ts +1067 -47
  172. package/src/adapters/gemini.ts +373 -0
  173. package/src/adapters/grok.ts +492 -0
  174. package/src/adapters/openai.ts +48 -9
  175. package/src/auth/claude.ts +120 -78
  176. package/src/auth/gemini.ts +207 -0
  177. package/src/auth/grok.ts +255 -0
  178. package/src/auth/index.ts +47 -9
  179. package/src/auth/keychain.ts +95 -28
  180. package/src/auth/openai.ts +29 -36
  181. package/src/cli/commands/auth.ts +89 -10
  182. package/src/cli/commands/create.ts +13 -4
  183. package/src/cli/interactive.ts +1774 -142
  184. package/src/config/defaults.ts +19 -2
  185. package/src/config/index.ts +36 -1
  186. package/src/config/schema.ts +30 -1
  187. package/src/generators/fullstack.ts +551 -0
  188. package/src/generators/index.ts +25 -1
  189. package/src/generators/python.ts +65 -20
  190. package/src/generators/templates/fullstack.ts +1047 -0
  191. package/src/generators/typescript.ts +69 -20
  192. package/src/state/index.ts +713 -4
  193. package/src/state/registry.ts +278 -0
  194. package/src/types/cli.ts +8 -0
  195. package/src/types/consensus.ts +197 -6
  196. package/src/types/project.ts +82 -1
  197. package/src/types/workflow.ts +90 -1
  198. package/src/workflow/auto-fix.ts +340 -0
  199. package/src/workflow/consensus.ts +1180 -16
  200. package/src/workflow/execution-mode.ts +673 -74
  201. package/src/workflow/index.ts +95 -6
  202. package/src/workflow/milestone-workflow.ts +576 -0
  203. package/src/workflow/plan-mode.ts +924 -50
  204. package/src/workflow/plan-storage.ts +1282 -0
  205. package/src/workflow/project-verification.ts +471 -0
  206. package/src/workflow/task-workflow.ts +528 -0
  207. package/src/workflow/test-runner.ts +120 -5
  208. package/src/workflow/ui-designer.ts +337 -0
  209. package/src/workflow/ui-setup.ts +797 -0
  210. package/src/workflow/ui-verification.ts +357 -0
  211. package/src/workflow/workflow-logger.ts +353 -0
  212. package/src/workflow/workspace-manager.ts +912 -0
  213. package/tests/config/config.test.ts +1 -1
  214. package/tests/types/consensus.test.ts +3 -3
  215. package/tests/workflow/plan-mode.test.ts +213 -0
  216. package/tests/workflow/test-runner.test.ts +5 -3
@@ -0,0 +1,357 @@
1
+ /**
2
+ * UI/UX Verification Module
3
+ * Verifies visual design, responsiveness, and accessibility
4
+ *
5
+ * FUTURE IMPLEMENTATION - Requires:
6
+ * - Puppeteer/Playwright for screenshots
7
+ * - Visual regression testing
8
+ * - Accessibility testing (axe-core)
9
+ */
10
+
11
+ /**
12
+ * UI Design Specification
13
+ * User should provide this before project generation
14
+ */
15
+ export interface UIDesignSpec {
16
+ /** Design system to use */
17
+ designSystem: 'tailwind' | 'shadcn' | 'mui' | 'chakra' | 'custom';
18
+
19
+ /** Color palette */
20
+ colors: {
21
+ primary: string;
22
+ secondary: string;
23
+ accent: string;
24
+ background: string;
25
+ text: string;
26
+ error: string;
27
+ success: string;
28
+ warning: string;
29
+ };
30
+
31
+ /** Typography */
32
+ typography: {
33
+ fontFamily: string;
34
+ headingFont?: string;
35
+ baseSize: string;
36
+ };
37
+
38
+ /** Spacing scale */
39
+ spacing: 'compact' | 'comfortable' | 'spacious';
40
+
41
+ /** Border radius */
42
+ borderRadius: 'none' | 'small' | 'medium' | 'large' | 'full';
43
+
44
+ /** Dark mode support */
45
+ darkMode: boolean;
46
+
47
+ /** Responsive breakpoints to test */
48
+ breakpoints: ('mobile' | 'tablet' | 'desktop' | 'wide')[];
49
+
50
+ /** Accessibility level */
51
+ accessibilityLevel: 'A' | 'AA' | 'AAA';
52
+ }
53
+
54
+ /**
55
+ * Default design spec for when user doesn't provide one
56
+ */
57
+ export const DEFAULT_DESIGN_SPEC: UIDesignSpec = {
58
+ designSystem: 'shadcn',
59
+ colors: {
60
+ primary: '#3b82f6', // Blue 500
61
+ secondary: '#6b7280', // Gray 500
62
+ accent: '#8b5cf6', // Violet 500
63
+ background: '#ffffff',
64
+ text: '#111827', // Gray 900
65
+ error: '#ef4444', // Red 500
66
+ success: '#22c55e', // Green 500
67
+ warning: '#f59e0b', // Amber 500
68
+ },
69
+ typography: {
70
+ fontFamily: 'Inter, system-ui, sans-serif',
71
+ baseSize: '16px',
72
+ },
73
+ spacing: 'comfortable',
74
+ borderRadius: 'medium',
75
+ darkMode: true,
76
+ breakpoints: ['mobile', 'tablet', 'desktop'],
77
+ accessibilityLevel: 'AA',
78
+ };
79
+
80
+ /**
81
+ * UI Verification Result
82
+ */
83
+ export interface UIVerificationResult {
84
+ passed: boolean;
85
+ category: string;
86
+ check: string;
87
+ message: string;
88
+ screenshot?: string;
89
+ severity: 'error' | 'warning' | 'info';
90
+ }
91
+
92
+ /**
93
+ * Generate Tailwind config from design spec
94
+ */
95
+ export function generateTailwindConfig(spec: UIDesignSpec): string {
96
+ return `/** @type {import('tailwindcss').Config} */
97
+ export default {
98
+ content: [
99
+ "./index.html",
100
+ "./src/**/*.{js,ts,jsx,tsx}",
101
+ ],
102
+ darkMode: ${spec.darkMode ? "'class'" : 'false'},
103
+ theme: {
104
+ extend: {
105
+ colors: {
106
+ primary: {
107
+ DEFAULT: '${spec.colors.primary}',
108
+ 50: '${lighten(spec.colors.primary, 0.9)}',
109
+ 100: '${lighten(spec.colors.primary, 0.8)}',
110
+ 200: '${lighten(spec.colors.primary, 0.6)}',
111
+ 300: '${lighten(spec.colors.primary, 0.4)}',
112
+ 400: '${lighten(spec.colors.primary, 0.2)}',
113
+ 500: '${spec.colors.primary}',
114
+ 600: '${darken(spec.colors.primary, 0.1)}',
115
+ 700: '${darken(spec.colors.primary, 0.2)}',
116
+ 800: '${darken(spec.colors.primary, 0.3)}',
117
+ 900: '${darken(spec.colors.primary, 0.4)}',
118
+ },
119
+ secondary: {
120
+ DEFAULT: '${spec.colors.secondary}',
121
+ },
122
+ accent: {
123
+ DEFAULT: '${spec.colors.accent}',
124
+ },
125
+ },
126
+ fontFamily: {
127
+ sans: ['${spec.typography.fontFamily}'],
128
+ ${spec.typography.headingFont ? `heading: ['${spec.typography.headingFont}'],` : ''}
129
+ },
130
+ borderRadius: {
131
+ DEFAULT: '${getBorderRadius(spec.borderRadius)}',
132
+ },
133
+ },
134
+ },
135
+ plugins: [],
136
+ }
137
+ `;
138
+ }
139
+
140
+ /**
141
+ * Generate CSS variables from design spec
142
+ */
143
+ export function generateCSSVariables(spec: UIDesignSpec): string {
144
+ return `:root {
145
+ /* Colors */
146
+ --color-primary: ${spec.colors.primary};
147
+ --color-secondary: ${spec.colors.secondary};
148
+ --color-accent: ${spec.colors.accent};
149
+ --color-background: ${spec.colors.background};
150
+ --color-text: ${spec.colors.text};
151
+ --color-error: ${spec.colors.error};
152
+ --color-success: ${spec.colors.success};
153
+ --color-warning: ${spec.colors.warning};
154
+
155
+ /* Typography */
156
+ --font-family: ${spec.typography.fontFamily};
157
+ --font-size-base: ${spec.typography.baseSize};
158
+
159
+ /* Spacing */
160
+ --spacing-unit: ${getSpacingUnit(spec.spacing)};
161
+
162
+ /* Border Radius */
163
+ --border-radius: ${getBorderRadius(spec.borderRadius)};
164
+ }
165
+
166
+ ${spec.darkMode ? `
167
+ .dark {
168
+ --color-background: #111827;
169
+ --color-text: #f9fafb;
170
+ }
171
+ ` : ''}
172
+ `;
173
+ }
174
+
175
+ /**
176
+ * Helper to lighten a hex color
177
+ */
178
+ function lighten(hex: string, amount: number): string {
179
+ const num = parseInt(hex.replace('#', ''), 16);
180
+ const r = Math.min(255, Math.floor((num >> 16) + (255 - (num >> 16)) * amount));
181
+ const g = Math.min(255, Math.floor(((num >> 8) & 0x00FF) + (255 - ((num >> 8) & 0x00FF)) * amount));
182
+ const b = Math.min(255, Math.floor((num & 0x0000FF) + (255 - (num & 0x0000FF)) * amount));
183
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
184
+ }
185
+
186
+ /**
187
+ * Helper to darken a hex color
188
+ */
189
+ function darken(hex: string, amount: number): string {
190
+ const num = parseInt(hex.replace('#', ''), 16);
191
+ const r = Math.max(0, Math.floor((num >> 16) * (1 - amount)));
192
+ const g = Math.max(0, Math.floor(((num >> 8) & 0x00FF) * (1 - amount)));
193
+ const b = Math.max(0, Math.floor((num & 0x0000FF) * (1 - amount)));
194
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
195
+ }
196
+
197
+ /**
198
+ * Get spacing unit based on preference
199
+ */
200
+ function getSpacingUnit(spacing: 'compact' | 'comfortable' | 'spacious'): string {
201
+ switch (spacing) {
202
+ case 'compact': return '0.25rem';
203
+ case 'comfortable': return '0.5rem';
204
+ case 'spacious': return '0.75rem';
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Get border radius based on preference
210
+ */
211
+ function getBorderRadius(radius: 'none' | 'small' | 'medium' | 'large' | 'full'): string {
212
+ switch (radius) {
213
+ case 'none': return '0';
214
+ case 'small': return '0.25rem';
215
+ case 'medium': return '0.5rem';
216
+ case 'large': return '1rem';
217
+ case 'full': return '9999px';
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Component library setup instructions
223
+ */
224
+ export const COMPONENT_LIBRARY_SETUP: Record<string, { install: string; setup: string }> = {
225
+ shadcn: {
226
+ install: 'npx shadcn@latest init',
227
+ setup: `
228
+ # After init, add components as needed:
229
+ npx shadcn@latest add button
230
+ npx shadcn@latest add card
231
+ npx shadcn@latest add input
232
+ npx shadcn@latest add dialog
233
+ # etc.
234
+ `,
235
+ },
236
+ mui: {
237
+ install: 'npm install @mui/material @emotion/react @emotion/styled',
238
+ setup: `
239
+ // Wrap app in ThemeProvider
240
+ import { ThemeProvider, createTheme } from '@mui/material/styles';
241
+ const theme = createTheme({ /* your theme */ });
242
+ `,
243
+ },
244
+ chakra: {
245
+ install: 'npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion',
246
+ setup: `
247
+ // Wrap app in ChakraProvider
248
+ import { ChakraProvider } from '@chakra-ui/react';
249
+ `,
250
+ },
251
+ };
252
+
253
+ /**
254
+ * Visual regression test configuration
255
+ * TODO: Implement with Playwright
256
+ */
257
+ export interface VisualTestConfig {
258
+ /** Pages to test */
259
+ pages: Array<{
260
+ path: string;
261
+ name: string;
262
+ waitForSelector?: string;
263
+ }>;
264
+
265
+ /** Viewports to test */
266
+ viewports: Array<{
267
+ name: string;
268
+ width: number;
269
+ height: number;
270
+ }>;
271
+
272
+ /** Screenshot comparison threshold */
273
+ threshold: number;
274
+ }
275
+
276
+ /**
277
+ * Default visual test configuration
278
+ */
279
+ export const DEFAULT_VISUAL_TEST_CONFIG: VisualTestConfig = {
280
+ pages: [
281
+ { path: '/', name: 'home' },
282
+ { path: '/dashboard', name: 'dashboard' },
283
+ { path: '/board', name: 'board', waitForSelector: '[data-testid="kanban-board"]' },
284
+ ],
285
+ viewports: [
286
+ { name: 'mobile', width: 375, height: 667 },
287
+ { name: 'tablet', width: 768, height: 1024 },
288
+ { name: 'desktop', width: 1280, height: 800 },
289
+ ],
290
+ threshold: 0.1, // 10% difference allowed
291
+ };
292
+
293
+ /**
294
+ * Accessibility checks to perform
295
+ * TODO: Implement with axe-core
296
+ */
297
+ export const ACCESSIBILITY_CHECKS = [
298
+ 'color-contrast',
299
+ 'aria-roles',
300
+ 'keyboard-navigation',
301
+ 'focus-indicators',
302
+ 'alt-text',
303
+ 'form-labels',
304
+ 'heading-order',
305
+ 'link-purpose',
306
+ ];
307
+
308
+ /**
309
+ * FUTURE: Run visual regression tests
310
+ * Requires Playwright installation
311
+ */
312
+ export async function runVisualTests(
313
+ _projectDir: string,
314
+ _config: VisualTestConfig = DEFAULT_VISUAL_TEST_CONFIG
315
+ ): Promise<UIVerificationResult[]> {
316
+ // TODO: Implement with Playwright
317
+ // 1. Start dev server
318
+ // 2. Navigate to each page
319
+ // 3. Take screenshots at each viewport
320
+ // 4. Compare with baseline (if exists)
321
+ // 5. Report differences
322
+
323
+ console.warn('[UI Verification] Visual testing not yet implemented - requires Playwright');
324
+
325
+ return [{
326
+ passed: true,
327
+ category: 'Visual',
328
+ check: 'Visual regression tests',
329
+ message: 'Visual testing not yet implemented',
330
+ severity: 'warning',
331
+ }];
332
+ }
333
+
334
+ /**
335
+ * FUTURE: Run accessibility tests
336
+ * Requires axe-core installation
337
+ */
338
+ export async function runAccessibilityTests(
339
+ _projectDir: string,
340
+ _level: 'A' | 'AA' | 'AAA' = 'AA'
341
+ ): Promise<UIVerificationResult[]> {
342
+ // TODO: Implement with axe-core
343
+ // 1. Start dev server
344
+ // 2. Navigate to each page
345
+ // 3. Run axe-core analysis
346
+ // 4. Report violations
347
+
348
+ console.warn('[UI Verification] Accessibility testing not yet implemented - requires axe-core');
349
+
350
+ return [{
351
+ passed: true,
352
+ category: 'Accessibility',
353
+ check: 'WCAG compliance',
354
+ message: 'Accessibility testing not yet implemented',
355
+ severity: 'warning',
356
+ }];
357
+ }
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Workflow Logger
3
+ * Provides persistent logging of all workflow stages for transparency and debugging
4
+ */
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ /**
10
+ * Log entry structure
11
+ */
12
+ export interface LogEntry {
13
+ timestamp: string;
14
+ stage: WorkflowStage;
15
+ event: string;
16
+ message: string;
17
+ data?: Record<string, unknown>;
18
+ level: LogLevel;
19
+ }
20
+
21
+ /**
22
+ * Log levels for filtering and display
23
+ */
24
+ export type LogLevel = 'info' | 'warn' | 'error' | 'success' | 'debug';
25
+
26
+ /**
27
+ * Workflow stages for categorization
28
+ */
29
+ export type WorkflowStage =
30
+ | 'init'
31
+ | 'plan-generation'
32
+ | 'plan-parsing'
33
+ | 'consensus'
34
+ | 'arbitration'
35
+ | 'execution'
36
+ | 'task'
37
+ | 'milestone'
38
+ | 'testing'
39
+ | 'verification'
40
+ | 'ui-design'
41
+ | 'ui-setup'
42
+ | 'completion';
43
+
44
+ /**
45
+ * Workflow logger class that persists logs to the project's docs folder
46
+ */
47
+ export class WorkflowLogger {
48
+ private projectDir: string;
49
+ private logFile: string;
50
+ private entries: LogEntry[] = [];
51
+ private initialized: boolean = false;
52
+
53
+ constructor(projectDir: string) {
54
+ this.projectDir = projectDir;
55
+ this.logFile = path.join(projectDir, 'docs', 'WORKFLOW_LOG.md');
56
+ }
57
+
58
+ /**
59
+ * Initialize the logger and load existing entries
60
+ */
61
+ async initialize(): Promise<void> {
62
+ if (this.initialized) return;
63
+
64
+ const docsDir = path.join(this.projectDir, 'docs');
65
+
66
+ try {
67
+ await fs.mkdir(docsDir, { recursive: true });
68
+
69
+ // Check if log file exists
70
+ try {
71
+ const content = await fs.readFile(this.logFile, 'utf-8');
72
+ // Parse existing entries from the file
73
+ this.entries = this.parseExistingLog(content);
74
+ } catch {
75
+ // File doesn't exist yet, start fresh
76
+ this.entries = [];
77
+ }
78
+
79
+ this.initialized = true;
80
+ } catch (error) {
81
+ console.error('Failed to initialize workflow logger:', error);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Parse existing log entries from markdown file
87
+ */
88
+ private parseExistingLog(content: string): LogEntry[] {
89
+ const entries: LogEntry[] = [];
90
+
91
+ // Parse entries from the markdown format
92
+ const entryRegex = /### \[(\d{4}-\d{2}-\d{2}T[^\]]+)\] \[([^\]]+)\] \[([^\]]+)\] ([^\n]+)\n([\s\S]*?)(?=### \[|## Session|$)/g;
93
+
94
+ let match;
95
+ while ((match = entryRegex.exec(content)) !== null) {
96
+ try {
97
+ const entry: LogEntry = {
98
+ timestamp: match[1],
99
+ level: match[2] as LogLevel,
100
+ stage: match[3] as WorkflowStage,
101
+ event: '',
102
+ message: match[4].trim(),
103
+ };
104
+
105
+ // Try to parse data if present
106
+ const dataMatch = match[5]?.match(/```json\n([\s\S]*?)\n```/);
107
+ if (dataMatch) {
108
+ try {
109
+ entry.data = JSON.parse(dataMatch[1]);
110
+ } catch {
111
+ // Ignore JSON parse errors
112
+ }
113
+ }
114
+
115
+ entries.push(entry);
116
+ } catch {
117
+ // Skip malformed entries
118
+ }
119
+ }
120
+
121
+ return entries;
122
+ }
123
+
124
+ /**
125
+ * Log an entry to the workflow log
126
+ */
127
+ async log(
128
+ stage: WorkflowStage,
129
+ event: string,
130
+ message: string,
131
+ data?: Record<string, unknown>,
132
+ level: LogLevel = 'info'
133
+ ): Promise<void> {
134
+ await this.initialize();
135
+
136
+ const entry: LogEntry = {
137
+ timestamp: new Date().toISOString(),
138
+ stage,
139
+ event,
140
+ message,
141
+ data,
142
+ level,
143
+ };
144
+
145
+ this.entries.push(entry);
146
+ await this.persist();
147
+ }
148
+
149
+ /**
150
+ * Log info message
151
+ */
152
+ async info(stage: WorkflowStage, event: string, message: string, data?: Record<string, unknown>): Promise<void> {
153
+ await this.log(stage, event, message, data, 'info');
154
+ }
155
+
156
+ /**
157
+ * Log warning message
158
+ */
159
+ async warn(stage: WorkflowStage, event: string, message: string, data?: Record<string, unknown>): Promise<void> {
160
+ await this.log(stage, event, message, data, 'warn');
161
+ }
162
+
163
+ /**
164
+ * Log error message
165
+ */
166
+ async error(stage: WorkflowStage, event: string, message: string, data?: Record<string, unknown>): Promise<void> {
167
+ await this.log(stage, event, message, data, 'error');
168
+ }
169
+
170
+ /**
171
+ * Log success message
172
+ */
173
+ async success(stage: WorkflowStage, event: string, message: string, data?: Record<string, unknown>): Promise<void> {
174
+ await this.log(stage, event, message, data, 'success');
175
+ }
176
+
177
+ /**
178
+ * Log stage start
179
+ */
180
+ async stageStart(stage: WorkflowStage, description: string, data?: Record<string, unknown>): Promise<void> {
181
+ await this.info(stage, 'stage_start', `Starting: ${description}`, data);
182
+ }
183
+
184
+ /**
185
+ * Log stage completion
186
+ */
187
+ async stageComplete(stage: WorkflowStage, description: string, data?: Record<string, unknown>): Promise<void> {
188
+ await this.success(stage, 'stage_complete', `Completed: ${description}`, data);
189
+ }
190
+
191
+ /**
192
+ * Log stage failure
193
+ */
194
+ async stageFailed(stage: WorkflowStage, description: string, error: string, data?: Record<string, unknown>): Promise<void> {
195
+ await this.error(stage, 'stage_failed', `Failed: ${description} - ${error}`, data);
196
+ }
197
+
198
+ /**
199
+ * Persist log entries to file
200
+ */
201
+ private async persist(): Promise<void> {
202
+ try {
203
+ const content = this.formatMarkdown();
204
+ await fs.writeFile(this.logFile, content, 'utf-8');
205
+ } catch (error) {
206
+ console.error('Failed to persist workflow log:', error);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Format log entries as markdown
212
+ */
213
+ private formatMarkdown(): string {
214
+ const lines: string[] = [
215
+ '# Workflow Execution Log',
216
+ '',
217
+ 'This file tracks all stages of the Popeye workflow execution for transparency and debugging.',
218
+ '',
219
+ '---',
220
+ '',
221
+ ];
222
+
223
+ // Group entries by date
224
+ const entriesByDate = new Map<string, LogEntry[]>();
225
+
226
+ for (const entry of this.entries) {
227
+ const date = entry.timestamp.split('T')[0];
228
+ if (!entriesByDate.has(date)) {
229
+ entriesByDate.set(date, []);
230
+ }
231
+ entriesByDate.get(date)!.push(entry);
232
+ }
233
+
234
+ // Write entries grouped by date
235
+ for (const [date, dateEntries] of entriesByDate) {
236
+ lines.push(`## Session: ${date}`);
237
+ lines.push('');
238
+
239
+ for (const entry of dateEntries) {
240
+ const levelIcon = this.getLevelIcon(entry.level);
241
+ const time = entry.timestamp.split('T')[1].split('.')[0];
242
+
243
+ lines.push(`### [${time}] ${levelIcon} **${entry.stage}** - ${entry.message}`);
244
+
245
+ if (entry.data && Object.keys(entry.data).length > 0) {
246
+ lines.push('');
247
+ lines.push('<details>');
248
+ lines.push('<summary>Details</summary>');
249
+ lines.push('');
250
+ lines.push('```json');
251
+ lines.push(JSON.stringify(entry.data, null, 2));
252
+ lines.push('```');
253
+ lines.push('</details>');
254
+ }
255
+
256
+ lines.push('');
257
+ }
258
+ }
259
+
260
+ // Add summary statistics
261
+ lines.push('---');
262
+ lines.push('');
263
+ lines.push('## Summary Statistics');
264
+ lines.push('');
265
+ lines.push(`- **Total Entries:** ${this.entries.length}`);
266
+ lines.push(`- **Errors:** ${this.entries.filter(e => e.level === 'error').length}`);
267
+ lines.push(`- **Warnings:** ${this.entries.filter(e => e.level === 'warn').length}`);
268
+ lines.push(`- **Successful Steps:** ${this.entries.filter(e => e.level === 'success').length}`);
269
+ lines.push('');
270
+
271
+ return lines.join('\n');
272
+ }
273
+
274
+ /**
275
+ * Get icon for log level
276
+ */
277
+ private getLevelIcon(level: LogLevel): string {
278
+ switch (level) {
279
+ case 'error':
280
+ return '[ERROR]';
281
+ case 'warn':
282
+ return '[WARN]';
283
+ case 'success':
284
+ return '[OK]';
285
+ case 'debug':
286
+ return '[DEBUG]';
287
+ default:
288
+ return '[INFO]';
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Get all log entries
294
+ */
295
+ getEntries(): LogEntry[] {
296
+ return [...this.entries];
297
+ }
298
+
299
+ /**
300
+ * Get entries for a specific stage
301
+ */
302
+ getEntriesForStage(stage: WorkflowStage): LogEntry[] {
303
+ return this.entries.filter(e => e.stage === stage);
304
+ }
305
+
306
+ /**
307
+ * Get error entries
308
+ */
309
+ getErrors(): LogEntry[] {
310
+ return this.entries.filter(e => e.level === 'error');
311
+ }
312
+
313
+ /**
314
+ * Clear the log (for testing or reset)
315
+ */
316
+ async clear(): Promise<void> {
317
+ this.entries = [];
318
+ await this.persist();
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Global logger instances cache
324
+ */
325
+ const loggerCache = new Map<string, WorkflowLogger>();
326
+
327
+ /**
328
+ * Get or create a workflow logger for a project
329
+ */
330
+ export function getWorkflowLogger(projectDir: string): WorkflowLogger {
331
+ const normalizedPath = path.resolve(projectDir);
332
+
333
+ if (!loggerCache.has(normalizedPath)) {
334
+ loggerCache.set(normalizedPath, new WorkflowLogger(normalizedPath));
335
+ }
336
+
337
+ return loggerCache.get(normalizedPath)!;
338
+ }
339
+
340
+ /**
341
+ * Quick logging functions for common use cases
342
+ */
343
+ export async function logWorkflowEvent(
344
+ projectDir: string,
345
+ stage: WorkflowStage,
346
+ event: string,
347
+ message: string,
348
+ data?: Record<string, unknown>,
349
+ level: LogLevel = 'info'
350
+ ): Promise<void> {
351
+ const logger = getWorkflowLogger(projectDir);
352
+ await logger.log(stage, event, message, data, level);
353
+ }