From 417f3c0f8c3cd2bcddc6454e9c75f3dc1efc165d Mon Sep 17 00:00:00 2001 From: catlog22 Date: Wed, 10 Dec 2025 10:01:07 +0800 Subject: [PATCH] feat: enhance workflow session management with status updates and CSS styling --- .claude/commands/workflow/execute.md | 8 +++ .claude/commands/workflow/plan.md | 4 +- .claude/commands/workflow/session/complete.md | 17 ++++-- .claude/workflows/tool-strategy.md | 18 +++++- ccw/src/cli.js | 2 +- ccw/src/commands/tool.js | 43 ++++++++++---- .../templates/dashboard-css/02-session.css | 22 ++++++++ ccw/src/templates/dashboard-js/views/home.js | 56 ++++++++++++++++--- 8 files changed, 143 insertions(+), 27 deletions(-) diff --git a/.claude/commands/workflow/execute.md b/.claude/commands/workflow/execute.md index f64b94cc..228b771a 100644 --- a/.claude/commands/workflow/execute.md +++ b/.claude/commands/workflow/execute.md @@ -160,6 +160,14 @@ bash(cat .workflow/active/${sessionId}/workflow-session.json) **Output**: Store session metadata in memory **DO NOT read task JSONs yet** - defer until execution phase (lazy loading) +#### Step 1.4: Update Session Status to Active +**Purpose**: Update workflow-session.json status from "planning" to "active" for dashboard monitoring. + +```bash +# Update status atomically using jq +bash(jq '.status = "active"' .workflow/active/${sessionId}/workflow-session.json > /tmp/ws.json && mv /tmp/ws.json .workflow/active/${sessionId}/workflow-session.json) +``` + **Resume Mode**: This entire phase is skipped when `--resume-session="session-id"` flag is provided. ### Phase 2: Planning Document Validation diff --git a/.claude/commands/workflow/plan.md b/.claude/commands/workflow/plan.md index 938fc719..5e70de02 100644 --- a/.claude/commands/workflow/plan.md +++ b/.claude/commands/workflow/plan.md @@ -61,7 +61,7 @@ Phase 2: Context Gathering ├─ Tasks attached: Analyze structure → Identify integration → Generate package └─ Output: contextPath + conflict_risk -Phase 3: Conflict Resolution (conditional) +Phase 3: Conflict Resolution └─ Decision (conflict_risk check): ├─ conflict_risk ≥ medium → Execute /workflow:tools:conflict-resolution │ ├─ Tasks attached: Detect conflicts → Present to user → Apply strategies @@ -168,7 +168,7 @@ SlashCommand(command="/workflow:tools:context-gather --session [sessionId] \"[st --- -### Phase 3: Conflict Resolution (Optional - auto-triggered by conflict risk) +### Phase 3: Conflict Resolution **Trigger**: Only execute when context-package.json indicates conflict_risk is "medium" or "high" diff --git a/.claude/commands/workflow/session/complete.md b/.claude/commands/workflow/session/complete.md index 40d803f1..abb66f18 100644 --- a/.claude/commands/workflow/session/complete.md +++ b/.claude/commands/workflow/session/complete.md @@ -166,13 +166,21 @@ Analyze workflow session for archival preparation. Session is STILL in active lo bash(mkdir -p .workflow/archives/) ``` -#### Step 3.2: Move Session to Archive +#### Step 3.2: Update Session Status to Completed +**Purpose**: Update workflow-session.json status to "completed" for dashboard display. + +```bash +# Update status atomically using jq +bash(jq '.status = "completed"' .workflow/active/WFS-session-name/workflow-session.json > /tmp/ws.json && mv /tmp/ws.json .workflow/active/WFS-session-name/workflow-session.json) +``` + +#### Step 3.3: Move Session to Archive ```bash bash(mv .workflow/active/WFS-session-name .workflow/archives/WFS-session-name) ``` **Result**: Session now at `.workflow/archives/WFS-session-name/` -#### Step 3.3: Update Manifest +#### Step 3.4: Update Manifest ```bash # Read current manifest (or create empty array if not exists) bash(test -f .workflow/archives/manifest.json && cat .workflow/archives/manifest.json || echo "[]") @@ -200,7 +208,7 @@ manifest.push(archiveEntry); Write('.workflow/archives/manifest.json', JSON.stringify(manifest, null, 2)); ``` -#### Step 3.4: Remove Archiving Marker +#### Step 3.5: Remove Archiving Marker ```bash bash(rm .workflow/archives/WFS-session-name/.archiving) ``` @@ -464,11 +472,12 @@ Session state: PARTIALLY COMPLETE (session archived, manifest needs update) **Phase 3: Atomic Commit** (Transactional file operations) - Create archive directory +- Update session status to "completed" - Move session to archive location - Update manifest.json with archive entry - Remove `.archiving` marker - **All-or-nothing**: Either all succeed or session remains in safe state -- **Total**: 4 bash commands + JSON manipulation +- **Total**: 5 bash commands + JSON manipulation **Phase 4: Project Registry Update** (Optional feature tracking) - Check project.json exists diff --git a/.claude/workflows/tool-strategy.md b/.claude/workflows/tool-strategy.md index 53209232..9ca703e5 100644 --- a/.claude/workflows/tool-strategy.md +++ b/.claude/workflows/tool-strategy.md @@ -10,7 +10,23 @@ - Complex API research → Exa Code Context - Real-time information needs → Exa Web Search -## ⚡ CCW edit_file Tool (AI-Powered Editing) +## ⚡ CCW Tool Execution + +### General Usage (JSON Parameters) + +```bash +ccw tool exec '{"param": "value"}' +``` + +**Examples**: +```bash +ccw tool exec get_modules_by_depth '{}' +ccw tool exec classify_folders '{"path": "./src"}' +``` + +**Available Tools**: `ccw tool list` + +### edit_file Tool (AI-Powered Editing) **When to Use**: Edit tool fails 1+ times on same file diff --git a/ccw/src/cli.js b/ccw/src/cli.js index 0a69036d..557d0c83 100644 --- a/ccw/src/cli.js +++ b/ccw/src/cli.js @@ -108,7 +108,7 @@ export function run(argv) { // Tool command program - .command('tool [subcommand] [args]') + .command('tool [subcommand] [args...]') .description('Execute CCW tools') .option('--path ', 'File path (for edit_file)') .option('--old ', 'Old text to replace (for edit_file)') diff --git a/ccw/src/commands/tool.js b/ccw/src/commands/tool.js index c5600e86..af5fcc29 100644 --- a/ccw/src/commands/tool.js +++ b/ccw/src/commands/tool.js @@ -68,11 +68,15 @@ async function schemaAction(options) { /** * Execute a tool with given parameters + * @param {string} toolName - Tool name + * @param {string|undefined} jsonParams - JSON string of parameters + * @param {Object} options - CLI options (--path, --old, --new for edit_file) */ -async function execAction(toolName, options) { +async function execAction(toolName, jsonParams, options) { if (!toolName) { console.error(chalk.red('Tool name is required')); - console.error(chalk.gray('Usage: ccw tool exec edit_file --path file.txt --old "old" --new "new"')); + console.error(chalk.gray('Usage: ccw tool exec \'{"param": "value"}\'')); + console.error(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old" --new "new"')); process.exit(1); } @@ -83,10 +87,20 @@ async function execAction(toolName, options) { process.exit(1); } - // Build params from CLI options - const params = {}; + // Build params from CLI options or JSON + let params = {}; - if (toolName === 'edit_file') { + // Check if JSON params provided + if (jsonParams && jsonParams.trim().startsWith('{')) { + try { + params = JSON.parse(jsonParams); + } catch (e) { + console.error(chalk.red('Invalid JSON parameters')); + console.error(chalk.gray(`Parse error: ${e.message}`)); + process.exit(1); + } + } else if (toolName === 'edit_file') { + // Legacy support for edit_file with --path, --old, --new options if (!options.path || !options.old || !options.new) { console.error(chalk.red('edit_file requires --path, --old, and --new parameters')); console.error(chalk.gray('Usage: ccw tool exec edit_file --path file.txt --old "old text" --new "new text"')); @@ -95,11 +109,13 @@ async function execAction(toolName, options) { params.path = options.path; params.oldText = options.old; params.newText = options.new; - } else { - console.error(chalk.red(`Tool "${toolName}" is not supported via CLI parameters`)); - console.error(chalk.gray('Currently only edit_file is supported')); + } else if (jsonParams) { + // Non-JSON string provided but not for edit_file + console.error(chalk.red('Parameters must be valid JSON')); + console.error(chalk.gray(`Usage: ccw tool exec ${toolName} '{"param": "value"}'`)); process.exit(1); } + // If no params provided, use empty object (tool may have defaults) // Execute tool const result = await executeTool(toolName, params); @@ -110,18 +126,24 @@ async function execAction(toolName, options) { /** * Tool command entry point + * @param {string} subcommand - Subcommand (list, schema, exec) + * @param {string[]} args - Arguments array [toolName, jsonParams, ...] + * @param {Object} options - CLI options */ export async function toolCommand(subcommand, args, options) { + // args is now an array due to [args...] in cli.js + const argsArray = Array.isArray(args) ? args : (args ? [args] : []); + // Handle subcommands switch (subcommand) { case 'list': await listAction(); break; case 'schema': - await schemaAction({ name: args }); + await schemaAction({ name: argsArray[0] }); break; case 'exec': - await execAction(args, options); + await execAction(argsArray[0], argsArray[1], options); break; default: console.log(chalk.bold.cyan('\nCCW Tool System\n')); @@ -133,6 +155,7 @@ export async function toolCommand(subcommand, args, options) { console.log('Usage:'); console.log(chalk.gray(' ccw tool list')); console.log(chalk.gray(' ccw tool schema edit_file')); + console.log(chalk.gray(' ccw tool exec \'{"param": "value"}\'')); console.log(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old text" --new "new text"')); } } diff --git a/ccw/src/templates/dashboard-css/02-session.css b/ccw/src/templates/dashboard-css/02-session.css index 72bc7b4f..f3984522 100644 --- a/ccw/src/templates/dashboard-css/02-session.css +++ b/ccw/src/templates/dashboard-css/02-session.css @@ -60,6 +60,28 @@ color: hsl(var(--muted-foreground)); } +.session-status.planning { + background: hsl(260 70% 90%); + color: hsl(260 70% 45%); + animation: planning-pulse 2s ease-in-out infinite; +} + +@keyframes planning-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + +/* Planning card style - dashed border with subtle animation */ +.session-card.planning { + border: 2px dashed hsl(260 70% 60%); + background: linear-gradient(135deg, hsl(var(--card)) 0%, hsl(260 70% 97%) 100%); +} + +.session-card.planning:hover { + border-color: hsl(260 70% 50%); + box-shadow: 0 4px 12px hsl(260 70% 50% / 0.2); +} + .session-type-badge { font-size: 0.65rem; font-weight: 500; diff --git a/ccw/src/templates/dashboard-js/views/home.js b/ccw/src/templates/dashboard-js/views/home.js index 2861c267..ad8c50ff 100644 --- a/ccw/src/templates/dashboard-js/views/home.js +++ b/ccw/src/templates/dashboard-js/views/home.js @@ -98,27 +98,48 @@ function renderSessionCard(session) { const isActive = session._isActive !== false; const date = session.created_at; + // Detect planning status from session.status field + const isPlanning = session.status === 'planning'; + // Get session type badge const sessionType = session.type || 'workflow'; const typeBadge = sessionType !== 'workflow' ? `${sessionType}` : ''; + // Determine status badge class and text + // Priority: archived > planning > active + let statusClass, statusText; + if (!isActive) { + // Archived sessions always show as ARCHIVED regardless of status field + statusClass = 'archived'; + statusText = 'ARCHIVED'; + } else if (isPlanning) { + statusClass = 'planning'; + statusText = 'PLANNING'; + } else { + statusClass = 'active'; + statusText = 'ACTIVE'; + } + // Store session data for modal const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-'); sessionDataStore[sessionKey] = session; // Special rendering for review sessions if (sessionType === 'review') { - return renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date); + return renderReviewSessionCard(session, sessionKey, typeBadge, isActive, isPlanning, date); } + // Card class includes planning modifier for special styling (only for active sessions) + const cardClass = isActive && isPlanning ? 'session-card planning' : 'session-card'; + return ` -
+
${escapeHtml(session.session_id || 'Unknown')}
${typeBadge} - - ${isActive ? 'ACTIVE' : 'ARCHIVED'} + + ${statusText}
@@ -127,7 +148,7 @@ function renderSessionCard(session) { ${formatDate(date)} ${taskCount} tasks
- ${taskCount > 0 ? ` + ${taskCount > 0 && !isPlanning ? `
Progress
@@ -144,7 +165,7 @@ function renderSessionCard(session) { } // Special card rendering for review sessions -function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date) { +function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, isPlanning, date) { // Calculate findings stats from reviewDimensions const dimensions = session.reviewDimensions || []; let totalFindings = 0; @@ -162,14 +183,31 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date) lowCount += findings.filter(f => f.severity === 'low').length; }); + // Determine status badge class and text + // Priority: archived > planning > active + let statusClass, statusText; + if (!isActive) { + statusClass = 'archived'; + statusText = 'ARCHIVED'; + } else if (isPlanning) { + statusClass = 'planning'; + statusText = 'PLANNING'; + } else { + statusClass = 'active'; + statusText = 'ACTIVE'; + } + + // Card class includes planning modifier for special styling (only for active sessions) + const cardClass = isActive && isPlanning ? 'session-card planning' : 'session-card'; + return ` -
+
${escapeHtml(session.session_id || 'Unknown')}
${typeBadge} - - ${isActive ? 'ACTIVE' : 'ARCHIVED'} + + ${statusText}