mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-06-27 00:29:58 +00:00

This commit resolves several issues with the task expansion system to ensure higher quality subtasks and better synchronization: 1. Task File Generation - Add automatic regeneration of task files after expanding tasks - Ensure individual task text files stay in sync with tasks.json - Avoids manual regeneration steps after task expansion 2. Perplexity API Integration - Fix 'researchPrompt is not defined' error in Perplexity integration - Add specialized research-oriented prompt template - Improve system message for better context and instruction - Better fallback to Claude when Perplexity unavailable 3. Subtask Parsing Improvements - Enhance regex pattern to handle more formatting variations - Implement multiple parsing strategies for different response formats: * Improved section detection with flexible headings * Added support for numbered and bulleted lists * Implemented heuristic-based title and description extraction - Create more meaningful dummy subtasks with relevant titles and descriptions instead of generic placeholders - Ensure minimal descriptions are always provided 4. Quality Verification and Retry System - Add post-expansion verification to identify low-quality subtask sets - Detect tasks with too many generic/placeholder subtasks - Implement interactive retry mechanism with enhanced prompts - Use adjusted settings for retries (research mode, subtask count) - Clear existing subtasks before retry to prevent duplicates - Provide detailed reporting of verification and retry process These changes significantly improve the quality of generated subtasks and reduce the need for manual intervention when subtask generation produces suboptimal results.
322 lines
10 KiB
JavaScript
Executable File
322 lines
10 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
console.log('Starting claude-task-init...');
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { execSync } from 'child_process';
|
|
import readline from 'readline';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname } from 'path';
|
|
|
|
// Debug information
|
|
console.log('Node version:', process.version);
|
|
console.log('Current directory:', process.cwd());
|
|
console.log('Script path:', import.meta.url);
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
// Define log levels and colors
|
|
const LOG_LEVELS = {
|
|
debug: 0,
|
|
info: 1,
|
|
warn: 2,
|
|
error: 3
|
|
};
|
|
|
|
const COLORS = {
|
|
reset: '\x1b[0m',
|
|
bright: '\x1b[1m',
|
|
dim: '\x1b[2m',
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
magenta: '\x1b[35m',
|
|
cyan: '\x1b[36m'
|
|
};
|
|
|
|
// Get log level from environment or default to info
|
|
const LOG_LEVEL = process.env.LOG_LEVEL ? LOG_LEVELS[process.env.LOG_LEVEL.toLowerCase()] : LOG_LEVELS.info;
|
|
|
|
// Logging function
|
|
function log(level, ...args) {
|
|
const levelValue = LOG_LEVELS[level.toLowerCase()];
|
|
|
|
if (levelValue >= LOG_LEVEL) {
|
|
const prefix = {
|
|
debug: `${COLORS.dim}[DEBUG]${COLORS.reset}`,
|
|
info: `${COLORS.blue}[INFO]${COLORS.reset}`,
|
|
warn: `${COLORS.yellow}[WARN]${COLORS.reset}`,
|
|
error: `${COLORS.red}[ERROR]${COLORS.reset}`
|
|
}[level.toLowerCase()];
|
|
|
|
console.log(prefix, ...args);
|
|
}
|
|
|
|
// Write to debug log if DEBUG=true
|
|
if (process.env.DEBUG === 'true') {
|
|
const logMessage = `[${level.toUpperCase()}] ${args.join(' ')}\n`;
|
|
fs.appendFileSync('init-debug.log', logMessage);
|
|
}
|
|
}
|
|
|
|
// Function to create directory if it doesn't exist
|
|
function ensureDirectoryExists(dirPath) {
|
|
if (!fs.existsSync(dirPath)) {
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
log('info', `Created directory: ${dirPath}`);
|
|
}
|
|
}
|
|
|
|
// Function to copy a file from the package to the target directory
|
|
function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
|
// Get the file content from the appropriate source directory
|
|
let sourcePath;
|
|
|
|
// Map template names to their actual source paths
|
|
switch(templateName) {
|
|
case 'dev.js':
|
|
sourcePath = path.join(__dirname, 'dev.js');
|
|
break;
|
|
case 'scripts_README.md':
|
|
sourcePath = path.join(__dirname, '..', 'assets', 'scripts_README.md');
|
|
break;
|
|
case 'dev_workflow.mdc':
|
|
sourcePath = path.join(__dirname, '..', '.cursor', 'rules', 'dev_workflow.mdc');
|
|
break;
|
|
case 'cursor_rules.mdc':
|
|
sourcePath = path.join(__dirname, '..', '.cursor', 'rules', 'cursor_rules.mdc');
|
|
break;
|
|
case 'self_improve.mdc':
|
|
sourcePath = path.join(__dirname, '..', '.cursor', 'rules', 'self_improve.mdc');
|
|
break;
|
|
case 'README-task-master.md':
|
|
sourcePath = path.join(__dirname, '..', 'README-task-master.md');
|
|
break;
|
|
default:
|
|
// For other files like env.example, gitignore, etc. that don't have direct equivalents
|
|
sourcePath = path.join(__dirname, '..', 'assets', templateName);
|
|
}
|
|
|
|
// Check if the source file exists
|
|
if (!fs.existsSync(sourcePath)) {
|
|
// Fall back to templates directory for files that might not have been moved yet
|
|
sourcePath = path.join(__dirname, '..', 'assets', templateName);
|
|
if (!fs.existsSync(sourcePath)) {
|
|
log('error', `Source file not found: ${sourcePath}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let content = fs.readFileSync(sourcePath, 'utf8');
|
|
|
|
// Replace placeholders with actual values
|
|
Object.entries(replacements).forEach(([key, value]) => {
|
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
content = content.replace(regex, value);
|
|
});
|
|
|
|
// Write the content to the target path
|
|
fs.writeFileSync(targetPath, content);
|
|
log('info', `Created file: ${targetPath}`);
|
|
}
|
|
|
|
// Main function to initialize a new project
|
|
async function initializeProject(options = {}) {
|
|
// If options are provided, use them directly without prompting
|
|
if (options.projectName && options.projectDescription) {
|
|
const projectName = options.projectName;
|
|
const projectDescription = options.projectDescription;
|
|
const projectVersion = options.projectVersion || '1.0.0';
|
|
const authorName = options.authorName || '';
|
|
|
|
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
|
return {
|
|
projectName,
|
|
projectDescription,
|
|
projectVersion,
|
|
authorName
|
|
};
|
|
}
|
|
|
|
// Otherwise, prompt the user for input
|
|
// Create readline interface only when needed
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout
|
|
});
|
|
|
|
try {
|
|
const projectName = await promptQuestion(rl, 'Enter project name: ');
|
|
const projectDescription = await promptQuestion(rl, 'Enter project description: ');
|
|
const projectVersionInput = await promptQuestion(rl, 'Enter project version (default: 1.0.0): ');
|
|
const authorName = await promptQuestion(rl, 'Enter your name: ');
|
|
|
|
// Set default version if not provided
|
|
const projectVersion = projectVersionInput.trim() ? projectVersionInput : '1.0.0';
|
|
|
|
// Close the readline interface
|
|
rl.close();
|
|
|
|
// Create the project structure
|
|
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
|
|
|
return {
|
|
projectName,
|
|
projectDescription,
|
|
projectVersion,
|
|
authorName
|
|
};
|
|
} catch (error) {
|
|
// Make sure to close readline on error
|
|
rl.close();
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Helper function to promisify readline question
|
|
function promptQuestion(rl, question) {
|
|
return new Promise((resolve) => {
|
|
rl.question(question, (answer) => {
|
|
resolve(answer);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Function to create the project structure
|
|
function createProjectStructure(projectName, projectDescription, projectVersion, authorName) {
|
|
const targetDir = process.cwd();
|
|
log('info', `Initializing project in ${targetDir}`);
|
|
|
|
// Create directories
|
|
ensureDirectoryExists(path.join(targetDir, '.cursor', 'rules'));
|
|
ensureDirectoryExists(path.join(targetDir, 'scripts'));
|
|
ensureDirectoryExists(path.join(targetDir, 'tasks'));
|
|
|
|
// Create package.json
|
|
const packageJson = {
|
|
name: projectName.toLowerCase().replace(/\s+/g, '-'),
|
|
version: projectVersion,
|
|
description: projectDescription,
|
|
author: authorName,
|
|
type: "module",
|
|
scripts: {
|
|
"dev": "node scripts/dev.js",
|
|
"list": "node scripts/dev.js list",
|
|
"generate": "node scripts/dev.js generate",
|
|
"parse-prd": "node scripts/dev.js parse-prd"
|
|
},
|
|
dependencies: {
|
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
"chalk": "^4.1.2",
|
|
"commander": "^11.1.0",
|
|
"dotenv": "^16.3.1",
|
|
"openai": "^4.86.1"
|
|
}
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(targetDir, 'package.json'),
|
|
JSON.stringify(packageJson, null, 2)
|
|
);
|
|
log('info', 'Created package.json');
|
|
|
|
// Copy template files with replacements
|
|
const replacements = {
|
|
projectName,
|
|
projectDescription,
|
|
projectVersion,
|
|
authorName,
|
|
year: new Date().getFullYear()
|
|
};
|
|
|
|
// Copy .env.example
|
|
copyTemplateFile('env.example', path.join(targetDir, '.env.example'), replacements);
|
|
|
|
// Copy .gitignore
|
|
copyTemplateFile('gitignore', path.join(targetDir, '.gitignore'));
|
|
|
|
// Copy dev_workflow.mdc
|
|
copyTemplateFile('dev_workflow.mdc', path.join(targetDir, '.cursor', 'rules', 'dev_workflow.mdc'));
|
|
|
|
// Copy cursor_rules.mdc
|
|
copyTemplateFile('cursor_rules.mdc', path.join(targetDir, '.cursor', 'rules', 'cursor_rules.mdc'));
|
|
|
|
// Copy self_improve.mdc
|
|
copyTemplateFile('self_improve.mdc', path.join(targetDir, '.cursor', 'rules', 'self_improve.mdc'));
|
|
|
|
// Copy scripts/dev.js
|
|
copyTemplateFile('dev.js', path.join(targetDir, 'scripts', 'dev.js'));
|
|
|
|
// Copy scripts/README.md
|
|
copyTemplateFile('scripts_README.md', path.join(targetDir, 'scripts', 'README.md'));
|
|
|
|
// Copy example_prd.txt
|
|
copyTemplateFile('example_prd.txt', path.join(targetDir, 'scripts', 'example_prd.txt'));
|
|
|
|
// Create main README.md
|
|
copyTemplateFile('README-task-master.md', path.join(targetDir, 'README.md'), replacements);
|
|
|
|
// Initialize git repository if git is available
|
|
try {
|
|
if (!fs.existsSync(path.join(targetDir, '.git'))) {
|
|
execSync('git init', { stdio: 'ignore' });
|
|
log('info', 'Initialized git repository');
|
|
}
|
|
} catch (error) {
|
|
log('warn', 'Git not available, skipping repository initialization');
|
|
}
|
|
|
|
// Run npm install automatically
|
|
log('info', 'Installing dependencies...');
|
|
try {
|
|
execSync('npm install', { stdio: 'inherit', cwd: targetDir });
|
|
log('info', `${COLORS.green}Dependencies installed successfully!${COLORS.reset}`);
|
|
} catch (error) {
|
|
log('error', 'Failed to install dependencies:', error.message);
|
|
log('error', 'Please run npm install manually');
|
|
}
|
|
|
|
log('info', `${COLORS.green}${COLORS.bright}Project initialized successfully!${COLORS.reset}`);
|
|
log('info', '');
|
|
log('info', 'Next steps:');
|
|
log('info', '1. Create a .env file with your ANTHROPIC_API_KEY (see .env.example)');
|
|
log('info', '2. Add your PRD.txt to the /scripts directory');
|
|
log('info', '3. Ask Cursor Agent to parse your PRD.txt and generate tasks');
|
|
log('info', '└── You can also manually run `npm run parse-prd -- --input=<your-prd-file.txt>` to generate tasks');
|
|
log('info', '4. Review the README.md file to learn how to use other commands via Cursor Agent.');
|
|
log('info', '');
|
|
}
|
|
|
|
// Run the initialization if this script is executed directly
|
|
// The original check doesn't work with npx and global commands
|
|
// if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
// Instead, we'll always run the initialization if this file is the main module
|
|
console.log('Checking if script should run initialization...');
|
|
console.log('import.meta.url:', import.meta.url);
|
|
console.log('process.argv:', process.argv);
|
|
|
|
// Always run initialization when this file is loaded directly
|
|
// This works with both direct node execution and npx/global commands
|
|
(async function main() {
|
|
try {
|
|
console.log('Starting initialization...');
|
|
await initializeProject();
|
|
// Process should exit naturally after completion
|
|
console.log('Initialization completed, exiting...');
|
|
process.exit(0);
|
|
} catch (error) {
|
|
console.error('Failed to initialize project:', error);
|
|
log('error', 'Failed to initialize project:', error);
|
|
process.exit(1);
|
|
}
|
|
})();
|
|
|
|
// Export functions for programmatic use
|
|
export {
|
|
initializeProject,
|
|
createProjectStructure,
|
|
log
|
|
};
|