opencode-conductor-cdd-plugin 1.0.0-beta.17 → 1.0.0-beta.19
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/prompts/agent/cdd.md +16 -16
- package/dist/prompts/agent/implementer.md +5 -5
- package/dist/prompts/agent.md +7 -7
- package/dist/prompts/cdd/implement.json +1 -1
- package/dist/prompts/cdd/revert.json +1 -1
- package/dist/prompts/cdd/setup.json +2 -2
- package/dist/prompts/cdd/setup.test.js +40 -118
- package/dist/prompts/cdd/setup.test.ts +40 -143
- package/dist/test/integration/rebrand.test.js +15 -14
- package/dist/utils/agentMapping.js +2 -0
- package/dist/utils/archive-tracks.d.ts +28 -0
- package/dist/utils/archive-tracks.js +154 -1
- package/dist/utils/archive-tracks.test.d.ts +1 -0
- package/dist/utils/archive-tracks.test.js +495 -0
- package/dist/utils/codebaseAnalysis.d.ts +61 -0
- package/dist/utils/codebaseAnalysis.js +429 -0
- package/dist/utils/codebaseAnalysis.test.d.ts +1 -0
- package/dist/utils/codebaseAnalysis.test.js +556 -0
- package/dist/utils/documentGeneration.d.ts +97 -0
- package/dist/utils/documentGeneration.js +301 -0
- package/dist/utils/documentGeneration.test.d.ts +1 -0
- package/dist/utils/documentGeneration.test.js +380 -0
- package/dist/utils/interactiveMenu.d.ts +56 -0
- package/dist/utils/interactiveMenu.js +144 -0
- package/dist/utils/interactiveMenu.test.d.ts +1 -0
- package/dist/utils/interactiveMenu.test.js +231 -0
- package/dist/utils/interactiveSetup.d.ts +43 -0
- package/dist/utils/interactiveSetup.js +131 -0
- package/dist/utils/interactiveSetup.test.d.ts +1 -0
- package/dist/utils/interactiveSetup.test.js +124 -0
- package/dist/utils/metadataTracker.d.ts +39 -0
- package/dist/utils/metadataTracker.js +105 -0
- package/dist/utils/metadataTracker.test.d.ts +1 -0
- package/dist/utils/metadataTracker.test.js +265 -0
- package/dist/utils/planParser.d.ts +25 -0
- package/dist/utils/planParser.js +107 -0
- package/dist/utils/planParser.test.d.ts +1 -0
- package/dist/utils/planParser.test.js +119 -0
- package/dist/utils/projectMaturity.d.ts +53 -0
- package/dist/utils/projectMaturity.js +179 -0
- package/dist/utils/projectMaturity.test.d.ts +1 -0
- package/dist/utils/projectMaturity.test.js +298 -0
- package/dist/utils/questionGenerator.d.ts +51 -0
- package/dist/utils/questionGenerator.js +535 -0
- package/dist/utils/questionGenerator.test.d.ts +1 -0
- package/dist/utils/questionGenerator.test.js +328 -0
- package/dist/utils/setupIntegration.d.ts +72 -0
- package/dist/utils/setupIntegration.js +179 -0
- package/dist/utils/setupIntegration.test.d.ts +1 -0
- package/dist/utils/setupIntegration.test.js +344 -0
- package/dist/utils/statusDisplay.d.ts +35 -0
- package/dist/utils/statusDisplay.js +81 -0
- package/dist/utils/statusDisplay.test.d.ts +1 -0
- package/dist/utils/statusDisplay.test.js +102 -0
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Question, QuestionType } from './questionGenerator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Interactive Menu System
|
|
4
|
+
*
|
|
5
|
+
* Provides menu rendering and input validation for the CDD setup process.
|
|
6
|
+
* Adapts to LLM-based interaction pattern (no terminal UI library required).
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Format questions with lettered options (A-E)
|
|
10
|
+
* - Validate single/multiple selections
|
|
11
|
+
* - Parse user input into selection arrays
|
|
12
|
+
* - Render complete menus with instructions
|
|
13
|
+
*
|
|
14
|
+
* Based on reference implementations:
|
|
15
|
+
* - derekbar90/opencode-conductor
|
|
16
|
+
* - gemini-cli-extensions/conductor
|
|
17
|
+
*/
|
|
18
|
+
export interface MenuOptions {
|
|
19
|
+
showInstructions?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format a question with numbered display and lettered options
|
|
23
|
+
*
|
|
24
|
+
* Example output:
|
|
25
|
+
* Question 1: What are the key features? (Select all that apply)
|
|
26
|
+
* A) User authentication
|
|
27
|
+
* B) Real-time updates
|
|
28
|
+
* C) Data analytics
|
|
29
|
+
* D) Enter custom features
|
|
30
|
+
* E) Auto-generate from codebase
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatQuestionWithOptions(question: Question, questionNumber: number): string;
|
|
33
|
+
/**
|
|
34
|
+
* Validate user selection input
|
|
35
|
+
*
|
|
36
|
+
* Rules:
|
|
37
|
+
* - Exclusive: Single letter A-E
|
|
38
|
+
* - Additive: Single letter or comma-separated (A,B,C)
|
|
39
|
+
* - Option E cannot be combined with others in additive
|
|
40
|
+
* - No duplicates allowed
|
|
41
|
+
* - Case insensitive
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateSelection(input: string, questionType: QuestionType): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Parse user selection input into array of letters
|
|
46
|
+
*
|
|
47
|
+
* Returns empty array if invalid
|
|
48
|
+
* Normalizes to uppercase, removes duplicates, and sorts for additive
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseSelection(input: string, questionType: QuestionType): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Render complete interactive menu with question and instructions
|
|
53
|
+
*
|
|
54
|
+
* Generates LLM-compatible menu display suitable for text-based interaction
|
|
55
|
+
*/
|
|
56
|
+
export declare function renderMenu(question: Question, questionNumber: number, options?: MenuOptions): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const VALID_OPTIONS = ['A', 'B', 'C', 'D', 'E'];
|
|
2
|
+
const MAX_OPTION_LENGTH = 80;
|
|
3
|
+
/**
|
|
4
|
+
* Format a question with numbered display and lettered options
|
|
5
|
+
*
|
|
6
|
+
* Example output:
|
|
7
|
+
* Question 1: What are the key features? (Select all that apply)
|
|
8
|
+
* A) User authentication
|
|
9
|
+
* B) Real-time updates
|
|
10
|
+
* C) Data analytics
|
|
11
|
+
* D) Enter custom features
|
|
12
|
+
* E) Auto-generate from codebase
|
|
13
|
+
*/
|
|
14
|
+
export function formatQuestionWithOptions(question, questionNumber) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
// Question header with number
|
|
17
|
+
const suffix = question.type === 'additive' ? ' (Select all that apply)' : '';
|
|
18
|
+
lines.push(`Question ${questionNumber}: ${question.text}${suffix}`);
|
|
19
|
+
lines.push('');
|
|
20
|
+
// Format options A-E
|
|
21
|
+
question.options.forEach((option, index) => {
|
|
22
|
+
const letter = VALID_OPTIONS[index];
|
|
23
|
+
// Truncate option text: if > 80, cut to fit "..." within 80 char limit
|
|
24
|
+
// Target: 80 max, so truncate to 75 + "..." (3) = 78 total
|
|
25
|
+
const truncated = option.length > MAX_OPTION_LENGTH
|
|
26
|
+
? option.substring(0, 75) + '...'
|
|
27
|
+
: option;
|
|
28
|
+
lines.push(`${letter}) ${truncated}`);
|
|
29
|
+
});
|
|
30
|
+
return lines.join('\n');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate user selection input
|
|
34
|
+
*
|
|
35
|
+
* Rules:
|
|
36
|
+
* - Exclusive: Single letter A-E
|
|
37
|
+
* - Additive: Single letter or comma-separated (A,B,C)
|
|
38
|
+
* - Option E cannot be combined with others in additive
|
|
39
|
+
* - No duplicates allowed
|
|
40
|
+
* - Case insensitive
|
|
41
|
+
*/
|
|
42
|
+
export function validateSelection(input, questionType) {
|
|
43
|
+
const trimmed = input.trim();
|
|
44
|
+
if (!trimmed) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Parse selections
|
|
48
|
+
const selections = trimmed
|
|
49
|
+
.split(',')
|
|
50
|
+
.map(s => s.trim().toUpperCase())
|
|
51
|
+
.filter(s => s.length > 0);
|
|
52
|
+
// Check for empty selections after split
|
|
53
|
+
if (selections.length === 0) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// All selections must be valid letters (A-E)
|
|
57
|
+
for (const selection of selections) {
|
|
58
|
+
if (!VALID_OPTIONS.includes(selection)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Check for duplicates using Set
|
|
63
|
+
const uniqueSelections = new Set(selections);
|
|
64
|
+
if (uniqueSelections.size !== selections.length) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// Exclusive: Only one selection allowed
|
|
68
|
+
if (questionType === 'exclusive' && uniqueSelections.size > 1) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
// Option E (auto-generate) cannot be combined with others in additive
|
|
72
|
+
if (questionType === 'additive' && uniqueSelections.size > 1 && uniqueSelections.has('E')) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse user selection input into array of letters
|
|
79
|
+
*
|
|
80
|
+
* Returns empty array if invalid
|
|
81
|
+
* Normalizes to uppercase, removes duplicates, and sorts for additive
|
|
82
|
+
*/
|
|
83
|
+
export function parseSelection(input, questionType) {
|
|
84
|
+
const trimmed = input.trim();
|
|
85
|
+
if (!trimmed) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
// Parse and normalize
|
|
89
|
+
const selections = trimmed
|
|
90
|
+
.split(',')
|
|
91
|
+
.map(s => s.trim().toUpperCase())
|
|
92
|
+
.filter(s => s.length > 0);
|
|
93
|
+
// Remove duplicates
|
|
94
|
+
const uniqueSelections = [...new Set(selections)];
|
|
95
|
+
// Now validate the unique selections
|
|
96
|
+
const validationInput = uniqueSelections.join(',');
|
|
97
|
+
if (!validateSelection(validationInput, questionType)) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
// Sort additive selections for consistency
|
|
101
|
+
if (questionType === 'additive') {
|
|
102
|
+
return uniqueSelections.sort();
|
|
103
|
+
}
|
|
104
|
+
return uniqueSelections;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get section-specific instruction text for the menu
|
|
108
|
+
*/
|
|
109
|
+
function getSectionInstructions(question) {
|
|
110
|
+
const instructions = [];
|
|
111
|
+
if (question.type === 'additive') {
|
|
112
|
+
instructions.push('- You can select multiple options by separating them with commas (e.g., "A,B,C")');
|
|
113
|
+
instructions.push('- Or select a single option (e.g., "A")');
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
instructions.push('- Select one option (e.g., "A")');
|
|
117
|
+
}
|
|
118
|
+
instructions.push('- Option D: Enter custom text when prompted');
|
|
119
|
+
instructions.push(`- Option E: Auto-generate this section from your codebase`);
|
|
120
|
+
if (question.type === 'additive') {
|
|
121
|
+
instructions.push('- Note: Option E cannot be combined with other options');
|
|
122
|
+
}
|
|
123
|
+
return instructions.join('\n');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Render complete interactive menu with question and instructions
|
|
127
|
+
*
|
|
128
|
+
* Generates LLM-compatible menu display suitable for text-based interaction
|
|
129
|
+
*/
|
|
130
|
+
export function renderMenu(question, questionNumber, options = {}) {
|
|
131
|
+
const { showInstructions = true } = options;
|
|
132
|
+
const lines = [];
|
|
133
|
+
// Add formatted question with options
|
|
134
|
+
lines.push(formatQuestionWithOptions(question, questionNumber));
|
|
135
|
+
// Add instructions if enabled
|
|
136
|
+
if (showInstructions) {
|
|
137
|
+
lines.push('');
|
|
138
|
+
lines.push('Instructions:');
|
|
139
|
+
lines.push('Enter your selection (e.g., "A,B,C" for multiple or "A" for single)');
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push(getSectionInstructions(question));
|
|
142
|
+
}
|
|
143
|
+
return lines.join('\n');
|
|
144
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderMenu, validateSelection, parseSelection, formatQuestionWithOptions, } from './interactiveMenu.js';
|
|
3
|
+
describe('interactiveMenu', () => {
|
|
4
|
+
describe('formatQuestionWithOptions', () => {
|
|
5
|
+
it('should format additive question with lettered options', () => {
|
|
6
|
+
const question = {
|
|
7
|
+
id: 'product-1',
|
|
8
|
+
text: 'What are the key features?',
|
|
9
|
+
type: 'additive',
|
|
10
|
+
section: 'product',
|
|
11
|
+
options: [
|
|
12
|
+
'User authentication',
|
|
13
|
+
'Real-time updates',
|
|
14
|
+
'Data analytics',
|
|
15
|
+
'Enter custom features',
|
|
16
|
+
'Auto-generate from codebase',
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
const result = formatQuestionWithOptions(question, 1);
|
|
20
|
+
expect(result).toContain('Question 1:');
|
|
21
|
+
expect(result).toContain('What are the key features? (Select all that apply)');
|
|
22
|
+
expect(result).toContain('A) User authentication');
|
|
23
|
+
expect(result).toContain('B) Real-time updates');
|
|
24
|
+
expect(result).toContain('C) Data analytics');
|
|
25
|
+
expect(result).toContain('D) Enter custom features');
|
|
26
|
+
expect(result).toContain('E) Auto-generate from codebase');
|
|
27
|
+
});
|
|
28
|
+
it('should format exclusive question without suffix', () => {
|
|
29
|
+
const question = {
|
|
30
|
+
id: 'product-2',
|
|
31
|
+
text: 'What is the primary platform?',
|
|
32
|
+
type: 'exclusive',
|
|
33
|
+
section: 'product',
|
|
34
|
+
options: ['Web', 'Mobile', 'Desktop', 'Custom', 'Auto-generate'],
|
|
35
|
+
};
|
|
36
|
+
const result = formatQuestionWithOptions(question, 2);
|
|
37
|
+
expect(result).toContain('Question 2:');
|
|
38
|
+
expect(result).toContain('What is the primary platform?');
|
|
39
|
+
expect(result).not.toContain('(Select all that apply)');
|
|
40
|
+
expect(result).toContain('A) Web');
|
|
41
|
+
expect(result).toContain('B) Mobile');
|
|
42
|
+
});
|
|
43
|
+
it('should truncate long options to 80 characters', () => {
|
|
44
|
+
const question = {
|
|
45
|
+
id: 'test-1',
|
|
46
|
+
text: 'Test question',
|
|
47
|
+
type: 'exclusive',
|
|
48
|
+
section: 'product',
|
|
49
|
+
options: [
|
|
50
|
+
'This is a very long option text that exceeds the eighty character limit and should be truncated with ellipsis',
|
|
51
|
+
'Short',
|
|
52
|
+
'Custom',
|
|
53
|
+
'Auto-generate',
|
|
54
|
+
'Another option',
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
const result = formatQuestionWithOptions(question, 1);
|
|
58
|
+
expect(result).toContain('A) This is a very long option text that exceeds the eighty character limit and...');
|
|
59
|
+
expect(result).toContain('B) Short');
|
|
60
|
+
});
|
|
61
|
+
it('should number questions starting from provided index', () => {
|
|
62
|
+
const question = {
|
|
63
|
+
id: 'test-1',
|
|
64
|
+
text: 'Test question',
|
|
65
|
+
type: 'exclusive',
|
|
66
|
+
section: 'product',
|
|
67
|
+
options: ['A', 'B', 'C', 'D', 'E'],
|
|
68
|
+
};
|
|
69
|
+
const result1 = formatQuestionWithOptions(question, 1);
|
|
70
|
+
const result5 = formatQuestionWithOptions(question, 5);
|
|
71
|
+
expect(result1).toContain('Question 1:');
|
|
72
|
+
expect(result5).toContain('Question 5:');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('validateSelection', () => {
|
|
76
|
+
it('should accept valid single letter for exclusive choice', () => {
|
|
77
|
+
expect(validateSelection('A', 'exclusive')).toBe(true);
|
|
78
|
+
expect(validateSelection('B', 'exclusive')).toBe(true);
|
|
79
|
+
expect(validateSelection('C', 'exclusive')).toBe(true);
|
|
80
|
+
expect(validateSelection('D', 'exclusive')).toBe(true);
|
|
81
|
+
expect(validateSelection('E', 'exclusive')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('should accept lowercase letters for exclusive choice', () => {
|
|
84
|
+
expect(validateSelection('a', 'exclusive')).toBe(true);
|
|
85
|
+
expect(validateSelection('e', 'exclusive')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it('should reject invalid letters for exclusive choice', () => {
|
|
88
|
+
expect(validateSelection('F', 'exclusive')).toBe(false);
|
|
89
|
+
expect(validateSelection('Z', 'exclusive')).toBe(false);
|
|
90
|
+
expect(validateSelection('1', 'exclusive')).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
it('should reject multiple selections for exclusive choice', () => {
|
|
93
|
+
expect(validateSelection('A,B', 'exclusive')).toBe(false);
|
|
94
|
+
expect(validateSelection('A,B,C', 'exclusive')).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
it('should accept valid single letter for additive choice', () => {
|
|
97
|
+
expect(validateSelection('A', 'additive')).toBe(true);
|
|
98
|
+
expect(validateSelection('E', 'additive')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it('should accept multiple comma-separated letters for additive choice', () => {
|
|
101
|
+
expect(validateSelection('A,B', 'additive')).toBe(true);
|
|
102
|
+
expect(validateSelection('A,B,C', 'additive')).toBe(true);
|
|
103
|
+
expect(validateSelection('A,C,D', 'additive')).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
it('should accept multiple letters with spaces for additive choice', () => {
|
|
106
|
+
expect(validateSelection('A, B', 'additive')).toBe(true);
|
|
107
|
+
expect(validateSelection('A, B, C', 'additive')).toBe(true);
|
|
108
|
+
expect(validateSelection(' A , B , C ', 'additive')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
it('should reject if any letter is invalid in additive choice', () => {
|
|
111
|
+
expect(validateSelection('A,F', 'additive')).toBe(false);
|
|
112
|
+
expect(validateSelection('A,B,Z', 'additive')).toBe(false);
|
|
113
|
+
expect(validateSelection('A,1', 'additive')).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
it('should reject duplicate selections in additive choice', () => {
|
|
116
|
+
expect(validateSelection('A,A', 'additive')).toBe(false);
|
|
117
|
+
expect(validateSelection('A,B,A', 'additive')).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
it('should reject empty input', () => {
|
|
120
|
+
expect(validateSelection('', 'exclusive')).toBe(false);
|
|
121
|
+
expect(validateSelection('', 'additive')).toBe(false);
|
|
122
|
+
expect(validateSelection(' ', 'exclusive')).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
it('should reject option E in combination with others for additive', () => {
|
|
125
|
+
expect(validateSelection('A,E', 'additive')).toBe(false);
|
|
126
|
+
expect(validateSelection('E,A', 'additive')).toBe(false);
|
|
127
|
+
expect(validateSelection('A,B,E', 'additive')).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it('should accept option E alone for additive', () => {
|
|
130
|
+
expect(validateSelection('E', 'additive')).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('parseSelection', () => {
|
|
134
|
+
it('should parse single exclusive selection', () => {
|
|
135
|
+
expect(parseSelection('A', 'exclusive')).toEqual(['A']);
|
|
136
|
+
expect(parseSelection('E', 'exclusive')).toEqual(['E']);
|
|
137
|
+
});
|
|
138
|
+
it('should normalize to uppercase', () => {
|
|
139
|
+
expect(parseSelection('a', 'exclusive')).toEqual(['A']);
|
|
140
|
+
expect(parseSelection('e', 'exclusive')).toEqual(['E']);
|
|
141
|
+
});
|
|
142
|
+
it('should parse single additive selection', () => {
|
|
143
|
+
expect(parseSelection('A', 'additive')).toEqual(['A']);
|
|
144
|
+
});
|
|
145
|
+
it('should parse multiple additive selections', () => {
|
|
146
|
+
expect(parseSelection('A,B', 'additive')).toEqual(['A', 'B']);
|
|
147
|
+
expect(parseSelection('A,B,C', 'additive')).toEqual(['A', 'B', 'C']);
|
|
148
|
+
});
|
|
149
|
+
it('should parse multiple selections with spaces', () => {
|
|
150
|
+
expect(parseSelection('A, B', 'additive')).toEqual(['A', 'B']);
|
|
151
|
+
expect(parseSelection(' A , B , C ', 'additive')).toEqual(['A', 'B', 'C']);
|
|
152
|
+
});
|
|
153
|
+
it('should parse lowercase multiple selections', () => {
|
|
154
|
+
expect(parseSelection('a,b,c', 'additive')).toEqual(['A', 'B', 'C']);
|
|
155
|
+
});
|
|
156
|
+
it('should return empty array for invalid input', () => {
|
|
157
|
+
expect(parseSelection('', 'exclusive')).toEqual([]);
|
|
158
|
+
expect(parseSelection('F', 'exclusive')).toEqual([]);
|
|
159
|
+
expect(parseSelection('A,F', 'additive')).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
it('should remove duplicates and sort for additive', () => {
|
|
162
|
+
expect(parseSelection('C,A,B', 'additive')).toEqual(['A', 'B', 'C']);
|
|
163
|
+
expect(parseSelection('B,A,C,A', 'additive')).toEqual(['A', 'B', 'C']);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('renderMenu', () => {
|
|
167
|
+
it('should render complete menu with instructions', () => {
|
|
168
|
+
const question = {
|
|
169
|
+
id: 'product-1',
|
|
170
|
+
text: 'What are the key features?',
|
|
171
|
+
type: 'additive',
|
|
172
|
+
section: 'product',
|
|
173
|
+
options: ['Auth', 'Real-time', 'Analytics', 'Custom', 'Auto-generate'],
|
|
174
|
+
};
|
|
175
|
+
const options = {
|
|
176
|
+
showInstructions: true,
|
|
177
|
+
};
|
|
178
|
+
const result = renderMenu(question, 1, options);
|
|
179
|
+
expect(result).toContain('Question 1:');
|
|
180
|
+
expect(result).toContain('What are the key features?');
|
|
181
|
+
expect(result).toContain('A) Auth');
|
|
182
|
+
expect(result).toContain('Instructions:');
|
|
183
|
+
expect(result).toContain('Enter your selection (e.g., "A,B,C" for multiple or "A" for single)');
|
|
184
|
+
});
|
|
185
|
+
it('should render menu without instructions when disabled', () => {
|
|
186
|
+
const question = {
|
|
187
|
+
id: 'product-1',
|
|
188
|
+
text: 'Test',
|
|
189
|
+
type: 'exclusive',
|
|
190
|
+
section: 'product',
|
|
191
|
+
options: ['A', 'B', 'C', 'D', 'E'],
|
|
192
|
+
};
|
|
193
|
+
const options = {
|
|
194
|
+
showInstructions: false,
|
|
195
|
+
};
|
|
196
|
+
const result = renderMenu(question, 1, options);
|
|
197
|
+
expect(result).not.toContain('Instructions:');
|
|
198
|
+
});
|
|
199
|
+
it('should include section-specific autogenerate text for option E', () => {
|
|
200
|
+
const productQuestion = {
|
|
201
|
+
id: 'product-1',
|
|
202
|
+
text: 'Test',
|
|
203
|
+
type: 'exclusive',
|
|
204
|
+
section: 'product',
|
|
205
|
+
options: ['A', 'B', 'C', 'D', 'Auto-generate product guide from codebase'],
|
|
206
|
+
};
|
|
207
|
+
const result = renderMenu(productQuestion, 1, { showInstructions: false });
|
|
208
|
+
expect(result).toContain('E) Auto-generate product guide from codebase');
|
|
209
|
+
});
|
|
210
|
+
it('should show different instructions for additive vs exclusive', () => {
|
|
211
|
+
const additiveQuestion = {
|
|
212
|
+
id: 'test-1',
|
|
213
|
+
text: 'Test additive',
|
|
214
|
+
type: 'additive',
|
|
215
|
+
section: 'product',
|
|
216
|
+
options: ['A', 'B', 'C', 'D', 'E'],
|
|
217
|
+
};
|
|
218
|
+
const exclusiveQuestion = {
|
|
219
|
+
id: 'test-2',
|
|
220
|
+
text: 'Test exclusive',
|
|
221
|
+
type: 'exclusive',
|
|
222
|
+
section: 'product',
|
|
223
|
+
options: ['A', 'B', 'C', 'D', 'E'],
|
|
224
|
+
};
|
|
225
|
+
const additiveResult = renderMenu(additiveQuestion, 1, { showInstructions: true });
|
|
226
|
+
const exclusiveResult = renderMenu(exclusiveQuestion, 1, { showInstructions: true });
|
|
227
|
+
expect(additiveResult).toContain('Select all that apply');
|
|
228
|
+
expect(exclusiveResult).not.toContain('Select all that apply');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Setup Command Handler
|
|
3
|
+
*
|
|
4
|
+
* This module provides the entry point for the interactive CDD setup experience.
|
|
5
|
+
* It replaces the static JSON-based setup prompt with a dynamic, code-driven workflow.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Auto-detects project maturity (greenfield vs brownfield)
|
|
9
|
+
* - Generates context-aware questions based on codebase analysis
|
|
10
|
+
* - Interactive menu with 5 options per question (A-C contextual, D custom, E auto-generate)
|
|
11
|
+
* - Creates all 5 CDD documents: product.md, guidelines.md, tech-stack.md, styleguides.md, workflow.md
|
|
12
|
+
* - Resume functionality via setup_state.json checkpoints
|
|
13
|
+
* - Approval/revision loops for each document
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Interactive Setup Configuration
|
|
17
|
+
*/
|
|
18
|
+
export interface InteractiveSetupConfig {
|
|
19
|
+
projectPath: string;
|
|
20
|
+
outputDir?: string;
|
|
21
|
+
maxRevisions?: number;
|
|
22
|
+
resume?: boolean;
|
|
23
|
+
language?: 'en' | 'es';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Setup Result for API consumers
|
|
27
|
+
*/
|
|
28
|
+
export interface InteractiveSetupResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
message: string;
|
|
31
|
+
documentsCreated: string[];
|
|
32
|
+
errors?: string[];
|
|
33
|
+
resumed?: boolean;
|
|
34
|
+
resumedFrom?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Main entry point for interactive setup
|
|
38
|
+
*/
|
|
39
|
+
export declare function executeInteractiveSetup(config: InteractiveSetupConfig): Promise<InteractiveSetupResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Generate setup summary message
|
|
42
|
+
*/
|
|
43
|
+
export declare function generateSetupSummary(result: InteractiveSetupResult): string;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Setup Command Handler
|
|
3
|
+
*
|
|
4
|
+
* This module provides the entry point for the interactive CDD setup experience.
|
|
5
|
+
* It replaces the static JSON-based setup prompt with a dynamic, code-driven workflow.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Auto-detects project maturity (greenfield vs brownfield)
|
|
9
|
+
* - Generates context-aware questions based on codebase analysis
|
|
10
|
+
* - Interactive menu with 5 options per question (A-C contextual, D custom, E auto-generate)
|
|
11
|
+
* - Creates all 5 CDD documents: product.md, guidelines.md, tech-stack.md, styleguides.md, workflow.md
|
|
12
|
+
* - Resume functionality via setup_state.json checkpoints
|
|
13
|
+
* - Approval/revision loops for each document
|
|
14
|
+
*/
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { runFullSetup } from './setupIntegration.js';
|
|
17
|
+
/**
|
|
18
|
+
* Default responder for interactive CLI
|
|
19
|
+
* This is a placeholder - the actual implementation should use
|
|
20
|
+
* the OpenCode CLI input tools for interactive prompts
|
|
21
|
+
*
|
|
22
|
+
* For testing: Returns option 'E' (auto-generate) to avoid hanging
|
|
23
|
+
*/
|
|
24
|
+
async function defaultResponder(question, questionNumber) {
|
|
25
|
+
// In production, this would use OpenCode CLI input
|
|
26
|
+
// For now, auto-select option E to enable testing
|
|
27
|
+
return ['E'];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Default approval flow for document review
|
|
31
|
+
* This is a placeholder - the actual implementation should use
|
|
32
|
+
* the OpenCode CLI input tools for approval prompts
|
|
33
|
+
*/
|
|
34
|
+
async function defaultApprovalFlow(draft) {
|
|
35
|
+
// TODO: Replace with actual OpenCode CLI approval prompt
|
|
36
|
+
console.log('\n[Document Draft Generated]');
|
|
37
|
+
console.log(`Word count: ${draft.wordCount}`);
|
|
38
|
+
console.log(`Sections included: ${draft.sectionsIncluded.length}`);
|
|
39
|
+
// For now, auto-approve
|
|
40
|
+
// This will be replaced when integrated with OpenCode CLI
|
|
41
|
+
return {
|
|
42
|
+
approved: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Main entry point for interactive setup
|
|
47
|
+
*/
|
|
48
|
+
export async function executeInteractiveSetup(config) {
|
|
49
|
+
try {
|
|
50
|
+
const outputDir = config.outputDir || path.join(config.projectPath, 'conductor-cdd');
|
|
51
|
+
const setupOptions = {
|
|
52
|
+
projectPath: config.projectPath,
|
|
53
|
+
outputDir,
|
|
54
|
+
responder: defaultResponder,
|
|
55
|
+
approvalFlow: defaultApprovalFlow,
|
|
56
|
+
maxRevisions: config.maxRevisions || 3,
|
|
57
|
+
resume: config.resume || false,
|
|
58
|
+
};
|
|
59
|
+
const result = await runFullSetup(setupOptions);
|
|
60
|
+
if (result.success) {
|
|
61
|
+
return {
|
|
62
|
+
success: true,
|
|
63
|
+
message: `Successfully created ${result.documentsCreated.length} CDD documents`,
|
|
64
|
+
documentsCreated: result.documentsCreated,
|
|
65
|
+
resumed: result.resumed,
|
|
66
|
+
resumedFrom: result.resumedFrom,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
message: result.error || 'Setup failed',
|
|
73
|
+
documentsCreated: result.documentsCreated || [],
|
|
74
|
+
errors: result.error ? [result.error] : [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
message: 'Interactive setup failed with an unexpected error',
|
|
82
|
+
documentsCreated: [],
|
|
83
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Generate setup summary message
|
|
89
|
+
*/
|
|
90
|
+
export function generateSetupSummary(result) {
|
|
91
|
+
const lines = [];
|
|
92
|
+
if (result.success) {
|
|
93
|
+
lines.push('✅ CDD Setup Complete!');
|
|
94
|
+
lines.push('');
|
|
95
|
+
if (result.resumed) {
|
|
96
|
+
lines.push(`📋 Resumed from checkpoint: ${result.resumedFrom}`);
|
|
97
|
+
lines.push('');
|
|
98
|
+
}
|
|
99
|
+
lines.push('📄 Documents created:');
|
|
100
|
+
result.documentsCreated.forEach(doc => {
|
|
101
|
+
lines.push(` • ${path.basename(doc)}`);
|
|
102
|
+
});
|
|
103
|
+
lines.push('');
|
|
104
|
+
lines.push('🎯 Next steps:');
|
|
105
|
+
lines.push(' 1. Review the generated documents in conductor-cdd/');
|
|
106
|
+
lines.push(' 2. Customize them to match your project needs');
|
|
107
|
+
lines.push(' 3. Start your first track with: cdd:new-track');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
lines.push('❌ CDD Setup Failed');
|
|
111
|
+
lines.push('');
|
|
112
|
+
lines.push(`Error: ${result.message}`);
|
|
113
|
+
if (result.documentsCreated.length > 0) {
|
|
114
|
+
lines.push('');
|
|
115
|
+
lines.push('⚠️ Partially completed documents:');
|
|
116
|
+
result.documentsCreated.forEach(doc => {
|
|
117
|
+
lines.push(` • ${path.basename(doc)}`);
|
|
118
|
+
});
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push('💡 You can resume setup by running cdd:setup again');
|
|
121
|
+
}
|
|
122
|
+
if (result.errors && result.errors.length > 0) {
|
|
123
|
+
lines.push('');
|
|
124
|
+
lines.push('🔍 Error details:');
|
|
125
|
+
result.errors.forEach(err => {
|
|
126
|
+
lines.push(` • ${err}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|