popeye-cli 1.0.0
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/.env.example +25 -0
- package/.prettierrc +8 -0
- package/README.md +320 -0
- package/dist/adapters/claude.d.ts +82 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +230 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/openai.d.ts +48 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +257 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/auth/claude.d.ts +44 -0
- package/dist/auth/claude.d.ts.map +1 -0
- package/dist/auth/claude.js +139 -0
- package/dist/auth/claude.js.map +1 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +141 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keychain.d.ts +66 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +125 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/openai-entry.d.ts +9 -0
- package/dist/auth/openai-entry.d.ts.map +1 -0
- package/dist/auth/openai-entry.js +410 -0
- package/dist/auth/openai-entry.js.map +1 -0
- package/dist/auth/openai.d.ts +71 -0
- package/dist/auth/openai.d.ts.map +1 -0
- package/dist/auth/openai.js +212 -0
- package/dist/auth/openai.js.map +1 -0
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +213 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +162 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/config.d.ts +10 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +215 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +10 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +240 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +18 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +241 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/status.d.ts +18 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +154 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +71 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interactive.d.ts +9 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +330 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/output.d.ts +182 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +355 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/defaults.d.ts +57 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +103 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +138 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +244 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +220 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +141 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/generators/index.d.ts +101 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +200 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/python.d.ts +48 -0
- package/dist/generators/python.d.ts.map +1 -0
- package/dist/generators/python.js +262 -0
- package/dist/generators/python.js.map +1 -0
- package/dist/generators/templates/index.d.ts +6 -0
- package/dist/generators/templates/index.d.ts.map +1 -0
- package/dist/generators/templates/index.js +6 -0
- package/dist/generators/templates/index.js.map +1 -0
- package/dist/generators/templates/python.d.ts +53 -0
- package/dist/generators/templates/python.d.ts.map +1 -0
- package/dist/generators/templates/python.js +454 -0
- package/dist/generators/templates/python.js.map +1 -0
- package/dist/generators/templates/typescript.d.ts +53 -0
- package/dist/generators/templates/typescript.d.ts.map +1 -0
- package/dist/generators/templates/typescript.js +394 -0
- package/dist/generators/templates/typescript.js.map +1 -0
- package/dist/generators/typescript.d.ts +64 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +271 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/state/index.d.ts +168 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +338 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/persistence.d.ts +91 -0
- package/dist/state/persistence.d.ts.map +1 -0
- package/dist/state/persistence.js +201 -0
- package/dist/state/persistence.js.map +1 -0
- package/dist/types/cli.d.ts +132 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +17 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/consensus.d.ts +111 -0
- package/dist/types/consensus.d.ts.map +1 -0
- package/dist/types/consensus.js +29 -0
- package/dist/types/consensus.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +73 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +55 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/workflow.d.ts +236 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +74 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/workflow/consensus.d.ts +89 -0
- package/dist/workflow/consensus.d.ts.map +1 -0
- package/dist/workflow/consensus.js +220 -0
- package/dist/workflow/consensus.js.map +1 -0
- package/dist/workflow/execution-mode.d.ts +82 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -0
- package/dist/workflow/execution-mode.js +346 -0
- package/dist/workflow/execution-mode.js.map +1 -0
- package/dist/workflow/index.d.ts +110 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +283 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +83 -0
- package/dist/workflow/plan-mode.d.ts.map +1 -0
- package/dist/workflow/plan-mode.js +241 -0
- package/dist/workflow/plan-mode.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +87 -0
- package/dist/workflow/test-runner.d.ts.map +1 -0
- package/dist/workflow/test-runner.js +273 -0
- package/dist/workflow/test-runner.js.map +1 -0
- package/eslint.config.js +25 -0
- package/package.json +66 -0
- package/src/adapters/claude.ts +298 -0
- package/src/adapters/openai.ts +300 -0
- package/src/auth/claude.ts +166 -0
- package/src/auth/index.ts +171 -0
- package/src/auth/keychain.ts +138 -0
- package/src/auth/openai-entry.ts +410 -0
- package/src/auth/openai.ts +260 -0
- package/src/auth/server.ts +252 -0
- package/src/cli/commands/auth.ts +194 -0
- package/src/cli/commands/config.ts +241 -0
- package/src/cli/commands/create.ts +308 -0
- package/src/cli/commands/index.ts +10 -0
- package/src/cli/commands/resume.ts +304 -0
- package/src/cli/commands/status.ts +189 -0
- package/src/cli/index.ts +90 -0
- package/src/cli/interactive.ts +418 -0
- package/src/cli/output.ts +410 -0
- package/src/config/defaults.ts +114 -0
- package/src/config/index.ts +315 -0
- package/src/config/schema.ts +164 -0
- package/src/generators/index.ts +251 -0
- package/src/generators/python.ts +318 -0
- package/src/generators/templates/index.ts +6 -0
- package/src/generators/templates/python.ts +465 -0
- package/src/generators/templates/typescript.ts +417 -0
- package/src/generators/typescript.ts +340 -0
- package/src/index.ts +13 -0
- package/src/state/index.ts +454 -0
- package/src/state/persistence.ts +230 -0
- package/src/types/cli.ts +146 -0
- package/src/types/consensus.ts +116 -0
- package/src/types/index.ts +64 -0
- package/src/types/project.ts +85 -0
- package/src/types/workflow.ts +149 -0
- package/src/workflow/consensus.ts +299 -0
- package/src/workflow/execution-mode.ts +517 -0
- package/src/workflow/index.ts +396 -0
- package/src/workflow/plan-mode.ts +356 -0
- package/src/workflow/test-runner.ts +345 -0
- package/tests/adapters/openai.test.ts +145 -0
- package/tests/config/config.test.ts +208 -0
- package/tests/generators/generators.test.ts +185 -0
- package/tests/types/consensus.test.ts +152 -0
- package/tests/types/project.test.ts +134 -0
- package/tests/workflow/consensus.test.ts +221 -0
- package/tests/workflow/test-runner.test.ts +214 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for OpenAI adapter
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { parseConsensusResponse } from '../../src/adapters/openai.js';
|
|
7
|
+
|
|
8
|
+
describe('parseConsensusResponse', () => {
|
|
9
|
+
it('should parse a complete consensus response', () => {
|
|
10
|
+
const response = `
|
|
11
|
+
ANALYSIS:
|
|
12
|
+
This is a well-structured plan that covers the main requirements.
|
|
13
|
+
The architecture is sound and follows best practices.
|
|
14
|
+
|
|
15
|
+
STRENGTHS:
|
|
16
|
+
- Clear project structure
|
|
17
|
+
- Comprehensive test coverage
|
|
18
|
+
- Good separation of concerns
|
|
19
|
+
|
|
20
|
+
CONCERNS:
|
|
21
|
+
- Missing error handling strategy
|
|
22
|
+
- No deployment plan
|
|
23
|
+
- Security considerations not addressed
|
|
24
|
+
|
|
25
|
+
RECOMMENDATIONS:
|
|
26
|
+
- Add error handling middleware
|
|
27
|
+
- Include deployment configuration
|
|
28
|
+
- Document security measures
|
|
29
|
+
|
|
30
|
+
CONSENSUS: 85%
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const result = parseConsensusResponse(response);
|
|
34
|
+
|
|
35
|
+
expect(result.score).toBe(85);
|
|
36
|
+
expect(result.approved).toBe(false);
|
|
37
|
+
expect(result.analysis).toContain('well-structured plan');
|
|
38
|
+
expect(result.strengths).toContain('Clear project structure');
|
|
39
|
+
expect(result.concerns).toContain('Missing error handling strategy');
|
|
40
|
+
expect(result.recommendations).toContain('Add error handling middleware');
|
|
41
|
+
expect(result.rawResponse).toBe(response);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should mark as approved when score >= 95', () => {
|
|
45
|
+
const response = `
|
|
46
|
+
ANALYSIS:
|
|
47
|
+
Excellent plan.
|
|
48
|
+
|
|
49
|
+
STRENGTHS:
|
|
50
|
+
- Everything is perfect
|
|
51
|
+
|
|
52
|
+
CONCERNS:
|
|
53
|
+
- None
|
|
54
|
+
|
|
55
|
+
RECOMMENDATIONS:
|
|
56
|
+
- None needed
|
|
57
|
+
|
|
58
|
+
CONSENSUS: 98%
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
const result = parseConsensusResponse(response);
|
|
62
|
+
|
|
63
|
+
expect(result.score).toBe(98);
|
|
64
|
+
expect(result.approved).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle missing sections gracefully', () => {
|
|
68
|
+
const response = `
|
|
69
|
+
Some random text without proper formatting.
|
|
70
|
+
|
|
71
|
+
CONSENSUS: 70%
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
const result = parseConsensusResponse(response);
|
|
75
|
+
|
|
76
|
+
expect(result.score).toBe(70);
|
|
77
|
+
expect(result.approved).toBe(false);
|
|
78
|
+
expect(result.analysis).toBe('');
|
|
79
|
+
expect(result.strengths).toEqual([]);
|
|
80
|
+
expect(result.concerns).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle missing score', () => {
|
|
84
|
+
const response = `
|
|
85
|
+
ANALYSIS:
|
|
86
|
+
No score provided in this response.
|
|
87
|
+
|
|
88
|
+
STRENGTHS:
|
|
89
|
+
- Some strength
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const result = parseConsensusResponse(response);
|
|
93
|
+
|
|
94
|
+
expect(result.score).toBe(0);
|
|
95
|
+
expect(result.approved).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should parse bulleted lists correctly', () => {
|
|
99
|
+
const response = `
|
|
100
|
+
ANALYSIS:
|
|
101
|
+
Good plan.
|
|
102
|
+
|
|
103
|
+
STRENGTHS:
|
|
104
|
+
- First strength
|
|
105
|
+
- Second strength
|
|
106
|
+
* Third strength
|
|
107
|
+
+ Fourth strength
|
|
108
|
+
|
|
109
|
+
CONCERNS:
|
|
110
|
+
1. First concern
|
|
111
|
+
2. Second concern
|
|
112
|
+
|
|
113
|
+
RECOMMENDATIONS:
|
|
114
|
+
- Recommendation one
|
|
115
|
+
- Recommendation two
|
|
116
|
+
|
|
117
|
+
CONSENSUS: 80%
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
const result = parseConsensusResponse(response);
|
|
121
|
+
|
|
122
|
+
expect(result.strengths).toHaveLength(4);
|
|
123
|
+
expect(result.concerns).toHaveLength(2);
|
|
124
|
+
expect(result.recommendations).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle various score formats', () => {
|
|
128
|
+
// With space
|
|
129
|
+
expect(parseConsensusResponse('CONSENSUS: 90%').score).toBe(90);
|
|
130
|
+
|
|
131
|
+
// Without space
|
|
132
|
+
expect(parseConsensusResponse('CONSENSUS:95%').score).toBe(95);
|
|
133
|
+
|
|
134
|
+
// Lowercase
|
|
135
|
+
expect(parseConsensusResponse('consensus: 88%').score).toBe(88);
|
|
136
|
+
|
|
137
|
+
// Mixed case
|
|
138
|
+
expect(parseConsensusResponse('Consensus: 75%').score).toBe(75);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should handle edge case scores', () => {
|
|
142
|
+
expect(parseConsensusResponse('CONSENSUS: 0%').score).toBe(0);
|
|
143
|
+
expect(parseConsensusResponse('CONSENSUS: 100%').score).toBe(100);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for configuration management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { DEFAULT_CONFIG } from '../../src/config/defaults.js';
|
|
7
|
+
import { ConfigSchema } from '../../src/config/schema.js';
|
|
8
|
+
import { deepMerge } from '../../src/config/index.js';
|
|
9
|
+
|
|
10
|
+
describe('DEFAULT_CONFIG', () => {
|
|
11
|
+
it('should have all required sections', () => {
|
|
12
|
+
expect(DEFAULT_CONFIG.consensus).toBeDefined();
|
|
13
|
+
expect(DEFAULT_CONFIG.apis).toBeDefined();
|
|
14
|
+
expect(DEFAULT_CONFIG.project).toBeDefined();
|
|
15
|
+
expect(DEFAULT_CONFIG.directories).toBeDefined();
|
|
16
|
+
expect(DEFAULT_CONFIG.output).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should have valid consensus defaults', () => {
|
|
20
|
+
expect(DEFAULT_CONFIG.consensus.threshold).toBe(95);
|
|
21
|
+
expect(DEFAULT_CONFIG.consensus.max_disagreements).toBe(5);
|
|
22
|
+
expect(DEFAULT_CONFIG.consensus.escalation_action).toBe('pause');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should have valid API defaults', () => {
|
|
26
|
+
expect(DEFAULT_CONFIG.apis.openai.model).toBe('gpt-4o');
|
|
27
|
+
expect(DEFAULT_CONFIG.apis.openai.temperature).toBe(0.3);
|
|
28
|
+
expect(DEFAULT_CONFIG.apis.openai.max_tokens).toBe(4096);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should have valid project defaults', () => {
|
|
32
|
+
expect(DEFAULT_CONFIG.project.default_language).toBe('python');
|
|
33
|
+
expect(DEFAULT_CONFIG.project.python.package_manager).toBe('pip');
|
|
34
|
+
expect(DEFAULT_CONFIG.project.typescript.package_manager).toBe('npm');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should pass schema validation', () => {
|
|
38
|
+
const result = ConfigSchema.safeParse(DEFAULT_CONFIG);
|
|
39
|
+
expect(result.success).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('ConfigSchema', () => {
|
|
44
|
+
it('should accept valid complete config', () => {
|
|
45
|
+
const config = {
|
|
46
|
+
consensus: {
|
|
47
|
+
threshold: 90,
|
|
48
|
+
max_disagreements: 3,
|
|
49
|
+
escalation_action: 'pause' as const,
|
|
50
|
+
},
|
|
51
|
+
apis: {
|
|
52
|
+
openai: {
|
|
53
|
+
model: 'gpt-4o-mini' as const,
|
|
54
|
+
temperature: 0.5,
|
|
55
|
+
max_tokens: 2048,
|
|
56
|
+
available_models: ['gpt-4o', 'gpt-4o-mini'],
|
|
57
|
+
},
|
|
58
|
+
claude: {
|
|
59
|
+
model: 'claude-sonnet-4-20250514',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
project: {
|
|
63
|
+
default_language: 'typescript' as const,
|
|
64
|
+
python: {
|
|
65
|
+
package_manager: 'pip' as const,
|
|
66
|
+
test_framework: 'pytest',
|
|
67
|
+
min_version: '3.10',
|
|
68
|
+
},
|
|
69
|
+
typescript: {
|
|
70
|
+
package_manager: 'npm' as const,
|
|
71
|
+
test_framework: 'vitest' as const,
|
|
72
|
+
min_version: '18',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
directories: {
|
|
76
|
+
docs: './docs',
|
|
77
|
+
tests: './tests',
|
|
78
|
+
plans: './plans',
|
|
79
|
+
},
|
|
80
|
+
output: {
|
|
81
|
+
format: 'markdown' as const,
|
|
82
|
+
verbose: true,
|
|
83
|
+
timestamps: false,
|
|
84
|
+
show_consensus_dialog: true,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const result = ConfigSchema.safeParse(config);
|
|
89
|
+
expect(result.success).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should accept partial config', () => {
|
|
93
|
+
const config = {
|
|
94
|
+
consensus: {
|
|
95
|
+
threshold: 80,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = ConfigSchema.safeParse(config);
|
|
100
|
+
expect(result.success).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should reject invalid threshold', () => {
|
|
104
|
+
const config = {
|
|
105
|
+
consensus: {
|
|
106
|
+
threshold: 150,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const result = ConfigSchema.safeParse(config);
|
|
111
|
+
expect(result.success).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should reject invalid language', () => {
|
|
115
|
+
const config = {
|
|
116
|
+
project: {
|
|
117
|
+
default_language: 'java',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const result = ConfigSchema.safeParse(config);
|
|
122
|
+
expect(result.success).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should reject invalid model', () => {
|
|
126
|
+
const config = {
|
|
127
|
+
apis: {
|
|
128
|
+
openai: {
|
|
129
|
+
model: 'gpt-5',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const result = ConfigSchema.safeParse(config);
|
|
135
|
+
expect(result.success).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('deepMerge', () => {
|
|
140
|
+
it('should merge simple objects', () => {
|
|
141
|
+
const target = { a: 1, b: 2 };
|
|
142
|
+
const source = { b: 3, c: 4 };
|
|
143
|
+
|
|
144
|
+
const result = deepMerge(target, source);
|
|
145
|
+
|
|
146
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should deep merge nested objects', () => {
|
|
150
|
+
const target = {
|
|
151
|
+
level1: {
|
|
152
|
+
level2: {
|
|
153
|
+
a: 1,
|
|
154
|
+
b: 2,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const source = {
|
|
159
|
+
level1: {
|
|
160
|
+
level2: {
|
|
161
|
+
b: 3,
|
|
162
|
+
c: 4,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = deepMerge(target, source);
|
|
168
|
+
|
|
169
|
+
expect(result).toEqual({
|
|
170
|
+
level1: {
|
|
171
|
+
level2: {
|
|
172
|
+
a: 1,
|
|
173
|
+
b: 3,
|
|
174
|
+
c: 4,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should not modify original objects', () => {
|
|
181
|
+
const target = { a: 1 };
|
|
182
|
+
const source = { b: 2 };
|
|
183
|
+
|
|
184
|
+
deepMerge(target, source);
|
|
185
|
+
|
|
186
|
+
expect(target).toEqual({ a: 1 });
|
|
187
|
+
expect(source).toEqual({ b: 2 });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should handle arrays by replacement', () => {
|
|
191
|
+
const target = { arr: [1, 2, 3] };
|
|
192
|
+
const source = { arr: [4, 5] };
|
|
193
|
+
|
|
194
|
+
const result = deepMerge(target, source);
|
|
195
|
+
|
|
196
|
+
expect(result.arr).toEqual([4, 5]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should skip undefined values in source', () => {
|
|
200
|
+
const target = { a: 1, b: 2 };
|
|
201
|
+
const source = { a: undefined, c: 3 };
|
|
202
|
+
|
|
203
|
+
const result = deepMerge(target, source);
|
|
204
|
+
|
|
205
|
+
// deepMerge skips undefined values, keeping original
|
|
206
|
+
expect(result).toEqual({ a: 1, b: 2, c: 3 });
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for project generators
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
getProjectFiles,
|
|
8
|
+
getTestCommand,
|
|
9
|
+
getBuildCommand,
|
|
10
|
+
getLintCommand,
|
|
11
|
+
getFileExtension,
|
|
12
|
+
getSourceDir,
|
|
13
|
+
getTestDir,
|
|
14
|
+
} from '../../src/generators/index.js';
|
|
15
|
+
import {
|
|
16
|
+
generatePyprojectToml,
|
|
17
|
+
generateReadme as generatePythonReadme,
|
|
18
|
+
generateGitignore as generatePythonGitignore,
|
|
19
|
+
} from '../../src/generators/templates/python.js';
|
|
20
|
+
import {
|
|
21
|
+
generatePackageJson,
|
|
22
|
+
generateTsconfig,
|
|
23
|
+
generateReadme as generateTsReadme,
|
|
24
|
+
generateGitignore as generateTsGitignore,
|
|
25
|
+
} from '../../src/generators/templates/typescript.js';
|
|
26
|
+
|
|
27
|
+
describe('getProjectFiles', () => {
|
|
28
|
+
it('should return Python project files', () => {
|
|
29
|
+
const files = getProjectFiles('my-project', 'python');
|
|
30
|
+
|
|
31
|
+
expect(files).toContain('pyproject.toml');
|
|
32
|
+
expect(files).toContain('requirements.txt');
|
|
33
|
+
expect(files).toContain('README.md');
|
|
34
|
+
expect(files).toContain('tests/conftest.py');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return TypeScript project files', () => {
|
|
38
|
+
const files = getProjectFiles('my-project', 'typescript');
|
|
39
|
+
|
|
40
|
+
expect(files).toContain('package.json');
|
|
41
|
+
expect(files).toContain('tsconfig.json');
|
|
42
|
+
expect(files).toContain('README.md');
|
|
43
|
+
expect(files).toContain('src/index.ts');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('getTestCommand', () => {
|
|
48
|
+
it('should return pytest for Python', () => {
|
|
49
|
+
expect(getTestCommand('python')).toContain('pytest');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return npm test for TypeScript', () => {
|
|
53
|
+
expect(getTestCommand('typescript')).toContain('npm');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('getBuildCommand', () => {
|
|
58
|
+
it('should return pip install for Python', () => {
|
|
59
|
+
expect(getBuildCommand('python')).toContain('pip');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return npm build for TypeScript', () => {
|
|
63
|
+
expect(getBuildCommand('typescript')).toContain('npm');
|
|
64
|
+
expect(getBuildCommand('typescript')).toContain('build');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('getLintCommand', () => {
|
|
69
|
+
it('should return ruff for Python', () => {
|
|
70
|
+
expect(getLintCommand('python')).toContain('ruff');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return npm lint for TypeScript', () => {
|
|
74
|
+
expect(getLintCommand('typescript')).toContain('lint');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('getFileExtension', () => {
|
|
79
|
+
it('should return py for Python', () => {
|
|
80
|
+
expect(getFileExtension('python')).toBe('py');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return ts for TypeScript', () => {
|
|
84
|
+
expect(getFileExtension('typescript')).toBe('ts');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('getSourceDir', () => {
|
|
89
|
+
it('should return src for both languages', () => {
|
|
90
|
+
expect(getSourceDir('python')).toBe('src');
|
|
91
|
+
expect(getSourceDir('typescript')).toBe('src');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('getTestDir', () => {
|
|
96
|
+
it('should return tests for both languages', () => {
|
|
97
|
+
expect(getTestDir('python')).toBe('tests');
|
|
98
|
+
expect(getTestDir('typescript')).toBe('tests');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('Python templates', () => {
|
|
103
|
+
describe('generatePyprojectToml', () => {
|
|
104
|
+
it('should generate valid pyproject.toml', () => {
|
|
105
|
+
const content = generatePyprojectToml('my-project');
|
|
106
|
+
|
|
107
|
+
expect(content).toContain('[build-system]');
|
|
108
|
+
expect(content).toContain('name = "my-project"');
|
|
109
|
+
expect(content).toContain('requires-python');
|
|
110
|
+
expect(content).toContain('[tool.pytest.ini_options]');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('generateReadme', () => {
|
|
115
|
+
it('should include project name', () => {
|
|
116
|
+
const readme = generatePythonReadme('awesome-app', 'An awesome application');
|
|
117
|
+
|
|
118
|
+
expect(readme).toContain('# awesome-app');
|
|
119
|
+
expect(readme).toContain('An awesome application');
|
|
120
|
+
expect(readme).toContain('Installation');
|
|
121
|
+
expect(readme).toContain('pytest');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('generateGitignore', () => {
|
|
126
|
+
it('should include Python patterns', () => {
|
|
127
|
+
const gitignore = generatePythonGitignore();
|
|
128
|
+
|
|
129
|
+
expect(gitignore).toContain('__pycache__');
|
|
130
|
+
expect(gitignore).toContain('*.py[cod]'); // Pattern for .pyc, .pyo, .pyd
|
|
131
|
+
expect(gitignore).toContain('venv');
|
|
132
|
+
expect(gitignore).toContain('.env');
|
|
133
|
+
expect(gitignore).toContain('.popeye');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('TypeScript templates', () => {
|
|
139
|
+
describe('generatePackageJson', () => {
|
|
140
|
+
it('should generate valid package.json', () => {
|
|
141
|
+
const content = generatePackageJson('my-app', 'Test app');
|
|
142
|
+
const pkg = JSON.parse(content);
|
|
143
|
+
|
|
144
|
+
expect(pkg.name).toBe('my-app');
|
|
145
|
+
expect(pkg.description).toBe('Test app');
|
|
146
|
+
expect(pkg.scripts.build).toBeDefined();
|
|
147
|
+
expect(pkg.scripts.test).toBeDefined();
|
|
148
|
+
expect(pkg.devDependencies.typescript).toBeDefined();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('generateTsconfig', () => {
|
|
153
|
+
it('should generate valid tsconfig.json', () => {
|
|
154
|
+
const content = generateTsconfig();
|
|
155
|
+
const config = JSON.parse(content);
|
|
156
|
+
|
|
157
|
+
expect(config.compilerOptions).toBeDefined();
|
|
158
|
+
expect(config.compilerOptions.strict).toBe(true);
|
|
159
|
+
expect(config.compilerOptions.target).toBe('ES2022');
|
|
160
|
+
expect(config.include).toContain('src/**/*');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('generateReadme', () => {
|
|
165
|
+
it('should include project name', () => {
|
|
166
|
+
const readme = generateTsReadme('cool-app', 'A cool application');
|
|
167
|
+
|
|
168
|
+
expect(readme).toContain('# cool-app');
|
|
169
|
+
expect(readme).toContain('A cool application');
|
|
170
|
+
expect(readme).toContain('npm install');
|
|
171
|
+
expect(readme).toContain('npm test');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('generateGitignore', () => {
|
|
176
|
+
it('should include Node patterns', () => {
|
|
177
|
+
const gitignore = generateTsGitignore();
|
|
178
|
+
|
|
179
|
+
expect(gitignore).toContain('node_modules');
|
|
180
|
+
expect(gitignore).toContain('dist');
|
|
181
|
+
expect(gitignore).toContain('.env');
|
|
182
|
+
expect(gitignore).toContain('.popeye');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for consensus types and schemas
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
ConsensusConfigSchema,
|
|
8
|
+
DEFAULT_CONSENSUS_CONFIG,
|
|
9
|
+
} from '../../src/types/consensus.js';
|
|
10
|
+
|
|
11
|
+
describe('ConsensusConfigSchema', () => {
|
|
12
|
+
describe('valid inputs', () => {
|
|
13
|
+
it('should accept valid config with all fields', () => {
|
|
14
|
+
const config = {
|
|
15
|
+
threshold: 90,
|
|
16
|
+
maxIterations: 3,
|
|
17
|
+
openaiKey: 'sk-test-key',
|
|
18
|
+
openaiModel: 'gpt-4o' as const,
|
|
19
|
+
escalationAction: 'pause' as const,
|
|
20
|
+
temperature: 0.5,
|
|
21
|
+
maxTokens: 2048,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
25
|
+
expect(result.success).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should accept config with required fields only', () => {
|
|
29
|
+
const config = {
|
|
30
|
+
openaiKey: 'sk-test-key',
|
|
31
|
+
openaiModel: 'gpt-4o' as const,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should accept threshold at boundaries', () => {
|
|
39
|
+
// Minimum threshold
|
|
40
|
+
const minResult = ConsensusConfigSchema.safeParse({
|
|
41
|
+
threshold: 0,
|
|
42
|
+
openaiKey: 'sk-test-key',
|
|
43
|
+
openaiModel: 'gpt-4o' as const,
|
|
44
|
+
});
|
|
45
|
+
expect(minResult.success).toBe(true);
|
|
46
|
+
|
|
47
|
+
// Maximum threshold
|
|
48
|
+
const maxResult = ConsensusConfigSchema.safeParse({
|
|
49
|
+
threshold: 100,
|
|
50
|
+
openaiKey: 'sk-test-key',
|
|
51
|
+
openaiModel: 'gpt-4o' as const,
|
|
52
|
+
});
|
|
53
|
+
expect(maxResult.success).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('invalid inputs', () => {
|
|
58
|
+
it('should reject threshold below 0', () => {
|
|
59
|
+
const config = {
|
|
60
|
+
threshold: -1,
|
|
61
|
+
openaiKey: 'sk-test-key',
|
|
62
|
+
openaiModel: 'gpt-4o' as const,
|
|
63
|
+
};
|
|
64
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
65
|
+
expect(result.success).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should reject threshold above 100', () => {
|
|
69
|
+
const config = {
|
|
70
|
+
threshold: 101,
|
|
71
|
+
openaiKey: 'sk-test-key',
|
|
72
|
+
openaiModel: 'gpt-4o' as const,
|
|
73
|
+
};
|
|
74
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
75
|
+
expect(result.success).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should reject maxIterations below 1', () => {
|
|
79
|
+
const config = {
|
|
80
|
+
maxIterations: 0,
|
|
81
|
+
openaiKey: 'sk-test-key',
|
|
82
|
+
openaiModel: 'gpt-4o' as const,
|
|
83
|
+
};
|
|
84
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
85
|
+
expect(result.success).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should reject maxIterations above 10', () => {
|
|
89
|
+
const config = {
|
|
90
|
+
maxIterations: 11,
|
|
91
|
+
openaiKey: 'sk-test-key',
|
|
92
|
+
openaiModel: 'gpt-4o' as const,
|
|
93
|
+
};
|
|
94
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
95
|
+
expect(result.success).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should reject temperature below 0', () => {
|
|
99
|
+
const config = {
|
|
100
|
+
temperature: -0.1,
|
|
101
|
+
openaiKey: 'sk-test-key',
|
|
102
|
+
openaiModel: 'gpt-4o' as const,
|
|
103
|
+
};
|
|
104
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
105
|
+
expect(result.success).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should reject temperature above 2', () => {
|
|
109
|
+
const config = {
|
|
110
|
+
temperature: 2.1,
|
|
111
|
+
openaiKey: 'sk-test-key',
|
|
112
|
+
openaiModel: 'gpt-4o' as const,
|
|
113
|
+
};
|
|
114
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
115
|
+
expect(result.success).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should reject maxTokens below 100', () => {
|
|
119
|
+
const config = {
|
|
120
|
+
maxTokens: 50,
|
|
121
|
+
openaiKey: 'sk-test-key',
|
|
122
|
+
openaiModel: 'gpt-4o' as const,
|
|
123
|
+
};
|
|
124
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
125
|
+
expect(result.success).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should reject maxTokens above 32000', () => {
|
|
129
|
+
const config = {
|
|
130
|
+
maxTokens: 200000,
|
|
131
|
+
openaiKey: 'sk-test-key',
|
|
132
|
+
openaiModel: 'gpt-4o' as const,
|
|
133
|
+
};
|
|
134
|
+
const result = ConsensusConfigSchema.safeParse(config);
|
|
135
|
+
expect(result.success).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('DEFAULT_CONSENSUS_CONFIG', () => {
|
|
141
|
+
it('should have valid default values', () => {
|
|
142
|
+
expect(DEFAULT_CONSENSUS_CONFIG.threshold).toBe(95);
|
|
143
|
+
expect(DEFAULT_CONSENSUS_CONFIG.maxIterations).toBe(5);
|
|
144
|
+
expect(DEFAULT_CONSENSUS_CONFIG.openaiModel).toBe('gpt-4o');
|
|
145
|
+
expect(DEFAULT_CONSENSUS_CONFIG.temperature).toBe(0.3);
|
|
146
|
+
expect(DEFAULT_CONSENSUS_CONFIG.maxTokens).toBe(4096);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should have escalation action', () => {
|
|
150
|
+
expect(DEFAULT_CONSENSUS_CONFIG.escalationAction).toBe('pause');
|
|
151
|
+
});
|
|
152
|
+
});
|