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.
- package/.claude/agents/cli-execution-agent.md +61 -0
- package/.claude/agents/cli-lite-planning-agent.md +322 -36
- package/.claude/agents/issue-plan-agent.md +95 -11
- package/.claude/commands/cli/codex-review.md +8 -2
- package/.claude/commands/issue/plan.md +20 -6
- package/.claude/commands/workflow/lite-execute.md +56 -16
- package/.claude/commands/workflow/lite-fix.md +108 -9
- package/.claude/commands/workflow/lite-plan.md +1 -1
- package/.claude/skills/ccw-loop/README.md +303 -0
- package/.claude/skills/ccw-loop/SKILL.md +259 -0
- package/.claude/skills/ccw-loop/phases/actions/action-complete.md +320 -0
- package/.claude/skills/ccw-loop/phases/actions/action-debug-with-file.md +485 -0
- package/.claude/skills/ccw-loop/phases/actions/action-develop-with-file.md +365 -0
- package/.claude/skills/ccw-loop/phases/actions/action-init.md +200 -0
- package/.claude/skills/ccw-loop/phases/actions/action-menu.md +192 -0
- package/.claude/skills/ccw-loop/phases/actions/action-validate-with-file.md +307 -0
- package/.claude/skills/ccw-loop/phases/orchestrator.md +486 -0
- package/.claude/skills/ccw-loop/phases/state-schema.md +474 -0
- package/.claude/skills/ccw-loop/specs/action-catalog.md +300 -0
- package/.claude/skills/ccw-loop/specs/loop-requirements.md +192 -0
- package/.claude/skills/ccw-loop/templates/progress-template.md +175 -0
- package/.claude/skills/ccw-loop/templates/understanding-template.md +303 -0
- package/.claude/skills/ccw-loop/templates/validation-template.md +258 -0
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +29 -0
- package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +200 -0
- package/.codex/prompts/debug-with-file.md +609 -0
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +8 -1
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +16 -4
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +37 -4
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/commands/loop.d.ts +10 -0
- package/ccw/dist/commands/loop.d.ts.map +1 -0
- package/ccw/dist/commands/loop.js +289 -0
- package/ccw/dist/commands/loop.js.map +1 -0
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +4 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/claude-routes.js +5 -3
- package/ccw/dist/core/routes/claude-routes.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts +6 -0
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +42 -13
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/cli-settings-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-settings-routes.js +44 -0
- package/ccw/dist/core/routes/cli-settings-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens/semantic-handlers.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens/semantic-handlers.js +3 -2
- package/ccw/dist/core/routes/codexlens/semantic-handlers.js.map +1 -1
- package/ccw/dist/core/routes/core-memory-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/core-memory-routes.js +4 -2
- package/ccw/dist/core/routes/core-memory-routes.js.map +1 -1
- package/ccw/dist/core/routes/files-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/files-routes.js +4 -2
- package/ccw/dist/core/routes/files-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +3 -0
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/loop-routes.d.ts +24 -0
- package/ccw/dist/core/routes/loop-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/loop-routes.js +334 -0
- package/ccw/dist/core/routes/loop-routes.js.map +1 -0
- package/ccw/dist/core/routes/loop-v2-routes.d.ts +35 -0
- package/ccw/dist/core/routes/loop-v2-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/loop-v2-routes.js +1208 -0
- package/ccw/dist/core/routes/loop-v2-routes.js.map +1 -0
- package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/memory-routes.js +2 -1
- package/ccw/dist/core/routes/memory-routes.js.map +1 -1
- package/ccw/dist/core/routes/task-routes.d.ts +12 -0
- package/ccw/dist/core/routes/task-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/task-routes.js +321 -0
- package/ccw/dist/core/routes/task-routes.js.map +1 -0
- package/ccw/dist/core/routes/test-loop-routes.d.ts +11 -0
- package/ccw/dist/core/routes/test-loop-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/test-loop-routes.js +298 -0
- package/ccw/dist/core/routes/test-loop-routes.js.map +1 -0
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +43 -3
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/core/websocket.d.ts +59 -0
- package/ccw/dist/core/websocket.d.ts.map +1 -1
- package/ccw/dist/core/websocket.js +34 -0
- package/ccw/dist/core/websocket.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +40 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.js +119 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +66 -47
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/loop-manager.d.ts +84 -0
- package/ccw/dist/tools/loop-manager.d.ts.map +1 -0
- package/ccw/dist/tools/loop-manager.js +425 -0
- package/ccw/dist/tools/loop-manager.js.map +1 -0
- package/ccw/dist/tools/loop-state-manager.d.ts +47 -0
- package/ccw/dist/tools/loop-state-manager.d.ts.map +1 -0
- package/ccw/dist/tools/loop-state-manager.js +149 -0
- package/ccw/dist/tools/loop-state-manager.js.map +1 -0
- package/ccw/dist/tools/loop-task-manager.d.ts +138 -0
- package/ccw/dist/tools/loop-task-manager.d.ts.map +1 -0
- package/ccw/dist/tools/loop-task-manager.js +270 -0
- package/ccw/dist/tools/loop-task-manager.js.map +1 -0
- package/ccw/dist/types/index.d.ts +1 -0
- package/ccw/dist/types/index.d.ts.map +1 -1
- package/ccw/dist/types/index.js +1 -0
- package/ccw/dist/types/index.js.map +1 -1
- package/ccw/dist/types/loop.d.ts +257 -0
- package/ccw/dist/types/loop.d.ts.map +1 -0
- package/ccw/dist/types/loop.js +17 -0
- package/ccw/dist/types/loop.js.map +1 -0
- package/ccw/scripts/IMPLEMENTATION-SUMMARY.md +2 -2
- package/ccw/scripts/QUICK-REFERENCE.md +1 -1
- package/ccw/scripts/README-memory-embedder.md +1 -1
- package/ccw/scripts/memory_embedder.py +1 -1
- package/ccw/src/cli.ts +9 -1
- package/ccw/src/commands/cli.ts +16 -4
- package/ccw/src/commands/issue.ts +41 -5
- package/ccw/src/commands/loop.ts +344 -0
- package/ccw/src/core/dashboard-generator.ts +4 -1
- package/ccw/src/core/routes/claude-routes.ts +5 -3
- package/ccw/src/core/routes/cli-routes.ts +47 -15
- package/ccw/src/core/routes/cli-settings-routes.ts +47 -0
- package/ccw/src/core/routes/codexlens/semantic-handlers.ts +3 -2
- package/ccw/src/core/routes/core-memory-routes.ts +4 -2
- package/ccw/src/core/routes/files-routes.ts +4 -2
- package/ccw/src/core/routes/hooks-routes.ts +3 -0
- package/ccw/src/core/routes/loop-routes.ts +386 -0
- package/ccw/src/core/routes/loop-v2-routes.ts +1412 -0
- package/ccw/src/core/routes/memory-routes.ts +2 -1
- package/ccw/src/core/routes/task-routes.ts +361 -0
- package/ccw/src/core/routes/test-loop-routes.ts +312 -0
- package/ccw/src/core/server.ts +44 -3
- package/ccw/src/core/websocket.ts +104 -0
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +56 -0
- package/ccw/src/templates/dashboard-css/32-issue-manager.css +160 -0
- package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +57 -2
- package/ccw/src/templates/dashboard-css/36-loop-monitor.css +1896 -0
- package/ccw/src/templates/dashboard-css/36-loop-monitor.css.backup +1877 -0
- package/ccw/src/templates/dashboard-js/components/cli-status.js +64 -3
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +251 -110
- package/ccw/src/templates/dashboard-js/components/navigation.js +10 -0
- package/ccw/src/templates/dashboard-js/components/notifications.js +16 -0
- package/ccw/src/templates/dashboard-js/i18n.js +475 -1
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +3 -2
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +159 -0
- package/ccw/src/templates/dashboard-js/views/loop-monitor.js +3244 -0
- package/ccw/src/templates/dashboard.html +20 -2
- package/ccw/src/tools/claude-cli-tools.ts +143 -0
- package/ccw/src/tools/codex-lens.ts +71 -44
- package/ccw/src/tools/loop-manager.ts +519 -0
- package/ccw/src/tools/loop-state-manager.ts +173 -0
- package/ccw/src/tools/loop-task-manager.ts +380 -0
- package/ccw/src/types/index.ts +1 -0
- package/ccw/src/types/loop.ts +316 -0
- 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
|
-
'
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
668
|
-
activeExecutions.
|
|
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
|
-
//
|
|
688
|
-
activeExecutions.
|
|
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
|
|
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 :
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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);
|