opencode-conductor-cdd-plugin 1.0.0-beta.18 → 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.
Files changed (32) hide show
  1. package/dist/prompts/cdd/setup.json +2 -2
  2. package/dist/prompts/cdd/setup.test.js +40 -118
  3. package/dist/prompts/cdd/setup.test.ts +40 -143
  4. package/dist/utils/codebaseAnalysis.d.ts +61 -0
  5. package/dist/utils/codebaseAnalysis.js +429 -0
  6. package/dist/utils/codebaseAnalysis.test.d.ts +1 -0
  7. package/dist/utils/codebaseAnalysis.test.js +556 -0
  8. package/dist/utils/documentGeneration.d.ts +97 -0
  9. package/dist/utils/documentGeneration.js +301 -0
  10. package/dist/utils/documentGeneration.test.d.ts +1 -0
  11. package/dist/utils/documentGeneration.test.js +380 -0
  12. package/dist/utils/interactiveMenu.d.ts +56 -0
  13. package/dist/utils/interactiveMenu.js +144 -0
  14. package/dist/utils/interactiveMenu.test.d.ts +1 -0
  15. package/dist/utils/interactiveMenu.test.js +231 -0
  16. package/dist/utils/interactiveSetup.d.ts +43 -0
  17. package/dist/utils/interactiveSetup.js +131 -0
  18. package/dist/utils/interactiveSetup.test.d.ts +1 -0
  19. package/dist/utils/interactiveSetup.test.js +124 -0
  20. package/dist/utils/projectMaturity.d.ts +53 -0
  21. package/dist/utils/projectMaturity.js +179 -0
  22. package/dist/utils/projectMaturity.test.d.ts +1 -0
  23. package/dist/utils/projectMaturity.test.js +298 -0
  24. package/dist/utils/questionGenerator.d.ts +51 -0
  25. package/dist/utils/questionGenerator.js +535 -0
  26. package/dist/utils/questionGenerator.test.d.ts +1 -0
  27. package/dist/utils/questionGenerator.test.js +328 -0
  28. package/dist/utils/setupIntegration.d.ts +72 -0
  29. package/dist/utils/setupIntegration.js +179 -0
  30. package/dist/utils/setupIntegration.test.d.ts +1 -0
  31. package/dist/utils/setupIntegration.test.js +344 -0
  32. package/package.json +1 -1
