@compilr-dev/cli 0.7.4 → 0.7.6

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 (43) hide show
  1. package/dist/agent.d.ts +7 -2
  2. package/dist/agent.js +55 -39
  3. package/dist/commands-v2/handlers/project.js +65 -2
  4. package/dist/commands-v2/handlers/settings.js +18 -26
  5. package/dist/compilr-diff-companion.vsix +0 -0
  6. package/dist/db/schema.d.ts +1 -1
  7. package/dist/episodes/index.d.ts +13 -13
  8. package/dist/episodes/index.js +13 -14
  9. package/dist/guide/cli-guide-entries.js +54 -2
  10. package/dist/handlers/delegation-handlers.js +228 -50
  11. package/dist/handlers/interactive-flow-handlers.d.ts +26 -0
  12. package/dist/handlers/interactive-flow-handlers.js +61 -0
  13. package/dist/handlers/propose-alternatives-handlers.d.ts +36 -0
  14. package/dist/handlers/propose-alternatives-handlers.js +65 -0
  15. package/dist/index.js +13 -2
  16. package/dist/repl-v2.d.ts +66 -0
  17. package/dist/repl-v2.js +382 -53
  18. package/dist/shared-handlers.d.ts +57 -5
  19. package/dist/shared-handlers.js +50 -0
  20. package/dist/tools/consult.d.ts +14 -0
  21. package/dist/tools/consult.js +73 -0
  22. package/dist/tools/delegate.d.ts +12 -6
  23. package/dist/tools/delegate.js +35 -19
  24. package/dist/tools/interactive-flow.d.ts +13 -0
  25. package/dist/tools/interactive-flow.js +19 -0
  26. package/dist/tools/platform-adapter.d.ts +14 -2
  27. package/dist/tools/platform-adapter.js +16 -4
  28. package/dist/tools/propose-alternatives.d.ts +13 -0
  29. package/dist/tools/propose-alternatives.js +19 -0
  30. package/dist/tools.d.ts +22 -98
  31. package/dist/tools.js +27 -382
  32. package/dist/ui/markdown-renderer.js +26 -7
  33. package/dist/ui/overlay/data/tutorials/projects/new-project.js +56 -20
  34. package/dist/ui/overlay/impl/interactive-flow-overlay-v2.d.ts +79 -0
  35. package/dist/ui/overlay/impl/interactive-flow-overlay-v2.js +580 -0
  36. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +32 -0
  37. package/dist/ui/overlay/impl/new-overlay-v2.js +305 -66
  38. package/dist/ui/overlay/impl/propose-alternatives-overlay-v2.d.ts +53 -0
  39. package/dist/ui/overlay/impl/propose-alternatives-overlay-v2.js +326 -0
  40. package/dist/ui/overlay/index.d.ts +2 -0
  41. package/dist/ui/overlay/index.js +2 -0
  42. package/dist/ui/tool-formatters.js +61 -3
  43. package/package.json +2 -2
