Compare commits

...

3 Commits

Author SHA1 Message Date
Joe Danziger
cf2c06697a Call rules interactive setup during init (#833) 2025-06-20 22:05:25 +02:00
Joe Danziger
727f1ec4eb store tasks in git by default (#835) 2025-06-20 18:49:38 +02:00
Ralph Khreish
648353794e fix: update task by id (#834) 2025-06-20 18:11:17 +02:00
9 changed files with 177 additions and 31 deletions

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Call rules interactive setup during init

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix issues with task creation/update where subtasks are being created like id: <parent_task>.<subtask> instead if just id: <subtask>

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Store tasks in Git by default

View File

@@ -33,7 +33,7 @@ export function registerInitializeProjectTool(server) {
storeTasksInGit: z storeTasksInGit: z
.boolean() .boolean()
.optional() .optional()
.default(false) .default(true)
.describe('Store tasks in Git (tasks.json and tasks/ directory).'), .describe('Store tasks in Git (tasks.json and tasks/ directory).'),
yes: z yes: z
.boolean() .boolean()

View File

@@ -352,10 +352,30 @@ async function initializeProject(options = {}) {
// console.log('Skip prompts determined:', skipPrompts); // console.log('Skip prompts determined:', skipPrompts);
// } // }
const selectedRuleProfiles = let selectedRuleProfiles;
options.rules && Array.isArray(options.rules) && options.rules.length > 0 if (options.rulesExplicitlyProvided) {
? options.rules // If --rules flag was used, always respect it.
: RULE_PROFILES; // Default to all profiles log(
'info',
`Using rule profiles provided via command line: ${options.rules.join(', ')}`
);
selectedRuleProfiles = options.rules;
} else if (skipPrompts) {
// If non-interactive (e.g., --yes) and no rules specified, default to ALL.
log(
'info',
`No rules specified in non-interactive mode, defaulting to all profiles.`
);
selectedRuleProfiles = RULE_PROFILES;
} else {
// If interactive and no rules specified, default to NONE.
// The 'rules --setup' wizard will handle selection.
log(
'info',
'No rules specified; interactive setup will be launched to select profiles.'
);
selectedRuleProfiles = [];
}
if (skipPrompts) { if (skipPrompts) {
if (!isSilentMode()) { if (!isSilentMode()) {
@@ -373,7 +393,7 @@ async function initializeProject(options = {}) {
options.addAliases !== undefined ? options.addAliases : true; // Default to true if not specified options.addAliases !== undefined ? options.addAliases : true; // Default to true if not specified
const initGit = options.initGit !== undefined ? options.initGit : true; // Default to true if not specified const initGit = options.initGit !== undefined ? options.initGit : true; // Default to true if not specified
const storeTasksInGit = const storeTasksInGit =
options.storeTasksInGit !== undefined ? options.storeTasksInGit : false; // Default to false if not specified options.storeTasksInGit !== undefined ? options.storeTasksInGit : true; // Default to true if not specified
if (dryRun) { if (dryRun) {
log('info', 'DRY RUN MODE: No files will be modified'); log('info', 'DRY RUN MODE: No files will be modified');
@@ -443,17 +463,17 @@ async function initializeProject(options = {}) {
} }
// Prompt for Git tasks storage (skip if --git-tasks or --no-git-tasks flag was provided) // Prompt for Git tasks storage (skip if --git-tasks or --no-git-tasks flag was provided)
let storeGitPrompted = false; // Default to false let storeGitPrompted = true; // Default to true
if (options.storeTasksInGit !== undefined) { if (options.storeTasksInGit !== undefined) {
storeGitPrompted = options.storeTasksInGit; // Use flag value if provided storeGitPrompted = options.storeTasksInGit; // Use flag value if provided
} else { } else {
const gitTasksInput = await promptQuestion( const gitTasksInput = await promptQuestion(
rl, rl,
chalk.cyan( chalk.cyan(
'Store tasks in Git (tasks.json and tasks/ directory)? (y/N): ' 'Store tasks in Git (tasks.json and tasks/ directory)? (Y/n): '
) )
); );
storeGitPrompted = gitTasksInput.trim().toLowerCase() === 'y'; storeGitPrompted = gitTasksInput.trim().toLowerCase() !== 'n';
} }
// Confirm settings... // Confirm settings...
@@ -492,16 +512,6 @@ async function initializeProject(options = {}) {
'info', 'info',
`Using rule profiles provided via command line: ${selectedRuleProfiles.join(', ')}` `Using rule profiles provided via command line: ${selectedRuleProfiles.join(', ')}`
); );
} else {
try {
const targetDir = process.cwd();
execSync('npx task-master rules setup', {
stdio: 'inherit',
cwd: targetDir
});
} catch (error) {
log('error', 'Failed to run interactive rules setup:', error.message);
}
} }
const dryRun = options.dryRun || false; const dryRun = options.dryRun || false;
@@ -541,7 +551,9 @@ async function initializeProject(options = {}) {
); );
rl.close(); rl.close();
} catch (error) { } catch (error) {
if (rl) {
rl.close(); rl.close();
}
log('error', `Error during initialization process: ${error.message}`); log('error', `Error during initialization process: ${error.message}`);
process.exit(1); process.exit(1);
} }
@@ -564,7 +576,7 @@ function createProjectStructure(
storeTasksInGit, storeTasksInGit,
dryRun, dryRun,
options, options,
selectedRuleProfiles = RULE_PROFILES // Default to all rule profiles selectedRuleProfiles = RULE_PROFILES
) { ) {
const targetDir = process.cwd(); const targetDir = process.cwd();
log('info', `Initializing project in ${targetDir}`); log('info', `Initializing project in ${targetDir}`);
@@ -665,11 +677,14 @@ function createProjectStructure(
log('warn', 'Git not available, skipping repository initialization'); log('warn', 'Git not available, skipping repository initialization');
} }
// Generate profile rules from assets/rules // Only run the manual transformer if rules were provided via flags.
log('info', 'Generating profile rules from assets/rules...'); // The interactive `rules --setup` wizard handles its own installation.
if (options.rulesExplicitlyProvided || options.yes) {
log('info', 'Generating profile rules from command-line flags...');
for (const profileName of selectedRuleProfiles) { for (const profileName of selectedRuleProfiles) {
_processSingleProfile(profileName); _processSingleProfile(profileName);
} }
}
// Add shell aliases if requested // Add shell aliases if requested
if (addAliases) { if (addAliases) {
@@ -699,6 +714,49 @@ function createProjectStructure(
); );
} }
// === Add Rule Profiles Setup Step ===
if (
!isSilentMode() &&
!dryRun &&
!options?.yes &&
!options.rulesExplicitlyProvided
) {
console.log(
boxen(chalk.cyan('Configuring Rule Profiles...'), {
padding: 0.5,
margin: { top: 1, bottom: 0.5 },
borderStyle: 'round',
borderColor: 'blue'
})
);
log(
'info',
'Running interactive rules setup. Please select which rule profiles to include.'
);
try {
// Correct command confirmed by you.
execSync('npx task-master rules --setup', {
stdio: 'inherit',
cwd: targetDir
});
log('success', 'Rule profiles configured.');
} catch (error) {
log('error', 'Failed to configure rule profiles:', error.message);
log('warn', 'You may need to run "task-master rules --setup" manually.');
}
} else if (isSilentMode() || dryRun || options?.yes) {
// This branch can log why setup was skipped, similar to the model setup logic.
if (options.rulesExplicitlyProvided) {
log(
'info',
'Skipping interactive rules setup because --rules flag was used.'
);
} else {
log('info', 'Skipping interactive rules setup in non-interactive mode.');
}
}
// =====================================
// === Add Model Configuration Step === // === Add Model Configuration Step ===
if (!isSilentMode() && !dryRun && !options?.yes) { if (!isSilentMode() && !dryRun && !options?.yes) {
console.log( console.log(

View File

@@ -3828,7 +3828,26 @@ Examples:
if (options[RULES_SETUP_ACTION]) { if (options[RULES_SETUP_ACTION]) {
// Run interactive rules setup ONLY (no project init) // Run interactive rules setup ONLY (no project init)
const selectedRuleProfiles = await runInteractiveProfilesSetup(); const selectedRuleProfiles = await runInteractiveProfilesSetup();
for (const profile of selectedRuleProfiles) {
if (!selectedRuleProfiles || selectedRuleProfiles.length === 0) {
console.log(chalk.yellow('No profiles selected. Exiting.'));
return;
}
console.log(
chalk.blue(
`Installing ${selectedRuleProfiles.length} selected profile(s)...`
)
);
for (let i = 0; i < selectedRuleProfiles.length; i++) {
const profile = selectedRuleProfiles[i];
console.log(
chalk.blue(
`Processing profile ${i + 1}/${selectedRuleProfiles.length}: ${profile}...`
)
);
if (!isValidProfile(profile)) { if (!isValidProfile(profile)) {
console.warn( console.warn(
`Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.` `Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
@@ -3836,16 +3855,20 @@ Examples:
continue; continue;
} }
const profileConfig = getRulesProfile(profile); const profileConfig = getRulesProfile(profile);
const addResult = convertAllRulesToProfileRules( const addResult = convertAllRulesToProfileRules(
projectDir, projectDir,
profileConfig profileConfig
); );
if (typeof profileConfig.onAddRulesProfile === 'function') {
profileConfig.onAddRulesProfile(projectDir);
}
console.log(chalk.green(generateProfileSummary(profile, addResult))); console.log(chalk.green(generateProfileSummary(profile, addResult)));
} }
console.log(
chalk.green(
`\nCompleted installation of all ${selectedRuleProfiles.length} profile(s).`
)
);
return; return;
} }

View File

@@ -39,7 +39,23 @@ const updatedTaskSchema = z
priority: z.string().optional(), priority: z.string().optional(),
details: z.string().optional(), details: z.string().optional(),
testStrategy: z.string().optional(), testStrategy: z.string().optional(),
subtasks: z.array(z.any()).optional() subtasks: z
.array(
z.object({
id: z
.number()
.int()
.positive()
.describe('Sequential subtask ID starting from 1'),
title: z.string(),
description: z.string(),
status: z.string(),
dependencies: z.array(z.number().int()).optional(),
details: z.string().optional(),
testStrategy: z.string().optional()
})
)
.optional()
}) })
.strip(); // Allows parsing even if AI adds extra fields, but validation focuses on schema .strip(); // Allows parsing even if AI adds extra fields, but validation focuses on schema
@@ -441,6 +457,8 @@ Guidelines:
9. Instead, add a new subtask that clearly indicates what needs to be changed or replaced 9. Instead, add a new subtask that clearly indicates what needs to be changed or replaced
10. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted 10. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted
11. Ensure any new subtasks have unique IDs that don't conflict with existing ones 11. Ensure any new subtasks have unique IDs that don't conflict with existing ones
12. CRITICAL: For subtask IDs, use ONLY numeric values (1, 2, 3, etc.) NOT strings ("1", "2", "3")
13. CRITICAL: Subtask IDs should start from 1 and increment sequentially (1, 2, 3...) - do NOT use parent task ID as prefix
The changes described in the prompt should be thoughtfully applied to make the task more accurate and actionable.`; The changes described in the prompt should be thoughtfully applied to make the task more accurate and actionable.`;
@@ -573,6 +591,37 @@ The changes described in the prompt should be thoughtfully applied to make the t
); );
updatedTask.status = taskToUpdate.status; updatedTask.status = taskToUpdate.status;
} }
// Fix subtask IDs if they exist (ensure they are numeric and sequential)
if (updatedTask.subtasks && Array.isArray(updatedTask.subtasks)) {
let currentSubtaskId = 1;
updatedTask.subtasks = updatedTask.subtasks.map((subtask) => {
// Fix AI-generated subtask IDs that might be strings or use parent ID as prefix
const correctedSubtask = {
...subtask,
id: currentSubtaskId, // Override AI-generated ID with correct sequential ID
dependencies: Array.isArray(subtask.dependencies)
? subtask.dependencies
.map((dep) =>
typeof dep === 'string' ? parseInt(dep, 10) : dep
)
.filter(
(depId) =>
!Number.isNaN(depId) &&
depId >= 1 &&
depId < currentSubtaskId
)
: [],
status: subtask.status || 'pending'
};
currentSubtaskId++;
return correctedSubtask;
});
report(
'info',
`Fixed ${updatedTask.subtasks.length} subtask IDs to be sequential numeric IDs.`
);
}
// Preserve completed subtasks (Keep existing logic) // Preserve completed subtasks (Keep existing logic)
if (taskToUpdate.subtasks?.length > 0) { if (taskToUpdate.subtasks?.length > 0) {
if (!updatedTask.subtasks) { if (!updatedTask.subtasks) {

View File

@@ -255,7 +255,7 @@ function mergeWithExistingFile(
function manageGitignoreFile( function manageGitignoreFile(
targetPath, targetPath,
content, content,
storeTasksInGit = false, storeTasksInGit = true,
log = null log = null
) { ) {
// Validate inputs // Validate inputs

View File

@@ -206,6 +206,7 @@ export function convertAllRulesToProfileRules(projectDir, profile) {
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const assetsDir = path.join(__dirname, '..', '..', 'assets'); const assetsDir = path.join(__dirname, '..', '..', 'assets');
if (typeof profile.onPostConvertRulesProfile === 'function') { if (typeof profile.onPostConvertRulesProfile === 'function') {
profile.onPostConvertRulesProfile(projectDir, assetsDir); profile.onPostConvertRulesProfile(projectDir, assetsDir);
} }