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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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
|
+
}
|