Skip to content

Commit 12b6821

Browse files
Jasonclaude
authored andcommitted
Fix checkbox prompt error in feature prioritization
- Added validation to prevent selecting all features as must-have for MVP - Added conditional logic to skip nice-to-have prompt when no features remain - Created unit tests to verify the fix works correctly - Updated version to 3.2.7 Fixes the error: "[checkbox prompt] No selectable choices. All choices are disabled." 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 1ba3904 commit 12b6821

File tree

3 files changed

+92
-11
lines changed

3 files changed

+92
-11
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "context-forge",
3-
"version": "3.2.6",
3+
"version": "3.2.7",
44
"description": "AI orchestration platform with autonomous teams, enhancement planning, migration tools, 25+ slash commands, checkpoints & hooks. Multi-IDE: Claude, Cursor, Windsurf, Cline, Copilot",
55
"main": "dist/index.js",
66
"bin": {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import inquirer from 'inquirer';
2+
import { features } from '../features';
3+
4+
jest.mock('inquirer');
5+
6+
describe('features prompt', () => {
7+
beforeEach(() => {
8+
jest.clearAllMocks();
9+
});
10+
11+
it('should handle case where all features are selected as must-have', async () => {
12+
const mockPrompt = jest.mocked(inquirer.prompt);
13+
14+
// Mock initial feature selection - select all features
15+
mockPrompt.mockResolvedValueOnce({
16+
selectedFeatures: ['auth', 'dashboard', 'crud', 'file-upload', 'realtime', 'email', 'search'],
17+
});
18+
19+
// Mock no custom features
20+
mockPrompt.mockResolvedValueOnce({
21+
hasCustomFeatures: false,
22+
});
23+
24+
// Mock must-have selection - this should validate against selecting all
25+
mockPrompt.mockResolvedValueOnce({
26+
mustHaveFeatures: ['auth', 'crud', 'dashboard'],
27+
});
28+
29+
// Mock nice-to-have selection - should have remaining features
30+
mockPrompt.mockResolvedValueOnce({
31+
niceToHaveFeatures: ['search', 'email'],
32+
});
33+
34+
const result = await features('web');
35+
36+
expect(result).toHaveLength(7);
37+
expect(result.filter((f) => f.priority === 'must-have')).toHaveLength(3);
38+
expect(result.filter((f) => f.priority === 'nice-to-have')).toHaveLength(2);
39+
expect(result.filter((f) => f.priority === 'should-have')).toHaveLength(2);
40+
});
41+
42+
it('should skip nice-to-have prompt when all features are must-have', async () => {
43+
const mockPrompt = jest.mocked(inquirer.prompt);
44+
45+
// Mock initial feature selection - select only 2 features
46+
mockPrompt.mockResolvedValueOnce({
47+
selectedFeatures: ['auth', 'crud'],
48+
});
49+
50+
// Mock no custom features
51+
mockPrompt.mockResolvedValueOnce({
52+
hasCustomFeatures: false,
53+
});
54+
55+
// Mock must-have selection - select all available features
56+
mockPrompt.mockResolvedValueOnce({
57+
mustHaveFeatures: ['auth', 'crud'],
58+
});
59+
60+
const result = await features('web');
61+
62+
// Should only call prompt 3 times (not 4) since nice-to-have is skipped
63+
expect(mockPrompt).toHaveBeenCalledTimes(3);
64+
expect(result).toHaveLength(2);
65+
expect(result.every((f) => f.priority === 'must-have')).toBe(true);
66+
});
67+
});

src/cli/prompts/features.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -376,19 +376,33 @@ async function prioritizeFeatures(features: Feature[]): Promise<Feature[]> {
376376
message: 'Which features are MUST-HAVE for the MVP?',
377377
choices: priorityChoices,
378378
default: features.filter((f) => f.priority === 'must-have').map((f) => f.id),
379+
validate: (input) => {
380+
if (input.length === 0) {
381+
return 'Please select at least one must-have feature';
382+
}
383+
if (input.length === features.length) {
384+
return 'Please leave some features for later phases. Not all features can be must-have for MVP.';
385+
}
386+
return true;
387+
},
379388
},
380389
]);
381390

382-
const { niceToHaveFeatures } = await inquirer.prompt([
383-
{
384-
type: 'checkbox',
385-
name: 'niceToHaveFeatures',
386-
message: 'Which features are NICE-TO-HAVE (can be added later)?',
387-
choices: priorityChoices
388-
.filter((c) => !mustHaveFeatures.includes(c.value))
389-
.map((c) => ({ ...c, disabled: false })),
390-
},
391-
]);
391+
const remainingChoices = priorityChoices.filter((c) => !mustHaveFeatures.includes(c.value));
392+
393+
let niceToHaveFeatures: string[] = [];
394+
395+
if (remainingChoices.length > 0) {
396+
const response = await inquirer.prompt([
397+
{
398+
type: 'checkbox',
399+
name: 'niceToHaveFeatures',
400+
message: 'Which features are NICE-TO-HAVE (can be added later)?',
401+
choices: remainingChoices,
402+
},
403+
]);
404+
niceToHaveFeatures = response.niceToHaveFeatures;
405+
}
392406

393407
// Update priorities based on user selection
394408
return features.map((feature) => ({

0 commit comments

Comments
 (0)