claude-code-workflow 6.3.36 → 6.3.38

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 (250) hide show
  1. package/.claude/commands/workflow/lite-execute.md +2 -0
  2. package/.claude/commands/workflow/lite-fix.md +108 -9
  3. package/.claude/skills/ccw-loop/README.md +303 -0
  4. package/.claude/skills/ccw-loop/SKILL.md +259 -0
  5. package/.claude/skills/ccw-loop/phases/actions/action-complete.md +320 -0
  6. package/.claude/skills/ccw-loop/phases/actions/action-debug-with-file.md +485 -0
  7. package/.claude/skills/ccw-loop/phases/actions/action-develop-with-file.md +365 -0
  8. package/.claude/skills/ccw-loop/phases/actions/action-init.md +200 -0
  9. package/.claude/skills/ccw-loop/phases/actions/action-menu.md +192 -0
  10. package/.claude/skills/ccw-loop/phases/actions/action-validate-with-file.md +307 -0
  11. package/.claude/skills/ccw-loop/phases/orchestrator.md +486 -0
  12. package/.claude/skills/ccw-loop/phases/state-schema.md +474 -0
  13. package/.claude/skills/ccw-loop/specs/action-catalog.md +300 -0
  14. package/.claude/skills/ccw-loop/specs/loop-requirements.md +192 -0
  15. package/.claude/skills/ccw-loop/templates/progress-template.md +175 -0
  16. package/.claude/skills/ccw-loop/templates/understanding-template.md +303 -0
  17. package/.claude/skills/ccw-loop/templates/validation-template.md +258 -0
  18. package/.codex/agents/action-planning-agent.md +885 -0
  19. package/.codex/agents/ccw-loop-executor.md +260 -0
  20. package/.codex/agents/cli-discuss-agent.md +391 -0
  21. package/.codex/agents/cli-execution-agent.md +333 -0
  22. package/.codex/agents/cli-explore-agent.md +186 -0
  23. package/.codex/agents/cli-lite-planning-agent.md +736 -0
  24. package/.codex/agents/cli-planning-agent.md +562 -0
  25. package/.codex/agents/code-developer.md +408 -0
  26. package/.codex/agents/conceptual-planning-agent.md +321 -0
  27. package/.codex/agents/context-search-agent.md +585 -0
  28. package/.codex/agents/debug-explore-agent.md +436 -0
  29. package/.codex/agents/doc-generator.md +334 -0
  30. package/.codex/agents/issue-plan-agent.md +417 -0
  31. package/.codex/agents/issue-queue-agent.md +311 -0
  32. package/.codex/agents/memory-bridge.md +96 -0
  33. package/.codex/agents/test-context-search-agent.md +402 -0
  34. package/.codex/agents/test-fix-agent.md +359 -0
  35. package/.codex/agents/ui-design-agent.md +595 -0
  36. package/.codex/agents/universal-executor.md +135 -0
  37. package/.codex/prompts/issue-discover-by-prompt.md +364 -0
  38. package/.codex/prompts/issue-discover.md +261 -0
  39. package/.codex/prompts/issue-execute.md +10 -0
  40. package/.codex/prompts/issue-new.md +285 -0
  41. package/.codex/prompts/issue-plan.md +161 -63
  42. package/.codex/prompts/issue-queue.md +298 -288
  43. package/.codex/prompts/lite-execute.md +627 -133
  44. package/.codex/prompts/lite-fix.md +670 -0
  45. package/.codex/prompts/lite-plan-a.md +337 -0
  46. package/.codex/prompts/lite-plan-b.md +485 -0
  47. package/.codex/prompts/{lite-plan.md → lite-plan-c.md} +601 -469
  48. package/.codex/skills/ccw-loop/README.md +171 -0
  49. package/.codex/skills/ccw-loop/SKILL.md +349 -0
  50. package/.codex/skills/ccw-loop/phases/actions/action-complete.md +269 -0
  51. package/.codex/skills/ccw-loop/phases/actions/action-debug.md +286 -0
  52. package/.codex/skills/ccw-loop/phases/actions/action-develop.md +183 -0
  53. package/.codex/skills/ccw-loop/phases/actions/action-init.md +164 -0
  54. package/.codex/skills/ccw-loop/phases/actions/action-menu.md +205 -0
  55. package/.codex/skills/ccw-loop/phases/actions/action-validate.md +250 -0
  56. package/.codex/skills/ccw-loop/phases/orchestrator.md +416 -0
  57. package/.codex/skills/ccw-loop/phases/state-schema.md +388 -0
  58. package/.codex/skills/ccw-loop/specs/action-catalog.md +182 -0
  59. package/.codex/skills/ccw-loop-b/README.md +102 -0
  60. package/.codex/skills/ccw-loop-b/SKILL.md +322 -0
  61. package/.codex/skills/ccw-loop-b/phases/orchestrator.md +257 -0
  62. package/.codex/skills/ccw-loop-b/phases/state-schema.md +181 -0
  63. package/ccw/dist/cli.d.ts.map +1 -1
  64. package/ccw/dist/cli.js +12 -1
  65. package/ccw/dist/cli.js.map +1 -1
  66. package/ccw/dist/commands/cli.d.ts.map +1 -1
  67. package/ccw/dist/commands/cli.js +14 -1
  68. package/ccw/dist/commands/cli.js.map +1 -1
  69. package/ccw/dist/commands/install.d.ts.map +1 -1
  70. package/ccw/dist/commands/install.js +38 -7
  71. package/ccw/dist/commands/install.js.map +1 -1
  72. package/ccw/dist/commands/issue.d.ts +3 -0
  73. package/ccw/dist/commands/issue.d.ts.map +1 -1
  74. package/ccw/dist/commands/issue.js +107 -0
  75. package/ccw/dist/commands/issue.js.map +1 -1
  76. package/ccw/dist/commands/loop.d.ts +10 -0
  77. package/ccw/dist/commands/loop.d.ts.map +1 -0
  78. package/ccw/dist/commands/loop.js +289 -0
  79. package/ccw/dist/commands/loop.js.map +1 -0
  80. package/ccw/dist/commands/upgrade.js +1 -1
  81. package/ccw/dist/commands/upgrade.js.map +1 -1
  82. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -1
  83. package/ccw/dist/config/litellm-api-config-manager.js +3 -2
  84. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -1
  85. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  86. package/ccw/dist/core/dashboard-generator.js +4 -1
  87. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  88. package/ccw/dist/core/memory-embedder-bridge.d.ts.map +1 -1
  89. package/ccw/dist/core/memory-embedder-bridge.js +2 -5
  90. package/ccw/dist/core/memory-embedder-bridge.js.map +1 -1
  91. package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
  92. package/ccw/dist/core/routes/claude-routes.js +5 -3
  93. package/ccw/dist/core/routes/claude-routes.js.map +1 -1
  94. package/ccw/dist/core/routes/cli-routes.d.ts +6 -0
  95. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  96. package/ccw/dist/core/routes/cli-routes.js +42 -13
  97. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  98. package/ccw/dist/core/routes/cli-settings-routes.d.ts.map +1 -1
  99. package/ccw/dist/core/routes/cli-settings-routes.js +44 -0
  100. package/ccw/dist/core/routes/cli-settings-routes.js.map +1 -1
  101. package/ccw/dist/core/routes/codexlens/config-handlers.d.ts.map +1 -1
  102. package/ccw/dist/core/routes/codexlens/config-handlers.js +7 -6
  103. package/ccw/dist/core/routes/codexlens/config-handlers.js.map +1 -1
  104. package/ccw/dist/core/routes/codexlens/semantic-handlers.d.ts.map +1 -1
  105. package/ccw/dist/core/routes/codexlens/semantic-handlers.js +5 -4
  106. package/ccw/dist/core/routes/codexlens/semantic-handlers.js.map +1 -1
  107. package/ccw/dist/core/routes/core-memory-routes.d.ts.map +1 -1
  108. package/ccw/dist/core/routes/core-memory-routes.js +4 -2
  109. package/ccw/dist/core/routes/core-memory-routes.js.map +1 -1
  110. package/ccw/dist/core/routes/files-routes.d.ts.map +1 -1
  111. package/ccw/dist/core/routes/files-routes.js +4 -2
  112. package/ccw/dist/core/routes/files-routes.js.map +1 -1
  113. package/ccw/dist/core/routes/graph-routes.d.ts.map +1 -1
  114. package/ccw/dist/core/routes/graph-routes.js +17 -2
  115. package/ccw/dist/core/routes/graph-routes.js.map +1 -1
  116. package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -1
  117. package/ccw/dist/core/routes/issue-routes.js +280 -33
  118. package/ccw/dist/core/routes/issue-routes.js.map +1 -1
  119. package/ccw/dist/core/routes/loop-routes.d.ts +24 -0
  120. package/ccw/dist/core/routes/loop-routes.d.ts.map +1 -0
  121. package/ccw/dist/core/routes/loop-routes.js +334 -0
  122. package/ccw/dist/core/routes/loop-routes.js.map +1 -0
  123. package/ccw/dist/core/routes/loop-v2-routes.d.ts +44 -0
  124. package/ccw/dist/core/routes/loop-v2-routes.d.ts.map +1 -0
  125. package/ccw/dist/core/routes/loop-v2-routes.js +1260 -0
  126. package/ccw/dist/core/routes/loop-v2-routes.js.map +1 -0
  127. package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
  128. package/ccw/dist/core/routes/memory-routes.js +2 -1
  129. package/ccw/dist/core/routes/memory-routes.js.map +1 -1
  130. package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
  131. package/ccw/dist/core/routes/system-routes.js +3 -2
  132. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  133. package/ccw/dist/core/routes/task-routes.d.ts +12 -0
  134. package/ccw/dist/core/routes/task-routes.d.ts.map +1 -0
  135. package/ccw/dist/core/routes/task-routes.js +321 -0
  136. package/ccw/dist/core/routes/task-routes.js.map +1 -0
  137. package/ccw/dist/core/routes/test-loop-routes.d.ts +11 -0
  138. package/ccw/dist/core/routes/test-loop-routes.d.ts.map +1 -0
  139. package/ccw/dist/core/routes/test-loop-routes.js +298 -0
  140. package/ccw/dist/core/routes/test-loop-routes.js.map +1 -0
  141. package/ccw/dist/core/server.d.ts.map +1 -1
  142. package/ccw/dist/core/server.js +47 -5
  143. package/ccw/dist/core/server.js.map +1 -1
  144. package/ccw/dist/core/websocket.d.ts +59 -0
  145. package/ccw/dist/core/websocket.d.ts.map +1 -1
  146. package/ccw/dist/core/websocket.js +34 -0
  147. package/ccw/dist/core/websocket.js.map +1 -1
  148. package/ccw/dist/tools/claude-cli-tools.d.ts +40 -0
  149. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  150. package/ccw/dist/tools/claude-cli-tools.js +119 -0
  151. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  152. package/ccw/dist/tools/codex-lens-lsp.d.ts.map +1 -1
  153. package/ccw/dist/tools/codex-lens-lsp.js +2 -5
  154. package/ccw/dist/tools/codex-lens-lsp.js.map +1 -1
  155. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  156. package/ccw/dist/tools/codex-lens.js +22 -32
  157. package/ccw/dist/tools/codex-lens.js.map +1 -1
  158. package/ccw/dist/tools/litellm-client.d.ts +6 -0
  159. package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
  160. package/ccw/dist/tools/litellm-client.js +15 -2
  161. package/ccw/dist/tools/litellm-client.js.map +1 -1
  162. package/ccw/dist/tools/loop-manager.d.ts +84 -0
  163. package/ccw/dist/tools/loop-manager.d.ts.map +1 -0
  164. package/ccw/dist/tools/loop-manager.js +425 -0
  165. package/ccw/dist/tools/loop-manager.js.map +1 -0
  166. package/ccw/dist/tools/loop-state-manager.d.ts +47 -0
  167. package/ccw/dist/tools/loop-state-manager.d.ts.map +1 -0
  168. package/ccw/dist/tools/loop-state-manager.js +149 -0
  169. package/ccw/dist/tools/loop-state-manager.js.map +1 -0
  170. package/ccw/dist/tools/loop-task-manager.d.ts +149 -0
  171. package/ccw/dist/tools/loop-task-manager.d.ts.map +1 -0
  172. package/ccw/dist/tools/loop-task-manager.js +270 -0
  173. package/ccw/dist/tools/loop-task-manager.js.map +1 -0
  174. package/ccw/dist/tools/native-session-discovery.d.ts.map +1 -1
  175. package/ccw/dist/tools/native-session-discovery.js +35 -7
  176. package/ccw/dist/tools/native-session-discovery.js.map +1 -1
  177. package/ccw/dist/types/index.d.ts +1 -0
  178. package/ccw/dist/types/index.d.ts.map +1 -1
  179. package/ccw/dist/types/index.js +1 -0
  180. package/ccw/dist/types/index.js.map +1 -1
  181. package/ccw/dist/types/loop.d.ts +257 -0
  182. package/ccw/dist/types/loop.d.ts.map +1 -0
  183. package/ccw/dist/types/loop.js +17 -0
  184. package/ccw/dist/types/loop.js.map +1 -0
  185. package/ccw/dist/utils/codexlens-path.d.ts +36 -0
  186. package/ccw/dist/utils/codexlens-path.d.ts.map +1 -0
  187. package/ccw/dist/utils/codexlens-path.js +56 -0
  188. package/ccw/dist/utils/codexlens-path.js.map +1 -0
  189. package/ccw/dist/utils/uv-manager.d.ts.map +1 -1
  190. package/ccw/dist/utils/uv-manager.js +3 -2
  191. package/ccw/dist/utils/uv-manager.js.map +1 -1
  192. package/ccw/src/cli.ts +13 -1
  193. package/ccw/src/commands/cli.ts +14 -1
  194. package/ccw/src/commands/install.ts +50 -7
  195. package/ccw/src/commands/issue.ts +119 -0
  196. package/ccw/src/commands/loop.ts +344 -0
  197. package/ccw/src/commands/upgrade.ts +1 -1
  198. package/ccw/src/config/litellm-api-config-manager.ts +3 -2
  199. package/ccw/src/core/dashboard-generator.ts +4 -1
  200. package/ccw/src/core/memory-embedder-bridge.ts +2 -6
  201. package/ccw/src/core/routes/claude-routes.ts +5 -3
  202. package/ccw/src/core/routes/cli-routes.ts +48 -16
  203. package/ccw/src/core/routes/cli-settings-routes.ts +47 -0
  204. package/ccw/src/core/routes/codexlens/config-handlers.ts +7 -6
  205. package/ccw/src/core/routes/codexlens/semantic-handlers.ts +5 -4
  206. package/ccw/src/core/routes/core-memory-routes.ts +4 -2
  207. package/ccw/src/core/routes/files-routes.ts +4 -2
  208. package/ccw/src/core/routes/graph-routes.ts +18 -2
  209. package/ccw/src/core/routes/issue-routes.ts +308 -33
  210. package/ccw/src/core/routes/loop-routes.ts +386 -0
  211. package/ccw/src/core/routes/loop-v2-routes.ts +1470 -0
  212. package/ccw/src/core/routes/memory-routes.ts +2 -1
  213. package/ccw/src/core/routes/system-routes.ts +3 -2
  214. package/ccw/src/core/routes/task-routes.ts +361 -0
  215. package/ccw/src/core/routes/test-loop-routes.ts +312 -0
  216. package/ccw/src/core/server.ts +49 -5
  217. package/ccw/src/core/websocket.ts +104 -0
  218. package/ccw/src/templates/dashboard-css/02-session.css +2 -0
  219. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +103 -1
  220. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +56 -0
  221. package/ccw/src/templates/dashboard-css/32-issue-manager.css +32 -0
  222. package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +55 -0
  223. package/ccw/src/templates/dashboard-css/36-loop-monitor.css +1896 -0
  224. package/ccw/src/templates/dashboard-css/36-loop-monitor.css.backup +1877 -0
  225. package/ccw/src/templates/dashboard-js/components/cli-history.js +48 -48
  226. package/ccw/src/templates/dashboard-js/components/cli-status.js +64 -3
  227. package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +251 -110
  228. package/ccw/src/templates/dashboard-js/components/navigation.js +16 -0
  229. package/ccw/src/templates/dashboard-js/components/notifications.js +22 -0
  230. package/ccw/src/templates/dashboard-js/components/version-check.js +38 -0
  231. package/ccw/src/templates/dashboard-js/i18n.js +601 -1
  232. package/ccw/src/templates/dashboard-js/state.js +2 -0
  233. package/ccw/src/templates/dashboard-js/views/cli-manager.js +4 -3
  234. package/ccw/src/templates/dashboard-js/views/issue-manager.js +183 -1
  235. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +55 -11
  236. package/ccw/src/templates/dashboard-js/views/loop-monitor.js +3345 -0
  237. package/ccw/src/templates/dashboard.html +68 -4
  238. package/ccw/src/tools/claude-cli-tools.ts +143 -0
  239. package/ccw/src/tools/codex-lens-lsp.ts +2 -5
  240. package/ccw/src/tools/codex-lens.ts +27 -38
  241. package/ccw/src/tools/litellm-client.ts +16 -2
  242. package/ccw/src/tools/loop-manager.ts +519 -0
  243. package/ccw/src/tools/loop-state-manager.ts +173 -0
  244. package/ccw/src/tools/loop-task-manager.ts +391 -0
  245. package/ccw/src/tools/native-session-discovery.ts +38 -7
  246. package/ccw/src/types/index.ts +1 -0
  247. package/ccw/src/types/loop.ts +316 -0
  248. package/ccw/src/utils/codexlens-path.ts +60 -0
  249. package/ccw/src/utils/uv-manager.ts +3 -2
  250. package/package.json +1 -1
