feat: Enhance task management and fix initialization issues
This commit includes several important improvements: 1. Add support for updating multiple tasks at once with comma-separated IDs - Modify setTaskStatus to handle lists like id=1,1.1,1.2 - Fix subtask handling to properly update subtask statuses - Add in-progress as a valid status option 2. Fix initialization script issues - Add debugging information to help diagnose npx execution problems - Improve error handling and readline interface management - Remove conditional check that prevented script from running in some environments - Add troubleshooting section to README.md 3. Improve package preparation - Make scripts executable during package preparation - Update version to 1.0.7 These changes make the package more robust and user-friendly, particularly for first-time users and those managing complex task hierarchies.
This commit is contained in:
18
README.md
18
README.md
@@ -29,6 +29,24 @@ This will prompt you for project details and set up a new project with the neces
|
||||
1. This package uses ES modules. Your package.json should include `"type": "module"`.
|
||||
2. The Anthropic SDK version should be 0.39.0 or higher.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### If `npx claude-task-init` doesn't respond:
|
||||
|
||||
Try running it with Node directly:
|
||||
|
||||
```bash
|
||||
node node_modules/claude-task-master/scripts/init.js
|
||||
```
|
||||
|
||||
Or clone the repository and run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/eyaltoledano/claude-task-master.git
|
||||
cd claude-task-master
|
||||
node scripts/init.js
|
||||
```
|
||||
|
||||
## Integrating with Cursor AI
|
||||
|
||||
Claude Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-task-master",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.7",
|
||||
"description": "A task management system for AI-driven development with Claude",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
* -> Generates per-task files (e.g., task_001.txt) from tasks.json
|
||||
*
|
||||
* 4) set-status --id=4 --status=done
|
||||
* -> Updates a single task's status to done (or pending, deferred, etc.).
|
||||
* -> Updates a single task's status to done (or pending, deferred, in-progress, etc.).
|
||||
* -> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2
|
||||
*
|
||||
* 5) list
|
||||
* -> Lists tasks in a brief console view (ID, title, status).
|
||||
@@ -302,13 +303,65 @@ function generateTaskFiles(tasksPath, outputDir) {
|
||||
//
|
||||
// 4) set-status
|
||||
//
|
||||
function setTaskStatus(tasksPath, taskId, newStatus) {
|
||||
function setTaskStatus(tasksPath, taskIdInput, newStatus) {
|
||||
// For recursive calls with multiple IDs, we need to read the latest data each time
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
log('error', "No valid tasks found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Handle multiple task IDs (comma-separated)
|
||||
if (typeof taskIdInput === 'string' && taskIdInput.includes(',')) {
|
||||
const taskIds = taskIdInput.split(',').map(id => id.trim());
|
||||
log('info', `Processing multiple tasks: ${taskIds.join(', ')}`);
|
||||
|
||||
// Process each task ID individually
|
||||
for (const taskId of taskIds) {
|
||||
// Create a new instance for each task to ensure we're working with fresh data
|
||||
setTaskStatus(tasksPath, taskId, newStatus);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert numeric taskId to number if it's not a subtask ID
|
||||
const taskId = (!isNaN(taskIdInput) && !String(taskIdInput).includes('.'))
|
||||
? parseInt(taskIdInput, 10)
|
||||
: taskIdInput;
|
||||
|
||||
// Check if this is a subtask ID (e.g., "1.1")
|
||||
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||
const [parentIdStr, subtaskIdStr] = taskId.split('.');
|
||||
const parentId = parseInt(parentIdStr, 10);
|
||||
const subtaskId = parseInt(subtaskIdStr, 10);
|
||||
|
||||
const parentTask = data.tasks.find(t => t.id === parentId);
|
||||
|
||||
if (!parentTask) {
|
||||
log('error', `Parent task with ID=${parentId} not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!parentTask.subtasks || parentTask.subtasks.length === 0) {
|
||||
log('error', `Parent task with ID=${parentId} has no subtasks.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const subtask = parentTask.subtasks.find(st => st.id === subtaskId);
|
||||
if (!subtask) {
|
||||
log('error', `Subtask with ID=${subtaskId} not found in parent task ID=${parentId}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const oldStatus = subtask.status;
|
||||
subtask.status = newStatus;
|
||||
writeJSON(tasksPath, data);
|
||||
log('info', `Subtask ${parentId}.${subtaskId} status changed from '${oldStatus}' to '${newStatus}'.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle regular task ID
|
||||
const task = data.tasks.find(t => t.id === taskId);
|
||||
if (!task) {
|
||||
log('error', `Task with ID=${taskId} not found.`);
|
||||
@@ -701,11 +754,11 @@ function parseSubtasksFromText(text, startId, expectedCount) {
|
||||
process.exit(1);
|
||||
}
|
||||
if (!statusArg) {
|
||||
log('error', "Missing --status=<newStatus> argument (e.g. done, pending, deferred).");
|
||||
log('error', "Missing --status=<newStatus> argument (e.g., done, pending, deferred, in-progress).");
|
||||
process.exit(1);
|
||||
}
|
||||
log('info', `Setting task ${idArg} status to "${statusArg}"...`);
|
||||
setTaskStatus(tasksPath, parseInt(idArg, 10), statusArg);
|
||||
log('info', `Setting task(s) ${idArg} status to "${statusArg}"...`);
|
||||
setTaskStatus(tasksPath, idArg, statusArg);
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
@@ -744,7 +797,8 @@ Subcommands:
|
||||
-> Generates per-task files (e.g., task_001.txt) from tasks.json
|
||||
|
||||
4) set-status --id=4 --status=done
|
||||
-> Updates a single task's status to done (or pending, deferred, etc.).
|
||||
-> Updates a single task's status to done (or pending, deferred, in-progress, etc.).
|
||||
-> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2
|
||||
|
||||
5) list
|
||||
-> Lists tasks in a brief console view (ID, title, status).
|
||||
|
||||
157
scripts/init.js
157
scripts/init.js
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
console.log('Starting claude-task-init...');
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
@@ -7,15 +9,14 @@ 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);
|
||||
|
||||
// Create readline interface for user input
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// Define log levels and colors
|
||||
const LOG_LEVELS = {
|
||||
debug: 0,
|
||||
@@ -88,47 +89,63 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
||||
|
||||
// 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) => {
|
||||
// If options are provided, use them directly
|
||||
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);
|
||||
resolve({
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName
|
||||
});
|
||||
} else {
|
||||
// Otherwise, prompt the user for input
|
||||
rl.question('Enter project name: ', (projectName) => {
|
||||
rl.question('Enter project description: ', (projectDescription) => {
|
||||
rl.question('Enter project version (default: 1.0.0): ', (projectVersion) => {
|
||||
rl.question('Enter your name: ', (authorName) => {
|
||||
// Set default version if not provided
|
||||
if (!projectVersion.trim()) {
|
||||
projectVersion = '1.0.0';
|
||||
}
|
||||
|
||||
// Create the project structure
|
||||
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
||||
|
||||
rl.close();
|
||||
resolve({
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
rl.question(question, (answer) => {
|
||||
resolve(answer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -199,22 +216,6 @@ function createProjectStructure(projectName, projectDescription, projectVersion,
|
||||
// Create main README.md
|
||||
copyTemplateFile('README.md', path.join(targetDir, 'README.md'), replacements);
|
||||
|
||||
// Create empty tasks.json
|
||||
const tasksJson = {
|
||||
meta: {
|
||||
name: projectName,
|
||||
version: projectVersion,
|
||||
description: projectDescription
|
||||
},
|
||||
tasks: []
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(targetDir, 'tasks.json'),
|
||||
JSON.stringify(tasksJson, null, 2)
|
||||
);
|
||||
log('info', 'Created tasks.json');
|
||||
|
||||
// Initialize git repository if git is available
|
||||
try {
|
||||
if (!fs.existsSync(path.join(targetDir, '.git'))) {
|
||||
@@ -236,16 +237,28 @@ function createProjectStructure(projectName, projectDescription, projectVersion,
|
||||
}
|
||||
|
||||
// Run the initialization if this script is executed directly
|
||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||
(async function main() {
|
||||
try {
|
||||
await initializeProject();
|
||||
} catch (error) {
|
||||
log('error', 'Failed to initialize project:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
}
|
||||
// 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 {
|
||||
|
||||
@@ -144,6 +144,17 @@ function preparePackage() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Make scripts executable
|
||||
log('info', 'Making scripts executable...');
|
||||
try {
|
||||
execSync('chmod +x scripts/init.js', { stdio: 'ignore' });
|
||||
log('info', 'Made scripts/init.js executable');
|
||||
execSync('chmod +x scripts/dev.js', { stdio: 'ignore' });
|
||||
log('info', 'Made scripts/dev.js executable');
|
||||
} catch (error) {
|
||||
log('error', 'Failed to make scripts executable:', error.message);
|
||||
}
|
||||
|
||||
log('success', 'Package preparation completed successfully!');
|
||||
log('info', 'You can now publish the package with:');
|
||||
log('info', ' npm publish');
|
||||
|
||||
Reference in New Issue
Block a user