@@ -0,0 +1,429 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { execSync } from 'child_process';
4
+ /**
5
+ * Parse ignore files with precedence: .geminiignore > .ignore > .gitignore
6
+ */
7
+ export function parseIgnoreFiles(projectPath) {
8
+ const ignoreFiles = ['.geminiignore', '.ignore', '.gitignore'];
9
+ for (const ignoreFile of ignoreFiles) {
10
+ const ignoreFilePath = path.join(projectPath, ignoreFile);
11
+ if (fs.existsSync(ignoreFilePath)) {
12
+ return parseIgnoreFile(ignoreFilePath);
13
+ }
14
+ }
15
+ return [];
16
+ }
17
+ /**
18
+ * Parse a single ignore file
19
+ */
20
+ function parseIgnoreFile(filePath) {
21
+ const content = fs.readFileSync(filePath, 'utf-8');
22
+ const lines = content.split('\n');
23
+ const patterns = [];
24
+ for (const line of lines) {
25
+ const trimmed = line.trim();
26
+ // Skip empty lines and comments
27
+ if (!trimmed || trimmed.startsWith('#')) {
28
+ continue;
29
+ }
30
+ // Handle negation patterns
31
+ if (trimmed.startsWith('!')) {
32
+ patterns.push({
33
+ pattern: trimmed.substring(1),
34
+ negated: true,
35
+ });
36
+ }
37
+ else {
38
+ patterns.push({
39
+ pattern: trimmed,
40
+ negated: false,
41
+ });
42
+ }
43
+ }
44
+ return patterns;
45
+ }
46
+ /**
47
+ * Detect and parse dependency manifests
48
+ */
49
+ export function detectManifests(projectPath) {
50
+ const manifests = [];
51
+ // package.json (Node.js)
52
+ const packageJsonPath = path.join(projectPath, 'package.json');
53
+ if (fs.existsSync(packageJsonPath)) {
54
+ try {
55
+ const content = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
56
+ manifests.push({
57
+ type: 'package.json',
58
+ path: packageJsonPath,
59
+ metadata: {
60
+ name: content.name,
61
+ version: content.version,
62
+ description: content.description,
63
+ },
64
+ dependencies: {
65
+ ...content.dependencies,
66
+ ...content.devDependencies,
67
+ },
68
+ });
69
+ }
70
+ catch (error) {
71
+ // Malformed JSON - return partial data
72
+ manifests.push({
73
+ type: 'package.json',
74
+ path: packageJsonPath,
75
+ metadata: { error: 'Malformed JSON' },
76
+ });
77
+ }
78
+ }
79
+ // pom.xml (Maven)
80
+ const pomPath = path.join(projectPath, 'pom.xml');
81
+ if (fs.existsSync(pomPath)) {
82
+ const content = fs.readFileSync(pomPath, 'utf-8');
83
+ const groupId = content.match(/<groupId>(.*?)<\/groupId>/)?.[1];
84
+ const artifactId = content.match(/<artifactId>(.*?)<\/artifactId>/)?.[1];
85
+ const version = content.match(/<version>(.*?)<\/version>/)?.[1];
86
+ manifests.push({
87
+ type: 'pom.xml',
88
+ path: pomPath,
89
+ metadata: { groupId, artifactId, version },
90
+ });
91
+ }
92
+ // requirements.txt (Python)
93
+ const requirementsPath = path.join(projectPath, 'requirements.txt');
94
+ if (fs.existsSync(requirementsPath)) {
95
+ const content = fs.readFileSync(requirementsPath, 'utf-8');
96
+ const dependencies = {};
97
+ content.split('\n').forEach(line => {
98
+ const trimmed = line.trim();
99
+ if (trimmed && !trimmed.startsWith('#')) {
100
+ const match = trimmed.match(/^([^=<>]+)(==|>=|<=|>|<)(.+)$/);
101
+ if (match) {
102
+ dependencies[match[1].trim()] = match[3].trim();
103
+ }
104
+ }
105
+ });
106
+ manifests.push({
107
+ type: 'requirements.txt',
108
+ path: requirementsPath,
109
+ metadata: {},
110
+ dependencies,
111
+ });
112
+ }
113
+ // go.mod (Go)
114
+ const goModPath = path.join(projectPath, 'go.mod');
115
+ if (fs.existsSync(goModPath)) {
116
+ const content = fs.readFileSync(goModPath, 'utf-8');
117
+ const moduleName = content.match(/^module\s+(.+)$/m)?.[1];
118
+ manifests.push({
119
+ type: 'go.mod',
120
+ path: goModPath,
121
+ metadata: { module: moduleName },
122
+ });
123
+ }
124
+ // Cargo.toml (Rust)
125
+ const cargoPath = path.join(projectPath, 'Cargo.toml');
126
+ if (fs.existsSync(cargoPath)) {
127
+ const content = fs.readFileSync(cargoPath, 'utf-8');
128
+ const name = content.match(/^\[package\][\s\S]*?name\s*=\s*"(.+?)"/m)?.[1];
129
+ const version = content.match(/^\[package\][\s\S]*?version\s*=\s*"(.+?)"/m)?.[1];
130
+ manifests.push({
131
+ type: 'Cargo.toml',
132
+ path: cargoPath,
133
+ metadata: { name, version },
134
+ });
135
+ }
136
+ // Gemfile (Ruby)
137
+ const gemfilePath = path.join(projectPath, 'Gemfile');
138
+ if (fs.existsSync(gemfilePath)) {
139
+ const content = fs.readFileSync(gemfilePath, 'utf-8');
140
+ const dependencies = {};
141
+ content.split('\n').forEach(line => {
142
+ const match = line.match(/gem\s+['"](.+?)['"](?:,\s*['"](.+?)['"])?/);
143
+ if (match) {
144
+ dependencies[match[1]] = match[2] || '*';
145
+ }
146
+ });
147
+ manifests.push({
148
+ type: 'Gemfile',
149
+ path: gemfilePath,
150
+ metadata: {},
151
+ dependencies,
152
+ });
153
+ }
154
+ return manifests;
155
+ }
156
+ /**
157
+ * Infer architecture patterns from project structure
158
+ */
159
+ export function inferArchitecture(projectPath) {
160
+ const architectures = [];
161
+ // Check for monorepo indicators
162
+ if (fs.existsSync(path.join(projectPath, 'packages')) ||
163
+ fs.existsSync(path.join(projectPath, 'apps'))) {
164
+ architectures.push('monorepo');
165
+ }
166
+ // Check package.json workspaces
167
+ const packageJsonPath = path.join(projectPath, 'package.json');
168
+ if (fs.existsSync(packageJsonPath)) {
169
+ try {
170
+ const content = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
171
+ if (content.workspaces) {
172
+ architectures.push('monorepo');
173
+ }
174
+ }
175
+ catch (error) {
176
+ // Ignore malformed JSON
177
+ }
178
+ }
179
+ // Check for lerna.json
180
+ if (fs.existsSync(path.join(projectPath, 'lerna.json'))) {
181
+ architectures.push('monorepo');
182
+ }
183
+ // Check for microservices (multiple service directories with manifests)
184
+ try {
185
+ const entries = fs.readdirSync(projectPath, { withFileTypes: true });
186
+ const serviceDirs = entries.filter(e => e.isDirectory() &&
187
+ (e.name.includes('service') || e.name.includes('api')) &&
188
+ fs.existsSync(path.join(projectPath, e.name, 'package.json')));
189
+ if (serviceDirs.length >= 2) {
190
+ architectures.push('microservices');
191
+ }
192
+ }
193
+ catch (error) {
194
+ // Directory read error
195
+ }
196
+ // Check for MVC pattern
197
+ const hasMVC = fs.existsSync(path.join(projectPath, 'models')) &&
198
+ fs.existsSync(path.join(projectPath, 'views')) &&
199
+ fs.existsSync(path.join(projectPath, 'controllers'));
200
+ if (hasMVC) {
201
+ architectures.push('mvc');
202
+ }
203
+ // Check for Clean Architecture
204
+ const hasCleanArch = fs.existsSync(path.join(projectPath, 'domain')) &&
205
+ fs.existsSync(path.join(projectPath, 'application')) &&
206
+ fs.existsSync(path.join(projectPath, 'infrastructure'));
207
+ if (hasCleanArch) {
208
+ architectures.push('clean-architecture');
209
+ }
210
+ // Check for Hexagonal Architecture
211
+ const hasHexagonal = fs.existsSync(path.join(projectPath, 'adapters')) &&
212
+ fs.existsSync(path.join(projectPath, 'ports'));
213
+ if (hasHexagonal) {
214
+ architectures.push('hexagonal-architecture');
215
+ }
216
+ // Check for Layered Architecture
217
+ const srcPath = path.join(projectPath, 'src');
218
+ if (fs.existsSync(srcPath)) {
219
+ const hasLayered = fs.existsSync(path.join(srcPath, 'api')) &&
220
+ fs.existsSync(path.join(srcPath, 'services')) &&
221
+ fs.existsSync(path.join(srcPath, 'data'));
222
+ if (hasLayered) {
223
+ architectures.push('layered-architecture');
224
+ }
225
+ }
226
+ return architectures.length > 0 ? architectures : ['unknown'];
227
+ }
228
+ /**
229
+ * Detect programming languages by file extensions
230
+ */
231
+ export function detectLanguages(projectPath) {
232
+ const languageExtensions = {
233
+ '.ts': 'TypeScript',
234
+ '.tsx': 'TypeScript',
235
+ '.js': 'JavaScript',
236
+ '.jsx': 'JavaScript',
237
+ '.py': 'Python',
238
+ '.go': 'Go',
239
+ '.rs': 'Rust',
240
+ '.rb': 'Ruby',
241
+ '.java': 'Java',
242
+ };
243
+ const counts = {};
244
+ function scanDirectory(dirPath, depth = 0) {
245
+ if (depth > 5)
246
+ return; // Limit recursion depth
247
+ try {
248
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
249
+ for (const entry of entries) {
250
+ // Skip common directories to avoid
251
+ if (entry.isDirectory() && ['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
252
+ continue;
253
+ }
254
+ const fullPath = path.join(dirPath, entry.name);
255
+ if (entry.isDirectory()) {
256
+ scanDirectory(fullPath, depth + 1);
257
+ }
258
+ else if (entry.isFile()) {
259
+ const ext = path.extname(entry.name);
260
+ const language = languageExtensions[ext];
261
+ if (language) {
262
+ counts[language] = (counts[language] || 0) + 1;
263
+ }
264
+ }
265
+ }
266
+ }
267
+ catch (error) {
268
+ // Permission error or other issue
269
+ }
270
+ }
271
+ scanDirectory(projectPath);
272
+ // Convert to percentages
273
+ const total = Object.values(counts).reduce((sum, count) => sum + count, 0);
274
+ if (total === 0)
275
+ return {};
276
+ const percentages = {};
277
+ for (const [lang, count] of Object.entries(counts)) {
278
+ percentages[lang] = Math.round((count / total) * 100);
279
+ }
280
+ return percentages;
281
+ }
282
+ /**
283
+ * Detect frameworks from manifests
284
+ */
285
+ export function detectFrameworks(manifests) {
286
+ const frontend = new Set();
287
+ const backend = new Set();
288
+ const frameworkMap = {
289
+ 'react': { type: 'frontend', name: 'React' },
290
+ 'next': { type: 'frontend', name: 'Next.js' },
291
+ 'vue': { type: 'frontend', name: 'Vue' },
292
+ 'angular': { type: 'frontend', name: 'Angular' },
293
+ 'svelte': { type: 'frontend', name: 'Svelte' },
294
+ 'express': { type: 'backend', name: 'Express' },
295
+ 'fastify': { type: 'backend', name: 'Fastify' },
296
+ 'koa': { type: 'backend', name: 'Koa' },
297
+ 'django': { type: 'backend', name: 'Django' },
298
+ 'flask': { type: 'backend', name: 'Flask' },
299
+ 'spring-boot': { type: 'backend', name: 'Spring Boot' },
300
+ 'spring': { type: 'backend', name: 'Spring' },
301
+ };
302
+ for (const manifest of manifests) {
303
+ if (!manifest.dependencies)
304
+ continue;
305
+ for (const [depName] of Object.entries(manifest.dependencies)) {
306
+ const lowerDep = depName.toLowerCase();
307
+ for (const [key, value] of Object.entries(frameworkMap)) {
308
+ if (lowerDep.includes(key)) {
309
+ if (value.type === 'frontend') {
310
+ frontend.add(value.name);
311
+ }
312
+ else {
313
+ backend.add(value.name);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ return {
320
+ frontend: Array.from(frontend),
321
+ backend: Array.from(backend),
322
+ };
323
+ }
324
+ /**
325
+ * Detect database drivers from manifests
326
+ */
327
+ function detectDatabases(manifests) {
328
+ const databases = new Set();
329
+ const dbDrivers = {
330
+ 'mongoose': 'MongoDB',
331
+ 'mongodb': 'MongoDB',
332
+ 'pg': 'PostgreSQL',
333
+ 'postgres': 'PostgreSQL',
334
+ 'mysql': 'MySQL',
335
+ 'mysql2': 'MySQL',
336
+ 'sqlite': 'SQLite',
337
+ 'sqlite3': 'SQLite',
338
+ 'redis': 'Redis',
339
+ };
340
+ for (const manifest of manifests) {
341
+ if (!manifest.dependencies)
342
+ continue;
343
+ for (const [depName] of Object.entries(manifest.dependencies)) {
344
+ const lowerDep = depName.toLowerCase();
345
+ for (const [key, value] of Object.entries(dbDrivers)) {
346
+ if (lowerDep.includes(key)) {
347
+ databases.add(value);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ return Array.from(databases);
353
+ }
354
+ /**
355
+ * Extract project goal from README or package.json
356
+ */
357
+ function extractProjectGoal(projectPath, manifests) {
358
+ // Check README.md first
359
+ const readmePath = path.join(projectPath, 'README.md');
360
+ if (fs.existsSync(readmePath)) {
361
+ const content = fs.readFileSync(readmePath, 'utf-8');
362
+ // Get first paragraph after title
363
+ const lines = content.split('\n');
364
+ for (let i = 0; i < Math.min(lines.length, 10); i++) {
365
+ const line = lines[i].trim();
366
+ if (line && !line.startsWith('#') && line.length > 20) {
367
+ return line;
368
+ }
369
+ }
370
+ }
371
+ // Fallback to package.json description
372
+ const packageManifest = manifests.find(m => m.type === 'package.json');
373
+ if (packageManifest?.metadata?.description) {
374
+ return packageManifest.metadata.description;
375
+ }
376
+ return undefined;
377
+ }
378
+ /**
379
+ * Get list of files using git ls-files or fallback to manual scan
380
+ */
381
+ function getFileList(projectPath) {
382
+ try {
383
+ // Try git ls-files first (faster and respects .gitignore)
384
+ const output = execSync('git ls-files --exclude-standard -co', {
385
+ cwd: projectPath,
386
+ encoding: 'utf-8',
387
+ stdio: ['pipe', 'pipe', 'ignore'],
388
+ });
389
+ return output.trim().split('\n').filter(Boolean);
390
+ }
391
+ catch (error) {
392
+ // Fallback to manual scan
393
+ return [];
394
+ }
395
+ }
396
+ /**
397
+ * Read file with size limit (for large files, read only first/last 20 lines)
398
+ */
399
+ function readFileWithLimit(filePath, maxSize = 1024 * 1024) {
400
+ const stats = fs.statSync(filePath);
401
+ if (stats.size > maxSize) {
402
+ // File is too large, read only first and last 20 lines
403
+ const content = fs.readFileSync(filePath, 'utf-8');
404
+ const lines = content.split('\n');
405
+ const first20 = lines.slice(0, 20).join('\n');
406
+ const last20 = lines.slice(-20).join('\n');
407
+ return `${first20}\n\n... (file truncated) ...\n\n${last20}`;
408
+ }
409
+ return fs.readFileSync(filePath, 'utf-8');
410
+ }
411
+ /**
412
+ * Main function: Analyze entire codebase
413
+ */
414
+ export function analyzeCodebase(projectPath) {
415
+ const manifests = detectManifests(projectPath);
416
+ const languages = detectLanguages(projectPath);
417
+ const frameworks = detectFrameworks(manifests);
418
+ const databases = detectDatabases(manifests);
419
+ const architecture = inferArchitecture(projectPath);
420
+ const projectGoal = extractProjectGoal(projectPath, manifests);
421
+ return {
422
+ languages,
423
+ frameworks,
424
+ databases,
425
+ architecture,
426
+ manifests,
427
+ projectGoal,
428
+ };
429
+ }
@@ -0,0 +1 @@
1
+ export {};