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,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Agent SDK adapter
|
|
3
|
+
* Wraps the Claude Agent SDK for code execution and generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { query, type SDKMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options for executing a prompt through Claude
|
|
10
|
+
*/
|
|
11
|
+
export interface ClaudeExecuteOptions {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
allowedTools?: string[];
|
|
14
|
+
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
|
|
15
|
+
systemPrompt?: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
onMessage?: (message: SDKMessage) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Result from Claude execution
|
|
22
|
+
*/
|
|
23
|
+
export interface ClaudeExecuteResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
response: string;
|
|
26
|
+
toolCalls: ToolCallRecord[];
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Record of a tool call made by Claude
|
|
32
|
+
*/
|
|
33
|
+
export interface ToolCallRecord {
|
|
34
|
+
tool: string;
|
|
35
|
+
input: Record<string, unknown>;
|
|
36
|
+
output?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default allowed tools for autonomous operation
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_ALLOWED_TOOLS = [
|
|
43
|
+
'Read',
|
|
44
|
+
'Write',
|
|
45
|
+
'Edit',
|
|
46
|
+
'MultiEdit',
|
|
47
|
+
'Bash',
|
|
48
|
+
'Grep',
|
|
49
|
+
'Glob',
|
|
50
|
+
'LS',
|
|
51
|
+
'TodoRead',
|
|
52
|
+
'TodoWrite',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Execute a prompt through the Claude Agent SDK
|
|
57
|
+
*
|
|
58
|
+
* @param prompt - The prompt to execute
|
|
59
|
+
* @param options - Execution options
|
|
60
|
+
* @returns The execution result
|
|
61
|
+
*/
|
|
62
|
+
export async function executePrompt(
|
|
63
|
+
prompt: string,
|
|
64
|
+
options: ClaudeExecuteOptions = {}
|
|
65
|
+
): Promise<ClaudeExecuteResult> {
|
|
66
|
+
const {
|
|
67
|
+
cwd,
|
|
68
|
+
allowedTools = DEFAULT_ALLOWED_TOOLS,
|
|
69
|
+
permissionMode = 'bypassPermissions',
|
|
70
|
+
systemPrompt,
|
|
71
|
+
onMessage,
|
|
72
|
+
} = options;
|
|
73
|
+
|
|
74
|
+
const toolCalls: ToolCallRecord[] = [];
|
|
75
|
+
let response = '';
|
|
76
|
+
let error: string | undefined;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const result = query({
|
|
80
|
+
prompt,
|
|
81
|
+
options: {
|
|
82
|
+
cwd,
|
|
83
|
+
allowedTools,
|
|
84
|
+
permissionMode,
|
|
85
|
+
systemPrompt: systemPrompt || { type: 'preset', preset: 'claude_code' },
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
for await (const message of result) {
|
|
90
|
+
// Call the message handler if provided
|
|
91
|
+
if (onMessage) {
|
|
92
|
+
onMessage(message);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Process different message types
|
|
96
|
+
if (message.type === 'assistant') {
|
|
97
|
+
const assistantMessage = message as { type: 'assistant'; message: { content: string | Array<{ type: string; text?: string }> } };
|
|
98
|
+
if (typeof assistantMessage.message.content === 'string') {
|
|
99
|
+
response += assistantMessage.message.content;
|
|
100
|
+
} else if (Array.isArray(assistantMessage.message.content)) {
|
|
101
|
+
for (const block of assistantMessage.message.content) {
|
|
102
|
+
if (block.type === 'text' && block.text) {
|
|
103
|
+
response += block.text;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else if (message.type === 'result') {
|
|
108
|
+
// Handle result messages which may contain tool information
|
|
109
|
+
const resultMessage = message as { type: 'result'; result?: string; error?: { message: string } };
|
|
110
|
+
if (resultMessage.error) {
|
|
111
|
+
error = resultMessage.error.message;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: !error,
|
|
118
|
+
response: response.trim(),
|
|
119
|
+
toolCalls,
|
|
120
|
+
error,
|
|
121
|
+
};
|
|
122
|
+
} catch (err) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
response: '',
|
|
126
|
+
toolCalls,
|
|
127
|
+
error: err instanceof Error ? err.message : 'Unknown error executing prompt',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Execute code generation for a specific task
|
|
134
|
+
*
|
|
135
|
+
* @param task - The task description
|
|
136
|
+
* @param context - Additional context about the project
|
|
137
|
+
* @param options - Execution options
|
|
138
|
+
*/
|
|
139
|
+
export async function generateCode(
|
|
140
|
+
task: string,
|
|
141
|
+
context: string,
|
|
142
|
+
options: ClaudeExecuteOptions = {}
|
|
143
|
+
): Promise<ClaudeExecuteResult> {
|
|
144
|
+
const prompt = `
|
|
145
|
+
## Task
|
|
146
|
+
${task}
|
|
147
|
+
|
|
148
|
+
## Project Context
|
|
149
|
+
${context}
|
|
150
|
+
|
|
151
|
+
## Instructions
|
|
152
|
+
1. Implement the task following best practices
|
|
153
|
+
2. Write clean, well-documented code
|
|
154
|
+
3. Include appropriate error handling
|
|
155
|
+
4. Follow the project's existing patterns and style
|
|
156
|
+
|
|
157
|
+
Please implement this task now.
|
|
158
|
+
`.trim();
|
|
159
|
+
|
|
160
|
+
return executePrompt(prompt, {
|
|
161
|
+
...options,
|
|
162
|
+
allowedTools: options.allowedTools || [
|
|
163
|
+
...DEFAULT_ALLOWED_TOOLS,
|
|
164
|
+
'WebSearch',
|
|
165
|
+
'WebFetch',
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Run tests and capture results
|
|
172
|
+
*
|
|
173
|
+
* @param testCommand - The test command to run
|
|
174
|
+
* @param cwd - Working directory
|
|
175
|
+
*/
|
|
176
|
+
export async function runTests(
|
|
177
|
+
testCommand: string,
|
|
178
|
+
cwd?: string
|
|
179
|
+
): Promise<ClaudeExecuteResult> {
|
|
180
|
+
const prompt = `
|
|
181
|
+
Run the following test command and report the results:
|
|
182
|
+
|
|
183
|
+
\`\`\`bash
|
|
184
|
+
${testCommand}
|
|
185
|
+
\`\`\`
|
|
186
|
+
|
|
187
|
+
After running the tests:
|
|
188
|
+
1. Report the total number of tests
|
|
189
|
+
2. Report how many passed and failed
|
|
190
|
+
3. If there are failures, summarize what failed
|
|
191
|
+
4. Provide any relevant error messages
|
|
192
|
+
`.trim();
|
|
193
|
+
|
|
194
|
+
return executePrompt(prompt, {
|
|
195
|
+
cwd,
|
|
196
|
+
allowedTools: ['Bash', 'Read'],
|
|
197
|
+
permissionMode: 'bypassPermissions',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Analyze codebase to understand structure and patterns
|
|
203
|
+
*
|
|
204
|
+
* @param cwd - Working directory of the project
|
|
205
|
+
*/
|
|
206
|
+
export async function analyzeCodebase(cwd: string): Promise<ClaudeExecuteResult> {
|
|
207
|
+
const prompt = `
|
|
208
|
+
Analyze this codebase and provide:
|
|
209
|
+
|
|
210
|
+
1. **Project Structure**: Overview of directories and their purposes
|
|
211
|
+
2. **Key Technologies**: Languages, frameworks, and major dependencies
|
|
212
|
+
3. **Architecture Patterns**: Design patterns and architectural decisions observed
|
|
213
|
+
4. **Code Style**: Naming conventions, formatting, and documentation style
|
|
214
|
+
5. **Test Setup**: Testing framework and test organization
|
|
215
|
+
6. **Build/Deploy**: Build tools and deployment configuration
|
|
216
|
+
|
|
217
|
+
Be concise but thorough in your analysis.
|
|
218
|
+
`.trim();
|
|
219
|
+
|
|
220
|
+
return executePrompt(prompt, {
|
|
221
|
+
cwd,
|
|
222
|
+
allowedTools: ['Read', 'Glob', 'Grep', 'LS'],
|
|
223
|
+
permissionMode: 'default', // Read-only analysis
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Create a development plan from a specification
|
|
229
|
+
*
|
|
230
|
+
* @param specification - The project specification
|
|
231
|
+
* @param context - Additional context (existing code, etc.)
|
|
232
|
+
*/
|
|
233
|
+
export async function createPlan(
|
|
234
|
+
specification: string,
|
|
235
|
+
context: string = ''
|
|
236
|
+
): Promise<ClaudeExecuteResult> {
|
|
237
|
+
const prompt = `
|
|
238
|
+
Create a detailed development plan for the following specification:
|
|
239
|
+
|
|
240
|
+
## Specification
|
|
241
|
+
${specification}
|
|
242
|
+
|
|
243
|
+
${context ? `## Additional Context\n${context}` : ''}
|
|
244
|
+
|
|
245
|
+
## Required Plan Sections
|
|
246
|
+
|
|
247
|
+
1. **Background & Context**: Summarize the project requirements
|
|
248
|
+
2. **Goals & Objectives**: List measurable objectives
|
|
249
|
+
3. **Milestones**: Break down into major phases
|
|
250
|
+
4. **Tasks**: Detail specific tasks for each milestone
|
|
251
|
+
5. **Test Plan**: Define tests for each task
|
|
252
|
+
6. **Risks & Mitigations**: Identify potential issues
|
|
253
|
+
|
|
254
|
+
Format the plan as markdown with clear sections and bullet points.
|
|
255
|
+
`.trim();
|
|
256
|
+
|
|
257
|
+
return executePrompt(prompt, {
|
|
258
|
+
allowedTools: ['Read', 'Glob'],
|
|
259
|
+
permissionMode: 'plan',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Revise a plan based on feedback
|
|
265
|
+
*
|
|
266
|
+
* @param originalPlan - The original plan
|
|
267
|
+
* @param feedback - Feedback to incorporate
|
|
268
|
+
* @param concerns - Specific concerns to address
|
|
269
|
+
*/
|
|
270
|
+
export async function revisePlan(
|
|
271
|
+
originalPlan: string,
|
|
272
|
+
feedback: string,
|
|
273
|
+
concerns: string[]
|
|
274
|
+
): Promise<ClaudeExecuteResult> {
|
|
275
|
+
const prompt = `
|
|
276
|
+
Revise the following plan based on the feedback provided:
|
|
277
|
+
|
|
278
|
+
## Original Plan
|
|
279
|
+
${originalPlan}
|
|
280
|
+
|
|
281
|
+
## Feedback
|
|
282
|
+
${feedback}
|
|
283
|
+
|
|
284
|
+
## Specific Concerns to Address
|
|
285
|
+
${concerns.map((c, i) => `${i + 1}. ${c}`).join('\n')}
|
|
286
|
+
|
|
287
|
+
## Instructions
|
|
288
|
+
1. Address each concern specifically
|
|
289
|
+
2. Maintain the same plan structure
|
|
290
|
+
3. Note what changed from the original
|
|
291
|
+
4. Ensure the revised plan is complete and actionable
|
|
292
|
+
`.trim();
|
|
293
|
+
|
|
294
|
+
return executePrompt(prompt, {
|
|
295
|
+
allowedTools: [],
|
|
296
|
+
permissionMode: 'plan',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI API adapter
|
|
3
|
+
* Handles consensus reviews and plan validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import OpenAI from 'openai';
|
|
7
|
+
import type { ConsensusResult, ConsensusConfig, OpenAIModel } from '../types/index.js';
|
|
8
|
+
import { getOpenAIToken } from '../auth/index.js';
|
|
9
|
+
import { DEFAULT_CONSENSUS_CONFIG } from '../types/consensus.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create an OpenAI client with stored credentials
|
|
13
|
+
*/
|
|
14
|
+
export async function createClient(): Promise<OpenAI> {
|
|
15
|
+
const apiKey = await getOpenAIToken();
|
|
16
|
+
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
throw new Error('OpenAI API key not found. Run: popeye-cli auth openai');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return new OpenAI({ apiKey });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Request consensus review from OpenAI
|
|
26
|
+
*
|
|
27
|
+
* @param plan - The development plan to review
|
|
28
|
+
* @param context - Project context
|
|
29
|
+
* @param config - Consensus configuration
|
|
30
|
+
* @returns Consensus result
|
|
31
|
+
*/
|
|
32
|
+
export async function requestConsensus(
|
|
33
|
+
plan: string,
|
|
34
|
+
context: string,
|
|
35
|
+
config: Partial<ConsensusConfig> = {}
|
|
36
|
+
): Promise<ConsensusResult> {
|
|
37
|
+
const {
|
|
38
|
+
openaiModel = DEFAULT_CONSENSUS_CONFIG.openaiModel,
|
|
39
|
+
temperature = DEFAULT_CONSENSUS_CONFIG.temperature,
|
|
40
|
+
maxTokens = DEFAULT_CONSENSUS_CONFIG.maxTokens,
|
|
41
|
+
} = config;
|
|
42
|
+
|
|
43
|
+
const client = await createClient();
|
|
44
|
+
|
|
45
|
+
// Build the consensus review prompt (matches spec section 11.1)
|
|
46
|
+
const prompt = buildConsensusPrompt(plan, context);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const completion = await client.chat.completions.create({
|
|
50
|
+
model: openaiModel,
|
|
51
|
+
messages: [{ role: 'user', content: prompt }],
|
|
52
|
+
temperature,
|
|
53
|
+
max_tokens: maxTokens,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const response = completion.choices[0]?.message?.content || '';
|
|
57
|
+
return parseConsensusResponse(response);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof OpenAI.RateLimitError) {
|
|
60
|
+
// Implement exponential backoff retry
|
|
61
|
+
await sleep(5000);
|
|
62
|
+
return requestConsensus(plan, context, config);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build the consensus review prompt
|
|
70
|
+
* Follows spec section 11.1 format
|
|
71
|
+
*/
|
|
72
|
+
function buildConsensusPrompt(plan: string, context: string): string {
|
|
73
|
+
return `You are a senior software architect reviewing a development plan.
|
|
74
|
+
Analyze the following plan for completeness, correctness, and feasibility.
|
|
75
|
+
|
|
76
|
+
PROJECT CONTEXT:
|
|
77
|
+
${context}
|
|
78
|
+
|
|
79
|
+
PROPOSED PLAN:
|
|
80
|
+
${plan}
|
|
81
|
+
|
|
82
|
+
Please provide:
|
|
83
|
+
1. ANALYSIS: Detailed review of the plan
|
|
84
|
+
2. STRENGTHS: What works well
|
|
85
|
+
3. CONCERNS: Issues or gaps identified
|
|
86
|
+
4. RECOMMENDATIONS: Specific improvements
|
|
87
|
+
5. CONSENSUS SCORE: A percentage (0-100%) indicating your agreement
|
|
88
|
+
- 95-100%: Ready for execution
|
|
89
|
+
- 80-94%: Minor revisions needed
|
|
90
|
+
- 60-79%: Significant revisions needed
|
|
91
|
+
- Below 60%: Major rework required
|
|
92
|
+
|
|
93
|
+
Format your consensus score as: CONSENSUS: [X]%
|
|
94
|
+
|
|
95
|
+
Be thorough but constructive in your feedback.`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse the consensus response from OpenAI
|
|
100
|
+
* Extracts score, analysis, concerns, etc.
|
|
101
|
+
*/
|
|
102
|
+
export function parseConsensusResponse(response: string): ConsensusResult {
|
|
103
|
+
// Extract consensus score
|
|
104
|
+
const scoreMatch = response.match(/CONSENSUS:\s*(\d+)%/i);
|
|
105
|
+
const score = scoreMatch ? parseInt(scoreMatch[1], 10) : 0;
|
|
106
|
+
|
|
107
|
+
// Extract sections
|
|
108
|
+
const analysis = extractSection(response, 'ANALYSIS', 'STRENGTHS');
|
|
109
|
+
const strengthsText = extractSection(response, 'STRENGTHS', 'CONCERNS');
|
|
110
|
+
const concernsText = extractSection(response, 'CONCERNS', 'RECOMMENDATIONS');
|
|
111
|
+
const recommendationsText = extractSection(response, 'RECOMMENDATIONS', 'CONSENSUS');
|
|
112
|
+
|
|
113
|
+
// Parse lists from sections
|
|
114
|
+
const strengths = parseList(strengthsText);
|
|
115
|
+
const concerns = parseList(concernsText);
|
|
116
|
+
const recommendations = parseList(recommendationsText);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
score,
|
|
120
|
+
analysis: analysis.trim(),
|
|
121
|
+
strengths,
|
|
122
|
+
concerns,
|
|
123
|
+
recommendations,
|
|
124
|
+
approved: score >= 95,
|
|
125
|
+
rawResponse: response,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extract a section from the response between two headers
|
|
131
|
+
*/
|
|
132
|
+
function extractSection(response: string, startHeader: string, endHeader: string): string {
|
|
133
|
+
const startPattern = new RegExp(`${startHeader}[:\\s]*`, 'i');
|
|
134
|
+
const endPattern = new RegExp(`${endHeader}[:\\s]*`, 'i');
|
|
135
|
+
|
|
136
|
+
const startMatch = response.match(startPattern);
|
|
137
|
+
if (!startMatch) return '';
|
|
138
|
+
|
|
139
|
+
const startIndex = startMatch.index! + startMatch[0].length;
|
|
140
|
+
const endMatch = response.slice(startIndex).match(endPattern);
|
|
141
|
+
|
|
142
|
+
if (!endMatch) {
|
|
143
|
+
return response.slice(startIndex).trim();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return response.slice(startIndex, startIndex + endMatch.index!).trim();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Parse a bulleted or numbered list from text
|
|
151
|
+
*/
|
|
152
|
+
function parseList(text: string): string[] {
|
|
153
|
+
if (!text) return [];
|
|
154
|
+
|
|
155
|
+
const lines = text.split('\n');
|
|
156
|
+
const items: string[] = [];
|
|
157
|
+
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
// Match bullets (-, *, +) or numbers (1., 2., etc.)
|
|
160
|
+
const match = line.match(/^[\s]*[-*+][\s]+(.+)$|^[\s]*\d+\.[\s]+(.+)$/);
|
|
161
|
+
if (match) {
|
|
162
|
+
const item = (match[1] || match[2]).trim();
|
|
163
|
+
if (item) {
|
|
164
|
+
items.push(item);
|
|
165
|
+
}
|
|
166
|
+
} else if (line.trim() && !line.match(/^[A-Z]+:/)) {
|
|
167
|
+
// Non-empty line that's not a header
|
|
168
|
+
items.push(line.trim());
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return items;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Helper sleep function for rate limiting
|
|
177
|
+
*/
|
|
178
|
+
function sleep(ms: number): Promise<void> {
|
|
179
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validate that a model is available
|
|
184
|
+
*/
|
|
185
|
+
export async function validateModel(model: OpenAIModel): Promise<boolean> {
|
|
186
|
+
try {
|
|
187
|
+
const client = await createClient();
|
|
188
|
+
const models = await client.models.list();
|
|
189
|
+
return models.data.some((m) => m.id === model);
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* List available models
|
|
197
|
+
*/
|
|
198
|
+
export async function listAvailableModels(): Promise<string[]> {
|
|
199
|
+
try {
|
|
200
|
+
const client = await createClient();
|
|
201
|
+
const models = await client.models.list();
|
|
202
|
+
|
|
203
|
+
return models.data
|
|
204
|
+
.filter(
|
|
205
|
+
(m) =>
|
|
206
|
+
m.id.includes('gpt-4') ||
|
|
207
|
+
m.id.includes('gpt-3.5') ||
|
|
208
|
+
m.id.startsWith('o1')
|
|
209
|
+
)
|
|
210
|
+
.map((m) => m.id)
|
|
211
|
+
.sort();
|
|
212
|
+
} catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Expand a brief idea into a full specification using OpenAI
|
|
219
|
+
*
|
|
220
|
+
* @param idea - The brief project idea
|
|
221
|
+
* @param language - Target programming language
|
|
222
|
+
* @returns Expanded specification
|
|
223
|
+
*/
|
|
224
|
+
export async function expandIdea(
|
|
225
|
+
idea: string,
|
|
226
|
+
language: 'python' | 'typescript'
|
|
227
|
+
): Promise<string> {
|
|
228
|
+
const client = await createClient();
|
|
229
|
+
|
|
230
|
+
const prompt = `You are a senior software architect. A user wants to build a project with the following idea:
|
|
231
|
+
|
|
232
|
+
"${idea}"
|
|
233
|
+
|
|
234
|
+
The project will be implemented in ${language === 'python' ? 'Python' : 'TypeScript'}.
|
|
235
|
+
|
|
236
|
+
Expand this into a complete software specification including:
|
|
237
|
+
|
|
238
|
+
1. **Project Overview**: A clear description of what will be built
|
|
239
|
+
2. **Core Features**: List of main features and functionality
|
|
240
|
+
3. **Technical Requirements**:
|
|
241
|
+
- Language and framework choices
|
|
242
|
+
- Database requirements (if any)
|
|
243
|
+
- External APIs or services needed
|
|
244
|
+
- Authentication requirements (if any)
|
|
245
|
+
4. **Architecture Overview**: High-level system design
|
|
246
|
+
5. **API Specification** (if applicable): Key endpoints and their purposes
|
|
247
|
+
6. **Data Models**: Key entities and their relationships
|
|
248
|
+
7. **Non-Functional Requirements**: Performance, security, scalability considerations
|
|
249
|
+
8. **Deployment**: Docker configuration and deployment approach
|
|
250
|
+
|
|
251
|
+
Be specific and actionable. The specification should be detailed enough that a developer could implement it without further clarification.`;
|
|
252
|
+
|
|
253
|
+
const completion = await client.chat.completions.create({
|
|
254
|
+
model: 'gpt-4o',
|
|
255
|
+
messages: [{ role: 'user', content: prompt }],
|
|
256
|
+
temperature: 0.7,
|
|
257
|
+
max_tokens: 4096,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return completion.choices[0]?.message?.content || idea;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get feedback on generated code
|
|
265
|
+
*
|
|
266
|
+
* @param code - The code to review
|
|
267
|
+
* @param context - Context about what the code should do
|
|
268
|
+
*/
|
|
269
|
+
export async function reviewCode(code: string, context: string): Promise<ConsensusResult> {
|
|
270
|
+
const client = await createClient();
|
|
271
|
+
|
|
272
|
+
const prompt = `You are a senior software engineer reviewing code. Review the following code:
|
|
273
|
+
|
|
274
|
+
CONTEXT:
|
|
275
|
+
${context}
|
|
276
|
+
|
|
277
|
+
CODE:
|
|
278
|
+
\`\`\`
|
|
279
|
+
${code}
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
Provide:
|
|
283
|
+
1. ANALYSIS: Overall code quality assessment
|
|
284
|
+
2. STRENGTHS: What's done well
|
|
285
|
+
3. CONCERNS: Issues, bugs, or improvements needed
|
|
286
|
+
4. RECOMMENDATIONS: Specific fixes or enhancements
|
|
287
|
+
5. CONSENSUS SCORE: A percentage (0-100%) of how production-ready this code is
|
|
288
|
+
|
|
289
|
+
Format your score as: CONSENSUS: [X]%`;
|
|
290
|
+
|
|
291
|
+
const completion = await client.chat.completions.create({
|
|
292
|
+
model: 'gpt-4o',
|
|
293
|
+
messages: [{ role: 'user', content: prompt }],
|
|
294
|
+
temperature: 0.3,
|
|
295
|
+
max_tokens: 2048,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const response = completion.choices[0]?.message?.content || '';
|
|
299
|
+
return parseConsensusResponse(response);
|
|
300
|
+
}
|