claude-code-workflow 6.3.34 → 6.3.37

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 (162) hide show
  1. package/.claude/agents/cli-execution-agent.md +61 -0
  2. package/.claude/agents/cli-lite-planning-agent.md +322 -36
  3. package/.claude/agents/issue-plan-agent.md +95 -11
  4. package/.claude/commands/cli/codex-review.md +8 -2
  5. package/.claude/commands/issue/plan.md +20 -6
  6. package/.claude/commands/workflow/lite-execute.md +56 -16
  7. package/.claude/commands/workflow/lite-fix.md +108 -9
  8. package/.claude/commands/workflow/lite-plan.md +1 -1
  9. package/.claude/skills/ccw-loop/README.md +303 -0
  10. package/.claude/skills/ccw-loop/SKILL.md +259 -0
  11. package/.claude/skills/ccw-loop/phases/actions/action-complete.md +320 -0
  12. package/.claude/skills/ccw-loop/phases/actions/action-debug-with-file.md +485 -0
  13. package/.claude/skills/ccw-loop/phases/actions/action-develop-with-file.md +365 -0
  14. package/.claude/skills/ccw-loop/phases/actions/action-init.md +200 -0
  15. package/.claude/skills/ccw-loop/phases/actions/action-menu.md +192 -0
  16. package/.claude/skills/ccw-loop/phases/actions/action-validate-with-file.md +307 -0
  17. package/.claude/skills/ccw-loop/phases/orchestrator.md +486 -0
  18. package/.claude/skills/ccw-loop/phases/state-schema.md +474 -0
  19. package/.claude/skills/ccw-loop/specs/action-catalog.md +300 -0
  20. package/.claude/skills/ccw-loop/specs/loop-requirements.md +192 -0
  21. package/.claude/skills/ccw-loop/templates/progress-template.md +175 -0
  22. package/.claude/skills/ccw-loop/templates/understanding-template.md +303 -0
  23. package/.claude/skills/ccw-loop/templates/validation-template.md +258 -0
  24. package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +29 -0
  25. package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +200 -0
  26. package/.codex/prompts/debug-with-file.md +609 -0
  27. package/ccw/dist/cli.d.ts.map +1 -1
  28. package/ccw/dist/cli.js +8 -1
  29. package/ccw/dist/cli.js.map +1 -1
  30. package/ccw/dist/commands/cli.d.ts.map +1 -1
  31. package/ccw/dist/commands/cli.js +16 -4
  32. package/ccw/dist/commands/cli.js.map +1 -1
  33. package/ccw/dist/commands/issue.d.ts.map +1 -1
  34. package/ccw/dist/commands/issue.js +37 -4
  35. package/ccw/dist/commands/issue.js.map +1 -1
  36. package/ccw/dist/commands/loop.d.ts +10 -0
  37. package/ccw/dist/commands/loop.d.ts.map +1 -0
  38. package/ccw/dist/commands/loop.js +289 -0
  39. package/ccw/dist/commands/loop.js.map +1 -0
  40. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  41. package/ccw/dist/core/dashboard-generator.js +4 -1
  42. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  43. package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
  44. package/ccw/dist/core/routes/claude-routes.js +5 -3
  45. package/ccw/dist/core/routes/claude-routes.js.map +1 -1
  46. package/ccw/dist/core/routes/cli-routes.d.ts +6 -0
  47. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  48. package/ccw/dist/core/routes/cli-routes.js +42 -13
  49. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  50. package/ccw/dist/core/routes/cli-settings-routes.d.ts.map +1 -1
  51. package/ccw/dist/core/routes/cli-settings-routes.js +44 -0
  52. package/ccw/dist/core/routes/cli-settings-routes.js.map +1 -1
  53. package/ccw/dist/core/routes/codexlens/semantic-handlers.d.ts.map +1 -1
  54. package/ccw/dist/core/routes/codexlens/semantic-handlers.js +3 -2
  55. package/ccw/dist/core/routes/codexlens/semantic-handlers.js.map +1 -1
  56. package/ccw/dist/core/routes/core-memory-routes.d.ts.map +1 -1
  57. package/ccw/dist/core/routes/core-memory-routes.js +4 -2
  58. package/ccw/dist/core/routes/core-memory-routes.js.map +1 -1
  59. package/ccw/dist/core/routes/files-routes.d.ts.map +1 -1
  60. package/ccw/dist/core/routes/files-routes.js +4 -2
  61. package/ccw/dist/core/routes/files-routes.js.map +1 -1
  62. package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
  63. package/ccw/dist/core/routes/hooks-routes.js +3 -0
  64. package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
  65. package/ccw/dist/core/routes/loop-routes.d.ts +24 -0
  66. package/ccw/dist/core/routes/loop-routes.d.ts.map +1 -0
  67. package/ccw/dist/core/routes/loop-routes.js +334 -0
  68. package/ccw/dist/core/routes/loop-routes.js.map +1 -0
  69. package/ccw/dist/core/routes/loop-v2-routes.d.ts +35 -0
  70. package/ccw/dist/core/routes/loop-v2-routes.d.ts.map +1 -0
  71. package/ccw/dist/core/routes/loop-v2-routes.js +1208 -0
  72. package/ccw/dist/core/routes/loop-v2-routes.js.map +1 -0
  73. package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
  74. package/ccw/dist/core/routes/memory-routes.js +2 -1
  75. package/ccw/dist/core/routes/memory-routes.js.map +1 -1
  76. package/ccw/dist/core/routes/task-routes.d.ts +12 -0
  77. package/ccw/dist/core/routes/task-routes.d.ts.map +1 -0
  78. package/ccw/dist/core/routes/task-routes.js +321 -0
  79. package/ccw/dist/core/routes/task-routes.js.map +1 -0
  80. package/ccw/dist/core/routes/test-loop-routes.d.ts +11 -0
  81. package/ccw/dist/core/routes/test-loop-routes.d.ts.map +1 -0
  82. package/ccw/dist/core/routes/test-loop-routes.js +298 -0
  83. package/ccw/dist/core/routes/test-loop-routes.js.map +1 -0
  84. package/ccw/dist/core/server.d.ts.map +1 -1
  85. package/ccw/dist/core/server.js +43 -3
  86. package/ccw/dist/core/server.js.map +1 -1
  87. package/ccw/dist/core/websocket.d.ts +59 -0
  88. package/ccw/dist/core/websocket.d.ts.map +1 -1
  89. package/ccw/dist/core/websocket.js +34 -0
  90. package/ccw/dist/core/websocket.js.map +1 -1
  91. package/ccw/dist/tools/claude-cli-tools.d.ts +40 -0
  92. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  93. package/ccw/dist/tools/claude-cli-tools.js +119 -0
  94. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  95. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  96. package/ccw/dist/tools/codex-lens.js +66 -47
  97. package/ccw/dist/tools/codex-lens.js.map +1 -1
  98. package/ccw/dist/tools/loop-manager.d.ts +84 -0
  99. package/ccw/dist/tools/loop-manager.d.ts.map +1 -0
  100. package/ccw/dist/tools/loop-manager.js +425 -0
  101. package/ccw/dist/tools/loop-manager.js.map +1 -0
  102. package/ccw/dist/tools/loop-state-manager.d.ts +47 -0
  103. package/ccw/dist/tools/loop-state-manager.d.ts.map +1 -0
  104. package/ccw/dist/tools/loop-state-manager.js +149 -0
  105. package/ccw/dist/tools/loop-state-manager.js.map +1 -0
  106. package/ccw/dist/tools/loop-task-manager.d.ts +138 -0
  107. package/ccw/dist/tools/loop-task-manager.d.ts.map +1 -0
  108. package/ccw/dist/tools/loop-task-manager.js +270 -0
  109. package/ccw/dist/tools/loop-task-manager.js.map +1 -0
  110. package/ccw/dist/types/index.d.ts +1 -0
  111. package/ccw/dist/types/index.d.ts.map +1 -1
  112. package/ccw/dist/types/index.js +1 -0
  113. package/ccw/dist/types/index.js.map +1 -1
  114. package/ccw/dist/types/loop.d.ts +257 -0
  115. package/ccw/dist/types/loop.d.ts.map +1 -0
  116. package/ccw/dist/types/loop.js +17 -0
  117. package/ccw/dist/types/loop.js.map +1 -0
  118. package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +2 -2
  119. package/ccw/scripts/QUICK-REFERENCE.md +1 -1
  120. package/ccw/scripts/README-memory-embedder.md +1 -1
  121. package/ccw/scripts/memory_embedder.py +1 -1
  122. package/ccw/src/cli.ts +9 -1
  123. package/ccw/src/commands/cli.ts +16 -4
  124. package/ccw/src/commands/issue.ts +41 -5
  125. package/ccw/src/commands/loop.ts +344 -0
  126. package/ccw/src/core/dashboard-generator.ts +4 -1
  127. package/ccw/src/core/routes/claude-routes.ts +5 -3
  128. package/ccw/src/core/routes/cli-routes.ts +47 -15
  129. package/ccw/src/core/routes/cli-settings-routes.ts +47 -0
  130. package/ccw/src/core/routes/codexlens/semantic-handlers.ts +3 -2
  131. package/ccw/src/core/routes/core-memory-routes.ts +4 -2
  132. package/ccw/src/core/routes/files-routes.ts +4 -2
  133. package/ccw/src/core/routes/hooks-routes.ts +3 -0
  134. package/ccw/src/core/routes/loop-routes.ts +386 -0
  135. package/ccw/src/core/routes/loop-v2-routes.ts +1412 -0
  136. package/ccw/src/core/routes/memory-routes.ts +2 -1
  137. package/ccw/src/core/routes/task-routes.ts +361 -0
  138. package/ccw/src/core/routes/test-loop-routes.ts +312 -0
  139. package/ccw/src/core/server.ts +44 -3
  140. package/ccw/src/core/websocket.ts +104 -0
  141. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +56 -0
  142. package/ccw/src/templates/dashboard-css/32-issue-manager.css +160 -0
  143. package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +57 -2
  144. package/ccw/src/templates/dashboard-css/36-loop-monitor.css +1896 -0
  145. package/ccw/src/templates/dashboard-css/36-loop-monitor.css.backup +1877 -0
  146. package/ccw/src/templates/dashboard-js/components/cli-status.js +64 -3
  147. package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +251 -110
  148. package/ccw/src/templates/dashboard-js/components/navigation.js +10 -0
  149. package/ccw/src/templates/dashboard-js/components/notifications.js +16 -0
  150. package/ccw/src/templates/dashboard-js/i18n.js +475 -1
  151. package/ccw/src/templates/dashboard-js/views/cli-manager.js +3 -2
  152. package/ccw/src/templates/dashboard-js/views/issue-manager.js +159 -0
  153. package/ccw/src/templates/dashboard-js/views/loop-monitor.js +3244 -0
  154. package/ccw/src/templates/dashboard.html +20 -2
  155. package/ccw/src/tools/claude-cli-tools.ts +143 -0
  156. package/ccw/src/tools/codex-lens.ts +71 -44
  157. package/ccw/src/tools/loop-manager.ts +519 -0
  158. package/ccw/src/tools/loop-state-manager.ts +173 -0
  159. package/ccw/src/tools/loop-task-manager.ts +380 -0
  160. package/ccw/src/types/index.ts +1 -0
  161. package/ccw/src/types/loop.ts +316 -0
  162. package/package.json +1 -1
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Loop Command
3
+ * CCW Loop System - CLI interface for loop management
4
+ * Reference: .workflow/.scratchpad/loop-system-complete-design-20260121.md section 4.3
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+ import { readFile } from 'fs/promises';
9
+ import { join, resolve } from 'path';
10
+ import { existsSync } from 'fs';
11
+ import { LoopManager } from '../tools/loop-manager.js';
12
+ import type { TaskLoopControl } from '../types/loop.js';
13
+
14
+ // Minimal Task interface for task config files
15
+ interface Task {
16
+ id: string;
17
+ title?: string;
18
+ loop_control?: TaskLoopControl;
19
+ }
20
+
21
+ /**
22
+ * Read task configuration
23
+ */
24
+ async function readTaskConfig(taskId: string, workflowDir: string): Promise<Task> {
25
+ const taskFile = join(workflowDir, '.task', `${taskId}.json`);
26
+
27
+ if (!existsSync(taskFile)) {
28
+ throw new Error(`Task file not found: ${taskFile}`);
29
+ }
30
+
31
+ const content = await readFile(taskFile, 'utf-8');
32
+ return JSON.parse(content) as Task;
33
+ }
34
+
35
+ /**
36
+ * Find active workflow session
37
+ */
38
+ function findActiveSession(cwd: string): string | null {
39
+ const workflowDir = join(cwd, '.workflow', 'active');
40
+
41
+ if (!existsSync(workflowDir)) {
42
+ return null;
43
+ }
44
+
45
+ const { readdirSync } = require('fs');
46
+ const sessions = readdirSync(workflowDir).filter((d: string) => d.startsWith('WFS-'));
47
+
48
+ if (sessions.length === 0) {
49
+ return null;
50
+ }
51
+
52
+ if (sessions.length === 1) {
53
+ return join(cwd, '.workflow', 'active', sessions[0]);
54
+ }
55
+
56
+ // Multiple sessions, require user to specify
57
+ console.error(chalk.red('\n Error: Multiple active sessions found:'));
58
+ sessions.forEach((s: string) => console.error(chalk.gray(` - ${s}`)));
59
+ console.error(chalk.yellow('\n Please specify session with --session <name>\n'));
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Get status badge with color
65
+ */
66
+ function getStatusBadge(status: string): string {
67
+ switch (status) {
68
+ case 'created':
69
+ return chalk.gray('○ created');
70
+ case 'running':
71
+ return chalk.cyan('● running');
72
+ case 'paused':
73
+ return chalk.yellow('⏸ paused');
74
+ case 'completed':
75
+ return chalk.green('✓ completed');
76
+ case 'failed':
77
+ return chalk.red('✗ failed');
78
+ default:
79
+ return status;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Format time ago
85
+ */
86
+ function timeAgo(timestamp: string): string {
87
+ const now = Date.now();
88
+ const then = new Date(timestamp).getTime();
89
+ const diff = Math.floor((now - then) / 1000);
90
+
91
+ if (diff < 60) return `${diff}s ago`;
92
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
93
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
94
+ return `${Math.floor(diff / 86400)}d ago`;
95
+ }
96
+
97
+ /**
98
+ * Start action
99
+ */
100
+ async function startAction(taskId: string, options: { session?: string }): Promise<void> {
101
+ const currentCwd = process.cwd();
102
+
103
+ // Find workflow session
104
+ let sessionDir: string | null;
105
+
106
+ if (options.session) {
107
+ sessionDir = join(currentCwd, '.workflow', 'active', options.session);
108
+ if (!existsSync(sessionDir)) {
109
+ console.error(chalk.red(`\n Error: Session not found: ${options.session}\n`));
110
+ process.exit(1);
111
+ }
112
+ } else {
113
+ sessionDir = findActiveSession(currentCwd);
114
+ if (!sessionDir) {
115
+ console.error(chalk.red('\n Error: No active workflow session found.'));
116
+ console.error(chalk.gray(' Run "ccw workflow:plan" first to create a session.\n'));
117
+ process.exit(1);
118
+ }
119
+ }
120
+
121
+ console.log(chalk.cyan(` Using session: ${sessionDir.split(/[\\/]/).pop()}`));
122
+
123
+ // Read task config
124
+ const task = await readTaskConfig(taskId, sessionDir);
125
+
126
+ if (!task.loop_control?.enabled) {
127
+ console.error(chalk.red(`\n Error: Task ${taskId} does not have loop enabled.\n`));
128
+ process.exit(1);
129
+ }
130
+
131
+ // Start loop
132
+ const loopManager = new LoopManager(sessionDir);
133
+ const loopId = await loopManager.startLoop(task as any); // Task interface compatible
134
+
135
+ console.log(chalk.green(`\n ✓ Loop started: ${loopId}`));
136
+ console.log(chalk.dim(` Status: ccw loop status ${loopId}`));
137
+ console.log(chalk.dim(` Pause: ccw loop pause ${loopId}`));
138
+ console.log(chalk.dim(` Stop: ccw loop stop ${loopId}\n`));
139
+ }
140
+
141
+ /**
142
+ * Status action
143
+ */
144
+ async function statusAction(loopId: string | undefined, options: { session?: string }): Promise<void> {
145
+ const currentCwd = process.cwd();
146
+ const sessionDir = options?.session
147
+ ? join(currentCwd, '.workflow', 'active', options.session)
148
+ : findActiveSession(currentCwd);
149
+
150
+ if (!sessionDir) {
151
+ console.error(chalk.red('\n Error: No active session found.\n'));
152
+ process.exit(1);
153
+ }
154
+
155
+ const loopManager = new LoopManager(sessionDir);
156
+
157
+ if (loopId) {
158
+ // Show single loop detail
159
+ const state = await loopManager.getStatus(loopId);
160
+
161
+ console.log(chalk.bold.cyan('\n Loop Status\n'));
162
+ console.log(` ${chalk.gray('ID:')} ${state.loop_id}`);
163
+ console.log(` ${chalk.gray('Task:')} ${state.task_id}`);
164
+ console.log(` ${chalk.gray('Status:')} ${getStatusBadge(state.status)}`);
165
+ console.log(` ${chalk.gray('Iteration:')} ${state.current_iteration}/${state.max_iterations}`);
166
+ console.log(` ${chalk.gray('Step:')} ${state.current_cli_step + 1}/${state.cli_sequence.length}`);
167
+ console.log(` ${chalk.gray('Created:')} ${state.created_at}`);
168
+ console.log(` ${chalk.gray('Updated:')} ${state.updated_at}`);
169
+
170
+ if (state.failure_reason) {
171
+ console.log(` ${chalk.gray('Reason:')} ${chalk.red(state.failure_reason)}`);
172
+ }
173
+
174
+ console.log(chalk.bold.cyan('\n CLI Sequence\n'));
175
+ state.cli_sequence.forEach((step, i) => {
176
+ const current = i === state.current_cli_step ? chalk.cyan('→') : ' ';
177
+ console.log(` ${current} ${i + 1}. ${chalk.bold(step.step_id)} (${step.tool})`);
178
+ });
179
+
180
+ if (state.execution_history && state.execution_history.length > 0) {
181
+ console.log(chalk.bold.cyan('\n Recent Executions\n'));
182
+ const recent = state.execution_history.slice(-5);
183
+ recent.forEach(exec => {
184
+ const status = exec.exit_code === 0 ? chalk.green('✓') : chalk.red('✗');
185
+ console.log(` ${status} ${exec.step_id} (${exec.tool}) - ${(exec.duration_ms / 1000).toFixed(1)}s`);
186
+ });
187
+ }
188
+
189
+ console.log();
190
+ } else {
191
+ // List all loops
192
+ const loops = await loopManager.listLoops();
193
+
194
+ if (loops.length === 0) {
195
+ console.log(chalk.yellow('\n No loops found.\n'));
196
+ return;
197
+ }
198
+
199
+ console.log(chalk.bold.cyan('\n Active Loops\n'));
200
+ console.log(chalk.gray(' Status ID Iteration Task'));
201
+ console.log(chalk.gray(' ' + '─'.repeat(70)));
202
+
203
+ loops.forEach(loop => {
204
+ const status = getStatusBadge(loop.status);
205
+ const iteration = `${loop.current_iteration}/${loop.max_iterations}`;
206
+ console.log(` ${status} ${chalk.dim(loop.loop_id.padEnd(35))} ${iteration.padEnd(9)} ${loop.task_id}`);
207
+ });
208
+
209
+ console.log();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Pause action
215
+ */
216
+ async function pauseAction(loopId: string, options: { session?: string }): Promise<void> {
217
+ const currentCwd = process.cwd();
218
+ const sessionDir = options.session
219
+ ? join(currentCwd, '.workflow', 'active', options.session)
220
+ : findActiveSession(currentCwd);
221
+
222
+ if (!sessionDir) {
223
+ console.error(chalk.red('\n Error: No active session found.\n'));
224
+ process.exit(1);
225
+ }
226
+
227
+ const loopManager = new LoopManager(sessionDir);
228
+ await loopManager.pauseLoop(loopId);
229
+ }
230
+
231
+ /**
232
+ * Resume action
233
+ */
234
+ async function resumeAction(loopId: string, options: { session?: string }): Promise<void> {
235
+ const currentCwd = process.cwd();
236
+ const sessionDir = options.session
237
+ ? join(currentCwd, '.workflow', 'active', options.session)
238
+ : findActiveSession(currentCwd);
239
+
240
+ if (!sessionDir) {
241
+ console.error(chalk.red('\n Error: No active session found.\n'));
242
+ process.exit(1);
243
+ }
244
+
245
+ const loopManager = new LoopManager(sessionDir);
246
+ await loopManager.resumeLoop(loopId);
247
+ }
248
+
249
+ /**
250
+ * Stop action
251
+ */
252
+ async function stopAction(loopId: string, options: { session?: string }): Promise<void> {
253
+ const currentCwd = process.cwd();
254
+ const sessionDir = options.session
255
+ ? join(currentCwd, '.workflow', 'active', options.session)
256
+ : findActiveSession(currentCwd);
257
+
258
+ if (!sessionDir) {
259
+ console.error(chalk.red('\n Error: No active session found.\n'));
260
+ process.exit(1);
261
+ }
262
+
263
+ const loopManager = new LoopManager(sessionDir);
264
+ await loopManager.stopLoop(loopId);
265
+ }
266
+
267
+ /**
268
+ * Loop command entry point
269
+ */
270
+ export async function loopCommand(
271
+ subcommand: string,
272
+ args: string | string[],
273
+ options: any
274
+ ): Promise<void> {
275
+ const argsArray = Array.isArray(args) ? args : (args ? [args] : []);
276
+
277
+ try {
278
+ switch (subcommand) {
279
+ case 'start':
280
+ if (!argsArray[0]) {
281
+ console.error(chalk.red('\n Error: Task ID is required\n'));
282
+ console.error(chalk.gray(' Usage: ccw loop start <task-id> [--session <name>]\n'));
283
+ process.exit(1);
284
+ }
285
+ await startAction(argsArray[0], options);
286
+ break;
287
+
288
+ case 'status':
289
+ await statusAction(argsArray[0], options);
290
+ break;
291
+
292
+ case 'pause':
293
+ if (!argsArray[0]) {
294
+ console.error(chalk.red('\n Error: Loop ID is required\n'));
295
+ console.error(chalk.gray(' Usage: ccw loop pause <loop-id>\n'));
296
+ process.exit(1);
297
+ }
298
+ await pauseAction(argsArray[0], options);
299
+ break;
300
+
301
+ case 'resume':
302
+ if (!argsArray[0]) {
303
+ console.error(chalk.red('\n Error: Loop ID is required\n'));
304
+ console.error(chalk.gray(' Usage: ccw loop resume <loop-id>\n'));
305
+ process.exit(1);
306
+ }
307
+ await resumeAction(argsArray[0], options);
308
+ break;
309
+
310
+ case 'stop':
311
+ if (!argsArray[0]) {
312
+ console.error(chalk.red('\n Error: Loop ID is required\n'));
313
+ console.error(chalk.gray(' Usage: ccw loop stop <loop-id>\n'));
314
+ process.exit(1);
315
+ }
316
+ await stopAction(argsArray[0], options);
317
+ break;
318
+
319
+ default:
320
+ // Show help
321
+ console.log(chalk.bold.cyan('\n CCW Loop System\n'));
322
+ console.log(' Manage automated CLI execution loops\n');
323
+ console.log(' Subcommands:');
324
+ console.log(chalk.gray(' start <task-id> Start a new loop from task configuration'));
325
+ console.log(chalk.gray(' status [loop-id] Show loop status (all or specific)'));
326
+ console.log(chalk.gray(' pause <loop-id> Pause a running loop'));
327
+ console.log(chalk.gray(' resume <loop-id> Resume a paused loop'));
328
+ console.log(chalk.gray(' stop <loop-id> Stop a loop'));
329
+ console.log();
330
+ console.log(' Options:');
331
+ console.log(chalk.gray(' --session <name> Specify workflow session'));
332
+ console.log();
333
+ console.log(' Examples:');
334
+ console.log(chalk.gray(' ccw loop start IMPL-3'));
335
+ console.log(chalk.gray(' ccw loop status'));
336
+ console.log(chalk.gray(' ccw loop status loop-IMPL-3-20260121120000'));
337
+ console.log(chalk.gray(' ccw loop pause loop-IMPL-3-20260121120000'));
338
+ console.log();
339
+ }
340
+ } catch (error) {
341
+ console.error(chalk.red(`\n ✗ Error: ${error instanceof Error ? error.message : error}\n`));
342
+ process.exit(1);
343
+ }
344
+ }
@@ -99,7 +99,10 @@ const MODULE_CSS_FILES = [
99
99
  '29-help.css',
100
100
  '30-core-memory.css',
101
101
  '31-api-settings.css',
102
- '34-discovery.css'
102
+ '32-issue-manager.css',
103
+ '33-cli-stream-viewer.css',
104
+ '34-discovery.css',
105
+ '36-loop-monitor.css'
103
106
  ];
104
107
 
105
108
  const MODULE_FILES = [
@@ -6,6 +6,7 @@ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, unlinkS
6
6
  import { dirname, join, relative } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import type { RouteContext } from './types.js';
9
+ import { getDefaultTool } from '../../tools/claude-cli-tools.js';
9
10
 
10
11
  interface ClaudeFile {
11
12
  id: string;
@@ -549,7 +550,8 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
549
550
  // API: CLI Sync (analyze and update CLAUDE.md using CLI tools)
550
551
  if (pathname === '/api/memory/claude/sync' && req.method === 'POST') {
551
552
  handlePostRequest(req, res, async (body: any) => {
552
- const { level, path: modulePath, tool = 'gemini', mode = 'update', targets } = body;
553
+ const { level, path: modulePath, tool, mode = 'update', targets } = body;
554
+ const resolvedTool = tool || getDefaultTool(initialPath);
553
555
 
554
556
  if (!level) {
555
557
  return { error: 'Missing level parameter', status: 400 };
@@ -598,7 +600,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
598
600
  type: 'CLI_EXECUTION_STARTED',
599
601
  payload: {
600
602
  executionId: syncId,
601
- tool: tool === 'qwen' ? 'qwen' : 'gemini',
603
+ tool: resolvedTool,
602
604
  mode: 'analysis',
603
605
  category: 'internal',
604
606
  context: 'claude-sync',
@@ -629,7 +631,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
629
631
 
630
632
  const startTime = Date.now();
631
633
  const result = await executeCliTool({
632
- tool: tool === 'qwen' ? 'qwen' : 'gemini',
634
+ tool: resolvedTool,
633
635
  prompt: cliPrompt,
634
636
  mode: 'analysis',
635
637
  format: 'plain',
@@ -60,9 +60,35 @@ interface ActiveExecution {
60
60
  startTime: number;
61
61
  output: string;
62
62
  status: 'running' | 'completed' | 'error';
63
+ completedTimestamp?: number; // When execution completed (for 5-minute retention)
63
64
  }
64
65
 
65
66
  const activeExecutions = new Map<string, ActiveExecution>();
67
+ const EXECUTION_RETENTION_MS = 5 * 60 * 1000; // 5 minutes
68
+
69
+ /**
70
+ * Cleanup stale completed executions older than retention period
71
+ * Runs periodically to prevent memory buildup
72
+ */
73
+ export function cleanupStaleExecutions(): void {
74
+ const now = Date.now();
75
+ const staleIds: string[] = [];
76
+
77
+ for (const [id, exec] of activeExecutions.entries()) {
78
+ if (exec.completedTimestamp && (now - exec.completedTimestamp) > EXECUTION_RETENTION_MS) {
79
+ staleIds.push(id);
80
+ }
81
+ }
82
+
83
+ staleIds.forEach(id => {
84
+ activeExecutions.delete(id);
85
+ console.log(`[ActiveExec] Cleaned up stale execution: ${id}`);
86
+ });
87
+
88
+ if (staleIds.length > 0) {
89
+ console.log(`[ActiveExec] Cleaned up ${staleIds.length} stale execution(s), remaining: ${activeExecutions.size}`);
90
+ }
91
+ }
66
92
 
67
93
  /**
68
94
  * Get all active CLI executions
@@ -113,19 +139,12 @@ export function updateActiveExecution(event: {
113
139
  activeExec.output += output;
114
140
  }
115
141
  } else if (type === 'completed') {
116
- // Mark as completed instead of immediately deleting
117
- // Keep execution visible for 5 minutes to allow page refreshes to see it
142
+ // Mark as completed with timestamp for retention-based cleanup
118
143
  const activeExec = activeExecutions.get(executionId);
119
144
  if (activeExec) {
120
145
  activeExec.status = success ? 'completed' : 'error';
121
-
122
- // Auto-cleanup after 5 minutes
123
- setTimeout(() => {
124
- activeExecutions.delete(executionId);
125
- console.log(`[ActiveExec] Auto-cleaned completed execution: ${executionId}`);
126
- }, 5 * 60 * 1000);
127
-
128
- console.log(`[ActiveExec] Marked as ${activeExec.status}, will auto-clean in 5 minutes`);
146
+ activeExec.completedTimestamp = Date.now();
147
+ console.log(`[ActiveExec] Marked as ${activeExec.status}, retained for ${EXECUTION_RETENTION_MS / 1000}s`);
129
148
  }
130
149
  }
131
150
  }
@@ -139,7 +158,10 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
139
158
 
140
159
  // API: Get Active CLI Executions (for state recovery)
141
160
  if (pathname === '/api/cli/active' && req.method === 'GET') {
142
- const executions = getActiveExecutions();
161
+ const executions = getActiveExecutions().map(exec => ({
162
+ ...exec,
163
+ isComplete: exec.status !== 'running'
164
+ }));
143
165
  res.writeHead(200, { 'Content-Type': 'application/json' });
144
166
  res.end(JSON.stringify({ executions }));
145
167
  return true;
@@ -664,8 +686,13 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
664
686
  });
665
687
  });
666
688
 
667
- // Remove from active executions on completion
668
- activeExecutions.delete(executionId);
689
+ // Mark as completed with timestamp for retention-based cleanup (not immediate delete)
690
+ const activeExec = activeExecutions.get(executionId);
691
+ if (activeExec) {
692
+ activeExec.status = result.success ? 'completed' : 'error';
693
+ activeExec.completedTimestamp = Date.now();
694
+ console.log(`[ActiveExec] Direct execution ${executionId} marked as ${activeExec.status}, retained for ${EXECUTION_RETENTION_MS / 1000}s`);
695
+ }
669
696
 
670
697
  // Broadcast completion
671
698
  broadcastToClients({
@@ -684,8 +711,13 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
684
711
  };
685
712
 
686
713
  } catch (error: unknown) {
687
- // Remove from active executions on error
688
- activeExecutions.delete(executionId);
714
+ // Mark as completed with timestamp for retention-based cleanup (not immediate delete)
715
+ const activeExec = activeExecutions.get(executionId);
716
+ if (activeExec) {
717
+ activeExec.status = 'error';
718
+ activeExec.completedTimestamp = Date.now();
719
+ console.log(`[ActiveExec] Direct execution ${executionId} marked as error, retained for ${EXECUTION_RETENTION_MS / 1000}s`);
720
+ }
689
721
 
690
722
  broadcastToClients({
691
723
  type: 'CLI_EXECUTION_ERROR',
@@ -16,6 +16,7 @@ import {
16
16
  } from '../../config/cli-settings-manager.js';
17
17
  import type { SaveEndpointRequest } from '../../types/cli-settings.js';
18
18
  import { validateSettings } from '../../types/cli-settings.js';
19
+ import { syncBuiltinToolsAvailability, getBuiltinToolsSyncReport } from '../../tools/claude-cli-tools.js';
19
20
 
20
21
  /**
21
22
  * Handle CLI Settings routes
@@ -228,5 +229,51 @@ export async function handleCliSettingsRoutes(ctx: RouteContext): Promise<boolea
228
229
  return true;
229
230
  }
230
231
 
232
+ // ========== SYNC BUILTIN TOOLS AVAILABILITY ==========
233
+ // POST /api/cli/settings/sync-tools
234
+ if (pathname === '/api/cli/settings/sync-tools' && req.method === 'POST') {
235
+ handlePostRequest(req, res, async (body: any) => {
236
+ const { initialPath } = ctx;
237
+ try {
238
+ const result = await syncBuiltinToolsAvailability(initialPath);
239
+
240
+ // Broadcast update event
241
+ broadcastToClients({
242
+ type: 'CLI_TOOLS_CONFIG_UPDATED',
243
+ payload: {
244
+ tools: result.config,
245
+ timestamp: new Date().toISOString()
246
+ }
247
+ });
248
+
249
+ res.writeHead(200, { 'Content-Type': 'application/json' });
250
+ res.end(JSON.stringify({
251
+ success: true,
252
+ changes: result.changes,
253
+ config: result.config
254
+ }));
255
+ } catch (err) {
256
+ res.writeHead(500, { 'Content-Type': 'application/json' });
257
+ res.end(JSON.stringify({ error: (err as Error).message }));
258
+ }
259
+ });
260
+ return true;
261
+ }
262
+
263
+ // GET /api/cli/settings/sync-report
264
+ if (pathname === '/api/cli/settings/sync-report' && req.method === 'GET') {
265
+ try {
266
+ const { initialPath } = ctx;
267
+ const report = await getBuiltinToolsSyncReport(initialPath);
268
+
269
+ res.writeHead(200, { 'Content-Type': 'application/json' });
270
+ res.end(JSON.stringify(report));
271
+ } catch (err) {
272
+ res.writeHead(500, { 'Content-Type': 'application/json' });
273
+ res.end(JSON.stringify({ error: (err as Error).message }));
274
+ }
275
+ return true;
276
+ }
277
+
231
278
  return false;
232
279
  }
@@ -16,6 +16,7 @@ import {
16
16
  } from '../../../utils/uv-manager.js';
17
17
  import type { RouteContext } from '../types.js';
18
18
  import { extractJSON } from './utils.js';
19
+ import { getDefaultTool } from '../../../tools/claude-cli-tools.js';
19
20
 
20
21
  export async function handleCodexLensSemanticRoutes(ctx: RouteContext): Promise<boolean> {
21
22
  const { pathname, url, req, res, initialPath, handlePostRequest } = ctx;
@@ -66,14 +67,14 @@ export async function handleCodexLensSemanticRoutes(ctx: RouteContext): Promise<
66
67
  // API: CodexLens LLM Enhancement (run enhance command)
67
68
  if (pathname === '/api/codexlens/enhance' && req.method === 'POST') {
68
69
  handlePostRequest(req, res, async (body) => {
69
- const { path: projectPath, tool = 'gemini', batchSize = 5, timeoutMs = 300000 } = body as {
70
+ const { path: projectPath, tool, batchSize = 5, timeoutMs = 300000 } = body as {
70
71
  path?: unknown;
71
72
  tool?: unknown;
72
73
  batchSize?: unknown;
73
74
  timeoutMs?: unknown;
74
75
  };
75
76
  const targetPath = typeof projectPath === 'string' && projectPath.trim().length > 0 ? projectPath : initialPath;
76
- const resolvedTool = typeof tool === 'string' && tool.trim().length > 0 ? tool : 'gemini';
77
+ const resolvedTool = typeof tool === 'string' && tool.trim().length > 0 ? tool : getDefaultTool(targetPath);
77
78
  const resolvedBatchSize = typeof batchSize === 'number' ? batchSize : Number(batchSize);
78
79
  const resolvedTimeoutMs = typeof timeoutMs === 'number' ? timeoutMs : Number(timeoutMs);
79
80
 
@@ -6,6 +6,7 @@ import { getEmbeddingStatus, generateEmbeddings } from '../memory-embedder-bridg
6
6
  import { checkSemanticStatus } from '../../tools/codex-lens.js';
7
7
  import { StoragePaths } from '../../config/storage-paths.js';
8
8
  import { join } from 'path';
9
+ import { getDefaultTool } from '../../tools/claude-cli-tools.js';
9
10
 
10
11
  /**
11
12
  * Route context interface
@@ -173,12 +174,13 @@ export async function handleCoreMemoryRoutes(ctx: RouteContext): Promise<boolean
173
174
  const memoryId = pathname.replace('/api/core-memory/memories/', '').replace('/summary', '');
174
175
 
175
176
  handlePostRequest(req, res, async (body) => {
176
- const { tool = 'gemini', path: projectPath } = body;
177
+ const { tool, path: projectPath } = body;
177
178
  const basePath = projectPath || initialPath;
179
+ const resolvedTool = tool || getDefaultTool(basePath);
178
180
 
179
181
  try {
180
182
  const store = getCoreMemoryStore(basePath);
181
- const summary = await store.generateSummary(memoryId, tool);
183
+ const summary = await store.generateSummary(memoryId, resolvedTool);
182
184
 
183
185
  // Broadcast update event
184
186
  broadcastToClients({
@@ -6,6 +6,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
6
6
  import { join } from 'path';
7
7
  import { validatePath as validateAllowedPath } from '../../utils/path-validator.js';
8
8
  import type { RouteContext } from './types.js';
9
+ import { getDefaultTool } from '../../tools/claude-cli-tools.js';
9
10
 
10
11
  // ========================================
11
12
  // Constants
@@ -471,7 +472,7 @@ export async function handleFilesRoutes(ctx: RouteContext): Promise<boolean> {
471
472
 
472
473
  const {
473
474
  path: targetPath,
474
- tool = 'gemini',
475
+ tool,
475
476
  strategy = 'single-layer'
476
477
  } = body as { path?: unknown; tool?: unknown; strategy?: unknown };
477
478
 
@@ -481,9 +482,10 @@ export async function handleFilesRoutes(ctx: RouteContext): Promise<boolean> {
481
482
 
482
483
  try {
483
484
  const validatedPath = await validateAllowedPath(targetPath, { mustExist: true, allowedDirectories: [initialPath] });
485
+ const resolvedTool = typeof tool === 'string' && tool.trim().length > 0 ? tool : getDefaultTool(validatedPath);
484
486
  return await triggerUpdateClaudeMd(
485
487
  validatedPath,
486
- typeof tool === 'string' ? tool : 'gemini',
488
+ resolvedTool,
487
489
  typeof strategy === 'string' ? strategy : 'single-layer'
488
490
  );
489
491
  } catch (err) {
@@ -39,6 +39,9 @@ function readSettingsFile(filePath: string): Record<string, unknown> {
39
39
  return {};
40
40
  }
41
41
  const content = readFileSync(filePath, 'utf8');
42
+ if (!content.trim()) {
43
+ return {};
44
+ }
42
45
  return JSON.parse(content);
43
46
  } catch (error: unknown) {
44
47
  console.error(`Error reading settings file ${filePath}:`, error);