ai-flow-dev 2.2.1 → 2.2.4

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/dist/cli.js CHANGED
@@ -13,40 +13,73 @@ const __dirname = dirname(__filename);
13
13
  const ROOT_DIR = path.resolve(__dirname, '..');
14
14
  const program = new Command();
15
15
  const AI_TOOLS = [
16
- {
17
- name: 'GitHub Copilot',
18
- value: 'copilot',
19
- description: '',
20
- },
21
- {
22
- name: 'Claude Code',
23
- value: 'claude',
24
- description: '',
25
- },
26
- {
27
- name: 'Cursor',
28
- value: 'cursor',
29
- description: '',
30
- },
31
- {
32
- name: 'Gemini',
33
- value: 'gemini',
34
- description: '',
16
+ { name: 'GitHub Copilot', value: 'copilot', description: '' },
17
+ { name: 'Claude Code', value: 'claude', description: '' },
18
+ { name: 'Cursor', value: 'cursor', description: '' },
19
+ { name: 'Gemini', value: 'gemini', description: '' },
20
+ { name: 'Antigravity', value: 'antigravity', description: '' },
21
+ { name: 'All AI Tools', value: 'all', description: '' },
22
+ ];
23
+ const SLASH_COMMANDS = [
24
+ { name: '/flow-build', desc: 'Full documentation flow' },
25
+ { name: '/flow-work', desc: 'Development orchestrator (feature, refactor, fix, resume)' },
26
+ { name: '/flow-check', desc: 'Combined code review & testing workflow' },
27
+ { name: '/flow-commit', desc: 'Atomic commits (Conventional Commits)' },
28
+ { name: '/flow-docs-sync', desc: 'Sincronización de documentación' },
29
+ ];
30
+ const PROJECT_PHASES = {
31
+ backend: {
32
+ label: 'Backend',
33
+ phases: [
34
+ 'fase 0: Context Discovery (proyectos existentes)',
35
+ 'fase 1: Discovery & Business',
36
+ 'fase 2: Data Architecture',
37
+ 'fase 3: System Architecture',
38
+ 'fase 4: Security & Auth',
39
+ 'fase 5: Code Standards',
40
+ 'fase 6: Testing',
41
+ 'fase 7: Operations + Tools',
42
+ 'fase 8: Project Setup & Final Docs',
43
+ 'fase 9: Implementation Roadmap (opcional)',
44
+ 'fase 10: User Stories Generation (opcional)',
45
+ ],
35
46
  },
36
- {
37
- name: 'Antigravity',
38
- value: 'antigravity',
39
- description: '',
47
+ frontend: {
48
+ label: 'Frontend',
49
+ phases: [
50
+ 'fase 0: Context Discovery (proyectos existentes)',
51
+ 'fase 1: Discovery & UX',
52
+ 'fase 2: Components & Framework',
53
+ 'fase 3: State Management',
54
+ 'fase 4: Styling & Design',
55
+ 'fase 5: Code Standards',
56
+ 'fase 6: Testing',
57
+ 'fase 7: Deployment',
58
+ 'fase 8: Project Setup & Final Docs',
59
+ 'fase 9: Implementation Roadmap (opcional)',
60
+ 'fase 10: User Stories Generation (opcional)',
61
+ ],
40
62
  },
41
- {
42
- name: 'All AI Tools',
43
- value: 'all',
44
- description: '',
63
+ mobile: {
64
+ label: 'Mobile',
65
+ phases: [
66
+ 'fase 0: Context Discovery (proyectos existentes)',
67
+ 'fase 1: Platform & Framework Selection',
68
+ 'fase 2: Navigation & Architecture',
69
+ 'fase 3: State & Data Management',
70
+ 'fase 4: Permissions & Native Features',
71
+ 'fase 5: Code Standards',
72
+ 'fase 6: Testing Strategy',
73
+ 'fase 7: Store Deployment',
74
+ 'fase 8: Project Setup & Final Documentation',
75
+ 'fase 9: Implementation Roadmap (opcional)',
76
+ 'fase 10: User Stories Generation (opcional)',
77
+ ],
45
78
  },
46
- ];
47
- // Read package.json for version
48
- const packageJsonPath = path.join(__dirname, '..', 'package.json');
49
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
79
+ };
80
+ // Read package.json for version (Sync for top-level usage)
81
+ const packageJsonPath = path.join(ROOT_DIR, 'package.json');
82
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
50
83
  const PKG_VERSION = packageJson.version;
51
84
  const EXIT = {
52
85
  OK: 0,
@@ -75,23 +108,21 @@ function fsErrorMessage(e) {
75
108
  const msg = anyErr && anyErr.message ? String(anyErr.message) : String(e);
76
109
  return `${code}: ${msg}`;
77
110
  }
78
- async function selectAITool(providedTool) {
79
- if (providedTool) {
80
- const tool = AI_TOOLS.find((t) => t.value === providedTool);
81
- if (!tool) {
82
- console.error(chalk.red(`❌ Invalid AI tool: ${providedTool}`));
83
- console.log(chalk.yellow('Available options: claude, cursor, copilot, gemini, antigravity, all'));
84
- process.exit(EXIT.INVALID_ARGS);
85
- }
86
- return providedTool === 'all'
87
- ? ['claude', 'cursor', 'copilot', 'gemini', 'antigravity']
88
- : [providedTool];
89
- }
90
- // If no TTY available (non-interactive mode, e.g., in tests), default to copilot
91
- if (!process.stdin.isTTY) {
92
- return ['copilot'];
111
+ function getProjectTypeLabel(type) {
112
+ switch (type) {
113
+ case 'backend':
114
+ return 'Backend';
115
+ case 'frontend':
116
+ return 'Frontend';
117
+ case 'fullstack':
118
+ return 'Full Stack';
119
+ case 'mobile':
120
+ return 'Mobile';
121
+ default:
122
+ return type;
93
123
  }
94
- // Display banner
124
+ }
125
+ function printBanner() {
95
126
  console.log('\n');
96
127
  console.log(chalk.cyan(' ╔═══════════════════════════════════════════════════════════════════╗'));
97
128
  console.log(chalk.cyan(' ║') +
@@ -134,19 +165,75 @@ async function selectAITool(providedTool) {
134
165
  chalk.cyan('║'));
135
166
  console.log(chalk.cyan(' ╚═══════════════════════════════════════════════════════════════════╝'));
136
167
  console.log('\n');
137
- console.log(chalk.white(' 📂 Project Setup'));
168
+ console.log(chalk.white(' 📂 Project Context'));
138
169
  console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
139
170
  console.log(chalk.gray(` Working Directory: ${process.cwd()}`));
140
- console.log(chalk.gray(' Version: 2.2.0'));
171
+ console.log(chalk.gray(` Version: ${PKG_VERSION}`));
141
172
  console.log('\n');
173
+ }
174
+ function printAvailableCommands(projectType) {
175
+ console.log(chalk.white('Available slash commands:'));
176
+ for (const cmd of SLASH_COMMANDS) {
177
+ console.log(chalk.gray(` ${cmd.name.padEnd(25)} - ${cmd.desc}`));
178
+ }
179
+ const phasesInfo = PROJECT_PHASES[projectType === 'fullstack' ? 'backend' : projectType];
180
+ if (phasesInfo) {
181
+ console.log(chalk.gray(`\n Fases disponibles (${phasesInfo.label}):`));
182
+ for (const phase of phasesInfo.phases) {
183
+ console.log(chalk.gray(` ${phase}`));
184
+ }
185
+ }
186
+ }
187
+ function printNextSteps(config) {
188
+ const toolsText = config.aiTools.length === 1
189
+ ? config.aiTools[0]
190
+ : `${config.aiTools.slice(0, -1).join(', ')} and ${config.aiTools[config.aiTools.length - 1]}`;
191
+ console.log(chalk.white('\nNext steps:'));
192
+ if (config.projectType === 'fullstack') {
193
+ const aiToolName = config.aiTools.includes('claude')
194
+ ? 'Claude Code'
195
+ : config.aiTools.includes('cursor')
196
+ ? 'Cursor'
197
+ : toolsText;
198
+ console.log(chalk.cyan(` 1. Open your AI tool (${aiToolName})`));
199
+ console.log(chalk.cyan(' 2. Backend Prompts: /backend-flow-build'));
200
+ console.log(chalk.cyan(' 3. Frontend Prompts: /frontend-flow-build'));
201
+ console.log(chalk.gray(' Each will guide you through up to 11 phases (0-10)\n'));
202
+ }
203
+ else {
204
+ const aiToolName = config.aiTools.includes('claude')
205
+ ? 'Claude Code'
206
+ : config.aiTools.includes('cursor')
207
+ ? 'Cursor'
208
+ : config.aiTools.includes('antigravity')
209
+ ? 'Antigravity'
210
+ : toolsText;
211
+ console.log(chalk.cyan(` 1. Open ${aiToolName}`));
212
+ console.log(chalk.cyan(' 2. Run: /flow-build'));
213
+ console.log(chalk.gray(' This will guide you through up to 11 phases (0-10)\n'));
214
+ }
215
+ printAvailableCommands(config.projectType);
216
+ }
217
+ async function selectAITool(providedTool) {
218
+ if (providedTool) {
219
+ const tool = AI_TOOLS.find((t) => t.value === providedTool);
220
+ if (!tool) {
221
+ console.error(chalk.red(`❌ Invalid AI tool: ${providedTool}`));
222
+ console.log(chalk.yellow('Available options: claude, cursor, copilot, gemini, antigravity, all'));
223
+ process.exit(EXIT.INVALID_ARGS);
224
+ }
225
+ return providedTool === 'all'
226
+ ? ['claude', 'cursor', 'copilot', 'gemini', 'antigravity']
227
+ : [providedTool];
228
+ }
229
+ if (!process.stdin.isTTY)
230
+ return ['copilot'];
231
+ printBanner();
142
232
  console.log(chalk.white(' 🤖 Select your AI development tool:'));
143
233
  console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
144
234
  const selectedTool = await select({
145
235
  message: 'Select your AI tool:',
146
- choices: AI_TOOLS.map((tool) => ({
147
- name: tool.name,
148
- value: tool.value,
149
- })),
236
+ choices: AI_TOOLS.map((tool) => ({ name: tool.name, value: tool.value })),
150
237
  default: 'copilot',
151
238
  });
152
239
  return selectedTool === 'all'
@@ -154,7 +241,6 @@ async function selectAITool(providedTool) {
154
241
  : [selectedTool];
155
242
  }
156
243
  async function selectProjectType(providedType) {
157
- // v1.4.0: Backend, Frontend, Fullstack, and Mobile supported
158
244
  if (providedType) {
159
245
  const valid = ['backend', 'frontend', 'fullstack', 'mobile'];
160
246
  if (!valid.includes(providedType)) {
@@ -164,11 +250,8 @@ async function selectProjectType(providedType) {
164
250
  }
165
251
  return providedType;
166
252
  }
167
- // If no TTY available (non-interactive mode, e.g., in tests), default to backend
168
- if (!process.stdin.isTTY) {
253
+ if (!process.stdin.isTTY)
169
254
  return 'backend';
170
- }
171
- // v1.4.0: Interactive selection for backend/frontend/fullstack/mobile
172
255
  const projectType = await select({
173
256
  message: 'What type of project are you building?',
174
257
  choices: [
@@ -182,14 +265,12 @@ async function selectProjectType(providedType) {
182
265
  return projectType;
183
266
  }
184
267
  async function checkIfInitialized(targetPath) {
185
- const bootstrapPath = path.join(targetPath, '.ai-flow');
186
- return await fs.pathExists(bootstrapPath);
268
+ return await fs.pathExists(path.join(targetPath, '.ai-flow'));
187
269
  }
188
270
  async function createBootstrapStructure(targetPath, aiTools, projectType = 'backend', dryRun, verbose) {
189
271
  const spinner = ora('Creating .ai-flow structure...').start();
190
272
  try {
191
273
  const bootstrapPath = path.join(targetPath, '.ai-flow');
192
- // Create core directories
193
274
  if (dryRun) {
194
275
  spinner.succeed('Created .ai-flow structure (dry-run)');
195
276
  return;
@@ -199,19 +280,16 @@ async function createBootstrapStructure(targetPath, aiTools, projectType = 'back
199
280
  await fs.ensureDir(path.join(bootstrapPath, 'prompts'));
200
281
  await fs.ensureDir(path.join(bootstrapPath, 'templates', 'docs'));
201
282
  await fs.ensureDir(path.join(bootstrapPath, 'templates', 'specs'));
202
- // Create config file with new projectType field
203
283
  const config = {
204
284
  version: PKG_VERSION,
205
- aiTools: aiTools,
285
+ aiTools,
206
286
  createdAt: new Date().toISOString(),
207
- projectType: projectType,
208
- // Deprecated fields for backward compatibility
287
+ projectType,
209
288
  backend: projectType === 'backend' || projectType === 'fullstack',
210
289
  frontend: projectType === 'frontend' || projectType === 'fullstack',
211
290
  mobile: projectType === 'mobile',
212
291
  };
213
292
  await fs.writeJSON(path.join(bootstrapPath, 'core', 'config.json'), config, { spaces: 2 });
214
- logVerbose(`Wrote ${path.join(bootstrapPath, 'core', 'config.json')}`, verbose);
215
293
  spinner.succeed('Created .ai-flow structure');
216
294
  }
217
295
  catch (error) {
@@ -222,109 +300,80 @@ async function createBootstrapStructure(targetPath, aiTools, projectType = 'back
222
300
  async function copyTemplates(targetPath, projectType = 'backend', aiTools = [], dryRun, verbose) {
223
301
  const spinner = ora('Copying templates to .ai-flow/templates/...').start();
224
302
  try {
225
- // Templates are copied WITHOUT rendering to .ai-flow/templates/
226
- // Phase 8 will render them to project root
227
303
  if (dryRun) {
228
304
  spinner.succeed('Templates copied (dry-run)');
229
305
  return;
230
306
  }
231
- await assertDirWritable(targetPath);
232
307
  const destTemplatesPath = path.join(targetPath, '.ai-flow', 'templates');
233
- // Find all .template.md and .template files in a directory and subfolders
234
308
  const walk = async (dir) => {
235
309
  let files = [];
236
310
  for (const entry of await fs.readdir(dir)) {
237
311
  const fullPath = path.join(dir, entry);
238
312
  const stat = await fs.stat(fullPath);
239
- if (stat.isDirectory()) {
313
+ if (stat.isDirectory())
240
314
  files = files.concat(await walk(fullPath));
241
- }
242
- else if (entry.endsWith('.template.md') || entry.endsWith('.template')) {
315
+ else if (entry.endsWith('.template.md') || entry.endsWith('.template'))
243
316
  files.push(fullPath);
244
- }
245
317
  }
246
318
  return files;
247
319
  };
248
- // Collect template files from project-type-specific directories
249
320
  const templateSources = [];
250
- // Always include root templates (AGENT.template.md)
251
- const rootTemplatesSource = path.join(ROOT_DIR, 'templates');
252
321
  const processedFiles = new Map();
253
- // Only scan root level files (not subdirectories)
254
- const allRootItems = await fs.readdir(rootTemplatesSource);
255
- for (const item of allRootItems) {
322
+ const rootTemplatesSource = path.join(ROOT_DIR, 'templates');
323
+ for (const item of await fs.readdir(rootTemplatesSource)) {
256
324
  const fullPath = path.join(rootTemplatesSource, item);
257
- const stat = await fs.stat(fullPath);
258
- if (stat.isFile() && item.endsWith('.template.md')) {
325
+ if ((await fs.stat(fullPath)).isFile() && item.endsWith('.template.md')) {
259
326
  processedFiles.set(item, { file: fullPath, base: rootTemplatesSource });
260
327
  }
261
328
  }
262
- // Include project-type-specific templates
263
- if (projectType === 'backend') {
264
- const backendSource = path.join(ROOT_DIR, 'templates', 'backend');
265
- templateSources.push({ source: backendSource, base: backendSource });
266
- }
267
- else if (projectType === 'frontend') {
268
- const frontendSource = path.join(ROOT_DIR, 'templates', 'frontend');
269
- templateSources.push({ source: frontendSource, base: frontendSource });
270
- }
329
+ if (projectType === 'backend')
330
+ templateSources.push({
331
+ source: path.join(ROOT_DIR, 'templates', 'backend'),
332
+ base: path.join(ROOT_DIR, 'templates', 'backend'),
333
+ });
334
+ else if (projectType === 'frontend')
335
+ templateSources.push({
336
+ source: path.join(ROOT_DIR, 'templates', 'frontend'),
337
+ base: path.join(ROOT_DIR, 'templates', 'frontend'),
338
+ });
271
339
  else if (projectType === 'fullstack') {
272
- // v1.3.0: Copy both backend and frontend templates
273
- // Priority: fullstack-specific templates > backend templates > frontend templates
274
340
  const fullstackSource = path.join(ROOT_DIR, 'templates', 'fullstack');
275
- const backendSource = path.join(ROOT_DIR, 'templates', 'backend');
276
- const frontendSource = path.join(ROOT_DIR, 'templates', 'frontend');
277
- // Check if fullstack templates directory exists
278
- const fullstackExists = await fs.pathExists(fullstackSource);
279
- if (fullstackExists) {
280
- templateSources.push({
281
- source: fullstackSource,
282
- base: fullstackSource,
283
- });
284
- }
285
- // Backend templates (used as base for conflicts)
286
- templateSources.push({ source: backendSource, base: backendSource });
287
- // Frontend templates (will overwrite only if not in fullstack and not conflicting with backend)
288
- templateSources.push({ source: frontendSource, base: frontendSource });
289
- }
290
- else if (projectType === 'mobile') {
291
- // v1.4.0: Copy mobile templates
292
- const mobileSource = path.join(ROOT_DIR, 'templates', 'mobile');
293
- templateSources.push({ source: mobileSource, base: mobileSource });
341
+ if (await fs.pathExists(fullstackSource))
342
+ templateSources.push({ source: fullstackSource, base: fullstackSource });
343
+ templateSources.push({
344
+ source: path.join(ROOT_DIR, 'templates', 'backend'),
345
+ base: path.join(ROOT_DIR, 'templates', 'backend'),
346
+ });
347
+ templateSources.push({
348
+ source: path.join(ROOT_DIR, 'templates', 'frontend'),
349
+ base: path.join(ROOT_DIR, 'templates', 'frontend'),
350
+ });
294
351
  }
295
- // Walk all source directories and collect template files
296
- // For fullstack, use a Map to track processed files (priority: root > fullstack > backend > frontend)
352
+ else if (projectType === 'mobile')
353
+ templateSources.push({
354
+ source: path.join(ROOT_DIR, 'templates', 'mobile'),
355
+ base: path.join(ROOT_DIR, 'templates', 'mobile'),
356
+ });
297
357
  for (const { source, base } of templateSources) {
298
- const files = await walk(source);
299
- for (const file of files) {
358
+ for (const file of await walk(source)) {
300
359
  const relPath = path.relative(base, file);
301
- // Only add if not already processed (first occurrence wins)
302
- if (!processedFiles.has(relPath)) {
360
+ if (!processedFiles.has(relPath))
303
361
  processedFiles.set(relPath, { file, base });
304
- }
305
362
  }
306
363
  }
307
- // Copy each template WITHOUT rendering to .ai-flow/templates/
308
364
  for (const [relPath, { file: templateFile }] of processedFiles) {
309
- // Skip AI tool-specific config files if the tool is not selected
310
365
  const fileName = path.basename(relPath);
311
366
  if (fileName === '.clauderules.template' &&
312
367
  !aiTools.includes('claude') &&
313
- !aiTools.includes('all')) {
314
- logVerbose(`Skipping ${relPath} (Claude not selected)`, verbose);
368
+ !aiTools.includes('all'))
315
369
  continue;
316
- }
317
370
  if (fileName === '.cursorrules.template' &&
318
371
  !aiTools.includes('cursor') &&
319
- !aiTools.includes('all')) {
320
- logVerbose(`Skipping ${relPath} (Cursor not selected)`, verbose);
372
+ !aiTools.includes('all'))
321
373
  continue;
322
- }
323
- // Preserve original file structure with .template extension
324
374
  const destPath = path.join(destTemplatesPath, relPath);
325
375
  await fs.ensureDir(path.dirname(destPath));
326
376
  await fs.copy(templateFile, destPath);
327
- logVerbose(`Copied ${relPath}`, verbose);
328
377
  }
329
378
  spinner.succeed('Templates copied');
330
379
  }
@@ -344,7 +393,6 @@ async function copyPrompts(targetPath, dryRun, verbose) {
344
393
  }
345
394
  await assertDirWritable(promptsTarget);
346
395
  await fs.copy(promptsSource, promptsTarget);
347
- logVerbose(`Copied prompts to ${promptsTarget}`, verbose);
348
396
  spinner.succeed('Master prompts copied');
349
397
  }
350
398
  catch (error) {
@@ -355,16 +403,12 @@ async function copyPrompts(targetPath, dryRun, verbose) {
355
403
  async function setupSlashCommands(targetPath, aiTools, projectType = 'backend', dryRun, verbose) {
356
404
  const spinner = ora('Setting up slash commands...').start();
357
405
  try {
358
- // Determine which prompt directories to copy from
359
406
  const promptSources = [];
360
- if (projectType === 'backend') {
407
+ if (projectType === 'backend')
361
408
  promptSources.push({ dir: 'backend' });
362
- }
363
- else if (projectType === 'frontend') {
409
+ else if (projectType === 'frontend')
364
410
  promptSources.push({ dir: 'frontend' });
365
- }
366
411
  else if (projectType === 'fullstack') {
367
- // For fullstack, copy both with prefixes
368
412
  promptSources.push({ dir: 'backend', prefix: 'backend-' });
369
413
  promptSources.push({ dir: 'frontend', prefix: 'frontend-' });
370
414
  }
@@ -374,7 +418,6 @@ async function setupSlashCommands(targetPath, aiTools, projectType = 'backend',
374
418
  for (const { dir, prefix } of promptSources) {
375
419
  const promptsSource = path.join(ROOT_DIR, 'prompts', dir);
376
420
  const allFiles = await fs.readdir(promptsSource);
377
- // Filter markdown files, excluding internal logic files (they are loaded by main commands)
378
421
  const files = allFiles.filter((file) => {
379
422
  const isMarkdown = file.endsWith('.md');
380
423
  const isInternalPhase = file.match(/^flow-build-phase-\d+.*\.md$/);
@@ -383,82 +426,31 @@ async function setupSlashCommands(targetPath, aiTools, projectType = 'backend',
383
426
  return isMarkdown && !isInternalPhase && !isInternalWork && !isInternalCheck;
384
427
  });
385
428
  for (const tool of aiTools) {
429
+ let commandsTarget = '';
430
+ let extension = '.md';
386
431
  if (tool === 'copilot') {
387
- // Copilot: prompts in .github/prompts with .prompt.md suffix
388
- const promptsTarget = path.join(targetPath, '.github', 'prompts');
389
- if (!dryRun) {
390
- await assertDirWritable(promptsTarget);
391
- await fs.ensureDir(promptsTarget);
392
- }
393
- for (const file of files) {
394
- const srcFile = path.join(promptsSource, file);
395
- const base = file.replace(/\.md$/, '');
396
- const destName = prefix ? `${prefix}${base}.prompt.md` : `${base}.prompt.md`;
397
- const destFile = path.join(promptsTarget, destName);
398
- if (!dryRun)
399
- await fs.copyFile(srcFile, destFile);
400
- logVerbose(`Installed ${destFile}`, verbose);
401
- }
402
- }
403
- else if (tool === 'claude') {
404
- const commandsTarget = path.join(targetPath, '.claude', 'commands');
405
- if (!dryRun) {
406
- await assertDirWritable(commandsTarget);
407
- await fs.ensureDir(commandsTarget);
408
- }
409
- for (const file of files) {
410
- const srcFile = path.join(promptsSource, file);
411
- const destName = prefix ? `${prefix}${file}` : file;
412
- const destFile = path.join(commandsTarget, destName);
413
- if (!dryRun)
414
- await fs.copyFile(srcFile, destFile);
415
- logVerbose(`Installed ${destFile}`, verbose);
416
- }
417
- }
418
- else if (tool === 'cursor') {
419
- const commandsTarget = path.join(targetPath, '.cursor', 'commands');
420
- if (!dryRun) {
421
- await assertDirWritable(commandsTarget);
422
- await fs.ensureDir(commandsTarget);
423
- }
424
- for (const file of files) {
425
- const srcFile = path.join(promptsSource, file);
426
- const destName = prefix ? `${prefix}${file}` : file;
427
- const destFile = path.join(commandsTarget, destName);
428
- if (!dryRun)
429
- await fs.copyFile(srcFile, destFile);
430
- logVerbose(`Installed ${destFile}`, verbose);
431
- }
432
+ commandsTarget = path.join(targetPath, '.github', 'prompts');
433
+ extension = '.prompt.md';
432
434
  }
433
- else if (tool === 'gemini') {
434
- const commandsTarget = path.join(targetPath, '.gemini', 'commands');
435
- if (!dryRun) {
436
- await assertDirWritable(commandsTarget);
437
- await fs.ensureDir(commandsTarget);
438
- }
439
- for (const file of files) {
440
- const srcFile = path.join(promptsSource, file);
441
- const destName = prefix ? `${prefix}${file}` : file;
442
- const destFile = path.join(commandsTarget, destName);
443
- if (!dryRun)
444
- await fs.copyFile(srcFile, destFile);
445
- logVerbose(`Installed ${destFile}`, verbose);
446
- }
435
+ else if (tool === 'claude')
436
+ commandsTarget = path.join(targetPath, '.claude', 'commands');
437
+ else if (tool === 'cursor')
438
+ commandsTarget = path.join(targetPath, '.cursor', 'commands');
439
+ else if (tool === 'gemini')
440
+ commandsTarget = path.join(targetPath, '.gemini', 'commands');
441
+ else if (tool === 'antigravity')
442
+ commandsTarget = path.join(targetPath, '.agent', 'workflows');
443
+ if (!dryRun && commandsTarget) {
444
+ await assertDirWritable(commandsTarget);
445
+ await fs.ensureDir(commandsTarget);
447
446
  }
448
- else if (tool === 'antigravity') {
449
- const workflowsTarget = path.join(targetPath, '.agent', 'workflows');
450
- if (!dryRun) {
451
- await assertDirWritable(workflowsTarget);
452
- await fs.ensureDir(workflowsTarget);
453
- }
454
- for (const file of files) {
455
- const srcFile = path.join(promptsSource, file);
456
- const destName = prefix ? `${prefix}${file}` : file;
457
- const destFile = path.join(workflowsTarget, destName);
458
- if (!dryRun)
459
- await fs.copyFile(srcFile, destFile);
460
- logVerbose(`Installed ${destFile}`, verbose);
461
- }
447
+ for (const file of files) {
448
+ const srcFile = path.join(promptsSource, file);
449
+ const base = file.replace(/\.md$/, '');
450
+ const destName = prefix ? `${prefix}${base}${extension}` : `${base}${extension}`;
451
+ const destFile = path.join(commandsTarget, destName);
452
+ if (!dryRun)
453
+ await fs.copyFile(srcFile, destFile);
462
454
  }
463
455
  }
464
456
  }
@@ -471,41 +463,27 @@ async function setupSlashCommands(targetPath, aiTools, projectType = 'backend',
471
463
  }
472
464
  async function initializeProject(targetPath, aiTool, projectType, projectName, projectDescription, flags) {
473
465
  try {
474
- // Check if already initialized
475
- const isInitialized = await checkIfInitialized(targetPath);
476
- if (isInitialized) {
466
+ if (await checkIfInitialized(targetPath)) {
477
467
  console.log(chalk.yellow('\n⚠️ Project already initialized with AI Flow'));
478
468
  let reinitialize = false;
479
- if (process.stdin.isTTY) {
480
- reinitialize = await confirm({
481
- message: 'Do you want to reinitialize?',
482
- default: false,
483
- });
484
- }
469
+ if (process.stdin.isTTY)
470
+ reinitialize = await confirm({ message: 'Do you want to reinitialize?', default: false });
485
471
  if (!reinitialize) {
486
472
  console.log(chalk.blue('Initialization cancelled'));
487
473
  return;
488
474
  }
489
475
  }
490
- // Select AI tools
491
476
  const aiTools = await selectAITool(aiTool);
492
- // Select project type (v1.2.0: backend or frontend)
493
477
  const selectedProjectType = await selectProjectType(projectType);
494
- // Infer project name from directory
495
478
  const inferredName = path
496
479
  .basename(targetPath)
497
480
  .split('-')
498
481
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
499
482
  .join(' ');
500
- // Request minimal project data only if not provided
501
483
  if (projectName && !isValidName(projectName)) {
502
484
  console.error(chalk.red('Invalid project name'));
503
485
  process.exit(EXIT.INVALID_ARGS);
504
486
  }
505
- if (projectDescription && !isValidDescription(projectDescription)) {
506
- console.error(chalk.red('Invalid project description'));
507
- process.exit(EXIT.INVALID_ARGS);
508
- }
509
487
  let finalProjectName = projectName;
510
488
  if (!finalProjectName) {
511
489
  if (process.stdin.isTTY) {
@@ -515,131 +493,23 @@ async function initializeProject(targetPath, aiTool, projectType, projectName, p
515
493
  validate: (input) => isValidName(input) || 'Enter 2-100 chars: letters, numbers, space, - _ .',
516
494
  });
517
495
  }
518
- else {
496
+ else
519
497
  finalProjectName = inferredName;
520
- }
521
498
  }
522
499
  console.log(chalk.cyan('\n📦 Initializing AI Flow...\n'));
523
- // Create structure
524
500
  await createBootstrapStructure(targetPath, aiTools, selectedProjectType, flags?.dryRun, flags?.verbose);
525
501
  await copyTemplates(targetPath, selectedProjectType, aiTools, flags?.dryRun, flags?.verbose);
526
502
  await copyPrompts(targetPath, flags?.dryRun, flags?.verbose);
527
503
  await setupSlashCommands(targetPath, aiTools, selectedProjectType, flags?.dryRun, flags?.verbose);
528
- const modeText = flags?.dryRun ? 'DRY-RUN' : 'WRITE';
529
504
  console.log(chalk.green('\n✅ AI Flow initialized successfully!'));
530
505
  console.log(chalk.white('\nSummary:'));
531
- console.log(chalk.gray(` Project: ${finalProjectName}`));
532
- console.log(chalk.gray(` Version: ${PKG_VERSION}`));
506
+ console.log(chalk.gray(` Project: ${finalProjectName}`));
507
+ console.log(chalk.gray(` Version: ${PKG_VERSION}`));
533
508
  console.log(chalk.gray(` Directory: ${targetPath}`));
534
- console.log(chalk.gray(` Tools: ${aiTools.join(', ')}`));
535
- console.log(chalk.gray(` Mode: ${modeText}`));
536
- console.log(chalk.white('\nNext steps:\n'));
537
- const toolsText = aiTools.length === 1
538
- ? aiTools[0]
539
- : `${aiTools.slice(0, -1).join(', ')} and ${aiTools[aiTools.length - 1]}`;
540
- if (selectedProjectType === 'fullstack') {
541
- if (aiTools.includes('claude')) {
542
- console.log(chalk.cyan(' 1. Open Claude Code'));
543
- console.log(chalk.cyan(' 2. Run: /backend-flow-build (for backend documentation)'));
544
- console.log(chalk.cyan(' 3. Run: /frontend-flow-build (for frontend documentation)'));
545
- console.log(chalk.gray(' Each will guide you through up to 11 phases (0-10)\n'));
546
- }
547
- else if (aiTools.includes('cursor')) {
548
- console.log(chalk.cyan(' 1. Open Cursor'));
549
- console.log(chalk.cyan(' 2. Run: /backend-flow-build (for backend documentation)'));
550
- console.log(chalk.cyan(' 3. Run: /frontend-flow-build (for frontend documentation)'));
551
- console.log(chalk.gray(' Each will guide you through up to 11 phases (0-10)\n'));
552
- }
553
- else {
554
- console.log(chalk.cyan(` 1. Open your AI tool (${toolsText})`));
555
- console.log(chalk.cyan(' 2. Run: /backend-flow-build (for backend documentation)'));
556
- console.log(chalk.cyan(' 3. Run: /frontend-flow-build (for frontend documentation)'));
557
- console.log(chalk.gray(' Each will guide you through up to 11 phases (0-10)\n'));
558
- }
559
- console.log(chalk.white('Available slash commands:'));
560
- console.log(chalk.gray(' Backend commands:'));
561
- console.log(chalk.gray(' /backend-flow-build - Flujo completo (9 fases en orden)'));
562
- console.log(chalk.gray(' /backend-flow-build fase N - Fase específica (0-9)'));
563
- console.log(chalk.gray(' /backend-flow-docs-sync - Update backend documentation\n'));
564
- console.log(chalk.gray(' Frontend commands:'));
565
- console.log(chalk.gray(' /frontend-flow-build - Flujo completo (8 fases en orden)'));
566
- console.log(chalk.gray(' /frontend-flow-build fase N - Fase específica (0-8)'));
567
- console.log(chalk.gray(' /frontend-flow-docs-sync - Update frontend documentation\n'));
568
- }
569
- else if (selectedProjectType === 'mobile') {
570
- if (aiTools.includes('claude')) {
571
- console.log(chalk.cyan(' 1. Open Claude Code'));
572
- console.log(chalk.cyan(' 2. Run: /flow-build'));
573
- console.log(chalk.gray(' This will start the 9-phase interactive setup\n'));
574
- }
575
- else if (aiTools.includes('cursor')) {
576
- console.log(chalk.cyan(' 1. Open Cursor'));
577
- console.log(chalk.cyan(' 2. Run: /flow-build'));
578
- console.log(chalk.gray(' This will start the 9-phase interactive setup\n'));
579
- }
580
- else {
581
- console.log(chalk.cyan(` 1. Open your AI tool (${toolsText})`));
582
- console.log(chalk.cyan(' 2. Run: /flow-build'));
583
- console.log(chalk.gray(' This will start the 9-phase interactive setup\n'));
584
- }
585
- console.log(chalk.white('Available slash commands:'));
586
- console.log(chalk.gray(' /flow-build - Flujo completo (8 fases en orden)'));
587
- console.log(chalk.gray(' /flow-build fase N - Fase específica (0-8: Discovery, Platform, Navigation, State, Permissions, Standards, Testing, Deployment)'));
588
- console.log(chalk.gray(' /flow-docs-sync - Update documentation when code changes\n'));
589
- }
590
- else {
591
- if (aiTools.includes('claude')) {
592
- console.log(chalk.cyan(' 1. Open Claude Code'));
593
- console.log(chalk.cyan(' 2. Run: /flow-build'));
594
- console.log(chalk.gray(' This will guide you through up to 11 phases (0-10)\n'));
595
- }
596
- else if (aiTools.includes('cursor')) {
597
- console.log(chalk.cyan(' 1. Open Cursor'));
598
- console.log(chalk.cyan(' 2. Run: /flow-build'));
599
- console.log(chalk.gray(' This will guide you through up to 11 phases (0-10)\n'));
600
- }
601
- else if (aiTools.includes('antigravity')) {
602
- console.log(chalk.cyan(' 1. Use Antigravity commands'));
603
- console.log(chalk.cyan(' 2. Run: /flow-build'));
604
- console.log(chalk.gray(' This will guide you through up to 11 phases (0-10)\n'));
605
- }
606
- else {
607
- console.log(chalk.cyan(` 1. Open your AI tool (${toolsText})`));
608
- console.log(chalk.cyan(' 2. Run: /flow-build'));
609
- console.log(chalk.gray(' This will guide you through up to 11 phases (0-10)\n'));
610
- }
611
- console.log(chalk.white('Available slash commands:'));
612
- console.log(chalk.gray(' /flow-build - Flujo completo (todas las fases en orden)'));
613
- console.log(chalk.gray(' /flow-build fase N - Fase específica (ver lista de fases abajo)'));
614
- console.log(chalk.gray(' /flow-docs-sync - Update documentation when code changes\n'));
615
- if (selectedProjectType === 'backend') {
616
- console.log(chalk.gray('\n Fases disponibles (Backend):'));
617
- console.log(chalk.gray(' fase 0: Context Discovery (proyectos existentes)'));
618
- console.log(chalk.gray(' fase 1: Discovery & Business'));
619
- console.log(chalk.gray(' fase 2: Data Architecture'));
620
- console.log(chalk.gray(' fase 3: System Architecture'));
621
- console.log(chalk.gray(' fase 4: Security & Auth'));
622
- console.log(chalk.gray(' fase 5: Code Standards'));
623
- console.log(chalk.gray(' fase 6: Testing'));
624
- console.log(chalk.gray(' fase 7: Operations + Tools'));
625
- console.log(chalk.gray(' fase 8: Project Setup & Final Docs'));
626
- console.log(chalk.gray(' fase 9: Implementation Roadmap (opcional)'));
627
- console.log(chalk.gray(' fase 10: User Stories Generation (opcional)'));
628
- }
629
- else {
630
- console.log(chalk.gray('\n Fases disponibles (Frontend):'));
631
- console.log(chalk.gray(' fase 0: Context Discovery (proyectos existentes)'));
632
- console.log(chalk.gray(' fase 1: Discovery & UX'));
633
- console.log(chalk.gray(' fase 2: Components & Framework'));
634
- console.log(chalk.gray(' fase 3: State Management'));
635
- console.log(chalk.gray(' fase 4: Styling & Design'));
636
- console.log(chalk.gray(' fase 5: Code Standards'));
637
- console.log(chalk.gray(' fase 6: Testing'));
638
- console.log(chalk.gray(' fase 7: Deployment'));
639
- console.log(chalk.gray(' fase 8: Project Setup & Final Docs'));
640
- }
641
- console.log(chalk.gray(' /flow-docs-sync - Update documentation when code changes\n'));
642
- }
509
+ console.log(chalk.gray(` Tools: ${aiTools.join(', ')}`));
510
+ console.log(chalk.gray(` Type: ${getProjectTypeLabel(selectedProjectType)} (${selectedProjectType})`));
511
+ console.log(chalk.gray(` Mode: ${flags?.dryRun ? 'DRY-RUN' : 'WRITE'}`));
512
+ printNextSteps({ aiTools, projectType: selectedProjectType });
643
513
  if (flags?.dryRun) {
644
514
  console.log(chalk.yellow('⚠️ Dry-run: no files were written. Run again without --dry-run to apply changes.\n'));
645
515
  }
@@ -653,36 +523,28 @@ async function initializeProject(targetPath, aiTool, projectType, projectName, p
653
523
  // CLI Commands
654
524
  program
655
525
  .name('ai-flow')
656
- .description('AI-powered development workflow from idea to production. Generate specs, plan features, and build with AI assistance.')
526
+ .description('AI-powered development workflow from idea to production.')
657
527
  .version(PKG_VERSION);
658
528
  program
659
529
  .command('init')
660
530
  .description('Initialize AI Flow in current directory')
661
531
  .argument('[path]', 'Target directory (defaults to current directory)', '.')
662
- .option('--ai <tool>', 'AI tool to use (claude, cursor, copilot, gemini, antigravity, all)')
663
- .option('--type <type>', 'Project type (backend, frontend, fullstack, mobile)')
664
- .option('--name <name>', 'Project name (skip interactive prompt)')
665
- .option('--description <desc>', 'Project description (skip interactive prompt)')
666
- .option('--verbose', 'Enable verbose logging')
532
+ .option('--ai <tool>', 'AI tool to use')
533
+ .option('--type <type>', 'Project type')
534
+ .option('--name <name>', 'Project name')
535
+ .option('--description <desc>', 'Project description')
667
536
  .option('--dry-run', 'Simulate without writing files')
668
537
  .action(async (targetPath, options) => {
669
- const absolutePath = path.resolve(targetPath);
670
- const flags = {
671
- dryRun: options.dryRun === true,
672
- verbose: options.verbose === true,
673
- };
674
- await initializeProject(absolutePath, options.ai, options.type, options.name, options.description, flags);
538
+ await initializeProject(path.resolve(targetPath), options.ai, options.type, options.name, options.description, { dryRun: !!options.dryRun });
675
539
  });
676
540
  program
677
541
  .command('check')
678
542
  .description('Check if current directory is initialized')
679
543
  .action(async () => {
680
- const isInitialized = await checkIfInitialized(process.cwd());
681
- if (isInitialized) {
544
+ if (await checkIfInitialized(process.cwd())) {
682
545
  console.log(chalk.green('✅ Project is initialized with AI Flow'));
683
546
  const configPath = path.join(process.cwd(), '.ai-flow', 'core', 'config.json');
684
547
  const config = await fs.readJSON(configPath);
685
- // Detect project type (support both old and new config format)
686
548
  const projectType = config.projectType ||
687
549
  (config.backend && !config.frontend
688
550
  ? 'backend'
@@ -691,70 +553,12 @@ program
691
553
  : config.mobile
692
554
  ? 'mobile'
693
555
  : 'backend');
694
- const projectTypeDisplay = projectType === 'backend'
695
- ? '🔧 Backend'
696
- : projectType === 'frontend'
697
- ? '🎨 Frontend'
698
- : projectType === 'fullstack'
699
- ? '🚀 Full Stack'
700
- : projectType === 'mobile'
701
- ? '📱 Mobile'
702
- : '🔧 Backend';
703
556
  console.log(chalk.white('\nConfiguration:'));
704
557
  console.log(chalk.gray(` Version: ${config.version}`));
705
- console.log(chalk.gray(` Project Type: ${projectTypeDisplay}`));
558
+ console.log(chalk.gray(` Project Type: ${getProjectTypeLabel(projectType)} (${projectType})`));
706
559
  console.log(chalk.gray(` AI Tools: ${config.aiTools.join(', ')}`));
707
560
  console.log(chalk.gray(` Created: ${new Date(config.createdAt).toLocaleString()}`));
708
- console.log(chalk.gray(` Working Dir: ${process.cwd()}`));
709
- // Show correct prompts path based on project type
710
- if (projectType === 'fullstack') {
711
- const backendPromptsPath = path.join(process.cwd(), '.ai-flow', 'prompts', 'backend', 'flow-build.md');
712
- const frontendPromptsPath = path.join(process.cwd(), '.ai-flow', 'prompts', 'frontend', 'flow-build.md');
713
- console.log(chalk.gray(` Backend Prompts: ${backendPromptsPath}`));
714
- console.log(chalk.gray(` Frontend Prompts: ${frontendPromptsPath}`));
715
- }
716
- else {
717
- const promptsPath = path.join(process.cwd(), '.ai-flow', 'prompts', projectType, 'flow-build.md');
718
- console.log(chalk.gray(` Prompts: ${promptsPath}`));
719
- }
720
- console.log(chalk.white('\nNext steps:'));
721
- if (projectType === 'fullstack') {
722
- if (config.aiTools.includes('claude')) {
723
- console.log(chalk.cyan(' 1. Open Claude Code'));
724
- console.log(chalk.cyan(' 2. Run: /backend-flow-build (for backend documentation)'));
725
- console.log(chalk.cyan(' 3. Run: /frontend-flow-build (for frontend documentation)'));
726
- }
727
- else if (config.aiTools.includes('cursor')) {
728
- console.log(chalk.cyan(' 1. Open Cursor'));
729
- console.log(chalk.cyan(' 2. Run: /backend-flow-build (for backend documentation)'));
730
- console.log(chalk.cyan(' 3. Run: /frontend-flow-build (for frontend documentation)'));
731
- }
732
- else {
733
- const toolsText = config.aiTools.length === 1
734
- ? config.aiTools[0]
735
- : `${config.aiTools.slice(0, -1).join(', ')} and ${config.aiTools[config.aiTools.length - 1]}`;
736
- console.log(chalk.cyan(` 1. Open your AI tool (${toolsText})`));
737
- console.log(chalk.cyan(' 2. Run: /backend-flow-build (for backend documentation)'));
738
- console.log(chalk.cyan(' 3. Run: /frontend-flow-build (for frontend documentation)'));
739
- }
740
- }
741
- else {
742
- if (config.aiTools.includes('claude')) {
743
- console.log(chalk.cyan(' 1. Open Claude Code'));
744
- console.log(chalk.cyan(' 2. Run: /flow-build'));
745
- }
746
- else if (config.aiTools.includes('cursor')) {
747
- console.log(chalk.cyan(' 1. Open Cursor'));
748
- console.log(chalk.cyan(' 2. Run: /flow-build'));
749
- }
750
- else {
751
- const toolsText = config.aiTools.length === 1
752
- ? config.aiTools[0]
753
- : `${config.aiTools.slice(0, -1).join(', ')} and ${config.aiTools[config.aiTools.length - 1]}`;
754
- console.log(chalk.cyan(` 1. Open your AI tool (${toolsText})`));
755
- console.log(chalk.cyan(' 2. Run: /flow-build'));
756
- }
757
- }
561
+ printNextSteps({ aiTools: config.aiTools, projectType });
758
562
  }
759
563
  else {
760
564
  console.log(chalk.yellow('⚠️ Project is not initialized'));