claude-wec 1.0.0

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