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.
- package/LICENSE +675 -0
- package/README.md +371 -0
- package/dist/api-docs.html +879 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-cIxJ4RXb.js +1226 -0
- package/dist/assets/index-oyEz69sP.css +32 -0
- package/dist/assets/vendor-codemirror-CJLzwpLB.js +39 -0
- package/dist/assets/vendor-react-DcyRfQm3.js +59 -0
- package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +9 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/generate-icons.md +19 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +12 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +12 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +12 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +12 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +12 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +12 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +12 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +12 -0
- package/dist/icons/icon-template.svg +12 -0
- package/dist/index.html +52 -0
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo.svg +17 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/cli-selection.png +0 -0
- package/dist/screenshots/desktop-main.png +0 -0
- package/dist/screenshots/mobile-chat.png +0 -0
- package/dist/screenshots/tools-modal.png +0 -0
- package/dist/sw.js +49 -0
- package/package.json +109 -0
- package/server/claude-sdk.js +721 -0
- package/server/cli.js +327 -0
- package/server/cursor-cli.js +267 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +361 -0
- package/server/database/init.sql +52 -0
- package/server/index.js +1747 -0
- package/server/middleware/auth.js +111 -0
- package/server/openai-codex.js +389 -0
- package/server/projects.js +1604 -0
- package/server/routes/agent.js +1230 -0
- package/server/routes/auth.js +135 -0
- package/server/routes/cli-auth.js +263 -0
- package/server/routes/codex.js +345 -0
- package/server/routes/commands.js +521 -0
- package/server/routes/cursor.js +795 -0
- package/server/routes/git.js +1128 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +552 -0
- package/server/routes/projects.js +378 -0
- package/server/routes/settings.js +178 -0
- package/server/routes/taskmaster.js +1963 -0
- package/server/routes/user.js +106 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/gitConfig.js +24 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- 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;
|