@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.
- package/.env.sample +3 -1
- package/README.md +160 -606
- package/dist/cli/index.js +23 -24
- package/dist/cli/setup.js +295 -36
- package/dist/mcp-server/index.js +67 -0
- package/dist/mcp-server/routes/generateStory.js +323 -56
- package/dist/story-generator/componentBlacklist.js +181 -0
- package/dist/story-generator/componentDiscovery.js +9 -2
- package/dist/story-generator/configLoader.js +109 -39
- package/dist/story-generator/considerationsLoader.js +204 -0
- package/dist/story-generator/documentation-sources.js +36 -0
- package/dist/story-generator/documentationLoader.js +214 -0
- package/dist/story-generator/dynamicPackageDiscovery.js +527 -0
- package/dist/story-generator/enhancedComponentDiscovery.js +369 -118
- package/dist/story-generator/generateStory.js +7 -3
- package/dist/story-generator/postProcessStory.js +71 -0
- package/dist/story-generator/promptGenerator.js +286 -37
- package/dist/story-generator/storyHistory.js +118 -0
- package/dist/story-generator/storyTracker.js +33 -18
- package/dist/story-generator/storyValidator.js +39 -0
- package/dist/story-generator/universalDesignSystemAdapter.js +209 -0
- package/dist/story-generator/validateStory.js +82 -7
- package/dist/story-ui.config.js +12 -5
- package/package.json +11 -6
- package/templates/StoryUI/StoryUIPanel.stories.tsx +29 -13
- package/templates/StoryUI/StoryUIPanel.tsx +489 -359
- package/templates/react-import-rule.json +36 -0
- package/templates/story-generation-rules.json +29 -0
- package/templates/story-ui-considerations.json +156 -0
- package/templates/story-ui-considerations.md +109 -0
- package/templates/story-ui-docs-README.md +55 -0
- package/dist/scripts/test-validation.js +0 -81
- package/dist/test-storybooks/chakra-test/src/components/index.js +0 -3
- package/dist/test-storybooks/custom-design-test/src/components/index.js +0 -3
- 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 =
|
|
90
|
+
cachedConfig = defaultConfig;
|
|
63
91
|
configLoadTime = now;
|
|
64
|
-
return
|
|
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, @
|
|
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
|
|
104
|
-
|
|
105
|
-
|
|
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
|
+
}
|