Compare commits

..

9 Commits

Author SHA1 Message Date
Ralph Khreish
7439be3803 fix: github actions 2025-04-02 01:52:13 +02:00
github-actions[bot]
38e416ef33 Version Packages (#81)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-02 00:32:46 +02:00
Ralph Khreish
aa185b28b2 fix: npm i breaking (#80) 2025-04-02 00:30:36 +02:00
github-actions[bot]
76618187f6 Version Packages (#57) 2025-03-31 17:13:02 +02:00
Ralph Khreish
757fd478d2 Add License (#45) 2025-03-31 17:09:31 +02:00
Eyal Toledano
6e6407f683 Merge pull request #69 from eyaltoledano/add-test-for-confirmation-prompt
Add test for confirmation prompt
2025-03-30 23:10:21 -04:00
Eyal Toledano
80f933cd82 test: Add tests for parse-prd overwrite confirmation and fix existing test
Adds unit tests to tests/unit/task-manager.test.js for the parse-prd command confirmation prompt when overwriting an existing tasks.json file. Also fixes the existing directory creation test. Refs #67, Fixes #65
2025-03-30 23:09:05 -04:00
Eyal Toledano
2c3986c097 Merge pull request #67 from joedanz/confirm-tasks.json-overwrite
Added confirmation for task overwrite if tasks.json exists.
Fully tested

POPS @JOEDANZ' DEV CHERRY!
2025-03-30 23:00:53 -04:00
Joe Danziger
7086a77625 Added confirmation for task overwrite if tasks.json exists.
Slight refactor moving numTasks and outputPath to top with the other variables.  Eliminates duplication, and keeps us from having to check path twice.
Resolves #65
2025-03-30 18:30:00 -04:00
15 changed files with 189 additions and 48 deletions

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Added changeset config #39

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
add github actions to automate github and npm releases

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Implement MCP server for all commands using tools.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix github actions creating npm releases on next branch push

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Fix addTask tool `projectRoot not defined`

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Add license to repo

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
fix mcp server not connecting to cursor

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Fix workflows

View File

@@ -3,7 +3,6 @@ on:
push:
branches:
- main
- next
jobs:
release:
runs-on: ubuntu-latest

27
CHANGELOG.md Normal file
View 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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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...`));

View File

@@ -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
};

View File

@@ -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', () => {