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.
Files changed (209) hide show
  1. package/.env.example +25 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +320 -0
  4. package/dist/adapters/claude.d.ts +82 -0
  5. package/dist/adapters/claude.d.ts.map +1 -0
  6. package/dist/adapters/claude.js +230 -0
  7. package/dist/adapters/claude.js.map +1 -0
  8. package/dist/adapters/openai.d.ts +48 -0
  9. package/dist/adapters/openai.d.ts.map +1 -0
  10. package/dist/adapters/openai.js +257 -0
  11. package/dist/adapters/openai.js.map +1 -0
  12. package/dist/auth/claude.d.ts +44 -0
  13. package/dist/auth/claude.d.ts.map +1 -0
  14. package/dist/auth/claude.js +139 -0
  15. package/dist/auth/claude.js.map +1 -0
  16. package/dist/auth/index.d.ts +61 -0
  17. package/dist/auth/index.d.ts.map +1 -0
  18. package/dist/auth/index.js +141 -0
  19. package/dist/auth/index.js.map +1 -0
  20. package/dist/auth/keychain.d.ts +66 -0
  21. package/dist/auth/keychain.d.ts.map +1 -0
  22. package/dist/auth/keychain.js +125 -0
  23. package/dist/auth/keychain.js.map +1 -0
  24. package/dist/auth/openai-entry.d.ts +9 -0
  25. package/dist/auth/openai-entry.d.ts.map +1 -0
  26. package/dist/auth/openai-entry.js +410 -0
  27. package/dist/auth/openai-entry.js.map +1 -0
  28. package/dist/auth/openai.d.ts +71 -0
  29. package/dist/auth/openai.d.ts.map +1 -0
  30. package/dist/auth/openai.js +212 -0
  31. package/dist/auth/openai.js.map +1 -0
  32. package/dist/auth/server.d.ts +32 -0
  33. package/dist/auth/server.d.ts.map +1 -0
  34. package/dist/auth/server.js +213 -0
  35. package/dist/auth/server.js.map +1 -0
  36. package/dist/cli/commands/auth.d.ts +10 -0
  37. package/dist/cli/commands/auth.d.ts.map +1 -0
  38. package/dist/cli/commands/auth.js +162 -0
  39. package/dist/cli/commands/auth.js.map +1 -0
  40. package/dist/cli/commands/config.d.ts +10 -0
  41. package/dist/cli/commands/config.d.ts.map +1 -0
  42. package/dist/cli/commands/config.js +215 -0
  43. package/dist/cli/commands/config.js.map +1 -0
  44. package/dist/cli/commands/create.d.ts +10 -0
  45. package/dist/cli/commands/create.d.ts.map +1 -0
  46. package/dist/cli/commands/create.js +240 -0
  47. package/dist/cli/commands/create.js.map +1 -0
  48. package/dist/cli/commands/index.d.ts +10 -0
  49. package/dist/cli/commands/index.d.ts.map +1 -0
  50. package/dist/cli/commands/index.js +10 -0
  51. package/dist/cli/commands/index.js.map +1 -0
  52. package/dist/cli/commands/resume.d.ts +18 -0
  53. package/dist/cli/commands/resume.d.ts.map +1 -0
  54. package/dist/cli/commands/resume.js +241 -0
  55. package/dist/cli/commands/resume.js.map +1 -0
  56. package/dist/cli/commands/status.d.ts +18 -0
  57. package/dist/cli/commands/status.d.ts.map +1 -0
  58. package/dist/cli/commands/status.js +154 -0
  59. package/dist/cli/commands/status.js.map +1 -0
  60. package/dist/cli/index.d.ts +17 -0
  61. package/dist/cli/index.d.ts.map +1 -0
  62. package/dist/cli/index.js +71 -0
  63. package/dist/cli/index.js.map +1 -0
  64. package/dist/cli/interactive.d.ts +9 -0
  65. package/dist/cli/interactive.d.ts.map +1 -0
  66. package/dist/cli/interactive.js +330 -0
  67. package/dist/cli/interactive.js.map +1 -0
  68. package/dist/cli/output.d.ts +182 -0
  69. package/dist/cli/output.d.ts.map +1 -0
  70. package/dist/cli/output.js +355 -0
  71. package/dist/cli/output.js.map +1 -0
  72. package/dist/config/defaults.d.ts +57 -0
  73. package/dist/config/defaults.d.ts.map +1 -0
  74. package/dist/config/defaults.js +103 -0
  75. package/dist/config/defaults.js.map +1 -0
  76. package/dist/config/index.d.ts +138 -0
  77. package/dist/config/index.d.ts.map +1 -0
  78. package/dist/config/index.js +244 -0
  79. package/dist/config/index.js.map +1 -0
  80. package/dist/config/schema.d.ts +220 -0
  81. package/dist/config/schema.d.ts.map +1 -0
  82. package/dist/config/schema.js +141 -0
  83. package/dist/config/schema.js.map +1 -0
  84. package/dist/generators/index.d.ts +101 -0
  85. package/dist/generators/index.d.ts.map +1 -0
  86. package/dist/generators/index.js +200 -0
  87. package/dist/generators/index.js.map +1 -0
  88. package/dist/generators/python.d.ts +48 -0
  89. package/dist/generators/python.d.ts.map +1 -0
  90. package/dist/generators/python.js +262 -0
  91. package/dist/generators/python.js.map +1 -0
  92. package/dist/generators/templates/index.d.ts +6 -0
  93. package/dist/generators/templates/index.d.ts.map +1 -0
  94. package/dist/generators/templates/index.js +6 -0
  95. package/dist/generators/templates/index.js.map +1 -0
  96. package/dist/generators/templates/python.d.ts +53 -0
  97. package/dist/generators/templates/python.d.ts.map +1 -0
  98. package/dist/generators/templates/python.js +454 -0
  99. package/dist/generators/templates/python.js.map +1 -0
  100. package/dist/generators/templates/typescript.d.ts +53 -0
  101. package/dist/generators/templates/typescript.d.ts.map +1 -0
  102. package/dist/generators/templates/typescript.js +394 -0
  103. package/dist/generators/templates/typescript.js.map +1 -0
  104. package/dist/generators/typescript.d.ts +64 -0
  105. package/dist/generators/typescript.d.ts.map +1 -0
  106. package/dist/generators/typescript.js +271 -0
  107. package/dist/generators/typescript.js.map +1 -0
  108. package/dist/index.d.ts +7 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +12 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/state/index.d.ts +168 -0
  113. package/dist/state/index.d.ts.map +1 -0
  114. package/dist/state/index.js +338 -0
  115. package/dist/state/index.js.map +1 -0
  116. package/dist/state/persistence.d.ts +91 -0
  117. package/dist/state/persistence.d.ts.map +1 -0
  118. package/dist/state/persistence.js +201 -0
  119. package/dist/state/persistence.js.map +1 -0
  120. package/dist/types/cli.d.ts +132 -0
  121. package/dist/types/cli.d.ts.map +1 -0
  122. package/dist/types/cli.js +17 -0
  123. package/dist/types/cli.js.map +1 -0
  124. package/dist/types/consensus.d.ts +111 -0
  125. package/dist/types/consensus.d.ts.map +1 -0
  126. package/dist/types/consensus.js +29 -0
  127. package/dist/types/consensus.js.map +1 -0
  128. package/dist/types/index.d.ts +9 -0
  129. package/dist/types/index.d.ts.map +1 -0
  130. package/dist/types/index.js +13 -0
  131. package/dist/types/index.js.map +1 -0
  132. package/dist/types/project.d.ts +73 -0
  133. package/dist/types/project.d.ts.map +1 -0
  134. package/dist/types/project.js +55 -0
  135. package/dist/types/project.js.map +1 -0
  136. package/dist/types/workflow.d.ts +236 -0
  137. package/dist/types/workflow.d.ts.map +1 -0
  138. package/dist/types/workflow.js +74 -0
  139. package/dist/types/workflow.js.map +1 -0
  140. package/dist/workflow/consensus.d.ts +89 -0
  141. package/dist/workflow/consensus.d.ts.map +1 -0
  142. package/dist/workflow/consensus.js +220 -0
  143. package/dist/workflow/consensus.js.map +1 -0
  144. package/dist/workflow/execution-mode.d.ts +82 -0
  145. package/dist/workflow/execution-mode.d.ts.map +1 -0
  146. package/dist/workflow/execution-mode.js +346 -0
  147. package/dist/workflow/execution-mode.js.map +1 -0
  148. package/dist/workflow/index.d.ts +110 -0
  149. package/dist/workflow/index.d.ts.map +1 -0
  150. package/dist/workflow/index.js +283 -0
  151. package/dist/workflow/index.js.map +1 -0
  152. package/dist/workflow/plan-mode.d.ts +83 -0
  153. package/dist/workflow/plan-mode.d.ts.map +1 -0
  154. package/dist/workflow/plan-mode.js +241 -0
  155. package/dist/workflow/plan-mode.js.map +1 -0
  156. package/dist/workflow/test-runner.d.ts +87 -0
  157. package/dist/workflow/test-runner.d.ts.map +1 -0
  158. package/dist/workflow/test-runner.js +273 -0
  159. package/dist/workflow/test-runner.js.map +1 -0
  160. package/eslint.config.js +25 -0
  161. package/package.json +66 -0
  162. package/src/adapters/claude.ts +298 -0
  163. package/src/adapters/openai.ts +300 -0
  164. package/src/auth/claude.ts +166 -0
  165. package/src/auth/index.ts +171 -0
  166. package/src/auth/keychain.ts +138 -0
  167. package/src/auth/openai-entry.ts +410 -0
  168. package/src/auth/openai.ts +260 -0
  169. package/src/auth/server.ts +252 -0
  170. package/src/cli/commands/auth.ts +194 -0
  171. package/src/cli/commands/config.ts +241 -0
  172. package/src/cli/commands/create.ts +308 -0
  173. package/src/cli/commands/index.ts +10 -0
  174. package/src/cli/commands/resume.ts +304 -0
  175. package/src/cli/commands/status.ts +189 -0
  176. package/src/cli/index.ts +90 -0
  177. package/src/cli/interactive.ts +418 -0
  178. package/src/cli/output.ts +410 -0
  179. package/src/config/defaults.ts +114 -0
  180. package/src/config/index.ts +315 -0
  181. package/src/config/schema.ts +164 -0
  182. package/src/generators/index.ts +251 -0
  183. package/src/generators/python.ts +318 -0
  184. package/src/generators/templates/index.ts +6 -0
  185. package/src/generators/templates/python.ts +465 -0
  186. package/src/generators/templates/typescript.ts +417 -0
  187. package/src/generators/typescript.ts +340 -0
  188. package/src/index.ts +13 -0
  189. package/src/state/index.ts +454 -0
  190. package/src/state/persistence.ts +230 -0
  191. package/src/types/cli.ts +146 -0
  192. package/src/types/consensus.ts +116 -0
  193. package/src/types/index.ts +64 -0
  194. package/src/types/project.ts +85 -0
  195. package/src/types/workflow.ts +149 -0
  196. package/src/workflow/consensus.ts +299 -0
  197. package/src/workflow/execution-mode.ts +517 -0
  198. package/src/workflow/index.ts +396 -0
  199. package/src/workflow/plan-mode.ts +356 -0
  200. package/src/workflow/test-runner.ts +345 -0
  201. package/tests/adapters/openai.test.ts +145 -0
  202. package/tests/config/config.test.ts +208 -0
  203. package/tests/generators/generators.test.ts +185 -0
  204. package/tests/types/consensus.test.ts +152 -0
  205. package/tests/types/project.test.ts +134 -0
  206. package/tests/workflow/consensus.test.ts +221 -0
  207. package/tests/workflow/test-runner.test.ts +214 -0
  208. package/tsconfig.json +25 -0
  209. 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
+ });