Compare commits

...

8 Commits

Author SHA1 Message Date
Ralph Khreish
ce9c521945 chore: add release-check CI to check if we are in release mode 2025-08-04 14:22:13 +03:00
Ralph Khreish
adeb76ee15 chore: exit pre-release mode 2025-08-04 10:02:00 +03:00
Ralph Khreish
d342070375 Merge pull request #1081 from eyaltoledano/next 2025-08-03 21:38:19 +03:00
github-actions[bot]
5e4dbac525 chore: rc version bump 2025-08-03 14:07:15 +00:00
Ralph Khreish
fb15c2eaf7 chore: improve pre-release CI to just use dropdown (#1080)
* chore: improve pre-release CI to just use dropdown

* chore: implement requested changes
2025-08-03 16:04:29 +02:00
Ralph Khreish
e8ceb08341 chore: improve release and pre-release CI (#1075)
* chore: improve release and pre-release CI

* chore: apply requested changes

* Update .github/workflows/pre-release.yml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* chore: apply requested changes

* chore: apply requested changes

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-03 15:17:18 +02:00
Ralph Khreish
e495b2b559 feat: improve scope up and down command & parse-prd improvements (#1079)
* feat: improve scope up and down command & parse-prd improvements

* chore: run format
2025-08-03 14:12:46 +02:00
Ralph Khreish
e0d1d03f33 chore: fix tag-extension and improve debugging (#1074) 2025-08-02 23:10:57 +02:00
18 changed files with 290 additions and 63 deletions

View File

@@ -0,0 +1,8 @@
---
"task-master-ai": patch
---
Fix scope-up/down prompts to include all required fields for better AI model compatibility
- Added missing `priority` field to scope adjustment prompts to prevent validation errors with Claude-code and other models
- Ensures generated JSON includes all fields required by the schema

13
.changeset/pre.json Normal file
View File

@@ -0,0 +1,13 @@
{
"mode": "exit",
"tag": "rc",
"initialVersions": {
"task-master-ai": "0.23.0",
"extension": "0.23.0"
},
"changesets": [
"fuzzy-words-count",
"tender-trams-refuse",
"vast-sites-leave"
]
}

View File

@@ -0,0 +1,8 @@
---
"task-master-ai": patch
---
Fix MCP scope-up/down tools not finding tasks
- Fixed task ID parsing in MCP layer - now correctly converts string IDs to numbers
- scope_up_task and scope_down_task MCP tools now work properly

View File

@@ -0,0 +1,11 @@
---
"task-master-ai": patch
---
Improve AI provider compatibility for JSON generation
- Fixed schema compatibility issues between Perplexity and OpenAI o3 models
- Removed nullable/default modifiers from Zod schemas for broader compatibility
- Added automatic JSON repair for malformed AI responses (handles cases like missing array values)
- Perplexity now uses JSON mode for more reliable structured output
- Post-processing handles default values separately from schema validation

View File

@@ -62,13 +62,51 @@ assert(rootPkg.repository, 'root package.json must have a repository field');
const tag = `${pkg.name}@${pkg.version}`;
// Get repository URL from root package.json
const repoUrl = rootPkg.repository.url;
// Get repository URL and clean it up for git ls-remote
let repoUrl = rootPkg.repository.url || rootPkg.repository;
if (typeof repoUrl === 'string') {
// Convert git+https://github.com/... to https://github.com/...
repoUrl = repoUrl.replace(/^git\+/, '');
// Ensure it ends with .git for proper remote access
if (!repoUrl.endsWith('.git')) {
repoUrl += '.git';
}
}
const { status, stdout, error } = spawnSync('git', ['ls-remote', repoUrl, tag]);
console.log(`Checking remote repository: ${repoUrl} for tag: ${tag}`);
assert.equal(status, 0, error);
let gitResult = spawnSync('git', ['ls-remote', repoUrl, tag], {
encoding: 'utf8',
env: { ...process.env }
});
const exists = String(stdout).trim() !== '';
if (gitResult.status !== 0) {
console.error('Git ls-remote failed:');
console.error('Exit code:', gitResult.status);
console.error('Error:', gitResult.error);
console.error('Stderr:', gitResult.stderr);
console.error('Command:', `git ls-remote ${repoUrl} ${tag}`);
// For CI environments, try using origin instead of the full URL
if (process.env.CI) {
console.log('Retrying with origin remote...');
gitResult = spawnSync('git', ['ls-remote', 'origin', tag], {
encoding: 'utf8'
});
if (gitResult.status !== 0) {
throw new Error(
`Failed to check remote for tag ${tag}. Exit code: ${gitResult.status}`
);
}
} else {
throw new Error(
`Failed to check remote for tag ${tag}. Exit code: ${gitResult.status}`
);
}
}
const exists = String(gitResult.stdout).trim() !== '';
if (!exists) {
console.log(`Creating new extension tag: ${tag}`);

View File

@@ -3,11 +3,12 @@ name: Pre-Release (RC)
on:
workflow_dispatch: # Allows manual triggering from GitHub UI/API
concurrency: pre-release-${{ github.ref }}
concurrency: pre-release-${{ github.ref_name }}
jobs:
rc:
runs-on: ubuntu-latest
# Only allow pre-releases on non-main branches
if: github.ref != 'refs/heads/main'
steps:
- uses: actions/checkout@v4
with:

36
.github/workflows/release-check.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Release Check
on:
pull_request:
branches:
- main
jobs:
check-release-mode:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check release mode
run: |
set -euo pipefail
echo "🔍 Checking if branch is in pre-release mode..."
if [[ -f .changeset/pre.json ]]; then
echo "❌ ERROR: This branch is in pre-release mode!"
echo ""
echo "Pre-release mode must be exited before merging to main."
echo ""
echo "To fix this, run the following commands in your branch:"
echo " npx changeset pre exit"
echo " git add -u"
echo " git commit -m 'chore: exit pre-release mode'"
echo " git push"
echo ""
echo "Then update this pull request."
exit 1
fi
echo "✅ Not in pre-release mode - PR can be merged"

View File

@@ -38,27 +38,27 @@ jobs:
run: npm ci
timeout-minutes: 2
- name: Exit pre-release mode and clean up
- name: Check pre-release mode
run: |
echo "🔄 Ensuring we're not in pre-release mode for main branch..."
# Exit pre-release mode if we're in it
npx changeset pre exit || echo "Not in pre-release mode"
# Remove pre.json file if it exists (belt and suspenders approach)
if [ -f .changeset/pre.json ]; then
echo "🧹 Removing pre.json file..."
rm -f .changeset/pre.json
fi
# Verify the file is gone
if [ ! -f .changeset/pre.json ]; then
echo "✅ pre.json successfully removed"
else
echo "❌ Failed to remove pre.json"
set -euo pipefail
echo "🔍 Checking pre-release mode status..."
if [[ -f .changeset/pre.json ]]; then
echo "❌ ERROR: Main branch is in pre-release mode!"
echo ""
echo "Pre-release mode should only be used on feature branches, not main."
echo ""
echo "To fix this, run the following commands locally:"
echo " npx changeset pre exit"
echo " git add -u"
echo " git commit -m 'chore: exit pre-release mode'"
echo " git push origin main"
echo ""
echo "Then re-run this workflow."
exit 1
fi
echo "✅ Not in pre-release mode - proceeding with release"
- name: Create Release Pull Request or Publish to npm
uses: changesets/action@v1
with:

View File

@@ -1,5 +1,24 @@
# task-master-ai
## 0.23.1-rc.0
### Patch Changes
- [#1079](https://github.com/eyaltoledano/claude-task-master/pull/1079) [`e495b2b`](https://github.com/eyaltoledano/claude-task-master/commit/e495b2b55950ee54c7d0f1817d8530e28bd79c05) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix scope-up/down prompts to include all required fields for better AI model compatibility
- Added missing `priority` field to scope adjustment prompts to prevent validation errors with Claude-code and other models
- Ensures generated JSON includes all fields required by the schema
- [#1079](https://github.com/eyaltoledano/claude-task-master/pull/1079) [`e495b2b`](https://github.com/eyaltoledano/claude-task-master/commit/e495b2b55950ee54c7d0f1817d8530e28bd79c05) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP scope-up/down tools not finding tasks
- Fixed task ID parsing in MCP layer - now correctly converts string IDs to numbers
- scope_up_task and scope_down_task MCP tools now work properly
- [#1079](https://github.com/eyaltoledano/claude-task-master/pull/1079) [`e495b2b`](https://github.com/eyaltoledano/claude-task-master/commit/e495b2b55950ee54c7d0f1817d8530e28bd79c05) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve AI provider compatibility for JSON generation
- Fixed schema compatibility issues between Perplexity and OpenAI o3 models
- Removed nullable/default modifiers from Zod schemas for broader compatibility
- Added automatic JSON repair for malformed AI responses (handles cases like missing array values)
- Perplexity now uses JSON mode for more reliable structured output
- Post-processing handles default values separately from schema validation
## 0.23.0
### Minor Changes

View File

@@ -71,8 +71,8 @@ export async function scopeDownDirect(args, log, context = {}) {
};
}
// Parse task IDs
const taskIds = id.split(',').map((taskId) => taskId.trim());
// Parse task IDs - convert to numbers as expected by scopeDownTask
const taskIds = id.split(',').map((taskId) => parseInt(taskId.trim(), 10));
log.info(
`Scoping down tasks: ${taskIds.join(', ')}, strength: ${strength}, research: ${research}`
@@ -90,10 +90,10 @@ export async function scopeDownDirect(args, log, context = {}) {
projectRoot,
commandName: 'scope-down',
outputType: 'mcp',
tag
},
'json', // outputFormat
tag,
research
},
'json' // outputFormat
);
// Restore normal logging

View File

@@ -71,8 +71,8 @@ export async function scopeUpDirect(args, log, context = {}) {
};
}
// Parse task IDs
const taskIds = id.split(',').map((taskId) => taskId.trim());
// Parse task IDs - convert to numbers as expected by scopeUpTask
const taskIds = id.split(',').map((taskId) => parseInt(taskId.trim(), 10));
log.info(
`Scoping up tasks: ${taskIds.join(', ')}, strength: ${strength}, research: ${research}`
@@ -90,10 +90,10 @@ export async function scopeUpDirect(args, log, context = {}) {
projectRoot,
commandName: 'scope-up',
outputType: 'mcp',
tag
},
'json', // outputFormat
tag,
research
},
'json' // outputFormat
);
// Restore normal logging

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "task-master-ai",
"version": "0.22.1-rc.0",
"version": "0.23.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "task-master-ai",
"version": "0.22.1-rc.0",
"version": "0.23.0",
"license": "MIT WITH Commons-Clause",
"workspaces": [
"apps/*",
@@ -46,6 +46,7 @@
"helmet": "^8.1.0",
"inquirer": "^12.5.0",
"jsonc-parser": "^3.3.1",
"jsonrepair": "^3.13.0",
"jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0",
"ollama-ai-provider": "^1.2.0",
@@ -84,7 +85,7 @@
}
},
"apps/extension": {
"version": "0.22.3",
"version": "0.23.0",
"devDependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
@@ -14942,6 +14943,15 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonrepair": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.0.tgz",
"integrity": "sha512-5YRzlAQ7tuzV1nAJu3LvDlrKtBFIALHN2+a+I1MGJCt3ldRDBF/bZuvIPzae8Epot6KBXd0awRZZcuoeAsZ/mw==",
"license": "ISC",
"bin": {
"jsonrepair": "bin/cli.js"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "task-master-ai",
"version": "0.23.0",
"version": "0.23.1-rc.0",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",
@@ -73,6 +73,7 @@
"helmet": "^8.1.0",
"inquirer": "^12.5.0",
"jsonc-parser": "^3.3.1",
"jsonrepair": "^3.13.0",
"jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0",
"ollama-ai-provider": "^1.2.0",

View File

@@ -1479,7 +1479,8 @@ function registerCommands(programInstance) {
projectRoot: taskMaster.getProjectRoot(),
tag,
commandName: 'scope-up',
outputType: 'cli'
outputType: 'cli',
research: options.research || false
};
const result = await scopeUpTask(
@@ -1605,7 +1606,8 @@ function registerCommands(programInstance) {
projectRoot: taskMaster.getProjectRoot(),
tag,
commandName: 'scope-down',
outputType: 'cli'
outputType: 'cli',
research: options.research || false
};
const result = await scopeDownTask(

View File

@@ -23,14 +23,14 @@ import { displayAiUsageSummary } from '../ui.js';
// Define the Zod schema for a SINGLE task object
const prdSingleTaskSchema = z.object({
id: z.number().int().positive(),
id: z.number(),
title: z.string().min(1),
description: z.string().min(1),
details: z.string().nullable(),
testStrategy: z.string().nullable(),
priority: z.enum(['high', 'medium', 'low']).nullable(),
dependencies: z.array(z.number().int().positive()).nullable(),
status: z.string().nullable()
details: z.string(),
testStrategy: z.string(),
priority: z.enum(['high', 'medium', 'low']),
dependencies: z.array(z.number()),
status: z.string()
});
// Define the Zod schema for the ENTIRE expected AI response object
@@ -257,10 +257,15 @@ async function parsePRD(prdPath, tasksPath, numTasks, options = {}) {
return {
...task,
id: newId,
status: 'pending',
status: task.status || 'pending',
priority: task.priority || 'medium',
dependencies: Array.isArray(task.dependencies) ? task.dependencies : [],
subtasks: []
subtasks: [],
// Ensure all required fields have values (even if empty strings)
title: task.title || '',
description: task.description || '',
details: task.details || '',
testStrategy: task.testStrategy || ''
};
});

View File

@@ -337,7 +337,7 @@ ${
}
Return a JSON object with a "subtasks" array. Each subtask should have:
- id: Sequential number starting from 1
- id: Sequential NUMBER starting from 1 (e.g., 1, 2, 3 - NOT "1", "2", "3")
- title: Clear, specific title
- description: Detailed description
- dependencies: Array of dependency IDs as STRINGS (use format ["${task.id}.1", "${task.id}.2"] for siblings, or empty array [] for no dependencies)
@@ -345,7 +345,9 @@ Return a JSON object with a "subtasks" array. Each subtask should have:
- status: "pending"
- testStrategy: Testing approach
IMPORTANT: Dependencies must be strings, not numbers!
IMPORTANT:
- The 'id' field must be a NUMBER, not a string!
- Dependencies must be strings, not numbers!
Ensure the JSON is valid and properly formatted.`;
@@ -358,14 +360,14 @@ Ensure the JSON is valid and properly formatted.`;
description: z.string().min(10),
dependencies: z.array(z.string()),
details: z.string().min(20),
status: z.string().default('pending'),
testStrategy: z.string().nullable().default('')
status: z.string(),
testStrategy: z.string()
})
)
});
const aiResult = await generateObjectService({
role: 'main',
role: context.research ? 'research' : 'main',
session: context.session,
systemPrompt,
prompt,
@@ -377,14 +379,21 @@ Ensure the JSON is valid and properly formatted.`;
const generatedSubtasks = aiResult.mainResult.subtasks || [];
// Post-process generated subtasks to ensure defaults
const processedGeneratedSubtasks = generatedSubtasks.map((subtask) => ({
...subtask,
status: subtask.status || 'pending',
testStrategy: subtask.testStrategy || ''
}));
// Update task with preserved subtasks + newly generated ones
task.subtasks = [...preservedSubtasks, ...generatedSubtasks];
task.subtasks = [...preservedSubtasks, ...processedGeneratedSubtasks];
return {
updatedTask: task,
regenerated: true,
preserved: preservedSubtasks.length,
generated: generatedSubtasks.length
generated: processedGeneratedSubtasks.length
};
} catch (error) {
log(
@@ -457,6 +466,7 @@ ADJUSTMENT REQUIREMENTS:
- description: Updated task description
- details: Updated implementation details
- testStrategy: Updated test strategy
- priority: Task priority ('low', 'medium', or 'high')
Ensure the JSON is valid and properly formatted.`;
@@ -501,14 +511,11 @@ async function adjustTaskComplexity(
.string()
.min(1)
.describe('Updated testing approach for the adjusted scope'),
priority: z
.enum(['low', 'medium', 'high'])
.optional()
.describe('Task priority level')
priority: z.enum(['low', 'medium', 'high']).describe('Task priority level')
});
const aiResult = await generateObjectService({
role: 'main',
role: context.research ? 'research' : 'main',
session: context.session,
systemPrompt,
prompt,
@@ -520,10 +527,16 @@ async function adjustTaskComplexity(
const updatedTaskData = aiResult.mainResult;
// Ensure priority has a value (in case AI didn't provide one)
const processedTaskData = {
...updatedTaskData,
priority: updatedTaskData.priority || task.priority || 'medium'
};
return {
updatedTask: {
...task,
...updatedTaskData
...processedTaskData
},
telemetryData: aiResult.telemetryData
};

View File

@@ -1,4 +1,12 @@
import { generateObject, generateText, streamText } from 'ai';
import {
generateObject,
generateText,
streamText,
zodSchema,
JSONParseError,
NoObjectGeneratedError
} from 'ai';
import { jsonrepair } from 'jsonrepair';
import { log } from '../../scripts/modules/utils.js';
/**
@@ -206,8 +214,8 @@ export class BaseAIProvider {
const result = await generateObject({
model: client(params.modelId),
messages: params.messages,
schema: params.schema,
mode: 'auto',
schema: zodSchema(params.schema),
mode: params.mode || 'auto',
maxTokens: params.maxTokens,
temperature: params.temperature
});
@@ -226,6 +234,43 @@ export class BaseAIProvider {
}
};
} catch (error) {
// Check if this is a JSON parsing error that we can potentially fix
if (
NoObjectGeneratedError.isInstance(error) &&
JSONParseError.isInstance(error.cause) &&
error.cause.text
) {
log(
'warn',
`${this.name} generated malformed JSON, attempting to repair...`
);
try {
// Use jsonrepair to fix the malformed JSON
const repairedJson = jsonrepair(error.cause.text);
const parsed = JSON.parse(repairedJson);
log('info', `Successfully repaired ${this.name} JSON output`);
// Return in the expected format
return {
object: parsed,
usage: {
// Extract usage information from the error if available
inputTokens: error.usage?.promptTokens || 0,
outputTokens: error.usage?.completionTokens || 0,
totalTokens: error.usage?.totalTokens || 0
}
};
} catch (repairError) {
log(
'error',
`Failed to repair ${this.name} JSON: ${repairError.message}`
);
// Fall through to handleError with original error
}
}
this.handleError('object generation', error);
}
}

View File

@@ -44,4 +44,21 @@ export class PerplexityAIProvider extends BaseAIProvider {
this.handleError('client initialization', error);
}
}
/**
* Override generateObject to use JSON mode for Perplexity
*
* NOTE: Perplexity models (especially sonar models) have known issues
* generating valid JSON, particularly with array fields. They often
* generate malformed JSON like "dependencies": , instead of "dependencies": []
*
* The base provider now handles JSON repair automatically for all providers.
*/
async generateObject(params) {
// Force JSON mode for Perplexity as it may help with reliability
return super.generateObject({
...params,
mode: 'json'
});
}
}