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:
Ralph Khreish
2025-07-19 00:57:47 +03:00
parent 858d4a1c54
commit 444aa5ae19
17 changed files with 148 additions and 83 deletions

View File

@@ -3727,10 +3727,7 @@ Examples:
const taskMaster = initTaskMaster({});
const projectRoot = taskMaster.getProjectRoot(); // Find project root for context
const { response, setup } = options;
console.log(
chalk.blue('Response language set to:', JSON.stringify(options))
);
let responseLanguage = response || 'English';
let responseLanguage = response !== undefined ? response : 'English';
if (setup) {
console.log(
chalk.blue('Starting interactive response language setup...')
@@ -3772,6 +3769,7 @@ Examples:
`❌ Error setting response language: ${result.error.message}`
)
);
process.exit(1);
}
});

View File

@@ -40,8 +40,10 @@ const subtaskSchema = z
.min(10)
.describe('Detailed description of the subtask'),
dependencies: z
.array(z.number().int())
.describe('IDs of prerequisite subtasks within this expansion'),
.array(z.string())
.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'),
status: z
.string()
@@ -235,12 +237,10 @@ function parseSubtasksFromText(
...rawSubtask,
id: currentId,
dependencies: Array.isArray(rawSubtask.dependencies)
? rawSubtask.dependencies
.map((dep) => (typeof dep === 'string' ? parseInt(dep, 10) : dep))
.filter(
(depId) =>
!Number.isNaN(depId) && depId >= startId && depId < currentId
)
? rawSubtask.dependencies.filter(
(dep) =>
typeof dep === 'string' && dep.startsWith(`${parentTaskId}.`)
)
: [],
status: 'pending'
};

View File

@@ -25,6 +25,10 @@ import { findConfigPath } from '../../../src/utils/path-utils.js';
import { log } from '../utils.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.
* @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) {
throw new Error(
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
throw new Error(CONFIG_MISSING_ERROR);
}
try {
@@ -298,9 +300,7 @@ async function getAvailableModelsList(options = {}) {
);
if (!configExists) {
throw new Error(
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
throw new Error(CONFIG_MISSING_ERROR);
}
try {
@@ -391,9 +391,7 @@ async function setModel(role, modelId, options = {}) {
);
if (!configExists) {
throw new Error(
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
throw new Error(CONFIG_MISSING_ERROR);
}
// Validate role

View File

@@ -19,7 +19,6 @@ import {
import { generateObjectService } from '../ai-services-unified.js';
import { getDebugFlag } from '../config-manager.js';
import { getPromptManager } from '../prompt-manager.js';
import generateTaskFiles from './generate-task-files.js';
import { displayAiUsageSummary } from '../ui.js';
// Define the Zod schema for a SINGLE task object

View File

@@ -34,7 +34,7 @@ function setResponseLanguage(lang, options = {}) {
error: {
code: 'CONFIG_MISSING',
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.'
}
};
}

View File

@@ -42,7 +42,39 @@ const updatedTaskSchema = z
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
})
.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 preprocessedTaskArraySchema = z.array(preprocessTaskSchema);
/**
* 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
const preprocessedTasks = parsedTasks.map((task) => ({
...task,
// Ensure subtasks is always an array (not null or undefined)
subtasks: Array.isArray(task.subtasks) ? task.subtasks : [],
// Ensure status has a default value if missing
status: task.status || 'pending',
// Ensure dependencies is always an array
dependencies: Array.isArray(task.dependencies) ? task.dependencies : []
}));
// Log missing fields for debugging before preprocessing
let hasWarnings = false;
parsedTasks.forEach((task, index) => {
const missingFields = [];
if (!task.hasOwnProperty('id')) missingFields.push('id');
if (!task.hasOwnProperty('status')) missingFields.push('status');
if (!task.hasOwnProperty('dependencies'))
missingFields.push('dependencies');
const validationResult = updatedTaskArraySchema.safeParse(preprocessedTasks);
if (!validationResult.success) {
report('error', 'Parsed task array failed Zod validation.');
validationResult.error.errors.forEach((err) => {
report('error', ` - Path '${err.path.join('.')}': ${err.message}`);
});
throw new Error(
`AI response failed task structure validation: ${validationResult.error.message}`
if (missingFields.length > 0) {
hasWarnings = true;
report(
'warn',
`Task ${index} is missing fields: ${missingFields.join(', ')} - will use defaults`
);
}
});
if (hasWarnings) {
report(
'warn',
'Some tasks were missing required fields. Applying defaults...'
);
}
report('info', 'Successfully validated task structure.');
return validationResult.data.slice(
// Use the preprocessing schema to add defaults and validate
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,
expectedCount || validationResult.data.length
expectedCount || preprocessResult.data.length
);
}