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,344 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { setupProductGuide, setupGuidelines, setupTechStack, setupStyleguides, setupWorkflow, runFullSetup, } from './setupIntegration.js';
5
+ describe('setupIntegration', () => {
6
+ const testOutputDir = path.join(process.cwd(), 'test-output-setup-integration');
7
+ const testProjectDir = path.join(testOutputDir, 'test-project');
8
+ beforeEach(() => {
9
+ // Create test directories
10
+ if (!fs.existsSync(testOutputDir)) {
11
+ fs.mkdirSync(testOutputDir, { recursive: true });
12
+ }
13
+ if (!fs.existsSync(testProjectDir)) {
14
+ fs.mkdirSync(testProjectDir, { recursive: true });
15
+ }
16
+ });
17
+ afterEach(() => {
18
+ // Clean up test files
19
+ if (fs.existsSync(testOutputDir)) {
20
+ fs.rmSync(testOutputDir, { recursive: true, force: true });
21
+ }
22
+ });
23
+ describe('setupProductGuide - End-to-End Integration', () => {
24
+ it('should generate product.md for greenfield project with user selections', async () => {
25
+ // Mock user responses: Select A, B, C for 3 questions
26
+ const mockResponder = vi.fn()
27
+ .mockResolvedValueOnce(['A']) // Question 1: Primary goal
28
+ .mockResolvedValueOnce(['B']) // Question 2: Target users
29
+ .mockResolvedValueOnce(['A']); // Question 3: Key features
30
+ const mockApproval = vi.fn()
31
+ .mockResolvedValueOnce({ approved: true, finalContent: undefined });
32
+ const options = {
33
+ projectPath: testProjectDir,
34
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
35
+ responder: mockResponder,
36
+ approvalFlow: mockApproval,
37
+ };
38
+ const result = await setupProductGuide(options);
39
+ expect(result.success).toBe(true);
40
+ expect(result.checkpoint).toBe('2.1_product_guide');
41
+ expect(result.filePath).toContain('product.md');
42
+ // Verify file was created
43
+ const productPath = path.join(testProjectDir, 'conductor-cdd', 'product.md');
44
+ expect(fs.existsSync(productPath)).toBe(true);
45
+ // Verify state file was created
46
+ const statePath = path.join(testProjectDir, 'conductor-cdd', 'setup_state.json');
47
+ expect(fs.existsSync(statePath)).toBe(true);
48
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
49
+ expect(state.last_successful_step).toBe('2.1_product_guide');
50
+ });
51
+ it('should generate product.md for brownfield project with context awareness', async () => {
52
+ // Create brownfield project structure
53
+ const packageJson = {
54
+ name: 'test-app',
55
+ version: '1.0.0',
56
+ dependencies: {
57
+ 'react': '^18.0.0',
58
+ 'express': '^4.18.0',
59
+ },
60
+ };
61
+ fs.writeFileSync(path.join(testProjectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
62
+ fs.mkdirSync(path.join(testProjectDir, 'src'), { recursive: true });
63
+ fs.writeFileSync(path.join(testProjectDir, 'src', 'index.ts'), 'console.log("test");');
64
+ const mockResponder = vi.fn()
65
+ .mockResolvedValueOnce(['A']) // Question 1
66
+ .mockResolvedValueOnce(['B']) // Question 2
67
+ .mockResolvedValueOnce(['A']); // Question 3
68
+ const mockApproval = vi.fn()
69
+ .mockResolvedValueOnce({ approved: true });
70
+ const options = {
71
+ projectPath: testProjectDir,
72
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
73
+ responder: mockResponder,
74
+ approvalFlow: mockApproval,
75
+ };
76
+ const result = await setupProductGuide(options);
77
+ expect(result.success).toBe(true);
78
+ expect(result.isBrownfield).toBe(true);
79
+ // Verify content includes detected technologies
80
+ const productPath = path.join(testProjectDir, 'conductor-cdd', 'product.md');
81
+ const content = fs.readFileSync(productPath, 'utf-8');
82
+ // Should detect context from package.json
83
+ expect(content.length).toBeGreaterThan(50);
84
+ });
85
+ it('should handle option E (auto-generate) early exit', async () => {
86
+ const mockResponder = vi.fn()
87
+ .mockResolvedValueOnce(['E']); // Auto-generate immediately
88
+ const mockApproval = vi.fn()
89
+ .mockResolvedValueOnce({ approved: true });
90
+ const options = {
91
+ projectPath: testProjectDir,
92
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
93
+ responder: mockResponder,
94
+ approvalFlow: mockApproval,
95
+ };
96
+ const result = await setupProductGuide(options);
97
+ if (!result.success) {
98
+ console.error('[Test Debug] Option E test failed:', result.error);
99
+ }
100
+ expect(result.success).toBe(true);
101
+ expect(result.autoGenerated).toBe(true);
102
+ expect(mockResponder).toHaveBeenCalledTimes(1); // Only asked once
103
+ // Verify file was still created
104
+ const productPath = path.join(testProjectDir, 'conductor-cdd', 'product.md');
105
+ expect(fs.existsSync(productPath)).toBe(true);
106
+ });
107
+ it('should handle option D (custom input) with user text', async () => {
108
+ const mockResponder = vi.fn()
109
+ .mockResolvedValueOnce(['D']) // Question 1: Custom input
110
+ .mockResolvedValueOnce(['A']) // Question 2
111
+ .mockResolvedValueOnce(['B']); // Question 3
112
+ const mockCustomInput = vi.fn()
113
+ .mockResolvedValueOnce('A revolutionary AI-powered task management platform');
114
+ const mockApproval = vi.fn()
115
+ .mockResolvedValueOnce({ approved: true });
116
+ const options = {
117
+ projectPath: testProjectDir,
118
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
119
+ responder: mockResponder,
120
+ approvalFlow: mockApproval,
121
+ customInputPrompt: mockCustomInput,
122
+ };
123
+ const result = await setupProductGuide(options);
124
+ expect(result.success).toBe(true);
125
+ expect(mockCustomInput).toHaveBeenCalled();
126
+ const productPath = path.join(testProjectDir, 'conductor-cdd', 'product.md');
127
+ const content = fs.readFileSync(productPath, 'utf-8');
128
+ expect(content).toContain('AI-powered task management platform');
129
+ });
130
+ it('should handle approval rejection and revision loop', async () => {
131
+ const mockResponder = vi.fn()
132
+ .mockResolvedValueOnce(['A']) // Question 1
133
+ .mockResolvedValueOnce(['B']) // Question 2
134
+ .mockResolvedValueOnce(['A']); // Question 3
135
+ const mockApproval = vi.fn()
136
+ .mockResolvedValueOnce({ approved: false, revisionGuidance: 'Add more details about users' })
137
+ .mockResolvedValueOnce({ approved: true, finalContent: 'Revised content with more details' });
138
+ const options = {
139
+ projectPath: testProjectDir,
140
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
141
+ responder: mockResponder,
142
+ approvalFlow: mockApproval,
143
+ };
144
+ const result = await setupProductGuide(options);
145
+ expect(result.success).toBe(true);
146
+ expect(result.revisionCount).toBe(1);
147
+ expect(mockApproval).toHaveBeenCalledTimes(2);
148
+ const productPath = path.join(testProjectDir, 'conductor-cdd', 'product.md');
149
+ const content = fs.readFileSync(productPath, 'utf-8');
150
+ expect(content).toContain('Revised content with more details');
151
+ });
152
+ it('should enforce max 5 questions limit', async () => {
153
+ // Setup to answer 10 times, but should only be asked 5
154
+ const mockResponder = vi.fn()
155
+ .mockResolvedValue(['A']);
156
+ const mockApproval = vi.fn()
157
+ .mockResolvedValueOnce({ approved: true });
158
+ const options = {
159
+ projectPath: testProjectDir,
160
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
161
+ responder: mockResponder,
162
+ approvalFlow: mockApproval,
163
+ };
164
+ const result = await setupProductGuide(options);
165
+ expect(result.success).toBe(true);
166
+ expect(mockResponder.mock.calls.length).toBeLessThanOrEqual(5);
167
+ });
168
+ it('should NOT include unselected options in final document', async () => {
169
+ const mockResponder = vi.fn()
170
+ .mockResolvedValueOnce(['A']) // Question 1: Select only A
171
+ .mockResolvedValueOnce(['A']) // Question 2
172
+ .mockResolvedValueOnce(['A']); // Question 3
173
+ const mockApproval = vi.fn()
174
+ .mockResolvedValueOnce({ approved: true });
175
+ const options = {
176
+ projectPath: testProjectDir,
177
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
178
+ responder: mockResponder,
179
+ approvalFlow: mockApproval,
180
+ };
181
+ const result = await setupProductGuide(options);
182
+ expect(result.success).toBe(true);
183
+ const productPath = path.join(testProjectDir, 'conductor-cdd', 'product.md');
184
+ const content = fs.readFileSync(productPath, 'utf-8');
185
+ // Should NOT contain option markers
186
+ expect(content).not.toMatch(/\bA\)/);
187
+ expect(content).not.toMatch(/\bB\)/);
188
+ expect(content).not.toMatch(/\bC\)/);
189
+ expect(content).not.toMatch(/\bD\)/);
190
+ expect(content).not.toMatch(/\bE\)/);
191
+ });
192
+ });
193
+ describe('runFullSetup - Complete Workflow Integration', () => {
194
+ it('should generate all 5 documents in sequence', async () => {
195
+ const mockResponder = vi.fn()
196
+ .mockResolvedValue(['A']); // Simple answer for all questions
197
+ const mockApproval = vi.fn()
198
+ .mockResolvedValue({ approved: true });
199
+ const options = {
200
+ projectPath: testProjectDir,
201
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
202
+ responder: mockResponder,
203
+ approvalFlow: mockApproval,
204
+ };
205
+ const result = await runFullSetup(options);
206
+ expect(result.success).toBe(true);
207
+ expect(result.documentsCreated).toHaveLength(5);
208
+ // Verify all 5 documents were created
209
+ const cddDir = path.join(testProjectDir, 'conductor-cdd');
210
+ expect(fs.existsSync(path.join(cddDir, 'product.md'))).toBe(true);
211
+ expect(fs.existsSync(path.join(cddDir, 'guidelines.md'))).toBe(true);
212
+ expect(fs.existsSync(path.join(cddDir, 'tech-stack.md'))).toBe(true);
213
+ expect(fs.existsSync(path.join(cddDir, 'styleguides.md'))).toBe(true);
214
+ expect(fs.existsSync(path.join(cddDir, 'workflow.md'))).toBe(true);
215
+ // Verify final state
216
+ const statePath = path.join(cddDir, 'setup_state.json');
217
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
218
+ expect(state.last_successful_step).toBe('2.5_workflow');
219
+ });
220
+ it('should resume from checkpoint if interrupted', async () => {
221
+ // First, create partial state (stopped after product.md)
222
+ const cddDir = path.join(testProjectDir, 'conductor-cdd');
223
+ fs.mkdirSync(cddDir, { recursive: true });
224
+ const partialState = {
225
+ last_successful_step: '2.1_product_guide',
226
+ product_timestamp: new Date().toISOString(),
227
+ };
228
+ fs.writeFileSync(path.join(cddDir, 'setup_state.json'), JSON.stringify(partialState, null, 2));
229
+ // Mock responder should only be called for remaining sections
230
+ const mockResponder = vi.fn()
231
+ .mockResolvedValue(['A']);
232
+ const mockApproval = vi.fn()
233
+ .mockResolvedValue({ approved: true });
234
+ const options = {
235
+ projectPath: testProjectDir,
236
+ outputDir: cddDir,
237
+ responder: mockResponder,
238
+ approvalFlow: mockApproval,
239
+ resume: true,
240
+ };
241
+ const result = await runFullSetup(options);
242
+ expect(result.success).toBe(true);
243
+ expect(result.resumed).toBe(true);
244
+ expect(result.resumedFrom).toBe('2.1_product_guide');
245
+ // Should have created remaining 4 documents
246
+ expect(result.documentsCreated).toHaveLength(4);
247
+ });
248
+ it('should handle failures and report partial completion', async () => {
249
+ const mockResponder = vi.fn()
250
+ .mockResolvedValue(['A']);
251
+ // Fail on guidelines approval
252
+ const mockApproval = vi.fn()
253
+ .mockResolvedValueOnce({ approved: true }) // product: success
254
+ .mockResolvedValue({ approved: false, revisionGuidance: 'Keep rejecting' }); // guidelines: fail
255
+ const options = {
256
+ projectPath: testProjectDir,
257
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
258
+ responder: mockResponder,
259
+ approvalFlow: mockApproval,
260
+ maxRevisions: 2,
261
+ };
262
+ const result = await runFullSetup(options);
263
+ expect(result.success).toBe(false);
264
+ expect(result.documentsCreated.length).toBeLessThan(5);
265
+ expect(result.error).toContain('revision');
266
+ // Product should be created, but not all others
267
+ const cddDir = path.join(testProjectDir, 'conductor-cdd');
268
+ expect(fs.existsSync(path.join(cddDir, 'product.md'))).toBe(true);
269
+ });
270
+ });
271
+ describe('Individual Section Setup Functions', () => {
272
+ it('should setup guidelines.md independently', async () => {
273
+ const mockResponder = vi.fn().mockResolvedValue(['A']);
274
+ const mockApproval = vi.fn().mockResolvedValue({ approved: true });
275
+ const options = {
276
+ projectPath: testProjectDir,
277
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
278
+ responder: mockResponder,
279
+ approvalFlow: mockApproval,
280
+ };
281
+ const result = await setupGuidelines(options);
282
+ expect(result.success).toBe(true);
283
+ expect(result.checkpoint).toBe('2.2_product_guidelines');
284
+ const guidelinesPath = path.join(testProjectDir, 'conductor-cdd', 'guidelines.md');
285
+ expect(fs.existsSync(guidelinesPath)).toBe(true);
286
+ });
287
+ it('should setup tech-stack.md with auto-detected dependencies', async () => {
288
+ // Create package.json for detection
289
+ const packageJson = {
290
+ name: 'test-app',
291
+ dependencies: {
292
+ 'react': '^18.0.0',
293
+ 'typescript': '^5.0.0',
294
+ },
295
+ };
296
+ fs.writeFileSync(path.join(testProjectDir, 'package.json'), JSON.stringify(packageJson, null, 2));
297
+ const mockResponder = vi.fn().mockResolvedValue(['E']); // Auto-generate
298
+ const mockApproval = vi.fn().mockResolvedValue({ approved: true });
299
+ const options = {
300
+ projectPath: testProjectDir,
301
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
302
+ responder: mockResponder,
303
+ approvalFlow: mockApproval,
304
+ };
305
+ const result = await setupTechStack(options);
306
+ expect(result.success).toBe(true);
307
+ expect(result.checkpoint).toBe('2.3_tech_stack');
308
+ const techStackPath = path.join(testProjectDir, 'conductor-cdd', 'tech-stack.md');
309
+ const content = fs.readFileSync(techStackPath, 'utf-8');
310
+ // Should detect React and TypeScript
311
+ expect(content.toLowerCase()).toMatch(/react|typescript/);
312
+ });
313
+ it('should setup styleguides.md', async () => {
314
+ const mockResponder = vi.fn().mockResolvedValue(['A']);
315
+ const mockApproval = vi.fn().mockResolvedValue({ approved: true });
316
+ const options = {
317
+ projectPath: testProjectDir,
318
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
319
+ responder: mockResponder,
320
+ approvalFlow: mockApproval,
321
+ };
322
+ const result = await setupStyleguides(options);
323
+ expect(result.success).toBe(true);
324
+ expect(result.checkpoint).toBe('2.4_code_styleguides');
325
+ const styleguidesPath = path.join(testProjectDir, 'conductor-cdd', 'styleguides.md');
326
+ expect(fs.existsSync(styleguidesPath)).toBe(true);
327
+ });
328
+ it('should setup workflow.md', async () => {
329
+ const mockResponder = vi.fn().mockResolvedValue(['A']);
330
+ const mockApproval = vi.fn().mockResolvedValue({ approved: true });
331
+ const options = {
332
+ projectPath: testProjectDir,
333
+ outputDir: path.join(testProjectDir, 'conductor-cdd'),
334
+ responder: mockResponder,
335
+ approvalFlow: mockApproval,
336
+ };
337
+ const result = await setupWorkflow(options);
338
+ expect(result.success).toBe(true);
339
+ expect(result.checkpoint).toBe('2.5_workflow');
340
+ const workflowPath = path.join(testProjectDir, 'conductor-cdd', 'workflow.md');
341
+ expect(fs.existsSync(workflowPath)).toBe(true);
342
+ });
343
+ });
344
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-conductor-cdd-plugin",
3
- "version": "1.0.0-beta.18",
3
+ "version": "1.0.0-beta.19",
4
4
  "description": "Context-Driven Development (CDD) plugin for OpenCode - Transform your AI coding workflow with structured specifications, plans, and implementation tracking",
5
5
  "type": "module",
6
6
  "repository": {