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,909 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Component Registry Builder
5
+ *
6
+ * Scans the codebase and builds an index of existing components
7
+ * with their properties, variants, CSS tokens, and structure.
8
+ *
9
+ * Supports: React, Vue, Svelte, Angular (auto-detected)
10
+ *
11
+ * Usage:
12
+ * flow figma scan # Full scan of codebase
13
+ * flow figma show <component> # Show component details
14
+ * flow figma export # Export registry as JSON
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const { getProjectRoot } = require('./flow-utils');
20
+
21
+ const PROJECT_ROOT = getProjectRoot();
22
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
23
+ const REGISTRY_PATH = path.join(WORKFLOW_DIR, 'state', 'component-registry.json');
24
+
25
+ // ============================================================
26
+ // Configuration
27
+ // ============================================================
28
+
29
+ const DEFAULT_CONFIG = {
30
+ componentDirs: [
31
+ 'src/components',
32
+ 'components',
33
+ 'src/ui',
34
+ 'ui',
35
+ 'app/components',
36
+ 'src/lib/components',
37
+ 'lib/components'
38
+ ],
39
+
40
+ frameworkPatterns: {
41
+ react: ['**/*.tsx', '**/*.jsx'],
42
+ vue: ['**/*.vue'],
43
+ svelte: ['**/*.svelte'],
44
+ angular: ['**/*.component.ts']
45
+ },
46
+
47
+ excludePatterns: [
48
+ '**/*.test.*',
49
+ '**/*.spec.*',
50
+ '**/*.stories.*',
51
+ '**/node_modules/**',
52
+ '**/__tests__/**',
53
+ '**/__mocks__/**',
54
+ '**/dist/**',
55
+ '**/build/**'
56
+ ],
57
+
58
+ tokenSources: [
59
+ 'src/styles/tokens.css',
60
+ 'src/styles/variables.css',
61
+ 'src/theme.ts',
62
+ 'src/theme.js',
63
+ 'tailwind.config.js',
64
+ 'tailwind.config.ts',
65
+ 'src/styles/theme.css'
66
+ ]
67
+ };
68
+
69
+ // ============================================================
70
+ // Framework Detection
71
+ // ============================================================
72
+
73
+ function detectFramework(projectRoot) {
74
+ const packageJsonPath = path.join(projectRoot, 'package.json');
75
+
76
+ if (!fs.existsSync(packageJsonPath)) {
77
+ return 'react'; // Default
78
+ }
79
+
80
+ try {
81
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
82
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
83
+
84
+ if (deps['vue'] || deps['nuxt']) return 'vue';
85
+ if (deps['svelte'] || deps['@sveltejs/kit']) return 'svelte';
86
+ if (deps['@angular/core']) return 'angular';
87
+ if (deps['react'] || deps['next'] || deps['gatsby']) return 'react';
88
+
89
+ return 'react'; // Default
90
+ } catch {
91
+ return 'react';
92
+ }
93
+ }
94
+
95
+ // ============================================================
96
+ // Component Scanner
97
+ // ============================================================
98
+
99
+ class ComponentScanner {
100
+ constructor(config = {}) {
101
+ this.config = { ...DEFAULT_CONFIG, ...config };
102
+ this.framework = detectFramework(PROJECT_ROOT);
103
+ this.registry = {
104
+ version: '1.0.0',
105
+ scannedAt: null,
106
+ projectRoot: PROJECT_ROOT,
107
+ framework: this.framework,
108
+ components: [],
109
+ tokens: {
110
+ colors: {},
111
+ spacing: {},
112
+ typography: {},
113
+ radius: {},
114
+ shadows: {}
115
+ }
116
+ };
117
+
118
+ // Try to load babel for better parsing
119
+ this.parser = null;
120
+ this.traverse = null;
121
+ try {
122
+ this.parser = require('@babel/parser');
123
+ this.traverse = require('@babel/traverse').default;
124
+ } catch {
125
+ // Babel not available, will use regex parsing
126
+ }
127
+ }
128
+
129
+ async scan() {
130
+ console.log('\n🔍 Scanning codebase for components...\n');
131
+ console.log(` Framework detected: ${this.framework}`);
132
+
133
+ // Find component directory
134
+ const componentDir = this.findComponentDir();
135
+ if (!componentDir) {
136
+ console.error('❌ No component directory found');
137
+ console.log(' Searched:', this.config.componentDirs.join(', '));
138
+ return null;
139
+ }
140
+
141
+ console.log(` Component directory: ${path.relative(PROJECT_ROOT, componentDir)}`);
142
+ console.log(` Parser: ${this.parser ? 'Babel AST' : 'Regex-based'}`);
143
+
144
+ // Scan for tokens first
145
+ await this.scanTokens();
146
+
147
+ // Scan components
148
+ await this.scanDirectory(componentDir);
149
+
150
+ // Post-process: calculate signatures
151
+ this.calculateSignatures();
152
+
153
+ this.registry.scannedAt = new Date().toISOString();
154
+
155
+ // Save registry
156
+ this.saveRegistry();
157
+
158
+ console.log(`\n✅ Found ${this.registry.components.length} components`);
159
+ console.log(`📄 Registry saved to: ${path.relative(PROJECT_ROOT, REGISTRY_PATH)}`);
160
+
161
+ return this.registry;
162
+ }
163
+
164
+ findComponentDir() {
165
+ for (const dir of this.config.componentDirs) {
166
+ const fullPath = path.join(PROJECT_ROOT, dir);
167
+ if (fs.existsSync(fullPath)) {
168
+ return fullPath;
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+
174
+ async scanTokens() {
175
+ console.log('\n📊 Scanning design tokens...');
176
+
177
+ for (const tokenSource of this.config.tokenSources) {
178
+ const fullPath = path.join(PROJECT_ROOT, tokenSource);
179
+ if (fs.existsSync(fullPath)) {
180
+ console.log(` Found: ${tokenSource}`);
181
+ await this.parseTokenFile(fullPath);
182
+ }
183
+ }
184
+ }
185
+
186
+ async parseTokenFile(filePath) {
187
+ const content = fs.readFileSync(filePath, 'utf-8');
188
+ const ext = path.extname(filePath);
189
+ const filename = path.basename(filePath);
190
+
191
+ if (ext === '.css') {
192
+ this.parseCSSTokens(content);
193
+ } else if (filename.includes('tailwind')) {
194
+ this.parseTailwindConfig(content);
195
+ } else if (ext === '.ts' || ext === '.js') {
196
+ this.parseJSTokens(content);
197
+ }
198
+ }
199
+
200
+ parseCSSTokens(content) {
201
+ const varRegex = /--([a-zA-Z0-9-]+):\s*([^;]+);/g;
202
+ let match;
203
+
204
+ while ((match = varRegex.exec(content)) !== null) {
205
+ const [, name, value] = match;
206
+ const trimmedValue = value.trim();
207
+
208
+ if (name.includes('color') || name.includes('bg') ||
209
+ trimmedValue.startsWith('#') || trimmedValue.startsWith('rgb') ||
210
+ trimmedValue.startsWith('hsl')) {
211
+ this.registry.tokens.colors[name] = trimmedValue;
212
+ } else if (name.includes('spacing') || name.includes('gap') ||
213
+ name.includes('margin') || name.includes('padding')) {
214
+ this.registry.tokens.spacing[name] = trimmedValue;
215
+ } else if (name.includes('font') || name.includes('text') ||
216
+ name.includes('line-height') || name.includes('letter')) {
217
+ this.registry.tokens.typography[name] = trimmedValue;
218
+ } else if (name.includes('radius') || name.includes('rounded')) {
219
+ this.registry.tokens.radius[name] = trimmedValue;
220
+ } else if (name.includes('shadow')) {
221
+ this.registry.tokens.shadows[name] = trimmedValue;
222
+ }
223
+ }
224
+ }
225
+
226
+ parseTailwindConfig(content) {
227
+ // Extract theme extensions from Tailwind
228
+ const colorMatch = content.match(/colors\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
229
+ if (colorMatch) {
230
+ const colorRegex = /['"]?([a-zA-Z0-9-]+)['"]?\s*:\s*['"]([^'"]+)['"]/g;
231
+ let match;
232
+ while ((match = colorRegex.exec(colorMatch[1])) !== null) {
233
+ this.registry.tokens.colors[match[1]] = match[2];
234
+ }
235
+ }
236
+
237
+ const spacingMatch = content.match(/spacing\s*:\s*\{([^}]+)\}/s);
238
+ if (spacingMatch) {
239
+ const spacingRegex = /['"]?([a-zA-Z0-9-]+)['"]?\s*:\s*['"]([^'"]+)['"]/g;
240
+ let match;
241
+ while ((match = spacingRegex.exec(spacingMatch[1])) !== null) {
242
+ this.registry.tokens.spacing[match[1]] = match[2];
243
+ }
244
+ }
245
+ }
246
+
247
+ parseJSTokens(content) {
248
+ // Parse JS/TS theme files
249
+ const colorMatch = content.match(/colors?\s*[=:]\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
250
+ if (colorMatch) {
251
+ const colorRegex = /['"]?([a-zA-Z0-9-]+)['"]?\s*:\s*['"]([^'"]+)['"]/g;
252
+ let match;
253
+ while ((match = colorRegex.exec(colorMatch[1])) !== null) {
254
+ this.registry.tokens.colors[match[1]] = match[2];
255
+ }
256
+ }
257
+ }
258
+
259
+ async scanDirectory(dir, relativePath = '') {
260
+ let entries;
261
+ try {
262
+ entries = fs.readdirSync(dir, { withFileTypes: true });
263
+ } catch {
264
+ return;
265
+ }
266
+
267
+ for (const entry of entries) {
268
+ const fullPath = path.join(dir, entry.name);
269
+ const relPath = path.join(relativePath, entry.name);
270
+
271
+ if (entry.isDirectory()) {
272
+ if (this.shouldSkipDirectory(entry.name)) continue;
273
+ await this.scanDirectory(fullPath, relPath);
274
+ } else if (entry.isFile()) {
275
+ if (this.isComponentFile(entry.name)) {
276
+ await this.parseComponentFile(fullPath, relPath);
277
+ }
278
+ }
279
+ }
280
+ }
281
+
282
+ shouldSkipDirectory(name) {
283
+ return name === 'node_modules' ||
284
+ name.startsWith('_') ||
285
+ name === '__tests__' ||
286
+ name === '__mocks__' ||
287
+ name === 'dist' ||
288
+ name === 'build' ||
289
+ name === '.git';
290
+ }
291
+
292
+ isComponentFile(filename) {
293
+ // Framework-specific patterns
294
+ const patterns = this.config.frameworkPatterns[this.framework] || [];
295
+
296
+ for (const pattern of patterns) {
297
+ const ext = pattern.replace('**/*', '');
298
+ if (filename.endsWith(ext)) {
299
+ // Exclude test/story files
300
+ if (filename.includes('.test.') ||
301
+ filename.includes('.spec.') ||
302
+ filename.includes('.stories.')) {
303
+ return false;
304
+ }
305
+ // Exclude index files (usually re-exports)
306
+ if (filename.startsWith('index.')) {
307
+ return false;
308
+ }
309
+ return true;
310
+ }
311
+ }
312
+
313
+ return false;
314
+ }
315
+
316
+ async parseComponentFile(filePath, relativePath) {
317
+ const content = fs.readFileSync(filePath, 'utf-8');
318
+ const ext = path.extname(filePath);
319
+ const componentName = path.basename(filePath, ext);
320
+
321
+ let component;
322
+
323
+ switch (this.framework) {
324
+ case 'vue':
325
+ component = this.parseVueComponent(content, componentName, relativePath, filePath);
326
+ break;
327
+ case 'svelte':
328
+ component = this.parseSvelteComponent(content, componentName, relativePath, filePath);
329
+ break;
330
+ case 'angular':
331
+ component = this.parseAngularComponent(content, componentName, relativePath, filePath);
332
+ break;
333
+ default:
334
+ component = this.parseReactComponent(content, componentName, relativePath, filePath);
335
+ }
336
+
337
+ if (component) {
338
+ this.registry.components.push(component);
339
+ }
340
+ }
341
+
342
+ parseReactComponent(content, componentName, relativePath, fullPath) {
343
+ const component = this.createBaseComponent(componentName, relativePath, fullPath);
344
+
345
+ if (this.parser && this.traverse) {
346
+ return this.parseReactWithBabel(content, component);
347
+ }
348
+
349
+ return this.parseReactWithRegex(content, component);
350
+ }
351
+
352
+ parseReactWithBabel(content, component) {
353
+ try {
354
+ const ast = this.parser.parse(content, {
355
+ sourceType: 'module',
356
+ plugins: ['typescript', 'jsx']
357
+ });
358
+
359
+ this.traverse(ast, {
360
+ ExportNamedDeclaration: (path) => {
361
+ if (path.node.declaration) {
362
+ if (path.node.declaration.type === 'FunctionDeclaration' && path.node.declaration.id) {
363
+ component.exports.push(path.node.declaration.id.name);
364
+ } else if (path.node.declaration.type === 'VariableDeclaration') {
365
+ path.node.declaration.declarations.forEach(d => {
366
+ if (d.id && d.id.name) component.exports.push(d.id.name);
367
+ });
368
+ }
369
+ }
370
+ },
371
+
372
+ ExportDefaultDeclaration: () => {
373
+ component.exports.push('default');
374
+ },
375
+
376
+ TSInterfaceDeclaration: (path) => {
377
+ if (path.node.id.name.includes('Props')) {
378
+ path.node.body.body.forEach(prop => {
379
+ if (prop.type === 'TSPropertySignature' && prop.key && prop.key.name) {
380
+ component.props.push({
381
+ name: prop.key.name,
382
+ required: !prop.optional,
383
+ type: this.extractTypeAnnotation(prop.typeAnnotation)
384
+ });
385
+ }
386
+ });
387
+ }
388
+ },
389
+
390
+ JSXElement: (path) => {
391
+ component.structure.elementCount++;
392
+
393
+ const openingElement = path.node.openingElement;
394
+ if (openingElement && openingElement.name && openingElement.name.name) {
395
+ const elementName = openingElement.name.name;
396
+
397
+ if (/^[A-Z]/.test(elementName)) {
398
+ if (!component.childComponents.includes(elementName)) {
399
+ component.childComponents.push(elementName);
400
+ }
401
+ }
402
+ }
403
+
404
+ if (path.node.children && path.node.children.length > 0) {
405
+ component.structure.hasChildren = true;
406
+ }
407
+
408
+ // Extract CSS classes
409
+ if (openingElement && openingElement.attributes) {
410
+ openingElement.attributes.forEach(attr => {
411
+ if (attr.type === 'JSXAttribute' && attr.name) {
412
+ if (attr.name.name === 'className' || attr.name.name === 'class') {
413
+ this.extractCSSFromAttribute(attr, component);
414
+ }
415
+ }
416
+ });
417
+ }
418
+ },
419
+
420
+ ImportDeclaration: (path) => {
421
+ const source = path.node.source.value;
422
+ if (!source.startsWith('.') && !source.startsWith('@/') && !source.startsWith('~/')) {
423
+ component.dependencies.push(source);
424
+ }
425
+ }
426
+ });
427
+
428
+ component.structure.depth = this.calculateJSXDepth(content);
429
+ component.type = this.classifyComponent(component);
430
+ component.variants = this.extractVariants(component.props);
431
+
432
+ return component;
433
+
434
+ } catch (err) {
435
+ console.log(` ⚠️ Babel parse error: ${component.name}`);
436
+ return this.parseReactWithRegex(content, component);
437
+ }
438
+ }
439
+
440
+ parseReactWithRegex(content, component) {
441
+ // Extract exports
442
+ const exportMatches = content.match(/export\s+(const|function|default\s+function)\s+(\w+)/g) || [];
443
+ exportMatches.forEach(match => {
444
+ const name = match.replace(/export\s+(const|function|default\s+function)\s+/, '');
445
+ if (name) component.exports.push(name);
446
+ });
447
+
448
+ // Extract props interface
449
+ const propsMatch = content.match(/interface\s+\w*Props\s*\{([^}]+)\}/s);
450
+ if (propsMatch) {
451
+ const propLines = propsMatch[1].split('\n');
452
+ propLines.forEach(line => {
453
+ const propMatch = line.match(/^\s*(\w+)(\?)?\s*:\s*(.+?);?\s*$/);
454
+ if (propMatch) {
455
+ component.props.push({
456
+ name: propMatch[1],
457
+ required: !propMatch[2],
458
+ type: propMatch[3].trim()
459
+ });
460
+ }
461
+ });
462
+ }
463
+
464
+ // Extract child components (PascalCase in JSX)
465
+ const childMatches = content.match(/<([A-Z][a-zA-Z0-9]*)/g) || [];
466
+ childMatches.forEach(match => {
467
+ const name = match.replace('<', '');
468
+ if (!component.childComponents.includes(name)) {
469
+ component.childComponents.push(name);
470
+ }
471
+ });
472
+
473
+ // Extract CSS classes
474
+ const classMatches = content.match(/className=["']([^"']+)["']/g) || [];
475
+ classMatches.forEach(match => {
476
+ const classes = match.replace(/className=["']/, '').replace(/["']$/, '');
477
+ this.extractCSSClasses(classes, component);
478
+ });
479
+
480
+ // Count JSX elements
481
+ component.structure.elementCount = (content.match(/<[A-Za-z]/g) || []).length;
482
+ component.structure.depth = this.calculateJSXDepth(content);
483
+ component.structure.hasChildren = content.includes('{children}') || content.includes('children');
484
+
485
+ component.type = this.classifyComponent(component);
486
+ component.variants = this.extractVariants(component.props);
487
+
488
+ return component;
489
+ }
490
+
491
+ parseVueComponent(content, componentName, relativePath, fullPath) {
492
+ const component = this.createBaseComponent(componentName, relativePath, fullPath);
493
+
494
+ // Extract script content
495
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
496
+ if (scriptMatch) {
497
+ const script = scriptMatch[1];
498
+
499
+ // Extract props from defineProps or props option
500
+ const propsMatch = script.match(/defineProps<\{([^}]+)\}>/s) ||
501
+ script.match(/props:\s*\{([^}]+)\}/s);
502
+ if (propsMatch) {
503
+ const propLines = propsMatch[1].split('\n');
504
+ propLines.forEach(line => {
505
+ const propMatch = line.match(/^\s*(\w+)(\?)?\s*:\s*(.+?),?\s*$/);
506
+ if (propMatch) {
507
+ component.props.push({
508
+ name: propMatch[1],
509
+ required: !propMatch[2],
510
+ type: propMatch[3].trim()
511
+ });
512
+ }
513
+ });
514
+ }
515
+ }
516
+
517
+ // Extract template content
518
+ const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
519
+ if (templateMatch) {
520
+ const template = templateMatch[1];
521
+
522
+ // Count elements
523
+ component.structure.elementCount = (template.match(/<[A-Za-z]/g) || []).length;
524
+
525
+ // Extract child components
526
+ const childMatches = template.match(/<([A-Z][a-zA-Z0-9-]*)/g) || [];
527
+ childMatches.forEach(match => {
528
+ const name = match.replace('<', '');
529
+ if (!component.childComponents.includes(name)) {
530
+ component.childComponents.push(name);
531
+ }
532
+ });
533
+
534
+ // Extract CSS classes
535
+ const classMatches = template.match(/class=["']([^"']+)["']/g) || [];
536
+ classMatches.forEach(match => {
537
+ const classes = match.replace(/class=["']/, '').replace(/["']$/, '');
538
+ this.extractCSSClasses(classes, component);
539
+ });
540
+
541
+ component.structure.hasChildren = template.includes('<slot');
542
+ }
543
+
544
+ component.structure.depth = this.calculateJSXDepth(content);
545
+ component.type = this.classifyComponent(component);
546
+ component.variants = this.extractVariants(component.props);
547
+
548
+ return component;
549
+ }
550
+
551
+ parseSvelteComponent(content, componentName, relativePath, fullPath) {
552
+ const component = this.createBaseComponent(componentName, relativePath, fullPath);
553
+
554
+ // Extract script content
555
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
556
+ if (scriptMatch) {
557
+ const script = scriptMatch[1];
558
+
559
+ // Extract props (export let)
560
+ const propMatches = script.match(/export\s+let\s+(\w+)(?:\s*:\s*([^=;]+))?/g) || [];
561
+ propMatches.forEach(match => {
562
+ const propMatch = match.match(/export\s+let\s+(\w+)(?:\s*:\s*([^=;]+))?/);
563
+ if (propMatch) {
564
+ component.props.push({
565
+ name: propMatch[1],
566
+ required: !match.includes('='),
567
+ type: propMatch[2]?.trim() || 'any'
568
+ });
569
+ }
570
+ });
571
+ }
572
+
573
+ // Count elements
574
+ component.structure.elementCount = (content.match(/<[A-Za-z]/g) || []).length;
575
+
576
+ // Extract child components (PascalCase)
577
+ const childMatches = content.match(/<([A-Z][a-zA-Z0-9]*)/g) || [];
578
+ childMatches.forEach(match => {
579
+ const name = match.replace('<', '');
580
+ if (!component.childComponents.includes(name)) {
581
+ component.childComponents.push(name);
582
+ }
583
+ });
584
+
585
+ // Extract CSS classes
586
+ const classMatches = content.match(/class(?:Name)?=["']([^"']+)["']/g) || [];
587
+ classMatches.forEach(match => {
588
+ const classes = match.replace(/class(?:Name)?=["']/, '').replace(/["']$/, '');
589
+ this.extractCSSClasses(classes, component);
590
+ });
591
+
592
+ component.structure.hasChildren = content.includes('<slot');
593
+ component.structure.depth = this.calculateJSXDepth(content);
594
+ component.type = this.classifyComponent(component);
595
+ component.variants = this.extractVariants(component.props);
596
+
597
+ return component;
598
+ }
599
+
600
+ parseAngularComponent(content, componentName, relativePath, fullPath) {
601
+ const component = this.createBaseComponent(componentName, relativePath, fullPath);
602
+
603
+ // Extract @Input() decorators
604
+ const inputMatches = content.match(/@Input\(\)\s*(\w+)(?:\s*:\s*([^;=]+))?/g) || [];
605
+ inputMatches.forEach(match => {
606
+ const propMatch = match.match(/@Input\(\)\s*(\w+)(?:\s*:\s*([^;=]+))?/);
607
+ if (propMatch) {
608
+ component.props.push({
609
+ name: propMatch[1],
610
+ required: !match.includes('?'),
611
+ type: propMatch[2]?.trim() || 'any'
612
+ });
613
+ }
614
+ });
615
+
616
+ // Extract selector
617
+ const selectorMatch = content.match(/selector:\s*['"]([^'"]+)['"]/);
618
+ if (selectorMatch) {
619
+ component.angularSelector = selectorMatch[1];
620
+ }
621
+
622
+ // Load template if inline
623
+ const templateMatch = content.match(/template:\s*`([\s\S]*?)`/);
624
+ if (templateMatch) {
625
+ const template = templateMatch[1];
626
+ component.structure.elementCount = (template.match(/<[A-Za-z]/g) || []).length;
627
+
628
+ // Extract CSS classes
629
+ const classMatches = template.match(/class=["']([^"']+)["']/g) || [];
630
+ classMatches.forEach(match => {
631
+ const classes = match.replace(/class=["']/, '').replace(/["']$/, '');
632
+ this.extractCSSClasses(classes, component);
633
+ });
634
+ }
635
+
636
+ component.structure.depth = 2; // Default for Angular
637
+ component.type = this.classifyComponent(component);
638
+ component.variants = this.extractVariants(component.props);
639
+
640
+ return component;
641
+ }
642
+
643
+ createBaseComponent(name, relativePath, fullPath) {
644
+ return {
645
+ name,
646
+ path: relativePath,
647
+ fullPath,
648
+ type: 'unknown',
649
+ exports: [],
650
+ props: [],
651
+ variants: [],
652
+ cssProperties: [],
653
+ dependencies: [],
654
+ childComponents: [],
655
+ structure: {
656
+ depth: 0,
657
+ elementCount: 0,
658
+ hasChildren: false
659
+ }
660
+ };
661
+ }
662
+
663
+ extractTypeAnnotation(typeAnnotation) {
664
+ if (!typeAnnotation) return 'unknown';
665
+
666
+ const type = typeAnnotation.typeAnnotation;
667
+ if (!type) return 'unknown';
668
+
669
+ switch (type.type) {
670
+ case 'TSStringKeyword': return 'string';
671
+ case 'TSNumberKeyword': return 'number';
672
+ case 'TSBooleanKeyword': return 'boolean';
673
+ case 'TSUnionType':
674
+ return type.types.map(t => {
675
+ if (t.type === 'TSLiteralType' && t.literal) {
676
+ return t.literal.value;
677
+ }
678
+ return t.type.replace('TS', '').replace('Keyword', '').toLowerCase();
679
+ });
680
+ default:
681
+ return type.type.replace('TS', '').replace('Keyword', '').toLowerCase();
682
+ }
683
+ }
684
+
685
+ extractCSSFromAttribute(attr, component) {
686
+ if (attr.value) {
687
+ let value = '';
688
+
689
+ if (attr.value.type === 'StringLiteral') {
690
+ value = attr.value.value;
691
+ } else if (attr.value.type === 'JSXExpressionContainer') {
692
+ if (attr.value.expression.type === 'TemplateLiteral') {
693
+ value = attr.value.expression.quasis.map(q => q.value.raw).join(' ');
694
+ }
695
+ }
696
+
697
+ if (value) {
698
+ this.extractCSSClasses(value, component);
699
+ }
700
+ }
701
+ }
702
+
703
+ extractCSSClasses(classString, component) {
704
+ const classes = classString.split(/\s+/).filter(c => c.length > 0);
705
+
706
+ classes.forEach(cls => {
707
+ if (cls.match(/^(bg-|text-|border-)/)) {
708
+ component.cssProperties.push({ type: 'color', value: cls });
709
+ } else if (cls.match(/^(p-|m-|gap-|space-|px-|py-|mx-|my-)/)) {
710
+ component.cssProperties.push({ type: 'spacing', value: cls });
711
+ } else if (cls.match(/^(text-|font-|leading-|tracking-)/)) {
712
+ component.cssProperties.push({ type: 'typography', value: cls });
713
+ } else if (cls.match(/^rounded/)) {
714
+ component.cssProperties.push({ type: 'radius', value: cls });
715
+ } else if (cls.match(/^shadow/)) {
716
+ component.cssProperties.push({ type: 'shadow', value: cls });
717
+ } else if (cls.match(/^(w-|h-|min-|max-)/)) {
718
+ component.cssProperties.push({ type: 'sizing', value: cls });
719
+ } else if (cls.match(/^(flex|grid|block|inline)/)) {
720
+ component.cssProperties.push({ type: 'layout', value: cls });
721
+ }
722
+ });
723
+ }
724
+
725
+ calculateJSXDepth(content) {
726
+ let maxDepth = 0;
727
+ let currentDepth = 0;
728
+
729
+ const matches = content.match(/<\/?[A-Z][^>]*>/g) || [];
730
+ matches.forEach(match => {
731
+ if (match.startsWith('</')) {
732
+ currentDepth--;
733
+ } else if (!match.endsWith('/>')) {
734
+ currentDepth++;
735
+ maxDepth = Math.max(maxDepth, currentDepth);
736
+ }
737
+ });
738
+
739
+ return maxDepth;
740
+ }
741
+
742
+ classifyComponent(component) {
743
+ const childCount = component.childComponents.length;
744
+ const depth = component.structure.depth;
745
+ const elementCount = component.structure.elementCount;
746
+
747
+ // Atom: Simple, no child components, low depth
748
+ if (childCount === 0 && depth <= 2 && elementCount <= 5) {
749
+ return 'atom';
750
+ }
751
+
752
+ // Molecule: Uses a few atoms, moderate complexity
753
+ if (childCount <= 3 && depth <= 4 && elementCount <= 15) {
754
+ return 'molecule';
755
+ }
756
+
757
+ // Organism: Complex, uses multiple molecules/atoms
758
+ return 'organism';
759
+ }
760
+
761
+ extractVariants(props) {
762
+ const variants = [];
763
+
764
+ props.forEach(prop => {
765
+ if (prop.name === 'variant' || prop.name === 'size' || prop.name === 'color' ||
766
+ prop.name === 'type' || prop.name === 'state' || prop.name === 'appearance') {
767
+ if (Array.isArray(prop.type)) {
768
+ variants.push({
769
+ name: prop.name,
770
+ options: prop.type
771
+ });
772
+ } else if (typeof prop.type === 'string' && prop.type.includes('|')) {
773
+ variants.push({
774
+ name: prop.name,
775
+ options: prop.type.split('|').map(t => t.trim().replace(/['"]/g, ''))
776
+ });
777
+ }
778
+ }
779
+ });
780
+
781
+ return variants;
782
+ }
783
+
784
+ calculateSignatures() {
785
+ this.registry.components.forEach(component => {
786
+ const signature = {
787
+ css: [...new Set(component.cssProperties.map(p => `${p.type}:${p.value}`))].sort().join('|'),
788
+ structure: `d${component.structure.depth}:e${component.structure.elementCount}:c${component.childComponents.length}`,
789
+ props: component.props.map(p => `${p.name}:${Array.isArray(p.type) ? 'enum' : p.type}`).sort().join('|'),
790
+ variants: component.variants.map(v => `${v.name}:${v.options.length}`).join('|')
791
+ };
792
+
793
+ component.signature = signature;
794
+ });
795
+ }
796
+
797
+ saveRegistry() {
798
+ const stateDir = path.dirname(REGISTRY_PATH);
799
+ if (!fs.existsSync(stateDir)) {
800
+ fs.mkdirSync(stateDir, { recursive: true });
801
+ }
802
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(this.registry, null, 2));
803
+ }
804
+
805
+ loadRegistry() {
806
+ if (fs.existsSync(REGISTRY_PATH)) {
807
+ return JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8'));
808
+ }
809
+ return null;
810
+ }
811
+ }
812
+
813
+ // ============================================================
814
+ // CLI
815
+ // ============================================================
816
+
817
+ async function main() {
818
+ const [,, command, ...args] = process.argv;
819
+
820
+ const scanner = new ComponentScanner();
821
+
822
+ switch (command) {
823
+ case 'scan': {
824
+ await scanner.scan();
825
+ break;
826
+ }
827
+
828
+ case 'show': {
829
+ const registry = scanner.loadRegistry();
830
+ if (!registry) {
831
+ console.error('No registry found. Run "scan" first.');
832
+ process.exit(1);
833
+ }
834
+
835
+ const componentName = args[0];
836
+ if (componentName) {
837
+ const component = registry.components.find(c =>
838
+ c.name.toLowerCase() === componentName.toLowerCase()
839
+ );
840
+
841
+ if (component) {
842
+ console.log(JSON.stringify(component, null, 2));
843
+ } else {
844
+ console.log(`Component "${componentName}" not found.`);
845
+ console.log('\nAvailable components:');
846
+ registry.components.forEach(c => {
847
+ console.log(` - ${c.name} (${c.type}) - ${c.path}`);
848
+ });
849
+ }
850
+ } else {
851
+ console.log('\n📦 Component Registry\n');
852
+ console.log(`Framework: ${registry.framework}`);
853
+ console.log(`Scanned: ${registry.scannedAt}`);
854
+ console.log(`Components: ${registry.components.length}\n`);
855
+
856
+ const grouped = { atom: [], molecule: [], organism: [] };
857
+ registry.components.forEach(c => {
858
+ if (grouped[c.type]) grouped[c.type].push(c);
859
+ });
860
+
861
+ for (const [type, components] of Object.entries(grouped)) {
862
+ if (components.length > 0) {
863
+ console.log(`${type.toUpperCase()}S (${components.length}):`);
864
+ components.forEach(c => {
865
+ console.log(` - ${c.name} → ${c.path}`);
866
+ });
867
+ console.log('');
868
+ }
869
+ }
870
+ }
871
+ break;
872
+ }
873
+
874
+ case 'export': {
875
+ const registry = scanner.loadRegistry();
876
+ if (registry) {
877
+ console.log(JSON.stringify(registry, null, 2));
878
+ } else {
879
+ console.error('No registry found. Run "scan" first.');
880
+ process.exit(1);
881
+ }
882
+ break;
883
+ }
884
+
885
+ default:
886
+ console.log(`
887
+ Wogi Flow - Component Registry Builder
888
+
889
+ Commands:
890
+ scan Scan codebase and build component registry
891
+ show [name] Show component details (or list all)
892
+ export Export registry as JSON
893
+
894
+ Usage:
895
+ ./scripts/flow figma scan
896
+ ./scripts/flow figma show Button
897
+ ./scripts/flow figma export > registry.json
898
+ `);
899
+ }
900
+ }
901
+
902
+ module.exports = { ComponentScanner, detectFramework };
903
+
904
+ if (require.main === module) {
905
+ main().catch(err => {
906
+ console.error(`Error: ${err.message}`);
907
+ process.exit(1);
908
+ });
909
+ }