forgedev 1.0.0 → 1.0.2
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/CLAUDE.md +3 -3
- package/README.md +246 -246
- package/bin/devforge.js +4 -4
- package/package.json +33 -33
- package/src/claude-configurator.js +260 -260
- package/src/cli.js +119 -119
- package/src/composer.js +214 -214
- package/src/doctor-checks.js +743 -743
- package/src/doctor-prompts.js +295 -295
- package/src/doctor.js +281 -281
- package/src/guided.js +315 -315
- package/src/index.js +148 -148
- package/src/init-mode.js +138 -134
- package/src/prompts.js +155 -155
- package/src/scanner.js +368 -368
- package/templates/claude-code/agents/code-quality-reviewer.md +41 -41
- package/templates/claude-code/agents/production-readiness.md +55 -55
- package/templates/claude-code/agents/security-reviewer.md +41 -41
- package/templates/claude-code/agents/spec-validator.md +34 -34
- package/templates/claude-code/agents/uat-validator.md +37 -37
- package/templates/claude-code/claude-md/base.md +33 -33
- package/templates/claude-code/commands/done.md +19 -19
- package/templates/claude-code/commands/generate-prd.md +45 -45
- package/templates/claude-code/commands/generate-uat.md +35 -35
- package/templates/claude-code/commands/next.md +20 -20
- package/templates/claude-code/commands/optimize-claude-md.md +31 -31
- package/templates/claude-code/commands/status.md +24 -24
- package/templates/claude-code/commands/workflows.md +26 -0
- package/templates/claude-code/hooks/polyglot.json +36 -36
- package/templates/claude-code/hooks/python.json +36 -36
- package/templates/claude-code/hooks/scripts/autofix-polyglot.sh +16 -16
- package/templates/claude-code/hooks/scripts/autofix-python.sh +14 -14
- package/templates/claude-code/hooks/scripts/autofix-typescript.sh +14 -14
- package/templates/claude-code/hooks/scripts/guard-protected-files.sh +21 -21
- package/templates/claude-code/hooks/typescript.json +36 -36
- package/templates/claude-code/commands/help.md +0 -26
package/src/guided.js
CHANGED
|
@@ -1,315 +1,315 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { log, writeFile } from './utils.js';
|
|
4
|
-
import { askDescription, askAdjustment, askGuidedConfirm } from './prompts.js';
|
|
5
|
-
import { recommend } from './recommender.js';
|
|
6
|
-
|
|
7
|
-
const FEATURE_MAP = {
|
|
8
|
-
auth: ['login', 'sign up', 'signup', 'account', 'user', 'password', 'register', 'authentication'],
|
|
9
|
-
realtime: ['real-time', 'realtime', 'live', 'instant', 'track', 'notify', 'chat', 'websocket', 'stream'],
|
|
10
|
-
ai: ['ai', 'smart', 'auto-generate', 'recommend', 'suggest', 'intelligent', 'gpt', 'llm', 'chatbot', 'openai', 'anthropic', 'claude', 'embedding', 'vector', 'rag', 'ml', 'machine learning'],
|
|
11
|
-
payments: ['pay', 'order', 'purchase', 'subscribe', 'billing', 'stripe', 'price', 'cart', 'checkout', 'invoice'],
|
|
12
|
-
uploads: ['upload', 'image', 'photo', 'file', 'document', 'pdf', 'attachment', 'media', 's3', 'storage'],
|
|
13
|
-
dashboard: ['dashboard', 'analytics', 'report', 'chart', 'stats', 'metrics', 'admin panel'],
|
|
14
|
-
roles: ['admin', 'role', 'permission', 'owner', 'manager', 'staff', 'customer', 'moderator'],
|
|
15
|
-
email: ['email', 'notification', 'send', 'newsletter', 'invite', 'alert'],
|
|
16
|
-
search: ['search', 'filter', 'browse', 'find', 'catalog', 'list', 'sort'],
|
|
17
|
-
scheduling: ['schedule', 'book', 'appointment', 'calendar', 'reserve', 'time slot', 'booking'],
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const FRONTEND_SIGNALS = ['dashboard', 'landing', 'ui', 'page', 'form', 'website', 'portal', 'interface', 'responsive', 'frontend', 'display', 'view', 'browse', 'menu'];
|
|
21
|
-
const BACKEND_ONLY_SIGNALS = ['api', 'endpoint', 'webhook', 'microservice', 'worker', 'cron', 'queue', 'batch', 'pipeline'];
|
|
22
|
-
const PYTHON_SIGNALS = ['python', 'fastapi', 'flask', 'django', 'ml', 'data science', 'scraping', 'pandas', 'numpy', 'tensorflow'];
|
|
23
|
-
|
|
24
|
-
const ENTITY_PATTERNS = [
|
|
25
|
-
/manage (?:their |the )?(\w+)/gi,
|
|
26
|
-
/(?:create|add|edit|update|delete|view|list|track|monitor) (?:their |the |a |an )?(\w+)/gi,
|
|
27
|
-
/(\w+) can /gi,
|
|
28
|
-
/store (\w+)/gi,
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const STOP_WORDS = new Set([
|
|
32
|
-
'i', 'a', 'an', 'the', 'and', 'or', 'but', 'is', 'are', 'was', 'were',
|
|
33
|
-
'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
|
|
34
|
-
'would', 'could', 'should', 'may', 'might', 'can', 'shall', 'to', 'of',
|
|
35
|
-
'in', 'for', 'on', 'with', 'at', 'by', 'from', 'it', 'its', 'this',
|
|
36
|
-
'that', 'these', 'those', 'my', 'your', 'their', 'our', 'them', 'they',
|
|
37
|
-
'we', 'you', 'me', 'him', 'her', 'us', 'what', 'which', 'who', 'whom',
|
|
38
|
-
'want', 'need', 'like', 'app', 'application', 'system', 'platform',
|
|
39
|
-
'where', 'when', 'how', 'not', 'no', 'so', 'if', 'then', 'also',
|
|
40
|
-
]);
|
|
41
|
-
|
|
42
|
-
export function analyzeDescription(text) {
|
|
43
|
-
if (!text || !text.trim()) {
|
|
44
|
-
return { features: [], entities: [], suggestedServiceType: 'full_stack', suggestedLanguage: 'typescript' };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const lower = text.toLowerCase();
|
|
48
|
-
const features = [];
|
|
49
|
-
|
|
50
|
-
for (const [feature, keywords] of Object.entries(FEATURE_MAP)) {
|
|
51
|
-
if (keywords.some(k => lower.includes(k))) {
|
|
52
|
-
features.push(feature);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Extract entities
|
|
57
|
-
const entitySet = new Set();
|
|
58
|
-
for (const pattern of ENTITY_PATTERNS) {
|
|
59
|
-
let match;
|
|
60
|
-
const regex = new RegExp(pattern.source, pattern.flags);
|
|
61
|
-
while ((match = regex.exec(lower)) !== null) {
|
|
62
|
-
const entity = match[1];
|
|
63
|
-
if (entity && entity.length > 2 && !STOP_WORDS.has(entity)) {
|
|
64
|
-
entitySet.add(entity);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const entities = [...entitySet];
|
|
69
|
-
|
|
70
|
-
// Determine if frontend/backend needed
|
|
71
|
-
const needsFrontend = FRONTEND_SIGNALS.some(s => lower.includes(s)) ||
|
|
72
|
-
features.some(f => ['dashboard', 'search', 'uploads'].includes(f)) ||
|
|
73
|
-
!BACKEND_ONLY_SIGNALS.some(s => lower.includes(s));
|
|
74
|
-
const needsBackend = BACKEND_ONLY_SIGNALS.some(s => lower.includes(s)) ||
|
|
75
|
-
features.some(f => ['auth', 'payments', 'email', 'ai'].includes(f)) ||
|
|
76
|
-
needsFrontend; // full-stack apps always need a backend
|
|
77
|
-
|
|
78
|
-
// Determine language
|
|
79
|
-
const suggestPython = PYTHON_SIGNALS.some(s => lower.includes(s));
|
|
80
|
-
const suggestedLanguage = suggestPython ? 'python' : 'typescript';
|
|
81
|
-
|
|
82
|
-
// Determine service type
|
|
83
|
-
let suggestedServiceType;
|
|
84
|
-
if (needsFrontend && needsBackend && suggestPython) {
|
|
85
|
-
suggestedServiceType = 'full_stack'; // will become polyglot
|
|
86
|
-
} else if (needsFrontend && needsBackend) {
|
|
87
|
-
suggestedServiceType = 'full_stack';
|
|
88
|
-
} else if (!needsFrontend && needsBackend) {
|
|
89
|
-
suggestedServiceType = 'api_service';
|
|
90
|
-
} else {
|
|
91
|
-
suggestedServiceType = 'web_app';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// AI service override — if AI is the primary focus
|
|
95
|
-
if (features.includes('ai') && !features.includes('dashboard') && !features.includes('payments')) {
|
|
96
|
-
suggestedServiceType = 'ai_service';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { features, entities, suggestedServiceType, suggestedLanguage };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function mapFeaturesToStack(analysis) {
|
|
103
|
-
const refinements = {
|
|
104
|
-
language: analysis.suggestedLanguage,
|
|
105
|
-
auth: analysis.features.includes('auth') || analysis.features.includes('roles'),
|
|
106
|
-
ai: analysis.features.includes('ai'),
|
|
107
|
-
fileUploads: analysis.features.includes('uploads'),
|
|
108
|
-
realtime: analysis.features.includes('realtime'),
|
|
109
|
-
deployment: 'docker',
|
|
110
|
-
claudeCode: true,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const stackConfig = recommend(analysis.suggestedServiceType, refinements);
|
|
114
|
-
return stackConfig;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function buildGuidedPreview(analysis, stackConfig) {
|
|
118
|
-
const lines = [];
|
|
119
|
-
|
|
120
|
-
lines.push(chalk.bold(' What you\'ll get:'));
|
|
121
|
-
|
|
122
|
-
// Group by detected entities/roles
|
|
123
|
-
const hasRoles = analysis.features.includes('roles');
|
|
124
|
-
const entities = analysis.entities;
|
|
125
|
-
|
|
126
|
-
if (entities.length > 0) {
|
|
127
|
-
lines.push(' ├── A website where you can:');
|
|
128
|
-
for (const entity of entities.slice(0, 5)) {
|
|
129
|
-
lines.push(` │ • Manage ${entity}`);
|
|
130
|
-
}
|
|
131
|
-
lines.push(' │');
|
|
132
|
-
} else {
|
|
133
|
-
lines.push(' ├── A website for your application');
|
|
134
|
-
lines.push(' │');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Feature descriptions in plain English
|
|
138
|
-
if (analysis.features.includes('auth') || analysis.features.includes('roles')) {
|
|
139
|
-
const roleDesc = hasRoles ? ' (with different roles like admin and user)' : '';
|
|
140
|
-
lines.push(` ├── User accounts${roleDesc}`);
|
|
141
|
-
}
|
|
142
|
-
if (analysis.features.includes('dashboard')) {
|
|
143
|
-
lines.push(' ├── A dashboard to see your data at a glance');
|
|
144
|
-
}
|
|
145
|
-
if (analysis.features.includes('realtime')) {
|
|
146
|
-
lines.push(' ├── Real-time updates (changes appear instantly)');
|
|
147
|
-
}
|
|
148
|
-
if (analysis.features.includes('payments')) {
|
|
149
|
-
lines.push(' ├── Payment processing for orders or subscriptions');
|
|
150
|
-
}
|
|
151
|
-
if (analysis.features.includes('uploads')) {
|
|
152
|
-
lines.push(' ├── File uploads (images, documents, etc.)');
|
|
153
|
-
}
|
|
154
|
-
if (analysis.features.includes('search')) {
|
|
155
|
-
lines.push(' ├── Search and filtering');
|
|
156
|
-
}
|
|
157
|
-
if (analysis.features.includes('scheduling')) {
|
|
158
|
-
lines.push(' ├── Scheduling and bookings');
|
|
159
|
-
}
|
|
160
|
-
if (analysis.features.includes('email')) {
|
|
161
|
-
lines.push(' ├── Email notifications');
|
|
162
|
-
}
|
|
163
|
-
if (analysis.features.includes('ai')) {
|
|
164
|
-
lines.push(' ├── AI-powered features');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Always included
|
|
168
|
-
lines.push(' ├── A database to store all your data');
|
|
169
|
-
lines.push(' │');
|
|
170
|
-
lines.push(' └── Developer tools:');
|
|
171
|
-
lines.push(' • Code quality checks (catches errors automatically)');
|
|
172
|
-
lines.push(' • Guided workflows (type /
|
|
173
|
-
lines.push(' • Testing templates');
|
|
174
|
-
|
|
175
|
-
return lines.join('\n');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function generateGettingStarted(projectName, stackConfig) {
|
|
179
|
-
const isPolyglot = stackConfig.stackId === 'polyglot-fullstack';
|
|
180
|
-
const isFastapi = stackConfig.stackId === 'fastapi-backend';
|
|
181
|
-
|
|
182
|
-
let setupCommands;
|
|
183
|
-
if (isFastapi) {
|
|
184
|
-
setupCommands = `cd backend
|
|
185
|
-
python -m venv venv
|
|
186
|
-
source venv/bin/activate # or venv\\\\Scripts\\\\activate on Windows
|
|
187
|
-
pip install -r requirements.txt
|
|
188
|
-
uvicorn app.main:app --reload`;
|
|
189
|
-
} else if (isPolyglot) {
|
|
190
|
-
setupCommands = `# Start the database:
|
|
191
|
-
docker compose up -d postgres
|
|
192
|
-
|
|
193
|
-
# Start the frontend:
|
|
194
|
-
cd frontend && npm install && npm run dev
|
|
195
|
-
|
|
196
|
-
# In another terminal, start the backend:
|
|
197
|
-
cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload`;
|
|
198
|
-
} else {
|
|
199
|
-
setupCommands = `npm install
|
|
200
|
-
npm run dev`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const devUrl = isFastapi ? 'http://localhost:8000/docs' : 'http://localhost:3000';
|
|
204
|
-
|
|
205
|
-
return `# Getting Started with ${projectName}
|
|
206
|
-
|
|
207
|
-
## What Just Happened?
|
|
208
|
-
|
|
209
|
-
DevForge created a starter version of your app with all the basic
|
|
210
|
-
building blocks in place. Think of it like getting a house with the
|
|
211
|
-
foundation, walls, and plumbing already done — you just need to
|
|
212
|
-
furnish and decorate it.
|
|
213
|
-
|
|
214
|
-
## Your Project Structure (Plain English)
|
|
215
|
-
|
|
216
|
-
\`\`\`
|
|
217
|
-
📁 ${projectName}/
|
|
218
|
-
├── src/ ← This is where your app's code lives
|
|
219
|
-
│ ├── app/ ← The pages people see in their browser
|
|
220
|
-
│ └── lib/ ← Helper code (like a toolbox)
|
|
221
|
-
├── docs/ ← Guides and documentation (you're reading one now!)
|
|
222
|
-
├── .claude/ ← AI assistant configuration (helps catch mistakes)
|
|
223
|
-
└── README.md ← Overview of your project
|
|
224
|
-
\`\`\`
|
|
225
|
-
|
|
226
|
-
## First Steps
|
|
227
|
-
|
|
228
|
-
### 1. Start your app
|
|
229
|
-
Open a terminal in this folder and run:
|
|
230
|
-
\`\`\`bash
|
|
231
|
-
${setupCommands}
|
|
232
|
-
\`\`\`
|
|
233
|
-
|
|
234
|
-
Then open ${devUrl} in your browser. You should see your app!
|
|
235
|
-
|
|
236
|
-
### 2. Make your first change
|
|
237
|
-
Open a file in your code editor. Change some text. Save.
|
|
238
|
-
Your browser should update automatically.
|
|
239
|
-
|
|
240
|
-
### 3. Get AI help (if you have Claude Code)
|
|
241
|
-
Type \`/
|
|
242
|
-
to do and give you step-by-step guidance.
|
|
243
|
-
|
|
244
|
-
## Common Tasks
|
|
245
|
-
|
|
246
|
-
| I want to... | Do this |
|
|
247
|
-
|-----------------------------|------------------------------------------------|
|
|
248
|
-
| Add a new page | Create a new folder in src/app/ with a page file |
|
|
249
|
-
| Change how something looks | Edit the styles in the component |
|
|
250
|
-
| Add a new feature | Type /
|
|
251
|
-
| Fix a bug | Type /
|
|
252
|
-
| Check if everything works | Type /
|
|
253
|
-
`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export async function runGuidedFlow(projectName, outputDir, scaffoldFn) {
|
|
257
|
-
let description = await askDescription();
|
|
258
|
-
|
|
259
|
-
if (!description || !description.trim()) {
|
|
260
|
-
log.error('Please provide a description of what you want to build.');
|
|
261
|
-
process.exit(1);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
let analysis = analyzeDescription(description);
|
|
265
|
-
let stackConfig = mapFeaturesToStack(analysis);
|
|
266
|
-
|
|
267
|
-
if (stackConfig.supported === false) {
|
|
268
|
-
log.warn('Could not determine a supported stack from your description.');
|
|
269
|
-
log.info('Try describing a web app, API, or full-stack application.');
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
stackConfig.projectName = projectName;
|
|
274
|
-
|
|
275
|
-
// Show preview loop
|
|
276
|
-
let confirmed = false;
|
|
277
|
-
while (!confirmed) {
|
|
278
|
-
console.log('');
|
|
279
|
-
console.log(chalk.bold(' Got it! Here\'s what I\'ll set up for you:'));
|
|
280
|
-
console.log('');
|
|
281
|
-
console.log(buildGuidedPreview(analysis, stackConfig));
|
|
282
|
-
console.log('');
|
|
283
|
-
|
|
284
|
-
const choice = await askGuidedConfirm();
|
|
285
|
-
|
|
286
|
-
if (choice === 'yes') {
|
|
287
|
-
confirmed = true;
|
|
288
|
-
} else {
|
|
289
|
-
const adjustment = await askAdjustment();
|
|
290
|
-
if (adjustment && adjustment.trim()) {
|
|
291
|
-
// Re-analyze with the combined description
|
|
292
|
-
description = description + '. ' + adjustment;
|
|
293
|
-
analysis = analyzeDescription(description);
|
|
294
|
-
stackConfig = mapFeaturesToStack(analysis);
|
|
295
|
-
if (stackConfig.supported === false) {
|
|
296
|
-
log.warn('Could not determine a supported stack. Using previous configuration.');
|
|
297
|
-
stackConfig = mapFeaturesToStack(analyzeDescription(description.split('. ').slice(0, -1).join('. ')));
|
|
298
|
-
}
|
|
299
|
-
stackConfig.projectName = projectName;
|
|
300
|
-
console.log('');
|
|
301
|
-
log.info('Updated!');
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Scaffold
|
|
307
|
-
await scaffoldFn(outputDir, stackConfig);
|
|
308
|
-
|
|
309
|
-
// Generate getting-started.md for guided mode
|
|
310
|
-
const gettingStartedContent = generateGettingStarted(projectName, stackConfig);
|
|
311
|
-
writeFile(
|
|
312
|
-
path.join(outputDir, 'docs', 'getting-started.md'),
|
|
313
|
-
gettingStartedContent
|
|
314
|
-
);
|
|
315
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { log, writeFile } from './utils.js';
|
|
4
|
+
import { askDescription, askAdjustment, askGuidedConfirm } from './prompts.js';
|
|
5
|
+
import { recommend } from './recommender.js';
|
|
6
|
+
|
|
7
|
+
const FEATURE_MAP = {
|
|
8
|
+
auth: ['login', 'sign up', 'signup', 'account', 'user', 'password', 'register', 'authentication'],
|
|
9
|
+
realtime: ['real-time', 'realtime', 'live', 'instant', 'track', 'notify', 'chat', 'websocket', 'stream'],
|
|
10
|
+
ai: ['ai', 'smart', 'auto-generate', 'recommend', 'suggest', 'intelligent', 'gpt', 'llm', 'chatbot', 'openai', 'anthropic', 'claude', 'embedding', 'vector', 'rag', 'ml', 'machine learning'],
|
|
11
|
+
payments: ['pay', 'order', 'purchase', 'subscribe', 'billing', 'stripe', 'price', 'cart', 'checkout', 'invoice'],
|
|
12
|
+
uploads: ['upload', 'image', 'photo', 'file', 'document', 'pdf', 'attachment', 'media', 's3', 'storage'],
|
|
13
|
+
dashboard: ['dashboard', 'analytics', 'report', 'chart', 'stats', 'metrics', 'admin panel'],
|
|
14
|
+
roles: ['admin', 'role', 'permission', 'owner', 'manager', 'staff', 'customer', 'moderator'],
|
|
15
|
+
email: ['email', 'notification', 'send', 'newsletter', 'invite', 'alert'],
|
|
16
|
+
search: ['search', 'filter', 'browse', 'find', 'catalog', 'list', 'sort'],
|
|
17
|
+
scheduling: ['schedule', 'book', 'appointment', 'calendar', 'reserve', 'time slot', 'booking'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const FRONTEND_SIGNALS = ['dashboard', 'landing', 'ui', 'page', 'form', 'website', 'portal', 'interface', 'responsive', 'frontend', 'display', 'view', 'browse', 'menu'];
|
|
21
|
+
const BACKEND_ONLY_SIGNALS = ['api', 'endpoint', 'webhook', 'microservice', 'worker', 'cron', 'queue', 'batch', 'pipeline'];
|
|
22
|
+
const PYTHON_SIGNALS = ['python', 'fastapi', 'flask', 'django', 'ml', 'data science', 'scraping', 'pandas', 'numpy', 'tensorflow'];
|
|
23
|
+
|
|
24
|
+
const ENTITY_PATTERNS = [
|
|
25
|
+
/manage (?:their |the )?(\w+)/gi,
|
|
26
|
+
/(?:create|add|edit|update|delete|view|list|track|monitor) (?:their |the |a |an )?(\w+)/gi,
|
|
27
|
+
/(\w+) can /gi,
|
|
28
|
+
/store (\w+)/gi,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const STOP_WORDS = new Set([
|
|
32
|
+
'i', 'a', 'an', 'the', 'and', 'or', 'but', 'is', 'are', 'was', 'were',
|
|
33
|
+
'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
|
|
34
|
+
'would', 'could', 'should', 'may', 'might', 'can', 'shall', 'to', 'of',
|
|
35
|
+
'in', 'for', 'on', 'with', 'at', 'by', 'from', 'it', 'its', 'this',
|
|
36
|
+
'that', 'these', 'those', 'my', 'your', 'their', 'our', 'them', 'they',
|
|
37
|
+
'we', 'you', 'me', 'him', 'her', 'us', 'what', 'which', 'who', 'whom',
|
|
38
|
+
'want', 'need', 'like', 'app', 'application', 'system', 'platform',
|
|
39
|
+
'where', 'when', 'how', 'not', 'no', 'so', 'if', 'then', 'also',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
export function analyzeDescription(text) {
|
|
43
|
+
if (!text || !text.trim()) {
|
|
44
|
+
return { features: [], entities: [], suggestedServiceType: 'full_stack', suggestedLanguage: 'typescript' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const lower = text.toLowerCase();
|
|
48
|
+
const features = [];
|
|
49
|
+
|
|
50
|
+
for (const [feature, keywords] of Object.entries(FEATURE_MAP)) {
|
|
51
|
+
if (keywords.some(k => lower.includes(k))) {
|
|
52
|
+
features.push(feature);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Extract entities
|
|
57
|
+
const entitySet = new Set();
|
|
58
|
+
for (const pattern of ENTITY_PATTERNS) {
|
|
59
|
+
let match;
|
|
60
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
61
|
+
while ((match = regex.exec(lower)) !== null) {
|
|
62
|
+
const entity = match[1];
|
|
63
|
+
if (entity && entity.length > 2 && !STOP_WORDS.has(entity)) {
|
|
64
|
+
entitySet.add(entity);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const entities = [...entitySet];
|
|
69
|
+
|
|
70
|
+
// Determine if frontend/backend needed
|
|
71
|
+
const needsFrontend = FRONTEND_SIGNALS.some(s => lower.includes(s)) ||
|
|
72
|
+
features.some(f => ['dashboard', 'search', 'uploads'].includes(f)) ||
|
|
73
|
+
!BACKEND_ONLY_SIGNALS.some(s => lower.includes(s));
|
|
74
|
+
const needsBackend = BACKEND_ONLY_SIGNALS.some(s => lower.includes(s)) ||
|
|
75
|
+
features.some(f => ['auth', 'payments', 'email', 'ai'].includes(f)) ||
|
|
76
|
+
needsFrontend; // full-stack apps always need a backend
|
|
77
|
+
|
|
78
|
+
// Determine language
|
|
79
|
+
const suggestPython = PYTHON_SIGNALS.some(s => lower.includes(s));
|
|
80
|
+
const suggestedLanguage = suggestPython ? 'python' : 'typescript';
|
|
81
|
+
|
|
82
|
+
// Determine service type
|
|
83
|
+
let suggestedServiceType;
|
|
84
|
+
if (needsFrontend && needsBackend && suggestPython) {
|
|
85
|
+
suggestedServiceType = 'full_stack'; // will become polyglot
|
|
86
|
+
} else if (needsFrontend && needsBackend) {
|
|
87
|
+
suggestedServiceType = 'full_stack';
|
|
88
|
+
} else if (!needsFrontend && needsBackend) {
|
|
89
|
+
suggestedServiceType = 'api_service';
|
|
90
|
+
} else {
|
|
91
|
+
suggestedServiceType = 'web_app';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// AI service override — if AI is the primary focus
|
|
95
|
+
if (features.includes('ai') && !features.includes('dashboard') && !features.includes('payments')) {
|
|
96
|
+
suggestedServiceType = 'ai_service';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { features, entities, suggestedServiceType, suggestedLanguage };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function mapFeaturesToStack(analysis) {
|
|
103
|
+
const refinements = {
|
|
104
|
+
language: analysis.suggestedLanguage,
|
|
105
|
+
auth: analysis.features.includes('auth') || analysis.features.includes('roles'),
|
|
106
|
+
ai: analysis.features.includes('ai'),
|
|
107
|
+
fileUploads: analysis.features.includes('uploads'),
|
|
108
|
+
realtime: analysis.features.includes('realtime'),
|
|
109
|
+
deployment: 'docker',
|
|
110
|
+
claudeCode: true,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const stackConfig = recommend(analysis.suggestedServiceType, refinements);
|
|
114
|
+
return stackConfig;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildGuidedPreview(analysis, stackConfig) {
|
|
118
|
+
const lines = [];
|
|
119
|
+
|
|
120
|
+
lines.push(chalk.bold(' What you\'ll get:'));
|
|
121
|
+
|
|
122
|
+
// Group by detected entities/roles
|
|
123
|
+
const hasRoles = analysis.features.includes('roles');
|
|
124
|
+
const entities = analysis.entities;
|
|
125
|
+
|
|
126
|
+
if (entities.length > 0) {
|
|
127
|
+
lines.push(' ├── A website where you can:');
|
|
128
|
+
for (const entity of entities.slice(0, 5)) {
|
|
129
|
+
lines.push(` │ • Manage ${entity}`);
|
|
130
|
+
}
|
|
131
|
+
lines.push(' │');
|
|
132
|
+
} else {
|
|
133
|
+
lines.push(' ├── A website for your application');
|
|
134
|
+
lines.push(' │');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Feature descriptions in plain English
|
|
138
|
+
if (analysis.features.includes('auth') || analysis.features.includes('roles')) {
|
|
139
|
+
const roleDesc = hasRoles ? ' (with different roles like admin and user)' : '';
|
|
140
|
+
lines.push(` ├── User accounts${roleDesc}`);
|
|
141
|
+
}
|
|
142
|
+
if (analysis.features.includes('dashboard')) {
|
|
143
|
+
lines.push(' ├── A dashboard to see your data at a glance');
|
|
144
|
+
}
|
|
145
|
+
if (analysis.features.includes('realtime')) {
|
|
146
|
+
lines.push(' ├── Real-time updates (changes appear instantly)');
|
|
147
|
+
}
|
|
148
|
+
if (analysis.features.includes('payments')) {
|
|
149
|
+
lines.push(' ├── Payment processing for orders or subscriptions');
|
|
150
|
+
}
|
|
151
|
+
if (analysis.features.includes('uploads')) {
|
|
152
|
+
lines.push(' ├── File uploads (images, documents, etc.)');
|
|
153
|
+
}
|
|
154
|
+
if (analysis.features.includes('search')) {
|
|
155
|
+
lines.push(' ├── Search and filtering');
|
|
156
|
+
}
|
|
157
|
+
if (analysis.features.includes('scheduling')) {
|
|
158
|
+
lines.push(' ├── Scheduling and bookings');
|
|
159
|
+
}
|
|
160
|
+
if (analysis.features.includes('email')) {
|
|
161
|
+
lines.push(' ├── Email notifications');
|
|
162
|
+
}
|
|
163
|
+
if (analysis.features.includes('ai')) {
|
|
164
|
+
lines.push(' ├── AI-powered features');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Always included
|
|
168
|
+
lines.push(' ├── A database to store all your data');
|
|
169
|
+
lines.push(' │');
|
|
170
|
+
lines.push(' └── Developer tools:');
|
|
171
|
+
lines.push(' • Code quality checks (catches errors automatically)');
|
|
172
|
+
lines.push(' • Guided workflows (type /workflows anytime you\'re stuck)');
|
|
173
|
+
lines.push(' • Testing templates');
|
|
174
|
+
|
|
175
|
+
return lines.join('\n');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function generateGettingStarted(projectName, stackConfig) {
|
|
179
|
+
const isPolyglot = stackConfig.stackId === 'polyglot-fullstack';
|
|
180
|
+
const isFastapi = stackConfig.stackId === 'fastapi-backend';
|
|
181
|
+
|
|
182
|
+
let setupCommands;
|
|
183
|
+
if (isFastapi) {
|
|
184
|
+
setupCommands = `cd backend
|
|
185
|
+
python -m venv venv
|
|
186
|
+
source venv/bin/activate # or venv\\\\Scripts\\\\activate on Windows
|
|
187
|
+
pip install -r requirements.txt
|
|
188
|
+
uvicorn app.main:app --reload`;
|
|
189
|
+
} else if (isPolyglot) {
|
|
190
|
+
setupCommands = `# Start the database:
|
|
191
|
+
docker compose up -d postgres
|
|
192
|
+
|
|
193
|
+
# Start the frontend:
|
|
194
|
+
cd frontend && npm install && npm run dev
|
|
195
|
+
|
|
196
|
+
# In another terminal, start the backend:
|
|
197
|
+
cd backend && pip install -r requirements.txt && uvicorn app.main:app --reload`;
|
|
198
|
+
} else {
|
|
199
|
+
setupCommands = `npm install
|
|
200
|
+
npm run dev`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const devUrl = isFastapi ? 'http://localhost:8000/docs' : 'http://localhost:3000';
|
|
204
|
+
|
|
205
|
+
return `# Getting Started with ${projectName}
|
|
206
|
+
|
|
207
|
+
## What Just Happened?
|
|
208
|
+
|
|
209
|
+
DevForge created a starter version of your app with all the basic
|
|
210
|
+
building blocks in place. Think of it like getting a house with the
|
|
211
|
+
foundation, walls, and plumbing already done — you just need to
|
|
212
|
+
furnish and decorate it.
|
|
213
|
+
|
|
214
|
+
## Your Project Structure (Plain English)
|
|
215
|
+
|
|
216
|
+
\`\`\`
|
|
217
|
+
📁 ${projectName}/
|
|
218
|
+
├── src/ ← This is where your app's code lives
|
|
219
|
+
│ ├── app/ ← The pages people see in their browser
|
|
220
|
+
│ └── lib/ ← Helper code (like a toolbox)
|
|
221
|
+
├── docs/ ← Guides and documentation (you're reading one now!)
|
|
222
|
+
├── .claude/ ← AI assistant configuration (helps catch mistakes)
|
|
223
|
+
└── README.md ← Overview of your project
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
## First Steps
|
|
227
|
+
|
|
228
|
+
### 1. Start your app
|
|
229
|
+
Open a terminal in this folder and run:
|
|
230
|
+
\`\`\`bash
|
|
231
|
+
${setupCommands}
|
|
232
|
+
\`\`\`
|
|
233
|
+
|
|
234
|
+
Then open ${devUrl} in your browser. You should see your app!
|
|
235
|
+
|
|
236
|
+
### 2. Make your first change
|
|
237
|
+
Open a file in your code editor. Change some text. Save.
|
|
238
|
+
Your browser should update automatically.
|
|
239
|
+
|
|
240
|
+
### 3. Get AI help (if you have Claude Code)
|
|
241
|
+
Type \`/workflows\` in Claude Code. It will ask what you're trying
|
|
242
|
+
to do and give you step-by-step guidance.
|
|
243
|
+
|
|
244
|
+
## Common Tasks
|
|
245
|
+
|
|
246
|
+
| I want to... | Do this |
|
|
247
|
+
|-----------------------------|------------------------------------------------|
|
|
248
|
+
| Add a new page | Create a new folder in src/app/ with a page file |
|
|
249
|
+
| Change how something looks | Edit the styles in the component |
|
|
250
|
+
| Add a new feature | Type /workflows → "Start a new feature" |
|
|
251
|
+
| Fix a bug | Type /workflows → "Fix a bug" |
|
|
252
|
+
| Check if everything works | Type /status |
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function runGuidedFlow(projectName, outputDir, scaffoldFn) {
|
|
257
|
+
let description = await askDescription();
|
|
258
|
+
|
|
259
|
+
if (!description || !description.trim()) {
|
|
260
|
+
log.error('Please provide a description of what you want to build.');
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let analysis = analyzeDescription(description);
|
|
265
|
+
let stackConfig = mapFeaturesToStack(analysis);
|
|
266
|
+
|
|
267
|
+
if (stackConfig.supported === false) {
|
|
268
|
+
log.warn('Could not determine a supported stack from your description.');
|
|
269
|
+
log.info('Try describing a web app, API, or full-stack application.');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
stackConfig.projectName = projectName;
|
|
274
|
+
|
|
275
|
+
// Show preview loop
|
|
276
|
+
let confirmed = false;
|
|
277
|
+
while (!confirmed) {
|
|
278
|
+
console.log('');
|
|
279
|
+
console.log(chalk.bold(' Got it! Here\'s what I\'ll set up for you:'));
|
|
280
|
+
console.log('');
|
|
281
|
+
console.log(buildGuidedPreview(analysis, stackConfig));
|
|
282
|
+
console.log('');
|
|
283
|
+
|
|
284
|
+
const choice = await askGuidedConfirm();
|
|
285
|
+
|
|
286
|
+
if (choice === 'yes') {
|
|
287
|
+
confirmed = true;
|
|
288
|
+
} else {
|
|
289
|
+
const adjustment = await askAdjustment();
|
|
290
|
+
if (adjustment && adjustment.trim()) {
|
|
291
|
+
// Re-analyze with the combined description
|
|
292
|
+
description = description + '. ' + adjustment;
|
|
293
|
+
analysis = analyzeDescription(description);
|
|
294
|
+
stackConfig = mapFeaturesToStack(analysis);
|
|
295
|
+
if (stackConfig.supported === false) {
|
|
296
|
+
log.warn('Could not determine a supported stack. Using previous configuration.');
|
|
297
|
+
stackConfig = mapFeaturesToStack(analyzeDescription(description.split('. ').slice(0, -1).join('. ')));
|
|
298
|
+
}
|
|
299
|
+
stackConfig.projectName = projectName;
|
|
300
|
+
console.log('');
|
|
301
|
+
log.info('Updated!');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Scaffold
|
|
307
|
+
await scaffoldFn(outputDir, stackConfig);
|
|
308
|
+
|
|
309
|
+
// Generate getting-started.md for guided mode
|
|
310
|
+
const gettingStartedContent = generateGettingStarted(projectName, stackConfig);
|
|
311
|
+
writeFile(
|
|
312
|
+
path.join(outputDir, 'docs', 'getting-started.md'),
|
|
313
|
+
gettingStartedContent
|
|
314
|
+
);
|
|
315
|
+
}
|