@tpitre/story-ui 2.1.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,125 @@
1
+ export class SessionManager {
2
+ constructor() {
3
+ this.sessions = new Map();
4
+ this.currentStoryId = null;
5
+ }
6
+ static getInstance() {
7
+ if (!SessionManager.instance) {
8
+ SessionManager.instance = new SessionManager();
9
+ }
10
+ return SessionManager.instance;
11
+ }
12
+ /**
13
+ * Track a newly generated story in the current session
14
+ */
15
+ trackStory(sessionId, story) {
16
+ const sessionStories = this.sessions.get(sessionId) || [];
17
+ // Extract keywords from prompt and title for smart matching
18
+ const keywords = this.extractKeywords(story.prompt + ' ' + story.title);
19
+ const sessionStory = {
20
+ ...story,
21
+ timestamp: new Date(),
22
+ keywords
23
+ };
24
+ sessionStories.push(sessionStory);
25
+ this.sessions.set(sessionId, sessionStories);
26
+ this.currentStoryId = story.id;
27
+ console.error(`[SessionManager] Tracked story: ${story.title} (${story.id}) for session ${sessionId}`);
28
+ }
29
+ /**
30
+ * Get the current story being discussed
31
+ */
32
+ getCurrentStory(sessionId) {
33
+ const stories = this.sessions.get(sessionId) || [];
34
+ if (this.currentStoryId) {
35
+ return stories.find(s => s.id === this.currentStoryId) || null;
36
+ }
37
+ // Return the most recent story if no current story is set
38
+ return stories[stories.length - 1] || null;
39
+ }
40
+ /**
41
+ * Find a story by context (keywords in the user's message)
42
+ */
43
+ findStoryByContext(sessionId, userMessage) {
44
+ const stories = this.sessions.get(sessionId) || [];
45
+ if (stories.length === 0)
46
+ return null;
47
+ const messageKeywords = this.extractKeywords(userMessage.toLowerCase());
48
+ // Score each story based on keyword matches
49
+ const scoredStories = stories.map(story => {
50
+ const score = story.keywords.filter(keyword => messageKeywords.some(msgKeyword => msgKeyword.includes(keyword) || keyword.includes(msgKeyword))).length;
51
+ return { story, score };
52
+ });
53
+ // Sort by score and recency
54
+ scoredStories.sort((a, b) => {
55
+ if (a.score !== b.score)
56
+ return b.score - a.score;
57
+ return b.story.timestamp.getTime() - a.story.timestamp.getTime();
58
+ });
59
+ // Return the best match if it has any keyword matches
60
+ if (scoredStories[0].score > 0) {
61
+ this.currentStoryId = scoredStories[0].story.id;
62
+ console.error(`[SessionManager] Found story by context: ${scoredStories[0].story.title} (score: ${scoredStories[0].score})`);
63
+ return scoredStories[0].story;
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Get all stories in the current session
69
+ */
70
+ getSessionStories(sessionId) {
71
+ return this.sessions.get(sessionId) || [];
72
+ }
73
+ /**
74
+ * Set the current story being discussed
75
+ */
76
+ setCurrentStory(sessionId, storyId) {
77
+ const stories = this.sessions.get(sessionId) || [];
78
+ if (stories.some(s => s.id === storyId)) {
79
+ this.currentStoryId = storyId;
80
+ console.error(`[SessionManager] Set current story to: ${storyId}`);
81
+ }
82
+ }
83
+ /**
84
+ * Clear session data (for cleanup)
85
+ */
86
+ clearSession(sessionId) {
87
+ this.sessions.delete(sessionId);
88
+ if (this.currentStoryId) {
89
+ this.currentStoryId = null;
90
+ }
91
+ }
92
+ /**
93
+ * Extract meaningful keywords from text
94
+ */
95
+ extractKeywords(text) {
96
+ // Remove common words and extract meaningful terms
97
+ const commonWords = new Set([
98
+ 'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
99
+ 'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through', 'during',
100
+ 'before', 'after', 'above', 'below', 'between', 'under', 'again', 'further',
101
+ 'then', 'once', 'create', 'make', 'generate', 'build', 'add', 'update',
102
+ 'component', 'story', 'please', 'should', 'would', 'could'
103
+ ]);
104
+ const words = text.toLowerCase()
105
+ .replace(/[^a-z0-9\s]/g, ' ')
106
+ .split(/\s+/)
107
+ .filter(word => word.length > 2 && !commonWords.has(word));
108
+ // Also extract compound terms (like "toast notification")
109
+ const phrases = [];
110
+ const importantPhrases = [
111
+ 'toast notification', 'banner notification', 'success message',
112
+ 'error message', 'warning message', 'info message',
113
+ 'dark mode', 'light mode', 'switch', 'toggle', 'button',
114
+ 'card', 'alert', 'modal', 'dialog', 'form', 'input',
115
+ 'table', 'list', 'grid', 'layout', 'navigation', 'menu'
116
+ ];
117
+ const lowerText = text.toLowerCase();
118
+ for (const phrase of importantPhrases) {
119
+ if (lowerText.includes(phrase)) {
120
+ phrases.push(phrase);
121
+ }
122
+ }
123
+ return [...new Set([...words, ...phrases])];
124
+ }
125
+ }
@@ -80,6 +80,10 @@ export const ICON_CORRECTIONS = {
80
80
  'ForkIcon': 'RepoForkedIcon',
81
81
  'CloseIcon': 'XIcon',
82
82
  'CheckmarkIcon': 'CheckIcon',
83
+ // Ant Design icon corrections
84
+ 'ExclamationTriangleOutlined': 'ExclamationCircleOutlined',
85
+ 'WarningTriangleOutlined': 'WarningOutlined',
86
+ 'ErrorCircleOutlined': 'CloseCircleOutlined',
83
87
  'CrossIcon': 'XIcon',
84
88
  'EditIcon': 'PencilIcon',
85
89
  'DeleteIcon': 'TrashIcon',
@@ -25,7 +25,14 @@ export function loadUserConfig() {
25
25
  for (const configPath of configPaths) {
26
26
  if (fs.existsSync(configPath)) {
27
27
  try {
28
- console.log(`Loading Story UI config from: ${configPath}`);
28
+ // Only log to stderr when in MCP mode to avoid corrupting JSON-RPC communication
29
+ const isMcpMode = process.argv.includes('mcp') || process.env.STORY_UI_MCP_MODE === 'true';
30
+ if (isMcpMode) {
31
+ console.error(`Loading Story UI config from: ${configPath}`);
32
+ }
33
+ else {
34
+ console.log(`Loading Story UI config from: ${configPath}`);
35
+ }
29
36
  // Read and evaluate the config file
30
37
  const configContent = fs.readFileSync(configPath, 'utf-8');
31
38
  // Handle both CommonJS and ES modules
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { logger } from './logger.js';
3
4
  /**
4
5
  * Loads AI considerations from a markdown or JSON file
5
6
  */
@@ -15,7 +16,7 @@ export function loadConsiderations(considerationsPath) {
15
16
  for (const possiblePath of possiblePaths) {
16
17
  if (fs.existsSync(possiblePath)) {
17
18
  considerationsPath = possiblePath;
18
- console.log(`Found considerations file at: ${considerationsPath}`);
19
+ logger.log(`Found considerations file at: ${considerationsPath}`);
19
20
  break;
20
21
  }
21
22
  }
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { glob } from 'glob';
4
+ import { logger } from './logger.js';
4
5
  export class DocumentationLoader {
5
6
  constructor(projectRoot) {
6
7
  this.cache = null;
@@ -32,7 +33,7 @@ export class DocumentationLoader {
32
33
  if (this.cache && stats.mtimeMs <= this.lastModified) {
33
34
  return this.cache;
34
35
  }
35
- console.log(`📚 Loading documentation from ${this.docsDir}`);
36
+ logger.log(`📚 Loading documentation from ${this.docsDir}`);
36
37
  const documentation = {
37
38
  sources: [],
38
39
  guidelines: [],
@@ -58,7 +59,7 @@ export class DocumentationLoader {
58
59
  allFiles.push(...matches);
59
60
  }
60
61
  const files = [...new Set(allFiles)]; // Remove duplicates
61
- console.log(`📄 Found ${files.length} documentation files`);
62
+ logger.log(`📄 Found ${files.length} documentation files`);
62
63
  // Process each file
63
64
  for (const file of files) {
64
65
  // Skip README files as they are instructional, not design system documentation
@@ -82,7 +83,7 @@ export class DocumentationLoader {
82
83
  // Cache the results
83
84
  this.cache = documentation;
84
85
  this.lastModified = stats.mtimeMs;
85
- console.log(`✅ Loaded documentation with ${documentation.guidelines.length} guidelines, ${Object.keys(documentation.tokens).length} token categories, ${Object.keys(documentation.patterns).length} patterns`);
86
+ logger.log(`✅ Loaded documentation with ${documentation.guidelines.length} guidelines, ${Object.keys(documentation.tokens).length} token categories, ${Object.keys(documentation.patterns).length} patterns`);
86
87
  return documentation;
87
88
  }
88
89
  /**
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { createRequire } from 'module';
4
+ import { logger } from './logger.js';
4
5
  /**
5
6
  * Dynamically discovers what components are actually available in an installed package
6
7
  */
@@ -29,7 +30,7 @@ export class DynamicPackageDiscovery {
29
30
  // Try to require the package and inspect its exports
30
31
  const packageExports = await this.requirePackage(this.packageName);
31
32
  if (!packageExports) {
32
- console.log(`🔄 Could not directly import ${this.packageName}, falling back to structure analysis`);
33
+ logger.log(`🔄 Could not directly import ${this.packageName}, falling back to structure analysis`);
33
34
  // Don't return null here - fall back to structure discovery
34
35
  }
35
36
  const components = [];
@@ -49,14 +50,14 @@ export class DynamicPackageDiscovery {
49
50
  }
50
51
  // Check if we found any actual components
51
52
  const componentCount = components.filter(c => c.isComponent).length;
52
- console.log(`📋 Found ${componentCount} components in main ${this.packageName} export`);
53
+ logger.log(`📋 Found ${componentCount} components in main ${this.packageName} export`);
53
54
  // If no components found in main export, fall back to structure analysis
54
55
  if (componentCount === 0) {
55
- console.log(`🔄 No components in main export, falling back to structure analysis for ${this.packageName}...`);
56
+ logger.log(`🔄 No components in main export, falling back to structure analysis for ${this.packageName}...`);
56
57
  const structureExports = this.discoverFromPackageStructure();
57
58
  if (structureExports) {
58
59
  const structureComponentNames = Object.keys(structureExports);
59
- console.log(`📁 Structure analysis found ${structureComponentNames.length} components`);
60
+ logger.log(`📁 Structure analysis found ${structureComponentNames.length} components`);
60
61
  // Replace with structure-discovered components
61
62
  allExports = structureComponentNames;
62
63
  components.length = 0; // Clear the array
@@ -75,7 +76,7 @@ export class DynamicPackageDiscovery {
75
76
  }
76
77
  else {
77
78
  // Failed to import - fall back to structure analysis
78
- console.log(`📁 Import failed, analyzing package structure for ${this.packageName}...`);
79
+ logger.log(`📁 Import failed, analyzing package structure for ${this.packageName}...`);
79
80
  const structureExports = this.discoverFromPackageStructure();
80
81
  if (structureExports) {
81
82
  allExports = Object.keys(structureExports);
@@ -91,7 +92,7 @@ export class DynamicPackageDiscovery {
91
92
  }
92
93
  }
93
94
  }
94
- console.log(`✅ Discovered ${components.filter(c => c.isComponent).length} components from ${this.packageName} v${packageVersion}`);
95
+ logger.log(`✅ Discovered ${components.filter(c => c.isComponent).length} components from ${this.packageName} v${packageVersion}`);
95
96
  return {
96
97
  components,
97
98
  allExports,
@@ -118,7 +119,11 @@ export class DynamicPackageDiscovery {
118
119
  // Check if this is a CSS import error (common with compiled design systems)
119
120
  const errorMessage = importError?.message || String(importError);
120
121
  if (errorMessage.includes('.css:') || errorMessage.includes('Unexpected token')) {
121
- console.log(`🔄 ${packageName}: CSS detected, using static analysis (normal for design systems)`);
122
+ logger.log(`🔄 ${packageName}: CSS detected, using static analysis (normal for design systems)`);
123
+ return this.discoverFromPackageStructure();
124
+ }
125
+ if (errorMessage.includes('Invalid hook call') || errorMessage.includes('Hooks can only be called')) {
126
+ logger.log(`🔄 ${packageName}: React hooks detected outside component context, using static analysis`);
122
127
  return this.discoverFromPackageStructure();
123
128
  }
124
129
  // Fall back to require (for CommonJS)
@@ -129,17 +134,21 @@ export class DynamicPackageDiscovery {
129
134
  }
130
135
  }
131
136
  catch (error) {
132
- // Check if this is a CSS import error
137
+ // Check if this is a CSS import error
133
138
  const errorMessage = error?.message || String(error);
134
139
  if (errorMessage.includes('.css:') || errorMessage.includes('Unexpected token')) {
135
- console.log(`🔄 ${packageName}: CSS detected, using static analysis (normal for design systems)`);
140
+ logger.log(`🔄 ${packageName}: CSS detected, using static analysis (normal for design systems)`);
136
141
  return this.discoverFromPackageStructure();
137
142
  }
138
143
  if (errorMessage.includes('window is not defined')) {
139
- console.log(`🔄 ${packageName}: Browser-only component, using static analysis`);
144
+ logger.log(`🔄 ${packageName}: Browser-only component, using static analysis`);
145
+ return this.discoverFromPackageStructure();
146
+ }
147
+ if (errorMessage.includes('Invalid hook call') || errorMessage.includes('Hooks can only be called')) {
148
+ logger.log(`🔄 ${packageName}: React hooks detected outside component context, using static analysis`);
140
149
  return this.discoverFromPackageStructure();
141
150
  }
142
- console.log(`📋 ${packageName}: Dynamic import failed, using static analysis`);
151
+ logger.log(`📋 ${packageName}: Dynamic import failed, using static analysis`);
143
152
  return this.discoverFromPackageStructure();
144
153
  }
145
154
  }
@@ -335,26 +344,26 @@ export class DynamicPackageDiscovery {
335
344
  const packagePath = path.join(this.projectRoot, 'node_modules', this.packageName);
336
345
  const packageJsonPath = path.join(packagePath, 'package.json');
337
346
  if (!fs.existsSync(packageJsonPath)) {
338
- console.log(`📦 No package.json found for ${this.packageName}`);
347
+ logger.log(`📦 No package.json found for ${this.packageName}`);
339
348
  return null;
340
349
  }
341
350
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
342
351
  const exports = {};
343
352
  // Method 1: Analyze package.json exports field
344
353
  if (packageJson.exports) {
345
- console.log(`📋 Analyzing exports field in ${this.packageName}/package.json`);
354
+ logger.log(`📋 Analyzing exports field in ${this.packageName}/package.json`);
346
355
  this.extractExportsFromPackageJson(packageJson.exports, exports);
347
356
  }
348
357
  // Method 2: Look for index.d.ts or main TypeScript declarations
349
358
  const typingsPath = packageJson.types || packageJson.typings || './dist/types/index.d.ts';
350
359
  const fullTypingsPath = path.join(packagePath, typingsPath);
351
360
  if (fs.existsSync(fullTypingsPath)) {
352
- console.log(`📋 Analyzing TypeScript declarations for ${this.packageName}`);
361
+ logger.log(`📋 Analyzing TypeScript declarations for ${this.packageName}`);
353
362
  this.extractExportsFromTypeDefinitions(fullTypingsPath, exports);
354
363
  }
355
364
  // Method 3: Scan for component subdirectories (for packages like Base Web)
356
365
  if (Object.keys(exports).length === 0) {
357
- console.log(`📁 Scanning subdirectories for ${this.packageName} components...`);
366
+ logger.log(`📁 Scanning subdirectories for ${this.packageName} components...`);
358
367
  this.scanComponentSubdirectories(packagePath, exports);
359
368
  }
360
369
  return Object.keys(exports).length > 0 ? exports : null;
@@ -369,9 +378,9 @@ export class DynamicPackageDiscovery {
369
378
  */
370
379
  scanComponentSubdirectories(packagePath, result) {
371
380
  try {
372
- console.log(`🔍 Scanning ${packagePath} for component subdirectories...`);
381
+ logger.log(`🔍 Scanning ${packagePath} for component subdirectories...`);
373
382
  const entries = fs.readdirSync(packagePath, { withFileTypes: true });
374
- console.log(`📁 Found ${entries.length} entries in ${packagePath}`);
383
+ logger.log(`📁 Found ${entries.length} entries in ${packagePath}`);
375
384
  let componentDirsFound = 0;
376
385
  for (const entry of entries) {
377
386
  if (!entry.isDirectory())
@@ -388,7 +397,7 @@ export class DynamicPackageDiscovery {
388
397
  // Look for component exports (functions/classes starting with uppercase)
389
398
  const componentExports = this.extractComponentsFromTypings(typingsContent);
390
399
  if (componentExports.length > 0) {
391
- console.log(`📦 Found ${componentExports.length} components in ${entry.name}/`);
400
+ logger.log(`📦 Found ${componentExports.length} components in ${entry.name}/`);
392
401
  // Add each component to the result
393
402
  for (const componentName of componentExports) {
394
403
  // Create a mock export function for this component
@@ -404,8 +413,8 @@ export class DynamicPackageDiscovery {
404
413
  }
405
414
  }
406
415
  }
407
- console.log(`✅ Scanned ${componentDirsFound} component directories for ${this.packageName}`);
408
- console.log(`📦 Total components found in subdirectories: ${Object.keys(result).length}`);
416
+ logger.log(`✅ Scanned ${componentDirsFound} component directories for ${this.packageName}`);
417
+ logger.log(`📦 Total components found in subdirectories: ${Object.keys(result).length}`);
409
418
  }
410
419
  catch (error) {
411
420
  console.warn(`Failed to scan subdirectories for ${this.packageName}:`, error);
@@ -484,7 +493,7 @@ export class DynamicPackageDiscovery {
484
493
  const componentName = key.replace('./', '').split('/').pop();
485
494
  if (componentName && /^[A-Z]/.test(componentName)) {
486
495
  result[componentName] = `Component_${componentName}`;
487
- console.log(`📍 Found component export: ${componentName}`);
496
+ logger.log(`📍 Found component export: ${componentName}`);
488
497
  }
489
498
  }
490
499
  }
@@ -512,7 +521,7 @@ export class DynamicPackageDiscovery {
512
521
  const componentName = match[1];
513
522
  if (componentName && /^[A-Z]/.test(componentName)) {
514
523
  result[componentName] = `Component_${componentName}`;
515
- console.log(`📍 Found component in .d.ts: ${componentName}`);
524
+ logger.log(`📍 Found component in .d.ts: ${componentName}`);
516
525
  }
517
526
  }
518
527
  }
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { DynamicPackageDiscovery } from './dynamicPackageDiscovery.js';
4
+ import { logger } from './logger.js';
4
5
  export class EnhancedComponentDiscovery {
5
6
  constructor(config) {
6
7
  this.discoveredComponents = new Map();
@@ -12,10 +13,10 @@ export class EnhancedComponentDiscovery {
12
13
  * Priority: 1. Dynamic Discovery 2. Static Lists 3. Manual Config
13
14
  */
14
15
  async discoverAll() {
15
- console.log('🔍 Starting comprehensive component discovery...');
16
+ logger.log('🔍 Starting comprehensive component discovery...');
16
17
  // Step 1: Discover from all sources
17
18
  const sources = this.identifySources();
18
- console.log(`📁 Found ${sources.length} discovery sources:`, sources.map(s => `${s.type}:${s.path}`));
19
+ logger.log(`📁 Found ${sources.length} discovery sources:`, sources.map(s => `${s.type}:${s.path}`));
19
20
  for (const source of sources) {
20
21
  try {
21
22
  switch (source.type) {
@@ -42,7 +43,7 @@ export class EnhancedComponentDiscovery {
42
43
  // Step 3: Resolve component conflicts and apply prioritization
43
44
  this.resolveComponentConflicts();
44
45
  const finalComponents = Array.from(this.discoveredComponents.values());
45
- console.log(`✅ Discovery complete: ${finalComponents.length} components found`);
46
+ logger.log(`✅ Discovery complete: ${finalComponents.length} components found`);
46
47
  // Log summary by source type
47
48
  this.logDiscoverySummary(finalComponents);
48
49
  return finalComponents;
@@ -64,7 +65,7 @@ export class EnhancedComponentDiscovery {
64
65
  // Resolve conflicts using priority system
65
66
  for (const [name, componentList] of conflicts) {
66
67
  if (componentList.length > 1) {
67
- console.log(`⚠️ Resolving conflict for component "${name}" (${componentList.length} versions found)`);
68
+ logger.log(`⚠️ Resolving conflict for component "${name}" (${componentList.length} versions found)`);
68
69
  // Priority order: local > manual config > npm
69
70
  const prioritized = componentList.sort((a, b) => {
70
71
  const getPriority = (comp) => {
@@ -82,7 +83,7 @@ export class EnhancedComponentDiscovery {
82
83
  for (const loser of losers) {
83
84
  this.discoveredComponents.delete(loser.name);
84
85
  }
85
- console.log(`✅ Kept ${winner.source.type} version of "${name}" from ${winner.source.path}`);
86
+ logger.log(`✅ Kept ${winner.source.type} version of "${name}" from ${winner.source.path}`);
86
87
  }
87
88
  }
88
89
  }
@@ -97,7 +98,7 @@ export class EnhancedComponentDiscovery {
97
98
  acc[sourceType]++;
98
99
  return acc;
99
100
  }, {});
100
- console.log('📊 Component discovery summary:', summary);
101
+ logger.log('📊 Component discovery summary:', summary);
101
102
  }
102
103
  /**
103
104
  * Get the project root directory from the config
@@ -263,12 +264,12 @@ export class EnhancedComponentDiscovery {
263
264
  console.warn(`Package ${source.path} not found in node_modules at ${packagePath}`);
264
265
  return;
265
266
  }
266
- console.log(`🔍 Dynamically discovering components from ${source.path}...`);
267
+ logger.log(`🔍 Dynamically discovering components from ${source.path}...`);
267
268
  // Use dynamic discovery to get real exports
268
269
  const dynamicDiscovery = new DynamicPackageDiscovery(source.path, projectRoot);
269
270
  const packageExports = await dynamicDiscovery.getRealPackageExports();
270
271
  if (!packageExports) {
271
- console.log(`📋 ${source.path}: Using static component list (design system detected)`);
272
+ logger.log(`📋 ${source.path}: Using static component list (design system detected)`);
272
273
  // Fallback to predefined components if dynamic discovery fails
273
274
  const knownComponents = this.getKnownDesignSystemComponents(source.path);
274
275
  if (knownComponents.length > 0) {
@@ -286,7 +287,7 @@ export class EnhancedComponentDiscovery {
286
287
  // Process the real components found in the package
287
288
  const realComponents = packageExports.components.filter(comp => comp.isComponent);
288
289
  console.log(`✅ Found ${realComponents.length} real components in ${source.path} v${packageExports.packageVersion}`);
289
- console.log(`📦 Available components: ${realComponents.map(c => c.name).join(', ')}`);
290
+ logger.log(`📦 Available components: ${realComponents.map(c => c.name).join(', ')}`);
290
291
  for (const realComp of realComponents) {
291
292
  // Get enhanced metadata from predefined list if available
292
293
  const knownComponents = this.getKnownDesignSystemComponents(source.path);
@@ -309,11 +310,51 @@ export class EnhancedComponentDiscovery {
309
310
  }
310
311
  /**
311
312
  * Get known components for popular design systems
312
- * Returns empty array to rely on dynamic discovery
313
+ * Returns a fallback list when dynamic discovery fails
313
314
  */
314
315
  getKnownDesignSystemComponents(packageName) {
315
- // Return empty array to rely purely on dynamic component discovery
316
- // This ensures we test the actual package scanning capabilities
316
+ // Chakra UI fallback components
317
+ if (packageName === '@chakra-ui/react') {
318
+ const basicComponents = [
319
+ // Layout
320
+ 'Box', 'Flex', 'Grid', 'Stack', 'HStack', 'VStack', 'Container', 'Center', 'Square', 'Circle',
321
+ 'SimpleGrid', 'Wrap', 'WrapItem', 'AspectRatio', 'Spacer', 'Divider',
322
+ // Typography
323
+ 'Text', 'Heading', 'Badge', 'Code', 'Kbd', 'Mark',
324
+ // Forms
325
+ 'Button', 'IconButton', 'Input', 'InputGroup', 'InputLeftElement', 'InputRightElement',
326
+ 'Textarea', 'Select', 'Checkbox', 'Radio', 'RadioGroup', 'Switch', 'Slider',
327
+ 'FormControl', 'FormLabel', 'FormHelperText', 'FormErrorMessage',
328
+ // Feedback
329
+ 'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription', 'Progress', 'Skeleton', 'Spinner',
330
+ 'Toast', 'useToast', 'CircularProgress', 'CircularProgressLabel',
331
+ // Data Display
332
+ 'Avatar', 'AvatarGroup', 'Card', 'CardHeader', 'CardBody', 'CardFooter',
333
+ 'Image', 'Badge', 'Stat', 'StatLabel', 'StatNumber', 'StatHelpText', 'StatArrow',
334
+ 'Table', 'Thead', 'Tbody', 'Tfoot', 'Tr', 'Th', 'Td', 'TableCaption',
335
+ // Navigation
336
+ 'Breadcrumb', 'BreadcrumbItem', 'BreadcrumbLink', 'Link', 'LinkBox', 'LinkOverlay',
337
+ 'Tabs', 'TabList', 'TabPanels', 'Tab', 'TabPanel',
338
+ // Overlay
339
+ 'Modal', 'ModalOverlay', 'ModalContent', 'ModalHeader', 'ModalFooter', 'ModalBody', 'ModalCloseButton',
340
+ 'Drawer', 'DrawerBody', 'DrawerFooter', 'DrawerHeader', 'DrawerOverlay', 'DrawerContent', 'DrawerCloseButton',
341
+ 'Menu', 'MenuButton', 'MenuList', 'MenuItem', 'MenuItemOption', 'MenuGroup', 'MenuOptionGroup', 'MenuDivider',
342
+ 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverHeader', 'PopoverBody', 'PopoverFooter', 'PopoverArrow', 'PopoverCloseButton',
343
+ 'Tooltip', 'AlertDialog', 'AlertDialogBody', 'AlertDialogFooter', 'AlertDialogHeader', 'AlertDialogContent', 'AlertDialogOverlay',
344
+ // Disclosure
345
+ 'Accordion', 'AccordionItem', 'AccordionButton', 'AccordionPanel', 'AccordionIcon',
346
+ 'VisuallyHidden', 'Show', 'Hide', 'Collapse',
347
+ // Media
348
+ 'Icon', 'CloseButton'
349
+ ];
350
+ return basicComponents.map(name => ({
351
+ name,
352
+ description: `${name} component from Chakra UI`,
353
+ category: this.categorizeComponent(name, '')
354
+ }));
355
+ }
356
+ // Add other design systems here as needed
357
+ // Default: return empty array
317
358
  return [];
318
359
  }
319
360
  /**
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { logger } from './logger.js';
3
4
  /**
4
5
  * Manages .gitignore entries for Story UI generated content
5
6
  */
@@ -26,7 +27,7 @@ export class GitignoreManager {
26
27
  // Check if the path is already ignored
27
28
  const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
28
29
  if (this.isPathIgnored(gitignoreContent, generatedPath)) {
29
- console.log(`✅ Generated stories directory already ignored: ${generatedPath}`);
30
+ logger.log(`✅ Generated stories directory already ignored: ${generatedPath}`);
30
31
  return;
31
32
  }
32
33
  // Add the ignore rule
@@ -60,7 +61,7 @@ export class GitignoreManager {
60
61
  createGitignore(gitignorePath, generatedPath) {
61
62
  const content = this.generateGitignoreSection(generatedPath);
62
63
  fs.writeFileSync(gitignorePath, content);
63
- console.log(`✅ Created .gitignore with Story UI generated directory: ${generatedPath}`);
64
+ logger.log(`✅ Created .gitignore with Story UI generated directory: ${generatedPath}`);
64
65
  }
65
66
  /**
66
67
  * Checks if the generated path is already ignored
@@ -89,7 +90,7 @@ export class GitignoreManager {
89
90
  const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
90
91
  const updatedContent = existingContent + separator + newSection;
91
92
  fs.writeFileSync(gitignorePath, updatedContent);
92
- console.log(`✅ Added Story UI generated directory to .gitignore: ${generatedPath}`);
93
+ logger.log(`✅ Added Story UI generated directory to .gitignore: ${generatedPath}`);
93
94
  }
94
95
  /**
95
96
  * Generates the gitignore section for Story UI
@@ -107,7 +108,7 @@ ${generatedPath}/**`;
107
108
  const generatedDir = this.config.generatedStoriesPath;
108
109
  if (!fs.existsSync(generatedDir)) {
109
110
  fs.mkdirSync(generatedDir, { recursive: true });
110
- console.log(`✅ Created generated stories directory: ${generatedDir}`);
111
+ logger.log(`✅ Created generated stories directory: ${generatedDir}`);
111
112
  // Create a README to explain the purpose
112
113
  this.createGeneratedDirectoryReadme(generatedDir);
113
114
  }
@@ -142,7 +143,7 @@ Stories in this directory will appear in Storybook under the "${this.config.stor
142
143
  Generated by [Story UI](https://github.com/your-org/story-ui) - AI-powered Storybook story generator.
143
144
  `;
144
145
  fs.writeFileSync(readmePath, readmeContent);
145
- console.log(`✅ Created README in generated directory`);
146
+ logger.log(`✅ Created README in generated directory`);
146
147
  }
147
148
  /**
148
149
  * Cleans up old generated stories (optional utility)
@@ -168,7 +169,7 @@ Generated by [Story UI](https://github.com/your-org/story-ui) - AI-powered Story
168
169
  }
169
170
  }
170
171
  if (cleanedCount > 0) {
171
- console.log(`🧹 Cleaned up ${cleanedCount} old generated stories`);
172
+ logger.log(`🧹 Cleaned up ${cleanedCount} old generated stories`);
172
173
  }
173
174
  }
174
175
  }
@@ -0,0 +1,52 @@
1
+ // Utility for safe logging that doesn't corrupt MCP stdio communication
2
+ const isMcpMode = () => {
3
+ return process.argv.includes('mcp') || process.env.STORY_UI_MCP_MODE === 'true';
4
+ };
5
+ // Remove emojis from strings to prevent JSON parsing errors in MCP
6
+ const stripEmojis = (str) => {
7
+ // Remove common emojis and unicode symbols
8
+ return str.replace(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F000}-\u{1F02F}]|[\u{1F0A0}-\u{1F0FF}]|[\u{1F100}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F900}-\u{1F9FF}]|[\u{2190}-\u{21FF}]|[\u{2300}-\u{23FF}]|[\u{25A0}-\u{25FF}]|[\u{2B00}-\u{2BFF}]|[\u{3000}-\u{303F}]|✅|❌|⚠️|🔍|📦|📋|🔄|📊|⭐|🚀|💡|🎯|🔧|📌|🏃|🎨|💪|🌟|🎉|🎊|👍|👎|📝|📄|🗑️|🗂️|📁|🖥️|💻|📱|🌐|🔒|🔓|🔑|🔨|⚡|🔥|💧|🌈|☀️|🌙|⭐|✨|💫|☁️|🌧️|⛈️|❄️|☃️|⛄|🌬️|💨|🌪️|🌫️|🌊|🎯/gu, '');
9
+ };
10
+ const processArgs = (args) => {
11
+ if (!isMcpMode())
12
+ return args;
13
+ return args.map(arg => {
14
+ if (typeof arg === 'string') {
15
+ return stripEmojis(arg);
16
+ }
17
+ return arg;
18
+ });
19
+ };
20
+ export const logger = {
21
+ log: (...args) => {
22
+ const processedArgs = processArgs(args);
23
+ if (isMcpMode()) {
24
+ console.error(...processedArgs);
25
+ }
26
+ else {
27
+ console.log(...args);
28
+ }
29
+ },
30
+ error: (...args) => {
31
+ const processedArgs = processArgs(args);
32
+ console.error(...processedArgs);
33
+ },
34
+ warn: (...args) => {
35
+ const processedArgs = processArgs(args);
36
+ if (isMcpMode()) {
37
+ console.error(...processedArgs);
38
+ }
39
+ else {
40
+ console.warn(...args);
41
+ }
42
+ },
43
+ info: (...args) => {
44
+ const processedArgs = processArgs(args);
45
+ if (isMcpMode()) {
46
+ console.error(...processedArgs);
47
+ }
48
+ else {
49
+ console.info(...args);
50
+ }
51
+ }
52
+ };