vibe-fabric 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +6 -0
  2. package/dist/cli/commands/init.d.ts +59 -0
  3. package/dist/cli/commands/init.d.ts.map +1 -1
  4. package/dist/cli/commands/init.js +659 -3
  5. package/dist/cli/commands/init.js.map +1 -1
  6. package/dist/cli/commands/repo/create.d.ts +35 -0
  7. package/dist/cli/commands/repo/create.d.ts.map +1 -0
  8. package/dist/cli/commands/repo/create.js +333 -0
  9. package/dist/cli/commands/repo/create.js.map +1 -0
  10. package/dist/cli/commands/status.d.ts.map +1 -1
  11. package/dist/cli/commands/status.js +31 -12
  12. package/dist/cli/commands/status.js.map +1 -1
  13. package/dist/cli/index.js +2 -0
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/core/planning.d.ts +72 -0
  16. package/dist/core/planning.d.ts.map +1 -0
  17. package/dist/core/planning.js +610 -0
  18. package/dist/core/planning.js.map +1 -0
  19. package/dist/core/repo/framework.d.ts +4 -0
  20. package/dist/core/repo/framework.d.ts.map +1 -1
  21. package/dist/core/repo/framework.js +6 -0
  22. package/dist/core/repo/framework.js.map +1 -1
  23. package/dist/core/state.d.ts +71 -0
  24. package/dist/core/state.d.ts.map +1 -0
  25. package/dist/core/state.js +218 -0
  26. package/dist/core/state.js.map +1 -0
  27. package/dist/core/status.d.ts.map +1 -1
  28. package/dist/core/status.js +17 -11
  29. package/dist/core/status.js.map +1 -1
  30. package/dist/types/config.d.ts +122 -10
  31. package/dist/types/config.d.ts.map +1 -1
  32. package/dist/types/config.js +26 -2
  33. package/dist/types/config.js.map +1 -1
  34. package/dist/types/planning.d.ts +286 -0
  35. package/dist/types/planning.d.ts.map +1 -0
  36. package/dist/types/planning.js +49 -0
  37. package/dist/types/planning.js.map +1 -0
  38. package/dist/types/runner.d.ts +14 -14
  39. package/dist/types/state.d.ts +640 -0
  40. package/dist/types/state.d.ts.map +1 -0
  41. package/dist/types/state.js +59 -0
  42. package/dist/types/state.js.map +1 -0
  43. package/dist/types/status.d.ts +3 -0
  44. package/dist/types/status.d.ts.map +1 -1
  45. package/package.json +1 -1
@@ -7,7 +7,9 @@ import { existsSync } from 'fs';
7
7
  import { isValidProjectName, getProjectNameError } from '../../types/config.js';
8
8
  import { validateGitHubToken, discoverGitHubToken, isGhCliInstalled, isClaudeCodeInstalled, isUvInstalled, } from '../../core/github.js';
9
9
  import { createPlanningHub, isInsideVibeProject } from '../../core/project.js';
