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,179 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { execSync } from 'child_process';
4
+ /**
5
+ * Manifest files that indicate a brownfield project
6
+ */
7
+ const MANIFEST_FILES = [
8
+ 'package.json',
9
+ 'pom.xml',
10
+ 'requirements.txt',
11
+ 'go.mod',
12
+ 'Cargo.toml',
13
+ 'Gemfile',
14
+ ];
15
+ /**
16
+ * Source directory names to check for code
17
+ */
18
+ const SOURCE_DIRECTORIES = ['src', 'app', 'lib'];
19
+ /**
20
+ * Code file extensions to detect
21
+ */
22
+ const CODE_EXTENSIONS = ['.ts', '.js', '.py', '.go', '.rs', '.rb', '.java', '.kt', '.swift'];
23
+ /**
24
+ * Version control system directories
25
+ */
26
+ const VCS_DIRECTORIES = ['.git', '.svn', '.hg'];
27
+ /**
28
+ * Detect project maturity (brownfield vs greenfield)
29
+ *
30
+ * A project is considered BROWNFIELD if ANY of:
31
+ * - Version control directory exists (.git, .svn, .hg)
32
+ * - Git repository is dirty (uncommitted changes)
33
+ * - Dependency manifests exist
34
+ * - Source directories contain code files
35
+ *
36
+ * A project is considered GREENFIELD only if NONE of the above are true.
37
+ *
38
+ * @param projectPath - Absolute path to project directory
39
+ * @returns 'brownfield' or 'greenfield'
40
+ */
41
+ export function detectProjectMaturity(projectPath) {
42
+ // Check version control
43
+ const vcs = checkVersionControl(projectPath);
44
+ if (vcs) {
45
+ return 'brownfield';
46
+ }
47
+ // Check git status (dirty repository)
48
+ const isDirty = checkGitStatus(projectPath);
49
+ if (isDirty) {
50
+ return 'brownfield';
51
+ }
52
+ // Check for dependency manifests
53
+ const manifests = checkManifestFiles(projectPath);
54
+ if (manifests.length > 0) {
55
+ return 'brownfield';
56
+ }
57
+ // Check for source directories with code
58
+ const sourceDirs = checkSourceDirectories(projectPath);
59
+ if (sourceDirs.length > 0) {
60
+ return 'brownfield';
61
+ }
62
+ // No brownfield indicators found
63
+ return 'greenfield';
64
+ }
65
+ /**
66
+ * Check if git repository has uncommitted changes
67
+ *
68
+ * @param projectPath - Absolute path to project directory
69
+ * @returns true if repository is dirty, false otherwise
70
+ */
71
+ export function checkGitStatus(projectPath) {
72
+ try {
73
+ const gitDir = path.join(projectPath, '.git');
74
+ if (!fs.existsSync(gitDir)) {
75
+ return false;
76
+ }
77
+ const output = execSync('git status --porcelain', {
78
+ cwd: projectPath,
79
+ encoding: 'utf-8',
80
+ stdio: ['pipe', 'pipe', 'ignore'], // Suppress stderr
81
+ });
82
+ return output.trim().length > 0;
83
+ }
84
+ catch (error) {
85
+ // Git command failed or not a git repository
86
+ return false;
87
+ }
88
+ }
89
+ /**
90
+ * Check for dependency manifest files
91
+ *
92
+ * @param projectPath - Absolute path to project directory
93
+ * @returns Array of found manifest file names
94
+ */
95
+ export function checkManifestFiles(projectPath) {
96
+ const found = [];
97
+ for (const manifest of MANIFEST_FILES) {
98
+ const manifestPath = path.join(projectPath, manifest);
99
+ if (fs.existsSync(manifestPath)) {
100
+ found.push(manifest);
101
+ }
102
+ }
103
+ return found;
104
+ }
105
+ /**
106
+ * Check for source directories containing code files
107
+ *
108
+ * @param projectPath - Absolute path to project directory
109
+ * @returns Array of found source directory names
110
+ */
111
+ export function checkSourceDirectories(projectPath) {
112
+ const found = [];
113
+ for (const dirName of SOURCE_DIRECTORIES) {
114
+ const dirPath = path.join(projectPath, dirName);
115
+ if (!fs.existsSync(dirPath)) {
116
+ continue;
117
+ }
118
+ const stat = fs.statSync(dirPath);
119
+ if (!stat.isDirectory()) {
120
+ continue;
121
+ }
122
+ // Check if directory contains code files
123
+ if (containsCodeFiles(dirPath)) {
124
+ found.push(dirName);
125
+ }
126
+ }
127
+ return found;
128
+ }
129
+ /**
130
+ * Check if directory contains code files (recursive, depth-limited)
131
+ *
132
+ * @param dirPath - Absolute path to directory
133
+ * @param maxDepth - Maximum recursion depth (default: 3)
134
+ * @param currentDepth - Current recursion depth
135
+ * @returns true if code files found
136
+ */
137
+ function containsCodeFiles(dirPath, maxDepth = 3, currentDepth = 0) {
138
+ if (currentDepth > maxDepth) {
139
+ return false;
140
+ }
141
+ try {
142
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
143
+ for (const entry of entries) {
144
+ const entryPath = path.join(dirPath, entry.name);
145
+ if (entry.isFile()) {
146
+ const ext = path.extname(entry.name);
147
+ if (CODE_EXTENSIONS.includes(ext)) {
148
+ return true;
149
+ }
150
+ }
151
+ else if (entry.isDirectory()) {
152
+ // Recursively check subdirectories
153
+ if (containsCodeFiles(entryPath, maxDepth, currentDepth + 1)) {
154
+ return true;
155
+ }
156
+ }
157
+ }
158
+ return false;
159
+ }
160
+ catch (error) {
161
+ // Permission error or other issue
162
+ return false;
163
+ }
164
+ }
165
+ /**
166
+ * Check for version control system directories
167
+ *
168
+ * @param projectPath - Absolute path to project directory
169
+ * @returns Name of VCS directory found, or null if none found
170
+ */
171
+ export function checkVersionControl(projectPath) {
172
+ for (const vcs of VCS_DIRECTORIES) {
173
+ const vcsPath = path.join(projectPath, vcs);
174
+ if (fs.existsSync(vcsPath)) {
175
+ return vcs;
176
+ }
177
+ }
178
+ return null;
179
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,298 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import { execSync } from 'child_process';
4
+ import { detectProjectMaturity, checkGitStatus, checkManifestFiles, checkSourceDirectories, checkVersionControl, } from './projectMaturity.js';
5
+ /**
6
+ * Project Maturity Detection Module Tests
7
+ *
8
+ * This module is responsible for determining whether a project is:
9
+ * - Brownfield (existing project with code/configuration)
10
+ * - Greenfield (new/empty project)
11
+ *
12
+ * Based on reference implementations from:
13
+ * - derekbar90/opencode-conductor
14
+ * - gemini-cli-extensions/conductor
15
+ */
16
+ // Mock file system and child_process
17
+ vi.mock('fs');
18
+ vi.mock('child_process');
19
+ describe('Project Maturity Detection', () => {
20
+ beforeEach(() => {
21
+ vi.clearAllMocks();
22
+ });
23
+ describe('detectProjectMaturity', () => {
24
+ it('should detect brownfield project with .git directory', () => {
25
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
26
+ return p.toString().endsWith('.git');
27
+ });
28
+ const result = detectProjectMaturity('/test/project');
29
+ expect(result).toBe('brownfield');
30
+ });
31
+ it('should detect brownfield project with dirty git repository', () => {
32
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
33
+ return p.toString().endsWith('.git');
34
+ });
35
+ vi.mocked(execSync).mockReturnValue('M file.txt\n');
36
+ const result = detectProjectMaturity('/test/project');
37
+ expect(result).toBe('brownfield');
38
+ });
39
+ it('should detect brownfield project with package.json manifest', () => {
40
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
41
+ return p.toString().endsWith('package.json');
42
+ });
43
+ const result = detectProjectMaturity('/test/project');
44
+ expect(result).toBe('brownfield');
45
+ });
46
+ it('should detect brownfield project with pom.xml manifest', () => {
47
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
48
+ return p.toString().endsWith('pom.xml');
49
+ });
50
+ const result = detectProjectMaturity('/test/project');
51
+ expect(result).toBe('brownfield');
52
+ });
53
+ it('should detect brownfield project with requirements.txt manifest', () => {
54
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
55
+ return p.toString().endsWith('requirements.txt');
56
+ });
57
+ const result = detectProjectMaturity('/test/project');
58
+ expect(result).toBe('brownfield');
59
+ });
60
+ it('should detect brownfield project with go.mod manifest', () => {
61
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
62
+ return p.toString().endsWith('go.mod');
63
+ });
64
+ const result = detectProjectMaturity('/test/project');
65
+ expect(result).toBe('brownfield');
66
+ });
67
+ it('should detect brownfield project with src/ directory containing code', () => {
68
+ vi.mocked(fs.existsSync).mockReturnValue(true);
69
+ vi.mocked(fs.statSync).mockReturnValue({
70
+ isDirectory: () => true,
71
+ });
72
+ vi.mocked(fs.readdirSync).mockReturnValue([
73
+ { name: 'index.ts', isFile: () => true, isDirectory: () => false },
74
+ ]);
75
+ const result = detectProjectMaturity('/test/project');
76
+ expect(result).toBe('brownfield');
77
+ });
78
+ it('should detect brownfield project with app/ directory containing code', () => {
79
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
80
+ return p.toString().includes('/app');
81
+ });
82
+ vi.mocked(fs.statSync).mockReturnValue({
83
+ isDirectory: () => true,
84
+ });
85
+ vi.mocked(fs.readdirSync).mockReturnValue([
86
+ { name: 'main.py', isFile: () => true, isDirectory: () => false },
87
+ ]);
88
+ const result = detectProjectMaturity('/test/project');
89
+ expect(result).toBe('brownfield');
90
+ });
91
+ it('should detect brownfield project with lib/ directory containing code', () => {
92
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
93
+ return p.toString().includes('/lib');
94
+ });
95
+ vi.mocked(fs.statSync).mockReturnValue({
96
+ isDirectory: () => true,
97
+ });
98
+ vi.mocked(fs.readdirSync).mockReturnValue([
99
+ { name: 'utils.js', isFile: () => true, isDirectory: () => false },
100
+ ]);
101
+ const result = detectProjectMaturity('/test/project');
102
+ expect(result).toBe('brownfield');
103
+ });
104
+ it('should detect brownfield project with .svn directory', () => {
105
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
106
+ return p.toString().endsWith('.svn');
107
+ });
108
+ const result = detectProjectMaturity('/test/project');
109
+ expect(result).toBe('brownfield');
110
+ });
111
+ it('should detect brownfield project with .hg directory', () => {
112
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
113
+ return p.toString().endsWith('.hg');
114
+ });
115
+ const result = detectProjectMaturity('/test/project');
116
+ expect(result).toBe('brownfield');
117
+ });
118
+ it('should detect greenfield project when directory is empty', () => {
119
+ vi.mocked(fs.existsSync).mockReturnValue(false);
120
+ const result = detectProjectMaturity('/test/project');
121
+ expect(result).toBe('greenfield');
122
+ });
123
+ it('should detect greenfield project with only README.md', () => {
124
+ vi.mocked(fs.existsSync).mockReturnValue(false);
125
+ const result = detectProjectMaturity('/test/project');
126
+ expect(result).toBe('greenfield');
127
+ });
128
+ it('should detect greenfield when no brownfield indicators present', () => {
129
+ vi.mocked(fs.existsSync).mockReturnValue(false);
130
+ const result = detectProjectMaturity('/test/project');
131
+ expect(result).toBe('greenfield');
132
+ });
133
+ it('should prioritize brownfield if ANY indicator is present', () => {
134
+ // Only .git exists, nothing else
135
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
136
+ return p.toString().endsWith('.git');
137
+ });
138
+ const result = detectProjectMaturity('/test/project');
139
+ expect(result).toBe('brownfield');
140
+ });
141
+ });
142
+ describe('checkGitStatus', () => {
143
+ it('should return true for dirty repository', () => {
144
+ vi.mocked(fs.existsSync).mockReturnValue(true);
145
+ vi.mocked(execSync).mockReturnValue('M file.txt\n');
146
+ const result = checkGitStatus('/test/project');
147
+ expect(result).toBe(true);
148
+ });
149
+ it('should return false for clean repository', () => {
150
+ vi.mocked(fs.existsSync).mockReturnValue(true);
151
+ vi.mocked(execSync).mockReturnValue('');
152
+ const result = checkGitStatus('/test/project');
153
+ expect(result).toBe(false);
154
+ });
155
+ it('should handle missing .git directory gracefully', () => {
156
+ vi.mocked(fs.existsSync).mockReturnValue(false);
157
+ const result = checkGitStatus('/test/project');
158
+ expect(result).toBe(false);
159
+ });
160
+ });
161
+ describe('checkManifestFiles', () => {
162
+ it('should detect package.json', () => {
163
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
164
+ return p.toString().endsWith('package.json');
165
+ });
166
+ const result = checkManifestFiles('/test/project');
167
+ expect(result).toContain('package.json');
168
+ });
169
+ it('should detect pom.xml', () => {
170
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
171
+ return p.toString().endsWith('pom.xml');
172
+ });
173
+ const result = checkManifestFiles('/test/project');
174
+ expect(result).toContain('pom.xml');
175
+ });
176
+ it('should detect requirements.txt', () => {
177
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
178
+ return p.toString().endsWith('requirements.txt');
179
+ });
180
+ const result = checkManifestFiles('/test/project');
181
+ expect(result).toContain('requirements.txt');
182
+ });
183
+ it('should detect go.mod', () => {
184
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
185
+ return p.toString().endsWith('go.mod');
186
+ });
187
+ const result = checkManifestFiles('/test/project');
188
+ expect(result).toContain('go.mod');
189
+ });
190
+ it('should detect Cargo.toml', () => {
191
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
192
+ return p.toString().endsWith('Cargo.toml');
193
+ });
194
+ const result = checkManifestFiles('/test/project');
195
+ expect(result).toContain('Cargo.toml');
196
+ });
197
+ it('should detect Gemfile', () => {
198
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
199
+ return p.toString().endsWith('Gemfile');
200
+ });
201
+ const result = checkManifestFiles('/test/project');
202
+ expect(result).toContain('Gemfile');
203
+ });
204
+ it('should return empty array when no manifests found', () => {
205
+ vi.mocked(fs.existsSync).mockReturnValue(false);
206
+ const result = checkManifestFiles('/test/project');
207
+ expect(result).toEqual([]);
208
+ });
209
+ });
210
+ describe('checkSourceDirectories', () => {
211
+ it('should detect src/ directory with .ts files', () => {
212
+ vi.mocked(fs.existsSync).mockReturnValue(true);
213
+ vi.mocked(fs.statSync).mockReturnValue({
214
+ isDirectory: () => true,
215
+ });
216
+ vi.mocked(fs.readdirSync).mockReturnValue([
217
+ { name: 'index.ts', isFile: () => true, isDirectory: () => false },
218
+ ]);
219
+ const result = checkSourceDirectories('/test/project');
220
+ expect(result).toContain('src');
221
+ });
222
+ it('should detect app/ directory with .py files', () => {
223
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
224
+ return p.toString().includes('/app');
225
+ });
226
+ vi.mocked(fs.statSync).mockReturnValue({
227
+ isDirectory: () => true,
228
+ });
229
+ vi.mocked(fs.readdirSync).mockReturnValue([
230
+ { name: 'main.py', isFile: () => true, isDirectory: () => false },
231
+ ]);
232
+ const result = checkSourceDirectories('/test/project');
233
+ expect(result).toContain('app');
234
+ });
235
+ it('should detect lib/ directory with .js files', () => {
236
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
237
+ return p.toString().includes('/lib');
238
+ });
239
+ vi.mocked(fs.statSync).mockReturnValue({
240
+ isDirectory: () => true,
241
+ });
242
+ vi.mocked(fs.readdirSync).mockReturnValue([
243
+ { name: 'utils.js', isFile: () => true, isDirectory: () => false },
244
+ ]);
245
+ const result = checkSourceDirectories('/test/project');
246
+ expect(result).toContain('lib');
247
+ });
248
+ it('should ignore empty source directories', () => {
249
+ vi.mocked(fs.existsSync).mockReturnValue(true);
250
+ vi.mocked(fs.statSync).mockReturnValue({
251
+ isDirectory: () => true,
252
+ });
253
+ vi.mocked(fs.readdirSync).mockReturnValue([]);
254
+ const result = checkSourceDirectories('/test/project');
255
+ expect(result).toEqual([]);
256
+ });
257
+ it('should ignore directories with only non-code files', () => {
258
+ vi.mocked(fs.existsSync).mockReturnValue(true);
259
+ vi.mocked(fs.statSync).mockReturnValue({
260
+ isDirectory: () => true,
261
+ });
262
+ vi.mocked(fs.readdirSync).mockReturnValue([
263
+ { name: 'README.md', isFile: () => true, isDirectory: () => false },
264
+ { name: 'notes.txt', isFile: () => true, isDirectory: () => false },
265
+ ]);
266
+ const result = checkSourceDirectories('/test/project');
267
+ expect(result).toEqual([]);
268
+ });
269
+ });
270
+ describe('checkVersionControl', () => {
271
+ it('should detect .git directory', () => {
272
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
273
+ return p.toString().endsWith('.git');
274
+ });
275
+ const result = checkVersionControl('/test/project');
276
+ expect(result).toBe('.git');
277
+ });
278
+ it('should detect .svn directory', () => {
279
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
280
+ return p.toString().endsWith('.svn');
281
+ });
282
+ const result = checkVersionControl('/test/project');
283
+ expect(result).toBe('.svn');
284
+ });
285
+ it('should detect .hg directory', () => {
286
+ vi.mocked(fs.existsSync).mockImplementation((p) => {
287
+ return p.toString().endsWith('.hg');
288
+ });
289
+ const result = checkVersionControl('/test/project');
290
+ expect(result).toBe('.hg');
291
+ });
292
+ it('should return null when no VCS found', () => {
293
+ vi.mocked(fs.existsSync).mockReturnValue(false);
294
+ const result = checkVersionControl('/test/project');
295
+ expect(result).toBeNull();
296
+ });
297
+ });
298
+ });
@@ -0,0 +1,51 @@
1
+ import { CodebaseAnalysis } from './codebaseAnalysis.js';
2
+ /**
3
+ * Question Generation Engine
4
+ *
5
+ * Generates context-aware questions for the CDD setup process:
6
+ * - Classifies questions as Additive (multiple) or Exclusive (single choice)
7
+ * - Generates brownfield questions based on codebase analysis
8
+ * - Generates greenfield questions based on common patterns
9
+ * - Creates 5 answer options (A-C: contextual, D: custom, E: autogenerate)
10
+ *
11
+ * Based on reference implementations:
12
+ * - derekbar90/opencode-conductor
13
+ * - gemini-cli-extensions/conductor
14
+ */
15
+ export type QuestionType = 'additive' | 'exclusive';
16
+ export type Section = 'product' | 'guidelines' | 'tech-stack' | 'styleguides' | 'workflow';
17
+ export interface Question {
18
+ id: string;
19
+ text: string;
20
+ type: QuestionType;
21
+ section: Section;
22
+ options: string[];
23
+ }
24
+ /**
25
+ * Classify question type based on question text
26
+ */
27
+ export declare function classifyQuestionType(questionText: string): QuestionType;
28
+ /**
29
+ * Add suffix to question text based on type
30
+ */
31
+ export declare function addQuestionSuffix(questionText: string, type: QuestionType): string;
32
+ /**
33
+ * Generate brownfield questions based on codebase analysis
34
+ */
35
+ export declare function generateBrownfieldQuestions(section: Section, analysis: CodebaseAnalysis): Question[];
36
+ /**
37
+ * Generate greenfield questions based on common patterns
38
+ */
39
+ export declare function generateGreenfieldQuestions(section: Section): Question[];
40
+ /**
41
+ * Generate answer options (always 5 options: A-C contextual, D custom, E autogenerate)
42
+ */
43
+ export declare function generateAnswerOptions(contextualOptions: string[], section: Section): string[];
44
+ /**
45
+ * Format question for display with lettered options
46
+ */
47
+ export declare function formatQuestion(question: Question, questionNumber: number): string;
48
+ /**
49
+ * Main function: Generate questions for a section
50
+ */
51
+ export declare function generateQuestionsForSection(section: Section, projectMaturity: 'brownfield' | 'greenfield', codebaseAnalysis?: CodebaseAnalysis): Question[];