| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963 |
- /**
- * 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()
- };
- 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
- // Use localhost to bypass proxy for internal server-to-server calls
- const tasksResponse = await fetch(`http://localhost:${process.env.SERVER_PORT || process.env.PORT || '3001'}/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;
|