Batch fixes before release (#1011)
* fix: improve projectRoot * fix: improve task-master lang command * feat: add documentation to the readme so more people can access it * fix: expand command subtask dependency validation * fix: update command more reliable with perplexity and other models * chore: fix CI * chore: implement requested changes * chore: fix CI
This commit is contained in:
5
.changeset/beige-windows-clean.md
Normal file
5
.changeset/beige-windows-clean.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Created a comprehensive documentation site for Task Master AI. Visit https://docs.task-master.dev to explore guides, API references, and examples.
|
||||||
7
.changeset/blue-rocks-clean.md
Normal file
7
.changeset/blue-rocks-clean.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Make `task-master update` more reliable with AI responses
|
||||||
|
|
||||||
|
The `update` command now handles AI responses more robustly. If the AI forgets to include certain task fields, the command will automatically fill in the missing data from your original tasks instead of failing. This means smoother bulk task updates without losing important information like IDs, dependencies, or completed subtasks.
|
||||||
7
.changeset/blue-rocks-dirty.md
Normal file
7
.changeset/blue-rocks-dirty.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix subtask dependency validation when expanding tasks
|
||||||
|
|
||||||
|
When using `task-master expand` to break down tasks into subtasks, dependencies between subtasks are now properly validated. Previously, subtasks with dependencies would fail validation. Now subtasks can correctly depend on their siblings within the same parent task.
|
||||||
5
.changeset/early-parts-throw.md
Normal file
5
.changeset/early-parts-throw.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix `task-master lang --setup` breaking when no language is defined, now defaults to English
|
||||||
7
.changeset/two-pots-move.md
Normal file
7
.changeset/two-pots-move.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve project root detection
|
||||||
|
|
||||||
|
- No longer creates an infinite loop when unable to detect your code workspace
|
||||||
@@ -14,7 +14,13 @@ A task management system for AI-driven development with Claude, designed to work
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For more detailed information, check out the documentation in the `docs` directory:
|
📚 **[View Full Documentation](https://docs.task-master.dev)**
|
||||||
|
|
||||||
|
For detailed guides, API references, and comprehensive examples, visit our documentation site.
|
||||||
|
|
||||||
|
### Quick Reference
|
||||||
|
|
||||||
|
The following documentation is also available in the `docs` directory:
|
||||||
|
|
||||||
- [Configuration Guide](docs/configuration.md) - Set up environment variables and customize Task Master
|
- [Configuration Guide](docs/configuration.md) - Set up environment variables and customize Task Master
|
||||||
- [Tutorial](docs/tutorial.md) - Step-by-step guide to getting started with Task Master
|
- [Tutorial](docs/tutorial.md) - Step-by-step guide to getting started with Task Master
|
||||||
|
|||||||
@@ -3727,10 +3727,7 @@ Examples:
|
|||||||
const taskMaster = initTaskMaster({});
|
const taskMaster = initTaskMaster({});
|
||||||
const projectRoot = taskMaster.getProjectRoot(); // Find project root for context
|
const projectRoot = taskMaster.getProjectRoot(); // Find project root for context
|
||||||
const { response, setup } = options;
|
const { response, setup } = options;
|
||||||
console.log(
|
let responseLanguage = response !== undefined ? response : 'English';
|
||||||
chalk.blue('Response language set to:', JSON.stringify(options))
|
|
||||||
);
|
|
||||||
let responseLanguage = response || 'English';
|
|
||||||
if (setup) {
|
if (setup) {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.blue('Starting interactive response language setup...')
|
chalk.blue('Starting interactive response language setup...')
|
||||||
@@ -3772,6 +3769,7 @@ Examples:
|
|||||||
`❌ Error setting response language: ${result.error.message}`
|
`❌ Error setting response language: ${result.error.message}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ const subtaskSchema = z
|
|||||||
.min(10)
|
.min(10)
|
||||||
.describe('Detailed description of the subtask'),
|
.describe('Detailed description of the subtask'),
|
||||||
dependencies: z
|
dependencies: z
|
||||||
.array(z.number().int())
|
.array(z.string())
|
||||||
.describe('IDs of prerequisite subtasks within this expansion'),
|
.describe(
|
||||||
|
'Array of subtask dependencies within the same parent task. Use format ["parentTaskId.1", "parentTaskId.2"]. Subtasks can only depend on siblings, not external tasks.'
|
||||||
|
),
|
||||||
details: z.string().min(20).describe('Implementation details and guidance'),
|
details: z.string().min(20).describe('Implementation details and guidance'),
|
||||||
status: z
|
status: z
|
||||||
.string()
|
.string()
|
||||||
@@ -235,12 +237,10 @@ function parseSubtasksFromText(
|
|||||||
...rawSubtask,
|
...rawSubtask,
|
||||||
id: currentId,
|
id: currentId,
|
||||||
dependencies: Array.isArray(rawSubtask.dependencies)
|
dependencies: Array.isArray(rawSubtask.dependencies)
|
||||||
? rawSubtask.dependencies
|
? rawSubtask.dependencies.filter(
|
||||||
.map((dep) => (typeof dep === 'string' ? parseInt(dep, 10) : dep))
|
(dep) =>
|
||||||
.filter(
|
typeof dep === 'string' && dep.startsWith(`${parentTaskId}.`)
|
||||||
(depId) =>
|
)
|
||||||
!Number.isNaN(depId) && depId >= startId && depId < currentId
|
|
||||||
)
|
|
||||||
: [],
|
: [],
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import { findConfigPath } from '../../../src/utils/path-utils.js';
|
|||||||
import { log } from '../utils.js';
|
import { log } from '../utils.js';
|
||||||
import { CUSTOM_PROVIDERS } from '../../../src/constants/providers.js';
|
import { CUSTOM_PROVIDERS } from '../../../src/constants/providers.js';
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const CONFIG_MISSING_ERROR =
|
||||||
|
'The configuration file is missing. Run "task-master init" to create it.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the list of models from OpenRouter API.
|
* Fetches the list of models from OpenRouter API.
|
||||||
* @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails.
|
* @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails.
|
||||||
@@ -168,9 +172,7 @@ async function getModelConfiguration(options = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!configExists) {
|
if (!configExists) {
|
||||||
throw new Error(
|
throw new Error(CONFIG_MISSING_ERROR);
|
||||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -298,9 +300,7 @@ async function getAvailableModelsList(options = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!configExists) {
|
if (!configExists) {
|
||||||
throw new Error(
|
throw new Error(CONFIG_MISSING_ERROR);
|
||||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -391,9 +391,7 @@ async function setModel(role, modelId, options = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!configExists) {
|
if (!configExists) {
|
||||||
throw new Error(
|
throw new Error(CONFIG_MISSING_ERROR);
|
||||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate role
|
// Validate role
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
import { generateObjectService } from '../ai-services-unified.js';
|
import { generateObjectService } from '../ai-services-unified.js';
|
||||||
import { getDebugFlag } from '../config-manager.js';
|
import { getDebugFlag } from '../config-manager.js';
|
||||||
import { getPromptManager } from '../prompt-manager.js';
|
import { getPromptManager } from '../prompt-manager.js';
|
||||||
import generateTaskFiles from './generate-task-files.js';
|
|
||||||
import { displayAiUsageSummary } from '../ui.js';
|
import { displayAiUsageSummary } from '../ui.js';
|
||||||
|
|
||||||
// Define the Zod schema for a SINGLE task object
|
// Define the Zod schema for a SINGLE task object
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ function setResponseLanguage(lang, options = {}) {
|
|||||||
error: {
|
error: {
|
||||||
code: 'CONFIG_MISSING',
|
code: 'CONFIG_MISSING',
|
||||||
message:
|
message:
|
||||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
'The configuration file is missing. Run "task-master init" to create it.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,39 @@ const updatedTaskSchema = z
|
|||||||
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
|
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
|
||||||
})
|
})
|
||||||
.strip(); // Allow potential extra fields during parsing if needed, then validate structure
|
.strip(); // Allow potential extra fields during parsing if needed, then validate structure
|
||||||
|
|
||||||
|
// Preprocessing schema that adds defaults before validation
|
||||||
|
const preprocessTaskSchema = z.preprocess((task) => {
|
||||||
|
// Ensure task is an object
|
||||||
|
if (typeof task !== 'object' || task === null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return task with defaults for missing fields
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
// Add defaults for required fields if missing
|
||||||
|
id: task.id ?? 0,
|
||||||
|
title: task.title ?? 'Untitled Task',
|
||||||
|
description: task.description ?? '',
|
||||||
|
status: task.status ?? 'pending',
|
||||||
|
dependencies: Array.isArray(task.dependencies) ? task.dependencies : [],
|
||||||
|
// Optional fields - preserve undefined/null distinction
|
||||||
|
priority: task.hasOwnProperty('priority') ? task.priority : null,
|
||||||
|
details: task.hasOwnProperty('details') ? task.details : null,
|
||||||
|
testStrategy: task.hasOwnProperty('testStrategy')
|
||||||
|
? task.testStrategy
|
||||||
|
: null,
|
||||||
|
subtasks: Array.isArray(task.subtasks)
|
||||||
|
? task.subtasks
|
||||||
|
: task.subtasks === null
|
||||||
|
? null
|
||||||
|
: []
|
||||||
|
};
|
||||||
|
}, updatedTaskSchema);
|
||||||
|
|
||||||
const updatedTaskArraySchema = z.array(updatedTaskSchema);
|
const updatedTaskArraySchema = z.array(updatedTaskSchema);
|
||||||
|
const preprocessedTaskArraySchema = z.array(preprocessTaskSchema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an array of task objects from AI's text response.
|
* Parses an array of task objects from AI's text response.
|
||||||
@@ -195,32 +227,50 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preprocess tasks to ensure required fields have proper defaults
|
// Log missing fields for debugging before preprocessing
|
||||||
const preprocessedTasks = parsedTasks.map((task) => ({
|
let hasWarnings = false;
|
||||||
...task,
|
parsedTasks.forEach((task, index) => {
|
||||||
// Ensure subtasks is always an array (not null or undefined)
|
const missingFields = [];
|
||||||
subtasks: Array.isArray(task.subtasks) ? task.subtasks : [],
|
if (!task.hasOwnProperty('id')) missingFields.push('id');
|
||||||
// Ensure status has a default value if missing
|
if (!task.hasOwnProperty('status')) missingFields.push('status');
|
||||||
status: task.status || 'pending',
|
if (!task.hasOwnProperty('dependencies'))
|
||||||
// Ensure dependencies is always an array
|
missingFields.push('dependencies');
|
||||||
dependencies: Array.isArray(task.dependencies) ? task.dependencies : []
|
|
||||||
}));
|
|
||||||
|
|
||||||
const validationResult = updatedTaskArraySchema.safeParse(preprocessedTasks);
|
if (missingFields.length > 0) {
|
||||||
if (!validationResult.success) {
|
hasWarnings = true;
|
||||||
report('error', 'Parsed task array failed Zod validation.');
|
report(
|
||||||
validationResult.error.errors.forEach((err) => {
|
'warn',
|
||||||
report('error', ` - Path '${err.path.join('.')}': ${err.message}`);
|
`Task ${index} is missing fields: ${missingFields.join(', ')} - will use defaults`
|
||||||
});
|
);
|
||||||
throw new Error(
|
}
|
||||||
`AI response failed task structure validation: ${validationResult.error.message}`
|
});
|
||||||
|
|
||||||
|
if (hasWarnings) {
|
||||||
|
report(
|
||||||
|
'warn',
|
||||||
|
'Some tasks were missing required fields. Applying defaults...'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
report('info', 'Successfully validated task structure.');
|
// Use the preprocessing schema to add defaults and validate
|
||||||
return validationResult.data.slice(
|
const preprocessResult = preprocessedTaskArraySchema.safeParse(parsedTasks);
|
||||||
|
|
||||||
|
if (!preprocessResult.success) {
|
||||||
|
// This should rarely happen now since preprocessing adds defaults
|
||||||
|
report('error', 'Failed to validate task array even after preprocessing.');
|
||||||
|
preprocessResult.error.errors.forEach((err) => {
|
||||||
|
report('error', ` - Path '${err.path.join('.')}': ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`AI response failed validation: ${preprocessResult.error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
report('info', 'Successfully validated and transformed task structure.');
|
||||||
|
return preprocessResult.data.slice(
|
||||||
0,
|
0,
|
||||||
expectedCount || validationResult.data.length
|
expectedCount || preprocessResult.data.length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,17 +56,17 @@
|
|||||||
"prompts": {
|
"prompts": {
|
||||||
"complexity-report": {
|
"complexity-report": {
|
||||||
"condition": "expansionPrompt",
|
"condition": "expansionPrompt",
|
||||||
"system": "You are an AI assistant helping with task breakdown. Generate {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks based on the provided prompt and context.\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array of the generated subtask objects.\nEach subtask object in the array must have keys: \"id\", \"title\", \"description\", \"dependencies\", \"details\", \"status\".\nEnsure the 'id' starts from {{nextSubtaskId}} and is sequential.\nEnsure 'dependencies' only reference valid prior subtask IDs generated in this response (starting from {{nextSubtaskId}}).\nEnsure 'status' is 'pending'.\nDo not include any other text or explanation.",
|
"system": "You are an AI assistant helping with task breakdown. Generate {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks based on the provided prompt and context.\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array of the generated subtask objects.\nEach subtask object in the array must have keys: \"id\", \"title\", \"description\", \"dependencies\", \"details\", \"status\".\nEnsure the 'id' starts from {{nextSubtaskId}} and is sequential.\nFor 'dependencies', use the full subtask ID format: \"{{task.id}}.1\", \"{{task.id}}.2\", etc. Only reference subtasks within this same task.\nEnsure 'status' is 'pending'.\nDo not include any other text or explanation.",
|
||||||
"user": "{{expansionPrompt}}{{#if additionalContext}}\n\n{{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\n\n{{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}"
|
"user": "{{expansionPrompt}}{{#if additionalContext}}\n\n{{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\n\n{{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}"
|
||||||
},
|
},
|
||||||
"research": {
|
"research": {
|
||||||
"condition": "useResearch === true && !expansionPrompt",
|
"condition": "useResearch === true && !expansionPrompt",
|
||||||
"system": "You are an AI assistant that responds ONLY with valid JSON objects as requested. The object should contain a 'subtasks' array.",
|
"system": "You are an AI assistant that responds ONLY with valid JSON objects as requested. The object should contain a 'subtasks' array.",
|
||||||
"user": "Analyze the following task and break it down into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks using your research capabilities. Assign sequential IDs starting from {{nextSubtaskId}}.\n\nParent Task:\nID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nConsider this context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nCRITICAL: Respond ONLY with a valid JSON object containing a single key \"subtasks\". The value must be an array of the generated subtasks, strictly matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": <number>, // Sequential ID starting from {{nextSubtaskId}}\n \"title\": \"<string>\",\n \"description\": \"<string>\",\n \"dependencies\": [<number>], // e.g., [{{nextSubtaskId}} + 1]. If no dependencies, use an empty array [].\n \"details\": \"<string>\",\n \"testStrategy\": \"<string>\" // Optional\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}appropriate number of{{/if}} subtasks)\n ]\n}\n\nImportant: For the 'dependencies' field, if a subtask has no dependencies, you MUST use an empty array, for example: \"dependencies\": []. Do not use null or omit the field.\n\nDo not include ANY explanatory text, markdown, or code block markers. Just the JSON object."
|
"user": "Analyze the following task and break it down into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks using your research capabilities. Assign sequential IDs starting from {{nextSubtaskId}}.\n\nParent Task:\nID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nConsider this context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nCRITICAL: Respond ONLY with a valid JSON object containing a single key \"subtasks\". The value must be an array of the generated subtasks, strictly matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": <number>, // Sequential ID starting from {{nextSubtaskId}}\n \"title\": \"<string>\",\n \"description\": \"<string>\",\n \"dependencies\": [\"<string>\"], // Use full subtask IDs like [\"{{task.id}}.1\", \"{{task.id}}.2\"]. If no dependencies, use an empty array [].\n \"details\": \"<string>\",\n \"testStrategy\": \"<string>\" // Optional\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}appropriate number of{{/if}} subtasks)\n ]\n}\n\nImportant: For the 'dependencies' field, if a subtask has no dependencies, you MUST use an empty array, for example: \"dependencies\": []. Do not use null or omit the field.\n\nDo not include ANY explanatory text, markdown, or code block markers. Just the JSON object."
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
"system": "You are an AI assistant helping with task breakdown for software development.\nYou need to break down a high-level task into {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks that can be implemented one by one.\n\nSubtasks should:\n1. Be specific and actionable implementation steps\n2. Follow a logical sequence\n3. Each handle a distinct part of the parent task\n4. Include clear guidance on implementation approach\n5. Have appropriate dependency chains between subtasks (using the new sequential IDs)\n6. Collectively cover all aspects of the parent task\n\nFor each subtask, provide:\n- id: Sequential integer starting from the provided nextSubtaskId\n- title: Clear, specific title\n- description: Detailed description\n- dependencies: Array of prerequisite subtask IDs (use the new sequential IDs)\n- details: Implementation details, the output should be in string\n- testStrategy: Optional testing approach\n\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array matching the structure described. Do not include any explanatory text, markdown formatting, or code block markers.",
|
"system": "You are an AI assistant helping with task breakdown for software development.\nYou need to break down a high-level task into {{#if (gt subtaskCount 0)}}{{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks that can be implemented one by one.\n\nSubtasks should:\n1. Be specific and actionable implementation steps\n2. Follow a logical sequence\n3. Each handle a distinct part of the parent task\n4. Include clear guidance on implementation approach\n5. Have appropriate dependency chains between subtasks (using full subtask IDs)\n6. Collectively cover all aspects of the parent task\n\nFor each subtask, provide:\n- id: Sequential integer starting from the provided nextSubtaskId\n- title: Clear, specific title\n- description: Detailed description\n- dependencies: Array of prerequisite subtask IDs using full format like [\"{{task.id}}.1\", \"{{task.id}}.2\"]\n- details: Implementation details, the output should be in string\n- testStrategy: Optional testing approach\n\nRespond ONLY with a valid JSON object containing a single key \"subtasks\" whose value is an array matching the structure described. Do not include any explanatory text, markdown formatting, or code block markers.",
|
||||||
"user": "Break down this task into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks:\n\nTask ID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nAdditional context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nReturn ONLY the JSON object containing the \"subtasks\" array, matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": {{nextSubtaskId}}, // First subtask ID\n \"title\": \"Specific subtask title\",\n \"description\": \"Detailed description\",\n \"dependencies\": [], // e.g., [{{nextSubtaskId}} + 1] if it depends on the next\n \"details\": \"Implementation guidance\",\n \"testStrategy\": \"Optional testing approach\"\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}a total of {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks with sequential IDs)\n ]\n}"
|
"user": "Break down this task into {{#if (gt subtaskCount 0)}}exactly {{subtaskCount}}{{else}}an appropriate number of{{/if}} specific subtasks:\n\nTask ID: {{task.id}}\nTitle: {{task.title}}\nDescription: {{task.description}}\nCurrent details: {{#if task.details}}{{task.details}}{{else}}None{{/if}}{{#if additionalContext}}\nAdditional context: {{additionalContext}}{{/if}}{{#if complexityReasoningContext}}\nComplexity Analysis Reasoning: {{complexityReasoningContext}}{{/if}}{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}{{/if}}\n\nReturn ONLY the JSON object containing the \"subtasks\" array, matching this structure:\n\n{\n \"subtasks\": [\n {\n \"id\": {{nextSubtaskId}}, // First subtask ID\n \"title\": \"Specific subtask title\",\n \"description\": \"Detailed description\",\n \"dependencies\": [], // e.g., [\"{{task.id}}.1\", \"{{task.id}}.2\"] for dependencies. Use empty array [] if no dependencies\n \"details\": \"Implementation guidance\",\n \"testStrategy\": \"Optional testing approach\"\n },\n // ... (repeat for {{#if (gt subtaskCount 0)}}a total of {{subtaskCount}}{{else}}an appropriate number of{{/if}} subtasks with sequential IDs)\n ]\n}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
"default": {
|
"default": {
|
||||||
"system": "You are an AI assistant helping to update software development tasks based on new context.\nYou will be given a set of tasks and a prompt describing changes or new implementation details.\nYour job is to update the tasks to reflect these changes, while preserving their basic structure.\n\nGuidelines:\n1. Maintain the same IDs, statuses, and dependencies unless specifically mentioned in the prompt\n2. Update titles, descriptions, details, and test strategies to reflect the new information\n3. Do not change anything unnecessarily - just adapt what needs to change based on the prompt\n4. You should return ALL the tasks in order, not just the modified ones\n5. Return a complete valid JSON object with the updated tasks array\n6. VERY IMPORTANT: Preserve all subtasks marked as \"done\" or \"completed\" - do not modify their content\n7. For tasks with completed subtasks, build upon what has already been done rather than rewriting everything\n8. If an existing completed subtask needs to be changed/undone based on the new context, DO NOT modify it directly\n9. Instead, add a new subtask that clearly indicates what needs to be changed or replaced\n10. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted\n\nThe changes described in the prompt should be applied to ALL tasks in the list.",
|
"system": "You are an AI assistant helping to update software development tasks based on new context.\nYou will be given a set of tasks and a prompt describing changes or new implementation details.\nYour job is to update the tasks to reflect these changes, while preserving their basic structure.\n\nCRITICAL RULES:\n1. Return ONLY a JSON array - no explanations, no markdown, no additional text before or after\n2. Each task MUST have ALL fields from the original (do not omit any fields)\n3. Maintain the same IDs, statuses, and dependencies unless specifically mentioned in the prompt\n4. Update titles, descriptions, details, and test strategies to reflect the new information\n5. Do not change anything unnecessarily - just adapt what needs to change based on the prompt\n6. You should return ALL the tasks in order, not just the modified ones\n7. Return a complete valid JSON array with all tasks\n8. VERY IMPORTANT: Preserve all subtasks marked as \"done\" or \"completed\" - do not modify their content\n9. For tasks with completed subtasks, build upon what has already been done rather than rewriting everything\n10. If an existing completed subtask needs to be changed/undone based on the new context, DO NOT modify it directly\n11. Instead, add a new subtask that clearly indicates what needs to be changed or replaced\n12. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted\n\nThe changes described in the prompt should be applied to ALL tasks in the list.",
|
||||||
"user": "Here are the tasks to update:\n{{{json tasks}}}\n\nPlease update these tasks based on the following new context:\n{{updatePrompt}}\n\nIMPORTANT: In the tasks JSON above, any subtasks with \"status\": \"done\" or \"status\": \"completed\" should be preserved exactly as is. Build your changes around these completed items.{{#if projectContext}}\n\n# Project Context\n\n{{projectContext}}{{/if}}\n\nReturn only the updated tasks as a valid JSON array."
|
"user": "Here are the tasks to update:\n{{{json tasks}}}\n\nPlease update these tasks based on the following new context:\n{{updatePrompt}}\n\nIMPORTANT: In the tasks JSON above, any subtasks with \"status\": \"done\" or \"status\": \"completed\" should be preserved exactly as is. Build your changes around these completed items.{{#if projectContext}}\n\n# Project Context\n\n{{projectContext}}{{/if}}\n\nRequired JSON structure for EACH task (ALL fields MUST be present):\n{\n \"id\": <number>,\n \"title\": <string>,\n \"description\": <string>,\n \"status\": <string>,\n \"dependencies\": <array>,\n \"priority\": <string or null>,\n \"details\": <string or null>,\n \"testStrategy\": <string or null>,\n \"subtasks\": <array or null>\n}\n\nReturn a valid JSON array containing ALL the tasks with ALL their fields:\n- id (number) - preserve existing value\n- title (string)\n- description (string)\n- status (string) - preserve existing value unless explicitly changing\n- dependencies (array) - preserve existing value unless explicitly changing\n- priority (string or null)\n- details (string or null)\n- testStrategy (string or null)\n- subtasks (array or null)\n\nReturn ONLY the JSON array now:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
LEGACY_CONFIG_FILE,
|
LEGACY_CONFIG_FILE,
|
||||||
COMPLEXITY_REPORT_FILE
|
COMPLEXITY_REPORT_FILE
|
||||||
} from './constants/paths.js';
|
} from './constants/paths.js';
|
||||||
|
import { findProjectRoot } from './utils/path-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TaskMaster class manages all the paths for the application.
|
* TaskMaster class manages all the paths for the application.
|
||||||
@@ -159,22 +160,6 @@ export class TaskMaster {
|
|||||||
* @returns {TaskMaster} An initialized TaskMaster instance.
|
* @returns {TaskMaster} An initialized TaskMaster instance.
|
||||||
*/
|
*/
|
||||||
export function initTaskMaster(overrides = {}) {
|
export function initTaskMaster(overrides = {}) {
|
||||||
const findProjectRoot = (startDir = process.cwd()) => {
|
|
||||||
const projectMarkers = [TASKMASTER_DIR, LEGACY_CONFIG_FILE];
|
|
||||||
let currentDir = path.resolve(startDir);
|
|
||||||
const rootDir = path.parse(currentDir).root;
|
|
||||||
while (currentDir !== rootDir) {
|
|
||||||
for (const marker of projectMarkers) {
|
|
||||||
const markerPath = path.join(currentDir, marker);
|
|
||||||
if (fs.existsSync(markerPath)) {
|
|
||||||
return currentDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentDir = path.dirname(currentDir);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolvePath = (
|
const resolvePath = (
|
||||||
pathType,
|
pathType,
|
||||||
override,
|
override,
|
||||||
@@ -264,13 +249,8 @@ export function initTaskMaster(overrides = {}) {
|
|||||||
|
|
||||||
paths.projectRoot = resolvedOverride;
|
paths.projectRoot = resolvedOverride;
|
||||||
} else {
|
} else {
|
||||||
const foundRoot = findProjectRoot();
|
// findProjectRoot now always returns a value (fallback to cwd)
|
||||||
if (!foundRoot) {
|
paths.projectRoot = findProjectRoot();
|
||||||
throw new Error(
|
|
||||||
'Unable to find project root. No project markers found. Run "init" command first.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
paths.projectRoot = foundRoot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskMaster Directory
|
// TaskMaster Directory
|
||||||
|
|||||||
@@ -66,8 +66,10 @@ export function findProjectRoot(startDir = process.cwd()) {
|
|||||||
|
|
||||||
let currentDir = path.resolve(startDir);
|
let currentDir = path.resolve(startDir);
|
||||||
const rootDir = path.parse(currentDir).root;
|
const rootDir = path.parse(currentDir).root;
|
||||||
|
const maxDepth = 50; // Reasonable limit to prevent infinite loops
|
||||||
|
let depth = 0;
|
||||||
|
|
||||||
while (currentDir !== rootDir) {
|
while (currentDir !== rootDir && depth < maxDepth) {
|
||||||
// Check if current directory contains any project markers
|
// Check if current directory contains any project markers
|
||||||
for (const marker of projectMarkers) {
|
for (const marker of projectMarkers) {
|
||||||
const markerPath = path.join(currentDir, marker);
|
const markerPath = path.join(currentDir, marker);
|
||||||
@@ -76,9 +78,11 @@ export function findProjectRoot(startDir = process.cwd()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentDir = path.dirname(currentDir);
|
currentDir = path.dirname(currentDir);
|
||||||
|
depth++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// Fallback to current working directory if no project root found
|
||||||
|
return process.cwd();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -109,16 +109,15 @@ describe('initTaskMaster', () => {
|
|||||||
expect(taskMaster.getProjectRoot()).toBe(tempDir);
|
expect(taskMaster.getProjectRoot()).toBe(tempDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw error when no project markers found', () => {
|
test('should return cwd when no project markers found cuz we changed the behavior of this function', () => {
|
||||||
// Arrange - Empty temp directory, no project markers
|
// Arrange - Empty temp directory, no project markers
|
||||||
process.chdir(tempDir);
|
process.chdir(tempDir);
|
||||||
|
|
||||||
// Act & Assert
|
// Act
|
||||||
expect(() => {
|
const taskMaster = initTaskMaster({});
|
||||||
initTaskMaster({});
|
|
||||||
}).toThrow(
|
// Assert
|
||||||
'Unable to find project root. No project markers found. Run "init" command first.'
|
expect(taskMaster.getProjectRoot()).toBe(tempDir);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user