opencode-conductor-cdd-plugin 1.0.0-beta.18 → 1.0.0-beta.20

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 (37) hide show
  1. package/README.md +19 -3
  2. package/dist/prompts/cdd/setup.json +2 -2
  3. package/dist/prompts/cdd/setup.test.js +40 -118
  4. package/dist/prompts/cdd/setup.test.ts +40 -143
  5. package/dist/utils/codebaseAnalysis.d.ts +61 -0
  6. package/dist/utils/codebaseAnalysis.js +429 -0
  7. package/dist/utils/codebaseAnalysis.test.d.ts +1 -0
  8. package/dist/utils/codebaseAnalysis.test.js +556 -0
  9. package/dist/utils/configDetection.d.ts +12 -0
  10. package/dist/utils/configDetection.js +23 -9
  11. package/dist/utils/configDetection.test.js +204 -7
  12. package/dist/utils/documentGeneration.d.ts +97 -0
  13. package/dist/utils/documentGeneration.js +301 -0
  14. package/dist/utils/documentGeneration.test.d.ts +1 -0
  15. package/dist/utils/documentGeneration.test.js +380 -0
  16. package/dist/utils/interactiveMenu.d.ts +56 -0
  17. package/dist/utils/interactiveMenu.js +144 -0
  18. package/dist/utils/interactiveMenu.test.d.ts +1 -0
  19. package/dist/utils/interactiveMenu.test.js +231 -0
  20. package/dist/utils/interactiveSetup.d.ts +43 -0
  21. package/dist/utils/interactiveSetup.js +131 -0
  22. package/dist/utils/interactiveSetup.test.d.ts +1 -0
  23. package/dist/utils/interactiveSetup.test.js +124 -0
  24. package/dist/utils/projectMaturity.d.ts +53 -0
  25. package/dist/utils/projectMaturity.js +179 -0
  26. package/dist/utils/projectMaturity.test.d.ts +1 -0
  27. package/dist/utils/projectMaturity.test.js +298 -0
  28. package/dist/utils/questionGenerator.d.ts +51 -0
  29. package/dist/utils/questionGenerator.js +535 -0
  30. package/dist/utils/questionGenerator.test.d.ts +1 -0
  31. package/dist/utils/questionGenerator.test.js +328 -0
  32. package/dist/utils/setupIntegration.d.ts +72 -0
  33. package/dist/utils/setupIntegration.js +179 -0
  34. package/dist/utils/setupIntegration.test.d.ts +1 -0
  35. package/dist/utils/setupIntegration.test.js +344 -0
  36. package/dist/utils/synergyState.test.js +17 -3
  37. package/package.json +2 -1