@@ -6,6 +6,7 @@ import { homedir } from 'os';
6
6
  import { getMemoryStore } from '../memory-store.js';
7
7
  import { executeCliTool } from '../../tools/cli-executor.js';
8
8
  import { SmartContentFormatter } from '../../tools/cli-output-converter.js';
9
+ import { getDefaultTool } from '../../tools/claude-cli-tools.js';
9
10
 
10
11
  /**
11
12
  * Route context interface
@@ -340,7 +341,7 @@ export async function handleMemoryRoutes(ctx: RouteContext): Promise<boolean> {
340
341
  if (pathname === '/api/memory/insights/analyze' && req.method === 'POST') {
341
342
  handlePostRequest(req, res, async (body: any) => {
342
343
  const projectPath = body.path || initialPath;
343
- const tool = body.tool || 'gemini'; // gemini, qwen, codex, claude
344
+ const tool = body.tool || getDefaultTool(projectPath);
344
345
  const prompts = body.prompts || [];
345
346
  const lang = body.lang || 'en'; // Language preference
346
347
 
@@ -145,7 +145,7 @@ async function getWorkflowData(projectPath: string): Promise<any> {
145
145
  generatedAt: new Date().toISOString(),
146
146
  activeSessions: [],
147
147
  archivedSessions: [],
148
- liteTasks: { litePlan: [], liteFix: [] },
148
+ liteTasks: { litePlan: [], liteFix: [], multiCliPlan: [] },
149
149
  reviewData: { dimensions: {} },
150
150
  projectOverview: null,
151
151
  statistics: {
@@ -155,7 +155,8 @@ async function getWorkflowData(projectPath: string): Promise<any> {
155
155
  completedTasks: 0,
156
156
  reviewFindings: 0,
157
157
  litePlanCount: 0,
158
- liteFixCount: 0
158
+ liteFixCount: 0,
159
+ multiCliPlanCount: 0
159
160
  },
160
161
  projectPath: normalizePathForDisplay(resolvedPath),
161
162
  recentPaths: getRecentPaths()
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Task Routes Module
3
+ * CCW Loop System - HTTP API endpoints for Task management
4
+ * Reference: .workflow/.scratchpad/loop-system-complete-design-20260121.md section 6.1
5
+ */
6
+
7
+ import { join } from 'path';
8
+ import { readdir, readFile, writeFile } from 'fs/promises';
9
+ import { existsSync } from 'fs';
10
+ import type { RouteContext } from './types.js';
11
+ import type { Task } from '../../types/loop.js';
12
+
13
+ /**
14
+ * Handle task routes
15
+ * @returns true if route was handled, false otherwise
16
+ */
17
+ export async function handleTaskRoutes(ctx: RouteContext): Promise<boolean> {
18
+ const { pathname, req, res, initialPath, handlePostRequest } = ctx;
19
+
20
+ // Get workflow directory from initialPath
21
+ const workflowDir = initialPath || process.cwd();
22
+ const taskDir = join(workflowDir, '.task');
23
+
24
+ // GET /api/tasks - List all tasks
25
+ if (pathname === '/api/tasks' && req.method === 'GET') {
26
+ try {
27
+ // Ensure task directory exists
28
+ if (!existsSync(taskDir)) {
29
+ res.writeHead(200, { 'Content-Type': 'application/json' });
30
+ res.end(JSON.stringify({ success: true, data: [], total: 0 }));
31
+ return true;
32
+ }
33
+
34
+ // Read all task files
35
+ const files = await readdir(taskDir);
36
+ const taskFiles = files.filter(f => f.endsWith('.json'));
37
+
38
+ const tasks: Task[] = [];
39
+ for (const file of taskFiles) {
40
+ try {
41
+ const filePath = join(taskDir, file);
42
+ const content = await readFile(filePath, 'utf-8');
43
+ const task = JSON.parse(content) as Task;
44
+ tasks.push(task);
45
+ } catch (error) {
46
+ // Skip invalid task files
47
+ console.error('Failed to read task file ' + file + ':', error);
48
+ }
49
+ }
50
+
51
+ // Parse query parameters
52
+ const url = new URL(req.url || '', `http://localhost`);
53
+ const loopOnly = url.searchParams.get('loop_only') === 'true';
54
+ const filterStatus = url.searchParams.get('filter'); // active | completed
55
+
56
+ // Apply filters
57
+ let filteredTasks = tasks;
58
+
59
+ // Filter by loop_control.enabled
60
+ if (loopOnly) {
61
+ filteredTasks = filteredTasks.filter(t => t.loop_control?.enabled);
62
+ }
63
+
64
+ // Filter by status
65
+ if (filterStatus) {
66
+ filteredTasks = filteredTasks.filter(t => t.status === filterStatus);
67
+ }
68
+
69
+ res.writeHead(200, { 'Content-Type': 'application/json' });
70
+ res.end(JSON.stringify({
71
+ success: true,
72
+ data: filteredTasks,
73
+ total: filteredTasks.length,
74
+ timestamp: new Date().toISOString()
75
+ }));
76
+ return true;
77
+ } catch (error) {
78
+ res.writeHead(500, { 'Content-Type': 'application/json' });
79
+ res.end(JSON.stringify({
80
+ success: false,
81
+ error: (error as Error).message
82
+ }));
83
+ return true;
84
+ }
85
+ }
86
+
87
+ // POST /api/tasks - Create new task
88
+ if (pathname === '/api/tasks' && req.method === 'POST') {
89
+ handlePostRequest(req, res, async (body) => {
90
+ const task = body as Partial<Task>;
91
+
92
+ // Validate required fields
93
+ if (!task.id) {
94
+ return { success: false, error: 'Task ID is required', status: 400 };
95
+ }
96
+
97
+ // Sanitize taskId to prevent path traversal
98
+ if (task.id.includes('/') || task.id.includes('\\') || task.id === '..' || task.id === '.') {
99
+ return { success: false, error: 'Invalid task ID format', status: 400 };
100
+ }
101
+
102
+ if (!task.loop_control) {
103
+ return { success: false, error: 'loop_control is required', status: 400 };
104
+ }
105
+
106
+ if (!task.loop_control.enabled) {
107
+ return { success: false, error: 'loop_control.enabled must be true', status: 400 };
108
+ }
109
+
110
+ if (!task.loop_control.cli_sequence || task.loop_control.cli_sequence.length === 0) {
111
+ return { success: false, error: 'cli_sequence must contain at least one step', status: 400 };
112
+ }
113
+
114
+ try {
115
+ // Ensure task directory exists
116
+ const { mkdir } = await import('fs/promises');
117
+ if (!existsSync(taskDir)) {
118
+ await mkdir(taskDir, { recursive: true });
119
+ }
120
+
121
+ // Check if task already exists
122
+ const taskPath = join(taskDir, task.id + '.json');
123
+ if (existsSync(taskPath)) {
124
+ return { success: false, error: 'Task already exists: ' + task.id, status: 409 };
125
+ }
126
+
127
+ // Build complete task object
128
+ const fullTask: Task = {
129
+ id: task.id,
130
+ title: task.title || task.id,
131
+ description: task.description || task.loop_control?.description || '',
132
+ status: task.status || 'active',
133
+ meta: task.meta,
134
+ context: task.context,
135
+ loop_control: task.loop_control
136
+ };
137
+
138
+ // Write task file
139
+ await writeFile(taskPath, JSON.stringify(fullTask, null, 2), 'utf-8');
140
+
141
+ return {
142
+ success: true,
143
+ data: {
144
+ task: fullTask,
145
+ path: taskPath
146
+ }
147
+ };
148
+ } catch (error) {
149
+ return { success: false, error: (error as Error).message, status: 500 };
150
+ }
151
+ });
152
+ return true;
153
+ }
154
+
155
+ // GET /api/tasks/:taskId - Get single task
156
+ const taskDetailMatch = pathname.match(/^\/api\/tasks\/([^\/]+)$/);
157
+ if (taskDetailMatch && req.method === 'GET') {
158
+ const taskId = decodeURIComponent(taskDetailMatch[1]);
159
+
160
+ // Sanitize taskId to prevent path traversal
161
+ if (taskId.includes('/') || taskId.includes('\\') || taskId === '..' || taskId === '.') {
162
+ res.writeHead(400, { 'Content-Type': 'application/json' });
163
+ res.end(JSON.stringify({ success: false, error: 'Invalid task ID format' }));
164
+ return true;
165
+ }
166
+
167
+ try {
168
+ const taskPath = join(taskDir, taskId + '.json');
169
+
170
+ if (!existsSync(taskPath)) {
171
+ res.writeHead(404, { 'Content-Type': 'application/json' });
172
+ res.end(JSON.stringify({ success: false, error: 'Task not found: ' + taskId }));
173
+ return true;
174
+ }
175
+
176
+ const content = await readFile(taskPath, 'utf-8');
177
+ const task = JSON.parse(content) as Task;
178
+
179
+ res.writeHead(200, { 'Content-Type': 'application/json' });
180
+ res.end(JSON.stringify({
181
+ success: true,
182
+ data: {
183
+ task: task
184
+ }
185
+ }));
186
+ return true;
187
+ } catch (error) {
188
+ res.writeHead(500, { 'Content-Type': 'application/json' });
189
+ res.end(JSON.stringify({
190
+ success: false,
191
+ error: (error as Error).message
192
+ }));
193
+ return true;
194
+ }
195
+ }
196
+
197
+ // POST /api/tasks/validate - Validate task loop_control configuration
198
+ if (pathname === '/api/tasks/validate' && req.method === 'POST') {
199
+ handlePostRequest(req, res, async (body) => {
200
+ const task = body as Partial<Task>;
201
+ const errors: string[] = [];
202
+ const warnings: string[] = [];
203
+
204
+ // Validate loop_control
205
+ if (!task.loop_control) {
206
+ errors.push('loop_control is required');
207
+ } else {
208
+ // Check enabled flag
209
+ if (typeof task.loop_control.enabled !== 'boolean') {
210
+ errors.push('loop_control.enabled must be a boolean');
211
+ }
212
+
213
+ // Check cli_sequence
214
+ if (!task.loop_control.cli_sequence || !Array.isArray(task.loop_control.cli_sequence)) {
215
+ errors.push('loop_control.cli_sequence must be an array');
216
+ } else if (task.loop_control.cli_sequence.length === 0) {
217
+ errors.push('loop_control.cli_sequence must contain at least one step');
218
+ } else {
219
+ // Validate each step
220
+ task.loop_control.cli_sequence.forEach((step, index) => {
221
+ if (!step.step_id) {
222
+ errors.push(`Step ${index + 1}: step_id is required`);
223
+ }
224
+ if (!step.tool) {
225
+ errors.push(`Step ${index + 1}: tool is required`);
226
+ } else if (!['gemini', 'qwen', 'codex', 'claude', 'bash'].includes(step.tool)) {
227
+ warnings.push(`Step ${index + 1}: unknown tool '${step.tool}'`);
228
+ }
229
+ if (!step.prompt_template && step.tool !== 'bash') {
230
+ errors.push(`Step ${index + 1}: prompt_template is required for non-bash steps`);
231
+ }
232
+ });
233
+ }
234
+
235
+ // Check max_iterations
236
+ if (task.loop_control.max_iterations !== undefined) {
237
+ if (typeof task.loop_control.max_iterations !== 'number' || task.loop_control.max_iterations < 1) {
238
+ errors.push('loop_control.max_iterations must be a positive number');
239
+ }
240
+ if (task.loop_control.max_iterations > 100) {
241
+ warnings.push('max_iterations > 100 may cause long execution times');
242
+ }
243
+ }
244
+ }
245
+
246
+ // Return validation result
247
+ const isValid = errors.length === 0;
248
+ return {
249
+ success: true,
250
+ data: {
251
+ valid: isValid,
252
+ errors,
253
+ warnings
254
+ }
255
+ };
256
+ });
257
+ return true;
258
+ }
259
+
260
+ // PUT /api/tasks/:taskId - Update existing task
261
+ if (pathname.match(/^\/api\/tasks\/[^/]+$/) && req.method === 'PUT') {
262
+ const taskId = pathname.split('/').pop();
263
+ if (!taskId) {
264
+ res.writeHead(400, { 'Content-Type': 'application/json' });
265
+ res.end(JSON.stringify({ success: false, error: 'Task ID required' }));
266
+ return true;
267
+ }
268
+
269
+ // Sanitize taskId to prevent path traversal
270
+ if (taskId.includes('/') || taskId.includes('\\') || taskId === '..' || taskId === '.') {
271
+ res.writeHead(400, { 'Content-Type': 'application/json' });
272
+ res.end(JSON.stringify({ success: false, error: 'Invalid task ID format' }));
273
+ return true;
274
+ }
275
+
276
+ handlePostRequest(req, res, async (body) => {
277
+ const updates = body as Partial<Task>;
278
+ const taskPath = join(taskDir, taskId + '.json');
279
+
280
+ // Check if task exists
281
+ if (!existsSync(taskPath)) {
282
+ return { success: false, error: 'Task not found: ' + taskId, status: 404 };
283
+ }
284
+
285
+ try {
286
+ // Read existing task
287
+ const existingContent = await readFile(taskPath, 'utf-8');
288
+ const existingTask = JSON.parse(existingContent) as Task;
289
+
290
+ // Merge updates (preserve id)
291
+ const updatedTask: Task = {
292
+ ...existingTask,
293
+ ...updates,
294
+ id: existingTask.id // Prevent id change
295
+ };
296
+
297
+ // If loop_control is being updated, merge it properly
298
+ if (updates.loop_control) {
299
+ updatedTask.loop_control = {
300
+ ...existingTask.loop_control,
301
+ ...updates.loop_control
302
+ };
303
+ }
304
+
305
+ // Write updated task
306
+ await writeFile(taskPath, JSON.stringify(updatedTask, null, 2), 'utf-8');
307
+
308
+ return {
309
+ success: true,
310
+ data: {
311
+ task: updatedTask,
312
+ path: taskPath
313
+ }
314
+ };
315
+ } catch (error) {
316
+ return { success: false, error: (error as Error).message, status: 500 };
317
+ }
318
+ });
319
+ return true;
320
+ }
321
+
322
+ // GET /api/tasks/:taskId - Get specific task
323
+ if (pathname.match(/^\/api\/tasks\/[^/]+$/) && req.method === 'GET') {
324
+ const taskId = pathname.split('/').pop();
325
+ if (!taskId) {
326
+ res.writeHead(400, { 'Content-Type': 'application/json' });
327
+ res.end(JSON.stringify({ success: false, error: 'Task ID required' }));
328
+ return true;
329
+ }
330
+
331
+ // Sanitize taskId to prevent path traversal
332
+ if (taskId.includes('/') || taskId.includes('\\') || taskId === '..' || taskId === '.') {
333
+ res.writeHead(400, { 'Content-Type': 'application/json' });
334
+ res.end(JSON.stringify({ success: false, error: 'Invalid task ID format' }));
335
+ return true;
336
+ }
337
+
338
+ try {
339
+ const taskPath = join(taskDir, taskId + '.json');
340
+
341
+ if (!existsSync(taskPath)) {
342
+ res.writeHead(404, { 'Content-Type': 'application/json' });
343
+ res.end(JSON.stringify({ success: false, error: 'Task not found' }));
344
+ return true;
345
+ }
346
+
347
+ const content = await readFile(taskPath, 'utf-8');
348
+ const task = JSON.parse(content) as Task;
349
+
350
+ res.writeHead(200, { 'Content-Type': 'application/json' });
351
+ res.end(JSON.stringify({ success: true, data: task }));
352
+ return true;
353
+ } catch (error) {
354
+ res.writeHead(500, { 'Content-Type': 'application/json' });
355
+ res.end(JSON.stringify({ success: false, error: (error as Error).message }));
356
+ return true;
357
+ }
358
+ }
359
+
360
+ return false;
361
+ }