mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-07-04 23:50:50 +00:00

This commit focuses on standardizing configuration and API key access patterns across key modules as part of subtask 61.34. Key changes include: - Refactored `ai-services.js` to remove global AI clients and use `resolveEnvVariable` for API key checks. Client instantiation now relies on `getAnthropicClient`/`getPerplexityClient` accepting a session object. - Refactored `task-manager.js` (`analyzeTaskComplexity` function) to use the unified `generateTextService` from `ai-services-unified.js`, removing direct AI client calls. - Replaced direct `process.env` access for model parameters and other configurations (`PERPLEXITY_MODEL`, `CONFIG.*`) in `task-manager.js` with calls to the appropriate getters from `config-manager.js` (e.g., `getResearchModelId(session)`, `getMainMaxTokens(session)`). - Ensured `utils.js` (`resolveEnvVariable`) correctly handles potentially undefined session objects. - Updated function signatures where necessary to propagate the `session` object for correct context-aware configuration/key retrieval. This moves towards the goal of using `ai-client-factory.js` and `ai-services-unified.js` as the standard pattern for AI interactions and centralizing configuration management through `config-manager.js`.
120 lines
3.7 KiB
JavaScript
120 lines
3.7 KiB
JavaScript
import path from 'path';
|
|
import { log, readJSON, writeJSON } from '../utils.js';
|
|
import generateTaskFiles from './generate-task-files.js';
|
|
|
|
/**
|
|
* Remove a subtask from its parent task
|
|
* @param {string} tasksPath - Path to the tasks.json file
|
|
* @param {string} subtaskId - ID of the subtask to remove in format "parentId.subtaskId"
|
|
* @param {boolean} convertToTask - Whether to convert the subtask to a standalone task
|
|
* @param {boolean} generateFiles - Whether to regenerate task files after removing the subtask
|
|
* @returns {Object|null} The removed subtask if convertToTask is true, otherwise null
|
|
*/
|
|
async function removeSubtask(
|
|
tasksPath,
|
|
subtaskId,
|
|
convertToTask = false,
|
|
generateFiles = true
|
|
) {
|
|
try {
|
|
log('info', `Removing subtask ${subtaskId}...`);
|
|
|
|
// Read the existing tasks
|
|
const data = readJSON(tasksPath);
|
|
if (!data || !data.tasks) {
|
|
throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
|
|
}
|
|
|
|
// Parse the subtask ID (format: "parentId.subtaskId")
|
|
if (!subtaskId.includes('.')) {
|
|
throw new Error(
|
|
`Invalid subtask ID format: ${subtaskId}. Expected format: "parentId.subtaskId"`
|
|
);
|
|
}
|
|
|
|
const [parentIdStr, subtaskIdStr] = subtaskId.split('.');
|
|
const parentId = parseInt(parentIdStr, 10);
|
|
const subtaskIdNum = parseInt(subtaskIdStr, 10);
|
|
|
|
// Find the parent task
|
|
const parentTask = data.tasks.find((t) => t.id === parentId);
|
|
if (!parentTask) {
|
|
throw new Error(`Parent task with ID ${parentId} not found`);
|
|
}
|
|
|
|
// Check if parent has subtasks
|
|
if (!parentTask.subtasks || parentTask.subtasks.length === 0) {
|
|
throw new Error(`Parent task ${parentId} has no subtasks`);
|
|
}
|
|
|
|
// Find the subtask to remove
|
|
const subtaskIndex = parentTask.subtasks.findIndex(
|
|
(st) => st.id === subtaskIdNum
|
|
);
|
|
if (subtaskIndex === -1) {
|
|
throw new Error(`Subtask ${subtaskId} not found`);
|
|
}
|
|
|
|
// Get a copy of the subtask before removing it
|
|
const removedSubtask = { ...parentTask.subtasks[subtaskIndex] };
|
|
|
|
// Remove the subtask from the parent
|
|
parentTask.subtasks.splice(subtaskIndex, 1);
|
|
|
|
// If parent has no more subtasks, remove the subtasks array
|
|
if (parentTask.subtasks.length === 0) {
|
|
delete parentTask.subtasks;
|
|
}
|
|
|
|
let convertedTask = null;
|
|
|
|
// Convert the subtask to a standalone task if requested
|
|
if (convertToTask) {
|
|
log('info', `Converting subtask ${subtaskId} to a standalone task...`);
|
|
|
|
// Find the highest task ID to determine the next ID
|
|
const highestId = Math.max(...data.tasks.map((t) => t.id));
|
|
const newTaskId = highestId + 1;
|
|
|
|
// Create the new task from the subtask
|
|
convertedTask = {
|
|
id: newTaskId,
|
|
title: removedSubtask.title,
|
|
description: removedSubtask.description || '',
|
|
details: removedSubtask.details || '',
|
|
status: removedSubtask.status || 'pending',
|
|
dependencies: removedSubtask.dependencies || [],
|
|
priority: parentTask.priority || 'medium' // Inherit priority from parent
|
|
};
|
|
|
|
// Add the parent task as a dependency if not already present
|
|
if (!convertedTask.dependencies.includes(parentId)) {
|
|
convertedTask.dependencies.push(parentId);
|
|
}
|
|
|
|
// Add the converted task to the tasks array
|
|
data.tasks.push(convertedTask);
|
|
|
|
log('info', `Created new task ${newTaskId} from subtask ${subtaskId}`);
|
|
} else {
|
|
log('info', `Subtask ${subtaskId} deleted`);
|
|
}
|
|
|
|
// Write the updated tasks back to the file
|
|
writeJSON(tasksPath, data);
|
|
|
|
// Generate task files if requested
|
|
if (generateFiles) {
|
|
log('info', 'Regenerating task files...');
|
|
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
|
}
|
|
|
|
return convertedTask;
|
|
} catch (error) {
|
|
log('error', `Error removing subtask: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export default removeSubtask;
|