@tpitre/story-ui 1.7.1 → 2.0.1

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 (35) hide show
  1. package/.env.sample +3 -1
  2. package/README.md +160 -606
  3. package/dist/cli/index.js +23 -24
  4. package/dist/cli/setup.js +295 -36
  5. package/dist/mcp-server/index.js +67 -0
  6. package/dist/mcp-server/routes/generateStory.js +323 -56
  7. package/dist/story-generator/componentBlacklist.js +181 -0
  8. package/dist/story-generator/componentDiscovery.js +9 -2
  9. package/dist/story-generator/configLoader.js +109 -39
  10. package/dist/story-generator/considerationsLoader.js +204 -0
  11. package/dist/story-generator/documentation-sources.js +36 -0
  12. package/dist/story-generator/documentationLoader.js +214 -0
  13. package/dist/story-generator/dynamicPackageDiscovery.js +527 -0
  14. package/dist/story-generator/enhancedComponentDiscovery.js +369 -118
  15. package/dist/story-generator/generateStory.js +7 -3
  16. package/dist/story-generator/postProcessStory.js +71 -0
  17. package/dist/story-generator/promptGenerator.js +286 -37
  18. package/dist/story-generator/storyHistory.js +118 -0
  19. package/dist/story-generator/storyTracker.js +33 -18
  20. package/dist/story-generator/storyValidator.js +39 -0
  21. package/dist/story-generator/universalDesignSystemAdapter.js +209 -0
  22. package/dist/story-generator/validateStory.js +82 -7
  23. package/dist/story-ui.config.js +12 -5
  24. package/package.json +11 -6
  25. package/templates/StoryUI/StoryUIPanel.stories.tsx +29 -13
  26. package/templates/StoryUI/StoryUIPanel.tsx +489 -359
  27. package/templates/react-import-rule.json +36 -0
  28. package/templates/story-generation-rules.json +29 -0
  29. package/templates/story-ui-considerations.json +156 -0
  30. package/templates/story-ui-considerations.md +109 -0
  31. package/templates/story-ui-docs-README.md +55 -0
  32. package/dist/scripts/test-validation.js +0 -81
  33. package/dist/test-storybooks/chakra-test/src/components/index.js +0 -3
  34. package/dist/test-storybooks/custom-design-test/src/components/index.js +0 -3
  35. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Blacklist of component names that AI commonly mistakes for real components
