@pixelbyte-software/pixcode 1.33.11 → 1.35.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 (164) hide show
  1. package/dist/api-docs.html +162 -9
  2. package/dist/assets/index-B8w57E1r.css +32 -0
  3. package/dist/assets/index-Djuh0wHV.js +854 -0
  4. package/dist/favicon.svg +8 -8
  5. package/dist/icons/icon-128x128.svg +9 -9
  6. package/dist/icons/icon-144x144.svg +9 -9
  7. package/dist/icons/icon-152x152.svg +9 -9
  8. package/dist/icons/icon-192x192.svg +9 -9
  9. package/dist/icons/icon-384x384.svg +9 -9
  10. package/dist/icons/icon-512x512.svg +9 -9
  11. package/dist/icons/icon-72x72.svg +9 -9
  12. package/dist/icons/icon-96x96.svg +9 -9
  13. package/dist/icons/icon-template.svg +9 -9
  14. package/dist/index.html +2 -2
  15. package/dist/logo.svg +12 -12
  16. package/dist/openapi.yaml +383 -1
  17. package/dist-server/server/claude-sdk.js +38 -7
  18. package/dist-server/server/claude-sdk.js.map +1 -1
  19. package/dist-server/server/cli.js +12 -17
  20. package/dist-server/server/cli.js.map +1 -1
  21. package/dist-server/server/daemon-manager.js +98 -51
  22. package/dist-server/server/daemon-manager.js.map +1 -1
  23. package/dist-server/server/database/json-store.js +8 -5
  24. package/dist-server/server/database/json-store.js.map +1 -1
  25. package/dist-server/server/index.js +34 -9
  26. package/dist-server/server/index.js.map +1 -1
  27. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js +73 -0
  28. package/dist-server/server/modules/orchestration/a2a/adapter-registry.js.map +1 -0
  29. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js +17 -0
  30. package/dist-server/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.js.map +1 -0
  31. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js +234 -0
  32. package/dist-server/server/modules/orchestration/a2a/adapters/claude-code.adapter.js.map +1 -0
  33. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js +202 -0
  34. package/dist-server/server/modules/orchestration/a2a/adapters/codex.adapter.js.map +1 -0
  35. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js +205 -0
  36. package/dist-server/server/modules/orchestration/a2a/adapters/cursor.adapter.js.map +1 -0
  37. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js +205 -0
  38. package/dist-server/server/modules/orchestration/a2a/adapters/gemini.adapter.js.map +1 -0
  39. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js +205 -0
  40. package/dist-server/server/modules/orchestration/a2a/adapters/opencode.adapter.js.map +1 -0
  41. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js +205 -0
  42. package/dist-server/server/modules/orchestration/a2a/adapters/qwen.adapter.js.map +1 -0
  43. package/dist-server/server/modules/orchestration/a2a/agent-card.js +50 -0
  44. package/dist-server/server/modules/orchestration/a2a/agent-card.js.map +1 -0
  45. package/dist-server/server/modules/orchestration/a2a/auth.middleware.js +25 -0
  46. package/dist-server/server/modules/orchestration/a2a/auth.middleware.js.map +1 -0
  47. package/dist-server/server/modules/orchestration/a2a/bus.js +34 -0
  48. package/dist-server/server/modules/orchestration/a2a/bus.js.map +1 -0
  49. package/dist-server/server/modules/orchestration/a2a/routes.js +497 -0
  50. package/dist-server/server/modules/orchestration/a2a/routes.js.map +1 -0
  51. package/dist-server/server/modules/orchestration/a2a/task-store.js +144 -0
  52. package/dist-server/server/modules/orchestration/a2a/task-store.js.map +1 -0
  53. package/dist-server/server/modules/orchestration/a2a/types.js +6 -0
  54. package/dist-server/server/modules/orchestration/a2a/types.js.map +1 -0
  55. package/dist-server/server/modules/orchestration/a2a/validator.js +101 -0
  56. package/dist-server/server/modules/orchestration/a2a/validator.js.map +1 -0
  57. package/dist-server/server/modules/orchestration/index.js +24 -0
  58. package/dist-server/server/modules/orchestration/index.js.map +1 -0
  59. package/dist-server/server/modules/orchestration/preview/port-watcher.js +90 -0
  60. package/dist-server/server/modules/orchestration/preview/port-watcher.js.map +1 -0
  61. package/dist-server/server/modules/orchestration/preview/preview-proxy.js +58 -0
  62. package/dist-server/server/modules/orchestration/preview/preview-proxy.js.map +1 -0
  63. package/dist-server/server/modules/orchestration/preview/types.js +2 -0
  64. package/dist-server/server/modules/orchestration/preview/types.js.map +1 -0
  65. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js +37 -0
  66. package/dist-server/server/modules/orchestration/tasks/orchestration-task-store.js.map +1 -0
  67. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js +68 -0
  68. package/dist-server/server/modules/orchestration/tasks/orchestration-task.routes.js.map +1 -0
  69. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js +128 -0
  70. package/dist-server/server/modules/orchestration/tasks/orchestration-task.service.js.map +1 -0
  71. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js +2 -0
  72. package/dist-server/server/modules/orchestration/tasks/orchestration-task.types.js.map +1 -0
  73. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js +126 -0
  74. package/dist-server/server/modules/orchestration/workflows/built-in-workflows.js.map +1 -0
  75. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1047 -0
  76. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -0
  77. package/dist-server/server/modules/orchestration/workflows/workflow-store.js +76 -0
  78. package/dist-server/server/modules/orchestration/workflows/workflow-store.js.map +1 -0
  79. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +151 -0
  80. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -0
  81. package/dist-server/server/modules/orchestration/workflows/workflow.types.js +2 -0
  82. package/dist-server/server/modules/orchestration/workflows/workflow.types.js.map +1 -0
  83. package/dist-server/server/modules/orchestration/workflows/workspace-target.js +98 -0
  84. package/dist-server/server/modules/orchestration/workflows/workspace-target.js.map +1 -0
  85. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js +122 -0
  86. package/dist-server/server/modules/orchestration/workspace/docker-workspace.js.map +1 -0
  87. package/dist-server/server/modules/orchestration/workspace/path-safety.js +48 -0
  88. package/dist-server/server/modules/orchestration/workspace/path-safety.js.map +1 -0
  89. package/dist-server/server/modules/orchestration/workspace/types.js +11 -0
  90. package/dist-server/server/modules/orchestration/workspace/types.js.map +1 -0
  91. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js +80 -0
  92. package/dist-server/server/modules/orchestration/workspace/workspace-manager.js.map +1 -0
  93. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js +96 -0
  94. package/dist-server/server/modules/orchestration/workspace/worktree-workspace.js.map +1 -0
  95. package/dist-server/server/modules/providers/index.js +3 -0
  96. package/dist-server/server/modules/providers/index.js.map +1 -0
  97. package/dist-server/server/openai-codex.js +35 -4
  98. package/dist-server/server/openai-codex.js.map +1 -1
  99. package/dist-server/server/routes/taskmaster.js +106 -89
  100. package/dist-server/server/routes/taskmaster.js.map +1 -1
  101. package/package.json +3 -1
  102. package/scripts/smoke/a2a-roundtrip.mjs +167 -0
  103. package/scripts/smoke/orchestration-api.mjs +172 -0
  104. package/scripts/smoke/orchestration-live-run.mjs +176 -0
  105. package/server/claude-sdk.js +48 -7
  106. package/server/cli.js +12 -17
  107. package/server/daemon-manager.js +90 -51
  108. package/server/database/db.js +794 -794
  109. package/server/database/json-store.js +8 -5
  110. package/server/index.js +49 -9
  111. package/server/modules/orchestration/a2a/adapter-registry.ts +108 -0
  112. package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +55 -0
  113. package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +284 -0
  114. package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -0
  115. package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -0
  116. package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -0
  117. package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -0
  118. package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -0
  119. package/server/modules/orchestration/a2a/agent-card.ts +55 -0
  120. package/server/modules/orchestration/a2a/auth.middleware.ts +29 -0
  121. package/server/modules/orchestration/a2a/bus.ts +46 -0
  122. package/server/modules/orchestration/a2a/routes.ts +577 -0
  123. package/server/modules/orchestration/a2a/task-store.ts +178 -0
  124. package/server/modules/orchestration/a2a/types.ts +125 -0
  125. package/server/modules/orchestration/a2a/validator.ts +113 -0
  126. package/server/modules/orchestration/index.ts +66 -0
  127. package/server/modules/orchestration/preview/port-watcher.ts +112 -0
  128. package/server/modules/orchestration/preview/preview-proxy.ts +60 -0
  129. package/server/modules/orchestration/preview/types.ts +19 -0
  130. package/server/modules/orchestration/tasks/orchestration-task-store.ts +45 -0
  131. package/server/modules/orchestration/tasks/orchestration-task.routes.ts +73 -0
  132. package/server/modules/orchestration/tasks/orchestration-task.service.ts +145 -0
  133. package/server/modules/orchestration/tasks/orchestration-task.types.ts +29 -0
  134. package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -0
  135. package/server/modules/orchestration/workflows/workflow-runner.ts +1206 -0
  136. package/server/modules/orchestration/workflows/workflow-store.ts +97 -0
  137. package/server/modules/orchestration/workflows/workflow.routes.ts +169 -0
  138. package/server/modules/orchestration/workflows/workflow.types.ts +70 -0
  139. package/server/modules/orchestration/workflows/workspace-target.ts +120 -0
  140. package/server/modules/orchestration/workspace/docker-workspace.ts +135 -0
  141. package/server/modules/orchestration/workspace/path-safety.ts +55 -0
  142. package/server/modules/orchestration/workspace/types.ts +52 -0
  143. package/server/modules/orchestration/workspace/workspace-manager.ts +97 -0
  144. package/server/modules/orchestration/workspace/worktree-workspace.ts +125 -0
  145. package/server/modules/providers/index.ts +2 -0
  146. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +130 -130
  147. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
  148. package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
  149. package/server/modules/providers/list/qwen/qwen-auth.provider.ts +145 -145
  150. package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
  151. package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
  152. package/server/modules/providers/shared/provider-configs.ts +142 -142
  153. package/server/openai-codex.js +40 -4
  154. package/server/qwen-code-cli.js +395 -395
  155. package/server/qwen-response-handler.js +73 -73
  156. package/server/routes/qwen.js +27 -27
  157. package/server/routes/taskmaster.js +116 -91
  158. package/server/services/external-access.js +171 -171
  159. package/server/services/provider-models.js +381 -381
  160. package/server/services/telegram/telegram-http-client.js +130 -130
  161. package/server/services/vapid-keys.js +36 -36
  162. package/server/utils/port-access.js +209 -209
  163. package/dist/assets/index-B1ghfb4w.css +0 -32
  164. package/dist/assets/index-oLYHJ2X5.js +0 -852
