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