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.
- package/README.md +19 -3
- 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/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/configDetection.d.ts +12 -0
- package/dist/utils/configDetection.js +23 -9
- package/dist/utils/configDetection.test.js +204 -7
- 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/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/synergyState.test.js +17 -3
- package/package.json +2 -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
|
+
});
|
|
@@ -15,6 +15,7 @@ describe("synergyState", () => {
|
|
|
15
15
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
16
16
|
hasCDDInOpenCode: false,
|
|
17
17
|
hasCDDInOMO: false,
|
|
18
|
+
hasCDDInSlim: true,
|
|
18
19
|
synergyActive: true,
|
|
19
20
|
synergyFramework: 'oh-my-opencode-slim',
|
|
20
21
|
slimAgents: ['explorer', 'librarian', 'oracle', 'designer'],
|
|
@@ -28,6 +29,7 @@ describe("synergyState", () => {
|
|
|
28
29
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
29
30
|
hasCDDInOpenCode: false,
|
|
30
31
|
hasCDDInOMO: false,
|
|
32
|
+
hasCDDInSlim: true,
|
|
31
33
|
synergyActive: true,
|
|
32
34
|
synergyFramework: 'oh-my-opencode-slim',
|
|
33
35
|
slimAgents: ['explorer', 'librarian'],
|
|
@@ -42,6 +44,7 @@ describe("synergyState", () => {
|
|
|
42
44
|
.mockReturnValueOnce({
|
|
43
45
|
hasCDDInOpenCode: false,
|
|
44
46
|
hasCDDInOMO: false,
|
|
47
|
+
hasCDDInSlim: true,
|
|
45
48
|
synergyActive: true,
|
|
46
49
|
synergyFramework: 'oh-my-opencode-slim',
|
|
47
50
|
slimAgents: ['explorer'],
|
|
@@ -49,6 +52,7 @@ describe("synergyState", () => {
|
|
|
49
52
|
.mockReturnValueOnce({
|
|
50
53
|
hasCDDInOpenCode: false,
|
|
51
54
|
hasCDDInOMO: true,
|
|
55
|
+
hasCDDInSlim: false,
|
|
52
56
|
synergyActive: true,
|
|
53
57
|
synergyFramework: 'oh-my-opencode',
|
|
54
58
|
cddModel: 'model-1',
|
|
@@ -64,13 +68,15 @@ describe("synergyState", () => {
|
|
|
64
68
|
.mockReturnValueOnce({
|
|
65
69
|
hasCDDInOpenCode: false,
|
|
66
70
|
hasCDDInOMO: false,
|
|
71
|
+
hasCDDInSlim: true,
|
|
67
72
|
synergyActive: true,
|
|
68
73
|
synergyFramework: 'oh-my-opencode-slim',
|
|
69
74
|
slimAgents: ['explorer'],
|
|
70
75
|
})
|
|
71
76
|
.mockReturnValueOnce({
|
|
72
77
|
hasCDDInOpenCode: false,
|
|
73
|
-
hasCDDInOMO:
|
|
78
|
+
hasCDDInOMO: true,
|
|
79
|
+
hasCDDInSlim: false,
|
|
74
80
|
synergyActive: true,
|
|
75
81
|
synergyFramework: 'oh-my-opencode',
|
|
76
82
|
cddModel: 'model-changed',
|
|
@@ -85,6 +91,7 @@ describe("synergyState", () => {
|
|
|
85
91
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
86
92
|
hasCDDInOpenCode: true,
|
|
87
93
|
hasCDDInOMO: false,
|
|
94
|
+
hasCDDInSlim: false,
|
|
88
95
|
synergyActive: false,
|
|
89
96
|
synergyFramework: 'none',
|
|
90
97
|
cddModel: 'model-1',
|
|
@@ -99,13 +106,15 @@ describe("synergyState", () => {
|
|
|
99
106
|
.mockReturnValueOnce({
|
|
100
107
|
hasCDDInOpenCode: false,
|
|
101
108
|
hasCDDInOMO: false,
|
|
109
|
+
hasCDDInSlim: true,
|
|
102
110
|
synergyActive: true,
|
|
103
111
|
synergyFramework: 'oh-my-opencode-slim',
|
|
104
112
|
slimAgents: ['explorer'],
|
|
105
113
|
})
|
|
106
114
|
.mockReturnValueOnce({
|
|
107
115
|
hasCDDInOpenCode: false,
|
|
108
|
-
hasCDDInOMO:
|
|
116
|
+
hasCDDInOMO: true,
|
|
117
|
+
hasCDDInSlim: false,
|
|
109
118
|
synergyActive: true,
|
|
110
119
|
synergyFramework: 'oh-my-opencode',
|
|
111
120
|
cddModel: 'model-1',
|
|
@@ -124,6 +133,7 @@ describe("synergyState", () => {
|
|
|
124
133
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
125
134
|
hasCDDInOpenCode: false,
|
|
126
135
|
hasCDDInOMO: false,
|
|
136
|
+
hasCDDInSlim: false,
|
|
127
137
|
synergyActive: false,
|
|
128
138
|
synergyFramework: 'none',
|
|
129
139
|
});
|
|
@@ -136,6 +146,7 @@ describe("synergyState", () => {
|
|
|
136
146
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
137
147
|
hasCDDInOpenCode: false,
|
|
138
148
|
hasCDDInOMO: false,
|
|
149
|
+
hasCDDInSlim: true,
|
|
139
150
|
synergyActive: true,
|
|
140
151
|
synergyFramework: 'oh-my-opencode-slim',
|
|
141
152
|
slimAgents: ['explorer', 'oracle'],
|
|
@@ -147,6 +158,7 @@ describe("synergyState", () => {
|
|
|
147
158
|
vi.mocked(detectCDDConfig).mockReturnValue({
|
|
148
159
|
hasCDDInOpenCode: false,
|
|
149
160
|
hasCDDInOMO: true,
|
|
161
|
+
hasCDDInSlim: false,
|
|
150
162
|
synergyActive: true,
|
|
151
163
|
synergyFramework: 'oh-my-opencode',
|
|
152
164
|
cddModel: 'model-1',
|
|
@@ -163,13 +175,15 @@ describe("synergyState", () => {
|
|
|
163
175
|
.mockReturnValueOnce({
|
|
164
176
|
hasCDDInOpenCode: false,
|
|
165
177
|
hasCDDInOMO: false,
|
|
178
|
+
hasCDDInSlim: true,
|
|
166
179
|
synergyActive: true,
|
|
167
180
|
synergyFramework: 'oh-my-opencode-slim',
|
|
168
181
|
slimAgents: ['explorer'],
|
|
169
182
|
})
|
|
170
183
|
.mockReturnValueOnce({
|
|
171
184
|
hasCDDInOpenCode: false,
|
|
172
|
-
hasCDDInOMO:
|
|
185
|
+
hasCDDInOMO: true,
|
|
186
|
+
hasCDDInSlim: false,
|
|
173
187
|
synergyActive: true,
|
|
174
188
|
synergyFramework: 'oh-my-opencode',
|
|
175
189
|
cddModel: 'changed',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-conductor-cdd-plugin",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.20",
|
|
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": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"@semantic-release/npm": "^12.0.1",
|
|
54
54
|
"@semantic-release/release-notes-generator": "^14.0.0",
|
|
55
55
|
"@types/node": "^20.0.0",
|
|
56
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
56
57
|
"semantic-release": "^24.2.1",
|
|
57
58
|
"typescript": "^5.0.0",
|
|
58
59
|
"vitest": "^4.0.16"
|