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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for project types and schemas
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
ProjectSpecSchema,
|
|
8
|
+
OutputLanguageSchema,
|
|
9
|
+
OpenAIModelSchema,
|
|
10
|
+
} from '../../src/types/project.js';
|
|
11
|
+
|
|
12
|
+
describe('ProjectSpecSchema', () => {
|
|
13
|
+
describe('valid inputs', () => {
|
|
14
|
+
it('should accept a valid project spec with all fields', () => {
|
|
15
|
+
const spec = {
|
|
16
|
+
idea: 'Build a REST API for managing tasks',
|
|
17
|
+
name: 'task-api',
|
|
18
|
+
language: 'python',
|
|
19
|
+
openaiModel: 'gpt-4o',
|
|
20
|
+
outputDir: '/tmp/projects',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
24
|
+
expect(result.success).toBe(true);
|
|
25
|
+
if (result.success) {
|
|
26
|
+
expect(result.data.idea).toBe(spec.idea);
|
|
27
|
+
expect(result.data.language).toBe('python');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should accept spec with only required fields', () => {
|
|
32
|
+
const spec = {
|
|
33
|
+
idea: 'Simple CLI tool',
|
|
34
|
+
language: 'typescript',
|
|
35
|
+
openaiModel: 'gpt-4o-mini',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
39
|
+
expect(result.success).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should accept all valid OpenAI models', () => {
|
|
43
|
+
const models = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'];
|
|
44
|
+
|
|
45
|
+
for (const model of models) {
|
|
46
|
+
const spec = {
|
|
47
|
+
idea: 'Test project idea here',
|
|
48
|
+
language: 'python',
|
|
49
|
+
openaiModel: model,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('invalid inputs', () => {
|
|
59
|
+
it('should reject idea shorter than 10 characters', () => {
|
|
60
|
+
const spec = {
|
|
61
|
+
idea: 'Too short',
|
|
62
|
+
language: 'python',
|
|
63
|
+
openaiModel: 'gpt-4o',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
67
|
+
expect(result.success).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should reject invalid language', () => {
|
|
71
|
+
const spec = {
|
|
72
|
+
idea: 'Build something great',
|
|
73
|
+
language: 'java',
|
|
74
|
+
openaiModel: 'gpt-4o',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
78
|
+
expect(result.success).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should reject invalid OpenAI model', () => {
|
|
82
|
+
const spec = {
|
|
83
|
+
idea: 'Build something great',
|
|
84
|
+
language: 'python',
|
|
85
|
+
openaiModel: 'invalid-model',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
89
|
+
expect(result.success).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should reject missing required fields', () => {
|
|
93
|
+
const spec = {
|
|
94
|
+
name: 'my-project',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = ProjectSpecSchema.safeParse(spec);
|
|
98
|
+
expect(result.success).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('OutputLanguageSchema', () => {
|
|
104
|
+
it('should accept python', () => {
|
|
105
|
+
const result = OutputLanguageSchema.safeParse('python');
|
|
106
|
+
expect(result.success).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should accept typescript', () => {
|
|
110
|
+
const result = OutputLanguageSchema.safeParse('typescript');
|
|
111
|
+
expect(result.success).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should reject invalid language', () => {
|
|
115
|
+
const result = OutputLanguageSchema.safeParse('ruby');
|
|
116
|
+
expect(result.success).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('OpenAIModelSchema', () => {
|
|
121
|
+
it('should accept all valid models', () => {
|
|
122
|
+
const validModels = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview', 'o1-mini'];
|
|
123
|
+
|
|
124
|
+
for (const model of validModels) {
|
|
125
|
+
const result = OpenAIModelSchema.safeParse(model);
|
|
126
|
+
expect(result.success).toBe(true);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should reject invalid model', () => {
|
|
131
|
+
const result = OpenAIModelSchema.safeParse('gpt-5');
|
|
132
|
+
expect(result.success).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for consensus workflow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
formatPlanForReview,
|
|
8
|
+
extractConcerns,
|
|
9
|
+
meetsThreshold,
|
|
10
|
+
validatePlanStructure,
|
|
11
|
+
calculateAverageScore,
|
|
12
|
+
getScoreTrend,
|
|
13
|
+
} from '../../src/workflow/consensus.js';
|
|
14
|
+
import type { ConsensusResult, ConsensusIteration } from '../../src/types/consensus.js';
|
|
15
|
+
|
|
16
|
+
describe('formatPlanForReview', () => {
|
|
17
|
+
it('should format plan with context', () => {
|
|
18
|
+
const plan = '# My Plan\n\nThis is the plan content.';
|
|
19
|
+
const context = 'Python project for data processing';
|
|
20
|
+
|
|
21
|
+
const formatted = formatPlanForReview(plan, context);
|
|
22
|
+
|
|
23
|
+
expect(formatted).toContain('## Development Plan');
|
|
24
|
+
expect(formatted).toContain(plan);
|
|
25
|
+
expect(formatted).toContain('## Project Context');
|
|
26
|
+
expect(formatted).toContain(context);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should handle empty context', () => {
|
|
30
|
+
const plan = 'Simple plan';
|
|
31
|
+
const context = '';
|
|
32
|
+
|
|
33
|
+
const formatted = formatPlanForReview(plan, context);
|
|
34
|
+
|
|
35
|
+
expect(formatted).toContain(plan);
|
|
36
|
+
expect(formatted).toContain('## Project Context');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('extractConcerns', () => {
|
|
41
|
+
it('should extract concerns from result', () => {
|
|
42
|
+
const result: ConsensusResult = {
|
|
43
|
+
score: 80,
|
|
44
|
+
analysis: 'Good plan overall',
|
|
45
|
+
concerns: ['Missing error handling', 'No tests defined'],
|
|
46
|
+
recommendations: ['Add logging', 'Consider caching'],
|
|
47
|
+
approved: false,
|
|
48
|
+
rawResponse: '',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const concerns = extractConcerns(result);
|
|
52
|
+
|
|
53
|
+
expect(concerns).toContain('Missing error handling');
|
|
54
|
+
expect(concerns).toContain('No tests defined');
|
|
55
|
+
expect(concerns).toContain('Consider: Add logging');
|
|
56
|
+
expect(concerns).toContain('Consider: Consider caching');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle empty concerns and recommendations', () => {
|
|
60
|
+
const result: ConsensusResult = {
|
|
61
|
+
score: 95,
|
|
62
|
+
analysis: 'Perfect',
|
|
63
|
+
approved: true,
|
|
64
|
+
rawResponse: '',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const concerns = extractConcerns(result);
|
|
68
|
+
|
|
69
|
+
expect(concerns).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('meetsThreshold', () => {
|
|
74
|
+
it('should return true when score meets threshold', () => {
|
|
75
|
+
expect(meetsThreshold(95, 95)).toBe(true);
|
|
76
|
+
expect(meetsThreshold(100, 95)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return false when score is below threshold', () => {
|
|
80
|
+
expect(meetsThreshold(94, 95)).toBe(false);
|
|
81
|
+
expect(meetsThreshold(0, 95)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should use default threshold of 95', () => {
|
|
85
|
+
expect(meetsThreshold(95)).toBe(true);
|
|
86
|
+
expect(meetsThreshold(94)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('validatePlanStructure', () => {
|
|
91
|
+
it('should validate plan with all required sections', () => {
|
|
92
|
+
const plan = `
|
|
93
|
+
# Background
|
|
94
|
+
Some background info
|
|
95
|
+
|
|
96
|
+
## Goals
|
|
97
|
+
1. Goal one
|
|
98
|
+
2. Goal two
|
|
99
|
+
|
|
100
|
+
### Milestones
|
|
101
|
+
- Milestone 1
|
|
102
|
+
- Milestone 2
|
|
103
|
+
|
|
104
|
+
#### Tasks
|
|
105
|
+
- Task 1
|
|
106
|
+
- Task 2
|
|
107
|
+
|
|
108
|
+
##### Test Plan
|
|
109
|
+
- Test case 1
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const result = validatePlanStructure(plan);
|
|
113
|
+
|
|
114
|
+
expect(result.valid).toBe(true);
|
|
115
|
+
expect(result.missingSections).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should detect missing sections', () => {
|
|
119
|
+
const plan = `
|
|
120
|
+
# Background
|
|
121
|
+
Some background
|
|
122
|
+
|
|
123
|
+
## Goals
|
|
124
|
+
Some goals
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const result = validatePlanStructure(plan);
|
|
128
|
+
|
|
129
|
+
expect(result.valid).toBe(false);
|
|
130
|
+
expect(result.missingSections).toContain('Milestones');
|
|
131
|
+
expect(result.missingSections).toContain('Tasks');
|
|
132
|
+
expect(result.missingSections).toContain('Test');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should handle case-insensitive matching', () => {
|
|
136
|
+
const plan = `
|
|
137
|
+
# BACKGROUND
|
|
138
|
+
## GOALS
|
|
139
|
+
### MILESTONES
|
|
140
|
+
#### TASKS
|
|
141
|
+
##### TEST
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const result = validatePlanStructure(plan);
|
|
145
|
+
|
|
146
|
+
expect(result.valid).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('calculateAverageScore', () => {
|
|
151
|
+
it('should calculate average of iteration scores', () => {
|
|
152
|
+
const iterations: ConsensusIteration[] = [
|
|
153
|
+
{ iteration: 1, plan: '', result: { score: 80, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
154
|
+
{ iteration: 2, plan: '', result: { score: 90, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
155
|
+
{ iteration: 3, plan: '', result: { score: 100, analysis: '', approved: true, rawResponse: '' }, timestamp: '' },
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const average = calculateAverageScore(iterations);
|
|
159
|
+
|
|
160
|
+
expect(average).toBe(90);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should return 0 for empty iterations', () => {
|
|
164
|
+
expect(calculateAverageScore([])).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle single iteration', () => {
|
|
168
|
+
const iterations: ConsensusIteration[] = [
|
|
169
|
+
{ iteration: 1, plan: '', result: { score: 75, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
expect(calculateAverageScore(iterations)).toBe(75);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('getScoreTrend', () => {
|
|
177
|
+
it('should detect improving trend', () => {
|
|
178
|
+
const iterations: ConsensusIteration[] = [
|
|
179
|
+
{ iteration: 1, plan: '', result: { score: 70, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
180
|
+
{ iteration: 2, plan: '', result: { score: 75, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
181
|
+
{ iteration: 3, plan: '', result: { score: 85, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
182
|
+
{ iteration: 4, plan: '', result: { score: 90, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
expect(getScoreTrend(iterations)).toBe('improving');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should detect declining trend', () => {
|
|
189
|
+
const iterations: ConsensusIteration[] = [
|
|
190
|
+
{ iteration: 1, plan: '', result: { score: 90, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
191
|
+
{ iteration: 2, plan: '', result: { score: 85, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
192
|
+
{ iteration: 3, plan: '', result: { score: 75, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
193
|
+
{ iteration: 4, plan: '', result: { score: 70, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
expect(getScoreTrend(iterations)).toBe('declining');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should detect stable trend', () => {
|
|
200
|
+
const iterations: ConsensusIteration[] = [
|
|
201
|
+
{ iteration: 1, plan: '', result: { score: 80, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
202
|
+
{ iteration: 2, plan: '', result: { score: 82, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
203
|
+
{ iteration: 3, plan: '', result: { score: 81, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
204
|
+
{ iteration: 4, plan: '', result: { score: 83, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
expect(getScoreTrend(iterations)).toBe('stable');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return stable for single iteration', () => {
|
|
211
|
+
const iterations: ConsensusIteration[] = [
|
|
212
|
+
{ iteration: 1, plan: '', result: { score: 80, analysis: '', approved: false, rawResponse: '' }, timestamp: '' },
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
expect(getScoreTrend(iterations)).toBe('stable');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should return stable for empty iterations', () => {
|
|
219
|
+
expect(getScoreTrend([])).toBe('stable');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for test runner module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
buildTestCommand,
|
|
8
|
+
parseTestOutput,
|
|
9
|
+
getTestSummary,
|
|
10
|
+
DEFAULT_TEST_COMMANDS,
|
|
11
|
+
} from '../../src/workflow/test-runner.js';
|
|
12
|
+
|
|
13
|
+
describe('buildTestCommand', () => {
|
|
14
|
+
describe('Python', () => {
|
|
15
|
+
it('should build basic pytest command', () => {
|
|
16
|
+
const command = buildTestCommand({ language: 'python' });
|
|
17
|
+
expect(command).toBe('python -m pytest tests/');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should include verbose flag', () => {
|
|
21
|
+
const command = buildTestCommand({ language: 'python', verbose: true });
|
|
22
|
+
expect(command).toContain('-v');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should include coverage flag', () => {
|
|
26
|
+
const command = buildTestCommand({ language: 'python', coverage: true });
|
|
27
|
+
expect(command).toContain('--cov=src');
|
|
28
|
+
expect(command).toContain('--cov-report=term-missing');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should use custom test directory', () => {
|
|
32
|
+
const command = buildTestCommand({ language: 'python', testDir: 'my_tests/' });
|
|
33
|
+
expect(command).toContain('my_tests/');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('TypeScript', () => {
|
|
38
|
+
it('should build basic npm test command', () => {
|
|
39
|
+
const command = buildTestCommand({ language: 'typescript' });
|
|
40
|
+
expect(command).toBe('npm test');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should include coverage flag', () => {
|
|
44
|
+
const command = buildTestCommand({ language: 'typescript', coverage: true });
|
|
45
|
+
expect(command).toContain('--coverage');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('parseTestOutput', () => {
|
|
51
|
+
describe('Python/pytest', () => {
|
|
52
|
+
it('should parse successful pytest output', () => {
|
|
53
|
+
const output = `
|
|
54
|
+
============================= test session starts ==============================
|
|
55
|
+
collected 10 items
|
|
56
|
+
|
|
57
|
+
tests/test_main.py .......... [100%]
|
|
58
|
+
|
|
59
|
+
============================== 10 passed in 2.34s ==============================
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
const result = parseTestOutput(output, 'python');
|
|
63
|
+
|
|
64
|
+
expect(result.success).toBe(true);
|
|
65
|
+
expect(result.passed).toBe(10);
|
|
66
|
+
expect(result.failed).toBe(0);
|
|
67
|
+
expect(result.total).toBe(10);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should parse pytest output with failures', () => {
|
|
71
|
+
const output = `
|
|
72
|
+
============================= test session starts ==============================
|
|
73
|
+
collected 5 items
|
|
74
|
+
|
|
75
|
+
tests/test_main.py ...F. [100%]
|
|
76
|
+
|
|
77
|
+
=================================== FAILURES ===================================
|
|
78
|
+
FAILED tests/test_main.py::test_something
|
|
79
|
+
================================ 1 failed, 4 passed in 1.23s ==============================
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
const result = parseTestOutput(output, 'python');
|
|
83
|
+
|
|
84
|
+
expect(result.success).toBe(false);
|
|
85
|
+
expect(result.passed).toBe(4);
|
|
86
|
+
expect(result.failed).toBe(1);
|
|
87
|
+
expect(result.total).toBe(5);
|
|
88
|
+
expect(result.failedTests).toContain('tests/test_main.py::test_something');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle no tests collected', () => {
|
|
92
|
+
const output = 'collected 0 items';
|
|
93
|
+
|
|
94
|
+
const result = parseTestOutput(output, 'python');
|
|
95
|
+
|
|
96
|
+
expect(result.success).toBe(false);
|
|
97
|
+
expect(result.total).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('TypeScript/Jest', () => {
|
|
102
|
+
it('should parse successful Jest output', () => {
|
|
103
|
+
const output = `
|
|
104
|
+
PASS tests/index.test.ts
|
|
105
|
+
MyComponent
|
|
106
|
+
v should render correctly (25 ms)
|
|
107
|
+
v should handle click events (12 ms)
|
|
108
|
+
|
|
109
|
+
Tests: 2 passed, 2 total
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const result = parseTestOutput(output, 'typescript');
|
|
113
|
+
|
|
114
|
+
expect(result.success).toBe(true);
|
|
115
|
+
expect(result.passed).toBe(2);
|
|
116
|
+
expect(result.total).toBe(2);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should parse Jest output with failures', () => {
|
|
120
|
+
const output = `
|
|
121
|
+
FAIL tests/index.test.ts
|
|
122
|
+
MyComponent
|
|
123
|
+
v should render correctly (25 ms)
|
|
124
|
+
x should handle click events (12 ms)
|
|
125
|
+
|
|
126
|
+
Tests: 1 failed, 1 passed, 2 total
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const result = parseTestOutput(output, 'typescript');
|
|
130
|
+
|
|
131
|
+
expect(result.success).toBe(false);
|
|
132
|
+
expect(result.passed).toBe(1);
|
|
133
|
+
expect(result.failed).toBe(1);
|
|
134
|
+
expect(result.total).toBe(2);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('getTestSummary', () => {
|
|
140
|
+
it('should summarize successful tests', () => {
|
|
141
|
+
const result = {
|
|
142
|
+
success: true,
|
|
143
|
+
passed: 10,
|
|
144
|
+
failed: 0,
|
|
145
|
+
total: 10,
|
|
146
|
+
output: '',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const summary = getTestSummary(result);
|
|
150
|
+
|
|
151
|
+
expect(summary).toContain('10/10 tests passed');
|
|
152
|
+
expect(summary).toContain('✓');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should summarize failed tests', () => {
|
|
156
|
+
const result = {
|
|
157
|
+
success: false,
|
|
158
|
+
passed: 8,
|
|
159
|
+
failed: 2,
|
|
160
|
+
total: 10,
|
|
161
|
+
output: '',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const summary = getTestSummary(result);
|
|
165
|
+
|
|
166
|
+
expect(summary).toContain('8/10 tests passed');
|
|
167
|
+
expect(summary).toContain('2 failed');
|
|
168
|
+
expect(summary).toContain('✗');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should handle error case', () => {
|
|
172
|
+
const result = {
|
|
173
|
+
success: false,
|
|
174
|
+
passed: 0,
|
|
175
|
+
failed: 0,
|
|
176
|
+
total: 0,
|
|
177
|
+
output: '',
|
|
178
|
+
error: 'Tests crashed',
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const summary = getTestSummary(result);
|
|
182
|
+
|
|
183
|
+
expect(summary).toContain('Tests crashed');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should handle no tests found', () => {
|
|
187
|
+
const result = {
|
|
188
|
+
success: false,
|
|
189
|
+
passed: 0,
|
|
190
|
+
failed: 0,
|
|
191
|
+
total: 0,
|
|
192
|
+
output: '',
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const summary = getTestSummary(result);
|
|
196
|
+
|
|
197
|
+
expect(summary).toContain('No tests found');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('DEFAULT_TEST_COMMANDS', () => {
|
|
202
|
+
it('should have commands for both languages', () => {
|
|
203
|
+
expect(DEFAULT_TEST_COMMANDS.python).toBeDefined();
|
|
204
|
+
expect(DEFAULT_TEST_COMMANDS.typescript).toBeDefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should have valid pytest command for Python', () => {
|
|
208
|
+
expect(DEFAULT_TEST_COMMANDS.python).toContain('pytest');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should have valid npm command for TypeScript', () => {
|
|
212
|
+
expect(DEFAULT_TEST_COMMANDS.typescript).toContain('npm');
|
|
213
|
+
});
|
|
214
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"allowSyntheticDefaultImports": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
25
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['tests/**/*.test.ts'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
reporter: ['text', 'json', 'html'],
|
|
11
|
+
include: ['src/**/*.ts'],
|
|
12
|
+
exclude: ['src/index.ts', 'src/**/*.d.ts'],
|
|
13
|
+
thresholds: {
|
|
14
|
+
statements: 80,
|
|
15
|
+
branches: 80,
|
|
16
|
+
functions: 80,
|
|
17
|
+
lines: 80,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
testTimeout: 10000,
|
|
21
|
+
},
|
|
22
|
+
});
|