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,299 @@
1
+ /**
2
+ * Consensus workflow module
3
+ * Handles the iterative consensus-building process between Claude and OpenAI
4
+ */
5
+
6
+ import type { ConsensusResult, ConsensusIteration, ConsensusConfig } from '../types/consensus.js';
7
+ import { DEFAULT_CONSENSUS_CONFIG } from '../types/consensus.js';
8
+ import { requestConsensus } from '../adapters/openai.js';
9
+ import { revisePlan } from '../adapters/claude.js';
10
+ import { recordConsensusIteration } from '../state/index.js';
11
+
12
+ /**
13
+ * Options for consensus iteration
14
+ */
15
+ export interface ConsensusOptions {
16
+ projectDir: string;
17
+ config?: Partial<ConsensusConfig>;
18
+ onIteration?: (iteration: number, result: ConsensusResult) => void;
19
+ onRevision?: (iteration: number, revisedPlan: string) => void;
20
+ }
21
+
22
+ /**
23
+ * Result of the consensus process
24
+ */
25
+ export interface ConsensusProcessResult {
26
+ approved: boolean;
27
+ finalPlan: string;
28
+ finalScore: number;
29
+ iterations: ConsensusIteration[];
30
+ totalIterations: number;
31
+ }
32
+
33
+ /**
34
+ * Format a plan for consensus review
35
+ * Structures the plan in a way that's optimal for review
36
+ *
37
+ * @param plan - The raw plan content
38
+ * @param context - Project context
39
+ * @returns Formatted plan string
40
+ */
41
+ export function formatPlanForReview(plan: string, context: string): string {
42
+ return `
43
+ ## Development Plan
44
+
45
+ ${plan}
46
+
47
+ ## Project Context
48
+
49
+ ${context}
50
+ `.trim();
51
+ }
52
+
53
+ /**
54
+ * Extract concerns from a consensus result for revision
55
+ *
56
+ * @param result - The consensus result
57
+ * @returns Array of concerns to address
58
+ */
59
+ export function extractConcerns(result: ConsensusResult): string[] {
60
+ const concerns: string[] = [];
61
+
62
+ // Add explicit concerns
63
+ if (result.concerns && result.concerns.length > 0) {
64
+ concerns.push(...result.concerns);
65
+ }
66
+
67
+ // Add recommendations as concerns to address
68
+ if (result.recommendations && result.recommendations.length > 0) {
69
+ concerns.push(...result.recommendations.map((r) => `Consider: ${r}`));
70
+ }
71
+
72
+ return concerns;
73
+ }
74
+
75
+ /**
76
+ * Check if consensus threshold is met
77
+ *
78
+ * @param score - The consensus score
79
+ * @param threshold - The threshold to meet (default from config)
80
+ * @returns True if threshold is met
81
+ */
82
+ export function meetsThreshold(
83
+ score: number,
84
+ threshold: number = DEFAULT_CONSENSUS_CONFIG.threshold
85
+ ): boolean {
86
+ return score >= threshold;
87
+ }
88
+
89
+ /**
90
+ * Iterate until consensus is reached
91
+ *
92
+ * @param initialPlan - The initial plan to review
93
+ * @param context - Project context
94
+ * @param options - Consensus options
95
+ * @returns The consensus process result
96
+ */
97
+ export async function iterateUntilConsensus(
98
+ initialPlan: string,
99
+ context: string,
100
+ options: ConsensusOptions
101
+ ): Promise<ConsensusProcessResult> {
102
+ const {
103
+ projectDir,
104
+ config = {},
105
+ onIteration,
106
+ onRevision,
107
+ } = options;
108
+
109
+ const {
110
+ threshold = DEFAULT_CONSENSUS_CONFIG.threshold,
111
+ maxIterations = DEFAULT_CONSENSUS_CONFIG.maxIterations,
112
+ } = config;
113
+
114
+ const iterations: ConsensusIteration[] = [];
115
+ let currentPlan = initialPlan;
116
+ let iteration = 0;
117
+
118
+ while (iteration < maxIterations) {
119
+ iteration++;
120
+
121
+ // Request consensus review from OpenAI
122
+ const consensusResult = await requestConsensus(currentPlan, context, config);
123
+
124
+ // Record the iteration
125
+ const iterationRecord: ConsensusIteration = {
126
+ iteration,
127
+ plan: currentPlan,
128
+ result: consensusResult,
129
+ timestamp: new Date().toISOString(),
130
+ };
131
+
132
+ iterations.push(iterationRecord);
133
+
134
+ // Save to project state
135
+ await recordConsensusIteration(projectDir, iterationRecord);
136
+
137
+ // Notify callback
138
+ if (onIteration) {
139
+ onIteration(iteration, consensusResult);
140
+ }
141
+
142
+ // Check if we've reached consensus
143
+ if (meetsThreshold(consensusResult.score, threshold)) {
144
+ return {
145
+ approved: true,
146
+ finalPlan: currentPlan,
147
+ finalScore: consensusResult.score,
148
+ iterations,
149
+ totalIterations: iteration,
150
+ };
151
+ }
152
+
153
+ // If not at max iterations, revise the plan
154
+ if (iteration < maxIterations) {
155
+ const concerns = extractConcerns(consensusResult);
156
+
157
+ // Use Claude to revise the plan
158
+ const revisionResult = await revisePlan(
159
+ currentPlan,
160
+ consensusResult.analysis,
161
+ concerns
162
+ );
163
+
164
+ if (revisionResult.success && revisionResult.response) {
165
+ currentPlan = revisionResult.response;
166
+
167
+ if (onRevision) {
168
+ onRevision(iteration, currentPlan);
169
+ }
170
+ } else {
171
+ // If revision fails, try to continue with current plan
172
+ console.warn(`Plan revision failed at iteration ${iteration}:`, revisionResult.error);
173
+ }
174
+ }
175
+ }
176
+
177
+ // Max iterations reached without consensus
178
+ const lastIteration = iterations[iterations.length - 1];
179
+ return {
180
+ approved: false,
181
+ finalPlan: currentPlan,
182
+ finalScore: lastIteration?.result.score || 0,
183
+ iterations,
184
+ totalIterations: iteration,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Get a summary of the consensus process
190
+ *
191
+ * @param result - The consensus process result
192
+ * @returns Human-readable summary
193
+ */
194
+ export function summarizeConsensusProcess(result: ConsensusProcessResult): string {
195
+ const lines: string[] = [];
196
+
197
+ lines.push(`## Consensus Summary`);
198
+ lines.push('');
199
+ lines.push(`**Status:** ${result.approved ? 'APPROVED' : 'NOT APPROVED'}`);
200
+ lines.push(`**Final Score:** ${result.finalScore}%`);
201
+ lines.push(`**Total Iterations:** ${result.totalIterations}`);
202
+ lines.push('');
203
+
204
+ lines.push(`### Iteration History`);
205
+ lines.push('');
206
+
207
+ for (const iteration of result.iterations) {
208
+ lines.push(`#### Iteration ${iteration.iteration}`);
209
+ lines.push(`- Score: ${iteration.result.score}%`);
210
+ lines.push(`- Strengths: ${iteration.result.strengths?.length || 0}`);
211
+ lines.push(`- Concerns: ${iteration.result.concerns?.length || 0}`);
212
+ lines.push('');
213
+ }
214
+
215
+ if (!result.approved) {
216
+ const lastResult = result.iterations[result.iterations.length - 1]?.result;
217
+ if (lastResult?.concerns && lastResult.concerns.length > 0) {
218
+ lines.push(`### Remaining Concerns`);
219
+ lines.push('');
220
+ for (const concern of lastResult.concerns) {
221
+ lines.push(`- ${concern}`);
222
+ }
223
+ }
224
+ }
225
+
226
+ return lines.join('\n');
227
+ }
228
+
229
+ /**
230
+ * Validate a plan structure has required sections
231
+ *
232
+ * @param plan - The plan to validate
233
+ * @returns Validation result with missing sections
234
+ */
235
+ export function validatePlanStructure(plan: string): {
236
+ valid: boolean;
237
+ missingSections: string[];
238
+ } {
239
+ const requiredSections = [
240
+ 'Background',
241
+ 'Goals',
242
+ 'Milestones',
243
+ 'Tasks',
244
+ 'Test',
245
+ ];
246
+
247
+ const missingSections: string[] = [];
248
+
249
+ for (const section of requiredSections) {
250
+ // Check for section header (case-insensitive)
251
+ const pattern = new RegExp(`(^|\\n)#+\\s*${section}`, 'i');
252
+ if (!pattern.test(plan)) {
253
+ missingSections.push(section);
254
+ }
255
+ }
256
+
257
+ return {
258
+ valid: missingSections.length === 0,
259
+ missingSections,
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Calculate average score across iterations
265
+ *
266
+ * @param iterations - The consensus iterations
267
+ * @returns Average score
268
+ */
269
+ export function calculateAverageScore(iterations: ConsensusIteration[]): number {
270
+ if (iterations.length === 0) return 0;
271
+
272
+ const sum = iterations.reduce((acc, it) => acc + it.result.score, 0);
273
+ return Math.round(sum / iterations.length);
274
+ }
275
+
276
+ /**
277
+ * Get the score trend across iterations
278
+ *
279
+ * @param iterations - The consensus iterations
280
+ * @returns 'improving', 'declining', or 'stable'
281
+ */
282
+ export function getScoreTrend(
283
+ iterations: ConsensusIteration[]
284
+ ): 'improving' | 'declining' | 'stable' {
285
+ if (iterations.length < 2) return 'stable';
286
+
287
+ const scores = iterations.map((it) => it.result.score);
288
+ const firstHalf = scores.slice(0, Math.floor(scores.length / 2));
289
+ const secondHalf = scores.slice(Math.floor(scores.length / 2));
290
+
291
+ const firstAvg = firstHalf.reduce((a, b) => a + b, 0) / firstHalf.length;
292
+ const secondAvg = secondHalf.reduce((a, b) => a + b, 0) / secondHalf.length;
293
+
294
+ const diff = secondAvg - firstAvg;
295
+
296
+ if (diff > 5) return 'improving';
297
+ if (diff < -5) return 'declining';
298
+ return 'stable';
299
+ }