@@ -1,73 +1,73 @@
1
- // Qwen Code Response Handler — stream-json parser.
2
- // Qwen Code is a fork of Gemini CLI and emits the same NDJSON stream-json
3
- // event shape (type: init | message | tool_use | tool_result | result | error).
4
- // This handler is intentionally a structural twin of gemini-response-handler;
5
- // the split keeps provider normalization clean and lets the two evolve
6
- // independently if Qwen's protocol ever diverges.
7
- import { sessionsService } from './modules/providers/services/sessions.service.js';
8
-
9
- class QwenResponseHandler {
10
- constructor(ws, options = {}) {
11
- this.ws = ws;
12
- this.buffer = '';
13
- this.onContentFragment = options.onContentFragment || null;
14
- this.onInit = options.onInit || null;
15
- this.onToolUse = options.onToolUse || null;
16
- this.onToolResult = options.onToolResult || null;
17
- }
18
-
19
- processData(data) {
20
- this.buffer += data;
21
-
22
- const lines = this.buffer.split('\n');
23
- this.buffer = lines.pop() || '';
24
-
25
- for (const line of lines) {
26
- if (!line.trim()) continue;
27
- try {
28
- const event = JSON.parse(line);
29
- this.handleEvent(event);
30
- } catch {
31
- // Not a JSON line — debug output or CLI warning, ignore.
32
- }
33
- }
34
- }
35
-
36
- handleEvent(event) {
37
- const sid = typeof this.ws.getSessionId === 'function' ? this.ws.getSessionId() : null;
38
-
39
- if (event.type === 'init') {
40
- if (this.onInit) this.onInit(event);
41
- return;
42
- }
43
-
44
- if (event.type === 'message' && event.role === 'assistant') {
45
- const content = event.content || '';
46
- if (this.onContentFragment && content) this.onContentFragment(content);
47
- } else if (event.type === 'tool_use' && this.onToolUse) {
48
- this.onToolUse(event);
49
- } else if (event.type === 'tool_result' && this.onToolResult) {
50
- this.onToolResult(event);
51
- }
52
-
53
- const normalized = sessionsService.normalizeMessage('qwen', event, sid);
54
- for (const msg of normalized) {
55
- this.ws.send(msg);
56
- }
57
- }
58
-
59
- forceFlush() {
60
- if (this.buffer.trim()) {
61
- try {
62
- const event = JSON.parse(this.buffer);
63
- this.handleEvent(event);
64
- } catch { /* ignore */ }
65
- }
66
- }
67
-
68
- destroy() {
69
- this.buffer = '';
70
- }
71
- }
72
-
73
- export default QwenResponseHandler;
1
+ // Qwen Code Response Handler — stream-json parser.
2
+ // Qwen Code is a fork of Gemini CLI and emits the same NDJSON stream-json
3
+ // event shape (type: init | message | tool_use | tool_result | result | error).
4
+ // This handler is intentionally a structural twin of gemini-response-handler;
5
+ // the split keeps provider normalization clean and lets the two evolve
6
+ // independently if Qwen's protocol ever diverges.
7
+ import { sessionsService } from './modules/providers/services/sessions.service.js';
8
+
9
+ class QwenResponseHandler {
10
+ constructor(ws, options = {}) {
11
+ this.ws = ws;
12
+ this.buffer = '';
13
+ this.onContentFragment = options.onContentFragment || null;
14
+ this.onInit = options.onInit || null;
15
+ this.onToolUse = options.onToolUse || null;
16
+ this.onToolResult = options.onToolResult || null;
17
+ }
18
+
19
+ processData(data) {
20
+ this.buffer += data;
21
+
22
+ const lines = this.buffer.split('\n');
23
+ this.buffer = lines.pop() || '';
24
+
25
+ for (const line of lines) {
26
+ if (!line.trim()) continue;
27
+ try {
28
+ const event = JSON.parse(line);
29
+ this.handleEvent(event);
30
+ } catch {
31
+ // Not a JSON line — debug output or CLI warning, ignore.
32
+ }
33
+ }
34
+ }
35
+
36
+ handleEvent(event) {
37
+ const sid = typeof this.ws.getSessionId === 'function' ? this.ws.getSessionId() : null;
38
+
39
+ if (event.type === 'init') {
40
+ if (this.onInit) this.onInit(event);
41
+ return;
42
+ }
43
+
44
+ if (event.type === 'message' && event.role === 'assistant') {
45
+ const content = event.content || '';
46
+ if (this.onContentFragment && content) this.onContentFragment(content);
47
+ } else if (event.type === 'tool_use' && this.onToolUse) {
48
+ this.onToolUse(event);
49
+ } else if (event.type === 'tool_result' && this.onToolResult) {
50
+ this.onToolResult(event);
51
+ }
52
+
53
+ const normalized = sessionsService.normalizeMessage('qwen', event, sid);
54
+ for (const msg of normalized) {
55
+ this.ws.send(msg);
56
+ }
57
+ }
58
+
59
+ forceFlush() {
60
+ if (this.buffer.trim()) {
61
+ try {
62
+ const event = JSON.parse(this.buffer);
63
+ this.handleEvent(event);
64
+ } catch { /* ignore */ }
65
+ }
66
+ }
67
+
68
+ destroy() {
69
+ this.buffer = '';
70
+ }
71
+ }
72
+
73
+ export default QwenResponseHandler;
@@ -1,27 +1,27 @@
1
- import express from 'express';
2
-
3
- import sessionManager from '../sessionManager.js';
4
- import { sessionNamesDb } from '../database/db.js';
5
-
6
- const router = express.Router();
7
-
8
- // Delete a Qwen Code session — mirrors the Gemini/Codex routes so the UI's
9
- // unified delete flow works identically regardless of the active provider.
10
- router.delete('/sessions/:sessionId', async (req, res) => {
11
- try {
12
- const { sessionId } = req.params;
13
-
14
- if (!sessionId || typeof sessionId !== 'string' || !/^[a-zA-Z0-9_.-]{1,100}$/.test(sessionId)) {
15
- return res.status(400).json({ success: false, error: 'Invalid session ID format' });
16
- }
17
-
18
- await sessionManager.deleteSession(sessionId);
19
- sessionNamesDb.deleteName(sessionId, 'qwen');
20
- res.json({ success: true });
21
- } catch (error) {
22
- console.error(`Error deleting Qwen session ${req.params.sessionId}:`, error);
23
- res.status(500).json({ success: false, error: error.message });
24
- }
25
- });
26
-
27
- export default router;
1
+ import express from 'express';
2
+
3
+ import sessionManager from '../sessionManager.js';
4
+ import { sessionNamesDb } from '../database/db.js';
5
+
6
+ const router = express.Router();
7
+
8
+ // Delete a Qwen Code session — mirrors the Gemini/Codex routes so the UI's
9
+ // unified delete flow works identically regardless of the active provider.
10
+ router.delete('/sessions/:sessionId', async (req, res) => {
11
+ try {
12
+ const { sessionId } = req.params;
13
+
14
+ if (!sessionId || typeof sessionId !== 'string' || !/^[a-zA-Z0-9_.-]{1,100}$/.test(sessionId)) {
15
+ return res.status(400).json({ success: false, error: 'Invalid session ID format' });
16
+ }
17
+
18
+ await sessionManager.deleteSession(sessionId);
19
+ sessionNamesDb.deleteName(sessionId, 'qwen');
20
+ res.json({ success: true });
21
+ } catch (error) {
22
+ console.error(`Error deleting Qwen session ${req.params.sessionId}:`, error);
23
+ res.status(500).json({ success: false, error: error.message });
24
+ }
25
+ });
26
+
27
+ export default router;
@@ -16,6 +16,7 @@ import { spawn } from 'child_process';
16
16
  import { extractProjectDirectory } from '../projects.js';
