mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat: enhance workflow session management with status updates and CSS styling
This commit is contained in:
@@ -160,6 +160,14 @@ bash(cat .workflow/active/${sessionId}/workflow-session.json)
|
|||||||
**Output**: Store session metadata in memory
|
**Output**: Store session metadata in memory
|
||||||
**DO NOT read task JSONs yet** - defer until execution phase (lazy loading)
|
**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.
|
**Resume Mode**: This entire phase is skipped when `--resume-session="session-id"` flag is provided.
|
||||||
|
|
||||||
### Phase 2: Planning Document Validation
|
### Phase 2: Planning Document Validation
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Phase 2: Context Gathering
|
|||||||
├─ Tasks attached: Analyze structure → Identify integration → Generate package
|
├─ Tasks attached: Analyze structure → Identify integration → Generate package
|
||||||
└─ Output: contextPath + conflict_risk
|
└─ Output: contextPath + conflict_risk
|
||||||
|
|
||||||
Phase 3: Conflict Resolution (conditional)
|
Phase 3: Conflict Resolution
|
||||||
└─ Decision (conflict_risk check):
|
└─ Decision (conflict_risk check):
|
||||||
├─ conflict_risk ≥ medium → Execute /workflow:tools:conflict-resolution
|
├─ conflict_risk ≥ medium → Execute /workflow:tools:conflict-resolution
|
||||||
│ ├─ Tasks attached: Detect conflicts → Present to user → Apply strategies
|
│ ├─ 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"
|
**Trigger**: Only execute when context-package.json indicates conflict_risk is "medium" or "high"
|
||||||
|
|
||||||
|
|||||||
@@ -166,13 +166,21 @@ Analyze workflow session for archival preparation. Session is STILL in active lo
|
|||||||
bash(mkdir -p .workflow/archives/)
|
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
|
||||||
bash(mv .workflow/active/WFS-session-name .workflow/archives/WFS-session-name)
|
bash(mv .workflow/active/WFS-session-name .workflow/archives/WFS-session-name)
|
||||||
```
|
```
|
||||||
**Result**: Session now at `.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
|
```bash
|
||||||
# Read current manifest (or create empty array if not exists)
|
# Read current manifest (or create empty array if not exists)
|
||||||
bash(test -f .workflow/archives/manifest.json && cat .workflow/archives/manifest.json || echo "[]")
|
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));
|
Write('.workflow/archives/manifest.json', JSON.stringify(manifest, null, 2));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step 3.4: Remove Archiving Marker
|
#### Step 3.5: Remove Archiving Marker
|
||||||
```bash
|
```bash
|
||||||
bash(rm .workflow/archives/WFS-session-name/.archiving)
|
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)
|
**Phase 3: Atomic Commit** (Transactional file operations)
|
||||||
- Create archive directory
|
- Create archive directory
|
||||||
|
- Update session status to "completed"
|
||||||
- Move session to archive location
|
- Move session to archive location
|
||||||
- Update manifest.json with archive entry
|
- Update manifest.json with archive entry
|
||||||
- Remove `.archiving` marker
|
- Remove `.archiving` marker
|
||||||
- **All-or-nothing**: Either all succeed or session remains in safe state
|
- **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)
|
**Phase 4: Project Registry Update** (Optional feature tracking)
|
||||||
- Check project.json exists
|
- Check project.json exists
|
||||||
|
|||||||
@@ -10,7 +10,23 @@
|
|||||||
- Complex API research → Exa Code Context
|
- Complex API research → Exa Code Context
|
||||||
- Real-time information needs → Exa Web Search
|
- 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 <tool_name> '{"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
|
**When to Use**: Edit tool fails 1+ times on same file
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function run(argv) {
|
|||||||
|
|
||||||
// Tool command
|
// Tool command
|
||||||
program
|
program
|
||||||
.command('tool [subcommand] [args]')
|
.command('tool [subcommand] [args...]')
|
||||||
.description('Execute CCW tools')
|
.description('Execute CCW tools')
|
||||||
.option('--path <path>', 'File path (for edit_file)')
|
.option('--path <path>', 'File path (for edit_file)')
|
||||||
.option('--old <text>', 'Old text to replace (for edit_file)')
|
.option('--old <text>', 'Old text to replace (for edit_file)')
|
||||||
|
|||||||
@@ -68,11 +68,15 @@ async function schemaAction(options) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a tool with given parameters
|
* 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) {
|
if (!toolName) {
|
||||||
console.error(chalk.red('Tool name is required'));
|
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 <tool_name> \'{"param": "value"}\''));
|
||||||
|
console.error(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old" --new "new"'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +87,20 @@ async function execAction(toolName, options) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build params from CLI options
|
// Build params from CLI options or JSON
|
||||||
const params = {};
|
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) {
|
if (!options.path || !options.old || !options.new) {
|
||||||
console.error(chalk.red('edit_file requires --path, --old, and --new parameters'));
|
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"'));
|
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.path = options.path;
|
||||||
params.oldText = options.old;
|
params.oldText = options.old;
|
||||||
params.newText = options.new;
|
params.newText = options.new;
|
||||||
} else {
|
} else if (jsonParams) {
|
||||||
console.error(chalk.red(`Tool "${toolName}" is not supported via CLI parameters`));
|
// Non-JSON string provided but not for edit_file
|
||||||
console.error(chalk.gray('Currently only edit_file is supported'));
|
console.error(chalk.red('Parameters must be valid JSON'));
|
||||||
|
console.error(chalk.gray(`Usage: ccw tool exec ${toolName} '{"param": "value"}'`));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
// If no params provided, use empty object (tool may have defaults)
|
||||||
|
|
||||||
// Execute tool
|
// Execute tool
|
||||||
const result = await executeTool(toolName, params);
|
const result = await executeTool(toolName, params);
|
||||||
@@ -110,18 +126,24 @@ async function execAction(toolName, options) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool command entry point
|
* 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) {
|
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
|
// Handle subcommands
|
||||||
switch (subcommand) {
|
switch (subcommand) {
|
||||||
case 'list':
|
case 'list':
|
||||||
await listAction();
|
await listAction();
|
||||||
break;
|
break;
|
||||||
case 'schema':
|
case 'schema':
|
||||||
await schemaAction({ name: args });
|
await schemaAction({ name: argsArray[0] });
|
||||||
break;
|
break;
|
||||||
case 'exec':
|
case 'exec':
|
||||||
await execAction(args, options);
|
await execAction(argsArray[0], argsArray[1], options);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log(chalk.bold.cyan('\nCCW Tool System\n'));
|
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('Usage:');
|
||||||
console.log(chalk.gray(' ccw tool list'));
|
console.log(chalk.gray(' ccw tool list'));
|
||||||
console.log(chalk.gray(' ccw tool schema edit_file'));
|
console.log(chalk.gray(' ccw tool schema edit_file'));
|
||||||
|
console.log(chalk.gray(' ccw tool exec <tool_name> \'{"param": "value"}\''));
|
||||||
console.log(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old text" --new "new text"'));
|
console.log(chalk.gray(' ccw tool exec edit_file --path file.txt --old "old text" --new "new text"'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,28 @@
|
|||||||
color: hsl(var(--muted-foreground));
|
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 {
|
.session-type-badge {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@@ -98,27 +98,48 @@ function renderSessionCard(session) {
|
|||||||
const isActive = session._isActive !== false;
|
const isActive = session._isActive !== false;
|
||||||
const date = session.created_at;
|
const date = session.created_at;
|
||||||
|
|
||||||
|
// Detect planning status from session.status field
|
||||||
|
const isPlanning = session.status === 'planning';
|
||||||
|
|
||||||
// Get session type badge
|
// Get session type badge
|
||||||
const sessionType = session.type || 'workflow';
|
const sessionType = session.type || 'workflow';
|
||||||
const typeBadge = sessionType !== 'workflow' ? `<span class="session-type-badge ${sessionType}">${sessionType}</span>` : '';
|
const typeBadge = sessionType !== 'workflow' ? `<span class="session-type-badge ${sessionType}">${sessionType}</span>` : '';
|
||||||
|
|
||||||
|
// 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
|
// Store session data for modal
|
||||||
const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||||
sessionDataStore[sessionKey] = session;
|
sessionDataStore[sessionKey] = session;
|
||||||
|
|
||||||
// Special rendering for review sessions
|
// Special rendering for review sessions
|
||||||
if (sessionType === 'review') {
|
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 `
|
return `
|
||||||
<div class="session-card" onclick="showSessionDetailPage('${sessionKey}')">
|
<div class="${cardClass}" onclick="showSessionDetailPage('${sessionKey}')">
|
||||||
<div class="session-header">
|
<div class="session-header">
|
||||||
<div class="session-title">${escapeHtml(session.session_id || 'Unknown')}</div>
|
<div class="session-title">${escapeHtml(session.session_id || 'Unknown')}</div>
|
||||||
<div class="session-badges">
|
<div class="session-badges">
|
||||||
${typeBadge}
|
${typeBadge}
|
||||||
<span class="session-status ${isActive ? 'active' : 'archived'}">
|
<span class="session-status ${statusClass}">
|
||||||
${isActive ? 'ACTIVE' : 'ARCHIVED'}
|
${statusText}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +148,7 @@ function renderSessionCard(session) {
|
|||||||
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
|
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
|
||||||
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${taskCount} tasks</span>
|
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${taskCount} tasks</span>
|
||||||
</div>
|
</div>
|
||||||
${taskCount > 0 ? `
|
${taskCount > 0 && !isPlanning ? `
|
||||||
<div class="progress-container">
|
<div class="progress-container">
|
||||||
<span class="progress-label">Progress</span>
|
<span class="progress-label">Progress</span>
|
||||||
<div class="progress-bar-wrapper">
|
<div class="progress-bar-wrapper">
|
||||||
@@ -144,7 +165,7 @@ function renderSessionCard(session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special card rendering for review sessions
|
// 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
|
// Calculate findings stats from reviewDimensions
|
||||||
const dimensions = session.reviewDimensions || [];
|
const dimensions = session.reviewDimensions || [];
|
||||||
let totalFindings = 0;
|
let totalFindings = 0;
|
||||||
@@ -162,14 +183,31 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date)
|
|||||||
lowCount += findings.filter(f => f.severity === 'low').length;
|
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 `
|
return `
|
||||||
<div class="session-card" onclick="showSessionDetailPage('${sessionKey}')">
|
<div class="${cardClass}" onclick="showSessionDetailPage('${sessionKey}')">
|
||||||
<div class="session-header">
|
<div class="session-header">
|
||||||
<div class="session-title">${escapeHtml(session.session_id || 'Unknown')}</div>
|
<div class="session-title">${escapeHtml(session.session_id || 'Unknown')}</div>
|
||||||
<div class="session-badges">
|
<div class="session-badges">
|
||||||
${typeBadge}
|
${typeBadge}
|
||||||
<span class="session-status ${isActive ? 'active' : 'archived'}">
|
<span class="session-status ${statusClass}">
|
||||||
${isActive ? 'ACTIVE' : 'ARCHIVED'}
|
${statusText}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user