@@ -26,6 +26,10 @@ type WorkflowMode = 'flexible' | 'guided';
26
26
  interface NewProjectState {
27
27
  step: WizardStep;
28
28
  projectType: 'new' | 'existing' | null;
29
+ /** SDK project type id picked at step 3 ('web', 'general', 'research', 'business-plan',
30
+ * 'content', 'tech-docs', 'course', 'book'). Drives whether steps 4+5 are shown and
31
+ * populates the roster step. */
32
+ projectTypeId: string;
29
33
  projectName: string;
30
34
  description: string;
31
35
  repoPattern: RepoPattern | null;
@@ -39,6 +43,13 @@ interface NewProjectState {
39
43
  gitUserName: string | null;
40
44
  gitUserEmail: string | null;
41
45
  gitRemoteInputMode: boolean;
46
+ rosterMode: 'auto' | 'custom' | 'skip';
47
+ /** Per-role include flag, pre-filled from the picked type's suggestedAgents.
48
+ * Only used when rosterMode === 'custom'. */
49
+ rosterToggles: Record<string, boolean>;
50
+ /** Sub-mode of step 7 — when true, render the toggle grid instead of the
51
+ * three-option selector. Same pattern as gitRemoteInputMode on step 6. */
52
+ rosterCustomizeMode: boolean;
42
53
  importPath: string;
43
54
  detectedProject: DetectedProject | null;
44
55
  importGitChoice: 'detected' | 'custom' | 'skip' | 'init' | 'init-skip' | null;
@@ -59,6 +70,13 @@ export interface NewProjectResultV2 {
59
70
  projectName?: string;
60
71
  description?: string;
61
72
  gitRemote?: string;
73
+ /** SDK project type id chosen at step 3. Used by the close handler to register the
74
+ * project with the correct `type` instead of the hardcoded 'general' fallback. */
75
+ projectTypeId?: string;
76
+ /** SDK-canonical short role keys to add to the team after generation. Resolved at
77
+ * close based on rosterMode ('auto' → all suggestedAgents, 'custom' → toggled ones,
78
+ * 'skip' → empty). */
79
+ rosterRolesToAdd?: string[];
62
80
  imported?: boolean;
63
81
  detectedProject?: DetectedProject;
64
82
  }
@@ -78,7 +96,17 @@ export declare class NewProjectOverlayV2 extends BaseOverlayV2<NewProjectState,
78
96
  private renderStep5;
79
97
  private renderStep6;
80
98
  private renderStep7;
99
+ /** Step 7 sub-mode: toggle each suggested agent on/off via Space. */
100
+ private renderStep7Customize;
81
101
  private renderStep8;
102
+ /** After the git step ends, advance to step 7 (roster) — but auto-skip to
103
+ * step 8 (final confirm) when the picked type has no suggested agents
104
+ * (e.g. 'general'). Centralised so both Enter-confirm and Esc-skip on the
105
+ * gitRemoteInputMode sub-mode take the same path. */
106
+ private advancePastGitStep;
107
+ /** Resolve the roles to add based on the chosen rosterMode + toggles. Used by
108
+ * both the step 8 summary render and the result builder at close time. */
109
+ private resolveRosterRolesToAdd;
82
110
  private renderImportStep1;
83
111
  private renderImportStep2;
84
112
  private renderImportStep3;
@@ -87,6 +115,10 @@ export declare class NewProjectOverlayV2 extends BaseOverlayV2<NewProjectState,
87
115
  private renderImportStepFooter;
88
116
  handleKey(key: KeyEvent): OverlayAction<NewProjectResultV2> | Promise<OverlayAction<NewProjectResultV2>>;
89
117
  private handleSelectionKey;
118
+ /** Keys for the step 7 customize sub-mode: ↑/↓ moves between agents, Space
119
+ * toggles the highlighted agent's include flag, Enter confirms the current
120
+ * selection and advances to step 8 (or back to mode picker if nothing left). */
121
+ private handleRosterCustomizeKey;
90
122
  private handleSelectionEnter;
91
123
  private handleInputKey;
92
124
  private handleGitRemoteInput;
@@ -23,7 +23,8 @@ import * as path from 'path';
23
23
  import { execSync } from 'child_process';
24
24
  import { BaseOverlayV2, renderBorder } from '../../base/index.js';
25
25
  import { checkGitConfig } from '../../../utils/git-config.js';
26
- import { TECH_STACK_LABELS, CODING_STANDARDS_LABELS, REPO_PATTERN_LABELS, generateProject, isValidProjectName, projectExists, generateCompilrMdForImport, } from '../../../templates/index.js';
26
+ import { TECH_STACK_LABELS, CODING_STANDARDS_LABELS, generateProject, isValidProjectName, projectExists, generateCompilrMdForImport, } from '../../../templates/index.js';
27
+ import { PROJECT_TYPES, getProjectTypeConfig, ROLE_METADATA } from '@compilr-dev/sdk';
27
28
  import { getProjectsPath, ensureProjectsDirectory } from '../../../settings/paths.js';
28
29
  import { detectProjectInfo, validateImportPath, getLanguageLabel, getFrameworkLabel, } from '../../../utils/project-detection.js';
29
30
  import { projectRepository } from '../../../db/repositories/index.js';
@@ -32,7 +33,6 @@ import { projectRepository } from '../../../db/repositories/index.js';
32
33
  // =============================================================================
33
34
  const TECH_STACK_OPTIONS = ['react-node-pg', 'react-python-pg', 'vue-node-pg', 'custom'];
34
35
  const CODING_STANDARDS_OPTIONS = ['strict', 'relaxed', 'custom'];
35
- const REPO_PATTERN_OPTIONS = ['single', 'two-repo'];
36
36
  const WORKFLOW_MODE_OPTIONS = ['flexible', 'guided'];
37
37
  const WORKFLOW_MODE_LABELS = {
38
38
  flexible: 'Flexible (user-driven, exploratory)',
@@ -46,7 +46,7 @@ function getMaxOptionsForStep(step) {
46
46
  case 0:
47
47
  return 2;
48
48
  case 3:
49
- return REPO_PATTERN_OPTIONS.length;
49
+ return PROJECT_TYPES.length; // Project type picker
50
50
  case 4:
51
51
  return TECH_STACK_OPTIONS.length;
52
52
  case 5:
@@ -54,7 +54,7 @@ function getMaxOptionsForStep(step) {
54
54
  case 6:
55
55
  return 2;
56
56
  case 7:
57
- return WORKFLOW_MODE_OPTIONS.length;
57
+ return 3; // Roster mode: auto / customize / skip
58
58
  case 8:
59
59
  return 2;
60
60
  default:
@@ -77,6 +77,7 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
77
77
  super({
78
78
  step: 0,
79
79
  projectType: null,
80
+ projectTypeId: 'web', // Default — Software Development, top of the SDK list
80
81
  projectName: '',
81
82
  description: '',
82
83
  repoPattern: 'single', // Default — two-repo option removed
@@ -90,6 +91,9 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
90
91
  gitUserName: null,
91
92
  gitUserEmail: null,
92
93
  gitRemoteInputMode: false,
94
+ rosterMode: 'auto',
95
+ rosterToggles: {},
96
+ rosterCustomizeMode: false,
93
97
  // Import project state
94
98
  importPath: '',
95
99
  detectedProject: null,
@@ -264,15 +268,28 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
264
268
  }
265
269
  renderStep3(s) {
266
270
  const lines = [];
267
- lines.push(chalk.bold(' Documentation structure'));
271
+ lines.push(chalk.bold(' What kind of project are you starting?'));
268
272
  lines.push('');
269
- for (let i = 0; i < REPO_PATTERN_OPTIONS.length; i++) {
270
- const pattern = REPO_PATTERN_OPTIONS[i];
271
- lines.push(this.renderSelectionOption(s, i, `${String(i + 1)}. ${REPO_PATTERN_LABELS[pattern]}`));
273
+ // List all SDK-registered project types with their canonical short labels +
274
+ // taglines. The picked type drives whether tech-stack/coding-standards steps
275
+ // appear and which suggestedAgents populate the roster step.
276
+ for (let i = 0; i < PROJECT_TYPES.length; i++) {
277
+ const cfg = PROJECT_TYPES[i];
278
+ const tagline = cfg.tagline ?? cfg.description;
279
+ // Truncate tagline to ~46 chars so the row fits the overlay width.
280
+ const trimmed = tagline.length > 46 ? tagline.slice(0, 43) + '…' : tagline;
281
+ lines.push(this.renderSelectionOption(s, i, `${cfg.label.padEnd(24)} ${s.muted(trimmed)}`));
272
282
  }
273
283
  lines.push('');
274
- lines.push(s.muted(' Single repo: All files in one repository'));
275
- lines.push(s.muted(' Two repos: Code and docs in separate repositories'));
284
+ // Show the picked type's full description + suggested-agent count beneath
285
+ // the list as live preview. Helps users see what the choice actually means
286
+ // without having to commit and back-arrow.
287
+ const picked = PROJECT_TYPES[this.state.selectedIndex];
288
+ lines.push(s.muted(' ' + picked.description));
289
+ if (picked.suggestedAgents.length > 0) {
290
+ const names = picked.suggestedAgents.map((a) => `$${a.role}`).join(', ');
291
+ lines.push(s.muted(` Comes with: ${names}`));
292
+ }
276
293
  return lines;
277
294
  }
278
295
  renderStep4(s) {
@@ -347,31 +364,84 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
347
364
  return lines;
348
365
  }
349
366
  renderStep7(s) {
367
+ // Customize sub-mode — toggle individual agents on/off.
368
+ if (this.state.rosterCustomizeMode) {
369
+ return this.renderStep7Customize(s);
370
+ }
350
371
  const lines = [];
351
- lines.push(chalk.bold(' Workflow mode'));
372
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
373
+ const suggested = typeConfig.suggestedAgents;
374
+ lines.push(chalk.bold(' Team roster'));
352
375
  lines.push('');
353
- for (let i = 0; i < WORKFLOW_MODE_OPTIONS.length; i++) {
354
- const mode = WORKFLOW_MODE_OPTIONS[i];
355
- const suffix = i === 0 ? ' (recommended)' : '';
356
- lines.push(this.renderSelectionOption(s, i, `${String(i + 1)}. ${WORKFLOW_MODE_LABELS[mode]}${suffix}`));
376
+ lines.push(s.muted(` ${typeConfig.label} suggests these specialists:`));
377
+ lines.push('');
378
+ // Show the suggested agents as a preview list with mascot + label + description.
379
+ for (const agent of suggested) {
380
+ const meta = ROLE_METADATA[agent.role];
381
+ const mascot = meta?.mascot ?? '[?]';
382
+ lines.push(s.muted(` ${mascot} ${agent.label.padEnd(14)} ${agent.description}`));
357
383
  }
358
384
  lines.push('');
359
- lines.push(s.muted(' Flexible: Work at your own pace, agent responds to requests'));
360
- lines.push(s.muted(' Guided: Agent suggests next steps, tracks work item progress'));
385
+ lines.push(' How should we set up the team?');
386
+ lines.push('');
387
+ const recommended = ' ← recommended';
388
+ lines.push(this.renderSelectionOption(s, 0, `Add all suggested (${String(suggested.length)})${this.state.selectedIndex === 0 ? recommended : ''}`));
389
+ lines.push(this.renderSelectionOption(s, 1, 'Customize selection'));
390
+ lines.push(this.renderSelectionOption(s, 2, 'Skip — start with just $default'));
391
+ return lines;
392
+ }
393
+ /** Step 7 sub-mode: toggle each suggested agent on/off via Space. */
394
+ renderStep7Customize(s) {
395
+ const lines = [];
396
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
397
+ const suggested = typeConfig.suggestedAgents;
398
+ lines.push(chalk.bold(' Customize team roster'));
399
+ lines.push('');
400
+ lines.push(s.muted(' Toggle agents to include in the initial team:'));
401
+ lines.push('');
402
+ for (let i = 0; i < suggested.length; i++) {
403
+ const agent = suggested[i];
404
+ const meta = ROLE_METADATA[agent.role];
405
+ const mascot = meta?.mascot ?? '[?]';
406
+ const included = (this.state.rosterToggles[agent.role] ?? true);
407
+ const checkbox = included ? '[✓]' : '[ ]';
408
+ const isSelected = i === this.state.selectedIndex;
409
+ const prefix = isSelected ? ' > ' : ' ';
410
+ const line = `${prefix}${checkbox} ${mascot} ${agent.label.padEnd(14)} ${s.muted(agent.description)}`;
411
+ lines.push(isSelected ? s.primary(line) : line);
412
+ }
413
+ lines.push('');
414
+ const selectedCount = suggested.filter((a) => (this.state.rosterToggles[a.role] ?? true)).length;
415
+ lines.push(s.muted(` ${String(selectedCount)} agents selected. You can /team add other roles later.`));
361
416
  return lines;
362
417
  }
363
418
  renderStep8(s) {
364
419
  const lines = [];
420
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
421
+ const isSoftware = typeConfig.category === 'software';
365
422
  lines.push(chalk.bold(' Ready to create project?'));
366
423
  lines.push('');
367
424
  lines.push(` Project: ${s.primary(this.state.projectName)}`);
425
+ lines.push(` Type: ${s.primary(typeConfig.label)}`);
368
426
  lines.push(` Location: ${s.primary('./' + this.state.projectName + '/')}`);
369
- lines.push(` Tech: ${s.primary(this.state.techStack ? TECH_STACK_LABELS[this.state.techStack] : 'Not set')}`);
370
- lines.push(` Standards: ${s.primary(this.state.codingStandards ? CODING_STANDARDS_LABELS[this.state.codingStandards] : 'Not set')}`);
427
+ // Tech stack + coding standards only apply to software-category projects.
428
+ if (isSoftware) {
429
+ lines.push(` Tech: ${s.primary(this.state.techStack ? TECH_STACK_LABELS[this.state.techStack] : 'Not set')}`);
430
+ lines.push(` Standards: ${s.primary(this.state.codingStandards ? CODING_STANDARDS_LABELS[this.state.codingStandards] : 'Not set')}`);
431
+ }
371
432
  lines.push(` Git: ${s.primary(this.state.initGit ? 'Yes' : 'No')}`);
372
433
  if (this.state.gitRemote) {
373
434
  lines.push(` Remote: ${s.primary(this.state.gitRemote)}`);
374
435
  }
436
+ // Roster summary — shows what will be batch-added after generation.
437
+ const rosterRoles = this.resolveRosterRolesToAdd();
438
+ if (rosterRoles.length > 0) {
439
+ const names = rosterRoles.map((r) => `$${r}`).join(', ');
440
+ lines.push(` Team: ${s.primary(`${String(rosterRoles.length)} specialists`)} ${s.muted(`(${names})`)}`);
441
+ }
442
+ else if (this.state.rosterMode === 'skip' || typeConfig.suggestedAgents.length === 0) {
443
+ lines.push(` Team: ${s.muted('$default only')}`);
444
+ }
375
445
  lines.push('');
376
446
  if (this.state.error) {
377
447
  lines.push(s.error(` ${this.state.error}`));
@@ -381,6 +451,34 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
381
451
  lines.push(this.renderSelectionOption(s, 1, 'Cancel'));
382
452
  return lines;
383
453
  }
454
+ /** After the git step ends, advance to step 7 (roster) — but auto-skip to
455
+ * step 8 (final confirm) when the picked type has no suggested agents
456
+ * (e.g. 'general'). Centralised so both Enter-confirm and Esc-skip on the
457
+ * gitRemoteInputMode sub-mode take the same path. */
458
+ advancePastGitStep() {
459
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
460
+ if (typeConfig.suggestedAgents.length === 0) {
461
+ // No roster question to ask — skip straight to confirm.
462
+ this.state.rosterMode = 'skip';
463
+ this.state.step = 8;
464
+ }
465
+ else {
466
+ this.state.step = 7;
467
+ }
468
+ this.state.selectedIndex = 0;
469
+ }
470
+ /** Resolve the roles to add based on the chosen rosterMode + toggles. Used by
471
+ * both the step 8 summary render and the result builder at close time. */
472
+ resolveRosterRolesToAdd() {
473
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
474
+ const suggested = typeConfig.suggestedAgents.map((a) => a.role);
475
+ if (this.state.rosterMode === 'skip')
476
+ return [];
477
+ if (this.state.rosterMode === 'auto')
478
+ return suggested;
479
+ // custom: only the toggles where !== false (defaults to included).
480
+ return suggested.filter((role) => (this.state.rosterToggles[role] ?? true));
481
+ }
384
482
  // ===========================================================================
385
483
  // Import Step Renderers
386
484
  // ===========================================================================
@@ -575,8 +673,7 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
575
673
  this.state.gitRemoteInputMode = false;
576
674
  this.state.gitRemote = '';
577
675
  this.state.inputBuffer = '';
578
- this.state.step = 8; // Skip step 7 (workflow mode removed — always flexible)
579
- this.state.selectedIndex = 0;
676
+ this.advancePastGitStep();
580
677
  return this.rerender();
581
678
  }
582
679
  if (this.state.step === 0) {
@@ -600,6 +697,12 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
600
697
  return this.noAction();
601
698
  }
602
699
  handleSelectionKey(key) {
700
+ // The customize sub-mode on step 7 has a different option count (one per
701
+ // suggested agent) than the parent step's 3 mode options, and adds Space
702
+ // as a toggle. Handle it explicitly before the generic selection logic.
703
+ if (this.state.step === 7 && this.state.rosterCustomizeMode) {
704
+ return this.handleRosterCustomizeKey(key);
705
+ }
603
706
  const maxOptions = getMaxOptionsForStep(this.state.step);
604
707
  // j/k for vim-style navigation
605
708
  if (key.name === 'j' && this.state.selectedIndex < maxOptions - 1) {
@@ -623,6 +726,35 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
623
726
  }
624
727
  return this.noAction();
625
728
  }
729
+ /** Keys for the step 7 customize sub-mode: ↑/↓ moves between agents, Space
730
+ * toggles the highlighted agent's include flag, Enter confirms the current
731
+ * selection and advances to step 8 (or back to mode picker if nothing left). */
732
+ handleRosterCustomizeKey(key) {
733
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
734
+ const max = typeConfig.suggestedAgents.length;
735
+ if ((this.isUpKey(key) || key.name === 'k') && this.state.selectedIndex > 0) {
736
+ this.state.selectedIndex--;
737
+ return this.rerender();
738
+ }
739
+ if ((this.isDownKey(key) || key.name === 'j') && this.state.selectedIndex < max - 1) {
740
+ this.state.selectedIndex++;
741
+ return this.rerender();
742
+ }
743
+ // Space toggles the highlighted agent's include flag.
744
+ if (key.name === 'space') {
745
+ const role = typeConfig.suggestedAgents[this.state.selectedIndex].role;
746
+ const current = (this.state.rosterToggles[role] ?? true);
747
+ this.state.rosterToggles = { ...this.state.rosterToggles, [role]: !current };
748
+ return this.rerender();
749
+ }
750
+ if (this.isEnterKey(key)) {
751
+ this.state.rosterCustomizeMode = false;
752
+ this.state.step = 8;
753
+ this.state.selectedIndex = 0;
754
+ return this.rerender();
755
+ }
756
+ return this.noAction();
757
+ }
626
758
  async handleSelectionEnter() {
627
759
  // Step 0 - choose between new and existing project
628
760
  if (this.state.step === 0) {
@@ -650,45 +782,84 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
650
782
  // Cancel
651
783
  return this.close({ created: false });
652
784
  }
653
- // Verify all required fields are set
654
- if (!this.state.repoPattern || !this.state.techStack || !this.state.codingStandards) {
785
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
786
+ const isSoftware = typeConfig.category === 'software';
787
+ // Software projects require tech stack + coding standards (the full
788
+ // scaffold needs them). Non-software projects don't go through the
789
+ // scaffolder — just need a directory + COMPILR.md.
790
+ if (isSoftware && (!this.state.repoPattern || !this.state.techStack || !this.state.codingStandards)) {
655
791
  this.state.error = 'Missing configuration values';
656
792
  return this.rerender();
657
793
  }
658
794
  // Show generating state
659
795
  this.state.isGenerating = true;
660
- // Create project
661
- const config = {
662
- name: this.state.projectName,
663
- description: this.state.description,
664
- repoPattern: this.state.repoPattern,
665
- techStack: this.state.techStack,
666
- codingStandards: this.state.codingStandards,
667
- initGit: this.state.initGit,
668
- };
669
796
  try {
670
- // Ensure projects directory exists and create project there
671
797
  ensureProjectsDirectory();
672
798
  const projectsPath = getProjectsPath();
673
- const result = await generateProject(config, projectsPath);
674
- if (result.success) {
675
- return this.close({
676
- created: true,
677
- projectPath: result.projectPath,
678
- docsPath: result.docsPath,
679
- filesCreated: result.filesCreated,
680
- trackInDatabase: true, // Always track in database
681
- workflowMode: this.state.workflowMode,
682
- projectName: this.state.projectName,
799
+ if (isSoftware) {
800
+ // Software path — full scaffold via SDK generateProject. The earlier
801
+ // validation guarantees these are set when isSoftware is true, but
802
+ // we provide explicit fallbacks here so the types resolve without
803
+ // non-null assertions.
804
+ const config = {
805
+ name: this.state.projectName,
683
806
  description: this.state.description,
684
- gitRemote: this.state.gitRemote || undefined,
685
- });
686
- }
687
- else {
807
+ repoPattern: this.state.repoPattern ?? 'single',
808
+ techStack: this.state.techStack ?? 'react-node-pg',
809
+ codingStandards: this.state.codingStandards ?? 'strict',
810
+ initGit: this.state.initGit,
811
+ };
812
+ const result = await generateProject(config, projectsPath);
813
+ if (result.success) {
814
+ return this.close({
815
+ created: true,
816
+ projectPath: result.projectPath,
817
+ docsPath: result.docsPath,
818
+ filesCreated: result.filesCreated,
819
+ trackInDatabase: true,
820
+ workflowMode: this.state.workflowMode,
821
+ projectName: this.state.projectName,
822
+ description: this.state.description,
823
+ gitRemote: this.state.gitRemote || undefined,
824
+ projectTypeId: this.state.projectTypeId,
825
+ rosterRolesToAdd: this.resolveRosterRolesToAdd(),
826
+ });
827
+ }
688
828
  this.state.isGenerating = false;
689
829
  this.state.error = result.error ?? 'Unknown error';
690
830
  return this.rerender();
691
831
  }
832
+ // Non-software path — lightweight directory + COMPILR.md (mirrors the
833
+ // desktop's ipc.ts:1239 branch). No tech-stack scaffold; the project
834
+ // type config drives downstream agent + skill availability.
835
+ const projectPath = path.join(projectsPath, this.state.projectName);
836
+ fs.mkdirSync(projectPath, { recursive: true });
837
+ fs.mkdirSync(path.join(projectPath, 'knowledge'), { recursive: true });
838
+ const displayName = this.state.projectName
839
+ .replace(/-/g, ' ')
840
+ .replace(/\b\w/g, (c) => c.toUpperCase());
841
+ const compilrMd = `# ${displayName}\n\n${this.state.description}\n\nProject type: ${this.state.projectTypeId}\n`;
842
+ fs.writeFileSync(path.join(projectPath, 'COMPILR.md'), compilrMd, 'utf-8');
843
+ if (this.state.initGit) {
844
+ try {
845
+ execSync('git init', { cwd: projectPath, stdio: 'ignore' });
846
+ }
847
+ catch {
848
+ // Best-effort — git not available, ignore.
849
+ }
850
+ }
851
+ return this.close({
852
+ created: true,
853
+ projectPath,
854
+ filesCreated: ['COMPILR.md', 'knowledge/'],
855
+ trackInDatabase: true,
856
+ workflowMode: this.state.workflowMode,
857
+ projectName: this.state.projectName,
858
+ description: this.state.description,
859
+ gitRemote: this.state.gitRemote || undefined,
860
+ projectTypeId: this.state.projectTypeId,
861
+ rosterRolesToAdd: this.resolveRosterRolesToAdd(),
862
+ });
692
863
  }
693
864
  catch (error) {
694
865
  this.state.isGenerating = false;
@@ -725,8 +896,7 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
725
896
  this.state.gitRemote = this.state.inputBuffer.trim();
726
897
  this.state.gitRemoteInputMode = false;
727
898
  this.state.inputBuffer = '';
728
- this.state.step = 8; // Skip step 7 (workflow mode removed)
729
- this.state.selectedIndex = 0;
899
+ this.advancePastGitStep();
730
900
  return this.rerender();
731
901
  }
732
902
  // Backspace - delete last character
@@ -1124,14 +1294,23 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
1124
1294
  break;
1125
1295
  case 2:
1126
1296
  this.state.description = this.state.inputBuffer.trim();
1127
- this.state.step = 4; // Skip step 3 (repo pattern removed)
1128
- this.state.selectedIndex = 0;
1297
+ // Advance to step 3 project type picker. Default highlight on the
1298
+ // type that's currently stored (defaults to 'web' from the constructor).
1299
+ this.state.step = 3;
1300
+ this.state.selectedIndex = PROJECT_TYPES.findIndex((t) => t.id === this.state.projectTypeId);
1301
+ if (this.state.selectedIndex < 0)
1302
+ this.state.selectedIndex = 0;
1129
1303
  break;
1130
- case 3:
1131
- this.state.repoPattern = REPO_PATTERN_OPTIONS[this.state.selectedIndex];
1132
- this.state.step = 4;
1304
+ case 3: {
1305
+ // User picked a project type. Record it; skip tech-stack + coding-standards
1306
+ // for non-software-category types (research, business, book, etc.).
1307
+ const picked = PROJECT_TYPES[this.state.selectedIndex];
1308
+ this.state.projectTypeId = picked.id;
1309
+ const isSoftware = picked.category === 'software';
1310
+ this.state.step = isSoftware ? 4 : 6;
1133
1311
  this.state.selectedIndex = 0;
1134
1312
  break;
1313
+ }
1135
1314
  case 4:
1136
1315
  this.state.techStack = TECH_STACK_OPTIONS[this.state.selectedIndex];
1137
1316
  this.state.step = 5;
@@ -1149,15 +1328,45 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
1149
1328
  this.state.gitRemoteInputMode = true;
1150
1329
  this.state.inputBuffer = '';
1151
1330
  break;
1152
- case 7:
1153
- this.state.workflowMode = WORKFLOW_MODE_OPTIONS[this.state.selectedIndex];
1154
- this.state.step = 8;
1155
- this.state.selectedIndex = 0;
1331
+ case 7: {
1332
+ // Roster mode selection: 0=auto, 1=customize, 2=skip.
1333
+ // Pre-fill toggles from suggestedAgents (all included by default).
1334
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
1335
+ const toggles = {};
1336
+ for (const a of typeConfig.suggestedAgents)
1337
+ toggles[a.role] = true;
1338
+ this.state.rosterToggles = toggles;
1339
+ if (this.state.selectedIndex === 0) {
1340
+ this.state.rosterMode = 'auto';
1341
+ this.state.step = 8;
1342
+ this.state.selectedIndex = 0;
1343
+ }
1344
+ else if (this.state.selectedIndex === 1) {
1345
+ // Enter customize sub-mode on the same step.
1346
+ this.state.rosterMode = 'custom';
1347
+ this.state.rosterCustomizeMode = true;
1348
+ this.state.selectedIndex = 0;
1349
+ }
1350
+ else {
1351
+ this.state.rosterMode = 'skip';
1352
+ this.state.step = 8;
1353
+ this.state.selectedIndex = 0;
1354
+ }
1156
1355
  break;
1356
+ }
1157
1357
  }
1158
1358
  this.state.error = null;
1159
1359
  }
1160
1360
  prevStep() {
1361
+ // Customize sub-mode on step 7 — back-arrow returns to roster-mode selection
1362
+ // on the same step, NOT to step 6. Mirrors how step 6's gitRemoteInputMode
1363
+ // back-arrow exits the input field without leaving step 6.
1364
+ if (this.state.step === 7 && this.state.rosterCustomizeMode) {
1365
+ this.state.rosterCustomizeMode = false;
1366
+ this.state.selectedIndex = 1; // "Customize selection" was option index 1
1367
+ this.state.error = null;
1368
+ return;
1369
+ }
1161
1370
  switch (this.state.step) {
1162
1371
  case 1:
1163
1372
  this.state.step = 0;
@@ -1168,29 +1377,59 @@ export class NewProjectOverlayV2 extends BaseOverlayV2 {
1168
1377
  this.state.inputBuffer = this.state.projectName;
1169
1378
  break;
1170
1379
  case 3:
1380
+ // Back from project-type picker to description
1171
1381
  this.state.step = 2;
1172
1382
  this.state.inputBuffer = this.state.description;
1173
1383
  break;
1174
1384
  case 4:
1175
- this.state.step = 2; // Back to description (step 3 repo pattern removed)
1176
- this.state.inputBuffer = this.state.description;
1385
+ // Back from tech stack to project-type picker
1386
+ this.state.step = 3;
1387
+ this.state.selectedIndex = PROJECT_TYPES.findIndex((t) => t.id === this.state.projectTypeId);
1388
+ if (this.state.selectedIndex < 0)
1389
+ this.state.selectedIndex = 0;
1177
1390
  break;
1178
1391
  case 5:
1179
1392
  this.state.step = 4;
1180
1393
  this.state.selectedIndex = this.state.techStack ? TECH_STACK_OPTIONS.indexOf(this.state.techStack) : 0;
1181
1394
  break;
1182
- case 6:
1183
- this.state.step = 5;
1184
- this.state.selectedIndex = this.state.codingStandards ? CODING_STANDARDS_OPTIONS.indexOf(this.state.codingStandards) : 0;
1395
+ case 6: {
1396
+ // Back from git step. Software types came via 5 (coding standards);
1397
+ // non-software types skipped 4+5 and came directly from 3.
1398
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
1399
+ const isSoftware = typeConfig.category === 'software';
1400
+ if (isSoftware) {
1401
+ this.state.step = 5;
1402
+ this.state.selectedIndex = this.state.codingStandards
1403
+ ? CODING_STANDARDS_OPTIONS.indexOf(this.state.codingStandards)
1404
+ : 0;
1405
+ }
1406
+ else {
1407
+ this.state.step = 3;
1408
+ this.state.selectedIndex = PROJECT_TYPES.findIndex((t) => t.id === this.state.projectTypeId);
1409
+ if (this.state.selectedIndex < 0)
1410
+ this.state.selectedIndex = 0;
1411
+ }
1185
1412
  break;
1413
+ }
1186
1414
  case 7:
1415
+ // Back from roster mode selection to git step
1187
1416
  this.state.step = 6;
1188
1417
  this.state.selectedIndex = this.state.initGit ? 0 : 1;
1189
1418
  break;
1190
- case 8:
1191
- this.state.step = 6; // Back to git (step 7 workflow mode removed)
1192
- this.state.selectedIndex = this.state.initGit ? 0 : 1;
1419
+ case 8: {
1420
+ // Back from confirm. If the project type has suggested agents, return to
1421
+ // step 7 (roster); otherwise step 7 was auto-skipped, return to step 6.
1422
+ const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
1423
+ if (typeConfig.suggestedAgents.length > 0) {
1424
+ this.state.step = 7;
1425
+ this.state.selectedIndex = this.state.rosterMode === 'auto' ? 0 : this.state.rosterMode === 'custom' ? 1 : 2;
1426
+ }
1427
+ else {
1428
+ this.state.step = 6;
1429
+ this.state.selectedIndex = this.state.initGit ? 0 : 1;
1430
+ }
1193
1431
  break;
1432
+ }
1194
1433
  }
1195
1434
  this.state.error = null;
1196
1435
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Propose Alternatives Overlay V2
3
+ *
4
+ * Modal overlay for picking one of 2-3 agent-proposed alternatives.
5
+ * One tab per alternative (vs ask_user's one tab per question) — tabs
6
+ * are for COMPARING different answers to the SAME single question, not
7
+ * for moving between distinct questions.
8
+ *
9
+ * Spec: project-docs/00-requirements/compilr-dev-cli/propose-alternatives-spec.md
10
+ */
11
+ import { BaseOverlayV2 } from '../../base/index.js';
12
+ import type { RenderContext, OverlayAction, KeyEvent } from '../types.js';
13
+ import type { Alternative } from '../../../shared-handlers.js';
14
+ interface ProposeAlternativesState {
15
+ /** Active tab — 0-based alternative index */
16
+ currentTab: number;
17
+ /** Whether the user has switched to the notes-input mode */
18
+ isAddingNotes: boolean;
19
+ /** Notes buffer (single-line; v1) */
20
+ notesBuffer: string;
21
+ }
22
+ export interface ProposeAlternativesOptionsV2 {
23
+ question: string;
24
+ context?: string;
25
+ alternatives: Alternative[];
26
+ }
27
+ export interface ProposeAlternativesResultV2 {
28
+ chosenIndex: number;
29
+ chosenTitle: string;
30
+ notes?: string;
31
+ rejected: boolean;
32
+ }
33
+ export declare class ProposeAlternativesOverlayV2 extends BaseOverlayV2<ProposeAlternativesState, ProposeAlternativesResultV2> {
34
+ readonly type: "inline";
35
+ readonly id = "propose-alternatives-overlay-v2";
36
+ private readonly question;
37
+ private readonly context?;
38
+ private readonly alternatives;
39
+ constructor(options: ProposeAlternativesOptionsV2);
40
+ protected renderContent(context: RenderContext): string[];
41
+ handleKey(key: KeyEvent): OverlayAction<ProposeAlternativesResultV2>;
42
+ getCloseSummary(result: ProposeAlternativesResultV2): string | null;
43
+ private handleNavigationKey;
44
+ private handleNotesKey;
45
+ private renderTabBar;
46
+ private renderActiveAlternative;
47
+ private renderContentBlock;
48
+ private renderNotesInput;
49
+ private renderInstructions;
50
+ private makePicked;
51
+ private makeRejected;
52
+ }
53
+ export {};