3
+ * These are often story export names or made-up components that don't exist
4
+ */
5
+ import { isDeprecatedComponent, getComponentReplacement } from './documentation-sources.js';
6
+ export const BLACKLISTED_COMPONENTS = [
7
+ // Story UI interface components (not design system components)
8
+ 'StoryUIPanel',
9
+ // Common mistaken imports
10
+ 'GitHubStyleRepoCard',
11
+ 'GitHubHeader',
12
+ 'CustomCard',
13
+ 'StyledCard',
14
+ 'LayoutWrapper',
15
+ 'ContentWrapper',
16
+ 'StoryCard',
17
+ 'UICard',
18
+ 'ComponentCard',
19
+ // Patterns that indicate story exports
20
+ /^.*Story$/,
21
+ /^.*Example$/,
22
+ /^.*Demo$/,
23
+ /^Custom.*/,
24
+ /^Styled.*/,
25
+ /^.*Layout$/,
26
+ /^.*Wrapper$/,
27
+ /^.*Container$/,
28
+ /^GitHub.*/,
29
+ /^.*Header$/, // Except for 'Header' itself
30
+ /^.*Card$/, // Except for specific known card components
31
+ ];
32
+ // Generic deprecated components (can be extended per design system)
33
+ const DEPRECATED_COMPONENTS = {
34
+ // Add deprecated components for supported design systems as needed
35
+ };
36
+ export function isBlacklistedComponent(componentName, validComponents, importPath) {
37
+ // Check if it's a known deprecated component from documentation
38
+ if (importPath && isDeprecatedComponent(importPath, componentName)) {
39
+ return true;
40
+ }
41
+ // Check for deprecated components for specific design systems
42
+ if (importPath) {
43
+ for (const [systemPath, deprecatedList] of Object.entries(DEPRECATED_COMPONENTS)) {
44
+ if (importPath.includes(systemPath) && deprecatedList.includes(componentName)) {
45
+ return true;
46
+ }
47
+ }
48
+ }
49
+ // First check if it's in the allowed list - if so, it's not blacklisted
50
+ if (validComponents.has(componentName)) {
51
+ return false;
52
+ }
53
+ // Check exact matches
54
+ if (BLACKLISTED_COMPONENTS.includes(componentName)) {
55
+ return true;
56
+ }
57
+ // Check regex patterns
58
+ for (const pattern of BLACKLISTED_COMPONENTS) {
59
+ if (pattern instanceof RegExp && pattern.test(componentName)) {
60
+ // Special cases - these are allowed even if they match patterns
61
+ if (componentName === 'Header' || componentName === 'Card') {
62
+ return false;
63
+ }
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+ /**
70
+ * Common icon name mistakes - maps incorrect names to correct ones
71
+ */
72
+ export const ICON_CORRECTIONS = {
73
+ 'CommitIcon': 'GitCommitIcon',
74
+ 'BranchIcon': 'GitBranchIcon',
75
+ 'MergeIcon': 'GitMergeIcon',
76
+ 'PullRequestIcon': 'GitPullRequestIcon',
77
+ 'RepoForkedIcon': 'RepoForkedIcon',
78
+ 'IssueIcon': 'IssueOpenedIcon',
79
+ 'PullIcon': 'GitPullRequestIcon',
80
+ 'ForkIcon': 'RepoForkedIcon',
81
+ 'CloseIcon': 'XIcon',
82
+ 'CheckmarkIcon': 'CheckIcon',
83
+ 'CrossIcon': 'XIcon',
84
+ 'EditIcon': 'PencilIcon',
85
+ 'DeleteIcon': 'TrashIcon',
86
+ 'SettingsIcon': 'GearIcon',
87
+ 'UserIcon': 'PersonIcon',
88
+ 'EmailIcon': 'MailIcon',
89
+ 'TimeIcon': 'ClockIcon',
90
+ 'CodeReviewIcon': 'CodeIcon',
91
+ 'CommentDiscussionIcon': 'CommentIcon',
92
+ };
93
+ export function isBlacklistedIcon(iconName, allowedIcons) {
94
+ // First check if it's in the allowed list - if so, it's not blacklisted
95
+ if (allowedIcons.has(iconName)) {
96
+ return false;
97
+ }
98
+ // Check if it's a known incorrect name
99
+ if (ICON_CORRECTIONS[iconName]) {
100
+ return true;
101
+ }
102
+ // Check if it follows incorrect patterns
103
+ const incorrectPatterns = [
104
+ // Icons that are missing the 'Git' prefix
105
+ /^(Commit|Branch|Merge|PullRequest)Icon$/,
106
+ // Icons with wrong suffixes
107
+ /Icon[0-9]+$/,
108
+ // Made up icon names
109
+ /^Custom.*Icon$/,
110
+ /^.*IconStyle$/,
111
+ ];
112
+ return incorrectPatterns.some(pattern => pattern.test(iconName));
113
+ }
114
+ export function validateImports(imports, allowedComponents) {
115
+ const valid = [];
116
+ const invalid = [];
117
+ const suggestions = new Map();
118
+ for (const importName of imports) {
119
+ if (isBlacklistedComponent(importName, allowedComponents)) {
120
+ invalid.push(importName);
121
+ // Suggest alternatives
122
+ const suggested = suggestAlternatives(importName, allowedComponents);
123
+ if (suggested.length > 0) {
124
+ suggestions.set(importName, suggested);
125
+ }
126
+ }
127
+ else if (!allowedComponents.has(importName)) {
128
+ invalid.push(importName);
129
+ }
130
+ else {
131
+ valid.push(importName);
132
+ }
133
+ }
134
+ return { valid, invalid, suggestions };
135
+ }
136
+ function suggestAlternatives(invalidComponent, allowedComponents) {
137
+ const suggestions = [];
138
+ // Specific mappings for common mistakes
139
+ const mappings = {
140
+ 'StoryUIPanel': ['Box', 'Card', 'Stack'],
141
+ 'GitHubStyleRepoCard': ['Box', 'Card'],
142
+ 'GitHubHeader': ['Header'],
143
+ 'CustomCard': ['Box', 'Card'],
144
+ 'StyledCard': ['Box', 'Card'],
145
+ 'LayoutWrapper': ['Box', 'Stack', 'PageLayout'],
146
+ 'ContentWrapper': ['Box', 'Stack'],
147
+ };
148
+ if (mappings[invalidComponent]) {
149
+ return mappings[invalidComponent].filter(comp => allowedComponents.has(comp));
150
+ }
151
+ // Generic suggestions based on patterns
152
+ if (invalidComponent.includes('Card')) {
153
+ suggestions.push('Box', 'Card');
154
+ }
155
+ if (invalidComponent.includes('Header')) {
156
+ suggestions.push('Header', 'Pagehead');
157
+ }
158
+ if (invalidComponent.includes('Layout') || invalidComponent.includes('Wrapper')) {
159
+ suggestions.push('Box', 'Stack', 'PageLayout');
160
+ }
161
+ return suggestions.filter(comp => allowedComponents.has(comp));
162
+ }
163
+ /**
164
+ * Get helpful error message for blacklisted component
165
+ */
166
+ export function getBlacklistErrorMessage(componentName, importPath) {
167
+ if (importPath) {
168
+ const replacement = getComponentReplacement(importPath, componentName);
169
+ if (replacement) {
170
+ return `"${componentName}" is deprecated. Use ${replacement} instead.`;
171
+ }
172
+ }
173
+ // Existing error messages
174
+ if (componentName.endsWith('Icon') && !componentName.includes('Icon')) {
175
+ return `"${componentName}" looks like an icon but may not exist. Check the available icons list.`;
176
+ }
177
+ if (BLACKLISTED_COMPONENTS.some(pattern => pattern instanceof RegExp && pattern.test(componentName))) {
178
+ return `"${componentName}" appears to be invalid. Use standard component names.`;
179
+ }
180
+ return `"${componentName}" is not a valid component.`;
181
+ }
@@ -10,10 +10,14 @@ export function discoverComponentsFromDirectory(componentsPath, componentPrefix
10
10
  }
11
11
  const components = [];
12
12
  const dirs = fs.readdirSync(componentsPath, { withFileTypes: true })
13
- .filter(d => d.isDirectory() && d.name !== 'generated' && !d.name.startsWith('.'));
13
+ .filter(d => d.isDirectory() && d.name !== 'generated' && !d.name.startsWith('.') && d.name !== 'StoryUI');
14
14
  for (const dir of dirs) {
15
15
  const componentName = componentPrefix + dir.name;
16
16
  const componentPath = path.join(componentsPath, dir.name);
17
+ // Skip Story UI components
18
+ if (componentName === 'StoryUIPanel' || componentName.startsWith('StoryUI')) {
19
+ continue;
20
+ }
17
21
  // Look for story files to extract component information
18
22
  const storyFile = path.join(componentPath, `${dir.name}.stories.tsx`);
19
23
  const componentFile = path.join(componentPath, `${dir.name}.tsx`);
@@ -201,8 +205,11 @@ function extractSlotsFromComponent(content) {
201
205
  return [];
202
206
  }
203
207
  function categorizeComponent(name, description) {
208
+ if (!name || typeof name !== 'string') {
209
+ return 'other';
210
+ }
204
211
  const lowerName = name.toLowerCase();
205
- const lowerDesc = description.toLowerCase();
212
+ const lowerDesc = (description && typeof description === 'string') ? description.toLowerCase() : '';
206
213
  if (lowerName.includes('layout') || lowerName.includes('grid') || lowerName.includes('container') || lowerName.includes('section')) {
207
214
  return 'layout';
208
215
  }
@@ -42,6 +42,20 @@ export function loadUserConfig() {
42
42
  eval(evalContent);
43
43
  const userConfig = module.exports;
44
44
  const config = createStoryUIConfig(userConfig.default || userConfig);
45
+ // Detect Storybook framework if not already specified
46
+ if (!config.storybookFramework) {
47
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
48
+ if (fs.existsSync(packageJsonPath)) {
49
+ try {
50
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
51
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
52
+ config.storybookFramework = detectStorybookFramework(dependencies);
53
+ }
54
+ catch (error) {
55
+ console.warn('Failed to detect Storybook framework:', error);
56
+ }
57
+ }
58
+ }
45
59
  // Cache the loaded config
46
60
  cachedConfig = config;
47
61
  configLoadTime = now;
@@ -58,10 +72,24 @@ export function loadUserConfig() {
58
72
  console.warn('No story-ui.config.js found. Using default configuration.');
59
73
  console.warn('Please create a story-ui.config.js file in your project root to configure Story UI for your design system.');
60
74
  }
75
+ // Create default config with detected framework
76
+ const defaultConfig = { ...DEFAULT_CONFIG };
77
+ // Detect Storybook framework for default config
78
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
79
+ if (fs.existsSync(packageJsonPath)) {
80
+ try {
81
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
82
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
83
+ defaultConfig.storybookFramework = detectStorybookFramework(dependencies);
84
+ }
85
+ catch (error) {
86
+ console.warn('Failed to detect Storybook framework:', error);
87
+ }
88
+ }
61
89
  // Cache the default config
62
- cachedConfig = DEFAULT_CONFIG;
90
+ cachedConfig = defaultConfig;
63
91
  configLoadTime = now;
64
- return DEFAULT_CONFIG;
92
+ return defaultConfig;
65
93
  }
66
94
  /**
67
95
  * Validates that the configuration has the necessary paths and components
@@ -83,7 +111,7 @@ export function validateConfig(config) {
83
111
  }
84
112
  }
85
113
  }
86
- // Determine if we're using an external package (like antd, @mui/material, etc.)
114
+ // Determine if we're using an external package (like antd, @chakra-ui/react, etc.)
87
115
  const isExternalPackage = config.importPath &&
88
116
  !config.importPath.startsWith('.') &&
89
117
  !config.importPath.startsWith('/') &&
@@ -100,15 +128,69 @@ export function validateConfig(config) {
100
128
  if (config.componentsMetadataPath && !fs.existsSync(config.componentsMetadataPath)) {
101
129
  errors.push(`Components metadata path does not exist: ${config.componentsMetadataPath}`);
102
130
  }
103
- // Check import path - but allow actual library names like 'antd'
104
- if (!config.importPath || config.importPath === 'your-component-library' || config.importPath.trim() === '') {
105
- errors.push('importPath must be configured to point to your component library');
131
+ // Check import path - but allow it to be optional if auto-discovery will find local components
132
+ const hasManualImportPath = config.importPath &&
133
+ config.importPath !== 'your-component-library' &&
134
+ config.importPath.trim() !== '';
135
+ const hasLocalComponents = checkForLocalComponents(config);
136
+ const hasManualComponents = config.components && config.components.length > 0;
137
+ if (!hasManualImportPath && !hasLocalComponents && !hasManualComponents) {
138
+ errors.push('Either importPath must be configured, or local components must be available for auto-discovery');
106
139
  }
107
140
  return {
108
141
  isValid: errors.length === 0,
109
142
  errors
110
143
  };
111
144
  }
145
+ /**
146
+ * Check if local components are available for auto-discovery
147
+ */
148
+ function checkForLocalComponents(config) {
149
+ // Get project root from generated stories path
150
+ let projectRoot = process.cwd();
151
+ if (config.generatedStoriesPath) {
152
+ let currentPath = path.resolve(config.generatedStoriesPath);
153
+ while (currentPath !== path.dirname(currentPath)) {
154
+ if (fs.existsSync(path.join(currentPath, 'package.json'))) {
155
+ projectRoot = currentPath;
156
+ break;
157
+ }
158
+ currentPath = path.dirname(currentPath);
159
+ }
160
+ }
161
+ // Check for manually configured components path
162
+ if (config.componentsPath && fs.existsSync(config.componentsPath)) {
163
+ return true;
164
+ }
165
+ // Check for common React component directories
166
+ const commonComponentDirs = [
167
+ 'src/components',
168
+ 'src/ui',
169
+ 'components',
170
+ 'ui',
171
+ 'src/lib/components',
172
+ 'lib/components',
173
+ 'src/shared/components',
174
+ 'shared/components'
175
+ ];
176
+ for (const dir of commonComponentDirs) {
177
+ const fullPath = path.join(projectRoot, dir);
178
+ if (fs.existsSync(fullPath)) {
179
+ // Check if it contains React component files
180
+ try {
181
+ const files = fs.readdirSync(fullPath);
182
+ const hasComponents = files.some(file => file.endsWith('.tsx') || file.endsWith('.jsx'));
183
+ if (hasComponents) {
184
+ return true;
185
+ }
186
+ }
187
+ catch (error) {
188
+ // Ignore errors and continue checking
189
+ }
190
+ }
191
+ }
192
+ return false;
193
+ }
112
194
  /**
113
195
  * Analyzes existing Storybook files to detect design system patterns
114
196
  */
@@ -203,6 +285,23 @@ export function analyzeExistingStories(projectRoot = process.cwd()) {
203
285
  layoutPatterns
204
286
  };
205
287
  }
288
+ /**
289
+ * Detects the Storybook framework being used
290
+ */
291
+ export function detectStorybookFramework(dependencies) {
292
+ // Check for Vite-based Storybook
293
+ if (dependencies['@storybook/react-vite']) {
294
+ return '@storybook/react-vite';
295
+ }
296
+ else if (dependencies['@storybook/react-webpack5']) {
297
+ return '@storybook/react-webpack5';
298
+ }
299
+ else if (dependencies['@storybook/nextjs']) {
300
+ return '@storybook/nextjs';
301
+ }
302
+ // Default to generic React
303
+ return '@storybook/react';
304
+ }
206
305
  /**
207
306
  * Auto-detects design system configuration by analyzing the project structure
208
307
  */
@@ -237,12 +336,15 @@ export function autoDetectDesignSystem() {
237
336
  const componentPrefix = findMostLikelyPrefix(analysis.componentPrefixes);
238
337
  // Determine layout patterns
239
338
  const layoutRules = detectLayoutPatterns(analysis.layoutPatterns, componentPrefix);
339
+ // Detect Storybook framework
340
+ const storybookFramework = detectStorybookFramework(dependencies);
240
341
  // Build configuration
241
342
  const config = {
242
343
  generatedStoriesPath: path.join(cwd, 'src/stories/generated/'),
243
344
  importPath: importPath,
244
345
  componentPrefix: componentPrefix,
245
- layoutRules: layoutRules
346
+ layoutRules: layoutRules,
347
+ storybookFramework: storybookFramework
246
348
  };
247
349
  // Only set componentsPath for local component libraries
248
350
  if (componentPath && !isExternalPackage) {
@@ -263,38 +365,6 @@ export function autoDetectDesignSystem() {
263
365
  * Detects known design systems from package.json dependencies
264
366
  */
265
367
  function detectKnownDesignSystems(dependencies) {
266
- // Material-UI detection
267
- if (dependencies['@mui/material']) {
268
- return {
269
- importPath: '@mui/material',
270
- componentPrefix: '',
271
- layoutRules: {
272
- multiColumnWrapper: 'Grid',
273
- columnComponent: 'Grid',
274
- containerComponent: 'Container',
275
- layoutExamples: {
276
- twoColumn: `<Grid container spacing={2}>
277
- <Grid item xs={6}>
278
- <Card>
279
- <CardContent>
280
- <Typography variant="h5">Left Card</Typography>
281
- <Typography>Left content</Typography>
282
- </CardContent>
283
- </Card>
284
- </Grid>
285
- <Grid item xs={6}>
286
- <Card>
287
- <CardContent>
288
- <Typography variant="h5">Right Card</Typography>
289
- <Typography>Right content</Typography>
290
- </CardContent>
291
- </Card>
292
- </Grid>
293
- </Grid>`
294
- }
295
- }
296
- };
297
- }
298
368
  // Chakra UI detection
299
369
  if (dependencies['@chakra-ui/react']) {
300
370
  return {
@@ -0,0 +1,204 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Loads AI considerations from a markdown or JSON file
5
+ */
6
+ export function loadConsiderations(considerationsPath) {
7
+ if (!considerationsPath) {
8
+ // Try to find considerations file in common locations
9
+ const possiblePaths = [
10
+ path.join(process.cwd(), 'story-ui-considerations.json'),
11
+ path.join(process.cwd(), 'story-ui-considerations.md'),
12
+ path.join(process.cwd(), '.storybook', 'story-ui-considerations.json'),
13
+ path.join(process.cwd(), '.storybook', 'story-ui-considerations.md'),
14
+ ];
15
+ for (const possiblePath of possiblePaths) {
16
+ if (fs.existsSync(possiblePath)) {
17
+ considerationsPath = possiblePath;
18
+ console.log(`Found considerations file at: ${considerationsPath}`);
19
+ break;
20
+ }
21
+ }
22
+ }
23
+ if (!considerationsPath || !fs.existsSync(considerationsPath)) {
24
+ return null;
25
+ }
26
+ try {
27
+ const content = fs.readFileSync(considerationsPath, 'utf-8');
28
+ const ext = path.extname(considerationsPath).toLowerCase();
29
+ if (ext === '.json') {
30
+ return JSON.parse(content);
31
+ }
32
+ else if (ext === '.md') {
33
+ return parseMarkdownConsiderations(content);
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.warn(`Failed to load considerations from ${considerationsPath}:`, error);
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Parses markdown content to extract AI considerations
43
+ */
44
+ function parseMarkdownConsiderations(content) {
45
+ const considerations = {};
46
+ // Extract library name and import path from frontmatter or headers
47
+ const libraryMatch = content.match(/\*\*Library Name\*\*:\s*(.+)/);
48
+ if (libraryMatch) {
49
+ considerations.libraryName = libraryMatch[1].trim();
50
+ }
51
+ const importMatch = content.match(/\*\*Import Path\*\*:\s*`(.+)`/);
52
+ if (importMatch) {
53
+ considerations.importPath = importMatch[1].trim();
54
+ }
55
+ // Extract sections
56
+ const sections = content.split(/^##\s+/m);
57
+ for (const section of sections) {
58
+ const lines = section.split('\n');
59
+ const title = lines[0]?.trim().toLowerCase();
60
+ if (title.includes('core principles')) {
61
+ considerations.corePrinciples = extractListItems(section);
62
+ }
63
+ else if (title.includes("do's and don'ts")) {
64
+ const doSection = section.match(/###\s*✅\s*DO\s*([\s\S]*?)(?=###|$)/);
65
+ const dontSection = section.match(/###\s*❌\s*DON'T\s*([\s\S]*?)(?=###|$)/);
66
+ if (doSection) {
67
+ considerations.dos = extractListItems(doSection[1]);
68
+ }
69
+ if (dontSection) {
70
+ considerations.donts = extractListItems(dontSection[1]);
71
+ }
72
+ }
73
+ else if (title.includes('special considerations')) {
74
+ considerations.specialConsiderations = extractListItems(section);
75
+ }
76
+ else if (title.includes('error patterns')) {
77
+ considerations.commonMistakes = extractErrorPatterns(section);
78
+ }
79
+ }
80
+ // Extract AI instructions from the entire content
81
+ considerations.aiInstructions = {
82
+ general: extractAIInstructions(content)
83
+ };
84
+ return considerations;
85
+ }
86
+ /**
87
+ * Extracts list items from a markdown section
88
+ */
89
+ function extractListItems(section) {
90
+ const items = [];
91
+ const lines = section.split('\n');
92
+ for (const line of lines) {
93
+ const trimmed = line.trim();
94
+ if (trimmed.startsWith('- ') || trimmed.startsWith('* ') || trimmed.match(/^\d+\.\s+/)) {
95
+ const item = trimmed.replace(/^[-*]\s+/, '').replace(/^\d+\.\s+/, '').trim();
96
+ if (item && !item.startsWith('<!--')) {
97
+ items.push(item);
98
+ }
99
+ }
100
+ }
101
+ return items;
102
+ }
103
+ /**
104
+ * Extracts error patterns from markdown
105
+ */
106
+ function extractErrorPatterns(section) {
107
+ const patterns = [];
108
+ const errorBlocks = section.match(/\d+\.\s*\*\*Wrong\*\*:[\s\S]*?(?=\d+\.\s*\*\*Wrong\*\*:|$)/g);
109
+ if (errorBlocks) {
110
+ for (const block of errorBlocks) {
111
+ const wrongMatch = block.match(/\*\*Wrong\*\*:\s*`([^`]+)`/);
112
+ const rightMatch = block.match(/\*\*Right\*\*:\s*`([^`]+)`/);
113
+ const whyMatch = block.match(/\*\*Why\*\*:\s*(.+)/);
114
+ const issueMatch = block.match(/^\d+\.\s*(.+?)\s*\n/);
115
+ if (wrongMatch && rightMatch) {
116
+ patterns.push({
117
+ issue: issueMatch ? issueMatch[1].replace(/\*\*/g, '').trim() : 'Pattern',
118
+ wrong: wrongMatch[1],
119
+ correct: rightMatch[1],
120
+ explanation: whyMatch ? whyMatch[1].trim() : ''
121
+ });
122
+ }
123
+ }
124
+ }
125
+ return patterns;
126
+ }
127
+ /**
128
+ * Extracts AI-specific instructions from the content
129
+ */
130
+ function extractAIInstructions(content) {
131
+ const instructions = [];
132
+ // Look for specific instruction patterns
133
+ const instructionPatterns = [
134
+ /(?:always|never|must|should|ensure|remember|important)(?:\s+\w+)*[:.]?\s*(.+)/gi,
135
+ /(?:use|prefer|avoid|don't)(?:\s+\w+)*[:.]?\s*(.+)/gi
136
+ ];
137
+ for (const pattern of instructionPatterns) {
138
+ const matches = content.matchAll(pattern);
139
+ for (const match of matches) {
140
+ const instruction = match[1].trim();
141
+ if (instruction.length > 10 && instruction.length < 200 && !instruction.includes('<!--')) {
142
+ instructions.push(instruction);
143
+ }
144
+ }
145
+ }
146
+ // Remove duplicates
147
+ return [...new Set(instructions)];
148
+ }
149
+ /**
150
+ * Converts considerations to prompt additions
151
+ */
152
+ export function considerationsToPrompt(considerations) {
153
+ const promptParts = [];
154
+ if (considerations.libraryName) {
155
+ promptParts.push(`LIBRARY: ${considerations.libraryName}`);
156
+ }
157
+ if (considerations.description) {
158
+ promptParts.push(`\n${considerations.description}`);
159
+ }
160
+ if (considerations.corePrinciples && considerations.corePrinciples.length > 0) {
161
+ promptParts.push('\nCORE PRINCIPLES:');
162
+ considerations.corePrinciples.forEach(principle => {
163
+ promptParts.push(`- ${principle}`);
164
+ });
165
+ }
166
+ if (considerations.dos && considerations.dos.length > 0) {
167
+ promptParts.push('\nIMPORTANT - ALWAYS DO:');
168
+ considerations.dos.forEach(rule => {
169
+ promptParts.push(`- ${rule}`);
170
+ });
171
+ }
172
+ if (considerations.donts && considerations.donts.length > 0) {
173
+ promptParts.push('\nIMPORTANT - NEVER DO:');
174
+ considerations.donts.forEach(rule => {
175
+ promptParts.push(`- ${rule}`);
176
+ });
177
+ }
178
+ if (considerations.commonMistakes && considerations.commonMistakes.length > 0) {
179
+ promptParts.push('\nCOMMON MISTAKES TO AVOID:');
180
+ considerations.commonMistakes.forEach(mistake => {
181
+ promptParts.push(`- ${mistake.issue}`);
182
+ promptParts.push(` WRONG: ${mistake.wrong}`);
183
+ promptParts.push(` CORRECT: ${mistake.correct}`);
184
+ if (mistake.explanation) {
185
+ promptParts.push(` WHY: ${mistake.explanation}`);
186
+ }
187
+ });
188
+ }
189
+ if (considerations.specialConsiderations && considerations.specialConsiderations.length > 0) {
190
+ promptParts.push('\nSPECIAL CONSIDERATIONS:');
191
+ considerations.specialConsiderations.forEach(consideration => {
192
+ promptParts.push(`- ${consideration}`);
193
+ });
194
+ }
195
+ if (considerations.aiInstructions) {
196
+ if (considerations.aiInstructions.general && considerations.aiInstructions.general.length > 0) {
197
+ promptParts.push('\nAI INSTRUCTIONS:');
198
+ considerations.aiInstructions.general.forEach(instruction => {
199
+ promptParts.push(`- ${instruction}`);
200
+ });
201
+ }
202
+ }
203
+ return promptParts.join('\n');
204
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Documentation sources for design systems
3
+ * Provides fallback documentation when custom docs aren't available
4
+ */
5
+ /**
6
+ * Pre-bundled documentation for popular design systems
7
+ * Users should provide their own documentation in story-ui-docs/ directory for best results
8
+ */
9
+ export const BUNDLED_DOCUMENTATION = {
10
+ // Currently empty - users should provide their own documentation
11
+ // in story-ui-docs/ directory for best AI story generation
12
+ };
13
+ /**
14
+ * Get documentation for a design system
15
+ * Falls back to bundled docs if no scraped docs exist
16
+ */
17
+ export function getDocumentation(importPath) {
18
+ // First check for scraped documentation
19
+ const cacheFile = `.story-ui-cache/${importPath.replace(/[@\/]/g, '-')}-docs.json`;
20
+ // For now, just return bundled docs if available
21
+ return BUNDLED_DOCUMENTATION[importPath] || null;
22
+ }
23
+ /**
24
+ * Check if a component is deprecated for a given design system
25
+ */
26
+ export function isDeprecatedComponent(importPath, componentName) {
27
+ const docs = getDocumentation(importPath);
28
+ return docs?.deprecations && docs.deprecations[componentName];
29
+ }
30
+ /**
31
+ * Get replacement suggestion for a deprecated component
32
+ */
33
+ export function getComponentReplacement(importPath, componentName) {
34
+ const docs = getDocumentation(importPath);
35
+ return docs?.deprecations?.[componentName] || null;
36
+ }