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"`.
|
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.
|
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
|
## 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.
|
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",
|
"name": "claude-task-master",
|
||||||
"version": "1.0.2",
|
"version": "1.0.7",
|
||||||
"description": "A task management system for AI-driven development with Claude",
|
"description": "A task management system for AI-driven development with Claude",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
* -> Generates per-task files (e.g., task_001.txt) from tasks.json
|
* -> Generates per-task files (e.g., task_001.txt) from tasks.json
|
||||||
*
|
*
|
||||||
* 4) set-status --id=4 --status=done
|
* 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
|
* 5) list
|
||||||
* -> Lists tasks in a brief console view (ID, title, status).
|
* -> Lists tasks in a brief console view (ID, title, status).
|
||||||
@@ -302,13 +303,65 @@ function generateTaskFiles(tasksPath, outputDir) {
|
|||||||
//
|
//
|
||||||
// 4) set-status
|
// 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);
|
const data = readJSON(tasksPath);
|
||||||
if (!data || !data.tasks) {
|
if (!data || !data.tasks) {
|
||||||
log('error', "No valid tasks found.");
|
log('error', "No valid tasks found.");
|
||||||
process.exit(1);
|
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);
|
const task = data.tasks.find(t => t.id === taskId);
|
||||||
if (!task) {
|
if (!task) {
|
||||||
log('error', `Task with ID=${taskId} not found.`);
|
log('error', `Task with ID=${taskId} not found.`);
|
||||||
@@ -701,11 +754,11 @@ function parseSubtasksFromText(text, startId, expectedCount) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (!statusArg) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
log('info', `Setting task ${idArg} status to "${statusArg}"...`);
|
log('info', `Setting task(s) ${idArg} status to "${statusArg}"...`);
|
||||||
setTaskStatus(tasksPath, parseInt(idArg, 10), statusArg);
|
setTaskStatus(tasksPath, idArg, statusArg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'list':
|
case 'list':
|
||||||
@@ -744,7 +797,8 @@ Subcommands:
|
|||||||
-> Generates per-task files (e.g., task_001.txt) from tasks.json
|
-> Generates per-task files (e.g., task_001.txt) from tasks.json
|
||||||
|
|
||||||
4) set-status --id=4 --status=done
|
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
|
5) list
|
||||||
-> Lists tasks in a brief console view (ID, title, status).
|
-> Lists tasks in a brief console view (ID, title, status).
|
||||||
|
|||||||
105
scripts/init.js
105
scripts/init.js
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
console.log('Starting claude-task-init...');
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
@@ -7,15 +9,14 @@ import readline from 'readline';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname } from 'path';
|
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 __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
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
|
// Define log levels and colors
|
||||||
const LOG_LEVELS = {
|
const LOG_LEVELS = {
|
||||||
debug: 0,
|
debug: 0,
|
||||||
@@ -88,8 +89,7 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
|||||||
|
|
||||||
// Main function to initialize a new project
|
// Main function to initialize a new project
|
||||||
async function initializeProject(options = {}) {
|
async function initializeProject(options = {}) {
|
||||||
return new Promise((resolve) => {
|
// If options are provided, use them directly without prompting
|
||||||
// If options are provided, use them directly
|
|
||||||
if (options.projectName && options.projectDescription) {
|
if (options.projectName && options.projectDescription) {
|
||||||
const projectName = options.projectName;
|
const projectName = options.projectName;
|
||||||
const projectDescription = options.projectDescription;
|
const projectDescription = options.projectDescription;
|
||||||
@@ -97,38 +97,55 @@ async function initializeProject(options = {}) {
|
|||||||
const authorName = options.authorName || '';
|
const authorName = options.authorName || '';
|
||||||
|
|
||||||
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
||||||
resolve({
|
return {
|
||||||
projectName,
|
projectName,
|
||||||
projectDescription,
|
projectDescription,
|
||||||
projectVersion,
|
projectVersion,
|
||||||
authorName
|
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';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Create the project structure
|
||||||
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
createProjectStructure(projectName, projectDescription, projectVersion, authorName);
|
||||||
|
|
||||||
rl.close();
|
return {
|
||||||
resolve({
|
|
||||||
projectName,
|
projectName,
|
||||||
projectDescription,
|
projectDescription,
|
||||||
projectVersion,
|
projectVersion,
|
||||||
authorName
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,22 +216,6 @@ function createProjectStructure(projectName, projectDescription, projectVersion,
|
|||||||
// Create main README.md
|
// Create main README.md
|
||||||
copyTemplateFile('README.md', path.join(targetDir, 'README.md'), replacements);
|
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
|
// Initialize git repository if git is available
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(path.join(targetDir, '.git'))) {
|
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
|
// Run the initialization if this script is executed directly
|
||||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
// The original check doesn't work with npx and global commands
|
||||||
(async function main() {
|
// 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 {
|
try {
|
||||||
|
console.log('Starting initialization...');
|
||||||
await initializeProject();
|
await initializeProject();
|
||||||
|
// Process should exit naturally after completion
|
||||||
|
console.log('Initialization completed, exiting...');
|
||||||
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize project:', error);
|
||||||
log('error', 'Failed to initialize project:', error);
|
log('error', 'Failed to initialize project:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
|
||||||
|
|
||||||
// Export functions for programmatic use
|
// Export functions for programmatic use
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -144,6 +144,17 @@ function preparePackage() {
|
|||||||
process.exit(1);
|
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('success', 'Package preparation completed successfully!');
|
||||||
log('info', 'You can now publish the package with:');
|
log('info', 'You can now publish the package with:');
|
||||||
log('info', ' npm publish');
|
log('info', ' npm publish');
|
||||||
|
|||||||
Reference in New Issue
Block a user