Compare commits
2 Commits
extension@
...
feat/impro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4107f83324 | ||
|
|
184bb5e68e |
8
.changeset/fuzzy-words-count.md
Normal file
8
.changeset/fuzzy-words-count.md
Normal 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
|
||||
8
.changeset/tender-trams-refuse.md
Normal file
8
.changeset/tender-trams-refuse.md
Normal 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
|
||||
11
.changeset/vast-sites-leave.md
Normal file
11
.changeset/vast-sites-leave.md
Normal 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
|
||||
12
.github/scripts/tag-extension.mjs
vendored
12
.github/scripts/tag-extension.mjs
vendored
@@ -86,19 +86,23 @@ if (gitResult.status !== 0) {
|
||||
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}`);
|
||||
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}`);
|
||||
throw new Error(
|
||||
`Failed to check remote for tag ${tag}. Exit code: ${gitResult.status}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
tag,
|
||||
research
|
||||
},
|
||||
'json', // outputFormat
|
||||
research
|
||||
'json' // outputFormat
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
|
||||
@@ -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
|
||||
tag,
|
||||
research
|
||||
},
|
||||
'json', // outputFormat
|
||||
research
|
||||
'json' // outputFormat
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 || ''
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user