wogiflow 1.0.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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,683 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Code Generator from Figma Decisions
5
+ *
6
+ * Takes confirmed decisions and generates:
7
+ * - Import statements for existing components
8
+ * - Variant additions for existing components
9
+ * - Prompts for Claude to generate new components
10
+ * - Composition code showing how everything fits together
11
+ *
12
+ * The generator creates prompts that Claude can use to generate
13
+ * framework-specific code based on the project's tech stack.
14
+ *
15
+ * Usage:
16
+ * flow figma generate <decisions.json> # Generate from decisions
17
+ * flow figma generate # Use saved decisions
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { detectFramework } = require('./flow-figma-index');
23
+ const { getProjectRoot, addRequestLogEntry, addAppMapComponent } = require('./flow-utils');
24
+
25
+ const PROJECT_ROOT = getProjectRoot();
26
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
27
+ const DECISIONS_PATH = path.join(WORKFLOW_DIR, 'state', 'figma-decisions.json');
28
+ const REGISTRY_PATH = path.join(WORKFLOW_DIR, 'state', 'component-registry.json');
29
+
30
+ // ============================================================
31
+ // Framework-Specific Templates
32
+ // ============================================================
33
+
34
+ const FRAMEWORK_TEMPLATES = {
35
+ react: {
36
+ component: `import React from 'react';
37
+
38
+ interface {{name}}Props {
39
+ className?: string;
40
+ children?: React.ReactNode;
41
+ {{props}}
42
+ }
43
+
44
+ /**
45
+ * {{name}} component
46
+ * Generated from Figma design
47
+ * Type: {{type}}
48
+ */
49
+ export function {{name}}({ className, children{{propNames}} }: {{name}}Props) {
50
+ return (
51
+ <div className={className}>
52
+ {children}
53
+ </div>
54
+ );
55
+ }
56
+
57
+ export default {{name}};`,
58
+
59
+ variant: `// Add to {{componentName}}'s variant type:
60
+ // variant?: '{{variantName}}' | ... existing variants ...
61
+
62
+ // Add to component's styling/classes:
63
+ // {{variantName}}: {
64
+ {{cssProperties}}
65
+ // }`,
66
+
67
+ imports: `import { {{componentName}} } from '@/components/{{path}}';`,
68
+
69
+ prompt: `Create a React component called {{name}} with these properties:
70
+
71
+ **Figma Properties:**
72
+ {{cssProperties}}
73
+
74
+ **Structure:**
75
+ - Type: {{type}}
76
+ - Has children: {{hasChildren}}
77
+ {{textContent}}
78
+
79
+ **Requirements:**
80
+ 1. Use TypeScript with proper interfaces
81
+ 2. Use Tailwind CSS classes (or project's styling approach)
82
+ 3. Make it responsive
83
+ 4. Add proper accessibility attributes
84
+ 5. Export both named and default exports`
85
+ },
86
+
87
+ vue: {
88
+ component: `<script setup lang="ts">
89
+ interface Props {
90
+ class?: string;
91
+ {{props}}
92
+ }
93
+
94
+ defineProps<Props>();
95
+ </script>
96
+
97
+ <template>
98
+ <div :class="props.class">
99
+ <slot />
100
+ </div>
101
+ </template>
102
+
103
+ <style scoped>
104
+ /* Add component styles here */
105
+ </style>`,
106
+
107
+ variant: `<!-- Add to {{componentName}}'s props:
108
+ variant?: '{{variantName}}' | ... existing variants ...
109
+ -->
110
+
111
+ <!-- Add conditional classes:
112
+ :class="{ '{{variantName}}': variant === '{{variantName}}' }"
113
+ -->`,
114
+
115
+ imports: `import {{componentName}} from '@/components/{{path}}';`,
116
+
117
+ prompt: `Create a Vue 3 component called {{name}} with these properties:
118
+
119
+ **Figma Properties:**
120
+ {{cssProperties}}
121
+
122
+ **Structure:**
123
+ - Type: {{type}}
124
+ - Has children: {{hasChildren}} (use <slot> if true)
125
+ {{textContent}}
126
+
127
+ **Requirements:**
128
+ 1. Use Composition API with <script setup>
129
+ 2. Use TypeScript
130
+ 3. Use Tailwind CSS or scoped styles
131
+ 4. Make it responsive
132
+ 5. Add proper accessibility`
133
+ },
134
+
135
+ svelte: {
136
+ component: `<script lang="ts">
137
+ export let class: string = '';
138
+ {{props}}
139
+ </script>
140
+
141
+ <div class={class}>
142
+ <slot />
143
+ </div>
144
+
145
+ <style>
146
+ /* Add component styles here */
147
+ </style>`,
148
+
149
+ variant: `<!-- Add to {{componentName}}:
150
+ export let variant: '{{variantName}}' | ... = 'default';
151
+ -->
152
+
153
+ <!-- Add conditional classes:
154
+ class:{{variantName}}={variant === '{{variantName}}'}
155
+ -->`,
156
+
157
+ imports: `import {{componentName}} from '$lib/components/{{path}}';`,
158
+
159
+ prompt: `Create a Svelte component called {{name}} with these properties:
160
+
161
+ **Figma Properties:**
162
+ {{cssProperties}}
163
+
164
+ **Structure:**
165
+ - Type: {{type}}
166
+ - Has children: {{hasChildren}} (use <slot> if true)
167
+ {{textContent}}
168
+
169
+ **Requirements:**
170
+ 1. Use TypeScript
171
+ 2. Use Tailwind CSS or component styles
172
+ 3. Make it responsive
173
+ 4. Export props properly`
174
+ },
175
+
176
+ angular: {
177
+ component: `import { Component, Input } from '@angular/core';
178
+
179
+ @Component({
180
+ selector: 'app-{{kebabName}}',
181
+ template: \`
182
+ <div [class]="className">
183
+ <ng-content></ng-content>
184
+ </div>
185
+ \`,
186
+ styles: []
187
+ })
188
+ export class {{name}}Component {
189
+ @Input() className: string = '';
190
+ {{props}}
191
+ }`,
192
+
193
+ variant: `// Add to {{componentName}}Component:
194
+ // @Input() variant: '{{variantName}}' | ... = 'default';
195
+
196
+ // Add to template:
197
+ // [ngClass]="{'{{variantName}}': variant === '{{variantName}}'}"`,
198
+
199
+ imports: `import { {{componentName}}Component } from './{{path}}';`,
200
+
201
+ prompt: `Create an Angular component called {{name}} with these properties:
202
+
203
+ **Figma Properties:**
204
+ {{cssProperties}}
205
+
206
+ **Structure:**
207
+ - Type: {{type}}
208
+ - Has children: {{hasChildren}} (use <ng-content> if true)
209
+ {{textContent}}
210
+
211
+ **Requirements:**
212
+ 1. Use standalone component (Angular 15+)
213
+ 2. Use TypeScript
214
+ 3. Use Tailwind CSS or component styles
215
+ 4. Make it responsive
216
+ 5. Add proper accessibility`
217
+ }
218
+ };
219
+
220
+ // ============================================================
221
+ // Code Generator
222
+ // ============================================================
223
+
224
+ class CodeGenerator {
225
+ constructor(decisions, options = {}) {
226
+ this.decisions = Array.isArray(decisions) ? decisions : decisions.decisions || [];
227
+ this.framework = options.framework || detectFramework(PROJECT_ROOT);
228
+ this.templates = FRAMEWORK_TEMPLATES[this.framework] || FRAMEWORK_TEMPLATES.react;
229
+ this.registry = this.loadRegistry();
230
+
231
+ this.output = {
232
+ framework: this.framework,
233
+ imports: [],
234
+ variants: [],
235
+ newComponents: [],
236
+ prompts: [],
237
+ composition: null
238
+ };
239
+ }
240
+
241
+ loadRegistry() {
242
+ if (fs.existsSync(REGISTRY_PATH)) {
243
+ return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8'));
244
+ }
245
+ return { components: [] };
246
+ }
247
+
248
+ generate() {
249
+ for (const decision of this.decisions) {
250
+ switch (decision.action) {
251
+ case 'use':
252
+ case 'use-with-adjustments':
253
+ this.addImport(decision);
254
+ break;
255
+ case 'add-variant':
256
+ this.addVariant(decision);
257
+ this.addImport(decision); // Also import the component
258
+ break;
259
+ case 'create-new':
260
+ this.createComponent(decision);
261
+ break;
262
+ }
263
+ }
264
+
265
+ // Generate composition code
266
+ this.generateComposition();
267
+
268
+ return this.output;
269
+ }
270
+
271
+ addImport(decision) {
272
+ const comp = decision.existingComponent;
273
+ if (!comp) return;
274
+
275
+ const importPath = comp.path.replace(/\.(tsx?|jsx?|vue|svelte)$/, '');
276
+
277
+ this.output.imports.push({
278
+ componentName: comp.name,
279
+ path: importPath,
280
+ importStatement: this.templates.imports
281
+ .replace(/\{\{componentName\}\}/g, comp.name)
282
+ .replace(/\{\{path\}\}/g, importPath),
283
+ usage: this.generateUsage(decision),
284
+ adjustments: decision.action === 'use-with-adjustments' ? decision.differences : null
285
+ });
286
+ }
287
+
288
+ addVariant(decision) {
289
+ const comp = decision.existingComponent;
290
+ if (!comp) return;
291
+
292
+ const figma = decision.figmaComponent;
293
+
294
+ this.output.variants.push({
295
+ componentName: comp.name,
296
+ path: comp.path,
297
+ variantName: decision.variantName,
298
+ figmaProperties: figma.css,
299
+ instructions: this.templates.variant
300
+ .replace(/\{\{componentName\}\}/g, comp.name)
301
+ .replace(/\{\{variantName\}\}/g, decision.variantName)
302
+ .replace(/\{\{cssProperties\}\}/g, this.formatCSSProperties(figma.css)),
303
+ prompt: this.generateVariantPrompt(decision)
304
+ });
305
+ }
306
+
307
+ createComponent(decision) {
308
+ const figma = decision.figmaComponent;
309
+ const componentName = decision.componentName || this.suggestName(figma.name);
310
+ const kebabName = componentName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
311
+
312
+ // Generate the prompt for Claude
313
+ const prompt = this.templates.prompt
314
+ .replace(/\{\{name\}\}/g, componentName)
315
+ .replace(/\{\{kebabName\}\}/g, kebabName)
316
+ .replace(/\{\{type\}\}/g, figma.type)
317
+ .replace(/\{\{hasChildren\}\}/g, figma.structure?.hasChildren ? 'true' : 'false')
318
+ .replace(/\{\{textContent\}\}/g, figma.textContent ? `- Text content: "${figma.textContent}"` : '')
319
+ .replace(/\{\{cssProperties\}\}/g, this.formatCSSPropertiesForPrompt(figma.css));
320
+
321
+ this.output.newComponents.push({
322
+ componentName: componentName,
323
+ suggestedPath: this.suggestPath(componentName, figma.type),
324
+ type: figma.type,
325
+ figmaProperties: figma.css,
326
+ template: this.generateTemplate(componentName, figma),
327
+ prompt: prompt
328
+ });
329
+
330
+ this.output.prompts.push({
331
+ componentName: componentName,
332
+ type: 'create',
333
+ prompt: prompt
334
+ });
335
+ }
336
+
337
+ generateUsage(decision) {
338
+ const comp = decision.existingComponent;
339
+ if (!comp) return '';
340
+
341
+ const figma = decision.figmaComponent;
342
+ const props = [];
343
+
344
+ // Add variant if exists
345
+ if (comp.variants?.length > 0) {
346
+ const matchingVariant = this.findMatchingVariant(comp, figma);
347
+ if (matchingVariant) {
348
+ props.push(`${matchingVariant.name}="${matchingVariant.value}"`);
349
+ }
350
+ }
351
+
352
+ // Framework-specific usage
353
+ switch (this.framework) {
354
+ case 'vue':
355
+ return `<${comp.name}${props.length ? ' ' + props.join(' ') : ''} />`;
356
+ case 'svelte':
357
+ return `<${comp.name}${props.length ? ' ' + props.join(' ') : ''} />`;
358
+ case 'angular':
359
+ const selector = `app-${comp.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`;
360
+ return `<${selector}${props.length ? ' ' + props.join(' ') : ''}></${selector}>`;
361
+ default:
362
+ return `<${comp.name}${props.length ? ' ' + props.join(' ') : ''} />`;
363
+ }
364
+ }
365
+
366
+ findMatchingVariant(comp, figma) {
367
+ for (const variant of comp.variants || []) {
368
+ const figmaName = figma.name.toLowerCase();
369
+ for (const option of variant.options) {
370
+ if (figmaName.includes(option.toLowerCase())) {
371
+ return { name: variant.name, value: option };
372
+ }
373
+ }
374
+ }
375
+ return null;
376
+ }
377
+
378
+ generateVariantPrompt(decision) {
379
+ const comp = decision.existingComponent;
380
+ const figma = decision.figmaComponent;
381
+
382
+ return `Add a new variant "${decision.variantName}" to the ${comp.name} component.
383
+
384
+ **Current component location:** ${comp.path}
385
+
386
+ **New variant properties from Figma:**
387
+ ${this.formatCSSPropertiesForPrompt(figma.css)}
388
+
389
+ **Instructions:**
390
+ 1. Add "${decision.variantName}" to the variant type/prop
391
+ 2. Add the corresponding styles for this variant
392
+ 3. Ensure existing variants still work
393
+ 4. Update any variant-related documentation/types`;
394
+ }
395
+
396
+ generateTemplate(name, figma) {
397
+ const props = this.generatePropsFromFigma(figma);
398
+ const propNames = props.length > 0 ? ', ' + props.map(p => p.name).join(', ') : '';
399
+
400
+ return this.templates.component
401
+ .replace(/\{\{name\}\}/g, name)
402
+ .replace(/\{\{kebabName\}\}/g, name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase())
403
+ .replace(/\{\{type\}\}/g, figma.type)
404
+ .replace(/\{\{props\}\}/g, props.map(p => ` ${p.name}?: ${p.type};`).join('\n'))
405
+ .replace(/\{\{propNames\}\}/g, propNames);
406
+ }
407
+
408
+ generatePropsFromFigma(figma) {
409
+ const props = [];
410
+
411
+ // Check for variant-like patterns in the name
412
+ const variantMatch = figma.name.match(/\b(primary|secondary|tertiary|success|error|warning|info)\b/i);
413
+ if (variantMatch) {
414
+ props.push({ name: 'variant', type: `'${variantMatch[1].toLowerCase()}'` });
415
+ }
416
+
417
+ const sizeMatch = figma.name.match(/\b(sm|md|lg|xl|small|medium|large)\b/i);
418
+ if (sizeMatch) {
419
+ props.push({ name: 'size', type: `'${sizeMatch[1].toLowerCase()}'` });
420
+ }
421
+
422
+ // Check if it has text content
423
+ if (figma.structure?.hasText || figma.textContent) {
424
+ props.push({ name: 'label', type: 'string' });
425
+ }
426
+
427
+ // Check if it has an icon
428
+ if (figma.structure?.hasIcon) {
429
+ props.push({ name: 'icon', type: 'React.ReactNode' }); // Framework-agnostic
430
+ }
431
+
432
+ return props;
433
+ }
434
+
435
+ formatCSSProperties(css) {
436
+ if (!css) return '// No CSS properties';
437
+
438
+ const lines = [];
439
+
440
+ Object.entries(css).forEach(([category, props]) => {
441
+ if (Array.isArray(props) && props.length > 0) {
442
+ lines.push(` // ${category}:`);
443
+ props.forEach(prop => {
444
+ const value = typeof prop.value === 'object'
445
+ ? prop.shorthand || JSON.stringify(prop.value)
446
+ : prop.value;
447
+ lines.push(` // ${prop.property}: ${value}`);
448
+ });
449
+ }
450
+ });
451
+
452
+ return lines.join('\n');
453
+ }
454
+
455
+ formatCSSPropertiesForPrompt(css) {
456
+ if (!css) return 'No CSS properties extracted';
457
+
458
+ const lines = [];
459
+
460
+ Object.entries(css).forEach(([category, props]) => {
461
+ if (Array.isArray(props) && props.length > 0) {
462
+ lines.push(`${category.charAt(0).toUpperCase() + category.slice(1)}:`);
463
+ props.forEach(prop => {
464
+ const value = typeof prop.value === 'object'
465
+ ? prop.shorthand || JSON.stringify(prop.value)
466
+ : prop.value;
467
+ lines.push(` - ${prop.property}: ${value}`);
468
+ });
469
+ }
470
+ });
471
+
472
+ return lines.join('\n');
473
+ }
474
+
475
+ suggestPath(name, type) {
476
+ const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
477
+
478
+ switch (this.framework) {
479
+ case 'vue':
480
+ return `src/components/${type}s/${name}.vue`;
481
+ case 'svelte':
482
+ return `src/lib/components/${type}s/${name}.svelte`;
483
+ case 'angular':
484
+ return `src/app/components/${type}s/${kebabName}/${kebabName}.component.ts`;
485
+ default:
486
+ return `src/components/${type}s/${name}.tsx`;
487
+ }
488
+ }
489
+
490
+ suggestName(figmaName) {
491
+ return (figmaName || 'Component')
492
+ .replace(/[^a-zA-Z0-9\s]/g, '')
493
+ .split(/\s+/)
494
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
495
+ .join('');
496
+ }
497
+
498
+ generateComposition() {
499
+ const imports = this.output.imports.map(i => i.importStatement).join('\n');
500
+ const usages = this.output.imports.map(i => ` ${i.usage}`).join('\n');
501
+ const newUsages = this.output.newComponents.map(c => ` <${c.componentName} />`).join('\n');
502
+
503
+ const allUsages = [usages, newUsages].filter(Boolean).join('\n');
504
+
505
+ // Framework-specific composition
506
+ let composition;
507
+ switch (this.framework) {
508
+ case 'vue':
509
+ composition = `<script setup>
510
+ ${imports}
511
+ </script>
512
+
513
+ <template>
514
+ <div class="composed-view">
515
+ ${allUsages}
516
+ </div>
517
+ </template>`;
518
+ break;
519
+
520
+ case 'svelte':
521
+ composition = `<script>
522
+ ${imports}
523
+ </script>
524
+
525
+ <div class="composed-view">
526
+ ${allUsages}
527
+ </div>`;
528
+ break;
529
+
530
+ case 'angular':
531
+ composition = `// Ensure components are imported in the module/standalone
532
+
533
+ @Component({
534
+ template: \`
535
+ <div class="composed-view">
536
+ ${allUsages}
537
+ </div>
538
+ \`
539
+ })`;
540
+ break;
541
+
542
+ default:
543
+ composition = `${imports}
544
+
545
+ export function ComposedView() {
546
+ return (
547
+ <div className="composed-view">
548
+ ${allUsages}
549
+ </div>
550
+ );
551
+ }`;
552
+ }
553
+
554
+ this.output.composition = {
555
+ imports: imports,
556
+ usages: allUsages,
557
+ fullExample: composition
558
+ };
559
+ }
560
+ }
561
+
562
+ // ============================================================
563
+ // CLI
564
+ // ============================================================
565
+
566
+ async function main() {
567
+ const [,, input] = process.argv;
568
+
569
+ // Load decisions
570
+ let decisionsPath = input || DECISIONS_PATH;
571
+
572
+ if (!fs.existsSync(decisionsPath)) {
573
+ console.error(`❌ Decisions file not found: ${decisionsPath}`);
574
+ console.error(` Run "flow figma confirm" first to create decisions.`);
575
+ process.exit(1);
576
+ }
577
+
578
+ const decisions = JSON.parse(fs.readFileSync(decisionsPath, 'utf-8'));
579
+ const generator = new CodeGenerator(decisions);
580
+ const output = generator.generate();
581
+
582
+ // Print summary
583
+ console.log(`
584
+ ╔═══════════════════════════════════════════════════════════════════╗
585
+ ║ FIGMA CODE GENERATOR ║
586
+ ╚═══════════════════════════════════════════════════════════════════╝
587
+
588
+ Framework: ${output.framework}
589
+ `);
590
+
591
+ if (output.imports.length > 0) {
592
+ console.log(`📦 Components to Import (${output.imports.length}):`);
593
+ output.imports.forEach(i => {
594
+ console.log(` • ${i.componentName} → ${i.usage}`);
595
+ });
596
+ console.log('');
597
+ }
598
+
599
+ if (output.variants.length > 0) {
600
+ console.log(`➕ Variants to Add (${output.variants.length}):`);
601
+ output.variants.forEach(v => {
602
+ console.log(` • ${v.componentName} + "${v.variantName}"`);
603
+ });
604
+ console.log('');
605
+ }
606
+
607
+ if (output.newComponents.length > 0) {
608
+ console.log(`🆕 New Components to Create (${output.newComponents.length}):`);
609
+ output.newComponents.forEach(c => {
610
+ console.log(` • ${c.componentName} → ${c.suggestedPath}`);
611
+ });
612
+ console.log('');
613
+ }
614
+
615
+ // Save output
616
+ const outputPath = path.join(WORKFLOW_DIR, 'state', 'figma-output.json');
617
+ fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
618
+ console.log(`📄 Full output saved to: ${path.relative(PROJECT_ROOT, outputPath)}`);
619
+
620
+ // Print prompts for new components
621
+ if (output.prompts.length > 0) {
622
+ console.log(`
623
+ ${'─'.repeat(70)}
624
+ PROMPTS FOR CLAUDE (copy these to generate components):
625
+ ${'─'.repeat(70)}
626
+ `);
627
+ output.prompts.forEach((p, i) => {
628
+ console.log(`\n=== ${i + 1}. ${p.componentName} ===\n`);
629
+ console.log(p.prompt);
630
+ });
631
+ }
632
+
633
+ // Log to request-log
634
+ const summary = [];
635
+ if (output.imports.length > 0) summary.push(`${output.imports.length} imports`);
636
+ if (output.variants.length > 0) summary.push(`${output.variants.length} variants`);
637
+ if (output.newComponents.length > 0) summary.push(`${output.newComponents.length} new components`);
638
+
639
+ if (summary.length > 0) {
640
+ const componentNames = [
641
+ ...output.imports.map(i => i.componentName),
642
+ ...output.variants.map(v => v.componentName),
643
+ ...output.newComponents.map(c => c.componentName)
644
+ ];
645
+
646
+ addRequestLogEntry({
647
+ type: 'new',
648
+ tags: ['#figma', ...componentNames.slice(0, 3).map(n => `#component:${n}`)],
649
+ request: 'Figma design analysis and code generation',
650
+ result: `Generated: ${summary.join(', ')}`,
651
+ files: [path.relative(PROJECT_ROOT, outputPath)]
652
+ });
653
+
654
+ console.log('\n📝 Logged to request-log.md');
655
+ }
656
+
657
+ // Add new components to app-map
658
+ if (output.newComponents.length > 0) {
659
+ let addedCount = 0;
660
+ for (const comp of output.newComponents) {
661
+ const added = addAppMapComponent({
662
+ name: comp.componentName,
663
+ type: 'component',
664
+ path: comp.suggestedPath,
665
+ variants: [],
666
+ description: `Generated from Figma (${output.framework})`
667
+ });
668
+ if (added) addedCount++;
669
+ }
670
+ if (addedCount > 0) {
671
+ console.log(`📋 Added ${addedCount} component(s) to app-map.md`);
672
+ }
673
+ }
674
+ }
675
+
676
+ module.exports = { CodeGenerator, FRAMEWORK_TEMPLATES };
677
+
678
+ if (require.main === module) {
679
+ main().catch(err => {
680
+ console.error(`Error: ${err.message}`);
681
+ process.exit(1);
682
+ });
683
+ }