@@ -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 {};
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import { executeInteractiveSetup, generateSetupSummary, } from './interactiveSetup.js';
6
+ describe('interactiveSetup', () => {
7
+ let testOutputDir;
8
+ let testProjectDir;
9
+ beforeEach(() => {
10
+ testOutputDir = path.join(os.tmpdir(), 'cdd-interactive-setup-test');
11
+ testProjectDir = path.join(testOutputDir, 'test-project');
12
+ if (fs.existsSync(testOutputDir)) {
13
+ fs.rmSync(testOutputDir, { recursive: true, force: true });
14
+ }
15
+ fs.mkdirSync(testProjectDir, { recursive: true });
16
+ });
17
+ afterEach(() => {
18
+ if (fs.existsSync(testOutputDir)) {
19
+ fs.rmSync(testOutputDir, { recursive: true, force: true });
20
+ }
21
+ });
22
+ describe('executeInteractiveSetup', () => {
23
+ it('should return success result when setup completes', async () => {
24
+ // Mock runFullSetup to return success
25
+ const { executeInteractiveSetup } = await import('./interactiveSetup.js');
26
+ const config = {
27
+ projectPath: testProjectDir,
28
+ };
29
+ const result = await executeInteractiveSetup(config);
30
+ // Note: This will use default responder which returns empty arrays
31
+ // In actual implementation, we need to mock the responder properly
32
+ expect(result).toHaveProperty('success');
33
+ expect(result).toHaveProperty('message');
34
+ expect(result).toHaveProperty('documentsCreated');
35
+ });
36
+ it('should use custom outputDir when provided', async () => {
37
+ const customOutputDir = path.join(testProjectDir, 'custom-cdd');
38
+ const config = {
39
+ projectPath: testProjectDir,
40
+ outputDir: customOutputDir,
41
+ };
42
+ await executeInteractiveSetup(config);
43
+ // Verify outputDir was used (will be created by setupIntegration)
44
+ // This is just a smoke test - full integration tested in setupIntegration.test.ts
45
+ expect(true).toBe(true);
46
+ });
47
+ it('should handle resume flag', async () => {
48
+ const config = {
49
+ projectPath: testProjectDir,
50
+ resume: true,
51
+ };
52
+ const result = await executeInteractiveSetup(config);
53
+ expect(result).toHaveProperty('resumed');
54
+ });
55
+ it('should handle errors gracefully', async () => {
56
+ // Pass invalid projectPath to trigger error
57
+ const config = {
58
+ projectPath: '/nonexistent/path/that/does/not/exist',
59
+ };
60
+ const result = await executeInteractiveSetup(config);
61
+ expect(result.success).toBe(false);
62
+ expect(result.message).toBeTruthy();
63
+ expect(result.errors).toBeDefined();
64
+ });
65
+ });
66
+ describe('generateSetupSummary', () => {
67
+ it('should generate success summary with documents', () => {
68
+ const result = {
69
+ success: true,
70
+ message: 'Setup complete',
71
+ documentsCreated: [
72
+ '/path/to/product.md',
73
+ '/path/to/guidelines.md',
74
+ '/path/to/tech-stack.md',
75
+ ],
76
+ };
77
+ const summary = generateSetupSummary(result);
78
+ expect(summary).toContain('✅ CDD Setup Complete!');
79
+ expect(summary).toContain('product.md');
80
+ expect(summary).toContain('guidelines.md');
81
+ expect(summary).toContain('tech-stack.md');
82
+ expect(summary).toContain('Next steps:');
83
+ });
84
+ it('should generate success summary with resume info', () => {
85
+ const result = {
86
+ success: true,
87
+ message: 'Setup complete',
88
+ documentsCreated: ['/path/to/guidelines.md'],
89
+ resumed: true,
90
+ resumedFrom: '2.1_product_guide',
91
+ };
92
+ const summary = generateSetupSummary(result);
93
+ expect(summary).toContain('Resumed from checkpoint');
94
+ expect(summary).toContain('2.1_product_guide');
95
+ });
96
+ it('should generate failure summary with partial completion', () => {
97
+ const result = {
98
+ success: false,
99
+ message: 'Setup failed at guidelines',
100
+ documentsCreated: ['/path/to/product.md'],
101
+ errors: ['Approval rejected 3 times'],
102
+ };
103
+ const summary = generateSetupSummary(result);
104
+ expect(summary).toContain('❌ CDD Setup Failed');
105
+ expect(summary).toContain('Setup failed at guidelines');
106
+ expect(summary).toContain('Partially completed');
107
+ expect(summary).toContain('product.md');
108
+ expect(summary).toContain('resume setup');
109
+ expect(summary).toContain('Approval rejected 3 times');
110
+ });
111
+ it('should generate failure summary with no documents', () => {
112
+ const result = {
113
+ success: false,
114
+ message: 'Project path does not exist',
115
+ documentsCreated: [],
116
+ errors: ['ENOENT: no such file or directory'],
117
+ };
118
+ const summary = generateSetupSummary(result);
119
+ expect(summary).toContain('❌ CDD Setup Failed');
120
+ expect(summary).toContain('Project path does not exist');
121
+ expect(summary).toContain('ENOENT');
122
+ });
123
+ });
124
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Project Maturity Detection Module
3
+ *
4
+ * Determines whether a project is brownfield (existing) or greenfield (new).
5
+ *
6
+ * Based on reference implementations:
7
+ * - derekbar90/opencode-conductor
8
+ * - gemini-cli-extensions/conductor
9
+ */
10
+ export type ProjectMaturity = 'brownfield' | 'greenfield';
11
+ /**
12
+ * Detect project maturity (brownfield vs greenfield)
13
+ *
14
+ * A project is considered BROWNFIELD if ANY of:
15
+ * - Version control directory exists (.git, .svn, .hg)
16
+ * - Git repository is dirty (uncommitted changes)
17
+ * - Dependency manifests exist
18
+ * - Source directories contain code files
19
+ *
20
+ * A project is considered GREENFIELD only if NONE of the above are true.
21
+ *
22
+ * @param projectPath - Absolute path to project directory
23
+ * @returns 'brownfield' or 'greenfield'
24
+ */
25
+ export declare function detectProjectMaturity(projectPath: string): ProjectMaturity;
26
+ /**
27
+ * Check if git repository has uncommitted changes
28
+ *
29
+ * @param projectPath - Absolute path to project directory
30
+ * @returns true if repository is dirty, false otherwise
31
+ */
32
+ export declare function checkGitStatus(projectPath: string): boolean;
33
+ /**
34
+ * Check for dependency manifest files
35
+ *
36
+ * @param projectPath - Absolute path to project directory
37
+ * @returns Array of found manifest file names
38
+ */
39
+ export declare function checkManifestFiles(projectPath: string): string[];
40
+ /**
41
+ * Check for source directories containing code files
42
+ *
43
+ * @param projectPath - Absolute path to project directory
44
+ * @returns Array of found source directory names
45
+ */
46
+ export declare function checkSourceDirectories(projectPath: string): string[];
47
+ /**
48
+ * Check for version control system directories
49
+ *
50
+ * @param projectPath - Absolute path to project directory
51
+ * @returns Name of VCS directory found, or null if none found
52
+ */
53
+ export declare function checkVersionControl(projectPath: string): string | null;