17
17
  import { detectTaskMasterMCPServer } from '../utils/mcp-detector.js';
18
18
  import { broadcastTaskMasterProjectUpdate, broadcastTaskMasterTasksUpdate } from '../utils/taskmaster-websocket.js';
19
+ import { orchestrationTaskService } from '@/modules/orchestration/tasks/orchestration-task.service.js';
19
20
 
20
21
  const router = express.Router();
21
22
 
@@ -94,6 +95,53 @@ async function checkTaskMasterInstallation() {
94
95
  });
95
96
  }
96
97
 
98
+
99
+ async function readTaskMasterTasks(projectName) {
100
+ const projectPath = await extractProjectDirectory(projectName);
101
+ const tasksFilePath = path.join(projectPath, '.taskmaster', 'tasks', 'tasks.json');
102
+
103
+ await fsPromises.access(tasksFilePath);
104
+ const tasksContent = await fsPromises.readFile(tasksFilePath, 'utf8');
105
+ const tasksData = JSON.parse(tasksContent);
106
+
107
+ let tasks = [];
108
+ let currentTag = 'master';
109
+
110
+ if (Array.isArray(tasksData)) {
111
+ tasks = tasksData;
112
+ } else if (tasksData.tasks) {
113
+ tasks = tasksData.tasks;
114
+ } else if (tasksData[currentTag] && tasksData[currentTag].tasks) {
115
+ tasks = tasksData[currentTag].tasks;
116
+ } else if (tasksData.master && tasksData.master.tasks) {
117
+ tasks = tasksData.master.tasks;
118
+ } else {
119
+ const firstTag = Object.keys(tasksData).find((key) =>
120
+ tasksData[key].tasks && Array.isArray(tasksData[key].tasks)
121
+ );
122
+ if (firstTag) {
123
+ tasks = tasksData[firstTag].tasks;
124
+ currentTag = firstTag;
125
+ }
126
+ }
127
+
128
+ const transformedTasks = tasks.map((task) => ({
129
+ id: task.id,
130
+ title: task.title || 'Untitled Task',
131
+ description: task.description || '',
132
+ status: task.status || 'pending',
133
+ priority: task.priority || 'medium',
134
+ dependencies: task.dependencies || [],
135
+ createdAt: task.createdAt || task.created || new Date().toISOString(),
136
+ updatedAt: task.updatedAt || task.updated || new Date().toISOString(),
137
+ details: task.details || '',
138
+ testStrategy: task.testStrategy || task.test_strategy || '',
139
+ subtasks: task.subtasks || []
140
+ }));
141
+
142
+ return { projectPath, transformedTasks, currentTag };
143
+ }
144
+
97
145
  // API Routes
