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
13 changed files with 275 additions and 1793 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
.boolean()
.optional()
.default(false)
.default(true)
.describe('Store tasks in Git (tasks.json and tasks/ directory).'),
yes: z
.boolean()

1671
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{
"name": "task-master-ai",
"version": "0.17.1-test",
"version": "0.17.1",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",
"bin": {
"task-master": "dist/task-master.cjs",
"task-master-mcp": "dist/task-master-mcp.cjs",
"task-master-ai": "dist/task-master-mcp.cjs"
"task-master": "bin/task-master.js",
"task-master-mcp": "mcp-server/server.js",
"task-master-ai": "mcp-server/server.js"
},
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest",
@@ -22,13 +22,7 @@
"inspector": "npx @modelcontextprotocol/inspector node mcp-server/server.js",
"mcp-server": "node mcp-server/server.js",
"format-check": "biome format .",
"format": "biome format . --write",
"prebuild": "npm test",
"build": "vite build && npm run postbuild",
"postbuild": "node scripts/add-shebang.js",
"build:bundle": "npm run build",
"build:watch": "vite build --watch",
"test:build": "echo 'Testing bundled binaries:' && echo '🔧 CLI (task-master):' && node dist/task-master.cjs --version && echo '🔌 MCP (task-master-ai):' && node dist/task-master-mcp.cjs --help | head -5 && echo '✅ Both bundles work!'"
"format": "biome format . --write"
},
"keywords": [
"claude",
@@ -97,11 +91,14 @@
"url": "https://github.com/eyaltoledano/claude-task-master/issues"
},
"files": [
"dist/**",
"mcp-server/**",
"README-task-master.md",
"scripts/**",
"assets/**",
".cursor/**",
"assets/**"
"README-task-master.md",
"index.js",
"bin/**",
"mcp-server/**",
"src/**"
],
"overrides": {
"node-fetch": "^2.6.12",
@@ -111,19 +108,15 @@
"@biomejs/biome": "^1.9.4",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.28.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@types/jest": "^29.5.14",
"execa": "^8.0.1",
"ink": "^5.0.1",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"mock-fs": "^5.5.0",
"pkg": "^5.8.1",
"prettier": "^3.5.3",
"react": "^18.3.1",
"supertest": "^7.1.0",
"tsx": "^4.16.2",
"vite": "^6.3.5"
"tsx": "^4.16.2"
}
}
}

View File

@@ -1,82 +0,0 @@
#!/usr/bin/env node
import {
readFileSync,
writeFileSync,
chmodSync,
copyFileSync,
mkdirSync,
existsSync
} from 'fs';
import { join, dirname } from 'path';
const bundlePaths = [
join(process.cwd(), 'dist/task-master.cjs'), // CLI tool
join(process.cwd(), 'dist/task-master-mcp.cjs') // MCP server
];
try {
// Copy necessary asset files to dist
const assetsToCopy = [
{
src: 'scripts/modules/supported-models.json',
dest: 'dist/supported-models.json'
},
{ src: 'README-task-master.md', dest: 'dist/README-task-master.md' }
];
console.log('📁 Copying assets...');
for (const asset of assetsToCopy) {
const srcPath = join(process.cwd(), asset.src);
const destPath = join(process.cwd(), asset.dest);
if (existsSync(srcPath)) {
// Ensure destination directory exists
const destDir = dirname(destPath);
if (!existsSync(destDir)) {
mkdirSync(destDir, { recursive: true });
}
copyFileSync(srcPath, destPath);
console.log(` ✅ Copied ${asset.src}${asset.dest}`);
} else {
console.log(` ⚠️ Source not found: ${asset.src}`);
}
}
// Process each bundle file
for (const bundlePath of bundlePaths) {
const fileName = bundlePath.split('/').pop();
if (!existsSync(bundlePath)) {
console.log(`⚠️ Bundle not found: ${fileName}`);
continue;
}
// Read the existing bundle
const bundleContent = readFileSync(bundlePath, 'utf8');
// Add shebang if it doesn't already exist
if (!bundleContent.startsWith('#!/usr/bin/env node')) {
const contentWithShebang = '#!/usr/bin/env node\n' + bundleContent;
writeFileSync(bundlePath, contentWithShebang);
console.log(`✅ Added shebang to ${fileName}`);
} else {
console.log(`✅ Shebang already exists in ${fileName}`);
}
// Make it executable
chmodSync(bundlePath, 0o755);
console.log(`✅ Made ${fileName} executable`);
}
console.log('📦 Both bundles ready:');
console.log(' 🔧 CLI tool: dist/task-master.cjs');
console.log(' 🔌 MCP server: dist/task-master-mcp.cjs');
console.log('🧪 Test with:');
console.log(' node dist/task-master.cjs --version');
console.log(' node dist/task-master-mcp.cjs --help');
} catch (error) {
console.error('❌ Post-build failed:', error.message);
process.exit(1);
}

View File