10
- import { isVibeProject } from '../../core/config.js';
10
+ import { isVibeProject, loadConfig, saveConfig } from '../../core/config.js';
11
+ import { getClaudeRecommendation, createPlannedRepo, addPlannedReposToConfig, formatTechStack, captureRequirementsWithClaude, saveDraftPrd, } from '../../core/planning.js';
12
+ import { loadSetupState, saveSetupState, clearSetupState, createSetupState, hasSetupState, setCurrentStep, markStepCompleted, setDescription, setRecommendation, setPlannedRepos, setRequirements, getStepDisplayName, } from '../../core/state.js';
11
13
  export class InitCommand extends Command {
12
14
  static paths = [['init']];
13
15
  static usage = Command.Usage({
@@ -40,11 +42,24 @@ export class InitCommand extends Command {
40
42
  projectPath = Option.String('--path,-p', {
41
43
  description: 'Create project at specified path',
42
44
  });
45
+ resume = Option.Boolean('--resume,-r', false, {
46
+ description: 'Resume interrupted setup',
47
+ });
48
+ noCapture = Option.Boolean('--no-capture', false, {
49
+ description: 'Skip requirement capture (new project)',
50
+ });
51
+ noPrd = Option.Boolean('--no-prd', false, {
52
+ description: 'Skip PRD generation',
53
+ });
43
54
  async execute() {
44
55
  this.context.stdout.write('\n');
45
56
  this.context.stdout.write(chalk.bold.blue(' VIBE-FABRIC') + chalk.gray(' - AI-Collaborative Development\n'));
46
57
  this.context.stdout.write(chalk.gray(' ─'.repeat(25)) + '\n\n');
47
58
  try {
59
+ // Handle resume mode
60
+ if (this.resume) {
61
+ return await this.handleResume();
62
+ }
48
63
  // Step 1: Check prerequisites
49
64
  await this.checkPrerequisites();
50
65
  // Step 2: Get project name
@@ -61,8 +76,15 @@ export class InitCommand extends Command {
61
76
  const token = await this.setupGitHubAuth();
62
77
  // Step 7: Create planning hub
63
78
  await this.createHub(finalPath, name, projectType, token);
64
- // Step 8: Show success message
65
- this.showSuccess(finalPath, name);
79
+ // Step 8: For new projects, run the planning workflow
80
+ if (projectType === 'new') {
81
+ const { plannedRepos, requirements } = await this.runNewProjectWorkflow(finalPath, name);
82
+ this.showNewProjectSuccess(finalPath, name, plannedRepos, requirements);
83
+ }
84
+ else {
85
+ // Step 9: Show success message for existing projects
86
+ this.showSuccess(finalPath, name);
87
+ }
66
88
  return 0;
67
89
  }
68
90
  catch (error) {
@@ -74,6 +96,151 @@ export class InitCommand extends Command {
74
96
  return 1;
75
97
  }
76
98
  }
99
+ /**
100
+ * Handle resume mode
101
+ */
102
+ async handleResume() {
103
+ // Look for state file in current directory or specified path
104
+ const searchPath = this.projectPath
105
+ ? path.resolve(this.projectPath)
106
+ : process.cwd();
107
+ // Check if there's a state file here
108
+ if (!hasSetupState(searchPath)) {
109
+ this.context.stderr.write(chalk.red(' No interrupted setup found.\n'));
110
+ this.context.stdout.write(chalk.gray(' Run `vibe init` to start a new setup.\n\n'));
111
+ return 1;
112
+ }
113
+ const state = await loadSetupState(searchPath);
114
+ if (!state) {
115
+ this.context.stderr.write(chalk.red(' Setup state file is corrupted.\n'));
116
+ this.context.stdout.write(chalk.gray(' Run `vibe init` to start a new setup.\n\n'));
117
+ return 1;
118
+ }
119
+ this.context.stdout.write(chalk.bold.cyan(' RESUMING SETUP\n'));
120
+ this.context.stdout.write(chalk.gray(' ─'.repeat(25)) + '\n\n');
121
+ this.context.stdout.write(` Project: ${chalk.cyan(state.projectName)}\n`);
122
+ this.context.stdout.write(` Type: ${chalk.cyan(state.projectType)}\n`);
123
+ this.context.stdout.write(` Last step: ${chalk.yellow(getStepDisplayName(state.currentStep))}\n\n`);
124
+ const resumeConfirm = await confirm({
125
+ message: 'Continue from where you left off?',
126
+ default: true,
127
+ });
128
+ if (!resumeConfirm) {
129
+ const restart = await confirm({
130
+ message: 'Start over with a fresh setup?',
131
+ default: false,
132
+ });
133
+ if (restart) {
134
+ await clearSetupState(searchPath);
135
+ this.context.stdout.write(chalk.gray('\n State cleared. Run `vibe init` to start fresh.\n\n'));
136
+ }
137
+ return 1;
138
+ }
139
+ return await this.continueFromState(state);
140
+ }
141
+ /**
142
+ * Continue setup from saved state
143
+ */
144
+ async continueFromState(state) {
145
+ try {
146
+ switch (state.currentStep) {
147
+ case 'requirement-capture':
148
+ return await this.continueFromRequirementCapture(state);
149
+ case 'prd-generation':
150
+ return await this.continueFromPrdGeneration(state);
151
+ case 'repo-planning':
152
+ // Re-run repo planning if we stopped there
153
+ if (state.data.recommendation && state.data.description) {
154
+ const confirmedRepos = await this.handleRecommendations(state.projectPath, state.data.description, state.data.recommendation);
155
+ if (confirmedRepos.length > 0) {
156
+ const spinner = ora('Creating planned repositories...').start();
157
+ for (const repo of confirmedRepos) {
158
+ await createPlannedRepo(state.projectPath, repo);
159
+ }
160
+ await addPlannedReposToConfig(state.projectPath, confirmedRepos);
161
+ spinner.succeed(`Created ${confirmedRepos.length} planned repositories`);
162
+ // Update state
163
+ let updatedState = setPlannedRepos(state, confirmedRepos);
164
+ updatedState = markStepCompleted(updatedState, 'repo-planning');
165
+ updatedState = setCurrentStep(updatedState, 'requirement-capture');
166
+ await saveSetupState(updatedState);
167
+ // Continue to requirement capture
168
+ return await this.continueFromRequirementCapture(updatedState);
169
+ }
170
+ }
171
+ // Fallback to showing success
172
+ await clearSetupState(state.projectPath);
173
+ this.showNewProjectSuccess(state.projectPath, state.projectName, [], undefined);
174
+ return 0;
175
+ default:
176
+ // For other steps, just show success - hub is created
177
+ await clearSetupState(state.projectPath);
178
+ if (state.projectType === 'new') {
179
+ this.showNewProjectSuccess(state.projectPath, state.projectName, state.data.plannedRepos || [], state.data.requirements);
180
+ }
181
+ else {
182
+ this.showSuccess(state.projectPath, state.projectName);
183
+ }
184
+ return 0;
185
+ }
186
+ }
187
+ catch (error) {
188
+ if (error instanceof Error && error.message === 'USER_CANCELLED') {
189
+ this.context.stdout.write(chalk.yellow('\nSetup cancelled. State saved for resume.\n'));
190
+ return 1;
191
+ }
192
+ throw error;
193
+ }
194
+ }
195
+ /**
196
+ * Continue from requirement capture step
197
+ */
198
+ async continueFromRequirementCapture(state) {
199
+ if (!state.data.description || !state.data.plannedRepos) {
200
+ await clearSetupState(state.projectPath);
201
+ this.showNewProjectSuccess(state.projectPath, state.projectName, [], undefined);
202
+ return 0;
203
+ }
204
+ // Ask about requirement capture
205
+ const result = await this.runRequirementCaptureStep(state.projectPath, state.projectName, state.data.description, state.data.plannedRepos);
206
+ if (result.requirements) {
207
+ let updatedState = setRequirements(state, result.requirements);
208
+ updatedState = markStepCompleted(updatedState, 'requirement-capture');
209
+ updatedState = setCurrentStep(updatedState, 'prd-generation');
210
+ await saveSetupState(updatedState);
211
+ return await this.continueFromPrdGeneration(updatedState);
212
+ }
213
+ // Skipped or no requirements - complete
214
+ await clearSetupState(state.projectPath);
215
+ this.showNewProjectSuccess(state.projectPath, state.projectName, state.data.plannedRepos, undefined);
216
+ return 0;
217
+ }
218
+ /**
219
+ * Continue from PRD generation step
220
+ */
221
+ async continueFromPrdGeneration(state) {
222
+ if (!state.data.requirements || !state.data.description) {
223
+ await clearSetupState(state.projectPath);
224
+ this.showNewProjectSuccess(state.projectPath, state.projectName, state.data.plannedRepos || [], undefined);
225
+ return 0;
226
+ }
227
+ // Generate and save PRD
228
+ if (!this.noPrd) {
229
+ const spinner = ora('Generating draft PRD...').start();
230
+ try {
231
+ const prdPath = await saveDraftPrd(state.projectPath, state.projectName, state.data.description, state.data.requirements);
232
+ spinner.succeed(`Draft PRD saved to ${chalk.cyan(prdPath)}`);
233
+ }
234
+ catch (error) {
235
+ spinner.fail('Failed to generate PRD');
236
+ this.context.stderr.write(chalk.yellow(` Warning: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
237
+ }
238
+ }
239
+ // Complete
240
+ await clearSetupState(state.projectPath);
241
+ this.showNewProjectSuccess(state.projectPath, state.projectName, state.data.plannedRepos || [], state.data.requirements);
242
+ return 0;
243
+ }
77
244
  /**
78
245
  * Check prerequisites (Claude Code, UV)
79
246
  */
@@ -242,5 +409,494 @@ export class InitCommand extends Command {
242
409
  this.context.stdout.write(chalk.gray(' 4. vibe scope # Create work scopes\n\n'));
243
410
  this.context.stdout.write(chalk.gray(' Run ') + chalk.cyan('vibe --help') + chalk.gray(' for all commands.\n\n'));
244
411
  }
412
+ /**
413
+ * Run new project workflow: capture description, get recommendations, plan repos, capture requirements
414
+ */
415
+ async runNewProjectWorkflow(projectPath, projectName) {
416
+ this.context.stdout.write('\n');
417
+ this.context.stdout.write(chalk.bold.cyan(' NEW PROJECT PLANNING\n'));
418
+ this.context.stdout.write(chalk.gray(' ─'.repeat(25)) + '\n\n');
419
+ // Initialize state for resume functionality
420
+ let state = createSetupState(projectName, projectPath, 'new');
421
+ state = setCurrentStep(state, 'description-capture');
422
+ await saveSetupState(state);
423
+ // Step 1: Capture project description
424
+ const description = await this.captureProjectDescription();
425
+ state = setDescription(state, description);
426
+ state = markStepCompleted(state, 'description-capture');
427
+ state = setCurrentStep(state, 'claude-recommendation');
428
+ await saveSetupState(state);
429
+ // Save description to config
430
+ const config = await loadConfig(projectPath);
431
+ if (config) {
432
+ config.project.description = description;
433
+ await saveConfig(projectPath, config);
434
+ }
435
+ // Step 2: Get Claude recommendations
436
+ let recommendation = null;
437
+ let attemptCount = 0;
438
+ const maxAttempts = 3;
439
+ while (attemptCount < maxAttempts) {
440
+ const spinner = ora('Claude is analyzing your project and recommending structure...').start();
441
+ try {
442
+ recommendation = await getClaudeRecommendation(projectPath, description);
443
+ spinner.succeed('Received recommendations from Claude');
444
+ if (recommendation) {
445
+ state = setRecommendation(state, recommendation);
446
+ }
447
+ state = markStepCompleted(state, 'claude-recommendation');
448
+ state = setCurrentStep(state, 'repo-planning');
449
+ await saveSetupState(state);
450
+ break;
451
+ }
452
+ catch (error) {
453
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
454
+ spinner.fail(`Failed to get recommendations: ${errorMessage}`);
455
+ attemptCount++;
456
+ if (attemptCount < maxAttempts) {
457
+ const retry = await confirm({
458
+ message: 'Would you like to retry?',
459
+ default: true,
460
+ });
461
+ if (!retry) {
462
+ throw new Error('USER_CANCELLED');
463
+ }
464
+ }
465
+ else {
466
+ this.context.stderr.write(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
467
+ throw new Error('Failed to get Claude recommendations after multiple attempts');
468
+ }
469
+ }
470
+ }
471
+ if (!recommendation || recommendation.repos.length === 0) {
472
+ this.context.stdout.write(chalk.yellow('\n No repositories recommended. You can add repos manually later.\n'));
473
+ await clearSetupState(projectPath);
474
+ return { plannedRepos: [] };
475
+ }
476
+ // Step 3: Display and confirm recommendations
477
+ const confirmedRepos = await this.handleRecommendations(projectPath, description, recommendation);
478
+ if (confirmedRepos.length === 0) {
479
+ this.context.stdout.write(chalk.yellow('\n No repositories planned. You can add repos manually later.\n'));
480
+ await clearSetupState(projectPath);
481
+ return { plannedRepos: [] };
482
+ }
483
+ // Step 4: Create planned repos
484
+ const spinner = ora('Creating planned repositories...').start();
485
+ try {
486
+ for (const repo of confirmedRepos) {
487
+ await createPlannedRepo(projectPath, repo);
488
+ }
489
+ await addPlannedReposToConfig(projectPath, confirmedRepos);
490
+ spinner.succeed(`Created ${confirmedRepos.length} planned repositories`);
491
+ state = setPlannedRepos(state, confirmedRepos);
492
+ state = markStepCompleted(state, 'repo-planning');
493
+ state = setCurrentStep(state, 'requirement-capture');
494
+ await saveSetupState(state);
495
+ }
496
+ catch (error) {
497
+ spinner.fail('Failed to create planned repositories');
498
+ throw error;
499
+ }
500
+ // Step 5: Requirement capture (optional)
501
+ const result = await this.runRequirementCaptureStep(projectPath, projectName, description, confirmedRepos);
502
+ if (result.requirements) {
503
+ state = setRequirements(state, result.requirements);
504
+ state = markStepCompleted(state, 'requirement-capture');
505
+ state = setCurrentStep(state, 'prd-generation');
506
+ await saveSetupState(state);
507
+ // Step 6: PRD generation (optional)
508
+ if (!this.noPrd) {
509
+ const prdSpinner = ora('Generating draft PRD...').start();
510
+ try {
511
+ const prdPath = await saveDraftPrd(projectPath, projectName, description, result.requirements);
512
+ prdSpinner.succeed(`Draft PRD saved to ${chalk.cyan(prdPath)}`);
513
+ state = markStepCompleted(state, 'prd-generation');
514
+ }
515
+ catch (error) {
516
+ prdSpinner.fail('Failed to generate PRD');
517
+ this.context.stderr.write(chalk.yellow(` Warning: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
518
+ }
519
+ }
520
+ }
521
+ // Cleanup state on successful completion
522
+ await clearSetupState(projectPath);
523
+ return { plannedRepos: confirmedRepos, requirements: result.requirements };
524
+ }
525
+ /**
526
+ * Run the requirement capture step
527
+ */
528
+ async runRequirementCaptureStep(_projectPath, _projectName, description, repos) {
529
+ // Skip if --no-capture flag is set
530
+ if (this.noCapture) {
531
+ this.context.stdout.write(chalk.gray('\n Skipping requirement capture (--no-capture).\n'));
532
+ return {};
533
+ }
534
+ this.context.stdout.write('\n');
535
+ this.context.stdout.write(chalk.bold.green(' ┌─────────────────────────────────────────────────────────┐\n'));
536
+ this.context.stdout.write(chalk.bold.green(' │') + chalk.bold(' Requirement Capture (Optional)') + chalk.bold.green(' │\n'));
537
+ this.context.stdout.write(chalk.bold.green(' └─────────────────────────────────────────────────────────┘\n'));
538
+ this.context.stdout.write('\n');
539
+ this.context.stdout.write(chalk.gray(' Claude will help you:\n'));
540
+ this.context.stdout.write(chalk.gray(' - Define user stories\n'));
541
+ this.context.stdout.write(chalk.gray(' - Identify key features\n'));
542
+ this.context.stdout.write(chalk.gray(' - Establish technical requirements\n\n'));
543
+ const captureRequirements = await confirm({
544
+ message: 'Would you like to capture initial requirements?',
545
+ default: true,
546
+ });
547
+ if (!captureRequirements) {
548
+ this.context.stdout.write(chalk.gray('\n Skipping requirement capture. You can use `vibe prd` later.\n'));
549
+ return {};
550
+ }
551
+ // Capture requirements with Claude
552
+ let requirements = null;
553
+ let attemptCount = 0;
554
+ const maxAttempts = 3;
555
+ while (attemptCount < maxAttempts) {
556
+ const spinner = ora('Claude is capturing requirements...').start();
557
+ try {
558
+ requirements = await captureRequirementsWithClaude(description, repos);
559
+ spinner.succeed('Requirements captured successfully');
560
+ // Display captured requirements summary
561
+ if (requirements) {
562
+ this.displayRequirementsSummary(requirements);
563
+ }
564
+ break;
565
+ }
566
+ catch (error) {
567
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
568
+ spinner.fail(`Failed to capture requirements: ${errorMessage}`);
569
+ attemptCount++;
570
+ if (attemptCount < maxAttempts) {
571
+ const retry = await confirm({
572
+ message: 'Would you like to retry?',
573
+ default: true,
574
+ });
575
+ if (!retry) {
576
+ this.context.stdout.write(chalk.gray('\n Skipping requirements. You can use `vibe prd` later.\n'));
577
+ return {};
578
+ }
579
+ }
580
+ else {
581
+ this.context.stdout.write(chalk.yellow('\n Could not capture requirements. You can use `vibe prd` later.\n'));
582
+ return {};
583
+ }
584
+ }
585
+ }
586
+ return { requirements: requirements || undefined };
587
+ }
588
+ /**
589
+ * Display a summary of captured requirements
590
+ */
591
+ displayRequirementsSummary(requirements) {
592
+ this.context.stdout.write('\n');
593
+ this.context.stdout.write(chalk.bold(' Captured Requirements Summary:\n\n'));
594
+ // User stories
595
+ this.context.stdout.write(chalk.cyan(` User Stories: ${requirements.userStories.length}\n`));
596
+ for (const story of requirements.userStories.slice(0, 3)) {
597
+ this.context.stdout.write(chalk.gray(` - ${story.id}: ${story.title}\n`));
598
+ }
599
+ if (requirements.userStories.length > 3) {
600
+ this.context.stdout.write(chalk.gray(` ... and ${requirements.userStories.length - 3} more\n`));
601
+ }
602
+ // Features
603
+ const mustHave = requirements.features.filter((f) => f.priority === 'must-have').length;
604
+ const shouldHave = requirements.features.filter((f) => f.priority === 'should-have').length;
605
+ const niceToHave = requirements.features.filter((f) => f.priority === 'nice-to-have').length;
606
+ this.context.stdout.write(chalk.cyan(`\n Features: ${requirements.features.length}\n`));
607
+ this.context.stdout.write(chalk.gray(` - Must-have: ${mustHave}\n`));
608
+ this.context.stdout.write(chalk.gray(` - Should-have: ${shouldHave}\n`));
609
+ this.context.stdout.write(chalk.gray(` - Nice-to-have: ${niceToHave}\n`));
610
+ // Technical requirements
611
+ this.context.stdout.write(chalk.cyan(`\n Technical Requirements: ${requirements.technicalRequirements.length}\n`));
612
+ // Non-functional requirements
613
+ this.context.stdout.write(chalk.cyan(` Non-Functional Requirements: ${requirements.nonFunctionalRequirements.length}\n`));
614
+ this.context.stdout.write('\n');
615
+ }
616
+ /**
617
+ * Capture project description from user
618
+ */
619
+ async captureProjectDescription() {
620
+ this.context.stdout.write(chalk.bold(' Describe your project\n'));
621
+ this.context.stdout.write(chalk.gray(' Tell me about what you\'re building so I can recommend a tech stack.\n\n'));
622
+ // Main description
623
+ const summary = await input({
624
+ message: 'What are you building? (1-2 sentences)',
625
+ validate: (value) => {
626
+ if (!value.trim())
627
+ return 'Description is required';
628
+ if (value.length < 10)
629
+ return 'Please provide more detail';
630
+ return true;
631
+ },
632
+ });
633
+ // Optional: goals
634
+ const wantsGoals = await confirm({
635
+ message: 'Would you like to add specific goals?',
636
+ default: false,
637
+ });
638
+ let goals;
639
+ if (wantsGoals) {
640
+ const goalsInput = await input({
641
+ message: 'List your goals (comma-separated):',
642
+ });
643
+ goals = goalsInput
644
+ .split(',')
645
+ .map((g) => g.trim())
646
+ .filter((g) => g.length > 0);
647
+ }
648
+ // Optional: constraints
649
+ const wantsConstraints = await confirm({
650
+ message: 'Any technical constraints or preferences?',
651
+ default: false,
652
+ });
653
+ let constraints;
654
+ if (wantsConstraints) {
655
+ const constraintsInput = await input({
656
+ message: 'List constraints (comma-separated, e.g., "must use PostgreSQL, needs SSO"):',
657
+ });
658
+ constraints = constraintsInput
659
+ .split(',')
660
+ .map((c) => c.trim())
661
+ .filter((c) => c.length > 0);
662
+ }
663
+ // Optional: target users
664
+ const targetUsers = await input({
665
+ message: 'Who are the target users? (optional, press Enter to skip):',
666
+ });
667
+ return {
668
+ summary,
669
+ goals,
670
+ constraints,
671
+ targetUsers: targetUsers || undefined,
672
+ };
673
+ }
674
+ /**
675
+ * Handle displaying and confirming recommendations
676
+ */
677
+ async handleRecommendations(projectPath, description, recommendation) {
678
+ // Display recommendations
679
+ this.displayRecommendations(recommendation);
680
+ // Get user choice
681
+ const choice = await this.getRecommendationChoice();
682
+ switch (choice) {
683
+ case 'confirm':
684
+ return recommendation.repos;
685
+ case 'modify':
686
+ return await this.modifyRecommendations(recommendation);
687
+ case 'restart':
688
+ // Recursively restart the recommendation process
689
+ const spinner = ora('Getting new recommendations from Claude...').start();
690
+ try {
691
+ const newRec = await getClaudeRecommendation(projectPath, description);
692
+ spinner.succeed('Received new recommendations');
693
+ if (newRec && newRec.repos.length > 0) {
694
+ return await this.handleRecommendations(projectPath, description, newRec);
695
+ }
696
+ return [];
697
+ }
698
+ catch (error) {
699
+ spinner.fail('Failed to get recommendations');
700
+ throw error;
701
+ }
702
+ }
703
+ }
704
+ /**
705
+ * Display Claude's recommendations
706
+ */
707
+ displayRecommendations(recommendation) {
708
+ this.context.stdout.write('\n');
709
+ this.context.stdout.write(chalk.bold.green(' ┌─────────────────────────────────────────────────────────┐\n'));
710
+ this.context.stdout.write(chalk.bold.green(' │') + chalk.bold(' Recommended Structure') + chalk.bold.green(' │\n'));
711
+ this.context.stdout.write(chalk.bold.green(' └─────────────────────────────────────────────────────────┘\n'));
712
+ this.context.stdout.write('\n');
713
+ recommendation.repos.forEach((repo, i) => {
714
+ this.context.stdout.write(chalk.bold(` ${i + 1}. ${repo.name}`) + chalk.gray(` (${repo.structure.type})\n`));
715
+ this.context.stdout.write(chalk.gray(` ${repo.description}\n`));
716
+ this.context.stdout.write(chalk.cyan(` Stack: `) + formatTechStack(repo.techStack) + '\n');
717
+ if (repo.structure.features.length > 0) {
718
+ this.context.stdout.write(chalk.cyan(` Features: `) + repo.structure.features.join(', ') + '\n');
719
+ }
720
+ this.context.stdout.write('\n');
721
+ });
722
+ this.context.stdout.write(chalk.gray(' Rationale: ') + recommendation.rationale + '\n\n');
723
+ }
724
+ /**
725
+ * Get user's choice on how to handle recommendations
726
+ */
727
+ async getRecommendationChoice() {
728
+ const choice = await select({
729
+ message: 'How would you like to proceed?',
730
+ choices: [
731
+ {
732
+ value: 'confirm',
733
+ name: 'Confirm - Use this structure',
734
+ description: 'Accept the recommended repositories',
735
+ },
736
+ {
737
+ value: 'modify',
738
+ name: 'Modify - Select which repos to keep',
739
+ description: 'Choose specific repos from the list',
740
+ },
741
+ {
742
+ value: 'restart',
743
+ name: 'Start Over - Get new recommendations',
744
+ description: 'Provide more context to Claude',
745
+ },
746
+ ],
747
+ });
748
+ return choice;
749
+ }
750
+ /**
751
+ * Let user modify recommended repos
752
+ */
753
+ async modifyRecommendations(recommendation) {
754
+ const selectedRepos = [];
755
+ for (const repo of recommendation.repos) {
756
+ const keep = await confirm({
757
+ message: `Keep "${repo.name}" (${formatTechStack(repo.techStack)})?`,
758
+ default: true,
759
+ });
760
+ if (keep) {
761
+ // Allow modifying the name/alias
762
+ const alias = await input({
763
+ message: ` Alias for ${repo.name}:`,
764
+ default: repo.alias,
765
+ validate: (value) => {
766
+ if (!value.trim())
767
+ return 'Alias is required';
768
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
769
+ return 'Alias must be lowercase, start with letter, use only letters, numbers, hyphens';
770
+ }
771
+ if (selectedRepos.some((r) => r.alias === value)) {
772
+ return 'Alias already used';
773
+ }
774
+ return true;
775
+ },
776
+ });
777
+ selectedRepos.push({
778
+ ...repo,
779
+ alias,
780
+ });
781
+ }
782
+ }
783
+ // Ask if they want to add any custom repos
784
+ const addMore = await confirm({
785
+ message: 'Would you like to add any additional repositories?',
786
+ default: false,
787
+ });
788
+ if (addMore) {
789
+ await this.addCustomRepos(selectedRepos);
790
+ }
791
+ return selectedRepos;
792
+ }
793
+ /**
794
+ * Let user add custom repos
795
+ */
796
+ async addCustomRepos(repos) {
797
+ let addingMore = true;
798
+ while (addingMore) {
799
+ const alias = await input({
800
+ message: 'Repository alias:',
801
+ validate: (value) => {
802
+ if (!value.trim())
803
+ return 'Alias is required';
804
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
805
+ return 'Alias must be lowercase, start with letter, use only letters, numbers, hyphens';
806
+ }
807
+ if (repos.some((r) => r.alias === value)) {
808
+ return 'Alias already used';
809
+ }
810
+ return true;
811
+ },
812
+ });
813
+ const name = await input({
814
+ message: 'Repository name:',
815
+ default: alias,
816
+ });
817
+ const description = await input({
818
+ message: 'Description:',
819
+ validate: (value) => (value.trim() ? true : 'Description is required'),
820
+ });
821
+ const structureType = await select({
822
+ message: 'Type:',
823
+ choices: [
824
+ { value: 'backend', name: 'Backend API' },
825
+ { value: 'frontend', name: 'Frontend App' },
826
+ { value: 'fullstack', name: 'Fullstack' },
827
+ { value: 'library', name: 'Library/Package' },
828
+ { value: 'service', name: 'Microservice' },
829
+ { value: 'cli', name: 'CLI Tool' },
830
+ ],
831
+ });
832
+ const language = await input({
833
+ message: 'Primary language:',
834
+ default: 'typescript',
835
+ });
836
+ const framework = await input({
837
+ message: 'Framework (optional):',
838
+ });
839
+ repos.push({
840
+ alias,
841
+ name,
842
+ description,
843
+ visibility: 'private',
844
+ techStack: {
845
+ language,
846
+ framework: framework || undefined,
847
+ },
848
+ structure: {
849
+ type: structureType,
850
+ features: [],
851
+ },
852
+ });
853
+ addingMore = await confirm({
854
+ message: 'Add another repository?',
855
+ default: false,
856
+ });
857
+ }
858
+ }
859
+ /**
860
+ * Show success message for new projects with planned repos
861
+ */
862
+ showNewProjectSuccess(projectPath, name, plannedRepos, requirements) {
863
+ this.context.stdout.write('\n');
864
+ this.context.stdout.write(chalk.green.bold(' ✓ Success!\n\n'));
865
+ this.context.stdout.write(chalk.bold(' Planning hub created at:\n'));
866
+ this.context.stdout.write(` ${chalk.cyan(projectPath)}\n\n`);
867
+ if (plannedRepos.length > 0) {
868
+ this.context.stdout.write(chalk.bold(' Planned repositories:\n'));
869
+ for (const repo of plannedRepos) {
870
+ this.context.stdout.write(` ${chalk.cyan('○')} ${repo.name} ` +
871
+ chalk.gray(`(${formatTechStack(repo.techStack)})\n`));
872
+ }
873
+ this.context.stdout.write('\n');
874
+ }
875
+ if (requirements) {
876
+ this.context.stdout.write(chalk.bold(' Requirements captured:\n'));
877
+ this.context.stdout.write(chalk.gray(` - ${requirements.userStories.length} user stories\n`));
878
+ this.context.stdout.write(chalk.gray(` - ${requirements.features.length} features\n`));
879
+ this.context.stdout.write(chalk.gray(` - Draft PRD saved to docs/prd/requirements-draft.md\n\n`));
880
+ }
881
+ this.context.stdout.write(chalk.bold(' Next steps:\n'));
882
+ this.context.stdout.write(chalk.gray(` 1. cd ${name}\n`));
883
+ if (plannedRepos.length > 0) {
884
+ this.context.stdout.write(chalk.gray(' 2. vibe repo create --all # Create repos on GitHub\n'));
885
+ if (requirements) {
886
+ this.context.stdout.write(chalk.gray(' 3. vibe prd # Refine the draft PRD\n'));
887
+ }
888
+ else {
889
+ this.context.stdout.write(chalk.gray(' 3. vibe prd # Define requirements\n'));
890
+ }
891
+ this.context.stdout.write(chalk.gray(' 4. vibe scope # Create work scopes\n\n'));
892
+ }
893
+ else {
894
+ this.context.stdout.write(chalk.gray(' 2. vibe repo add # Add repositories\n'));
895
+ this.context.stdout.write(chalk.gray(' 3. vibe prd # Create requirements\n'));
896
+ this.context.stdout.write(chalk.gray(' 4. vibe scope # Create work scopes\n\n'));
897
+ }
898
+ this.context.stdout.write(chalk.gray(' Run ') + chalk.cyan('vibe status') + chalk.gray(' to see planned vs active repos.\n'));
899
+ this.context.stdout.write(chalk.gray(' Run ') + chalk.cyan('vibe --help') + chalk.gray(' for all commands.\n\n'));
900
+ }
245
901
  }
246
902
  //# sourceMappingURL=init.js.map