taskmaster.js 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963
  1. /**
  2. * TASKMASTER API ROUTES
  3. * ====================
  4. *
  5. * This module provides API endpoints for TaskMaster integration including:
  6. * - .taskmaster folder detection in project directories
  7. * - MCP server configuration detection
  8. * - TaskMaster state and metadata management
  9. */
  10. import express from 'express';
  11. import fs from 'fs';
  12. import path from 'path';
  13. import { promises as fsPromises } from 'fs';
  14. import { spawn } from 'child_process';
  15. import { fileURLToPath } from 'url';
  16. import { dirname } from 'path';
  17. import os from 'os';
  18. import { extractProjectDirectory } from '../projects.js';
  19. import { detectTaskMasterMCPServer } from '../utils/mcp-detector.js';
  20. import { broadcastTaskMasterProjectUpdate, broadcastTaskMasterTasksUpdate } from '../utils/taskmaster-websocket.js';
  21. const __filename = fileURLToPath(import.meta.url);
  22. const __dirname = dirname(__filename);
  23. const router = express.Router();
  24. /**
  25. * Check if TaskMaster CLI is installed globally
  26. * @returns {Promise<Object>} Installation status result
  27. */
  28. async function checkTaskMasterInstallation() {
  29. return new Promise((resolve) => {
  30. // Check if task-master command is available
  31. const child = spawn('which', ['task-master'], {
  32. stdio: ['ignore', 'pipe', 'pipe'],
  33. shell: true
  34. });
  35. let output = '';
  36. let errorOutput = '';
  37. child.stdout.on('data', (data) => {
  38. output += data.toString();
  39. });
  40. child.stderr.on('data', (data) => {
  41. errorOutput += data.toString();
  42. });
  43. child.on('close', (code) => {
  44. if (code === 0 && output.trim()) {
  45. // TaskMaster is installed, get version
  46. const versionChild = spawn('task-master', ['--version'], {
  47. stdio: ['ignore', 'pipe', 'pipe'],
  48. shell: true
  49. });
  50. let versionOutput = '';
  51. versionChild.stdout.on('data', (data) => {
  52. versionOutput += data.toString();
  53. });
  54. versionChild.on('close', (versionCode) => {
  55. resolve({
  56. isInstalled: true,
  57. installPath: output.trim(),
  58. version: versionCode === 0 ? versionOutput.trim() : 'unknown',
  59. reason: null
  60. });
  61. });
  62. versionChild.on('error', () => {
  63. resolve({
  64. isInstalled: true,
  65. installPath: output.trim(),
  66. version: 'unknown',
  67. reason: null
  68. });
  69. });
  70. } else {
  71. resolve({
  72. isInstalled: false,
  73. installPath: null,
  74. version: null,
  75. reason: 'TaskMaster CLI not found in PATH'
  76. });
  77. }
  78. });
  79. child.on('error', (error) => {
  80. resolve({
  81. isInstalled: false,
  82. installPath: null,
  83. version: null,
  84. reason: `Error checking installation: ${error.message}`
  85. });
  86. });
  87. });
  88. }
  89. /**
  90. * Detect .taskmaster folder presence in a given project directory
  91. * @param {string} projectPath - Absolute path to project directory
  92. * @returns {Promise<Object>} Detection result with status and metadata
  93. */
  94. async function detectTaskMasterFolder(projectPath) {
  95. try {
  96. const taskMasterPath = path.join(projectPath, '.taskmaster');
  97. // Check if .taskmaster directory exists
  98. try {
  99. const stats = await fsPromises.stat(taskMasterPath);
  100. if (!stats.isDirectory()) {
  101. return {
  102. hasTaskmaster: false,
  103. reason: '.taskmaster exists but is not a directory'
  104. };
  105. }
  106. } catch (error) {
  107. if (error.code === 'ENOENT') {
  108. return {
  109. hasTaskmaster: false,
  110. reason: '.taskmaster directory not found'
  111. };
  112. }
  113. throw error;
  114. }
  115. // Check for key TaskMaster files
  116. const keyFiles = [
  117. 'tasks/tasks.json',
  118. 'config.json'
  119. ];
  120. const fileStatus = {};
  121. let hasEssentialFiles = true;
  122. for (const file of keyFiles) {
  123. const filePath = path.join(taskMasterPath, file);
  124. try {
  125. await fsPromises.access(filePath, fs.constants.R_OK);
  126. fileStatus[file] = true;
  127. } catch (error) {
  128. fileStatus[file] = false;
  129. if (file === 'tasks/tasks.json') {
  130. hasEssentialFiles = false;
  131. }
  132. }
  133. }
  134. // Parse tasks.json if it exists for metadata
  135. let taskMetadata = null;
  136. if (fileStatus['tasks/tasks.json']) {
  137. try {
  138. const tasksPath = path.join(taskMasterPath, 'tasks/tasks.json');
  139. const tasksContent = await fsPromises.readFile(tasksPath, 'utf8');
  140. const tasksData = JSON.parse(tasksContent);
  141. // Handle both tagged and legacy formats
  142. let tasks = [];
  143. if (tasksData.tasks) {
  144. // Legacy format
  145. tasks = tasksData.tasks;
  146. } else {
  147. // Tagged format - get tasks from all tags
  148. Object.values(tasksData).forEach(tagData => {
  149. if (tagData.tasks) {
  150. tasks = tasks.concat(tagData.tasks);
  151. }
  152. });
  153. }
  154. // Calculate task statistics
  155. const stats = tasks.reduce((acc, task) => {
  156. acc.total++;
  157. acc[task.status] = (acc[task.status] || 0) + 1;
  158. // Count subtasks
  159. if (task.subtasks) {
  160. task.subtasks.forEach(subtask => {
  161. acc.subtotalTasks++;
  162. acc.subtasks = acc.subtasks || {};
  163. acc.subtasks[subtask.status] = (acc.subtasks[subtask.status] || 0) + 1;
  164. });
  165. }
  166. return acc;
  167. }, {
  168. total: 0,
  169. subtotalTasks: 0,
  170. pending: 0,
  171. 'in-progress': 0,
  172. done: 0,
  173. review: 0,
  174. deferred: 0,
  175. cancelled: 0,
  176. subtasks: {}
  177. });
  178. taskMetadata = {
  179. taskCount: stats.total,
  180. subtaskCount: stats.subtotalTasks,
  181. completed: stats.done || 0,
  182. pending: stats.pending || 0,
  183. inProgress: stats['in-progress'] || 0,
  184. review: stats.review || 0,
  185. completionPercentage: stats.total > 0 ? Math.round((stats.done / stats.total) * 100) : 0,
  186. lastModified: (await fsPromises.stat(tasksPath)).mtime.toISOString()
  187. };
  188. } catch (parseError) {
  189. console.warn('Failed to parse tasks.json:', parseError.message);
  190. taskMetadata = { error: 'Failed to parse tasks.json' };
  191. }
  192. }
  193. return {
  194. hasTaskmaster: true,
  195. hasEssentialFiles,
  196. files: fileStatus,
  197. metadata: taskMetadata,
  198. path: taskMasterPath
  199. };
  200. } catch (error) {
  201. console.error('Error detecting TaskMaster folder:', error);
  202. return {
  203. hasTaskmaster: false,
  204. reason: `Error checking directory: ${error.message}`
  205. };
  206. }
  207. }
  208. // MCP detection is now handled by the centralized utility
  209. // API Routes
  210. /**
  211. * GET /api/taskmaster/installation-status
  212. * Check if TaskMaster CLI is installed on the system
  213. */
  214. router.get('/installation-status', async (req, res) => {
  215. try {
  216. const installationStatus = await checkTaskMasterInstallation();
  217. // Also check for MCP server configuration
  218. const mcpStatus = await detectTaskMasterMCPServer();
  219. res.json({
  220. success: true,
  221. installation: installationStatus,
  222. mcpServer: mcpStatus,
  223. isReady: installationStatus.isInstalled && mcpStatus.hasMCPServer
  224. });
  225. } catch (error) {
  226. console.error('Error checking TaskMaster installation:', error);
  227. res.status(500).json({
  228. success: false,
  229. error: 'Failed to check TaskMaster installation status',
  230. installation: {
  231. isInstalled: false,
  232. reason: `Server error: ${error.message}`
  233. },
  234. mcpServer: {
  235. hasMCPServer: false,
  236. reason: `Server error: ${error.message}`
  237. },
  238. isReady: false
  239. });
  240. }
  241. });
  242. /**
  243. * GET /api/taskmaster/detect/:projectName
  244. * Detect TaskMaster configuration for a specific project
  245. */
  246. router.get('/detect/:projectName', async (req, res) => {
  247. try {
  248. const { projectName } = req.params;
  249. // Use the existing extractProjectDirectory function to get actual project path
  250. let projectPath;
  251. try {
  252. projectPath = await extractProjectDirectory(projectName);
  253. } catch (error) {
  254. console.error('Error extracting project directory:', error);
  255. return res.status(404).json({
  256. error: 'Project path not found',
  257. projectName,
  258. message: error.message
  259. });
  260. }
  261. // Verify the project path exists
  262. try {
  263. await fsPromises.access(projectPath, fs.constants.R_OK);
  264. } catch (error) {
  265. return res.status(404).json({
  266. error: 'Project path not accessible',
  267. projectPath,
  268. projectName,
  269. message: error.message
  270. });
  271. }
  272. // Run detection in parallel
  273. const [taskMasterResult, mcpResult] = await Promise.all([
  274. detectTaskMasterFolder(projectPath),
  275. detectTaskMasterMCPServer()
  276. ]);
  277. // Determine overall status
  278. let status = 'not-configured';
  279. if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
  280. if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
  281. status = 'fully-configured';
  282. } else {
  283. status = 'taskmaster-only';
  284. }
  285. } else if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
  286. status = 'mcp-only';
  287. }
  288. const responseData = {
  289. projectName,
  290. projectPath,
  291. status,
  292. taskmaster: taskMasterResult,
  293. mcp: mcpResult,
  294. timestamp: new Date().toISOString()
  295. };
  296. res.json(responseData);
  297. } catch (error) {
  298. console.error('TaskMaster detection error:', error);
  299. res.status(500).json({
  300. error: 'Failed to detect TaskMaster configuration',
  301. message: error.message
  302. });
  303. }
  304. });
  305. /**
  306. * GET /api/taskmaster/detect-all
  307. * Detect TaskMaster configuration for all known projects
  308. * This endpoint works with the existing projects system
  309. */
  310. router.get('/detect-all', async (req, res) => {
  311. try {
  312. // Import getProjects from the projects module
  313. const { getProjects } = await import('../projects.js');
  314. const projects = await getProjects();
  315. // Run detection for all projects in parallel
  316. const detectionPromises = projects.map(async (project) => {
  317. try {
  318. // Use the project's fullPath if available, otherwise extract the directory
  319. let projectPath;
  320. if (project.fullPath) {
  321. projectPath = project.fullPath;
  322. } else {
  323. try {
  324. projectPath = await extractProjectDirectory(project.name);
  325. } catch (error) {
  326. throw new Error(`Failed to extract project directory: ${error.message}`);
  327. }
  328. }
  329. const [taskMasterResult, mcpResult] = await Promise.all([
  330. detectTaskMasterFolder(projectPath),
  331. detectTaskMasterMCPServer()
  332. ]);
  333. // Determine status
  334. let status = 'not-configured';
  335. if (taskMasterResult.hasTaskmaster && taskMasterResult.hasEssentialFiles) {
  336. if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
  337. status = 'fully-configured';
  338. } else {
  339. status = 'taskmaster-only';
  340. }
  341. } else if (mcpResult.hasMCPServer && mcpResult.isConfigured) {
  342. status = 'mcp-only';
  343. }
  344. return {
  345. projectName: project.name,
  346. displayName: project.displayName,
  347. projectPath,
  348. status,
  349. taskmaster: taskMasterResult,
  350. mcp: mcpResult
  351. };
  352. } catch (error) {
  353. return {
  354. projectName: project.name,
  355. displayName: project.displayName,
  356. status: 'error',
  357. error: error.message
  358. };
  359. }
  360. });
  361. const results = await Promise.all(detectionPromises);
  362. res.json({
  363. projects: results,
  364. summary: {
  365. total: results.length,
  366. fullyConfigured: results.filter(p => p.status === 'fully-configured').length,
  367. taskmasterOnly: results.filter(p => p.status === 'taskmaster-only').length,
  368. mcpOnly: results.filter(p => p.status === 'mcp-only').length,
  369. notConfigured: results.filter(p => p.status === 'not-configured').length,
  370. errors: results.filter(p => p.status === 'error').length
  371. },
  372. timestamp: new Date().toISOString()
  373. });
  374. } catch (error) {
  375. console.error('Bulk TaskMaster detection error:', error);
  376. res.status(500).json({
  377. error: 'Failed to detect TaskMaster configuration for projects',
  378. message: error.message
  379. });
  380. }
  381. });
  382. /**
  383. * POST /api/taskmaster/initialize/:projectName
  384. * Initialize TaskMaster in a project (placeholder for future CLI integration)
  385. */
  386. router.post('/initialize/:projectName', async (req, res) => {
  387. try {
  388. const { projectName } = req.params;
  389. const { rules } = req.body; // Optional rule profiles
  390. // This will be implemented in a later subtask with CLI integration
  391. res.status(501).json({
  392. error: 'TaskMaster initialization not yet implemented',
  393. message: 'This endpoint will execute task-master init via CLI in a future update',
  394. projectName,
  395. rules
  396. });
  397. } catch (error) {
  398. console.error('TaskMaster initialization error:', error);
  399. res.status(500).json({
  400. error: 'Failed to initialize TaskMaster',
  401. message: error.message
  402. });
  403. }
  404. });
  405. /**
  406. * GET /api/taskmaster/next/:projectName
  407. * Get the next recommended task using task-master CLI
  408. */
  409. router.get('/next/:projectName', async (req, res) => {
  410. try {
  411. const { projectName } = req.params;
  412. // Get project path
  413. let projectPath;
  414. try {
  415. projectPath = await extractProjectDirectory(projectName);
  416. } catch (error) {
  417. return res.status(404).json({
  418. error: 'Project not found',
  419. message: `Project "${projectName}" does not exist`
  420. });
  421. }
  422. // Try to execute task-master next command
  423. try {
  424. const { spawn } = await import('child_process');
  425. const nextTaskCommand = spawn('task-master', ['next'], {
  426. cwd: projectPath,
  427. stdio: ['pipe', 'pipe', 'pipe']
  428. });
  429. let stdout = '';
  430. let stderr = '';
  431. nextTaskCommand.stdout.on('data', (data) => {
  432. stdout += data.toString();
  433. });
  434. nextTaskCommand.stderr.on('data', (data) => {
  435. stderr += data.toString();
  436. });
  437. await new Promise((resolve, reject) => {
  438. nextTaskCommand.on('close', (code) => {
  439. if (code === 0) {
  440. resolve();
  441. } else {
  442. reject(new Error(`task-master next failed with code ${code}: ${stderr}`));
  443. }
  444. });
  445. nextTaskCommand.on('error', (error) => {
  446. reject(error);
  447. });
  448. });
  449. // Parse the output - task-master next usually returns JSON
  450. let nextTaskData = null;
  451. if (stdout.trim()) {
  452. try {
  453. nextTaskData = JSON.parse(stdout);
  454. } catch (parseError) {
  455. // If not JSON, treat as plain text
  456. nextTaskData = { message: stdout.trim() };
  457. }
  458. }
  459. res.json({
  460. projectName,
  461. projectPath,
  462. nextTask: nextTaskData,
  463. timestamp: new Date().toISOString()
  464. });
  465. } catch (cliError) {
  466. console.warn('Failed to execute task-master CLI:', cliError.message);
  467. // Fallback to loading tasks and finding next one locally
  468. // Use localhost to bypass proxy for internal server-to-server calls
  469. const tasksResponse = await fetch(`http://localhost:${process.env.SERVER_PORT || process.env.PORT || '3001'}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, {
  470. headers: {
  471. 'Authorization': req.headers.authorization
  472. }
  473. });
  474. if (tasksResponse.ok) {
  475. const tasksData = await tasksResponse.json();
  476. const nextTask = tasksData.tasks?.find(task =>
  477. task.status === 'pending' || task.status === 'in-progress'
  478. ) || null;
  479. res.json({
  480. projectName,
  481. projectPath,
  482. nextTask,
  483. fallback: true,
  484. message: 'Used fallback method (CLI not available)',
  485. timestamp: new Date().toISOString()
  486. });
  487. } else {
  488. throw new Error('Failed to load tasks via fallback method');
  489. }
  490. }
  491. } catch (error) {
  492. console.error('TaskMaster next task error:', error);
  493. res.status(500).json({
  494. error: 'Failed to get next task',
  495. message: error.message
  496. });
  497. }
  498. });
  499. /**
  500. * GET /api/taskmaster/tasks/:projectName
  501. * Load actual tasks from .taskmaster/tasks/tasks.json
  502. */
  503. router.get('/tasks/:projectName', async (req, res) => {
  504. try {
  505. const { projectName } = req.params;
  506. // Get project path
  507. let projectPath;
  508. try {
  509. projectPath = await extractProjectDirectory(projectName);
  510. } catch (error) {
  511. return res.status(404).json({
  512. error: 'Project not found',
  513. message: `Project "${projectName}" does not exist`
  514. });
  515. }
  516. const taskMasterPath = path.join(projectPath, '.taskmaster');
  517. const tasksFilePath = path.join(taskMasterPath, 'tasks', 'tasks.json');
  518. // Check if tasks file exists
  519. try {
  520. await fsPromises.access(tasksFilePath);
  521. } catch (error) {
  522. return res.json({
  523. projectName,
  524. tasks: [],
  525. message: 'No tasks.json file found'
  526. });
  527. }
  528. // Read and parse tasks file
  529. try {
  530. const tasksContent = await fsPromises.readFile(tasksFilePath, 'utf8');
  531. const tasksData = JSON.parse(tasksContent);
  532. let tasks = [];
  533. let currentTag = 'master';
  534. // Handle both tagged and legacy formats
  535. if (Array.isArray(tasksData)) {
  536. // Legacy format
  537. tasks = tasksData;
  538. } else if (tasksData.tasks) {
  539. // Simple format with tasks array
  540. tasks = tasksData.tasks;
  541. } else {
  542. // Tagged format - get tasks from current tag or master
  543. if (tasksData[currentTag] && tasksData[currentTag].tasks) {
  544. tasks = tasksData[currentTag].tasks;
  545. } else if (tasksData.master && tasksData.master.tasks) {
  546. tasks = tasksData.master.tasks;
  547. } else {
  548. // Get tasks from first available tag
  549. const firstTag = Object.keys(tasksData).find(key =>
  550. tasksData[key].tasks && Array.isArray(tasksData[key].tasks)
  551. );
  552. if (firstTag) {
  553. tasks = tasksData[firstTag].tasks;
  554. currentTag = firstTag;
  555. }
  556. }
  557. }
  558. // Transform tasks to ensure all have required fields
  559. const transformedTasks = tasks.map(task => ({
  560. id: task.id,
  561. title: task.title || 'Untitled Task',
  562. description: task.description || '',
  563. status: task.status || 'pending',
  564. priority: task.priority || 'medium',
  565. dependencies: task.dependencies || [],
  566. createdAt: task.createdAt || task.created || new Date().toISOString(),
  567. updatedAt: task.updatedAt || task.updated || new Date().toISOString(),
  568. details: task.details || '',
  569. testStrategy: task.testStrategy || task.test_strategy || '',
  570. subtasks: task.subtasks || []
  571. }));
  572. res.json({
  573. projectName,
  574. projectPath,
  575. tasks: transformedTasks,
  576. currentTag,
  577. totalTasks: transformedTasks.length,
  578. tasksByStatus: {
  579. pending: transformedTasks.filter(t => t.status === 'pending').length,
  580. 'in-progress': transformedTasks.filter(t => t.status === 'in-progress').length,
  581. done: transformedTasks.filter(t => t.status === 'done').length,
  582. review: transformedTasks.filter(t => t.status === 'review').length,
  583. deferred: transformedTasks.filter(t => t.status === 'deferred').length,
  584. cancelled: transformedTasks.filter(t => t.status === 'cancelled').length
  585. },
  586. timestamp: new Date().toISOString()
  587. });
  588. } catch (parseError) {
  589. console.error('Failed to parse tasks.json:', parseError);
  590. return res.status(500).json({
  591. error: 'Failed to parse tasks file',
  592. message: parseError.message
  593. });
  594. }
  595. } catch (error) {
  596. console.error('TaskMaster tasks loading error:', error);
  597. res.status(500).json({
  598. error: 'Failed to load TaskMaster tasks',
  599. message: error.message
  600. });
  601. }
  602. });
  603. /**
  604. * GET /api/taskmaster/prd/:projectName
  605. * List all PRD files in the project's .taskmaster/docs directory
  606. */
  607. router.get('/prd/:projectName', async (req, res) => {
  608. try {
  609. const { projectName } = req.params;
  610. // Get project path
  611. let projectPath;
  612. try {
  613. projectPath = await extractProjectDirectory(projectName);
  614. } catch (error) {
  615. return res.status(404).json({
  616. error: 'Project not found',
  617. message: `Project "${projectName}" does not exist`
  618. });
  619. }
  620. const docsPath = path.join(projectPath, '.taskmaster', 'docs');
  621. // Check if docs directory exists
  622. try {
  623. await fsPromises.access(docsPath, fs.constants.R_OK);
  624. } catch (error) {
  625. return res.json({
  626. projectName,
  627. prdFiles: [],
  628. message: 'No .taskmaster/docs directory found'
  629. });
  630. }
  631. // Read directory and filter for PRD files
  632. try {
  633. const files = await fsPromises.readdir(docsPath);
  634. const prdFiles = [];
  635. for (const file of files) {
  636. const filePath = path.join(docsPath, file);
  637. const stats = await fsPromises.stat(filePath);
  638. if (stats.isFile() && (file.endsWith('.txt') || file.endsWith('.md'))) {
  639. prdFiles.push({
  640. name: file,
  641. path: path.relative(projectPath, filePath),
  642. size: stats.size,
  643. modified: stats.mtime.toISOString(),
  644. created: stats.birthtime.toISOString()
  645. });
  646. }
  647. }
  648. res.json({
  649. projectName,
  650. projectPath,
  651. prdFiles: prdFiles.sort((a, b) => new Date(b.modified) - new Date(a.modified)),
  652. timestamp: new Date().toISOString()
  653. });
  654. } catch (readError) {
  655. console.error('Error reading docs directory:', readError);
  656. return res.status(500).json({
  657. error: 'Failed to read PRD files',
  658. message: readError.message
  659. });
  660. }
  661. } catch (error) {
  662. console.error('PRD list error:', error);
  663. res.status(500).json({
  664. error: 'Failed to list PRD files',
  665. message: error.message
  666. });
  667. }
  668. });
  669. /**
  670. * POST /api/taskmaster/prd/:projectName
  671. * Create or update a PRD file in the project's .taskmaster/docs directory
  672. */
  673. router.post('/prd/:projectName', async (req, res) => {
  674. try {
  675. const { projectName } = req.params;
  676. const { fileName, content } = req.body;
  677. if (!fileName || !content) {
  678. return res.status(400).json({
  679. error: 'Missing required fields',
  680. message: 'fileName and content are required'
  681. });
  682. }
  683. // Validate filename
  684. if (!fileName.match(/^[\w\-. ]+\.(txt|md)$/)) {
  685. return res.status(400).json({
  686. error: 'Invalid filename',
  687. message: 'Filename must end with .txt or .md and contain only alphanumeric characters, spaces, dots, and dashes'
  688. });
  689. }
  690. // Get project path
  691. let projectPath;
  692. try {
  693. projectPath = await extractProjectDirectory(projectName);
  694. } catch (error) {
  695. return res.status(404).json({
  696. error: 'Project not found',
  697. message: `Project "${projectName}" does not exist`
  698. });
  699. }
  700. const docsPath = path.join(projectPath, '.taskmaster', 'docs');
  701. const filePath = path.join(docsPath, fileName);
  702. // Ensure docs directory exists
  703. try {
  704. await fsPromises.mkdir(docsPath, { recursive: true });
  705. } catch (error) {
  706. console.error('Failed to create docs directory:', error);
  707. return res.status(500).json({
  708. error: 'Failed to create directory',
  709. message: error.message
  710. });
  711. }
  712. // Write the PRD file
  713. try {
  714. await fsPromises.writeFile(filePath, content, 'utf8');
  715. // Get file stats
  716. const stats = await fsPromises.stat(filePath);
  717. res.json({
  718. projectName,
  719. projectPath,
  720. fileName,
  721. filePath: path.relative(projectPath, filePath),
  722. size: stats.size,
  723. created: stats.birthtime.toISOString(),
  724. modified: stats.mtime.toISOString(),
  725. message: 'PRD file saved successfully',
  726. timestamp: new Date().toISOString()
  727. });
  728. } catch (writeError) {
  729. console.error('Failed to write PRD file:', writeError);
  730. return res.status(500).json({
  731. error: 'Failed to write PRD file',
  732. message: writeError.message
  733. });
  734. }
  735. } catch (error) {
  736. console.error('PRD create/update error:', error);
  737. res.status(500).json({
  738. error: 'Failed to create/update PRD file',
  739. message: error.message
  740. });
  741. }
  742. });
  743. /**
  744. * GET /api/taskmaster/prd/:projectName/:fileName
  745. * Get content of a specific PRD file
  746. */
  747. router.get('/prd/:projectName/:fileName', async (req, res) => {
  748. try {
  749. const { projectName, fileName } = req.params;
  750. // Get project path
  751. let projectPath;
  752. try {
  753. projectPath = await extractProjectDirectory(projectName);
  754. } catch (error) {
  755. return res.status(404).json({
  756. error: 'Project not found',
  757. message: `Project "${projectName}" does not exist`
  758. });
  759. }
  760. const filePath = path.join(projectPath, '.taskmaster', 'docs', fileName);
  761. // Check if file exists
  762. try {
  763. await fsPromises.access(filePath, fs.constants.R_OK);
  764. } catch (error) {
  765. return res.status(404).json({
  766. error: 'PRD file not found',
  767. message: `File "${fileName}" does not exist`
  768. });
  769. }
  770. // Read file content
  771. try {
  772. const content = await fsPromises.readFile(filePath, 'utf8');
  773. const stats = await fsPromises.stat(filePath);
  774. res.json({
  775. projectName,
  776. projectPath,
  777. fileName,
  778. filePath: path.relative(projectPath, filePath),
  779. content,
  780. size: stats.size,
  781. created: stats.birthtime.toISOString(),
  782. modified: stats.mtime.toISOString(),
  783. timestamp: new Date().toISOString()
  784. });
  785. } catch (readError) {
  786. console.error('Failed to read PRD file:', readError);
  787. return res.status(500).json({
  788. error: 'Failed to read PRD file',
  789. message: readError.message
  790. });
  791. }
  792. } catch (error) {
  793. console.error('PRD read error:', error);
  794. res.status(500).json({
  795. error: 'Failed to read PRD file',
  796. message: error.message
  797. });
  798. }
  799. });
  800. /**
  801. * DELETE /api/taskmaster/prd/:projectName/:fileName
  802. * Delete a specific PRD file
  803. */
  804. router.delete('/prd/:projectName/:fileName', async (req, res) => {
  805. try {
  806. const { projectName, fileName } = req.params;
  807. // Get project path
  808. let projectPath;
  809. try {
  810. projectPath = await extractProjectDirectory(projectName);
  811. } catch (error) {
  812. return res.status(404).json({
  813. error: 'Project not found',
  814. message: `Project "${projectName}" does not exist`
  815. });
  816. }
  817. const filePath = path.join(projectPath, '.taskmaster', 'docs', fileName);
  818. // Check if file exists
  819. try {
  820. await fsPromises.access(filePath, fs.constants.F_OK);
  821. } catch (error) {
  822. return res.status(404).json({
  823. error: 'PRD file not found',
  824. message: `File "${fileName}" does not exist`
  825. });
  826. }
  827. // Delete the file
  828. try {
  829. await fsPromises.unlink(filePath);
  830. res.json({
  831. projectName,
  832. projectPath,
  833. fileName,
  834. message: 'PRD file deleted successfully',
  835. timestamp: new Date().toISOString()
  836. });
  837. } catch (deleteError) {
  838. console.error('Failed to delete PRD file:', deleteError);
  839. return res.status(500).json({
  840. error: 'Failed to delete PRD file',
  841. message: deleteError.message
  842. });
  843. }
  844. } catch (error) {
  845. console.error('PRD delete error:', error);
  846. res.status(500).json({
  847. error: 'Failed to delete PRD file',
  848. message: error.message
  849. });
  850. }
  851. });
  852. /**
  853. * POST /api/taskmaster/init/:projectName
  854. * Initialize TaskMaster in a project
  855. */
  856. router.post('/init/:projectName', async (req, res) => {
  857. try {
  858. const { projectName } = req.params;
  859. // Get project path
  860. let projectPath;
  861. try {
  862. projectPath = await extractProjectDirectory(projectName);
  863. } catch (error) {
  864. return res.status(404).json({
  865. error: 'Project not found',
  866. message: `Project "${projectName}" does not exist`
  867. });
  868. }
  869. // Check if TaskMaster is already initialized
  870. const taskMasterPath = path.join(projectPath, '.taskmaster');
  871. try {
  872. await fsPromises.access(taskMasterPath, fs.constants.F_OK);
  873. return res.status(400).json({
  874. error: 'TaskMaster already initialized',
  875. message: 'TaskMaster is already configured for this project'
  876. });
  877. } catch (error) {
  878. // Directory doesn't exist, we can proceed
  879. }
  880. // Run taskmaster init command
  881. const initProcess = spawn('npx', ['task-master', 'init'], {
  882. cwd: projectPath,
  883. stdio: ['pipe', 'pipe', 'pipe']
  884. });
  885. let stdout = '';
  886. let stderr = '';
  887. initProcess.stdout.on('data', (data) => {
  888. stdout += data.toString();
  889. });
  890. initProcess.stderr.on('data', (data) => {
  891. stderr += data.toString();
  892. });
  893. initProcess.on('close', (code) => {
  894. if (code === 0) {
  895. // Broadcast TaskMaster project update via WebSocket
  896. if (req.app.locals.wss) {
  897. broadcastTaskMasterProjectUpdate(
  898. req.app.locals.wss,
  899. projectName,
  900. { hasTaskmaster: true, status: 'initialized' }
  901. );
  902. }
  903. res.json({
  904. projectName,
  905. projectPath,
  906. message: 'TaskMaster initialized successfully',
  907. output: stdout,
  908. timestamp: new Date().toISOString()
  909. });
  910. } else {
  911. console.error('TaskMaster init failed:', stderr);
  912. res.status(500).json({
  913. error: 'Failed to initialize TaskMaster',
  914. message: stderr || stdout,
  915. code
  916. });
  917. }
  918. });
  919. // Send 'yes' responses to automated prompts
  920. initProcess.stdin.write('yes\n');
  921. initProcess.stdin.end();
  922. } catch (error) {
  923. console.error('TaskMaster init error:', error);
  924. res.status(500).json({
  925. error: 'Failed to initialize TaskMaster',
  926. message: error.message
  927. });
  928. }
  929. });
  930. /**
  931. * POST /api/taskmaster/add-task/:projectName
  932. * Add a new task to the project
  933. */
  934. router.post('/add-task/:projectName', async (req, res) => {
  935. try {
  936. const { projectName } = req.params;
  937. const { prompt, title, description, priority = 'medium', dependencies } = req.body;
  938. if (!prompt && (!title || !description)) {
  939. return res.status(400).json({
  940. error: 'Missing required parameters',
  941. message: 'Either "prompt" or both "title" and "description" are required'
  942. });
  943. }
  944. // Get project path
  945. let projectPath;
  946. try {
  947. projectPath = await extractProjectDirectory(projectName);
  948. } catch (error) {
  949. return res.status(404).json({
  950. error: 'Project not found',
  951. message: `Project "${projectName}" does not exist`
  952. });
  953. }
  954. // Build the task-master add-task command
  955. const args = ['task-master-ai', 'add-task'];
  956. if (prompt) {
  957. args.push('--prompt', prompt);
  958. args.push('--research'); // Use research for AI-generated tasks
  959. } else {
  960. args.push('--prompt', `Create a task titled "${title}" with description: ${description}`);
  961. }
  962. if (priority) {
  963. args.push('--priority', priority);
  964. }
  965. if (dependencies) {
  966. args.push('--dependencies', dependencies);
  967. }
  968. // Run task-master add-task command
  969. const addTaskProcess = spawn('npx', args, {
  970. cwd: projectPath,
  971. stdio: ['pipe', 'pipe', 'pipe']
  972. });
  973. let stdout = '';
  974. let stderr = '';
  975. addTaskProcess.stdout.on('data', (data) => {
  976. stdout += data.toString();
  977. });
  978. addTaskProcess.stderr.on('data', (data) => {
  979. stderr += data.toString();
  980. });
  981. addTaskProcess.on('close', (code) => {
  982. console.log('Add task process completed with code:', code);
  983. console.log('Stdout:', stdout);
  984. console.log('Stderr:', stderr);
  985. if (code === 0) {
  986. // Broadcast task update via WebSocket
  987. if (req.app.locals.wss) {
  988. broadcastTaskMasterTasksUpdate(
  989. req.app.locals.wss,
  990. projectName
  991. );
  992. }
  993. res.json({
  994. projectName,
  995. projectPath,
  996. message: 'Task added successfully',
  997. output: stdout,
  998. timestamp: new Date().toISOString()
  999. });
  1000. } else {
  1001. console.error('Add task failed:', stderr);
  1002. res.status(500).json({
  1003. error: 'Failed to add task',
  1004. message: stderr || stdout,
  1005. code
  1006. });
  1007. }
  1008. });
  1009. addTaskProcess.stdin.end();
  1010. } catch (error) {
  1011. console.error('Add task error:', error);
  1012. res.status(500).json({
  1013. error: 'Failed to add task',
  1014. message: error.message
  1015. });
  1016. }
  1017. });
  1018. /**
  1019. * PUT /api/taskmaster/update-task/:projectName/:taskId
  1020. * Update a specific task using TaskMaster CLI
  1021. */
  1022. router.put('/update-task/:projectName/:taskId', async (req, res) => {
  1023. try {
  1024. const { projectName, taskId } = req.params;
  1025. const { title, description, status, priority, details } = req.body;
  1026. // Get project path
  1027. let projectPath;
  1028. try {
  1029. projectPath = await extractProjectDirectory(projectName);
  1030. } catch (error) {
  1031. return res.status(404).json({
  1032. error: 'Project not found',
  1033. message: `Project "${projectName}" does not exist`
  1034. });
  1035. }
  1036. // If only updating status, use set-status command
  1037. if (status && Object.keys(req.body).length === 1) {
  1038. const setStatusProcess = spawn('npx', ['task-master-ai', 'set-status', `--id=${taskId}`, `--status=${status}`], {
  1039. cwd: projectPath,
  1040. stdio: ['pipe', 'pipe', 'pipe']
  1041. });
  1042. let stdout = '';
  1043. let stderr = '';
  1044. setStatusProcess.stdout.on('data', (data) => {
  1045. stdout += data.toString();
  1046. });
  1047. setStatusProcess.stderr.on('data', (data) => {
  1048. stderr += data.toString();
  1049. });
  1050. setStatusProcess.on('close', (code) => {
  1051. if (code === 0) {
  1052. // Broadcast task update via WebSocket
  1053. if (req.app.locals.wss) {
  1054. broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectName);
  1055. }
  1056. res.json({
  1057. projectName,
  1058. projectPath,
  1059. taskId,
  1060. message: 'Task status updated successfully',
  1061. output: stdout,
  1062. timestamp: new Date().toISOString()
  1063. });
  1064. } else {
  1065. console.error('Set task status failed:', stderr);
  1066. res.status(500).json({
  1067. error: 'Failed to update task status',
  1068. message: stderr || stdout,
  1069. code
  1070. });
  1071. }
  1072. });
  1073. setStatusProcess.stdin.end();
  1074. } else {
  1075. // For other updates, use update-task command with a prompt describing the changes
  1076. const updates = [];
  1077. if (title) updates.push(`title: "${title}"`);
  1078. if (description) updates.push(`description: "${description}"`);
  1079. if (priority) updates.push(`priority: "${priority}"`);
  1080. if (details) updates.push(`details: "${details}"`);
  1081. const prompt = `Update task with the following changes: ${updates.join(', ')}`;
  1082. const updateProcess = spawn('npx', ['task-master-ai', 'update-task', `--id=${taskId}`, `--prompt=${prompt}`], {
  1083. cwd: projectPath,
  1084. stdio: ['pipe', 'pipe', 'pipe']
  1085. });
  1086. let stdout = '';
  1087. let stderr = '';
  1088. updateProcess.stdout.on('data', (data) => {
  1089. stdout += data.toString();
  1090. });
  1091. updateProcess.stderr.on('data', (data) => {
  1092. stderr += data.toString();
  1093. });
  1094. updateProcess.on('close', (code) => {
  1095. if (code === 0) {
  1096. // Broadcast task update via WebSocket
  1097. if (req.app.locals.wss) {
  1098. broadcastTaskMasterTasksUpdate(req.app.locals.wss, projectName);
  1099. }
  1100. res.json({
  1101. projectName,
  1102. projectPath,
  1103. taskId,
  1104. message: 'Task updated successfully',
  1105. output: stdout,
  1106. timestamp: new Date().toISOString()
  1107. });
  1108. } else {
  1109. console.error('Update task failed:', stderr);
  1110. res.status(500).json({
  1111. error: 'Failed to update task',
  1112. message: stderr || stdout,
  1113. code
  1114. });
  1115. }
  1116. });
  1117. updateProcess.stdin.end();
  1118. }
  1119. } catch (error) {
  1120. console.error('Update task error:', error);
  1121. res.status(500).json({
  1122. error: 'Failed to update task',
  1123. message: error.message
  1124. });
  1125. }
  1126. });
  1127. /**
  1128. * POST /api/taskmaster/parse-prd/:projectName
  1129. * Parse a PRD file to generate tasks
  1130. */
  1131. router.post('/parse-prd/:projectName', async (req, res) => {
  1132. try {
  1133. const { projectName } = req.params;
  1134. const { fileName = 'prd.txt', numTasks, append = false } = req.body;
  1135. // Get project path
  1136. let projectPath;
  1137. try {
  1138. projectPath = await extractProjectDirectory(projectName);
  1139. } catch (error) {
  1140. return res.status(404).json({
  1141. error: 'Project not found',
  1142. message: `Project "${projectName}" does not exist`
  1143. });
  1144. }
  1145. const prdPath = path.join(projectPath, '.taskmaster', 'docs', fileName);
  1146. // Check if PRD file exists
  1147. try {
  1148. await fsPromises.access(prdPath, fs.constants.F_OK);
  1149. } catch (error) {
  1150. return res.status(404).json({
  1151. error: 'PRD file not found',
  1152. message: `File "${fileName}" does not exist in .taskmaster/docs/`
  1153. });
  1154. }
  1155. // Build the command args
  1156. const args = ['task-master-ai', 'parse-prd', prdPath];
  1157. if (numTasks) {
  1158. args.push('--num-tasks', numTasks.toString());
  1159. }
  1160. if (append) {
  1161. args.push('--append');
  1162. }
  1163. args.push('--research'); // Use research for better PRD parsing
  1164. // Run task-master parse-prd command
  1165. const parsePRDProcess = spawn('npx', args, {
  1166. cwd: projectPath,
  1167. stdio: ['pipe', 'pipe', 'pipe']
  1168. });
  1169. let stdout = '';
  1170. let stderr = '';
  1171. parsePRDProcess.stdout.on('data', (data) => {
  1172. stdout += data.toString();
  1173. });
  1174. parsePRDProcess.stderr.on('data', (data) => {
  1175. stderr += data.toString();
  1176. });
  1177. parsePRDProcess.on('close', (code) => {
  1178. if (code === 0) {
  1179. // Broadcast task update via WebSocket
  1180. if (req.app.locals.wss) {
  1181. broadcastTaskMasterTasksUpdate(
  1182. req.app.locals.wss,
  1183. projectName
  1184. );
  1185. }
  1186. res.json({
  1187. projectName,
  1188. projectPath,
  1189. prdFile: fileName,
  1190. message: 'PRD parsed and tasks generated successfully',
  1191. output: stdout,
  1192. timestamp: new Date().toISOString()
  1193. });
  1194. } else {
  1195. console.error('Parse PRD failed:', stderr);
  1196. res.status(500).json({
  1197. error: 'Failed to parse PRD',
  1198. message: stderr || stdout,
  1199. code
  1200. });
  1201. }
  1202. });
  1203. parsePRDProcess.stdin.end();
  1204. } catch (error) {
  1205. console.error('Parse PRD error:', error);
  1206. res.status(500).json({
  1207. error: 'Failed to parse PRD',
  1208. message: error.message
  1209. });
  1210. }
  1211. });
  1212. /**
  1213. * GET /api/taskmaster/prd-templates
  1214. * Get available PRD templates
  1215. */
  1216. router.get('/prd-templates', async (req, res) => {
  1217. try {
  1218. // Return built-in templates
  1219. const templates = [
  1220. {
  1221. id: 'web-app',
  1222. name: 'Web Application',
  1223. description: 'Template for web application projects with frontend and backend components',
  1224. category: 'web',
  1225. content: `# Product Requirements Document - Web Application
  1226. ## Overview
  1227. **Product Name:** [Your App Name]
  1228. **Version:** 1.0
  1229. **Date:** ${new Date().toISOString().split('T')[0]}
  1230. **Author:** [Your Name]
  1231. ## Executive Summary
  1232. Brief description of what this web application will do and why it's needed.
  1233. ## Product Goals
  1234. - Goal 1: [Specific measurable goal]
  1235. - Goal 2: [Specific measurable goal]
  1236. - Goal 3: [Specific measurable goal]
  1237. ## User Stories
  1238. ### Core Features
  1239. 1. **User Registration & Authentication**
  1240. - As a user, I want to create an account so I can access personalized features
  1241. - As a user, I want to log in securely so my data is protected
  1242. - As a user, I want to reset my password if I forget it
  1243. 2. **Main Application Features**
  1244. - As a user, I want to [core feature 1] so I can [benefit]
  1245. - As a user, I want to [core feature 2] so I can [benefit]
  1246. - As a user, I want to [core feature 3] so I can [benefit]
  1247. 3. **User Interface**
  1248. - As a user, I want a responsive design so I can use the app on any device
  1249. - As a user, I want intuitive navigation so I can easily find features
  1250. ## Technical Requirements
  1251. ### Frontend
  1252. - Framework: React/Vue/Angular or vanilla JavaScript
  1253. - Styling: CSS framework (Tailwind, Bootstrap, etc.)
  1254. - State Management: Redux/Vuex/Context API
  1255. - Build Tools: Webpack/Vite
  1256. - Testing: Jest/Vitest for unit tests
  1257. ### Backend
  1258. - Runtime: Node.js/Python/Java
  1259. - Database: PostgreSQL/MySQL/MongoDB
  1260. - API: RESTful API or GraphQL
  1261. - Authentication: JWT tokens
  1262. - Testing: Integration and unit tests
  1263. ### Infrastructure
  1264. - Hosting: Cloud provider (AWS, Azure, GCP)
  1265. - CI/CD: GitHub Actions/GitLab CI
  1266. - Monitoring: Application monitoring tools
  1267. - Security: HTTPS, input validation, rate limiting
  1268. ## Success Metrics
  1269. - User engagement metrics
  1270. - Performance benchmarks (load time < 2s)
  1271. - Error rates < 1%
  1272. - User satisfaction scores
  1273. ## Timeline
  1274. - Phase 1: Core functionality (4-6 weeks)
  1275. - Phase 2: Advanced features (2-4 weeks)
  1276. - Phase 3: Polish and launch (2 weeks)
  1277. ## Constraints & Assumptions
  1278. - Budget constraints
  1279. - Technical limitations
  1280. - Team size and expertise
  1281. - Timeline constraints`
  1282. },
  1283. {
  1284. id: 'api',
  1285. name: 'REST API',
  1286. description: 'Template for REST API development projects',
  1287. category: 'backend',
  1288. content: `# Product Requirements Document - REST API
  1289. ## Overview
  1290. **API Name:** [Your API Name]
  1291. **Version:** v1.0
  1292. **Date:** ${new Date().toISOString().split('T')[0]}
  1293. **Author:** [Your Name]
  1294. ## Executive Summary
  1295. Description of the API's purpose, target users, and primary use cases.
  1296. ## API Goals
  1297. - Goal 1: Provide secure data access
  1298. - Goal 2: Ensure scalable architecture
  1299. - Goal 3: Maintain high availability (99.9% uptime)
  1300. ## Functional Requirements
  1301. ### Core Endpoints
  1302. 1. **Authentication Endpoints**
  1303. - POST /api/auth/login - User authentication
  1304. - POST /api/auth/logout - User logout
  1305. - POST /api/auth/refresh - Token refresh
  1306. - POST /api/auth/register - User registration
  1307. 2. **Data Management Endpoints**
  1308. - GET /api/resources - List resources with pagination
  1309. - GET /api/resources/{id} - Get specific resource
  1310. - POST /api/resources - Create new resource
  1311. - PUT /api/resources/{id} - Update existing resource
  1312. - DELETE /api/resources/{id} - Delete resource
  1313. 3. **Administrative Endpoints**
  1314. - GET /api/admin/users - Manage users (admin only)
  1315. - GET /api/admin/analytics - System analytics
  1316. - POST /api/admin/backup - Trigger system backup
  1317. ## Technical Requirements
  1318. ### API Design
  1319. - RESTful architecture following OpenAPI 3.0 specification
  1320. - JSON request/response format
  1321. - Consistent error response format
  1322. - API versioning strategy
  1323. ### Authentication & Security
  1324. - JWT token-based authentication
  1325. - Role-based access control (RBAC)
  1326. - Rate limiting (100 requests/minute per user)
  1327. - Input validation and sanitization
  1328. - HTTPS enforcement
  1329. ### Database
  1330. - Database type: [PostgreSQL/MongoDB/MySQL]
  1331. - Connection pooling
  1332. - Database migrations
  1333. - Backup and recovery procedures
  1334. ### Performance Requirements
  1335. - Response time: < 200ms for 95% of requests
  1336. - Throughput: 1000+ requests/second
  1337. - Concurrent users: 10,000+
  1338. - Database query optimization
  1339. ### Documentation
  1340. - Auto-generated API documentation (Swagger/OpenAPI)
  1341. - Code examples for common use cases
  1342. - SDK development for major languages
  1343. - Postman collection for testing
  1344. ## Error Handling
  1345. - Standardized error codes and messages
  1346. - Proper HTTP status codes
  1347. - Detailed error logging
  1348. - Graceful degradation strategies
  1349. ## Testing Strategy
  1350. - Unit tests (80%+ coverage)
  1351. - Integration tests for all endpoints
  1352. - Load testing and performance testing
  1353. - Security testing (OWASP compliance)
  1354. ## Monitoring & Logging
  1355. - Application performance monitoring
  1356. - Error tracking and alerting
  1357. - Access logs and audit trails
  1358. - Health check endpoints
  1359. ## Deployment
  1360. - Containerized deployment (Docker)
  1361. - CI/CD pipeline setup
  1362. - Environment management (dev, staging, prod)
  1363. - Blue-green deployment strategy
  1364. ## Success Metrics
  1365. - API uptime > 99.9%
  1366. - Average response time < 200ms
  1367. - Zero critical security vulnerabilities
  1368. - Developer adoption metrics`
  1369. },
  1370. {
  1371. id: 'mobile-app',
  1372. name: 'Mobile Application',
  1373. description: 'Template for mobile app development projects (iOS/Android)',
  1374. category: 'mobile',
  1375. content: `# Product Requirements Document - Mobile Application
  1376. ## Overview
  1377. **App Name:** [Your App Name]
  1378. **Platform:** iOS / Android / Cross-platform
  1379. **Version:** 1.0
  1380. **Date:** ${new Date().toISOString().split('T')[0]}
  1381. **Author:** [Your Name]
  1382. ## Executive Summary
  1383. Brief description of the mobile app's purpose, target audience, and key value proposition.
  1384. ## Product Goals
  1385. - Goal 1: [Specific user engagement goal]
  1386. - Goal 2: [Specific functionality goal]
  1387. - Goal 3: [Specific performance goal]
  1388. ## User Stories
  1389. ### Core Features
  1390. 1. **Onboarding & Authentication**
  1391. - As a new user, I want a simple onboarding process
  1392. - As a user, I want to sign up with email or social media
  1393. - As a user, I want biometric authentication for security
  1394. 2. **Main App Features**
  1395. - As a user, I want [core feature 1] accessible from home screen
  1396. - As a user, I want [core feature 2] to work offline
  1397. - As a user, I want to sync data across devices
  1398. 3. **User Experience**
  1399. - As a user, I want intuitive navigation patterns
  1400. - As a user, I want fast loading times
  1401. - As a user, I want accessibility features
  1402. ## Technical Requirements
  1403. ### Mobile Development
  1404. - **Cross-platform:** React Native / Flutter / Xamarin
  1405. - **Native:** Swift (iOS) / Kotlin (Android)
  1406. - **State Management:** Redux / MobX / Provider
  1407. - **Navigation:** React Navigation / Flutter Navigation
  1408. ### Backend Integration
  1409. - REST API or GraphQL integration
  1410. - Real-time features (WebSockets/Push notifications)
  1411. - Offline data synchronization
  1412. - Background processing
  1413. ### Device Features
  1414. - Camera and photo library access
  1415. - GPS location services
  1416. - Push notifications
  1417. - Biometric authentication
  1418. - Device storage
  1419. ### Performance Requirements
  1420. - App launch time < 3 seconds
  1421. - Screen transition animations < 300ms
  1422. - Memory usage optimization
  1423. - Battery usage optimization
  1424. ## Platform-Specific Considerations
  1425. ### iOS Requirements
  1426. - iOS 13.0+ minimum version
  1427. - App Store guidelines compliance
  1428. - iOS design guidelines (Human Interface Guidelines)
  1429. - TestFlight beta testing
  1430. ### Android Requirements
  1431. - Android 8.0+ (API level 26) minimum
  1432. - Google Play Store guidelines
  1433. - Material Design guidelines
  1434. - Google Play Console testing
  1435. ## User Interface Design
  1436. - Responsive design for different screen sizes
  1437. - Dark mode support
  1438. - Accessibility compliance (WCAG 2.1)
  1439. - Consistent design system
  1440. ## Security & Privacy
  1441. - Secure data storage (Keychain/Keystore)
  1442. - API communication encryption
  1443. - Privacy policy compliance (GDPR/CCPA)
  1444. - App security best practices
  1445. ## Testing Strategy
  1446. - Unit testing (80%+ coverage)
  1447. - UI/E2E testing (Detox/Appium)
  1448. - Device testing on multiple screen sizes
  1449. - Performance testing
  1450. - Security testing
  1451. ## App Store Deployment
  1452. - App store optimization (ASO)
  1453. - App icons and screenshots
  1454. - Store listing content
  1455. - Release management strategy
  1456. ## Analytics & Monitoring
  1457. - User analytics (Firebase/Analytics)
  1458. - Crash reporting (Crashlytics/Sentry)
  1459. - Performance monitoring
  1460. - User feedback collection
  1461. ## Success Metrics
  1462. - App store ratings > 4.0
  1463. - User retention rates
  1464. - Daily/Monthly active users
  1465. - App performance metrics
  1466. - Conversion rates`
  1467. },
  1468. {
  1469. id: 'data-analysis',
  1470. name: 'Data Analysis Project',
  1471. description: 'Template for data analysis and visualization projects',
  1472. category: 'data',
  1473. content: `# Product Requirements Document - Data Analysis Project
  1474. ## Overview
  1475. **Project Name:** [Your Analysis Project]
  1476. **Analysis Type:** [Descriptive/Predictive/Prescriptive]
  1477. **Date:** ${new Date().toISOString().split('T')[0]}
  1478. **Author:** [Your Name]
  1479. ## Executive Summary
  1480. Description of the business problem, data sources, and expected insights.
  1481. ## Project Goals
  1482. - Goal 1: [Specific business question to answer]
  1483. - Goal 2: [Specific prediction to make]
  1484. - Goal 3: [Specific recommendation to provide]
  1485. ## Business Requirements
  1486. ### Key Questions
  1487. 1. What patterns exist in the current data?
  1488. 2. What factors influence [target variable]?
  1489. 3. What predictions can be made for [future outcome]?
  1490. 4. What recommendations can improve [business metric]?
  1491. ### Success Criteria
  1492. - Actionable insights for stakeholders
  1493. - Statistical significance in findings
  1494. - Reproducible analysis pipeline
  1495. - Clear visualization and reporting
  1496. ## Data Requirements
  1497. ### Data Sources
  1498. 1. **Primary Data**
  1499. - Source: [Database/API/Files]
  1500. - Format: [CSV/JSON/SQL]
  1501. - Size: [Volume estimate]
  1502. - Update frequency: [Real-time/Daily/Monthly]
  1503. 2. **External Data**
  1504. - Third-party APIs
  1505. - Public datasets
  1506. - Market research data
  1507. ### Data Quality Requirements
  1508. - Data completeness (< 5% missing values)
  1509. - Data accuracy validation
  1510. - Data consistency checks
  1511. - Historical data availability
  1512. ## Technical Requirements
  1513. ### Data Pipeline
  1514. - Data extraction and ingestion
  1515. - Data cleaning and preprocessing
  1516. - Data transformation and feature engineering
  1517. - Data validation and quality checks
  1518. ### Analysis Tools
  1519. - **Programming:** Python/R/SQL
  1520. - **Libraries:** pandas, numpy, scikit-learn, matplotlib
  1521. - **Visualization:** Tableau, PowerBI, or custom dashboards
  1522. - **Version Control:** Git for code and DVC for data
  1523. ### Computing Resources
  1524. - Local development environment
  1525. - Cloud computing (AWS/GCP/Azure) if needed
  1526. - Database access and permissions
  1527. - Storage requirements
  1528. ## Analysis Methodology
  1529. ### Data Exploration
  1530. 1. Descriptive statistics and data profiling
  1531. 2. Data visualization and pattern identification
  1532. 3. Correlation analysis
  1533. 4. Outlier detection and handling
  1534. ### Statistical Analysis
  1535. 1. Hypothesis formulation
  1536. 2. Statistical testing
  1537. 3. Confidence intervals
  1538. 4. Effect size calculations
  1539. ### Machine Learning (if applicable)
  1540. 1. Feature selection and engineering
  1541. 2. Model selection and training
  1542. 3. Cross-validation and evaluation
  1543. 4. Model interpretation and explainability
  1544. ## Deliverables
  1545. ### Reports
  1546. - Executive summary for stakeholders
  1547. - Technical analysis report
  1548. - Data quality report
  1549. - Methodology documentation
  1550. ### Visualizations
  1551. - Interactive dashboards
  1552. - Static charts and graphs
  1553. - Data story presentations
  1554. - Key findings infographics
  1555. ### Code & Documentation
  1556. - Reproducible analysis scripts
  1557. - Data pipeline code
  1558. - Documentation and comments
  1559. - Testing and validation code
  1560. ## Timeline
  1561. - Phase 1: Data collection and exploration (2 weeks)
  1562. - Phase 2: Analysis and modeling (3 weeks)
  1563. - Phase 3: Reporting and visualization (1 week)
  1564. - Phase 4: Stakeholder presentation (1 week)
  1565. ## Risks & Assumptions
  1566. - Data availability and quality risks
  1567. - Technical complexity assumptions
  1568. - Resource and timeline constraints
  1569. - Stakeholder engagement assumptions
  1570. ## Success Metrics
  1571. - Stakeholder satisfaction with insights
  1572. - Accuracy of predictions (if applicable)
  1573. - Business impact of recommendations
  1574. - Reproducibility of results`
  1575. }
  1576. ];
  1577. res.json({
  1578. templates,
  1579. timestamp: new Date().toISOString()
  1580. });
  1581. } catch (error) {
  1582. console.error('PRD templates error:', error);
  1583. res.status(500).json({
  1584. error: 'Failed to get PRD templates',
  1585. message: error.message
  1586. });
  1587. }
  1588. });
  1589. /**
  1590. * POST /api/taskmaster/apply-template/:projectName
  1591. * Apply a PRD template to create a new PRD file
  1592. */
  1593. router.post('/apply-template/:projectName', async (req, res) => {
  1594. try {
  1595. const { projectName } = req.params;
  1596. const { templateId, fileName = 'prd.txt', customizations = {} } = req.body;
  1597. if (!templateId) {
  1598. return res.status(400).json({
  1599. error: 'Missing required parameter',
  1600. message: 'templateId is required'
  1601. });
  1602. }
  1603. // Get project path
  1604. let projectPath;
  1605. try {
  1606. projectPath = await extractProjectDirectory(projectName);
  1607. } catch (error) {
  1608. return res.status(404).json({
  1609. error: 'Project not found',
  1610. message: `Project "${projectName}" does not exist`
  1611. });
  1612. }
  1613. // Get the template content (this would normally fetch from the templates list)
  1614. const templates = await getAvailableTemplates();
  1615. const template = templates.find(t => t.id === templateId);
  1616. if (!template) {
  1617. return res.status(404).json({
  1618. error: 'Template not found',
  1619. message: `Template "${templateId}" does not exist`
  1620. });
  1621. }
  1622. // Apply customizations to template content
  1623. let content = template.content;
  1624. // Replace placeholders with customizations
  1625. for (const [key, value] of Object.entries(customizations)) {
  1626. const placeholder = `[${key}]`;
  1627. content = content.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'), value);
  1628. }
  1629. // Ensure .taskmaster/docs directory exists
  1630. const docsDir = path.join(projectPath, '.taskmaster', 'docs');
  1631. try {
  1632. await fsPromises.mkdir(docsDir, { recursive: true });
  1633. } catch (error) {
  1634. console.error('Failed to create docs directory:', error);
  1635. }
  1636. const filePath = path.join(docsDir, fileName);
  1637. // Write the template content to the file
  1638. try {
  1639. await fsPromises.writeFile(filePath, content, 'utf8');
  1640. res.json({
  1641. projectName,
  1642. projectPath,
  1643. templateId,
  1644. templateName: template.name,
  1645. fileName,
  1646. filePath: filePath,
  1647. message: 'PRD template applied successfully',
  1648. timestamp: new Date().toISOString()
  1649. });
  1650. } catch (writeError) {
  1651. console.error('Failed to write PRD template:', writeError);
  1652. return res.status(500).json({
  1653. error: 'Failed to write PRD template',
  1654. message: writeError.message
  1655. });
  1656. }
  1657. } catch (error) {
  1658. console.error('Apply template error:', error);
  1659. res.status(500).json({
  1660. error: 'Failed to apply PRD template',
  1661. message: error.message
  1662. });
  1663. }
  1664. });
  1665. // Helper function to get available templates
  1666. async function getAvailableTemplates() {
  1667. // This could be extended to read from files or database
  1668. return [
  1669. {
  1670. id: 'web-app',
  1671. name: 'Web Application',
  1672. description: 'Template for web application projects',
  1673. category: 'web',
  1674. content: `# Product Requirements Document - Web Application
  1675. ## Overview
  1676. **Product Name:** [Your App Name]
  1677. **Version:** 1.0
  1678. **Date:** ${new Date().toISOString().split('T')[0]}
  1679. **Author:** [Your Name]
  1680. ## Executive Summary
  1681. Brief description of what this web application will do and why it's needed.
  1682. ## User Stories
  1683. 1. As a user, I want [feature] so I can [benefit]
  1684. 2. As a user, I want [feature] so I can [benefit]
  1685. 3. As a user, I want [feature] so I can [benefit]
  1686. ## Technical Requirements
  1687. - Frontend framework
  1688. - Backend services
  1689. - Database requirements
  1690. - Security considerations
  1691. ## Success Metrics
  1692. - User engagement metrics
  1693. - Performance benchmarks
  1694. - Business objectives`
  1695. },
  1696. // Add other templates here if needed
  1697. ];
  1698. }
  1699. export default router;