Files
claudecodeui/server/routes/taskmaster.js
2025-08-28 12:11:42 +03:00

1971 lines
63 KiB
JavaScript

/**
* TASKMASTER API ROUTES
* ====================
*
* This module provides API endpoints for TaskMaster integration including:
* - .taskmaster folder detection in project directories
* - MCP server configuration detection
* - TaskMaster state and metadata management
*/
import express from 'express';
import fs from 'fs';
import path from 'path';
import { promises as fsPromises } from 'fs';
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import os from 'os';
import { extractProjectDirectory } from '../projects.js';
import { detectTaskMasterMCPServer } from '../utils/mcp-detector.js';
import { broadcastTaskMasterProjectUpdate, broadcastTaskMasterTasksUpdate } from '../utils/taskmaster-websocket.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const router = express.Router();
/**
* Check if TaskMaster CLI is installed globally
* @returns {Promise<Object>} Installation status result
*/
async function checkTaskMasterInstallation() {
return new Promise((resolve) => {
// Check if task-master command is available
const child = spawn('which', ['task-master'], {
stdio: ['ignore', 'pipe', 'pipe'],
shell: true
});
let output = '';
let errorOutput = '';
child.stdout.on('data', (data) => {
output += data.toString();
});
child.stderr.on('data', (data) => {
errorOutput += data.toString();
});
child.on('close', (code) => {
if (code === 0 && output.trim()) {
// TaskMaster is installed, get version
const versionChild = spawn('task-master', ['--version'], {
stdio: ['ignore', 'pipe', 'pipe'],
shell: true
});
let versionOutput = '';
versionChild.stdout.on('data', (data) => {
versionOutput += data.toString();
});
versionChild.on('close', (versionCode) => {
resolve({
isInstalled: true,
installPath: output.trim(),
version: versionCode === 0 ? versionOutput.trim() : 'unknown',
reason: null
});
});
versionChild.on('error', () => {
resolve({
isInstalled: true,
installPath: output.trim(),
version: 'unknown',
reason: null
});
});
} else {
resolve({
isInstalled: false,
installPath: null,
version: null,
reason: 'TaskMaster CLI not found in PATH'
});
}
});
child.on('error', (error) => {
resolve({
isInstalled: false,
installPath: null,
version: null,
reason: `Error checking installation: ${error.message}`
});
});
});
}
/**
* Detect .taskmaster folder presence in a given project directory
* @param {string} projectPath - Absolute path to project directory
* @returns {Promise<Object>} Detection result with status and metadata
*/
async function detectTaskMasterFolder(projectPath) {
try {
const taskMasterPath = path.join(projectPath, '.taskmaster');
// Check if .taskmaster directory exists
try {
const stats = await fsPromises.stat(taskMasterPath);
if (!stats.isDirectory()) {
return {
hasTaskmaster: false,
reason: '.taskmaster exists but is not a directory'
};
}
} catch (error) {
if (error.code === 'ENOENT') {
return {
hasTaskmaster: false,
reason: '.taskmaster directory not found'
};
}
throw error;
}
// Check for key TaskMaster files
const keyFiles = [
'tasks/tasks.json',
'config.json'
];
const fileStatus = {};
let hasEssentialFiles = true;
for (const file of keyFiles) {
const filePath = path.join(taskMasterPath, file);
try {
await fsPromises.access(filePath, fs.constants.R_OK);
fileStatus[file] = true;
} catch (error) {
fileStatus[file] = false;
if (file === 'tasks/tasks.json') {
hasEssentialFiles = false;
}
}
}
// Parse tasks.json if it exists for metadata
let taskMetadata = null;
if (fileStatus['tasks/tasks.json']) {
try {
const tasksPath = path.join(taskMasterPath, 'tasks/tasks.json');
const tasksContent = await fsPromises.readFile(tasksPath, 'utf8');
const tasksData = JSON.parse(tasksContent);
// Handle both tagged and legacy formats
let tasks = [];
if (tasksData.tasks) {
// Legacy format
tasks = tasksData.tasks;
} else {
// Tagged format - get tasks from all tags
Object.values(tasksData).forEach(tagData => {
if (tagData.tasks) {
tasks = tasks.concat(tagData.tasks);
}
});
}
// Calculate task statistics
const stats = tasks.reduce((acc, task) => {
acc.total++;
acc[task.status] = (acc[task.status] || 0) + 1;
// Count subtasks
if (task.subtasks) {
task.subtasks.forEach(subtask => {
acc.subtotalTasks++;
acc.subtasks = acc.subtasks || {};
acc.subtasks[subtask.status] = (acc.subtasks[subtask.status] || 0) + 1;
});
}
return acc;
}, {
total: 0,
subtotalTasks: 0,
pending: 0,
'in-progress': 0,
done: 0,
review: 0,
deferred: 0,
cancelled: 0,
subtasks: {}
});
taskMetadata = {
taskCount: stats.total,
subtaskCount: stats.subtotalTasks,
completed: stats.done || 0,
pending: stats.pending || 0,
inProgress: stats['in-progress'] || 0,
review: stats.review || 0,
completionPercentage: stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0,
lastModified: (await fsPromises.stat(tasksPath)).mtime.toISOString()
};
} catch (parseError) {
console.warn('Failed to parse tasks.json:', parseError.message);
taskMetadata = { error: 'Failed to parse tasks.json' };
}
}
return {
hasTaskmaster: true,
hasEssentialFiles,
files: fileStatus,
metadata: taskMetadata,
path: taskMasterPath
};
} catch (error) {
console.error('Error detecting TaskMaster folder:', error);
return {
hasTaskmaster: false,
reason: `Error checking directory: ${error.message}`
};
}
}
// MCP detection is now handled by the centralized utility
// API Routes
/**
* GET /api/taskmaster/installation-status
* Check if TaskMaster CLI is installed on the system
*/
router.get('/installation-status', async (req, res) => {
try {
const installationStatus = await checkTaskMasterInstallation();
// Also check for MCP server configuration
const mcpStatus = await detectTaskMasterMCPServer();
res.json({
success: true,
installation: installationStatus,
mcpServer: mcpStatus,
isReady: installationStatus.isInstalled && mcpStatus.hasMCPServer
});
} catch (error) {
console.error('Error checking TaskMaster installation:', error);
res.status(500).json({
success: false,
error: 'Failed to check TaskMaster installation status',
installation: {
isInstalled: false,
reason: `Server error: ${error.message}`
},
mcpServer: {
hasMCPServer: false,
reason: `Server error: ${error.message}`
},
isReady: false
});
}
});
/**
* GET /api/taskmaster/detect/:projectName
* Detect TaskMaster configuration for a specific project
*/
router.get('/detect/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Use the existing extractProjectDirectory function to get actual project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
console.error('Error extracting project directory:', error);
return res.status(404).json({
error: 'Project path not found',
projectName,
message: error.message
});
}
// Verify the project path exists
try {
await fsPromises.access(projectPath, fs.constants.R_OK);
} catch (error) {
return res.status(404).json({
error: 'Project path not accessible',
projectPath,
projectName,
message: error.message
});
}
// Run detection in parallel
const [taskMasterResult, mcpResult] = await Promise.all([
detectTaskMasterFolder(projectPath),
detectTaskMasterMCPServer()
]);
// Determine overall status
let status = 'not-configured';
if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'fully-configured';
} else {
status = 'taskmaster-only';
}
} else if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'mcp-only';
}
const responseData = {
projectName,
projectPath,
status,
taskmaster: taskMasterResult,
mcp: mcpResult,
timestamp: new Date().toISOString()
};
// Broadcast TaskMaster project update via WebSocket
if (req.app.locals.wss) {
broadcastTaskMasterProjectUpdate(
req.app.locals.wss,
projectName,
taskMasterResult
);
}
res.json(responseData);
} catch (error) {
console.error('TaskMaster detection error:', error);
res.status(500).json({
error: 'Failed to detect TaskMaster configuration',
message: error.message
});
}
});
/**
* GET /api/taskmaster/detect-all
* Detect TaskMaster configuration for all known projects
* This endpoint works with the existing projects system
*/
router.get('/detect-all', async (req, res) => {
try {
// Import getProjects from the projects module
const { getProjects } = await import('../projects.js');
const projects = await getProjects();
// Run detection for all projects in parallel
const detectionPromises = projects.map(async (project) => {
try {
// Use the project's fullPath if available, otherwise extract the directory
let projectPath;
if (project.fullPath) {
projectPath = project.fullPath;
} else {
try {
projectPath = await extractProjectDirectory(project.name);
} catch (error) {
throw new Error(`Failed to extract project directory: ${error.message}`);
}
}
const [taskMasterResult, mcpResult] = await Promise.all([
detectTaskMasterFolder(projectPath),
detectTaskMasterMCPServer()
]);
// Determine status
let status = 'not-configured';
if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'fully-configured';
} else {
status = 'taskmaster-only';
}
} else if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
status = 'mcp-only';
}
return {
projectName: project.name,
displayName: project.displayName,
projectPath,
status,
taskmaster: taskMasterResult,
mcp: mcpResult
};
} catch (error) {
return {
projectName: project.name,
displayName: project.displayName,
status: 'error',
error: error.message
};
}
});
const results = await Promise.all(detectionPromises);
res.json({
projects: results,
summary: {
total: results.length,
fullyConfigured: results.filter(p => p.status === 'fully-configured').length,
taskmasterOnly: results.filter(p => p.status === 'taskmaster-only').length,
mcpOnly: results.filter(p => p.status === 'mcp-only').length,
notConfigured: results.filter(p => p.status === 'not-configured').length,
errors: results.filter(p => p.status === 'error').length
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Bulk TaskMaster detection error:', error);
res.status(500).json({
error: 'Failed to detect TaskMaster configuration for projects',
message: error.message
});
}
});
/**
* POST /api/taskmaster/initialize/:projectName
* Initialize TaskMaster in a project (placeholder for future CLI integration)
*/
router.post('/initialize/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
const { rules } = req.body; // Optional rule profiles
// This will be implemented in a later subtask with CLI integration
res.status(501).json({
error: 'TaskMaster initialization not yet implemented',
message: 'This endpoint will execute task-master init via CLI in a future update',
projectName,
rules
});
} catch (error) {
console.error('TaskMaster initialization error:', error);
res.status(500).json({
error: 'Failed to initialize TaskMaster',
message: error.message
});
}
});
/**
* GET /api/taskmaster/next/:projectName
* Get the next recommended task using task-master CLI
*/
router.get('/next/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
// Try to execute task-master next command
try {
const { spawn } = await import('child_process');
const nextTaskCommand = spawn('task-master', ['next'], {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
nextTaskCommand.stdout.on('data', (data) => {
stdout += data.toString();
});
nextTaskCommand.stderr.on('data', (data) => {
stderr += data.toString();
});
await new Promise((resolve, reject) => {
nextTaskCommand.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`task-master next failed with code ${code}: ${stderr}`));
}
});
nextTaskCommand.on('error', (error) => {
reject(error);
});
});
// Parse the output - task-master next usually returns JSON
let nextTaskData = null;
if (stdout.trim()) {
try {
nextTaskData = JSON.parse(stdout);
} catch (parseError) {
// If not JSON, treat as plain text
nextTaskData = { message: stdout.trim() };
}
}
res.json({
projectName,
projectPath,
nextTask: nextTaskData,
timestamp: new Date().toISOString()
});
} catch (cliError) {
console.warn('Failed to execute task-master CLI:', cliError.message);
// Fallback to loading tasks and finding next one locally
const tasksResponse = await fetch(`${req.protocol}://${req.get('host')}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, {
headers: {
'Authorization': req.headers.authorization
}
});
if (tasksResponse.ok) {
const tasksData = await tasksResponse.json();
const nextTask = tasksData.tasks?.find(task =>
task.status === 'pending' || task.status === 'in-progress'
) || null;
res.json({
projectName,
projectPath,
nextTask,
fallback: true,
message: 'Used fallback method (CLI not available)',
timestamp: new Date().toISOString()
});
} else {
throw new Error('Failed to load tasks via fallback method');
}
}
} catch (error) {
console.error('TaskMaster next task error:', error);
res.status(500).json({
error: 'Failed to get next task',
message: error.message
});
}
});
/**
* GET /api/taskmaster/tasks/:projectName
* Load actual tasks from .taskmaster/tasks/tasks.json
*/
router.get('/tasks/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const taskMasterPath = path.join(projectPath, '.taskmaster');
const tasksFilePath = path.join(taskMasterPath, 'tasks', 'tasks.json');
// Check if tasks file exists
try {
await fsPromises.access(tasksFilePath);
} catch (error) {
return res.json({
projectName,
tasks: [],
message: 'No tasks.json file found'
});
}
// Read and parse tasks file
try {
const tasksContent = await fsPromises.readFile(tasksFilePath, 'utf8');
const tasksData = JSON.parse(tasksContent);
let tasks = [];
let currentTag = 'master';
// Handle both tagged and legacy formats
if (Array.isArray(tasksData)) {
// Legacy format
tasks = tasksData;
} else if (tasksData.tasks) {
// Simple format with tasks array
tasks = tasksData.tasks;
} else {
// Tagged format - get tasks from current tag or master
if (tasksData[currentTag] && tasksData[currentTag].tasks) {
tasks = tasksData[currentTag].tasks;
} else if (tasksData.master && tasksData.master.tasks) {
tasks = tasksData.master.tasks;
} else {
// Get tasks from first available tag
const firstTag = Object.keys(tasksData).find(key =>
tasksData[key].tasks && Array.isArray(tasksData[key].tasks)
);
if (firstTag) {
tasks = tasksData[firstTag].tasks;
currentTag = firstTag;
}
}
}
// Transform tasks to ensure all have required fields
const transformedTasks = tasks.map(task => ({
id: task.id,
title: task.title || 'Untitled Task',
description: task.description || '',
status: task.status || 'pending',
priority: task.priority || 'medium',
dependencies: task.dependencies || [],
createdAt: task.createdAt || task.created || new Date().toISOString(),
updatedAt: task.updatedAt || task.updated || new Date().toISOString(),
details: task.details || '',
testStrategy: task.testStrategy || task.test_strategy || '',
subtasks: task.subtasks || []
}));
res.json({
projectName,
projectPath,
tasks: transformedTasks,
currentTag,
totalTasks: transformedTasks.length,
tasksByStatus: {
pending: transformedTasks.filter(t => t.status === 'pending').length,
'in-progress': transformedTasks.filter(t => t.status === 'in-progress').length,
done: transformedTasks.filter(t => t.status === 'done').length,
review: transformedTasks.filter(t => t.status === 'review').length,
deferred: transformedTasks.filter(t => t.status === 'deferred').length,
cancelled: transformedTasks.filter(t => t.status === 'cancelled').length
},
timestamp: new Date().toISOString()
});
} catch (parseError) {
console.error('Failed to parse tasks.json:', parseError);
return res.status(500).json({
error: 'Failed to parse tasks file',
message: parseError.message
});
}
} catch (error) {
console.error('TaskMaster tasks loading error:', error);
res.status(500).json({
error: 'Failed to load TaskMaster tasks',
message: error.message
});
}
});
/**
* GET /api/taskmaster/prd/:projectName
* List all PRD files in the project's .taskmaster/docs directory
*/
router.get('/prd/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const docsPath = path.join(projectPath, '.taskmaster', 'docs');
// Check if docs directory exists
try {
await fsPromises.access(docsPath, fs.constants.R_OK);
} catch (error) {
return res.json({
projectName,
prdFiles: [],
message: 'No .taskmaster/docs directory found'
});
}
// Read directory and filter for PRD files
try {
const files = await fsPromises.readdir(docsPath);
const prdFiles = [];
for (const file of files) {
const filePath = path.join(docsPath, file);
const stats = await fsPromises.stat(filePath);
if (stats.isFile() && (file.endsWith('.txt') || file.endsWith('.md'))) {
prdFiles.push({
name: file,
path: path.relative(projectPath, filePath),
size: stats.size,
modified: stats.mtime.toISOString(),
created: stats.birthtime.toISOString()
});
}
}
res.json({
projectName,
projectPath,
prdFiles: prdFiles.sort((a, b) => new Date(b.modified) - new Date(a.modified)),
timestamp: new Date().toISOString()
});
} catch (readError) {
console.error('Error reading docs directory:', readError);
return res.status(500).json({
error: 'Failed to read PRD files',
message: readError.message
});
}
} catch (error) {
console.error('PRD list error:', error);
res.status(500).json({
error: 'Failed to list PRD files',
message: error.message
});
}
});
/**
* POST /api/taskmaster/prd/:projectName
* Create or update a PRD file in the project's .taskmaster/docs directory
*/
router.post('/prd/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
const { fileName, content } = req.body;
if (!fileName || !content) {
return res.status(400).json({
error: 'Missing required fields',
message: 'fileName and content are required'
});
}
// Validate filename
if (!fileName.match(/^[\w\-. ]+\.(txt|md)$/)) {
return res.status(400).json({
error: 'Invalid filename',
message: 'Filename must end with .txt or .md and contain only alphanumeric characters, spaces, dots, and dashes'
});
}
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const docsPath = path.join(projectPath, '.taskmaster', 'docs');
const filePath = path.join(docsPath, fileName);
// Ensure docs directory exists
try {
await fsPromises.mkdir(docsPath, { recursive: true });
} catch (error) {
console.error('Failed to create docs directory:', error);
return res.status(500).json({
error: 'Failed to create directory',
message: error.message
});
}
// Write the PRD file
try {
await fsPromises.writeFile(filePath, content, 'utf8');
// Get file stats
const stats = await fsPromises.stat(filePath);
res.json({
projectName,
projectPath,
fileName,
filePath: path.relative(projectPath, filePath),
size: stats.size,
created: stats.birthtime.toISOString(),
modified: stats.mtime.toISOString(),
message: 'PRD file saved successfully',
timestamp: new Date().toISOString()
});
} catch (writeError) {
console.error('Failed to write PRD file:', writeError);
return res.status(500).json({
error: 'Failed to write PRD file',
message: writeError.message
});
}
} catch (error) {
console.error('PRD create/update error:', error);
res.status(500).json({
error: 'Failed to create/update PRD file',
message: error.message
});
}
});
/**
* GET /api/taskmaster/prd/:projectName/:fileName
* Get content of a specific PRD file
*/
router.get('/prd/:projectName/:fileName', async (req, res) => {
try {
const { projectName, fileName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const filePath = path.join(projectPath, '.taskmaster', 'docs', fileName);
// Check if file exists
try {
await fsPromises.access(filePath, fs.constants.R_OK);
} catch (error) {
return res.status(404).json({
error: 'PRD file not found',
message: `File "${fileName}" does not exist`
});
}
// Read file content
try {
const content = await fsPromises.readFile(filePath, 'utf8');
const stats = await fsPromises.stat(filePath);
res.json({
projectName,
projectPath,
fileName,
filePath: path.relative(projectPath, filePath),
content,
size: stats.size,
created: stats.birthtime.toISOString(),
modified: stats.mtime.toISOString(),
timestamp: new Date().toISOString()
});
} catch (readError) {
console.error('Failed to read PRD file:', readError);
return res.status(500).json({
error: 'Failed to read PRD file',
message: readError.message
});
}
} catch (error) {
console.error('PRD read error:', error);
res.status(500).json({
error: 'Failed to read PRD file',
message: error.message
});
}
});
/**
* DELETE /api/taskmaster/prd/:projectName/:fileName
* Delete a specific PRD file
*/
router.delete('/prd/:projectName/:fileName', async (req, res) => {
try {
const { projectName, fileName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const filePath = path.join(projectPath, '.taskmaster', 'docs', fileName);
// Check if file exists
try {
await fsPromises.access(filePath, fs.constants.F_OK);
} catch (error) {
return res.status(404).json({
error: 'PRD file not found',
message: `File "${fileName}" does not exist`
});
}
// Delete the file
try {
await fsPromises.unlink(filePath);
res.json({
projectName,
projectPath,
fileName,
message: 'PRD file deleted successfully',
timestamp: new Date().toISOString()
});
} catch (deleteError) {
console.error('Failed to delete PRD file:', deleteError);
return res.status(500).json({
error: 'Failed to delete PRD file',
message: deleteError.message
});
}
} catch (error) {
console.error('PRD delete error:', error);
res.status(500).json({
error: 'Failed to delete PRD file',
message: error.message
});
}
});
/**
* POST /api/taskmaster/init/:projectName
* Initialize TaskMaster in a project
*/
router.post('/init/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
// Check if TaskMaster is already initialized
const taskMasterPath = path.join(projectPath, '.taskmaster');
try {
await fsPromises.access(taskMasterPath, fs.constants.F_OK);
return res.status(400).json({
error: 'TaskMaster already initialized',
message: 'TaskMaster is already configured for this project'
});
} catch (error) {
// Directory doesn't exist, we can proceed
}
// Run taskmaster init command
const initProcess = spawn('npx', ['task-master', 'init'], {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
initProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
initProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
initProcess.on('close', (code) => {
if (code === 0) {
// Broadcast TaskMaster project update via WebSocket
if (req.app.locals.wss) {
broadcastTaskMasterProjectUpdate(
req.app.locals.wss,
projectName,
{ hasTaskmaster: true, status: 'initialized' }
);
}
res.json({
projectName,
projectPath,
message: 'TaskMaster initialized successfully',
output: stdout,
timestamp: new Date().toISOString()
});
} else {
console.error('TaskMaster init failed:', stderr);
res.status(500).json({
error: 'Failed to initialize TaskMaster',
message: stderr || stdout,
code
});
}
});
// Send 'yes' responses to automated prompts
initProcess.stdin.write('yes\n');
initProcess.stdin.end();
} catch (error) {
console.error('TaskMaster init error:', error);
res.status(500).json({
error: 'Failed to initialize TaskMaster',
message: error.message
});
}
});
/**
* POST /api/taskmaster/add-task/:projectName
* Add a new task to the project
*/
router.post('/add-task/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
const { prompt, title, description, priority = 'medium', dependencies } = req.body;
if (!prompt && (!title || !description)) {
return res.status(400).json({
error: 'Missing required parameters',
message: 'Either "prompt" or both "title" and "description" are required'
});
}
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
// Build the task-master add-task command
const args = ['task-master-ai', 'add-task'];
if (prompt) {
args.push('--prompt', prompt);
args.push('--research'); // Use research for AI-generated tasks
} else {
args.push('--prompt', `Create a task titled "${title}" with description: ${description}`);
}
if (priority) {
args.push('--priority', priority);
}
if (dependencies) {
args.push('--dependencies', dependencies);
}
// Run task-master add-task command
const addTaskProcess = spawn('npx', args, {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
addTaskProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
addTaskProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
addTaskProcess.on('close', (code) => {
console.log('Add task process completed with code:', code);
console.log('Stdout:', stdout);
console.log('Stderr:', stderr);
if (code === 0) {
// Broadcast task update via WebSocket
if (req.app.locals.wss) {
broadcastTaskMasterTasksUpdate(
req.app.locals.wss,
projectName
);
}
res.json({
projectName,
projectPath,
message: 'Task added successfully',
output: stdout,
timestamp: new Date().toISOString()
});
} else {
console.error('Add task failed:', stderr);
res.status(500).json({
error: 'Failed to add task',
message: stderr || stdout,
code
});
}
});
addTaskProcess.stdin.end();
} catch (error) {
console.error('Add task error:', error);
res.status(500).json({
error: 'Failed to add task',
message: error.message
});
}
});
/**
* PUT /api/taskmaster/update-task/:projectName/:taskId
* Update a specific task using TaskMaster CLI
*/
router.put('/update-task/:projectName/:taskId', async (req, res) => {
try {
const { projectName, taskId } = req.params;
const { title, description, status, priority, details } = req.body;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
// If only updating status, use set-status command
if (status && Object.keys(req.body).length === 1) {
const setStatusProcess = spawn('npx', ['task-master-ai', 'set-status', `--id=${taskId}`, `--status=${status}`], {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
setStatusProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
setStatusProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
setStatusProcess.on('close', (code) => {
if (code === 0) {
// Broadcast task update via WebSocket
if (req.app.locals.wss) {
broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectName);
}
res.json({
projectName,
projectPath,
taskId,
message: 'Task status updated successfully',
output: stdout,
timestamp: new Date().toISOString()
});
} else {
console.error('Set task status failed:', stderr);
res.status(500).json({
error: 'Failed to update task status',
message: stderr || stdout,
code
});
}
});
setStatusProcess.stdin.end();
} else {
// For other updates, use update-task command with a prompt describing the changes
const updates = [];
if (title) updates.push(`title: "${title}"`);
if (description) updates.push(`description: "${description}"`);
if (priority) updates.push(`priority: "${priority}"`);
if (details) updates.push(`details: "${details}"`);
const prompt = `Update task with the following changes: ${updates.join(', ')}`;
const updateProcess = spawn('npx', ['task-master-ai', 'update-task', `--id=${taskId}`, `--prompt=${prompt}`], {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
updateProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
updateProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
updateProcess.on('close', (code) => {
if (code === 0) {
// Broadcast task update via WebSocket
if (req.app.locals.wss) {
broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectName);
}
res.json({
projectName,
projectPath,
taskId,
message: 'Task updated successfully',
output: stdout,
timestamp: new Date().toISOString()
});
} else {
console.error('Update task failed:', stderr);
res.status(500).json({
error: 'Failed to update task',
message: stderr || stdout,
code
});
}
});
updateProcess.stdin.end();
}
} catch (error) {
console.error('Update task error:', error);
res.status(500).json({
error: 'Failed to update task',
message: error.message
});
}
});
/**
* POST /api/taskmaster/parse-prd/:projectName
* Parse a PRD file to generate tasks
*/
router.post('/parse-prd/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
const { fileName = 'prd.txt', numTasks, append = false } = req.body;
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
const prdPath = path.join(projectPath, '.taskmaster', 'docs', fileName);
// Check if PRD file exists
try {
await fsPromises.access(prdPath, fs.constants.F_OK);
} catch (error) {
return res.status(404).json({
error: 'PRD file not found',
message: `File "${fileName}" does not exist in .taskmaster/docs/`
});
}
// Build the command args
const args = ['task-master-ai', 'parse-prd', prdPath];
if (numTasks) {
args.push('--num-tasks', numTasks.toString());
}
if (append) {
args.push('--append');
}
args.push('--research'); // Use research for better PRD parsing
// Run task-master parse-prd command
const parsePRDProcess = spawn('npx', args, {
cwd: projectPath,
stdio: ['pipe', 'pipe', 'pipe']
});
let stdout = '';
let stderr = '';
parsePRDProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
parsePRDProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
parsePRDProcess.on('close', (code) => {
if (code === 0) {
// Broadcast task update via WebSocket
if (req.app.locals.wss) {
broadcastTaskMasterTasksUpdate(
req.app.locals.wss,
projectName
);
}
res.json({
projectName,
projectPath,
prdFile: fileName,
message: 'PRD parsed and tasks generated successfully',
output: stdout,
timestamp: new Date().toISOString()
});
} else {
console.error('Parse PRD failed:', stderr);
res.status(500).json({
error: 'Failed to parse PRD',
message: stderr || stdout,
code
});
}
});
parsePRDProcess.stdin.end();
} catch (error) {
console.error('Parse PRD error:', error);
res.status(500).json({
error: 'Failed to parse PRD',
message: error.message
});
}
});
/**
* GET /api/taskmaster/prd-templates
* Get available PRD templates
*/
router.get('/prd-templates', async (req, res) => {
try {
// Return built-in templates
const templates = [
{
id: 'web-app',
name: 'Web Application',
description: 'Template for web application projects with frontend and backend components',
category: 'web',
content: `# Product Requirements Document - Web Application
## Overview
**Product Name:** [Your App Name]
**Version:** 1.0
**Date:** ${new Date().toISOString().split('T')[0]}
**Author:** [Your Name]
## Executive Summary
Brief description of what this web application will do and why it's needed.
## Product Goals
- Goal 1: [Specific measurable goal]
- Goal 2: [Specific measurable goal]
- Goal 3: [Specific measurable goal]
## User Stories
### Core Features
1. **User Registration & Authentication**
- As a user, I want to create an account so I can access personalized features
- As a user, I want to log in securely so my data is protected
- As a user, I want to reset my password if I forget it
2. **Main Application Features**
- As a user, I want to [core feature 1] so I can [benefit]
- As a user, I want to [core feature 2] so I can [benefit]
- As a user, I want to [core feature 3] so I can [benefit]
3. **User Interface**
- As a user, I want a responsive design so I can use the app on any device
- As a user, I want intuitive navigation so I can easily find features
## Technical Requirements
### Frontend
- Framework: React/Vue/Angular or vanilla JavaScript
- Styling: CSS framework (Tailwind, Bootstrap, etc.)
- State Management: Redux/Vuex/Context API
- Build Tools: Webpack/Vite
- Testing: Jest/Vitest for unit tests
### Backend
- Runtime: Node.js/Python/Java
- Database: PostgreSQL/MySQL/MongoDB
- API: RESTful API or GraphQL
- Authentication: JWT tokens
- Testing: Integration and unit tests
### Infrastructure
- Hosting: Cloud provider (AWS, Azure, GCP)
- CI/CD: GitHub Actions/GitLab CI
- Monitoring: Application monitoring tools
- Security: HTTPS, input validation, rate limiting
## Success Metrics
- User engagement metrics
- Performance benchmarks (load time < 2s)
- Error rates < 1%
- User satisfaction scores
## Timeline
- Phase 1: Core functionality (4-6 weeks)
- Phase 2: Advanced features (2-4 weeks)
- Phase 3: Polish and launch (2 weeks)
## Constraints & Assumptions
- Budget constraints
- Technical limitations
- Team size and expertise
- Timeline constraints`
},
{
id: 'api',
name: 'REST API',
description: 'Template for REST API development projects',
category: 'backend',
content: `# Product Requirements Document - REST API
## Overview
**API Name:** [Your API Name]
**Version:** v1.0
**Date:** ${new Date().toISOString().split('T')[0]}
**Author:** [Your Name]
## Executive Summary
Description of the API's purpose, target users, and primary use cases.
## API Goals
- Goal 1: Provide secure data access
- Goal 2: Ensure scalable architecture
- Goal 3: Maintain high availability (99.9% uptime)
## Functional Requirements
### Core Endpoints
1. **Authentication Endpoints**
- POST /api/auth/login - User authentication
- POST /api/auth/logout - User logout
- POST /api/auth/refresh - Token refresh
- POST /api/auth/register - User registration
2. **Data Management Endpoints**
- GET /api/resources - List resources with pagination
- GET /api/resources/{id} - Get specific resource
- POST /api/resources - Create new resource
- PUT /api/resources/{id} - Update existing resource
- DELETE /api/resources/{id} - Delete resource
3. **Administrative Endpoints**
- GET /api/admin/users - Manage users (admin only)
- GET /api/admin/analytics - System analytics
- POST /api/admin/backup - Trigger system backup
## Technical Requirements
### API Design
- RESTful architecture following OpenAPI 3.0 specification
- JSON request/response format
- Consistent error response format
- API versioning strategy
### Authentication & Security
- JWT token-based authentication
- Role-based access control (RBAC)
- Rate limiting (100 requests/minute per user)
- Input validation and sanitization
- HTTPS enforcement
### Database
- Database type: [PostgreSQL/MongoDB/MySQL]
- Connection pooling
- Database migrations
- Backup and recovery procedures
### Performance Requirements
- Response time: < 200ms for 95% of requests
- Throughput: 1000+ requests/second
- Concurrent users: 10,000+
- Database query optimization
### Documentation
- Auto-generated API documentation (Swagger/OpenAPI)
- Code examples for common use cases
- SDK development for major languages
- Postman collection for testing
## Error Handling
- Standardized error codes and messages
- Proper HTTP status codes
- Detailed error logging
- Graceful degradation strategies
## Testing Strategy
- Unit tests (80%+ coverage)
- Integration tests for all endpoints
- Load testing and performance testing
- Security testing (OWASP compliance)
## Monitoring & Logging
- Application performance monitoring
- Error tracking and alerting
- Access logs and audit trails
- Health check endpoints
## Deployment
- Containerized deployment (Docker)
- CI/CD pipeline setup
- Environment management (dev, staging, prod)
- Blue-green deployment strategy
## Success Metrics
- API uptime > 99.9%
- Average response time < 200ms
- Zero critical security vulnerabilities
- Developer adoption metrics`
},
{
id: 'mobile-app',
name: 'Mobile Application',
description: 'Template for mobile app development projects (iOS/Android)',
category: 'mobile',
content: `# Product Requirements Document - Mobile Application
## Overview
**App Name:** [Your App Name]
**Platform:** iOS / Android / Cross-platform
**Version:** 1.0
**Date:** ${new Date().toISOString().split('T')[0]}
**Author:** [Your Name]
## Executive Summary
Brief description of the mobile app's purpose, target audience, and key value proposition.
## Product Goals
- Goal 1: [Specific user engagement goal]
- Goal 2: [Specific functionality goal]
- Goal 3: [Specific performance goal]
## User Stories
### Core Features
1. **Onboarding & Authentication**
- As a new user, I want a simple onboarding process
- As a user, I want to sign up with email or social media
- As a user, I want biometric authentication for security
2. **Main App Features**
- As a user, I want [core feature 1] accessible from home screen
- As a user, I want [core feature 2] to work offline
- As a user, I want to sync data across devices
3. **User Experience**
- As a user, I want intuitive navigation patterns
- As a user, I want fast loading times
- As a user, I want accessibility features
## Technical Requirements
### Mobile Development
- **Cross-platform:** React Native / Flutter / Xamarin
- **Native:** Swift (iOS) / Kotlin (Android)
- **State Management:** Redux / MobX / Provider
- **Navigation:** React Navigation / Flutter Navigation
### Backend Integration
- REST API or GraphQL integration
- Real-time features (WebSockets/Push notifications)
- Offline data synchronization
- Background processing
### Device Features
- Camera and photo library access
- GPS location services
- Push notifications
- Biometric authentication
- Device storage
### Performance Requirements
- App launch time < 3 seconds
- Screen transition animations < 300ms
- Memory usage optimization
- Battery usage optimization
## Platform-Specific Considerations
### iOS Requirements
- iOS 13.0+ minimum version
- App Store guidelines compliance
- iOS design guidelines (Human Interface Guidelines)
- TestFlight beta testing
### Android Requirements
- Android 8.0+ (API level 26) minimum
- Google Play Store guidelines
- Material Design guidelines
- Google Play Console testing
## User Interface Design
- Responsive design for different screen sizes
- Dark mode support
- Accessibility compliance (WCAG 2.1)
- Consistent design system
## Security & Privacy
- Secure data storage (Keychain/Keystore)
- API communication encryption
- Privacy policy compliance (GDPR/CCPA)
- App security best practices
## Testing Strategy
- Unit testing (80%+ coverage)
- UI/E2E testing (Detox/Appium)
- Device testing on multiple screen sizes
- Performance testing
- Security testing
## App Store Deployment
- App store optimization (ASO)
- App icons and screenshots
- Store listing content
- Release management strategy
## Analytics & Monitoring
- User analytics (Firebase/Analytics)
- Crash reporting (Crashlytics/Sentry)
- Performance monitoring
- User feedback collection
## Success Metrics
- App store ratings > 4.0
- User retention rates
- Daily/Monthly active users
- App performance metrics
- Conversion rates`
},
{
id: 'data-analysis',
name: 'Data Analysis Project',
description: 'Template for data analysis and visualization projects',
category: 'data',
content: `# Product Requirements Document - Data Analysis Project
## Overview
**Project Name:** [Your Analysis Project]
**Analysis Type:** [Descriptive/Predictive/Prescriptive]
**Date:** ${new Date().toISOString().split('T')[0]}
**Author:** [Your Name]
## Executive Summary
Description of the business problem, data sources, and expected insights.
## Project Goals
- Goal 1: [Specific business question to answer]
- Goal 2: [Specific prediction to make]
- Goal 3: [Specific recommendation to provide]
## Business Requirements
### Key Questions
1. What patterns exist in the current data?
2. What factors influence [target variable]?
3. What predictions can be made for [future outcome]?
4. What recommendations can improve [business metric]?
### Success Criteria
- Actionable insights for stakeholders
- Statistical significance in findings
- Reproducible analysis pipeline
- Clear visualization and reporting
## Data Requirements
### Data Sources
1. **Primary Data**
- Source: [Database/API/Files]
- Format: [CSV/JSON/SQL]
- Size: [Volume estimate]
- Update frequency: [Real-time/Daily/Monthly]
2. **External Data**
- Third-party APIs
- Public datasets
- Market research data
### Data Quality Requirements
- Data completeness (< 5% missing values)
- Data accuracy validation
- Data consistency checks
- Historical data availability
## Technical Requirements
### Data Pipeline
- Data extraction and ingestion
- Data cleaning and preprocessing
- Data transformation and feature engineering
- Data validation and quality checks
### Analysis Tools
- **Programming:** Python/R/SQL
- **Libraries:** pandas, numpy, scikit-learn, matplotlib
- **Visualization:** Tableau, PowerBI, or custom dashboards
- **Version Control:** Git for code and DVC for data
### Computing Resources
- Local development environment
- Cloud computing (AWS/GCP/Azure) if needed
- Database access and permissions
- Storage requirements
## Analysis Methodology
### Data Exploration
1. Descriptive statistics and data profiling
2. Data visualization and pattern identification
3. Correlation analysis
4. Outlier detection and handling
### Statistical Analysis
1. Hypothesis formulation
2. Statistical testing
3. Confidence intervals
4. Effect size calculations
### Machine Learning (if applicable)
1. Feature selection and engineering
2. Model selection and training
3. Cross-validation and evaluation
4. Model interpretation and explainability
## Deliverables
### Reports
- Executive summary for stakeholders
- Technical analysis report
- Data quality report
- Methodology documentation
### Visualizations
- Interactive dashboards
- Static charts and graphs
- Data story presentations
- Key findings infographics
### Code & Documentation
- Reproducible analysis scripts
- Data pipeline code
- Documentation and comments
- Testing and validation code
## Timeline
- Phase 1: Data collection and exploration (2 weeks)
- Phase 2: Analysis and modeling (3 weeks)
- Phase 3: Reporting and visualization (1 week)
- Phase 4: Stakeholder presentation (1 week)
## Risks & Assumptions
- Data availability and quality risks
- Technical complexity assumptions
- Resource and timeline constraints
- Stakeholder engagement assumptions
## Success Metrics
- Stakeholder satisfaction with insights
- Accuracy of predictions (if applicable)
- Business impact of recommendations
- Reproducibility of results`
}
];
res.json({
templates,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('PRD templates error:', error);
res.status(500).json({
error: 'Failed to get PRD templates',
message: error.message
});
}
});
/**
* POST /api/taskmaster/apply-template/:projectName
* Apply a PRD template to create a new PRD file
*/
router.post('/apply-template/:projectName', async (req, res) => {
try {
const { projectName } = req.params;
const { templateId, fileName = 'prd.txt', customizations = {} } = req.body;
if (!templateId) {
return res.status(400).json({
error: 'Missing required parameter',
message: 'templateId is required'
});
}
// Get project path
let projectPath;
try {
projectPath = await extractProjectDirectory(projectName);
} catch (error) {
return res.status(404).json({
error: 'Project not found',
message: `Project "${projectName}" does not exist`
});
}
// Get the template content (this would normally fetch from the templates list)
const templates = await getAvailableTemplates();
const template = templates.find(t => t.id === templateId);
if (!template) {
return res.status(404).json({
error: 'Template not found',
message: `Template "${templateId}" does not exist`
});
}
// Apply customizations to template content
let content = template.content;
// Replace placeholders with customizations
for (const [key, value] of Object.entries(customizations)) {
const placeholder = `[${key}]`;
content = content.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), value);
}
// Ensure .taskmaster/docs directory exists
const docsDir = path.join(projectPath, '.taskmaster', 'docs');
try {
await fsPromises.mkdir(docsDir, { recursive: true });
} catch (error) {
console.error('Failed to create docs directory:', error);
}
const filePath = path.join(docsDir, fileName);
// Write the template content to the file
try {
await fsPromises.writeFile(filePath, content, 'utf8');
res.json({
projectName,
projectPath,
templateId,
templateName: template.name,
fileName,
filePath: filePath,
message: 'PRD template applied successfully',
timestamp: new Date().toISOString()
});
} catch (writeError) {
console.error('Failed to write PRD template:', writeError);
return res.status(500).json({
error: 'Failed to write PRD template',
message: writeError.message
});
}
} catch (error) {
console.error('Apply template error:', error);
res.status(500).json({
error: 'Failed to apply PRD template',
message: error.message
});
}
});
// Helper function to get available templates
async function getAvailableTemplates() {
// This could be extended to read from files or database
return [
{
id: 'web-app',
name: 'Web Application',
description: 'Template for web application projects',
category: 'web',
content: `# Product Requirements Document - Web Application
## Overview
**Product Name:** [Your App Name]
**Version:** 1.0
**Date:** ${new Date().toISOString().split('T')[0]}
**Author:** [Your Name]
## Executive Summary
Brief description of what this web application will do and why it's needed.
## User Stories
1. As a user, I want [feature] so I can [benefit]
2. As a user, I want [feature] so I can [benefit]
3. As a user, I want [feature] so I can [benefit]
## Technical Requirements
- Frontend framework
- Backend services
- Database requirements
- Security considerations
## Success Metrics
- User engagement metrics
- Performance benchmarks
- Business objectives`
},
// Add other templates here if needed
];
}
export default router;