@compilr-dev/cli 0.7.4 → 0.7.5
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/agent.d.ts +7 -2
- package/dist/agent.js +55 -39
- package/dist/commands-v2/handlers/project.js +65 -2
- package/dist/commands-v2/handlers/settings.js +18 -26
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/db/schema.d.ts +1 -1
- package/dist/episodes/index.d.ts +13 -13
- package/dist/episodes/index.js +13 -14
- package/dist/guide/cli-guide-entries.js +54 -2
- package/dist/handlers/delegation-handlers.js +228 -50
- package/dist/handlers/interactive-flow-handlers.d.ts +26 -0
- package/dist/handlers/interactive-flow-handlers.js +61 -0
- package/dist/handlers/propose-alternatives-handlers.d.ts +36 -0
- package/dist/handlers/propose-alternatives-handlers.js +65 -0
- package/dist/index.js +13 -2
- package/dist/repl-v2.d.ts +66 -0
- package/dist/repl-v2.js +382 -53
- package/dist/shared-handlers.d.ts +57 -5
- package/dist/shared-handlers.js +50 -0
- package/dist/tools/consult.d.ts +14 -0
- package/dist/tools/consult.js +73 -0
- package/dist/tools/delegate.d.ts +12 -6
- package/dist/tools/delegate.js +35 -19
- package/dist/tools/interactive-flow.d.ts +13 -0
- package/dist/tools/interactive-flow.js +19 -0
- package/dist/tools/platform-adapter.d.ts +14 -2
- package/dist/tools/platform-adapter.js +16 -4
- package/dist/tools/propose-alternatives.d.ts +13 -0
- package/dist/tools/propose-alternatives.js +19 -0
- package/dist/tools.d.ts +22 -98
- package/dist/tools.js +27 -382
- package/dist/ui/markdown-renderer.js +26 -7
- package/dist/ui/overlay/data/tutorials/projects/new-project.js +56 -20
- package/dist/ui/overlay/impl/interactive-flow-overlay-v2.d.ts +79 -0
- package/dist/ui/overlay/impl/interactive-flow-overlay-v2.js +580 -0
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +32 -0
- package/dist/ui/overlay/impl/new-overlay-v2.js +305 -66
- package/dist/ui/overlay/impl/propose-alternatives-overlay-v2.d.ts +53 -0
- package/dist/ui/overlay/impl/propose-alternatives-overlay-v2.js +326 -0
- package/dist/ui/overlay/index.d.ts +2 -0
- package/dist/ui/overlay/index.js +2 -0
- package/dist/ui/tool-formatters.js +61 -3
- 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,
|
|
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
|
|
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
|
|
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('
|
|
271
|
+
lines.push(chalk.bold(' What kind of project are you starting?'));
|
|
268
272
|
lines.push('');
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
372
|
+
const typeConfig = getProjectTypeConfig(this.state.projectTypeId);
|
|
373
|
+
const suggested = typeConfig.suggestedAgents;
|
|
374
|
+
lines.push(chalk.bold(' Team roster'));
|
|
352
375
|
lines.push('');
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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(
|
|
360
|
-
lines.push(
|
|
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
|
-
|
|
370
|
-
|
|
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.
|
|
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
|
-
|
|
654
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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.
|
|
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
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
1132
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
this.state.
|
|
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
|
-
|
|
1176
|
-
this.state.
|
|
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
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
1192
|
-
|
|
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 {};
|