Merge #164: feat(mcp): Refactor initialize_project tool for direct execution
Refactors the `initialize_project` MCP tool to call a dedicated direct function (`initializeProjectDirect`) instead of executing the CLI command. This improves reliability and aligns it with other MCP tools.
Key changes include:
- Modified `mcp-server/src/tools/initialize-project.js` to call `initializeProjectDirect`.
- Updated the tool's Zod schema to require the `projectRoot` parameter.
- Implemented `handleApiResult` for consistent MCP response formatting.
- Enhanced `mcp-server/src/core/direct-functions/initialize-project-direct.js`:
- Prioritizes `args.projectRoot` over session-derived paths for determining the target directory.
- Added validation to prevent initialization attempts in invalid directories (e.g., '/', home directory).
- Forces `yes: true` when calling the core `initializeProject` function for non-interactive use.
- Ensures `process.chdir()` targets the validated directory.
- Added more robust `isSilentMode()` checks in core modules (`utils.js`, `init.js`) to suppress console output during MCP operations.
This resolves issues where the tool previously failed due to incorrect fallback directory resolution (e.g., initializing in '/') when session context was incomplete.
This commit is contained in:
@@ -14,13 +14,13 @@ alwaysApply: false
|
||||
- **Purpose**: Defines and registers all CLI commands using Commander.js.
|
||||
- **Responsibilities** (See also: [`commands.mdc`](mdc:.cursor/rules/commands.mdc)):
|
||||
- Parses command-line arguments and options.
|
||||
- Invokes appropriate functions from other modules to execute commands.
|
||||
- Invokes appropriate functions from other modules to execute commands (e.g., calls `initializeProject` from `init.js` for the `init` command).
|
||||
- Handles user input and output related to command execution.
|
||||
- Implements input validation and error handling for CLI commands.
|
||||
- **Key Components**:
|
||||
- `programInstance` (Commander.js `Command` instance): Manages command definitions.
|
||||
- `registerCommands(programInstance)`: Function to register all application commands.
|
||||
- Command action handlers: Functions executed when a specific command is invoked.
|
||||
- Command action handlers: Functions executed when a specific command is invoked, delegating to core modules.
|
||||
|
||||
- **[`task-manager.js`](mdc:scripts/modules/task-manager.js): Task Data Management**
|
||||
- **Purpose**: Manages task data, including loading, saving, creating, updating, deleting, and querying tasks.
|
||||
@@ -148,10 +148,23 @@ alwaysApply: false
|
||||
- Robust error handling for background tasks
|
||||
- **Usage**: Used for CPU-intensive operations like task expansion and PRD parsing
|
||||
|
||||
- **[`init.js`](mdc:scripts/init.js): Project Initialization Logic**
|
||||
- **Purpose**: Contains the core logic for setting up a new Task Master project structure.
|
||||
- **Responsibilities**:
|
||||
- Creates necessary directories (`.cursor/rules`, `scripts`, `tasks`).
|
||||
- Copies template files (`.env.example`, `.gitignore`, rule files, `dev.js`, etc.).
|
||||
- Creates or merges `package.json` with required dependencies and scripts.
|
||||
- Sets up MCP configuration (`.cursor/mcp.json`).
|
||||
- Optionally initializes a git repository and installs dependencies.
|
||||
- Handles user prompts for project details *if* called without skip flags (`-y`).
|
||||
- **Key Function**:
|
||||
- `initializeProject(options)`: The main function exported and called by the `init` command's action handler in [`commands.js`](mdc:scripts/modules/commands.js). It receives parsed options directly.
|
||||
- **Note**: This script is used as a module and no longer handles its own argument parsing or direct execution via a separate `bin` file.
|
||||
|
||||
- **Data Flow and Module Dependencies**:
|
||||
|
||||
- **Commands Initiate Actions**: User commands entered via the CLI (handled by [`commands.js`](mdc:scripts/modules/commands.js)) are the entry points for most operations.
|
||||
- **Command Handlers Delegate to Managers**: Command handlers in [`commands.js`](mdc:scripts/modules/commands.js) call functions in [`task-manager.js`](mdc:scripts/modules/task-manager.js) and [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js) to perform core task and dependency management logic.
|
||||
- **Commands Initiate Actions**: User commands entered via the CLI (parsed by `commander` based on definitions in [`commands.js`](mdc:scripts/modules/commands.js)) are the entry points for most operations.
|
||||
- **Command Handlers Delegate to Core Logic**: Action handlers within [`commands.js`](mdc:scripts/modules/commands.js) call functions in core modules like [`task-manager.js`](mdc:scripts/modules/task-manager.js), [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js), and [`init.js`](mdc:scripts/init.js) (for the `init` command) to perform the actual work.
|
||||
- **UI for Presentation**: [`ui.js`](mdc:scripts/modules/ui.js) is used by command handlers and task/dependency managers to display information to the user. UI functions primarily consume data and format it for output, without modifying core application state.
|
||||
- **Utilities for Common Tasks**: [`utils.js`](mdc:scripts/modules/utils.js) provides helper functions used by all other modules for configuration, logging, file operations, and common data manipulations.
|
||||
- **AI Services Integration**: AI functionalities (complexity analysis, task expansion, PRD parsing) are invoked from [`task-manager.js`](mdc:scripts/modules/task-manager.js) and potentially [`commands.js`](mdc:scripts/modules/commands.js), likely using functions that would reside in a dedicated `ai-services.js` module or be integrated within `utils.js` or `task-manager.js`.
|
||||
|
||||
@@ -24,7 +24,7 @@ While this document details the implementation of Task Master's **CLI commands**
|
||||
programInstance
|
||||
.command('command-name')
|
||||
.description('Clear, concise description of what the command does')
|
||||
.option('-s, --short-option <value>', 'Option description', 'default value')
|
||||
.option('-o, --option <value>', 'Option description', 'default value')
|
||||
.option('--long-option <value>', 'Option description')
|
||||
.action(async (options) => {
|
||||
// Command implementation
|
||||
@@ -34,7 +34,8 @@ While this document details the implementation of Task Master's **CLI commands**
|
||||
- **Command Handler Organization**:
|
||||
- ✅ DO: Keep action handlers concise and focused
|
||||
- ✅ DO: Extract core functionality to appropriate modules
|
||||
- ✅ DO: Include validation for required parameters
|
||||
- ✅ DO: Have the action handler import and call the relevant function(s) from core modules (e.g., `task-manager.js`, `init.js`), passing the parsed `options`.
|
||||
- ✅ DO: Perform basic parameter validation (e.g., checking for required options) within the action handler or at the start of the called core function.
|
||||
- ❌ DON'T: Implement business logic in command handlers
|
||||
|
||||
## Best Practices for Removal/Delete Commands
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,30 +4,36 @@ about: Create a report to help us improve
|
||||
title: 'bug: '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Description
|
||||
|
||||
Detailed description of the problem, including steps to reproduce the issue.
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1. Step-by-step instructions to reproduce the issue
|
||||
2. Include command examples or UI interactions
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
Describe clearly what the expected outcome or behavior should be.
|
||||
|
||||
### Actual Behavior
|
||||
|
||||
Describe clearly what the actual outcome or behavior is.
|
||||
|
||||
### Screenshots or Logs
|
||||
|
||||
Provide screenshots, logs, or error messages if applicable.
|
||||
|
||||
### Environment
|
||||
|
||||
- Task Master version:
|
||||
- Node.js version:
|
||||
- Operating system:
|
||||
- IDE (if applicable):
|
||||
|
||||
### Additional Context
|
||||
|
||||
Any additional information or context that might help diagnose the issue.
|
||||
|
||||
@@ -4,30 +4,35 @@ about: Suggest an idea for this project
|
||||
title: 'feat: '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
> "Direct quote or clear summary of user request or need or user story."
|
||||
|
||||
### Motivation
|
||||
|
||||
Detailed explanation of why this feature is important. Describe the problem it solves or the benefit it provides.
|
||||
|
||||
### Proposed Solution
|
||||
|
||||
Clearly describe the proposed feature, including:
|
||||
|
||||
- High-level overview of the feature
|
||||
- Relevant technologies or integrations
|
||||
- How it fits into the existing workflow or architecture
|
||||
|
||||
### High-Level Workflow
|
||||
|
||||
1. Step-by-step description of how the feature will be implemented
|
||||
2. Include necessary intermediate milestones
|
||||
|
||||
### Key Elements
|
||||
|
||||
- Bullet-point list of technical or UX/UI enhancements
|
||||
- Mention specific integrations or APIs
|
||||
- Highlight changes needed in existing data models or commands
|
||||
|
||||
### Example Workflow
|
||||
|
||||
Provide a clear, concrete example demonstrating the feature:
|
||||
|
||||
```shell
|
||||
@@ -36,9 +41,11 @@ $ task-master [action]
|
||||
```
|
||||
|
||||
### Implementation Considerations
|
||||
|
||||
- Dependencies on external components or APIs
|
||||
- Backward compatibility requirements
|
||||
- Potential performance impacts or resource usage
|
||||
|
||||
### Out of Scope (Future Considerations)
|
||||
|
||||
Clearly list any features or improvements not included but relevant for future iterations.
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/feedback.md
vendored
7
.github/ISSUE_TEMPLATE/feedback.md
vendored
@@ -4,23 +4,28 @@ about: Give us specific feedback on the product/approach/tech
|
||||
title: 'feedback: '
|
||||
labels: feedback
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Feedback Summary
|
||||
|
||||
Provide a clear summary or direct quote from user feedback.
|
||||
|
||||
### User Context
|
||||
|
||||
Explain the user's context or scenario in which this feedback was provided.
|
||||
|
||||
### User Impact
|
||||
|
||||
Describe how this feedback affects the user experience or workflow.
|
||||
|
||||
### Suggestions
|
||||
|
||||
Provide any initial thoughts, potential solutions, or improvements based on the feedback.
|
||||
|
||||
### Relevant Screenshots or Examples
|
||||
|
||||
Attach screenshots, logs, or examples that illustrate the feedback.
|
||||
|
||||
### Additional Notes
|
||||
|
||||
Any additional context or related information.
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Claude Task Master Init
|
||||
* Direct executable for the init command
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Get the path to the init script
|
||||
const initScriptPath = resolve(__dirname, '../scripts/init.js');
|
||||
|
||||
// Pass through all arguments
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Spawn the init script with all arguments
|
||||
const child = spawn('node', [initScriptPath, ...args], {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd()
|
||||
});
|
||||
|
||||
// Handle exit
|
||||
child.on('close', (code) => {
|
||||
process.exit(code);
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --trace-deprecation
|
||||
|
||||
/**
|
||||
* Task Master
|
||||
@@ -225,47 +225,47 @@ function createDevScriptAction(commandName) {
|
||||
};
|
||||
}
|
||||
|
||||
// Special case for the 'init' command which uses a different script
|
||||
function registerInitCommand(program) {
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize a new project')
|
||||
.option('-y, --yes', 'Skip prompts and use default values')
|
||||
.option('-n, --name <name>', 'Project name')
|
||||
.option('-d, --description <description>', 'Project description')
|
||||
.option('-v, --version <version>', 'Project version')
|
||||
.option('-a, --author <author>', 'Author name')
|
||||
.option('--skip-install', 'Skip installing dependencies')
|
||||
.option('--dry-run', 'Show what would be done without making changes')
|
||||
.action((options) => {
|
||||
// Pass through any options to the init script
|
||||
const args = [
|
||||
'--yes',
|
||||
'name',
|
||||
'description',
|
||||
'version',
|
||||
'author',
|
||||
'skip-install',
|
||||
'dry-run'
|
||||
]
|
||||
.filter((opt) => options[opt])
|
||||
.map((opt) => {
|
||||
if (opt === 'yes' || opt === 'skip-install' || opt === 'dry-run') {
|
||||
return `--${opt}`;
|
||||
}
|
||||
return `--${opt}=${options[opt]}`;
|
||||
});
|
||||
// // Special case for the 'init' command which uses a different script
|
||||
// function registerInitCommand(program) {
|
||||
// program
|
||||
// .command('init')
|
||||
// .description('Initialize a new project')
|
||||
// .option('-y, --yes', 'Skip prompts and use default values')
|
||||
// .option('-n, --name <name>', 'Project name')
|
||||
// .option('-d, --description <description>', 'Project description')
|
||||
// .option('-v, --version <version>', 'Project version')
|
||||
// .option('-a, --author <author>', 'Author name')
|
||||
// .option('--skip-install', 'Skip installing dependencies')
|
||||
// .option('--dry-run', 'Show what would be done without making changes')
|
||||
// .action((options) => {
|
||||
// // Pass through any options to the init script
|
||||
// const args = [
|
||||
// '--yes',
|
||||
// 'name',
|
||||
// 'description',
|
||||
// 'version',
|
||||
// 'author',
|
||||
// 'skip-install',
|
||||
// 'dry-run'
|
||||
// ]
|
||||
// .filter((opt) => options[opt])
|
||||
// .map((opt) => {
|
||||
// if (opt === 'yes' || opt === 'skip-install' || opt === 'dry-run') {
|
||||
// return `--${opt}`;
|
||||
// }
|
||||
// return `--${opt}=${options[opt]}`;
|
||||
// });
|
||||
|
||||
const child = spawn('node', [initScriptPath, ...args], {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd()
|
||||
});
|
||||
// const child = spawn('node', [initScriptPath, ...args], {
|
||||
// stdio: 'inherit',
|
||||
// cwd: process.cwd()
|
||||
// });
|
||||
|
||||
child.on('close', (code) => {
|
||||
process.exit(code);
|
||||
});
|
||||
});
|
||||
}
|
||||
// child.on('close', (code) => {
|
||||
// process.exit(code);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// Set up the command-line interface
|
||||
const program = new Command();
|
||||
@@ -286,8 +286,8 @@ program.on('--help', () => {
|
||||
displayHelp();
|
||||
});
|
||||
|
||||
// Add special case commands
|
||||
registerInitCommand(program);
|
||||
// // Add special case commands
|
||||
// registerInitCommand(program);
|
||||
|
||||
program
|
||||
.command('dev')
|
||||
@@ -303,7 +303,7 @@ registerCommands(tempProgram);
|
||||
|
||||
// For each command in the temp instance, add a modified version to our actual program
|
||||
tempProgram.commands.forEach((cmd) => {
|
||||
if (['init', 'dev'].includes(cmd.name())) {
|
||||
if (['dev'].includes(cmd.name())) {
|
||||
// Skip commands we've already defined specially
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import path from 'path';
|
||||
import { initializeProject, log as initLog } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
// isSilentMode // Not used directly here
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { getProjectRootFromSession } from '../../tools/utils.js'; // Adjust path if necessary
|
||||
import os from 'os'; // Import os module for home directory check
|
||||
|
||||
/**
|
||||
* Direct function wrapper for initializing a project.
|
||||
* Derives target directory from session, sets CWD, and calls core init logic.
|
||||
* @param {object} args - Arguments containing project details and options (projectName, projectDescription, yes, etc.)
|
||||
* @param {object} log - The FastMCP logger instance.
|
||||
* @param {object} context - The context object, must contain { session }.
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
|
||||
*/
|
||||
export async function initializeProjectDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
const homeDir = os.homedir();
|
||||
let targetDirectory = null;
|
||||
|
||||
log.info(
|
||||
`CONTEXT received in direct function: ${context ? JSON.stringify(Object.keys(context)) : 'MISSING or Falsy'}`
|
||||
);
|
||||
log.info(
|
||||
`SESSION extracted in direct function: ${session ? 'Exists' : 'MISSING or Falsy'}`
|
||||
);
|
||||
log.info(`Args received in direct function: ${JSON.stringify(args)}`);
|
||||
|
||||
// --- Determine Target Directory ---
|
||||
// 1. Prioritize projectRoot passed directly in args
|
||||
// Ensure it's not null, '/', or the home directory
|
||||
if (
|
||||
args.projectRoot &&
|
||||
args.projectRoot !== '/' &&
|
||||
args.projectRoot !== homeDir
|
||||
) {
|
||||
log.info(`Using projectRoot directly from args: ${args.projectRoot}`);
|
||||
targetDirectory = args.projectRoot;
|
||||
} else {
|
||||
// 2. If args.projectRoot is missing or invalid, THEN try session (as a fallback)
|
||||
log.warn(
|
||||
`args.projectRoot ('${args.projectRoot}') is missing or invalid. Attempting to derive from session.`
|
||||
);
|
||||
const sessionDerivedPath = getProjectRootFromSession(session, log);
|
||||
// Validate the session-derived path as well
|
||||
if (
|
||||
sessionDerivedPath &&
|
||||
sessionDerivedPath !== '/' &&
|
||||
sessionDerivedPath !== homeDir
|
||||
) {
|
||||
log.info(
|
||||
`Using project root derived from session: ${sessionDerivedPath}`
|
||||
);
|
||||
targetDirectory = sessionDerivedPath;
|
||||
} else {
|
||||
log.error(
|
||||
`Could not determine a valid project root. args.projectRoot='${args.projectRoot}', sessionDerivedPath='${sessionDerivedPath}'`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Validate the final targetDirectory
|
||||
if (!targetDirectory) {
|
||||
// This error now covers cases where neither args.projectRoot nor session provided a valid path
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_TARGET_DIRECTORY',
|
||||
message: `Cannot initialize project: Could not determine a valid target directory. Please ensure a workspace/folder is open or specify projectRoot.`,
|
||||
details: `Attempted args.projectRoot: ${args.projectRoot}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// --- Proceed with validated targetDirectory ---
|
||||
log.info(`Validated target directory for initialization: ${targetDirectory}`);
|
||||
|
||||
const originalCwd = process.cwd();
|
||||
let resultData;
|
||||
let success = false;
|
||||
let errorResult = null;
|
||||
|
||||
log.info(
|
||||
`Temporarily changing CWD to ${targetDirectory} for initialization.`
|
||||
);
|
||||
process.chdir(targetDirectory); // Change CWD to the *validated* targetDirectory
|
||||
|
||||
enableSilentMode(); // Enable silent mode BEFORE calling the core function
|
||||
try {
|
||||
// Always force yes: true when called via MCP to avoid interactive prompts
|
||||
const options = {
|
||||
name: args.projectName,
|
||||
description: args.projectDescription,
|
||||
version: args.projectVersion,
|
||||
author: args.authorName,
|
||||
skipInstall: args.skipInstall,
|
||||
aliases: args.addAliases,
|
||||
yes: true // Force yes mode
|
||||
};
|
||||
|
||||
log.info(`Initializing project with options: ${JSON.stringify(options)}`);
|
||||
const result = await initializeProject(options); // Call core logic
|
||||
|
||||
// Format success result for handleApiResult
|
||||
resultData = {
|
||||
message: 'Project initialized successfully.',
|
||||
next_step:
|
||||
'Now that the project is initialized, create a PRD file at scripts/prd.txt and use the parse-prd tool to generate initial tasks.',
|
||||
...result // Include details returned by initializeProject
|
||||
};
|
||||
success = true;
|
||||
log.info(
|
||||
`Project initialization completed successfully in ${targetDirectory}.`
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Core initializeProject failed: ${error.message}`);
|
||||
errorResult = {
|
||||
code: 'INITIALIZATION_FAILED',
|
||||
message: `Core project initialization failed: ${error.message}`,
|
||||
details: error.stack
|
||||
};
|
||||
success = false;
|
||||
} finally {
|
||||
disableSilentMode(); // ALWAYS disable silent mode in finally
|
||||
log.info(`Restoring original CWD: ${originalCwd}`);
|
||||
process.chdir(originalCwd); // Change back to original CWD
|
||||
}
|
||||
|
||||
// Return in format expected by handleApiResult
|
||||
if (success) {
|
||||
return { success: true, data: resultData, fromCache: false };
|
||||
} else {
|
||||
return { success: false, error: errorResult, fromCache: false };
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { fixDependenciesDirect } from './direct-functions/fix-dependencies.js';
|
||||
import { complexityReportDirect } from './direct-functions/complexity-report.js';
|
||||
import { addDependencyDirect } from './direct-functions/add-dependency.js';
|
||||
import { removeTaskDirect } from './direct-functions/remove-task.js';
|
||||
import { initializeProjectDirect } from './direct-functions/initialize-project-direct.js';
|
||||
|
||||
// Re-export utility functions
|
||||
export { findTasksJsonPath } from './utils/path-utils.js';
|
||||
@@ -92,5 +93,6 @@ export {
|
||||
fixDependenciesDirect,
|
||||
complexityReportDirect,
|
||||
addDependencyDirect,
|
||||
removeTaskDirect
|
||||
removeTaskDirect,
|
||||
initializeProjectDirect
|
||||
};
|
||||
|
||||
@@ -1,99 +1,93 @@
|
||||
import { z } from 'zod';
|
||||
import { execSync } from 'child_process';
|
||||
import { createContentResponse, createErrorResponse } from './utils.js'; // Only need response creators
|
||||
import {
|
||||
createContentResponse,
|
||||
createErrorResponse,
|
||||
handleApiResult
|
||||
} from './utils.js';
|
||||
import { initializeProjectDirect } from '../core/task-master-core.js';
|
||||
|
||||
export function registerInitializeProjectTool(server) {
|
||||
server.addTool({
|
||||
name: 'initialize_project', // snake_case for tool name
|
||||
name: 'initialize_project',
|
||||
description:
|
||||
"Initializes a new Task Master project structure in the current working directory by running 'task-master init'.",
|
||||
"Initializes a new Task Master project structure by calling the core initialization logic. Derives target directory from client session. If project details (name, description, author) are not provided, prompts the user or skips if 'yes' flag is true. DO NOT run without parameters.",
|
||||
parameters: z.object({
|
||||
projectName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The name for the new project.'),
|
||||
.describe(
|
||||
'The name for the new project. If not provided, prompt the user for it.'
|
||||
),
|
||||
projectDescription: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('A brief description for the project.'),
|
||||
.describe(
|
||||
'A brief description for the project. If not provided, prompt the user for it.'
|
||||
),
|
||||
projectVersion: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("The initial version for the project (e.g., '0.1.0')."),
|
||||
authorName: z.string().optional().describe("The author's name."),
|
||||
.describe(
|
||||
"The initial version for the project (e.g., '0.1.0'). User input not needed unless user requests to override."
|
||||
),
|
||||
authorName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"The author's name. User input not needed unless user requests to override."
|
||||
),
|
||||
skipInstall: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe('Skip installing dependencies automatically.'),
|
||||
.describe(
|
||||
'Skip installing dependencies automatically. Never do this unless you are sure the project is already installed.'
|
||||
),
|
||||
addAliases: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe('Add shell aliases (tm, taskmaster) to shell config file.'),
|
||||
.describe(
|
||||
'Add shell aliases (tm, taskmaster) to shell config file. User input not needed.'
|
||||
),
|
||||
yes: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe('Skip prompts and use default values or provided arguments.')
|
||||
// projectRoot is not needed here as 'init' works on the current directory
|
||||
.describe(
|
||||
"Skip prompts and use default values or provided arguments. Use true if you wish to skip details like the project name, etc. If the project information required for the initialization is not available or provided by the user, prompt if the user wishes to provide them (name, description, author) or skip them. If the user wishes to skip, set the 'yes' flag to true and do not set any other parameters."
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe(
|
||||
'The root directory for the project. ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY. IF NOT SET, THE TOOL WILL NOT WORK.'
|
||||
)
|
||||
}),
|
||||
execute: async (args, { log }) => {
|
||||
// Destructure context to get log
|
||||
execute: async (args, context) => {
|
||||
const { log } = context;
|
||||
const session = context.session;
|
||||
|
||||
log.info(
|
||||
'>>> Full Context Received by Tool:',
|
||||
JSON.stringify(context, null, 2)
|
||||
);
|
||||
log.info(`Context received in tool function: ${context}`);
|
||||
log.info(
|
||||
`Session received in tool function: ${session ? session : 'undefined'}`
|
||||
);
|
||||
|
||||
try {
|
||||
log.info(
|
||||
`Executing initialize_project with args: ${JSON.stringify(args)}`
|
||||
`Executing initialize_project tool with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
|
||||
// Construct the command arguments carefully
|
||||
// Using npx ensures it uses the locally installed version if available, or fetches it
|
||||
let command = 'npx task-master init';
|
||||
const cliArgs = [];
|
||||
if (args.projectName)
|
||||
cliArgs.push(`--name "${args.projectName.replace(/"/g, '\\"')}"`); // Escape quotes
|
||||
if (args.projectDescription)
|
||||
cliArgs.push(
|
||||
`--description "${args.projectDescription.replace(/"/g, '\\"')}"`
|
||||
);
|
||||
if (args.projectVersion)
|
||||
cliArgs.push(
|
||||
`--version "${args.projectVersion.replace(/"/g, '\\"')}"`
|
||||
);
|
||||
if (args.authorName)
|
||||
cliArgs.push(`--author "${args.authorName.replace(/"/g, '\\"')}"`);
|
||||
if (args.skipInstall) cliArgs.push('--skip-install');
|
||||
if (args.addAliases) cliArgs.push('--aliases');
|
||||
if (args.yes) cliArgs.push('--yes');
|
||||
const result = await initializeProjectDirect(args, log, { session });
|
||||
|
||||
command += ' ' + cliArgs.join(' ');
|
||||
|
||||
log.info(`Constructed command: ${command}`);
|
||||
|
||||
// Execute the command in the current working directory of the server process
|
||||
// Capture stdout/stderr. Use a reasonable timeout (e.g., 5 minutes)
|
||||
const output = execSync(command, {
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe',
|
||||
timeout: 300000
|
||||
});
|
||||
|
||||
log.info(`Initialization output:\n${output}`);
|
||||
|
||||
// Return a standard success response manually
|
||||
return createContentResponse({
|
||||
message: 'Taskmaster successfully initialized for this project.',
|
||||
next_step:
|
||||
'Now that the project is initialized, the next step is to create the tasks by parsing a PRD. This will create the tasks folder and the initial task files. The parse-prd tool will required a prd.txt file as input in scripts/prd.txt. You can create a prd.txt file by asking the user about their idea, and then using the scripts/example_prd.txt file as a template to genrate a prd.txt file in scripts/. Before creating the PRD for the user, make sure you understand the idea fully and ask questions to eliminate ambiguity. You can then use the parse-prd tool to create the tasks. So: step 1 after initialization is to create a prd.txt file in scripts/prd.txt. Step 2 is to use the parse-prd tool to create the tasks. Do not bother looking for tasks after initialization, just use the parse-prd tool to create the tasks after creating a prd.txt from which to parse the tasks. '
|
||||
});
|
||||
return handleApiResult(result, log, 'Initialization failed');
|
||||
} catch (error) {
|
||||
// Catch errors from execSync or timeouts
|
||||
const errorMessage = `Project initialization failed: ${error.message}`;
|
||||
const errorDetails =
|
||||
error.stderr?.toString() || error.stdout?.toString() || error.message; // Provide stderr/stdout if available
|
||||
log.error(`${errorMessage}\nDetails: ${errorDetails}`);
|
||||
|
||||
// Return a standard error response manually
|
||||
return createErrorResponse(errorMessage, { details: errorDetails });
|
||||
const errorMessage = `Project initialization tool failed: ${error.message || 'Unknown error'}`;
|
||||
log.error(errorMessage, error);
|
||||
return createErrorResponse(errorMessage, { details: error.stack });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
16114
package-lock.json
generated
16114
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"task-master": "bin/task-master.js",
|
||||
"task-master-init": "bin/task-master-init.js",
|
||||
"task-master-mcp": "mcp-server/server.js"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -16,7 +15,7 @@
|
||||
"test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
|
||||
"prepare-package": "node scripts/prepare-package.js",
|
||||
"prepublishOnly": "npm run prepare-package",
|
||||
"prepare": "chmod +x bin/task-master.js bin/task-master-init.js mcp-server/server.js",
|
||||
"prepare": "chmod +x bin/task-master.js mcp-server/server.js",
|
||||
"changeset": "changeset",
|
||||
"release": "changeset publish",
|
||||
"inspector": "npx @modelcontextprotocol/inspector node mcp-server/server.js",
|
||||
|
||||
554
scripts/init.js
554
scripts/init.js
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Task Master
|
||||
* Copyright (c) 2025 Eyal Toledano, Ralph Khreish
|
||||
@@ -15,8 +13,6 @@
|
||||
* For the full license text, see the LICENSE file in the root directory.
|
||||
*/
|
||||
|
||||
console.log('Starting task-master-ai...');
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
@@ -27,52 +23,27 @@ import chalk from 'chalk';
|
||||
import figlet from 'figlet';
|
||||
import boxen from 'boxen';
|
||||
import gradient from 'gradient-string';
|
||||
import { Command } from 'commander';
|
||||
import {
|
||||
isSilentMode,
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from './modules/utils.js';
|
||||
|
||||
// Debug information
|
||||
console.log('Node version:', process.version);
|
||||
console.log('Current directory:', process.cwd());
|
||||
console.log('Script path:', import.meta.url);
|
||||
// Only log if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
console.log('Starting task-master-ai...');
|
||||
}
|
||||
|
||||
// Debug information - only log if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
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);
|
||||
|
||||
// Configure the CLI program
|
||||
const program = new Command();
|
||||
program
|
||||
.name('task-master-init')
|
||||
.description('Initialize a new Claude Task Master project')
|
||||
.version('1.0.0') // Will be replaced by prepare-package script
|
||||
.option('-y, --yes', 'Skip prompts and use default values')
|
||||
.option('-n, --name <name>', 'Project name')
|
||||
.option('-my_name <name>', 'Project name (alias for --name)')
|
||||
.option('-d, --description <description>', 'Project description')
|
||||
.option(
|
||||
'-my_description <description>',
|
||||
'Project description (alias for --description)'
|
||||
)
|
||||
.option('-v, --version <version>', 'Project version')
|
||||
.option('-my_version <version>', 'Project version (alias for --version)')
|
||||
.option('--my_name <name>', 'Project name (alias for --name)')
|
||||
.option('-a, --author <author>', 'Author name')
|
||||
.option('--skip-install', 'Skip installing dependencies')
|
||||
.option('--dry-run', 'Show what would be done without making changes')
|
||||
.option('--aliases', 'Add shell aliases (tm, taskmaster)')
|
||||
.parse(process.argv);
|
||||
|
||||
const options = program.opts();
|
||||
|
||||
// Map custom aliases to standard options
|
||||
if (options.my_name && !options.name) {
|
||||
options.name = options.my_name;
|
||||
}
|
||||
if (options.my_description && !options.description) {
|
||||
options.description = options.my_description;
|
||||
}
|
||||
if (options.my_version && !options.version) {
|
||||
options.version = options.my_version;
|
||||
}
|
||||
|
||||
// Define log levels
|
||||
const LOG_LEVELS = {
|
||||
debug: 0,
|
||||
@@ -93,6 +64,8 @@ const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']);
|
||||
|
||||
// Display a fancy banner
|
||||
function displayBanner() {
|
||||
if (isSilentMode()) return;
|
||||
|
||||
console.clear();
|
||||
const bannerText = figlet.textSync('Task Master AI', {
|
||||
font: 'Standard',
|
||||
@@ -130,16 +103,19 @@ function log(level, ...args) {
|
||||
if (LOG_LEVELS[level] >= LOG_LEVEL) {
|
||||
const icon = icons[level] || '';
|
||||
|
||||
if (level === 'error') {
|
||||
console.error(icon, chalk.red(...args));
|
||||
} else if (level === 'warn') {
|
||||
console.warn(icon, chalk.yellow(...args));
|
||||
} else if (level === 'success') {
|
||||
console.log(icon, chalk.green(...args));
|
||||
} else if (level === 'info') {
|
||||
console.log(icon, chalk.blue(...args));
|
||||
} else {
|
||||
console.log(icon, ...args);
|
||||
// Only output to console if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
if (level === 'error') {
|
||||
console.error(icon, chalk.red(...args));
|
||||
} else if (level === 'warn') {
|
||||
console.warn(icon, chalk.yellow(...args));
|
||||
} else if (level === 'success') {
|
||||
console.log(icon, chalk.green(...args));
|
||||
} else if (level === 'info') {
|
||||
console.log(icon, chalk.blue(...args));
|
||||
} else {
|
||||
console.log(icon, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,20 +395,43 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
||||
log('info', `Created file: ${targetPath}`);
|
||||
}
|
||||
|
||||
// Main function to initialize a new project
|
||||
// Main function to initialize a new project (Now relies solely on passed options)
|
||||
async function initializeProject(options = {}) {
|
||||
// Display the banner
|
||||
displayBanner();
|
||||
// Receives options as argument
|
||||
// Only display banner if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
// 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 || '';
|
||||
// Debug logging only if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
console.log('===== DEBUG: INITIALIZE PROJECT OPTIONS RECEIVED =====');
|
||||
console.log('Full options object:', JSON.stringify(options));
|
||||
console.log('options.yes:', options.yes);
|
||||
console.log('options.name:', options.name);
|
||||
console.log('==================================================');
|
||||
}
|
||||
|
||||
// Determine if we should skip prompts based on the passed options
|
||||
const skipPrompts = options.yes || (options.name && options.description);
|
||||
if (!isSilentMode()) {
|
||||
console.log('Skip prompts determined:', skipPrompts);
|
||||
}
|
||||
|
||||
if (skipPrompts) {
|
||||
if (!isSilentMode()) {
|
||||
console.log('SKIPPING PROMPTS - Using defaults or provided values');
|
||||
}
|
||||
|
||||
// Use provided options or defaults
|
||||
const projectName = options.name || 'task-master-project';
|
||||
const projectDescription =
|
||||
options.description || 'A project managed with Task Master AI';
|
||||
const projectVersion = options.version || '0.1.0'; // Default from commands.js or here
|
||||
const authorName = options.author || 'Vibe coder'; // Default if not provided
|
||||
const dryRun = options.dryRun || false;
|
||||
const skipInstall = options.skipInstall || false;
|
||||
const addAliases = options.addAliases || false;
|
||||
const addAliases = options.aliases || false;
|
||||
|
||||
if (dryRun) {
|
||||
log('info', 'DRY RUN MODE: No files will be modified');
|
||||
@@ -458,6 +457,7 @@ async function initializeProject(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// Create structure using determined values
|
||||
createProjectStructure(
|
||||
projectName,
|
||||
projectDescription,
|
||||
@@ -466,120 +466,112 @@ async function initializeProject(options = {}) {
|
||||
skipInstall,
|
||||
addAliases
|
||||
);
|
||||
return {
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Prompting logic (only runs if skipPrompts is false)
|
||||
log('info', 'Required options not provided, proceeding with prompts.');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// Otherwise, prompt the user for input
|
||||
// Create readline interface only when needed
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
try {
|
||||
// Prompt user for input...
|
||||
const projectName = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project name: ')
|
||||
);
|
||||
const projectDescription = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project description: ')
|
||||
);
|
||||
const projectVersionInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project version (default: 1.0.0): ')
|
||||
); // Use a default for prompt
|
||||
const authorName = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter your name: ')
|
||||
);
|
||||
const addAliasesInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Add shell aliases for task-master? (Y/n): ')
|
||||
);
|
||||
const addAliasesPrompted = addAliasesInput.trim().toLowerCase() !== 'n';
|
||||
const projectVersion = projectVersionInput.trim()
|
||||
? projectVersionInput
|
||||
: '1.0.0';
|
||||
|
||||
try {
|
||||
const projectName = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project name: ')
|
||||
);
|
||||
const projectDescription = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project description: ')
|
||||
);
|
||||
const projectVersionInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project version (default: 1.0.0): ')
|
||||
);
|
||||
const authorName = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter your name: ')
|
||||
);
|
||||
// Confirm settings...
|
||||
console.log('\nProject settings:');
|
||||
console.log(chalk.blue('Name:'), chalk.white(projectName));
|
||||
console.log(chalk.blue('Description:'), chalk.white(projectDescription));
|
||||
console.log(chalk.blue('Version:'), chalk.white(projectVersion));
|
||||
console.log(
|
||||
chalk.blue('Author:'),
|
||||
chalk.white(authorName || 'Not specified')
|
||||
);
|
||||
console.log(
|
||||
chalk.blue(
|
||||
'Add shell aliases (so you can use "tm" instead of "task-master"):'
|
||||
),
|
||||
chalk.white(addAliasesPrompted ? 'Yes' : 'No')
|
||||
);
|
||||
|
||||
// Ask about shell aliases
|
||||
const addAliasesInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Add shell aliases for task-master? (Y/n): ')
|
||||
);
|
||||
const addAliases = addAliasesInput.trim().toLowerCase() !== 'n';
|
||||
const confirmInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.yellow('\nDo you want to continue with these settings? (Y/n): ')
|
||||
);
|
||||
const shouldContinue = confirmInput.trim().toLowerCase() !== 'n';
|
||||
rl.close();
|
||||
|
||||
// Set default version if not provided
|
||||
const projectVersion = projectVersionInput.trim()
|
||||
? projectVersionInput
|
||||
: '1.0.0';
|
||||
|
||||
// Confirm settings
|
||||
console.log('\nProject settings:');
|
||||
console.log(chalk.blue('Name:'), chalk.white(projectName));
|
||||
console.log(chalk.blue('Description:'), chalk.white(projectDescription));
|
||||
console.log(chalk.blue('Version:'), chalk.white(projectVersion));
|
||||
console.log(
|
||||
chalk.blue('Author:'),
|
||||
chalk.white(authorName || 'Not specified')
|
||||
);
|
||||
console.log(
|
||||
chalk.blue('Add shell aliases:'),
|
||||
chalk.white(addAliases ? 'Yes' : 'No')
|
||||
);
|
||||
|
||||
const confirmInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.yellow('\nDo you want to continue with these settings? (Y/n): ')
|
||||
);
|
||||
const shouldContinue = confirmInput.trim().toLowerCase() !== 'n';
|
||||
|
||||
// Close the readline interface
|
||||
rl.close();
|
||||
|
||||
if (!shouldContinue) {
|
||||
log('info', 'Project initialization cancelled by user');
|
||||
return null;
|
||||
}
|
||||
|
||||
const dryRun = options.dryRun || false;
|
||||
const skipInstall = options.skipInstall || false;
|
||||
|
||||
if (dryRun) {
|
||||
log('info', 'DRY RUN MODE: No files will be modified');
|
||||
log('info', 'Would create/update necessary project files');
|
||||
if (addAliases) {
|
||||
log('info', 'Would add shell aliases for task-master');
|
||||
if (!shouldContinue) {
|
||||
log('info', 'Project initialization cancelled by user');
|
||||
process.exit(0); // Exit if cancelled
|
||||
return; // Added return for clarity
|
||||
}
|
||||
if (!skipInstall) {
|
||||
log('info', 'Would install dependencies');
|
||||
|
||||
// Still respect dryRun/skipInstall if passed initially even when prompting
|
||||
const dryRun = options.dryRun || false;
|
||||
const skipInstall = options.skipInstall || false;
|
||||
|
||||
if (dryRun) {
|
||||
log('info', 'DRY RUN MODE: No files will be modified');
|
||||
log(
|
||||
'info',
|
||||
`Would initialize project: ${projectName} (${projectVersion})`
|
||||
);
|
||||
log('info', `Description: ${projectDescription}`);
|
||||
log('info', `Author: ${authorName || 'Not specified'}`);
|
||||
log('info', 'Would create/update necessary project files');
|
||||
if (addAliasesPrompted) {
|
||||
log('info', 'Would add shell aliases for task-master');
|
||||
}
|
||||
if (!skipInstall) {
|
||||
log('info', 'Would install dependencies');
|
||||
}
|
||||
return {
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
dryRun: true
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
// Create structure using prompted values, respecting initial options where relevant
|
||||
createProjectStructure(
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
dryRun: true
|
||||
};
|
||||
skipInstall, // Use value from initial options
|
||||
addAliasesPrompted // Use value from prompt
|
||||
);
|
||||
} catch (error) {
|
||||
rl.close();
|
||||
log('error', `Error during prompting: ${error.message}`); // Use log function
|
||||
process.exit(1); // Exit on error during prompts
|
||||
}
|
||||
|
||||
// Create the project structure
|
||||
createProjectStructure(
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
skipInstall,
|
||||
addAliases
|
||||
);
|
||||
|
||||
return {
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to close readline on error
|
||||
rl.close();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -789,14 +781,16 @@ function createProjectStructure(
|
||||
}
|
||||
|
||||
// Run npm install automatically
|
||||
console.log(
|
||||
boxen(chalk.cyan('Installing dependencies...'), {
|
||||
padding: 0.5,
|
||||
margin: 0.5,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'blue'
|
||||
})
|
||||
);
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.cyan('Installing dependencies...'), {
|
||||
padding: 0.5,
|
||||
margin: 0.5,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'blue'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!skipInstall) {
|
||||
@@ -811,21 +805,23 @@ function createProjectStructure(
|
||||
}
|
||||
|
||||
// Display success message
|
||||
console.log(
|
||||
boxen(
|
||||
warmGradient.multiline(
|
||||
figlet.textSync('Success!', { font: 'Standard' })
|
||||
) +
|
||||
'\n' +
|
||||
chalk.green('Project initialized successfully!'),
|
||||
{
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
borderStyle: 'double',
|
||||
borderColor: 'green'
|
||||
}
|
||||
)
|
||||
);
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
warmGradient.multiline(
|
||||
figlet.textSync('Success!', { font: 'Standard' })
|
||||
) +
|
||||
'\n' +
|
||||
chalk.green('Project initialized successfully!'),
|
||||
{
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
borderStyle: 'double',
|
||||
borderColor: 'green'
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Add shell aliases if requested
|
||||
if (addAliases) {
|
||||
@@ -833,68 +829,70 @@ function createProjectStructure(
|
||||
}
|
||||
|
||||
// Display next steps in a nice box
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.cyan.bold('Things you can now do:') +
|
||||
'\n\n' +
|
||||
chalk.white('1. ') +
|
||||
chalk.yellow(
|
||||
'Rename .env.example to .env and add your ANTHROPIC_API_KEY and PERPLEXITY_API_KEY'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('2. ') +
|
||||
chalk.yellow(
|
||||
'Discuss your idea with AI, and once ready ask for a PRD using the example_prd.txt file, and save what you get to scripts/PRD.txt'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('3. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor Agent to parse your PRD.txt and generate tasks'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white(' └─ ') +
|
||||
chalk.dim('You can also run ') +
|
||||
chalk.cyan('task-master parse-prd <your-prd-file.txt>') +
|
||||
'\n' +
|
||||
chalk.white('4. ') +
|
||||
chalk.yellow('Ask Cursor to analyze the complexity of your tasks') +
|
||||
'\n' +
|
||||
chalk.white('5. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor which task is next to determine where to start'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('6. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor to expand any complex tasks that are too large or complex.'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('7. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor to set the status of a task, or multiple tasks. Use the task id from the task lists.'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('8. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor to update all tasks from a specific task id based on new learnings or pivots in your project.'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('9. ') +
|
||||
chalk.green.bold('Ship it!') +
|
||||
'\n\n' +
|
||||
chalk.dim(
|
||||
'* Review the README.md file to learn how to use other commands via Cursor Agent.'
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow',
|
||||
title: 'Getting Started',
|
||||
titleAlignment: 'center'
|
||||
}
|
||||
)
|
||||
);
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.cyan.bold('Things you can now do:') +
|
||||
'\n\n' +
|
||||
chalk.white('1. ') +
|
||||
chalk.yellow(
|
||||
'Rename .env.example to .env and add your ANTHROPIC_API_KEY and PERPLEXITY_API_KEY'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('2. ') +
|
||||
chalk.yellow(
|
||||
'Discuss your idea with AI, and once ready ask for a PRD using the example_prd.txt file, and save what you get to scripts/PRD.txt'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('3. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor Agent to parse your PRD.txt and generate tasks'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white(' └─ ') +
|
||||
chalk.dim('You can also run ') +
|
||||
chalk.cyan('task-master parse-prd <your-prd-file.txt>') +
|
||||
'\n' +
|
||||
chalk.white('4. ') +
|
||||
chalk.yellow('Ask Cursor to analyze the complexity of your tasks') +
|
||||
'\n' +
|
||||
chalk.white('5. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor which task is next to determine where to start'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('6. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor to expand any complex tasks that are too large or complex.'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('7. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor to set the status of a task, or multiple tasks. Use the task id from the task lists.'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('8. ') +
|
||||
chalk.yellow(
|
||||
'Ask Cursor to update all tasks from a specific task id based on new learnings or pivots in your project.'
|
||||
) +
|
||||
'\n' +
|
||||
chalk.white('9. ') +
|
||||
chalk.green.bold('Ship it!') +
|
||||
'\n\n' +
|
||||
chalk.dim(
|
||||
'* Review the README.md file to learn how to use other commands via Cursor Agent.'
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow',
|
||||
title: 'Getting Started',
|
||||
titleAlignment: 'center'
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to setup MCP configuration for Cursor integration
|
||||
@@ -985,51 +983,5 @@ function setupMCPConfiguration(targetDir, projectName) {
|
||||
log('info', 'MCP server will use the installed task-master-ai package');
|
||||
}
|
||||
|
||||
// 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...');
|
||||
|
||||
// Check if we should use the CLI options or prompt for input
|
||||
if (options.yes || (options.name && options.description)) {
|
||||
// When using --yes flag or providing name and description, use CLI options
|
||||
await initializeProject({
|
||||
projectName: options.name || 'task-master-project',
|
||||
projectDescription:
|
||||
options.description ||
|
||||
'A task management system for AI-driven development',
|
||||
projectVersion: options.version || '1.0.0',
|
||||
authorName: options.author || '',
|
||||
dryRun: options.dryRun || false,
|
||||
skipInstall: options.skipInstall || false,
|
||||
addAliases: options.aliases || false
|
||||
});
|
||||
} else {
|
||||
// Otherwise, prompt for input normally
|
||||
await initializeProject({
|
||||
dryRun: options.dryRun || false,
|
||||
skipInstall: options.skipInstall || false
|
||||
});
|
||||
}
|
||||
|
||||
// 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 };
|
||||
// Ensure necessary functions are exported
|
||||
export { initializeProject, log }; // Only export what's needed by commands.js
|
||||
|
||||
@@ -10,8 +10,9 @@ import boxen from 'boxen';
|
||||
import fs from 'fs';
|
||||
import https from 'https';
|
||||
import inquirer from 'inquirer';
|
||||
import ora from 'ora';
|
||||
|
||||
import { CONFIG, log, readJSON } from './utils.js';
|
||||
import { CONFIG, log, readJSON, writeJSON } from './utils.js';
|
||||
import {
|
||||
parsePRD,
|
||||
updateTasks,
|
||||
@@ -51,6 +52,8 @@ import {
|
||||
stopLoadingIndicator
|
||||
} from './ui.js';
|
||||
|
||||
import { initializeProject } from '../init.js';
|
||||
|
||||
/**
|
||||
* Configure and register CLI commands
|
||||
* @param {Object} program - Commander program instance
|
||||
@@ -1368,44 +1371,6 @@ function registerCommands(programInstance) {
|
||||
);
|
||||
}
|
||||
|
||||
// init command (documentation only, implementation is in init.js)
|
||||
programInstance
|
||||
.command('init')
|
||||
.description('Initialize a new project with Task Master structure')
|
||||
.option('-n, --name <name>', 'Project name')
|
||||
.option('-my_name <name>', 'Project name (alias for --name)')
|
||||
.option('--my_name <name>', 'Project name (alias for --name)')
|
||||
.option('-d, --description <description>', 'Project description')
|
||||
.option(
|
||||
'-my_description <description>',
|
||||
'Project description (alias for --description)'
|
||||
)
|
||||
.option('-v, --version <version>', 'Project version')
|
||||
.option('-my_version <version>', 'Project version (alias for --version)')
|
||||
.option('-a, --author <author>', 'Author name')
|
||||
.option('-y, --yes', 'Skip prompts and use default values')
|
||||
.option('--skip-install', 'Skip installing dependencies')
|
||||
.action(() => {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'The init command must be run as a standalone command: task-master init'
|
||||
)
|
||||
);
|
||||
console.log(chalk.cyan('Example usage:'));
|
||||
console.log(
|
||||
chalk.white(
|
||||
' task-master init -n "My Project" -d "Project description"'
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.white(
|
||||
' task-master init -my_name "My Project" -my_description "Project description"'
|
||||
)
|
||||
);
|
||||
console.log(chalk.white(' task-master init -y'));
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// remove-task command
|
||||
programInstance
|
||||
.command('remove-task')
|
||||
@@ -1552,6 +1517,37 @@ function registerCommands(programInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
// init command (Directly calls the implementation from init.js)
|
||||
programInstance
|
||||
.command('init')
|
||||
.description('Initialize a new project with Task Master structure')
|
||||
.option('-y, --yes', 'Skip prompts and use default values')
|
||||
.option('-n, --name <name>', 'Project name')
|
||||
.option('-d, --description <description>', 'Project description')
|
||||
.option('-v, --version <version>', 'Project version', '0.1.0') // Set default here
|
||||
.option('-a, --author <author>', 'Author name')
|
||||
.option('--skip-install', 'Skip installing dependencies')
|
||||
.option('--dry-run', 'Show what would be done without making changes')
|
||||
.option('--aliases', 'Add shell aliases (tm, taskmaster)')
|
||||
.action(async (cmdOptions) => {
|
||||
// cmdOptions contains parsed arguments
|
||||
try {
|
||||
console.log('DEBUG: Running init command action in commands.js');
|
||||
console.log(
|
||||
'DEBUG: Options received by action:',
|
||||
JSON.stringify(cmdOptions)
|
||||
);
|
||||
// Directly call the initializeProject function, passing the parsed options
|
||||
await initializeProject(cmdOptions);
|
||||
// initializeProject handles its own flow, including potential process.exit()
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red(`Error during initialization: ${error.message}`)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Add more commands as needed...
|
||||
|
||||
return programInstance;
|
||||
|
||||
39
tasks/task_060.txt
Normal file
39
tasks/task_060.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
# Task ID: 60
|
||||
# Title: Implement isValidTaskId Utility Function
|
||||
# Status: pending
|
||||
# Dependencies: None
|
||||
# Priority: medium
|
||||
# Description: Create a utility function that validates whether a given string conforms to the project's task ID format specification.
|
||||
# Details:
|
||||
Develop a function named `isValidTaskId` that takes a string parameter and returns a boolean indicating whether the string matches our task ID format. The task ID format follows these rules:
|
||||
|
||||
1. Must start with 'TASK-' prefix (case-sensitive)
|
||||
2. Followed by a numeric value (at least 1 digit)
|
||||
3. The numeric portion should not have leading zeros (unless it's just zero)
|
||||
4. The total length should be between 6 and 12 characters inclusive
|
||||
|
||||
Example valid IDs: 'TASK-1', 'TASK-42', 'TASK-1000'
|
||||
Example invalid IDs: 'task-1' (wrong case), 'TASK-' (missing number), 'TASK-01' (leading zero), 'TASK-A1' (non-numeric), 'TSK-1' (wrong prefix)
|
||||
|
||||
The function should be placed in the utilities directory and properly exported. Include JSDoc comments for clear documentation of parameters and return values.
|
||||
|
||||
# Test Strategy:
|
||||
Testing should include the following cases:
|
||||
|
||||
1. Valid task IDs:
|
||||
- 'TASK-1'
|
||||
- 'TASK-123'
|
||||
- 'TASK-9999'
|
||||
|
||||
2. Invalid task IDs:
|
||||
- Null or undefined input
|
||||
- Empty string
|
||||
- 'task-1' (lowercase prefix)
|
||||
- 'TASK-' (missing number)
|
||||
- 'TASK-01' (leading zero)
|
||||
- 'TASK-ABC' (non-numeric suffix)
|
||||
- 'TSK-1' (incorrect prefix)
|
||||
- 'TASK-12345678901' (too long)
|
||||
- 'TASK1' (missing hyphen)
|
||||
|
||||
Implement unit tests using the project's testing framework. Each test case should have a clear assertion message explaining why the test failed if it does. Also include edge cases such as strings with whitespace ('TASK- 1') or special characters ('TASK-1#').
|
||||
@@ -2726,6 +2726,16 @@
|
||||
"priority": "medium",
|
||||
"details": "Currently, the application is attempting to manually modify users' package.json files, which is not the recommended approach for npm packages. Instead:\n\n1. Review all code that directly manipulates package.json files in users' projects\n2. Remove these manual modifications\n3. Properly define all dependencies in the package.json of task-master-ai itself\n4. Ensure all peer dependencies are correctly specified\n5. For any scripts that need to be available to users, use proper npm bin linking or npx commands\n6. Update the installation process to leverage npm's built-in dependency management\n7. If configuration is needed in users' projects, implement a proper initialization command that creates config files rather than modifying package.json\n8. Document the new approach in the README and any other relevant documentation\n\nThis change will make the package more reliable, follow npm best practices, and prevent potential conflicts or errors when modifying users' project files.",
|
||||
"testStrategy": "1. Create a fresh test project directory\n2. Install the updated task-master-ai package using npm install task-master-ai\n3. Verify that no code attempts to modify the test project's package.json\n4. Confirm all dependencies are properly installed in node_modules\n5. Test all commands to ensure they work without the previous manual package.json modifications\n6. Try installing in projects with various existing configurations to ensure no conflicts occur\n7. Test the uninstall process to verify it cleanly removes the package without leaving unwanted modifications\n8. Verify the package works in different npm environments (npm 6, 7, 8) and with different Node.js versions\n9. Create an integration test that simulates a real user workflow from installation through usage"
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"title": "Implement isValidTaskId Utility Function",
|
||||
"description": "Create a utility function that validates whether a given string conforms to the project's task ID format specification.",
|
||||
"details": "Develop a function named `isValidTaskId` that takes a string parameter and returns a boolean indicating whether the string matches our task ID format. The task ID format follows these rules:\n\n1. Must start with 'TASK-' prefix (case-sensitive)\n2. Followed by a numeric value (at least 1 digit)\n3. The numeric portion should not have leading zeros (unless it's just zero)\n4. The total length should be between 6 and 12 characters inclusive\n\nExample valid IDs: 'TASK-1', 'TASK-42', 'TASK-1000'\nExample invalid IDs: 'task-1' (wrong case), 'TASK-' (missing number), 'TASK-01' (leading zero), 'TASK-A1' (non-numeric), 'TSK-1' (wrong prefix)\n\nThe function should be placed in the utilities directory and properly exported. Include JSDoc comments for clear documentation of parameters and return values.",
|
||||
"testStrategy": "Testing should include the following cases:\n\n1. Valid task IDs:\n - 'TASK-1'\n - 'TASK-123'\n - 'TASK-9999'\n\n2. Invalid task IDs:\n - Null or undefined input\n - Empty string\n - 'task-1' (lowercase prefix)\n - 'TASK-' (missing number)\n - 'TASK-01' (leading zero)\n - 'TASK-ABC' (non-numeric suffix)\n - 'TSK-1' (incorrect prefix)\n - 'TASK-12345678901' (too long)\n - 'TASK1' (missing hyphen)\n\nImplement unit tests using the project's testing framework. Each test case should have a clear assertion message explaining why the test failed if it does. Also include edge cases such as strings with whitespace ('TASK- 1') or special characters ('TASK-1#').",
|
||||
"status": "pending",
|
||||
"dependencies": [],
|
||||
"priority": "medium"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": [],
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": [],
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user