98
146
 
99
147
  /**
@@ -138,106 +186,40 @@ router.get('/installation-status', async (req, res) => {
138
186
  router.get('/tasks/:projectName', async (req, res) => {
139
187
  try {
140
188
  const { projectName } = req.params;
141
-
142
- // Get project path
143
- let projectPath;
144
- try {
145
- projectPath = await extractProjectDirectory(projectName);
146
- } catch (error) {
147
- return res.status(404).json({
148
- error: 'Project not found',
149
- message: `Project "${projectName}" does not exist`
150
- });
151
- }
152
189
 
153
- const taskMasterPath = path.join(projectPath, '.taskmaster');
154
- const tasksFilePath = path.join(taskMasterPath, 'tasks', 'tasks.json');
190
+ const { projectPath, transformedTasks, currentTag } = await readTaskMasterTasks(projectName);
155
191
 
156
- // Check if tasks file exists
157
- try {
158
- await fsPromises.access(tasksFilePath);
159
- } catch (error) {
192
+ res.json({
193
+ projectName,
194
+ projectPath,
195
+ tasks: transformedTasks,
196
+ currentTag,
197
+ totalTasks: transformedTasks.length,
198
+ tasksByStatus: {
199
+ pending: transformedTasks.filter(t => t.status === 'pending').length,
200
+ 'in-progress': transformedTasks.filter(t => t.status === 'in-progress').length,
201
+ done: transformedTasks.filter(t => t.status === 'done').length,
202
+ review: transformedTasks.filter(t => t.status === 'review').length,
203
+ deferred: transformedTasks.filter(t => t.status === 'deferred').length,
204
+ cancelled: transformedTasks.filter(t => t.status === 'cancelled').length
205
+ },
206
+ timestamp: new Date().toISOString()
207
+ });
208
+
209
+ } catch (error) {
210
+ if (error?.code === 'ENOENT') {
160
211
  return res.json({
161
- projectName,
212
+ projectName: req.params.projectName,
162
213
  tasks: [],
163
214
  message: 'No tasks.json file found'
164
215
  });
165
216
  }
166
-
167
- // Read and parse tasks file
168
- try {
169
- const tasksContent = await fsPromises.readFile(tasksFilePath, 'utf8');
170
- const tasksData = JSON.parse(tasksContent);
171
-
172
- let tasks = [];
173
- let currentTag = 'master';
174
-
175
- // Handle both tagged and legacy formats
176
- if (Array.isArray(tasksData)) {
177
- // Legacy format
178
- tasks = tasksData;
179
- } else if (tasksData.tasks) {
180
- // Simple format with tasks array
181
- tasks = tasksData.tasks;
182
- } else {
183
- // Tagged format - get tasks from current tag or master
184
- if (tasksData[currentTag] && tasksData[currentTag].tasks) {
185
- tasks = tasksData[currentTag].tasks;
186
- } else if (tasksData.master && tasksData.master.tasks) {
187
- tasks = tasksData.master.tasks;
188
- } else {
189
- // Get tasks from first available tag
190
- const firstTag = Object.keys(tasksData).find(key =>
191
- tasksData[key].tasks && Array.isArray(tasksData[key].tasks)
192
- );
193
- if (firstTag) {
194
- tasks = tasksData[firstTag].tasks;
195
- currentTag = firstTag;
196
- }
197
- }
198
- }
199
-
200
- // Transform tasks to ensure all have required fields
201
- const transformedTasks = tasks.map(task => ({
202
- id: task.id,
203
- title: task.title || 'Untitled Task',
204
- description: task.description || '',
205
- status: task.status || 'pending',
206
- priority: task.priority || 'medium',
207
- dependencies: task.dependencies || [],
208
- createdAt: task.createdAt || task.created || new Date().toISOString(),
209
- updatedAt: task.updatedAt || task.updated || new Date().toISOString(),
210
- details: task.details || '',
211
- testStrategy: task.testStrategy || task.test_strategy || '',
212
- subtasks: task.subtasks || []
213
- }));
214
-
215
- res.json({
216
- projectName,
217
- projectPath,
218
- tasks: transformedTasks,
219
- currentTag,
220
- totalTasks: transformedTasks.length,
221
- tasksByStatus: {
222
- pending: transformedTasks.filter(t => t.status === 'pending').length,
223
- 'in-progress': transformedTasks.filter(t => t.status === 'in-progress').length,
224
- done: transformedTasks.filter(t => t.status === 'done').length,
225
- review: transformedTasks.filter(t => t.status === 'review').length,
226
- deferred: transformedTasks.filter(t => t.status === 'deferred').length,
227
- cancelled: transformedTasks.filter(t => t.status === 'cancelled').length
228
- },
229
- timestamp: new Date().toISOString()
230
- });
231
-
232
- } catch (parseError) {
233
- console.error('Failed to parse tasks.json:', parseError);
234
- return res.status(500).json({
235
- error: 'Failed to parse tasks file',
236
- message: parseError.message
217
+ if (String(error?.message || '').includes('does not exist')) {
218
+ return res.status(404).json({
219
+ error: 'Project not found',
220
+ message: `Project "${req.params.projectName}" does not exist`
237
221
  });
238
222
  }
239
-
240
- } catch (error) {
241
223
  console.error('TaskMaster tasks loading error:', error);
242
224
  res.status(500).json({
243
225
  error: 'Failed to load TaskMaster tasks',
@@ -246,6 +228,49 @@ router.get('/tasks/:projectName', async (req, res) => {
246
228
  }
247
229
  });
248
230
 
231
+ /**
232
+ * POST /api/taskmaster/sync-orchestration/:projectName
233
+ * One-way sync: TaskMaster -> Orchestration tasks
234
+ */
235
+ router.post('/sync-orchestration/:projectName', async (req, res) => {
236
+ try {
237
+ const { projectName } = req.params;
238
+ const { transformedTasks } = await readTaskMasterTasks(projectName);
239
+
240
+ const projectId = typeof req.body?.projectId === 'string' && req.body.projectId.trim()
241
+ ? req.body.projectId.trim()
242
+ : projectName;
243
+
244
+ const syncedTasks = transformedTasks.map((task) =>
245
+ orchestrationTaskService.upsertFromTaskMaster({
246
+ projectId,
247
+ taskmasterId: String(task.id),
248
+ title: task.title,
249
+ description: task.description,
250
+ })
251
+ );
252
+
253
+ res.json({
254
+ success: true,
255
+ projectName,
256
+ projectId,
257
+ synced: syncedTasks.length,
258
+ tasks: syncedTasks,
259
+ timestamp: new Date().toISOString(),
260
+ });
261
+ } catch (error) {
262
+ if (error?.code === 'ENOENT') {
263
+ return res.json({ success: true, projectName: req.params.projectName, synced: 0, tasks: [] });
264
+ }
265
+ console.error('TaskMaster orchestration sync error:', error);
266
+ res.status(500).json({
267
+ success: false,
268
+ error: 'Failed to sync TaskMaster tasks to orchestration',
269
+ message: error.message,
270
+ });
271
+ }
272
+ });
273
+
249
274
  /**
250
275
  * GET /api/taskmaster/prd/:projectName
251
276
  * List all PRD files in the project's .taskmaster/docs directory