feat: enhance callClaude function with improved error handling and user feedback
- Add loading indicator with spinner animation during API calls - Implement retry logic with exponential backoff (1s, 2s, 4s) - Add dynamic task count adjustment when API calls fail - Improve error handling with detailed error messages - Add interactive user options when all retries are exhausted - Optimize token allocation based on PRD size - Add comprehensive validation of API responses - Improve error recovery strategies for various failure scenarios - Update parsePRD function to properly handle errors from callClaude
This commit is contained in:
151
scripts/dev.js
151
scripts/dev.js
@@ -45,6 +45,7 @@ import path from 'path';
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
|
import readline from 'readline';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
@@ -108,10 +109,37 @@ function writeJSON(filepath, data) {
|
|||||||
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8');
|
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callClaude(prdContent, prdPath, numTasks) {
|
// Add a simple loading indicator function
|
||||||
|
function startLoadingIndicator(message) {
|
||||||
|
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
process.stdout.write(`${message} `);
|
||||||
|
|
||||||
|
return setInterval(() => {
|
||||||
|
readline.cursorTo(process.stdout, message.length + 1);
|
||||||
|
process.stdout.write(frames[i]);
|
||||||
|
i = (i + 1) % frames.length;
|
||||||
|
}, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopLoadingIndicator(interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
readline.cursorTo(process.stdout, 0);
|
||||||
|
readline.clearLine(process.stdout, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callClaude(prdContent, prdPath, numTasks, retryCount = 0) {
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
const INITIAL_BACKOFF_MS = 1000;
|
||||||
|
|
||||||
log('info', `Starting Claude API call to process PRD from ${prdPath}...`);
|
log('info', `Starting Claude API call to process PRD from ${prdPath}...`);
|
||||||
log('debug', `PRD content length: ${prdContent.length} characters`);
|
log('debug', `PRD content length: ${prdContent.length} characters`);
|
||||||
|
|
||||||
|
// Start loading indicator
|
||||||
|
const loadingMessage = `Waiting for Claude to generate tasks${retryCount > 0 ? ` (retry ${retryCount}/${MAX_RETRIES})` : ''}...`;
|
||||||
|
const loadingIndicator = startLoadingIndicator(loadingMessage);
|
||||||
|
|
||||||
const TASKS_JSON_TEMPLATE = `
|
const TASKS_JSON_TEMPLATE = `
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
@@ -154,10 +182,26 @@ async function callClaude(prdContent, prdPath, numTasks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log('debug', "System prompt:", systemPrompt);
|
log('debug', "System prompt:", systemPrompt);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Calculate appropriate max tokens based on PRD size
|
||||||
|
let maxTokens = CONFIG.maxTokens;
|
||||||
|
// Rough estimate: 1 token ≈ 4 characters
|
||||||
|
const estimatedPrdTokens = Math.ceil(prdContent.length / 4);
|
||||||
|
// Ensure we have enough tokens for the response
|
||||||
|
if (estimatedPrdTokens > maxTokens / 2) {
|
||||||
|
// If PRD is large, increase max tokens if possible
|
||||||
|
const suggestedMaxTokens = Math.min(32000, estimatedPrdTokens * 2);
|
||||||
|
if (suggestedMaxTokens > maxTokens) {
|
||||||
|
log('info', `PRD is large (est. ${estimatedPrdTokens} tokens). Increasing max_tokens to ${suggestedMaxTokens}.`);
|
||||||
|
maxTokens = suggestedMaxTokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log('info', "Sending request to Claude API...");
|
log('info', "Sending request to Claude API...");
|
||||||
|
|
||||||
const response = await anthropic.messages.create({
|
const response = await anthropic.messages.create({
|
||||||
max_tokens: CONFIG.maxTokens,
|
max_tokens: maxTokens,
|
||||||
model: CONFIG.model,
|
model: CONFIG.model,
|
||||||
temperature: CONFIG.temperature,
|
temperature: CONFIG.temperature,
|
||||||
messages: [
|
messages: [
|
||||||
@@ -168,6 +212,9 @@ async function callClaude(prdContent, prdPath, numTasks) {
|
|||||||
],
|
],
|
||||||
system: systemPrompt
|
system: systemPrompt
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Stop loading indicator
|
||||||
|
stopLoadingIndicator(loadingIndicator);
|
||||||
log('info', "Received response from Claude API!");
|
log('info', "Received response from Claude API!");
|
||||||
|
|
||||||
// Extract the text content from the response
|
// Extract the text content from the response
|
||||||
@@ -186,12 +233,104 @@ async function callClaude(prdContent, prdPath, numTasks) {
|
|||||||
|
|
||||||
// Try to parse the response as JSON
|
// Try to parse the response as JSON
|
||||||
const parsedJson = JSON.parse(jsonText);
|
const parsedJson = JSON.parse(jsonText);
|
||||||
|
|
||||||
|
// Check if the response seems incomplete (e.g., missing closing brackets)
|
||||||
|
if (!parsedJson.tasks || parsedJson.tasks.length === 0) {
|
||||||
|
log('warn', "Parsed JSON has no tasks. Response may be incomplete.");
|
||||||
|
|
||||||
|
// If we have a numTasks parameter and it's greater than 5, try again with fewer tasks
|
||||||
|
if (numTasks && numTasks > 5 && retryCount < MAX_RETRIES) {
|
||||||
|
const reducedTasks = Math.max(5, Math.floor(numTasks * 0.7)); // Reduce by 30%, minimum 5
|
||||||
|
log('info', `Retrying with reduced task count: ${reducedTasks} (was ${numTasks})`);
|
||||||
|
return callClaude(prdContent, prdPath, reducedTasks, retryCount + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log('info', `Successfully parsed JSON with ${parsedJson.tasks?.length || 0} tasks`);
|
log('info', `Successfully parsed JSON with ${parsedJson.tasks?.length || 0} tasks`);
|
||||||
return parsedJson;
|
return parsedJson;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('error', "Failed to parse Claude's response as JSON:", error);
|
log('error', "Failed to parse Claude's response as JSON:", error);
|
||||||
log('debug', "Raw response:", textContent);
|
log('debug', "Raw response:", textContent);
|
||||||
throw new Error("Failed to parse Claude's response as JSON. See console for details.");
|
|
||||||
|
// Check if we should retry with different parameters
|
||||||
|
if (retryCount < MAX_RETRIES) {
|
||||||
|
// If we have a numTasks parameter, try again with fewer tasks
|
||||||
|
if (numTasks && numTasks > 3) {
|
||||||
|
const reducedTasks = Math.max(3, Math.floor(numTasks * 0.6)); // Reduce by 40%, minimum 3
|
||||||
|
log('info', `Retrying with reduced task count: ${reducedTasks} (was ${numTasks})`);
|
||||||
|
return callClaude(prdContent, prdPath, reducedTasks, retryCount + 1);
|
||||||
|
} else {
|
||||||
|
// Otherwise, just retry with the same parameters
|
||||||
|
log('info', `Retrying Claude API call (attempt ${retryCount + 1}/${MAX_RETRIES})...`);
|
||||||
|
return callClaude(prdContent, prdPath, numTasks, retryCount + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to parse Claude's response as JSON after multiple attempts. See console for details.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Stop loading indicator
|
||||||
|
stopLoadingIndicator(loadingIndicator);
|
||||||
|
|
||||||
|
log('error', "Error calling Claude API:", error);
|
||||||
|
|
||||||
|
// Implement exponential backoff for retries
|
||||||
|
if (retryCount < MAX_RETRIES) {
|
||||||
|
const backoffTime = INITIAL_BACKOFF_MS * Math.pow(2, retryCount);
|
||||||
|
log('info', `Retrying in ${backoffTime/1000} seconds (attempt ${retryCount + 1}/${MAX_RETRIES})...`);
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, backoffTime));
|
||||||
|
|
||||||
|
// If we have a numTasks parameter and it's greater than 3, try again with fewer tasks
|
||||||
|
if (numTasks && numTasks > 3) {
|
||||||
|
const reducedTasks = Math.max(3, Math.floor(numTasks * 0.7)); // Reduce by 30%, minimum 3
|
||||||
|
log('info', `Retrying with reduced task count: ${reducedTasks} (was ${numTasks})`);
|
||||||
|
return callClaude(prdContent, prdPath, reducedTasks, retryCount + 1);
|
||||||
|
} else {
|
||||||
|
// Otherwise, just retry with the same parameters
|
||||||
|
return callClaude(prdContent, prdPath, numTasks, retryCount + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've exhausted all retries, ask the user what to do
|
||||||
|
console.log("\nClaude API call failed after multiple attempts.");
|
||||||
|
console.log("Options:");
|
||||||
|
console.log("1. Retry with the same parameters");
|
||||||
|
console.log("2. Retry with fewer tasks (if applicable)");
|
||||||
|
console.log("3. Abort");
|
||||||
|
|
||||||
|
const readline = require('readline').createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
readline.question('Enter your choice (1-3): ', async (choice) => {
|
||||||
|
readline.close();
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case '1':
|
||||||
|
console.log("Retrying with the same parameters...");
|
||||||
|
resolve(await callClaude(prdContent, prdPath, numTasks, 0)); // Reset retry count
|
||||||
|
break;
|
||||||
|
case '2':
|
||||||
|
if (numTasks && numTasks > 2) {
|
||||||
|
const reducedTasks = Math.max(2, Math.floor(numTasks * 0.5)); // Reduce by 50%, minimum 2
|
||||||
|
console.log(`Retrying with reduced task count: ${reducedTasks} (was ${numTasks})...`);
|
||||||
|
resolve(await callClaude(prdContent, prdPath, reducedTasks, 0)); // Reset retry count
|
||||||
|
} else {
|
||||||
|
console.log("Cannot reduce task count further. Retrying with the same parameters...");
|
||||||
|
resolve(await callClaude(prdContent, prdPath, numTasks, 0)); // Reset retry count
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
default:
|
||||||
|
console.log("Aborting...");
|
||||||
|
reject(new Error("User aborted after multiple failed attempts"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,6 +349,8 @@ async function parsePRD(prdPath, tasksPath, numTasks) {
|
|||||||
|
|
||||||
// call claude to generate the tasks.json
|
// call claude to generate the tasks.json
|
||||||
log('info', "Calling Claude to generate tasks from PRD...");
|
log('info', "Calling Claude to generate tasks from PRD...");
|
||||||
|
|
||||||
|
try {
|
||||||
const claudeResponse = await callClaude(prdContent, prdPath, numTasks);
|
const claudeResponse = await callClaude(prdContent, prdPath, numTasks);
|
||||||
let tasks = claudeResponse.tasks || [];
|
let tasks = claudeResponse.tasks || [];
|
||||||
log('info', `Claude generated ${tasks.length} tasks from the PRD`);
|
log('info', `Claude generated ${tasks.length} tasks from the PRD`);
|
||||||
@@ -236,6 +377,10 @@ async function parsePRD(prdPath, tasksPath, numTasks) {
|
|||||||
log('info', `Writing ${tasks.length} tasks to ${tasksPath}...`);
|
log('info', `Writing ${tasks.length} tasks to ${tasksPath}...`);
|
||||||
writeJSON(tasksPath, data);
|
writeJSON(tasksPath, data);
|
||||||
log('info', `Parsed PRD from '${prdPath}' -> wrote ${tasks.length} tasks to '${tasksPath}'.`);
|
log('info', `Parsed PRD from '${prdPath}' -> wrote ${tasks.length} tasks to '${tasksPath}'.`);
|
||||||
|
} catch (error) {
|
||||||
|
log('error', "Failed to generate tasks:", error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
212
scripts/test-claude-errors.js
Executable file
212
scripts/test-claude-errors.js
Executable file
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test-claude-errors.js
|
||||||
|
*
|
||||||
|
* A test script to verify the error handling and retry logic in the callClaude function.
|
||||||
|
* This script creates a modified version of dev.js that simulates different error scenarios.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import { execSync, spawn } from 'child_process';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Create a simple PRD for testing
|
||||||
|
const createTestPRD = () => {
|
||||||
|
return `# Test PRD for Error Handling
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This is a simple test PRD to verify the error handling in the callClaude function.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
1. Create a simple web application
|
||||||
|
2. Implement user authentication
|
||||||
|
3. Add a dashboard for users
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a modified version of dev.js that simulates errors
|
||||||
|
function createErrorSimulationScript(errorType, failureCount = 2) {
|
||||||
|
// Read the original dev.js file
|
||||||
|
const devJsPath = path.join(__dirname, 'dev.js');
|
||||||
|
const devJsContent = fs.readFileSync(devJsPath, 'utf8');
|
||||||
|
|
||||||
|
// Create a modified version that simulates errors
|
||||||
|
let modifiedContent = devJsContent;
|
||||||
|
|
||||||
|
// Find the anthropic.messages.create call and replace it with our mock
|
||||||
|
const anthropicCallRegex = /const response = await anthropic\.messages\.create\(/;
|
||||||
|
|
||||||
|
let mockCode = '';
|
||||||
|
|
||||||
|
switch (errorType) {
|
||||||
|
case 'network':
|
||||||
|
mockCode = `
|
||||||
|
// Mock for network error simulation
|
||||||
|
let currentAttempt = 0;
|
||||||
|
const failureCount = ${failureCount};
|
||||||
|
|
||||||
|
// Simulate network error for the first few attempts
|
||||||
|
currentAttempt++;
|
||||||
|
console.log(\`[Mock] API call attempt \${currentAttempt}\`);
|
||||||
|
|
||||||
|
if (currentAttempt <= failureCount) {
|
||||||
|
console.log(\`[Mock] Simulating network error (attempt \${currentAttempt}/\${failureCount})\`);
|
||||||
|
throw new Error('Network error: Connection refused');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await anthropic.messages.create(`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timeout':
|
||||||
|
mockCode = `
|
||||||
|
// Mock for timeout error simulation
|
||||||
|
let currentAttempt = 0;
|
||||||
|
const failureCount = ${failureCount};
|
||||||
|
|
||||||
|
// Simulate timeout error for the first few attempts
|
||||||
|
currentAttempt++;
|
||||||
|
console.log(\`[Mock] API call attempt \${currentAttempt}\`);
|
||||||
|
|
||||||
|
if (currentAttempt <= failureCount) {
|
||||||
|
console.log(\`[Mock] Simulating timeout error (attempt \${currentAttempt}/\${failureCount})\`);
|
||||||
|
throw new Error('Request timed out after 60000ms');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await anthropic.messages.create(`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'invalid-json':
|
||||||
|
mockCode = `
|
||||||
|
// Mock for invalid JSON response
|
||||||
|
let currentAttempt = 0;
|
||||||
|
const failureCount = ${failureCount};
|
||||||
|
|
||||||
|
// Simulate invalid JSON for the first few attempts
|
||||||
|
currentAttempt++;
|
||||||
|
console.log(\`[Mock] API call attempt \${currentAttempt}\`);
|
||||||
|
|
||||||
|
if (currentAttempt <= failureCount) {
|
||||||
|
console.log(\`[Mock] Simulating invalid JSON response (attempt \${currentAttempt}/\${failureCount})\`);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
text: \`\`\`json\\n{"meta": {"projectName": "Test Project"}, "tasks": [{"id": 1, "title": "Task 1"\`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await anthropic.messages.create(`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'empty-tasks':
|
||||||
|
mockCode = `
|
||||||
|
// Mock for empty tasks array
|
||||||
|
let currentAttempt = 0;
|
||||||
|
const failureCount = ${failureCount};
|
||||||
|
|
||||||
|
// Simulate empty tasks array for the first few attempts
|
||||||
|
currentAttempt++;
|
||||||
|
console.log(\`[Mock] API call attempt \${currentAttempt}\`);
|
||||||
|
|
||||||
|
if (currentAttempt <= failureCount) {
|
||||||
|
console.log(\`[Mock] Simulating empty tasks array (attempt \${currentAttempt}/\${failureCount})\`);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
text: \`\`\`json\\n{"meta": {"projectName": "Test Project"}, "tasks": []}\\n\`\`\`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await anthropic.messages.create(`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// No modification
|
||||||
|
mockCode = `const response = await anthropic.messages.create(`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the anthropic call with our mock
|
||||||
|
modifiedContent = modifiedContent.replace(anthropicCallRegex, mockCode);
|
||||||
|
|
||||||
|
// Write the modified script to a temporary file
|
||||||
|
const tempScriptPath = path.join(__dirname, `temp-dev-${errorType}.js`);
|
||||||
|
fs.writeFileSync(tempScriptPath, modifiedContent, 'utf8');
|
||||||
|
|
||||||
|
return tempScriptPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to run a test with a specific error type
|
||||||
|
async function runErrorTest(errorType, numTasks = 5, failureCount = 2) {
|
||||||
|
console.log(`\n=== Test: ${errorType.toUpperCase()} Error Simulation ===`);
|
||||||
|
|
||||||
|
// Create a test PRD
|
||||||
|
const testPRD = createTestPRD();
|
||||||
|
const testPRDPath = path.join(__dirname, `test-prd-${errorType}.txt`);
|
||||||
|
fs.writeFileSync(testPRDPath, testPRD, 'utf8');
|
||||||
|
|
||||||
|
// Create a modified dev.js that simulates the specified error
|
||||||
|
const tempScriptPath = createErrorSimulationScript(errorType, failureCount);
|
||||||
|
|
||||||
|
console.log(`Created test PRD at ${testPRDPath}`);
|
||||||
|
console.log(`Created error simulation script at ${tempScriptPath}`);
|
||||||
|
console.log(`Running with error type: ${errorType}, failure count: ${failureCount}, tasks: ${numTasks}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Run the modified script
|
||||||
|
execSync(`node ${tempScriptPath} parse-prd --input=${testPRDPath} --tasks=${numTasks}`, {
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
console.log(`${errorType} error test completed successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${errorType} error test failed:`, error.message);
|
||||||
|
} finally {
|
||||||
|
// Clean up temporary files
|
||||||
|
if (fs.existsSync(tempScriptPath)) {
|
||||||
|
fs.unlinkSync(tempScriptPath);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(testPRDPath)) {
|
||||||
|
fs.unlinkSync(testPRDPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to run all error tests
|
||||||
|
async function runAllErrorTests() {
|
||||||
|
console.log('Starting error handling tests for callClaude function...');
|
||||||
|
|
||||||
|
// Test 1: Network error with automatic retry
|
||||||
|
await runErrorTest('network', 5, 2);
|
||||||
|
|
||||||
|
// Test 2: Timeout error with automatic retry
|
||||||
|
await runErrorTest('timeout', 5, 2);
|
||||||
|
|
||||||
|
// Test 3: Invalid JSON response with task reduction
|
||||||
|
await runErrorTest('invalid-json', 10, 2);
|
||||||
|
|
||||||
|
// Test 4: Empty tasks array with task reduction
|
||||||
|
await runErrorTest('empty-tasks', 15, 2);
|
||||||
|
|
||||||
|
// Test 5: Exhausted retries (more failures than MAX_RETRIES)
|
||||||
|
await runErrorTest('network', 5, 4);
|
||||||
|
|
||||||
|
console.log('\nAll error tests completed!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the tests
|
||||||
|
runAllErrorTests().catch(error => {
|
||||||
|
console.error('Error running tests:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
231
scripts/test-claude.js
Executable file
231
scripts/test-claude.js
Executable file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test-claude.js
|
||||||
|
*
|
||||||
|
* A simple test script to verify the improvements to the callClaude function.
|
||||||
|
* This script tests different scenarios:
|
||||||
|
* 1. Normal operation with a small PRD
|
||||||
|
* 2. Testing with a large number of tasks (to potentially trigger task reduction)
|
||||||
|
* 3. Simulating a failure to test retry logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
// Create a simple PRD for testing
|
||||||
|
const createTestPRD = (size = 'small', taskComplexity = 'simple') => {
|
||||||
|
let content = `# Test PRD - ${size.toUpperCase()} SIZE, ${taskComplexity.toUpperCase()} COMPLEXITY\n\n`;
|
||||||
|
|
||||||
|
// Add more content based on size
|
||||||
|
if (size === 'small') {
|
||||||
|
content += `
|
||||||
|
## Overview
|
||||||
|
This is a small test PRD to verify the callClaude function improvements.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
1. Create a simple web application
|
||||||
|
2. Implement user authentication
|
||||||
|
3. Add a dashboard for users
|
||||||
|
4. Create an admin panel
|
||||||
|
5. Implement data visualization
|
||||||
|
|
||||||
|
## Technical Stack
|
||||||
|
- Frontend: React
|
||||||
|
- Backend: Node.js
|
||||||
|
- Database: MongoDB
|
||||||
|
`;
|
||||||
|
} else if (size === 'medium') {
|
||||||
|
// Medium-sized PRD with more requirements
|
||||||
|
content += `
|
||||||
|
## Overview
|
||||||
|
This is a medium-sized test PRD to verify the callClaude function improvements.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
1. Create a web application with multiple pages
|
||||||
|
2. Implement user authentication with OAuth
|
||||||
|
3. Add a dashboard for users with customizable widgets
|
||||||
|
4. Create an admin panel with user management
|
||||||
|
5. Implement data visualization with charts and graphs
|
||||||
|
6. Add real-time notifications
|
||||||
|
7. Implement a search feature
|
||||||
|
8. Add user profile management
|
||||||
|
9. Implement role-based access control
|
||||||
|
10. Add a reporting system
|
||||||
|
11. Implement file uploads and management
|
||||||
|
12. Add a commenting system
|
||||||
|
13. Implement a rating system
|
||||||
|
14. Add a recommendation engine
|
||||||
|
15. Implement a payment system
|
||||||
|
|
||||||
|
## Technical Stack
|
||||||
|
- Frontend: React with TypeScript
|
||||||
|
- Backend: Node.js with Express
|
||||||
|
- Database: MongoDB with Mongoose
|
||||||
|
- Authentication: JWT and OAuth
|
||||||
|
- Deployment: Docker and Kubernetes
|
||||||
|
- CI/CD: GitHub Actions
|
||||||
|
- Monitoring: Prometheus and Grafana
|
||||||
|
`;
|
||||||
|
} else if (size === 'large') {
|
||||||
|
// Large PRD with many requirements
|
||||||
|
content += `
|
||||||
|
## Overview
|
||||||
|
This is a large test PRD to verify the callClaude function improvements.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
`;
|
||||||
|
// Generate 30 requirements
|
||||||
|
for (let i = 1; i <= 30; i++) {
|
||||||
|
content += `${i}. Requirement ${i} - This is a detailed description of requirement ${i}.\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
content += `
|
||||||
|
## Technical Stack
|
||||||
|
- Frontend: React with TypeScript
|
||||||
|
- Backend: Node.js with Express
|
||||||
|
- Database: MongoDB with Mongoose
|
||||||
|
- Authentication: JWT and OAuth
|
||||||
|
- Deployment: Docker and Kubernetes
|
||||||
|
- CI/CD: GitHub Actions
|
||||||
|
- Monitoring: Prometheus and Grafana
|
||||||
|
|
||||||
|
## User Stories
|
||||||
|
`;
|
||||||
|
// Generate 20 user stories
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
content += `- As a user, I want to be able to ${i} so that I can achieve benefit ${i}.\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
content += `
|
||||||
|
## Non-Functional Requirements
|
||||||
|
- Performance: The system should respond within 200ms
|
||||||
|
- Scalability: The system should handle 10,000 concurrent users
|
||||||
|
- Availability: The system should have 99.9% uptime
|
||||||
|
- Security: The system should comply with OWASP top 10
|
||||||
|
- Accessibility: The system should comply with WCAG 2.1 AA
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add complexity if needed
|
||||||
|
if (taskComplexity === 'complex') {
|
||||||
|
content += `
|
||||||
|
## Complex Requirements
|
||||||
|
- Implement a real-time collaboration system
|
||||||
|
- Add a machine learning-based recommendation engine
|
||||||
|
- Implement a distributed caching system
|
||||||
|
- Add a microservices architecture
|
||||||
|
- Implement a custom analytics engine
|
||||||
|
- Add support for multiple languages and locales
|
||||||
|
- Implement a custom search engine with advanced filtering
|
||||||
|
- Add a custom workflow engine
|
||||||
|
- Implement a custom reporting system
|
||||||
|
- Add a custom dashboard builder
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to run the tests
|
||||||
|
async function runTests() {
|
||||||
|
console.log('Starting tests for callClaude function improvements...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Instead of importing the callClaude function directly, we'll use the dev.js script
|
||||||
|
// with our test PRDs by running it as a child process
|
||||||
|
|
||||||
|
// Test 1: Small PRD, 5 tasks
|
||||||
|
console.log('\n=== Test 1: Small PRD, 5 tasks ===');
|
||||||
|
const smallPRD = createTestPRD('small', 'simple');
|
||||||
|
const smallPRDPath = path.join(__dirname, 'test-small-prd.txt');
|
||||||
|
fs.writeFileSync(smallPRDPath, smallPRD, 'utf8');
|
||||||
|
|
||||||
|
console.log(`Created test PRD at ${smallPRDPath}`);
|
||||||
|
console.log('Running dev.js with small PRD...');
|
||||||
|
|
||||||
|
// Use the child_process module to run the dev.js script
|
||||||
|
const { execSync } = await import('child_process');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const smallResult = execSync(`node ${path.join(__dirname, 'dev.js')} parse-prd --input=${smallPRDPath} --tasks=5`, {
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
console.log('Small PRD test completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Small PRD test failed:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Medium PRD, 15 tasks
|
||||||
|
console.log('\n=== Test 2: Medium PRD, 15 tasks ===');
|
||||||
|
const mediumPRD = createTestPRD('medium', 'simple');
|
||||||
|
const mediumPRDPath = path.join(__dirname, 'test-medium-prd.txt');
|
||||||
|
fs.writeFileSync(mediumPRDPath, mediumPRD, 'utf8');
|
||||||
|
|
||||||
|
console.log(`Created test PRD at ${mediumPRDPath}`);
|
||||||
|
console.log('Running dev.js with medium PRD...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mediumResult = execSync(`node ${path.join(__dirname, 'dev.js')} parse-prd --input=${mediumPRDPath} --tasks=15`, {
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
console.log('Medium PRD test completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Medium PRD test failed:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Large PRD, 25 tasks
|
||||||
|
console.log('\n=== Test 3: Large PRD, 25 tasks ===');
|
||||||
|
const largePRD = createTestPRD('large', 'complex');
|
||||||
|
const largePRDPath = path.join(__dirname, 'test-large-prd.txt');
|
||||||
|
fs.writeFileSync(largePRDPath, largePRD, 'utf8');
|
||||||
|
|
||||||
|
console.log(`Created test PRD at ${largePRDPath}`);
|
||||||
|
console.log('Running dev.js with large PRD...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const largeResult = execSync(`node ${path.join(__dirname, 'dev.js')} parse-prd --input=${largePRDPath} --tasks=25`, {
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
console.log('Large PRD test completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Large PRD test failed:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\nAll tests completed!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Test failed:', error);
|
||||||
|
} finally {
|
||||||
|
// Clean up test files
|
||||||
|
console.log('\nCleaning up test files...');
|
||||||
|
const testFiles = [
|
||||||
|
path.join(__dirname, 'test-small-prd.txt'),
|
||||||
|
path.join(__dirname, 'test-medium-prd.txt'),
|
||||||
|
path.join(__dirname, 'test-large-prd.txt')
|
||||||
|
];
|
||||||
|
|
||||||
|
testFiles.forEach(file => {
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
console.log(`Deleted ${file}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Cleanup complete.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the tests
|
||||||
|
runTests().catch(error => {
|
||||||
|
console.error('Error running tests:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user