diff --git a/.codex/skills/ccw-loop-b/SKILL.md b/.codex/skills/ccw-loop-b/SKILL.md index 7a9b50bc..32db4671 100644 --- a/.codex/skills/ccw-loop-b/SKILL.md +++ b/.codex/skills/ccw-loop-b/SKILL.md @@ -1,7 +1,7 @@ --- name: ccw-loop-b description: Hybrid orchestrator pattern for iterative development. Coordinator + specialized workers with batch wait, parallel split, and two-phase clarification. Triggers on "ccw-loop-b". -allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep +allowed-tools: spawn_agent, wait, send_input, close_agent, AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep --- # CCW Loop-B - Hybrid Orchestrator Pattern diff --git a/.codex/skills/ccw-loop/SKILL.md b/.codex/skills/ccw-loop/SKILL.md index 04e89b08..e5514fe4 100644 --- a/.codex/skills/ccw-loop/SKILL.md +++ b/.codex/skills/ccw-loop/SKILL.md @@ -1,7 +1,7 @@ --- name: ccw-loop description: Stateless iterative development loop with single-agent deep interaction. Supports develop, debug, validate phases with file-based state tracking. Triggers on "ccw-loop", "dev loop", "development loop", "开发循环", "迭代开发". -allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep +allowed-tools: spawn_agent, wait, send_input, close_agent, AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep --- # CCW Loop - Stateless Iterative Development Workflow diff --git a/.codex/skills/parallel-dev-cycle/SKILL.md b/.codex/skills/parallel-dev-cycle/SKILL.md index 26f7dda9..35ef1817 100644 --- a/.codex/skills/parallel-dev-cycle/SKILL.md +++ b/.codex/skills/parallel-dev-cycle/SKILL.md @@ -1,7 +1,7 @@ --- name: parallel-dev-cycle description: Multi-agent parallel development cycle with requirement analysis, exploration planning, code development, and validation. Supports continuous iteration with markdown progress documentation. Triggers on "parallel-dev-cycle". -allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep +allowed-tools: spawn_agent, wait, send_input, close_agent, AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep --- # Parallel Dev Cycle diff --git a/.codex/skills/workflow-lite-plan-execute/SKILL.md b/.codex/skills/workflow-lite-plan-execute/SKILL.md index ef847227..3c5cb1ca 100644 --- a/.codex/skills/workflow-lite-plan-execute/SKILL.md +++ b/.codex/skills/workflow-lite-plan-execute/SKILL.md @@ -10,7 +10,7 @@ Lite Plan produces an implementation plan and an `executionContext`, then hands ## Key Design Principles -1. **Shared Execution**: Lite Plan produces `executionContext` consumed by Phase 4 (Lite Execute) +1. **Shared Execution**: Lite Plan produces `executionContext` consumed by Phase 2 (Lite Execute) 2. **Progressive Phase Loading**: Only load phase docs when about to execute 3. **Auto-Continue**: After the plan is confirmed ("Allow"), automatically load execution phase 4. **Default Auto Mode**: When `--yes`, skip confirmations and auto-approve the plan @@ -49,7 +49,7 @@ $workflow-lite-plan-execute "docs/todo.md" | Phase | Document | Purpose | |-------|----------|---------| | 1 | `phases/01-lite-plan.md` | Lightweight planning with exploration, clarification, plan generation, and confirmation | -| 4 | `phases/04-lite-execute.md` | Shared execution engine: task grouping, batch execution, optional code review | +| 2 | `phases/02-lite-execute.md` | Shared execution engine: task grouping, batch execution, optional code review | ## Orchestrator Logic @@ -81,14 +81,14 @@ if (executionContext?.userSelection?.confirmation !== 'Allow' && !autoYes) { return } -// Phase 4: Lite Execute -Read('phases/04-lite-execute.md') +// Phase 2: Lite Execute +Read('phases/02-lite-execute.md') // Execute execution phase with executionContext from Phase 1 ``` ## executionContext Contract (High Level) -`executionContext` is the only contract between Phase 1 and Phase 4. +`executionContext` is the only contract between Phase 1 and Phase 2. Required (minimum) fields: ```javascript @@ -110,7 +110,7 @@ Initialization: ```json [ {"content": "Lite Plan - Planning", "status": "in_progress", "activeForm": "Planning"}, - {"content": "Execution (Phase 4)", "status": "pending", "activeForm": "Executing tasks"} + {"content": "Execution (Phase 2)", "status": "pending", "activeForm": "Executing tasks"} ] ``` @@ -118,14 +118,14 @@ After planning completes: ```json [ {"content": "Lite Plan - Planning", "status": "completed", "activeForm": "Planning"}, - {"content": "Execution (Phase 4)", "status": "in_progress", "activeForm": "Executing tasks"} + {"content": "Execution (Phase 2)", "status": "in_progress", "activeForm": "Executing tasks"} ] ``` ## Core Rules -1. **Planning phase NEVER modifies project code** - it may write planning artifacts, but all implementation is delegated to Phase 4 -2. **Phase 4 runs only after confirmation** - execute only when confirmation is "Allow" (or `--yes` auto mode) +1. **Planning phase NEVER modifies project code** - it may write planning artifacts, but all implementation is delegated to Phase 2 +2. **Phase 2 runs only after confirmation** - execute only when confirmation is "Allow" (or `--yes` auto mode) 3. **executionContext is the contract** between planning and execution phases 4. **Progressive loading**: Read phase doc only when about to execute 5. **File-path detection**: Treat input as a file path only if the path exists; do not infer from file extensions diff --git a/ccw/src/commands/install.ts b/ccw/src/commands/install.ts index 30ee7686..b6b2cc03 100644 --- a/ccw/src/commands/install.ts +++ b/ccw/src/commands/install.ts @@ -26,7 +26,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Source directories to install (includes .codex with prompts folder) -const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen']; +const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen', '.ccw']; // Subdirectories that should always be installed to global (~/.claude/) const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates']; @@ -380,7 +380,10 @@ export async function installCommand(options: InstallOptions): Promise { for (const dir of availableDirs) { const srcPath = join(sourceDir, dir); - const destPath = join(installPath, dir); + + // .ccw always installs to global ~/.ccw/ regardless of mode + const destBase = (mode === 'Path' && dir === '.ccw') ? homedir() : installPath; + const destPath = join(destBase, dir); spinner.text = `Installing ${dir}...`; diff --git a/ccw/src/commands/upgrade.ts b/ccw/src/commands/upgrade.ts index 47f351ee..909fa554 100644 --- a/ccw/src/commands/upgrade.ts +++ b/ccw/src/commands/upgrade.ts @@ -11,7 +11,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Source directories to install -const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen']; +const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen', '.ccw']; // Subdirectories that should always be installed to global (~/.claude/) const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates']; @@ -268,7 +268,10 @@ async function performUpgrade(manifest: any, sourceDir: string, version: string) // Copy each directory for (const dir of availableDirs) { const srcPath = join(sourceDir, dir); - const destPath = join(installPath, dir); + + // .ccw always upgrades to global ~/.ccw/ regardless of mode + const destBase = (mode === 'Path' && dir === '.ccw') ? homedir() : installPath; + const destPath = join(destBase, dir); // For Path mode on .claude, exclude global subdirs (they're already installed to global) const excludeDirs = (mode === 'Path' && dir === '.claude') ? GLOBAL_SUBDIRS : []; diff --git a/ccw/src/core/routes/ccw-routes.ts b/ccw/src/core/routes/ccw-routes.ts index 7c29ecb9..14bc7532 100644 --- a/ccw/src/core/routes/ccw-routes.ts +++ b/ccw/src/core/routes/ccw-routes.ts @@ -117,6 +117,70 @@ export async function handleCcwRoutes(ctx: RouteContext): Promise { return true; } + // API: CCW Install (non-interactive) + if (pathname === '/api/ccw/install' && req.method === 'POST') { + handlePostRequest(req, res, async (body) => { + const { mode = 'Global', path: installPath, force = true } = body as { mode?: string; path?: string; force?: boolean }; + + try { + const { spawn } = await import('child_process'); + + const args = ['install', '--mode', mode, '--force']; + if (mode === 'Path' && installPath) { + args.push('--path', installPath); + } + + const installProcess = spawn('ccw', args, { + shell: true, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + let stdout = ''; + let stderr = ''; + + installProcess.stdout?.on('data', (data: Buffer) => { + const chunk = data.toString(); + stdout += chunk; + broadcastToClients({ + type: 'CCW_INSTALL_OUTPUT', + payload: { data: chunk } + }); + }); + + installProcess.stderr?.on('data', (data: Buffer) => { + stderr += data.toString(); + }); + + return new Promise((resolve) => { + installProcess.on('close', (code: number | null) => { + if (code === 0) { + broadcastToClients({ + type: 'CCW_INSTALL_COMPLETED', + payload: { success: true, mode } + }); + resolve({ success: true, message: 'Installation completed', mode, output: stdout }); + } else { + resolve({ success: false, error: stderr || 'Installation failed', output: stdout, status: 500 }); + } + }); + + installProcess.on('error', (err: Error) => { + resolve({ success: false, error: err.message, status: 500 }); + }); + + // Timeout after 2 minutes + setTimeout(() => { + installProcess.kill(); + resolve({ success: false, error: 'Installation timed out', status: 504 }); + }, 120000); + }); + } catch (err: unknown) { + return { success: false, error: err instanceof Error ? err.message : String(err), status: 500 }; + } + }); + return true; + } + // API: CCW Upgrade if (pathname === '/api/ccw/upgrade' && req.method === 'POST') { handlePostRequest(req, res, async (body) => { diff --git a/ccw/src/core/routes/claude-routes.ts b/ccw/src/core/routes/claude-routes.ts index 8fdfcfc8..949118bb 100644 --- a/ccw/src/core/routes/claude-routes.ts +++ b/ccw/src/core/routes/claude-routes.ts @@ -943,7 +943,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { } // Find guidelines file path - always use user-level path - const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md'); + const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'chinese-response.md'); if (existsSync(userGuidelinesPath)) { guidelinesPath = userGuidelinesPath; @@ -979,7 +979,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { try { // Find guidelines file path - always use user-level path with ~ shorthand - const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'chinese-response.md'); + const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'chinese-response.md'); if (!existsSync(userGuidelinesPath)) { return { error: 'Chinese response guidelines file not found at ~/.ccw/workflows/chinese-response.md', status: 404 }; @@ -1107,7 +1107,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { } // Find guidelines file path - const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'cli-tools-usage.md'); + const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'cli-tools-usage.md'); if (existsSync(userGuidelinesPath)) { guidelinesPath = userGuidelinesPath; @@ -1172,7 +1172,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { if (content) content += '\n'; // Read and add updated section - const cliToolsUsagePath = join(homedir(), '.claude', 'workflows', 'cli-tools-usage.md'); + const cliToolsUsagePath = join(homedir(), '.ccw', 'workflows', 'cli-tools-usage.md'); let cliToolsUsageContent = ''; if (existsSync(cliToolsUsagePath)) { cliToolsUsageContent = readFileSync(cliToolsUsagePath, 'utf8'); @@ -1208,7 +1208,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { } // Read cli-tools-usage.md content - const cliToolsUsagePath = join(homedir(), '.claude', 'workflows', 'cli-tools-usage.md'); + const cliToolsUsagePath = join(homedir(), '.ccw', 'workflows', 'cli-tools-usage.md'); let cliToolsUsageContent = ''; if (existsSync(cliToolsUsagePath)) { cliToolsUsageContent = readFileSync(cliToolsUsagePath, 'utf8'); @@ -1266,7 +1266,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { } // Find guidelines file path - always use user-level path - const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md'); + const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'windows-platform.md'); if (existsSync(userGuidelinesPath)) { guidelinesPath = userGuidelinesPath; @@ -1301,7 +1301,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise { const userClaudeDir = join(homedir(), '.claude'); // Find guidelines file path - always use user-level path with ~ shorthand - const userGuidelinesPath = join(homedir(), '.claude', 'workflows', 'windows-platform.md'); + const userGuidelinesPath = join(homedir(), '.ccw', 'workflows', 'windows-platform.md'); if (!existsSync(userGuidelinesPath)) { return { error: 'Windows platform guidelines file not found at ~/.ccw/workflows/windows-platform.md', status: 404 }; diff --git a/ccw/src/core/routes/status-routes.ts b/ccw/src/core/routes/status-routes.ts index db962a29..14c27037 100644 --- a/ccw/src/core/routes/status-routes.ts +++ b/ccw/src/core/routes/status-routes.ts @@ -28,8 +28,8 @@ function checkCcwInstallStatus(): { missingFiles: string[]; installPath: string; } { - const claudeDir = join(homedir(), '.claude'); - const workflowsDir = join(claudeDir, 'workflows'); + const ccwDir = join(homedir(), '.ccw'); + const workflowsDir = join(ccwDir, 'workflows'); // Required workflow files for full functionality const requiredFiles = [ diff --git a/ccw/src/tools/claude-cli-tools.ts b/ccw/src/tools/claude-cli-tools.ts index e5116b4e..f96303e5 100644 --- a/ccw/src/tools/claude-cli-tools.ts +++ b/ccw/src/tools/claude-cli-tools.ts @@ -948,10 +948,10 @@ export function updateCodeIndexMcp( // Only update global CLAUDE.md (consistent with Chinese response / Windows platform) const globalClaudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md'); - // Define patterns for all formats - const codexlensPattern = /@~\/\.claude\/workflows\/context-tools\.md/g; - const acePattern = /@~\/\.claude\/workflows\/context-tools-ace\.md/g; - const nonePattern = /@~\/\.claude\/workflows\/context-tools-none\.md/g; + // Define patterns for all formats (match both old .claude and new .ccw paths) + const codexlensPattern = /@~\/\.(?:claude|ccw)\/workflows\/context-tools\.md/g; + const acePattern = /@~\/\.(?:claude|ccw)\/workflows\/context-tools-ace\.md/g; + const nonePattern = /@~\/\.(?:claude|ccw)\/workflows\/context-tools-none\.md/g; // Determine target file based on provider const targetFile = provider === 'ace' diff --git a/ccw/src/tools/template-discovery.ts b/ccw/src/tools/template-discovery.ts index 5772e988..3b07307b 100644 --- a/ccw/src/tools/template-discovery.ts +++ b/ccw/src/tools/template-discovery.ts @@ -37,7 +37,7 @@ export interface TemplateIndex { // Constants // ============================================================================ -const TEMPLATES_BASE_DIR = join(homedir(), '.claude', 'workflows', 'cli-templates'); +const TEMPLATES_BASE_DIR = join(homedir(), '.ccw', 'workflows', 'cli-templates'); const PROMPTS_DIR = join(TEMPLATES_BASE_DIR, 'prompts'); const PROTOCOLS_DIR = join(TEMPLATES_BASE_DIR, 'protocols');