@@ -352,10 +352,30 @@ async function initializeProject(options = {}) {
// console.log('Skip prompts determined:', skipPrompts);
// }
const selectedRuleProfiles =
options.rules && Array.isArray(options.rules) && options.rules.length > 0
? options.rules
: RULE_PROFILES; // Default to all profiles
let selectedRuleProfiles;
if (options.rulesExplicitlyProvided) {
// If --rules flag was used, always respect it.
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 (!isSilentMode()) {
@@ -373,7 +393,7 @@ async function initializeProject(options = {}) {
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 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) {
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)
let storeGitPrompted = false; // Default to false
let storeGitPrompted = true; // Default to true
if (options.storeTasksInGit !== undefined) {
storeGitPrompted = options.storeTasksInGit; // Use flag value if provided
} else {
const gitTasksInput = await promptQuestion(
rl,
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...
@@ -492,16 +512,6 @@ async function initializeProject(options = {}) {
'info',
`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;
@@ -541,7 +551,9 @@ async function initializeProject(options = {}) {
);
rl.close();
} catch (error) {
rl.close();
if (rl) {
rl.close();
}
log('error', `Error during initialization process: ${error.message}`);
process.exit(1);
}
@@ -564,7 +576,7 @@ function createProjectStructure(
storeTasksInGit,
dryRun,
options,
selectedRuleProfiles = RULE_PROFILES // Default to all rule profiles
selectedRuleProfiles = RULE_PROFILES
) {
const targetDir = process.cwd();
log('info', `Initializing project in ${targetDir}`);
@@ -665,10 +677,13 @@ function createProjectStructure(
log('warn', 'Git not available, skipping repository initialization');
}
// Generate profile rules from assets/rules
log('info', 'Generating profile rules from assets/rules...');
for (const profileName of selectedRuleProfiles) {
_processSingleProfile(profileName);
// Only run the manual transformer if rules were provided via flags.
// 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) {
_processSingleProfile(profileName);
}
}
// Add shell aliases if requested
@@ -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 ===
if (!isSilentMode() && !dryRun && !options?.yes) {
console.log(

View File

@@ -3828,7 +3828,26 @@ Examples:
if (options[RULES_SETUP_ACTION]) {
// Run interactive rules setup ONLY (no project init)
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)) {
console.warn(
`Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
@@ -3836,16 +3855,20 @@ Examples:
continue;
}
const profileConfig = getRulesProfile(profile);
const addResult = convertAllRulesToProfileRules(
projectDir,
profileConfig
);
if (typeof profileConfig.onAddRulesProfile === 'function') {
profileConfig.onAddRulesProfile(projectDir);
}
console.log(chalk.green(generateProfileSummary(profile, addResult)));
}
console.log(
chalk.green(
`\nCompleted installation of all ${selectedRuleProfiles.length} profile(s).`
)
);
return;
}

View File

@@ -39,7 +39,23 @@ const updatedTaskSchema = z
priority: z.string().optional(),
details: 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
@@ -441,6 +457,8 @@ Guidelines:
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
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.`;
@@ -573,6 +591,37 @@ The changes described in the prompt should be thoughtfully applied to make the t
);
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)
if (taskToUpdate.subtasks?.length > 0) {
if (!updatedTask.subtasks) {

View File

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

View File

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

View File

@@ -1,72 +0,0 @@
import { defineConfig } from 'vite';
import { resolve } from 'path';
import { nodeResolve } from '@rollup/plugin-node-resolve';
export default defineConfig({
build: {
ssr: true, // Use SSR mode for Node.js
rollupOptions: {
// Multiple entry points for different applications
input: {
'task-master': resolve(__dirname, 'bin/task-master.js'), // CLI tool
'task-master-mcp': resolve(__dirname, 'mcp-server/server.js') // MCP server
},
// Bundle everything except Node.js built-ins
external: [
// Node.js built-in modules
'fs',
'fs/promises',
'path',
'os',
'crypto',
'http',
'https',
'net',
'tls',
'child_process',
'util',
'events',
'stream',
'url',
'querystring',
'buffer',
'module',
'worker_threads',
'readline',
'process',
'assert',
'zlib',
'dns',
'perf_hooks',
// Optional dependencies that might not be available
'@anthropic-ai/claude-code'
],
output: {
// Generate separate files for each entry
dir: 'dist',
format: 'cjs', // CommonJS for Node.js compatibility
entryFileNames: '[name].cjs',
chunkFileNames: 'chunks/[name]-[hash].cjs',
assetFileNames: 'assets/[name].[ext]'
},
plugins: [
nodeResolve({
preferBuiltins: true,
exportConditions: ['node']
})
]
},
target: 'node18',
outDir: 'dist',
minify: false, // Keep readable for debugging
sourcemap: false
},
define: {
// Define any environment variables if needed
'process.env.NODE_ENV': '"production"'
},
ssr: {
// Don't externalize any dependencies - bundle them all
noExternal: true
}
});