diff --git a/.claude/commands/issue/queue.md b/.claude/commands/issue/queue.md index a454766b..4a15d3d1 100644 --- a/.claude/commands/issue/queue.md +++ b/.claude/commands/issue/queue.md @@ -329,10 +329,83 @@ const result = Task( const summary = JSON.parse(result); ``` -### Phase 5: Summary & Status Update +### Phase 5: Validation & Status Update ```javascript -// Agent already generated queue files, use summary +// ============ VALIDATION: Prevent "next returns empty" issues ============ + +const queuesDir = '.workflow/issues/queues'; +const indexPath = `${queuesDir}/index.json`; +const queuePath = `${queuesDir}/${queueId}.json`; + +// 1. Validate index.json has active_queue_id +const indexContent = Bash(`cat "${indexPath}" 2>/dev/null || echo '{}'`); +const index = JSON.parse(indexContent); + +if (index.active_queue_id !== queueId) { + console.log(`⚠ Fixing: index.json active_queue_id not set to ${queueId}`); + index.active_queue_id = queueId; + // Ensure queue entry exists in index + if (!index.queues) index.queues = []; + const existing = index.queues.find(q => q.id === queueId); + if (!existing) { + index.queues.unshift({ + id: queueId, + status: 'active', + issue_ids: summary.issues_queued, + total_solutions: summary.total_solutions, + completed_solutions: 0, + created_at: new Date().toISOString() + }); + } + Bash(`echo '${JSON.stringify(index, null, 2)}' > "${indexPath}"`); + console.log(`✓ Fixed: index.json updated with active_queue_id: ${queueId}`); +} + +// 2. Validate queue file exists and has correct structure +const queueContent = Bash(`cat "${queuePath}" 2>/dev/null || echo '{}'`); +const queue = JSON.parse(queueContent); + +if (!queue.solutions || queue.solutions.length === 0) { + console.error(`✗ ERROR: Queue file ${queuePath} has no solutions array`); + console.error(' Agent did not generate queue correctly. Aborting.'); + return; +} + +// 3. Validate all solutions have status: "pending" (not "queued" or other) +let statusFixed = 0; +for (const sol of queue.solutions) { + if (sol.status !== 'pending' && sol.status !== 'executing' && sol.status !== 'completed') { + console.log(`⚠ Fixing: ${sol.item_id} status "${sol.status}" → "pending"`); + sol.status = 'pending'; + statusFixed++; + } +} + +// 4. Validate at least one item has no dependencies (DAG entry point) +const entryPoints = queue.solutions.filter(s => + s.status === 'pending' && (!s.depends_on || s.depends_on.length === 0) +); + +if (entryPoints.length === 0) { + console.error(`✗ ERROR: No entry points found (all items have dependencies)`); + console.error(' This will cause "ccw issue next" to return empty.'); + console.error(' Check depends_on fields for circular dependencies.'); + // Try to fix by clearing first item's dependencies + if (queue.solutions.length > 0) { + console.log(`⚠ Fixing: Clearing depends_on for first item ${queue.solutions[0].item_id}`); + queue.solutions[0].depends_on = []; + } +} + +// Write back fixed queue if any changes made +if (statusFixed > 0 || entryPoints.length === 0) { + Bash(`echo '${JSON.stringify(queue, null, 2)}' > "${queuePath}"`); + console.log(`✓ Queue file updated with ${statusFixed} status fixes`); +} + +// ============ OUTPUT SUMMARY ============ + console.log(` ## Queue Formed: ${summary.queue_id} @@ -341,15 +414,20 @@ console.log(` **Issues**: ${summary.issues_queued.join(', ')} **Groups**: ${summary.execution_groups.map(g => `${g.id}(${g.count})`).join(', ')} **Conflicts Resolved**: ${summary.conflicts_resolved} +**Entry Points**: ${entryPoints.length} (items ready for immediate execution) -Next: \`/issue:execute\` +Next: \`/issue:execute\` or \`ccw issue next\` `); -// Update issue statuses via CLI (use `update` for pure field changes) -// Note: `queue add` has its own logic; here we only need status update +// Update issue statuses via CLI for (const issueId of summary.issues_queued) { Bash(`ccw issue update ${issueId} --status queued`); } + +// Final verification +const verifyResult = Bash(`ccw issue queue dag 2>/dev/null | head -20`); +console.log('\n### Verification (DAG Preview):'); +console.log(verifyResult); ``` ## Error Handling @@ -360,6 +438,10 @@ for (const issueId of summary.issues_queued) { | Circular dependency | List cycles, abort queue formation | | Unresolved conflicts | Agent resolves using ordering rules | | Invalid task reference | Skip and warn | +| **index.json not updated** | Auto-fix: Set active_queue_id to new queue | +| **Wrong status value** | Auto-fix: Convert non-pending status to "pending" | +| **No entry points (all have deps)** | Auto-fix: Clear depends_on for first item | +| **Queue file missing solutions** | Abort with error, agent must regenerate | ## Related Commands diff --git a/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css b/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css index 9a53b713..bbe9e8bb 100644 --- a/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +++ b/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css @@ -234,19 +234,25 @@ color: hsl(var(--muted-foreground)); cursor: pointer; white-space: nowrap; - transition: all 0.15s; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .cli-stream-tab:hover { background: hsl(var(--hover)); color: hsl(var(--foreground)); + transform: translateY(-1px); } .cli-stream-tab.active { background: hsl(var(--card)); border-color: hsl(var(--primary)); color: hsl(var(--foreground)); - box-shadow: 0 1px 3px rgb(0 0 0 / 0.1); + box-shadow: 0 2px 8px rgb(0 0 0 / 0.15); + transform: translateY(-1px); +} + +.cli-stream-tab.active .cli-stream-tab-tool { + color: hsl(var(--primary)); } .cli-stream-tab-status { @@ -379,7 +385,13 @@ white-space: pre-wrap; word-break: break-all; margin: 0; - padding: 0; + padding: 2px 0; + border-radius: 2px; + transition: background-color 0.15s; +} + +.cli-stream-line:hover { + background: hsl(0 0% 100% / 0.03); } .cli-stream-line.stdout { @@ -388,17 +400,33 @@ .cli-stream-line.stderr { color: hsl(8 75% 65%); + background: hsl(8 75% 65% / 0.05); } .cli-stream-line.system { color: hsl(210 80% 65%); font-style: italic; + padding-left: 8px; + border-left: 2px solid hsl(210 80% 65% / 0.5); } .cli-stream-line.info { color: hsl(200 80% 70%); } +/* JSON/Code syntax coloring in output */ +.cli-stream-line .json-key { + color: hsl(200 80% 70%); +} + +.cli-stream-line .json-string { + color: hsl(100 50% 60%); +} + +.cli-stream-line .json-number { + color: hsl(40 80% 65%); +} + /* Search highlight */ .cli-stream-highlight { background: hsl(50 100% 50% / 0.4); diff --git a/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js b/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js index ea893749..6d453a1d 100644 --- a/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +++ b/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js @@ -17,10 +17,23 @@ function initCliStreamViewer() { // Initialize keyboard shortcuts document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && isCliStreamViewerOpen) { - toggleCliStreamViewer(); + if (searchFilter) { + clearSearch(); + } else { + toggleCliStreamViewer(); + } + } + // Ctrl+F to focus search when viewer is open + if ((e.ctrlKey || e.metaKey) && e.key === 'f' && isCliStreamViewerOpen) { + e.preventDefault(); + const searchInput = document.getElementById('cliStreamSearchInput'); + if (searchInput) { + searchInput.focus(); + searchInput.select(); + } } }); - + // Initialize scroll detection for auto-scroll const content = document.getElementById('cliStreamContent'); if (content) { @@ -491,7 +504,9 @@ function _streamT(key) { 'cliStream.autoScroll': 'Auto-scroll', 'cliStream.close': 'Close', 'cliStream.cannotCloseRunning': 'Cannot close running execution', - 'cliStream.lines': 'lines' + 'cliStream.lines': 'lines', + 'cliStream.searchPlaceholder': 'Search output...', + 'cliStream.filterResults': 'results' }; return fallbacks[key] || key; } diff --git a/ccw/src/templates/dashboard-js/i18n.js b/ccw/src/templates/dashboard-js/i18n.js index b659a1a8..ef720a1a 100644 --- a/ccw/src/templates/dashboard-js/i18n.js +++ b/ccw/src/templates/dashboard-js/i18n.js @@ -55,6 +55,8 @@ const i18n = { 'cliStream.close': 'Close', 'cliStream.cannotCloseRunning': 'Cannot close running execution', 'cliStream.lines': 'lines', + 'cliStream.searchPlaceholder': 'Search output...', + 'cliStream.filterResults': 'results', // Sidebar - Project section 'nav.project': 'Project', @@ -1975,6 +1977,8 @@ const i18n = { 'cliStream.close': '关闭', 'cliStream.cannotCloseRunning': '无法关闭运行中的执行', 'cliStream.lines': '行', + 'cliStream.searchPlaceholder': '搜索输出...', + 'cliStream.filterResults': '条结果', // Sidebar - Project section 'nav.project': '项目', diff --git a/ccw/src/templates/dashboard.html b/ccw/src/templates/dashboard.html index 22e7671c..97753790 100644 --- a/ccw/src/templates/dashboard.html +++ b/ccw/src/templates/dashboard.html @@ -618,6 +618,16 @@ CLI Stream 0 +