Compare commits
9 Commits
crunchyman
...
crunchyman
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7439be3803 | ||
|
|
38e416ef33 | ||
|
|
aa185b28b2 | ||
|
|
76618187f6 | ||
|
|
757fd478d2 | ||
|
|
6e6407f683 | ||
|
|
80f933cd82 | ||
|
|
2c3986c097 | ||
|
|
7086a77625 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Added changeset config #39
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
add github actions to automate github and npm releases
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Implement MCP server for all commands using tools.
|
||||
5
.changeset/red-lights-mix.md
Normal file
5
.changeset/red-lights-mix.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix github actions creating npm releases on next branch push
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix addTask tool `projectRoot not defined`
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Add license to repo
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
fix mcp server not connecting to cursor
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix workflows
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -3,7 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
27
CHANGELOG.md
Normal file
27
CHANGELOG.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#80](https://github.com/eyaltoledano/claude-task-master/pull/80) [`aa185b2`](https://github.com/eyaltoledano/claude-task-master/commit/aa185b28b248b4ca93f9195b502e2f5187868eaa) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Remove non-existent package `@model-context-protocol/sdk`
|
||||
|
||||
- [#45](https://github.com/eyaltoledano/claude-task-master/pull/45) [`757fd47`](https://github.com/eyaltoledano/claude-task-master/commit/757fd478d2e2eff8506ae746c3470c6088f4d944) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add license to repo
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#44](https://github.com/eyaltoledano/claude-task-master/pull/44) [`eafdb47`](https://github.com/eyaltoledano/claude-task-master/commit/eafdb47418b444c03c092f653b438cc762d4bca8) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - add github actions to automate github and npm releases
|
||||
|
||||
- [#20](https://github.com/eyaltoledano/claude-task-master/pull/20) [`4eed269`](https://github.com/eyaltoledano/claude-task-master/commit/4eed2693789a444f704051d5fbb3ef8d460e4e69) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Implement MCP server for all commands using tools.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#44](https://github.com/eyaltoledano/claude-task-master/pull/44) [`44db895`](https://github.com/eyaltoledano/claude-task-master/commit/44db895303a9209416236e3d519c8a609ad85f61) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Added changeset config #39
|
||||
|
||||
- [#50](https://github.com/eyaltoledano/claude-task-master/pull/50) [`257160a`](https://github.com/eyaltoledano/claude-task-master/commit/257160a9670b5d1942e7c623bd2c1a3fde7c06a0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix addTask tool `projectRoot not defined`
|
||||
|
||||
- [#57](https://github.com/eyaltoledano/claude-task-master/pull/57) [`9fd42ee`](https://github.com/eyaltoledano/claude-task-master/commit/9fd42eeafdc25a96cdfb70aa3af01f525d26b4bc) Thanks [@github-actions](https://github.com/apps/github-actions)! - fix mcp server not connecting to cursor
|
||||
|
||||
- [#48](https://github.com/eyaltoledano/claude-task-master/pull/48) [`5ec3651`](https://github.com/eyaltoledano/claude-task-master/commit/5ec3651e6459add7354910a86b3c4db4d12bc5d1) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix workflows
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -7,7 +7,7 @@
|
||||
"": {
|
||||
"name": "task-master-ai",
|
||||
"version": "0.9.30",
|
||||
"license": "MIT",
|
||||
"license": "(BSL-1.1 AND Apache-2.0)",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"boxen": "^8.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.9.30",
|
||||
"version": "0.10.1",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
@@ -37,7 +37,6 @@
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@model-context-protocol/sdk": "^1.20.5",
|
||||
"boxen": "^8.0.1",
|
||||
"chalk": "^4.1.2",
|
||||
"cli-table3": "^0.6.5",
|
||||
|
||||
@@ -41,7 +41,8 @@ import {
|
||||
displayNextTask,
|
||||
displayTaskById,
|
||||
displayComplexityReport,
|
||||
getStatusWithColor
|
||||
getStatusWithColor,
|
||||
confirmTaskOverwrite
|
||||
} from './ui.js';
|
||||
|
||||
/**
|
||||
@@ -70,17 +71,34 @@ function registerCommands(programInstance) {
|
||||
.option('-i, --input <file>', 'Path to the PRD file (alternative to positional argument)')
|
||||
.option('-o, --output <file>', 'Output file path', 'tasks/tasks.json')
|
||||
.option('-n, --num-tasks <number>', 'Number of tasks to generate', '10')
|
||||
.option('-f, --force', 'Skip confirmation when overwriting existing tasks')
|
||||
.action(async (file, options) => {
|
||||
// Use input option if file argument not provided
|
||||
const inputFile = file || options.input;
|
||||
const defaultPrdPath = 'scripts/prd.txt';
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
const force = options.force || false;
|
||||
|
||||
// Helper function to check if tasks.json exists and confirm overwrite
|
||||
async function confirmOverwriteIfNeeded() {
|
||||
if (fs.existsSync(outputPath) && !force) {
|
||||
const shouldContinue = await confirmTaskOverwrite(outputPath);
|
||||
if (!shouldContinue) {
|
||||
console.log(chalk.yellow('Operation cancelled by user.'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no input file specified, check for default PRD location
|
||||
if (!inputFile) {
|
||||
if (fs.existsSync(defaultPrdPath)) {
|
||||
console.log(chalk.blue(`Using default PRD file: ${defaultPrdPath}`));
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
// Check for existing tasks.json before proceeding
|
||||
if (!await confirmOverwriteIfNeeded()) return;
|
||||
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
await parsePRD(defaultPrdPath, outputPath, numTasks);
|
||||
@@ -95,10 +113,12 @@ function registerCommands(programInstance) {
|
||||
chalk.cyan('Options:') + '\n' +
|
||||
' -i, --input <file> Path to the PRD file (alternative to positional argument)\n' +
|
||||
' -o, --output <file> Output file path (default: "tasks/tasks.json")\n' +
|
||||
' -n, --num-tasks <number> Number of tasks to generate (default: 10)\n\n' +
|
||||
' -n, --num-tasks <number> Number of tasks to generate (default: 10)\n' +
|
||||
' -f, --force Skip confirmation when overwriting existing tasks\n\n' +
|
||||
chalk.cyan('Example:') + '\n' +
|
||||
' task-master parse-prd requirements.txt --num-tasks 15\n' +
|
||||
' task-master parse-prd --input=requirements.txt\n\n' +
|
||||
' task-master parse-prd --input=requirements.txt\n' +
|
||||
' task-master parse-prd --force\n\n' +
|
||||
chalk.yellow('Note: This command will:') + '\n' +
|
||||
' 1. Look for a PRD file at scripts/prd.txt by default\n' +
|
||||
' 2. Use the file specified by --input or positional argument if provided\n' +
|
||||
@@ -108,8 +128,8 @@ function registerCommands(programInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
// Check for existing tasks.json before proceeding with specified input file
|
||||
if (!await confirmOverwriteIfNeeded()) return;
|
||||
|
||||
console.log(chalk.blue(`Parsing PRD file: ${inputFile}`));
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
|
||||
@@ -1061,6 +1061,33 @@ async function displayComplexityReport(reportPath) {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm overwriting existing tasks.json file
|
||||
* @param {string} tasksPath - Path to the tasks.json file
|
||||
* @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
|
||||
*/
|
||||
async function confirmTaskOverwrite(tasksPath) {
|
||||
console.log(boxen(
|
||||
chalk.yellow('It looks like you\'ve already generated tasks for this project.\n') +
|
||||
chalk.yellow('Executing this command will overwrite any existing tasks.'),
|
||||
{ padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } }
|
||||
));
|
||||
|
||||
// Use dynamic import to get the readline module
|
||||
const readline = await import('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
const answer = await new Promise(resolve => {
|
||||
rl.question(chalk.cyan('Are you sure you wish to continue? (y/N): '), resolve);
|
||||
});
|
||||
rl.close();
|
||||
|
||||
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
||||
}
|
||||
|
||||
// Export UI functions
|
||||
export {
|
||||
displayBanner,
|
||||
@@ -1074,4 +1101,5 @@ export {
|
||||
displayNextTask,
|
||||
displayTaskById,
|
||||
displayComplexityReport,
|
||||
confirmTaskOverwrite
|
||||
};
|
||||
@@ -25,6 +25,7 @@ const mockIsTaskDependentOn = jest.fn().mockReturnValue(false);
|
||||
const mockCreate = jest.fn(); // Mock for Anthropic messages.create
|
||||
const mockChatCompletionsCreate = jest.fn(); // Mock for Perplexity chat.completions.create
|
||||
const mockGetAvailableAIModel = jest.fn(); // <<<<< Added mock function
|
||||
const mockPromptYesNo = jest.fn(); // Mock for confirmation prompt
|
||||
|
||||
// Mock fs module
|
||||
jest.mock('fs', () => ({
|
||||
@@ -76,6 +77,7 @@ jest.mock('../../scripts/modules/utils.js', () => ({
|
||||
readComplexityReport: jest.fn(), // <<<<< Added mock
|
||||
findTaskInComplexityReport: jest.fn(), // <<<<< Added mock
|
||||
truncate: jest.fn((str, len) => str.slice(0, len)), // <<<<< Added mock
|
||||
promptYesNo: mockPromptYesNo, // Added mock for confirmation prompt
|
||||
}));
|
||||
|
||||
// Mock AI services - Update this mock
|
||||
@@ -129,6 +131,19 @@ jest.mock('../../scripts/modules/task-manager.js', () => {
|
||||
// Create a simplified version of parsePRD for testing
|
||||
const testParsePRD = async (prdPath, outputPath, numTasks) => {
|
||||
try {
|
||||
// Check if the output file already exists
|
||||
if (mockExistsSync(outputPath)) {
|
||||
const confirmOverwrite = await mockPromptYesNo(
|
||||
`Warning: ${outputPath} already exists. Overwrite?`,
|
||||
false
|
||||
);
|
||||
|
||||
if (!confirmOverwrite) {
|
||||
console.log(`Operation cancelled. ${outputPath} was not modified.`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const prdContent = mockReadFileSync(prdPath, 'utf8');
|
||||
const tasks = await mockCallClaude(prdContent, prdPath, numTasks);
|
||||
const dir = mockDirname(outputPath);
|
||||
@@ -563,6 +578,7 @@ describe('Task Manager Module', () => {
|
||||
mockDirname.mockReturnValue('tasks');
|
||||
mockCallClaude.mockResolvedValue(sampleClaudeResponse);
|
||||
mockGenerateTaskFiles.mockResolvedValue(undefined);
|
||||
mockPromptYesNo.mockResolvedValue(true); // Default to "yes" for confirmation
|
||||
});
|
||||
|
||||
test('should parse a PRD file and generate tasks', async () => {
|
||||
@@ -586,8 +602,13 @@ describe('Task Manager Module', () => {
|
||||
});
|
||||
|
||||
test('should create the tasks directory if it does not exist', async () => {
|
||||
// Mock existsSync to return false to simulate directory doesn't exist
|
||||
mockExistsSync.mockReturnValueOnce(false);
|
||||
// Mock existsSync to return false specifically for the directory check
|
||||
// but true for the output file check (so we don't trigger confirmation path)
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
|
||||
if (path === 'tasks') return false; // Directory doesn't exist
|
||||
return true; // Default for other paths
|
||||
});
|
||||
|
||||
// Call the function
|
||||
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
|
||||
@@ -624,6 +645,83 @@ describe('Task Manager Module', () => {
|
||||
// Verify generateTaskFiles was called
|
||||
expect(mockGenerateTaskFiles).toHaveBeenCalledWith('tasks/tasks.json', 'tasks');
|
||||
});
|
||||
|
||||
test('should prompt for confirmation when tasks.json already exists', async () => {
|
||||
// Setup mocks to simulate tasks.json already exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return true; // Output file exists
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Call the function
|
||||
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
|
||||
|
||||
// Verify prompt was called with expected message
|
||||
expect(mockPromptYesNo).toHaveBeenCalledWith(
|
||||
'Warning: tasks/tasks.json already exists. Overwrite?',
|
||||
false
|
||||
);
|
||||
|
||||
// Verify the file was written after confirmation
|
||||
expect(mockWriteJSON).toHaveBeenCalledWith('tasks/tasks.json', sampleClaudeResponse);
|
||||
});
|
||||
|
||||
test('should not overwrite tasks.json when user declines confirmation', async () => {
|
||||
// Setup mocks to simulate tasks.json already exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return true; // Output file exists
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Mock user declining the confirmation
|
||||
mockPromptYesNo.mockResolvedValueOnce(false);
|
||||
|
||||
// Mock console.log to capture output
|
||||
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
// Call the function
|
||||
const result = await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
|
||||
|
||||
// Verify prompt was called
|
||||
expect(mockPromptYesNo).toHaveBeenCalledWith(
|
||||
'Warning: tasks/tasks.json already exists. Overwrite?',
|
||||
false
|
||||
);
|
||||
|
||||
// Verify the file was NOT written
|
||||
expect(mockWriteJSON).not.toHaveBeenCalled();
|
||||
|
||||
// Verify appropriate message was logged
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
'Operation cancelled. tasks/tasks.json was not modified.'
|
||||
);
|
||||
|
||||
// Verify result is null when operation is cancelled
|
||||
expect(result).toBeNull();
|
||||
|
||||
// Restore console.log
|
||||
mockConsoleLog.mockRestore();
|
||||
});
|
||||
|
||||
test('should not prompt for confirmation when tasks.json does not exist', async () => {
|
||||
// Setup mocks to simulate tasks.json does not exist
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Call the function
|
||||
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
|
||||
|
||||
// Verify prompt was NOT called
|
||||
expect(mockPromptYesNo).not.toHaveBeenCalled();
|
||||
|
||||
// Verify the file was written without confirmation
|
||||
expect(mockWriteJSON).toHaveBeenCalledWith('tasks/tasks.json', sampleClaudeResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('updateTasks function', () => {
|
||||
|
||||
Reference in New Issue
Block a user