refactor: Update issue queue structure and commands

- Changed queue structure from 'queue' to 'tasks' in various files for clarity.
- Updated CLI commands to reflect new task ID usage instead of queue ID.
- Enhanced queue management with new delete functionality for historical queues.
- Improved metadata handling and task execution tracking.
- Updated dashboard and issue manager views to accommodate new task structure.
- Bumped version to 6.3.8 in package.json and package-lock.json.
This commit is contained in:
catlog22
2025-12-27 22:04:15 +08:00
parent 2e493277a1
commit b58589ddad
13 changed files with 394 additions and 336 deletions

View File

@@ -20,14 +20,7 @@ You are a specialized issue planning agent that combines exploration and plannin
```javascript
{
// Required
issues: [
{
id: string, // Issue ID (e.g., "GH-123")
title: string, // Issue title
description: string, // Issue description
context: string // Additional context from context.md
}
],
issue_ids: string[], // Issue IDs only (e.g., ["GH-123", "GH-124"])
project_root: string, // Project root path for ACE search
// Optional
@@ -36,6 +29,8 @@ You are a specialized issue planning agent that combines exploration and plannin
}
```
**Note**: Agent receives IDs only. Use `ccw issue status <id> --json` to fetch full details.
## Schema-Driven Output
**CRITICAL**: Read the solution schema first to determine output structure:
@@ -65,6 +60,31 @@ Phase 4: Validation & Output (15%)
## Phase 1: Issue Understanding
### Step 1: Fetch Issue Details via CLI
For each issue ID received, fetch full details:
```bash
ccw issue status <issue-id> --json
```
Returns:
```json
{
"issue": {
"id": "GH-123",
"title": "Add authentication",
"context": "...",
"affected_components": ["auth", "api"],
"lifecycle_requirements": { "test_strategy": "unit", "regression_scope": "affected" }
},
"solutions": [],
"bound": null
}
```
### Step 2: Analyze Issue
**Extract from each issue**:
- Title and description analysis
- Key requirements and constraints
@@ -661,6 +681,23 @@ function generateOutput(solutions, conflicts) {
}
```
### Solution Registration via CLI
**IMPORTANT**: Register solutions using CLI instead of direct file writes:
```bash
# 1. Write solution JSON to temp file
echo '<solution-json>' > /tmp/sol-{issue-id}.json
# 2. Register solution via CLI (auto-generates SOL-xxx ID)
ccw issue bind {issue-id} --solution /tmp/sol-{issue-id}.json
```
**CLI Output**: Returns registered solution ID for summary:
```
✓ Solution SOL-20251227-001 registered (5 tasks)
```
### Solution Schema (Closed-Loop Tasks)
Each task MUST include ALL 5 lifecycle phases:

View File

@@ -500,35 +500,35 @@ function canRunParallel(taskKey, groupTasks, taskGraph, conflicts) {
```javascript
function generateQueueItems(orderedTasks, taskGraph, conflicts) {
const queueItems = []
let queueIdCounter = 1
let itemIdCounter = 1
for (const key of orderedTasks) {
const node = taskGraph.get(key)
queueItems.push({
queue_id: `Q-${String(queueIdCounter++).padStart(3, '0')}`,
item_id: `T-${itemIdCounter++}`,
issue_id: node.issue_id,
solution_id: node.solution_id,
task_id: node.task.id,
status: 'pending',
execution_order: node.execution_order,
execution_group: node.execution_group,
depends_on: mapDependenciesToQueueIds(node, queueItems),
depends_on: mapDependenciesToItemIds(node, queueItems),
semantic_priority: node.semantic_priority,
queued_at: new Date().toISOString()
assigned_executor: node.task.executor || 'codex'
})
}
return queueItems
}
function mapDependenciesToQueueIds(node, queueItems) {
function mapDependenciesToItemIds(node, queueItems) {
return (node.task.depends_on || []).map(dep => {
const depKey = `${node.issue_id}:${dep}`
const queueItem = queueItems.find(q =>
q.issue_id === node.issue_id && q.task_id === dep
)
return queueItem?.queue_id || dep
return queueItem?.item_id || dep
})
}
```
@@ -538,7 +538,7 @@ function mapDependenciesToQueueIds(node, queueItems) {
```javascript
function generateOutput(queueItems, conflicts, groups) {
return {
queue: queueItems,
tasks: queueItems,
conflicts: conflicts.map(c => ({
type: c.type,
file: c.file,
@@ -652,10 +652,10 @@ function validateOrdering(queueItems, taskGraph) {
const node = taskGraph.get(key)
// Check dependencies come before
for (const depQueueId of item.depends_on) {
const depItem = queueItems.find(q => q.queue_id === depQueueId)
for (const depItemId of item.depends_on) {
const depItem = queueItems.find(q => q.item_id === depItemId)
if (depItem && depItem.execution_order >= item.execution_order) {
errors.push(`${item.queue_id} ordered before dependency ${depQueueId}`)
errors.push(`${item.item_id} ordered before dependency ${depItemId}`)
}
}
}
@@ -690,7 +690,7 @@ function validateOrdering(queueItems, taskGraph) {
5. Calculate semantic priority for all tasks
6. Validate ordering before output
7. Include rationale for conflict resolutions
8. Map depends_on to queue_ids in output
8. Map depends_on to item_ids in output
**NEVER**:
1. Execute tasks (ordering only)

View File

@@ -17,12 +17,14 @@ Execution orchestrator that coordinates codex instances. Each task is executed b
- No file reading in codex
- Orchestrator manages parallelism
## Storage Structure (Flat JSONL)
## Storage Structure (Queue History)
```
.workflow/issues/
├── issues.jsonl # All issues (one per line)
├── queue.json # Execution queue
├── queues/ # Queue history directory
│ ├── index.json # Queue index (active + history)
│ └── {queue-id}.json # Individual queue files
└── solutions/
├── {issue-id}.jsonl # Solutions for issue
└── ...
@@ -78,19 +80,19 @@ Phase 4: Completion
### Phase 1: Queue Loading
```javascript
// Load queue
const queuePath = '.workflow/issues/queue.json';
if (!Bash(`test -f "${queuePath}" && echo exists`).includes('exists')) {
console.log('No queue found. Run /issue:queue first.');
// Load active queue via CLI endpoint
const queueJson = Bash(`ccw issue status --json 2>/dev/null || echo '{}'`);
const queue = JSON.parse(queueJson);
if (!queue.id || queue.tasks?.length === 0) {
console.log('No active queue found. Run /issue:queue first.');
return;
}
const queue = JSON.parse(Read(queuePath));
// Count by status
const pending = queue.queue.filter(q => q.status === 'pending');
const executing = queue.queue.filter(q => q.status === 'executing');
const completed = queue.queue.filter(q => q.status === 'completed');
const pending = queue.tasks.filter(q => q.status === 'pending');
const executing = queue.tasks.filter(q => q.status === 'executing');
const completed = queue.tasks.filter(q => q.status === 'completed');
console.log(`
## Execution Queue Status
@@ -98,7 +100,7 @@ console.log(`
- Pending: ${pending.length}
- Executing: ${executing.length}
- Completed: ${completed.length}
- Total: ${queue.queue.length}
- Total: ${queue.tasks.length}
`);
if (pending.length === 0 && executing.length === 0) {
@@ -113,10 +115,10 @@ if (pending.length === 0 && executing.length === 0) {
// Find ready tasks (dependencies satisfied)
function getReadyTasks() {
const completedIds = new Set(
queue.queue.filter(q => q.status === 'completed').map(q => q.queue_id)
queue.tasks.filter(q => q.status === 'completed').map(q => q.item_id)
);
return queue.queue.filter(item => {
return queue.tasks.filter(item => {
if (item.status !== 'pending') return false;
return item.depends_on.every(depId => completedIds.has(depId));
});
@@ -141,9 +143,9 @@ readyTasks.sort((a, b) => a.execution_order - b.execution_order);
// Initialize TodoWrite
TodoWrite({
todos: readyTasks.slice(0, parallelLimit).map(t => ({
content: `[${t.queue_id}] ${t.issue_id}:${t.task_id}`,
content: `[${t.item_id}] ${t.issue_id}:${t.task_id}`,
status: 'pending',
activeForm: `Executing ${t.queue_id}`
activeForm: `Executing ${t.item_id}`
}))
});
```
@@ -207,7 +209,7 @@ This returns JSON with full lifecycle definition:
### Step 3: Report Completion
When ALL phases complete successfully:
\`\`\`bash
ccw issue complete <queue_id> --result '{
ccw issue complete <item_id> --result '{
"files_modified": ["path1", "path2"],
"tests_passed": true,
"regression_passed": true,
@@ -220,7 +222,7 @@ ccw issue complete <queue_id> --result '{
If any phase fails and cannot be fixed:
\`\`\`bash
ccw issue fail <queue_id> --reason "Phase X failed: <details>"
ccw issue fail <item_id> --reason "Phase X failed: <details>"
\`\`\`
### Rules
@@ -239,12 +241,12 @@ Begin by running: ccw issue next
if (executor === 'codex') {
Bash(
`ccw cli -p "${escapePrompt(codexPrompt)}" --tool codex --mode write --id exec-${queueItem.queue_id}`,
`ccw cli -p "${escapePrompt(codexPrompt)}" --tool codex --mode write --id exec-${queueItem.item_id}`,
timeout=3600000 // 1 hour timeout
);
} else if (executor === 'gemini') {
Bash(
`ccw cli -p "${escapePrompt(codexPrompt)}" --tool gemini --mode write --id exec-${queueItem.queue_id}`,
`ccw cli -p "${escapePrompt(codexPrompt)}" --tool gemini --mode write --id exec-${queueItem.item_id}`,
timeout=1800000 // 30 min timeout
);
} else {
@@ -252,7 +254,7 @@ Begin by running: ccw issue next
Task(
subagent_type="code-developer",
run_in_background=false,
description=`Execute ${queueItem.queue_id}`,
description=`Execute ${queueItem.item_id}`,
prompt=codexPrompt
);
}
@@ -265,23 +267,23 @@ for (let i = 0; i < readyTasks.length; i += parallelLimit) {
const batch = readyTasks.slice(i, i + parallelLimit);
console.log(`\n### Executing Batch ${Math.floor(i / parallelLimit) + 1}`);
console.log(batch.map(t => `- ${t.queue_id}: ${t.issue_id}:${t.task_id}`).join('\n'));
console.log(batch.map(t => `- ${t.item_id}: ${t.issue_id}:${t.task_id}`).join('\n'));
if (parallelLimit === 1) {
// Sequential execution
for (const task of batch) {
updateTodo(task.queue_id, 'in_progress');
updateTodo(task.item_id, 'in_progress');
await executeTask(task);
updateTodo(task.queue_id, 'completed');
updateTodo(task.item_id, 'completed');
}
} else {
// Parallel execution - launch all at once
const executions = batch.map(task => {
updateTodo(task.queue_id, 'in_progress');
updateTodo(task.item_id, 'in_progress');
return executeTask(task);
});
await Promise.all(executions);
batch.forEach(task => updateTodo(task.queue_id, 'completed'));
batch.forEach(task => updateTodo(task.item_id, 'completed'));
}
// Refresh ready tasks after batch
@@ -298,7 +300,7 @@ When codex calls `ccw issue next`, it receives:
```json
{
"queue_id": "Q-001",
"item_id": "T-1",
"issue_id": "GH-123",
"solution_id": "SOL-001",
"task": {
@@ -336,60 +338,38 @@ When codex calls `ccw issue next`, it receives:
### Phase 4: Completion Summary
```javascript
// Reload queue for final status
const finalQueue = JSON.parse(Read(queuePath));
// Reload queue for final status via CLI
const finalQueueJson = Bash(`ccw issue status --json 2>/dev/null || echo '{}'`);
const finalQueue = JSON.parse(finalQueueJson);
const summary = {
completed: finalQueue.queue.filter(q => q.status === 'completed').length,
failed: finalQueue.queue.filter(q => q.status === 'failed').length,
pending: finalQueue.queue.filter(q => q.status === 'pending').length,
total: finalQueue.queue.length
// Use queue._metadata for summary (already calculated by CLI)
const summary = finalQueue._metadata || {
completed_count: 0,
failed_count: 0,
pending_count: 0,
total_tasks: 0
};
console.log(`
## Execution Complete
**Completed**: ${summary.completed}/${summary.total}
**Failed**: ${summary.failed}
**Pending**: ${summary.pending}
**Completed**: ${summary.completed_count}/${summary.total_tasks}
**Failed**: ${summary.failed_count}
**Pending**: ${summary.pending_count}
### Task Results
${finalQueue.queue.map(q => {
${(finalQueue.tasks || []).map(q => {
const icon = q.status === 'completed' ? '✓' :
q.status === 'failed' ? '✗' :
q.status === 'executing' ? '⟳' : '○';
return `${icon} ${q.queue_id} [${q.issue_id}:${q.task_id}] - ${q.status}`;
return `${icon} ${q.item_id} [${q.issue_id}:${q.task_id}] - ${q.status}`;
}).join('\n')}
`);
// Update issue statuses in issues.jsonl
const issuesPath = '.workflow/issues/issues.jsonl';
const allIssues = Bash(`cat "${issuesPath}"`)
.split('\n')
.filter(line => line.trim())
.map(line => JSON.parse(line));
// Issue status updates are handled by ccw issue complete/fail endpoints
// No need to manually update issues.jsonl here
const issueIds = [...new Set(finalQueue.queue.map(q => q.issue_id))];
for (const issueId of issueIds) {
const issueTasks = finalQueue.queue.filter(q => q.issue_id === issueId);
if (issueTasks.every(q => q.status === 'completed')) {
console.log(`\n✓ Issue ${issueId} fully completed!`);
// Update issue status
const issueIndex = allIssues.findIndex(i => i.id === issueId);
if (issueIndex !== -1) {
allIssues[issueIndex].status = 'completed';
allIssues[issueIndex].completed_at = new Date().toISOString();
allIssues[issueIndex].updated_at = new Date().toISOString();
}
}
}
// Write updated issues.jsonl
Write(issuesPath, allIssues.map(i => JSON.stringify(i)).join('\n'));
if (summary.pending > 0) {
if (summary.pending_count > 0) {
console.log(`
### Continue Execution
Run \`/issue:execute\` again to execute remaining tasks.
@@ -405,7 +385,7 @@ if (flags.dryRun) {
## Dry Run - Would Execute
${readyTasks.map((t, i) => `
${i + 1}. ${t.queue_id}
${i + 1}. ${t.item_id}
Issue: ${t.issue_id}
Task: ${t.task_id}
Executor: ${t.assigned_executor}
@@ -426,7 +406,32 @@ No changes made. Remove --dry-run to execute.
| No ready tasks | Check dependencies, show blocked tasks |
| Codex timeout | Mark as failed, allow retry |
| ccw issue next empty | All tasks done or blocked |
| Task execution failure | Marked via ccw issue fail |
| Task execution failure | Marked via ccw issue fail, use `ccw issue retry` to reset |
## Troubleshooting
### Interrupted Tasks
If execution was interrupted (crashed/stopped), `ccw issue next` will automatically resume:
```bash
# Automatically returns the executing task for resumption
ccw issue next
```
Tasks in `executing` status are prioritized and returned first, no manual reset needed.
### Failed Tasks
If a task failed and you want to retry:
```bash
# Reset all failed tasks to pending
ccw issue retry
# Reset failed tasks for specific issue
ccw issue retry <issue-id>
```
## Endpoint Contract
@@ -435,16 +440,20 @@ No changes made. Remove --dry-run to execute.
- Marks task as 'executing'
- Returns `{ status: 'empty' }` when no tasks
### `ccw issue complete <queue-id>`
### `ccw issue complete <item-id>`
- Marks task as 'completed'
- Updates queue.json
- Checks if issue is fully complete
### `ccw issue fail <queue-id>`
### `ccw issue fail <item-id>`
- Marks task as 'failed'
- Records failure reason
- Allows retry via /issue:execute
### `ccw issue retry [issue-id]`
- Resets failed tasks to 'pending'
- Allows re-execution via `ccw issue next`
## Related Commands
- `/issue:plan` - Plan issues with solutions

View File

@@ -30,7 +30,7 @@ ccw issue task <id> --title "..." # Add task
ccw issue queue # List queue
ccw issue queue add <id> # Add to queue
ccw issue next # Get next task
ccw issue done <queue-id> # Complete task
ccw issue complete <item-id> # Complete task
```
## Usage
@@ -561,7 +561,7 @@ async function deleteIssueInteractive(issueId) {
const queuePath = '.workflow/issues/queue.json';
if (Bash(`test -f "${queuePath}" && echo exists`) === 'exists') {
const queue = JSON.parse(Bash(`cat "${queuePath}"`));
queue.queue = queue.queue.filter(q => q.issue_id !== issueId);
queue.tasks = queue.tasks.filter(q => q.issue_id !== issueId);
Write(queuePath, JSON.stringify(queue, null, 2));
}

View File

@@ -75,26 +75,16 @@ Phase 4: Summary
## Implementation
### Phase 1: Issue Loading
### Phase 1: Issue Loading (IDs Only)
```javascript
// Parse input and flags
const issuesPath = '.workflow/issues/issues.jsonl';
const batchSize = flags.batchSize || 3;
// Key fields for planning (avoid loading full issue data)
const PLAN_FIELDS = 'id,title,status,context,affected_components,lifecycle_requirements,priority,bound_solution_id';
let issueIds = [];
if (flags.allPending) {
// Use jq to filter pending/registered issues - extract only IDs
const pendingIds = Bash(`
cat "${issuesPath}" 2>/dev/null | \\
jq -r 'select(.status == "pending" or .status == "registered") | .id' 2>/dev/null || echo ''
`).trim();
issueIds = pendingIds ? pendingIds.split('\n').filter(Boolean) : [];
// Get pending issue IDs directly via CLI
const ids = Bash(`ccw issue list --status pending,registered --ids`).trim();
issueIds = ids ? ids.split('\n').filter(Boolean) : [];
if (issueIds.length === 0) {
console.log('No pending issues found.');
@@ -106,50 +96,27 @@ if (flags.allPending) {
issueIds = userInput.includes(',')
? userInput.split(',').map(s => s.trim())
: [userInput.trim()];
}
// Load issues using jq to extract only key fields
const issues = [];
for (const id of issueIds) {
// Use jq to find issue by ID and extract only needed fields
const issueJson = Bash(`
cat "${issuesPath}" 2>/dev/null | \\
jq -c 'select(.id == "${id}") | {${PLAN_FIELDS}}' 2>/dev/null | head -1
`).trim();
let issue;
if (issueJson) {
issue = JSON.parse(issueJson);
} else {
console.log(`Issue ${id} not found. Creating...`);
issue = {
id,
title: `Issue ${id}`,
status: 'registered',
priority: 3,
context: '',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
// Append to issues.jsonl
Bash(`echo '${JSON.stringify(issue)}' >> "${issuesPath}"`);
// Create if not exists
for (const id of issueIds) {
Bash(`ccw issue init ${id} --title "Issue ${id}" 2>/dev/null || true`);
}
issues.push(issue);
}
// Group into batches
const batches = [];
for (let i = 0; i < issues.length; i += batchSize) {
batches.push(issues.slice(i, i + batchSize));
for (let i = 0; i < issueIds.length; i += batchSize) {
batches.push(issueIds.slice(i, i + batchSize));
}
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es)`);
console.log(`Processing ${issueIds.length} issues in ${batches.length} batch(es)`);
TodoWrite({
todos: batches.flatMap((batch, i) => [
{ content: `Plan batch ${i+1}`, status: 'pending', activeForm: `Planning batch ${i+1}` }
])
todos: batches.map((_, i) => ({
content: `Plan batch ${i+1}`,
status: 'pending',
activeForm: `Planning batch ${i+1}`
}))
});
```
@@ -162,36 +129,47 @@ Bash(`mkdir -p .workflow/issues/solutions`);
for (const [batchIndex, batch] of batches.entries()) {
updateTodo(`Plan batch ${batchIndex + 1}`, 'in_progress');
// Build issue prompt for agent - agent writes solutions directly
// Build issue prompt for agent - pass IDs only, agent fetches details
const issuePrompt = `
## Issues to Plan (Closed-Loop Tasks Required)
${batch.map((issue, i) => `
### Issue ${i + 1}: ${issue.id}
**Title**: ${issue.title}
**Context**: ${issue.context || 'No context provided'}
**Affected Components**: ${issue.affected_components?.join(', ') || 'Not specified'}
**Issue IDs**: ${batch.join(', ')}
**Lifecycle Requirements**:
- Test Strategy: ${issue.lifecycle_requirements?.test_strategy || 'auto'}
- Regression Scope: ${issue.lifecycle_requirements?.regression_scope || 'affected'}
- Commit Strategy: ${issue.lifecycle_requirements?.commit_strategy || 'per-task'}
`).join('\n')}
### Step 1: Fetch Issue Details
For each issue ID, use CLI to get full details:
\`\`\`bash
ccw issue status <issue-id> --json
\`\`\`
Returns:
\`\`\`json
{
"issue": { "id", "title", "context", "affected_components", "lifecycle_requirements", ... },
"solutions": [...],
"bound": null
}
\`\`\`
## Project Root
${process.cwd()}
## Output Requirements
**IMPORTANT**: Write solutions DIRECTLY to files, do NOT return full solution content.
**IMPORTANT**: Register solutions via CLI, do NOT write files directly.
### 1. Write Solution Files
For each issue, write solution to: \`.workflow/issues/solutions/{issue-id}.jsonl\`
- Append one JSON line per solution
### 1. Register Solutions via CLI
For each issue, save solution to temp file and register via CLI:
\`\`\`bash
# Write solution JSON to temp file
echo '<solution-json>' > /tmp/sol-{issue-id}.json
# Register solution via CLI (generates SOL-xxx ID automatically)
ccw issue bind {issue-id} --solution /tmp/sol-{issue-id}.json
\`\`\`
- Solution must include all closed-loop task fields (see Solution Format below)
### 2. Return Summary Only
After writing solutions, return ONLY a brief JSON summary:
After registering solutions, return ONLY a brief JSON summary:
\`\`\`json
{
"planned": [
@@ -271,31 +249,34 @@ Each task MUST include ALL lifecycle phases:
// Collect issues needing user selection (multiple solutions)
const needSelection = [];
for (const issue of issues) {
const solPath = `.workflow/issues/solutions/${issue.id}.jsonl`;
for (const issueId of issueIds) {
// Get solutions via CLI
const statusJson = Bash(`ccw issue status ${issueId} --json 2>/dev/null || echo '{}'`).trim();
const status = JSON.parse(statusJson);
const solutions = status.solutions || [];
// Use jq to count solutions
const count = parseInt(Bash(`cat "${solPath}" 2>/dev/null | jq -s 'length' 2>/dev/null || echo '0'`).trim()) || 0;
if (solutions.length === 0) continue; // No solutions - skip silently (agent already reported)
if (count === 0) continue; // No solutions - skip silently (agent already reported)
if (count === 1) {
if (solutions.length === 1) {
// Auto-bind single solution
const solId = Bash(`cat "${solPath}" | jq -r '.id' | head -1`).trim();
bindSolution(issue.id, solId);
bindSolution(issueId, solutions[0].id);
} else {
// Multiple solutions - collect for batch selection
const options = Bash(`cat "${solPath}" | jq -c '{id, description, task_count: (.tasks | length)}'`).trim();
needSelection.push({ issue, options: options.split('\n').map(s => JSON.parse(s)) });
const options = solutions.map(s => ({
id: s.id,
description: s.description,
task_count: (s.tasks || []).length
}));
needSelection.push({ issueId, options });
}
}
// Batch ask user for multiple-solution issues
if (needSelection.length > 0) {
const answer = AskUserQuestion({
questions: needSelection.map(({ issue, options }) => ({
question: `Select solution for ${issue.id}:`,
header: issue.id,
questions: needSelection.map(({ issueId, options }) => ({
question: `Select solution for ${issueId}:`,
header: issueId,
multiSelect: false,
options: options.map(s => ({
label: `${s.id} (${s.task_count} tasks)`,
@@ -305,47 +286,27 @@ if (needSelection.length > 0) {
});
// Bind selected solutions
for (const { issue } of needSelection) {
const selectedSolId = extractSelectedSolutionId(answer, issue.id);
if (selectedSolId) bindSolution(issue.id, selectedSolId);
for (const { issueId } of needSelection) {
const selectedSolId = extractSelectedSolutionId(answer, issueId);
if (selectedSolId) bindSolution(issueId, selectedSolId);
}
}
// Helper: bind solution to issue
// Helper: bind solution to issue (using CLI for safety)
function bindSolution(issueId, solutionId) {
const now = new Date().toISOString();
const solPath = `.workflow/issues/solutions/${issueId}.jsonl`;
// Update issue status
Bash(`
tmpfile=$(mktemp) && \\
cat "${issuesPath}" | jq -c 'if .id == "${issueId}" then . + {
bound_solution_id: "${solutionId}", status: "planned",
planned_at: "${now}", updated_at: "${now}"
} else . end' > "$tmpfile" && mv "$tmpfile" "${issuesPath}"
`);
// Mark solution as bound
Bash(`
tmpfile=$(mktemp) && \\
cat "${solPath}" | jq -c 'if .id == "${solutionId}" then . + {
is_bound: true, bound_at: "${now}"
} else . + {is_bound: false} end' > "$tmpfile" && mv "$tmpfile" "${solPath}"
`);
Bash(`ccw issue bind ${issueId} ${solutionId}`);
}
```
### Phase 4: Summary
```javascript
// Brief summary using jq
const stats = Bash(`
cat "${issuesPath}" 2>/dev/null | \\
jq -s '[.[] | select(.status == "planned")] | length' 2>/dev/null || echo '0'
`).trim();
// Count planned issues via CLI
const plannedIds = Bash(`ccw issue list --status planned --ids`).trim();
const plannedCount = plannedIds ? plannedIds.split('\n').length : 0;
console.log(`
## Done: ${issues.length} issues → ${stats} planned
## Done: ${issueIds.length} issues → ${plannedCount} planned
Next: \`/issue:queue\`\`/issue:execute\`
`);

View File

@@ -77,10 +77,12 @@ Queue formation command using **issue-queue-agent** that analyzes all bound solu
# Flags
--issue <id> Form queue for specific issue only
--append <id> Append issue to active queue (don't create new)
--list List all queues with status
--switch <queue-id> Switch active queue
--archive Archive current queue (mark completed)
--clear <queue-id> Delete a queue from history
# CLI subcommands (ccw issue queue ...)
ccw issue queue list List all queues with status
ccw issue queue switch <queue-id> Switch active queue
ccw issue queue archive Archive current queue
ccw issue queue delete <queue-id> Delete queue from history
```
## Execution Process
@@ -234,7 +236,7 @@ Write(issuesPath, updatedIssues.map(i => JSON.stringify(i)).join('\n'));
console.log(`
## Queue Formed
**Total Tasks**: ${queueOutput.queue.length}
**Total Tasks**: ${queueOutput.tasks.length}
**Issues**: ${plannedIssues.length}
**Conflicts**: ${queueOutput.conflicts?.length || 0} (${queueOutput._metadata?.resolved_conflicts || 0} resolved)
@@ -256,14 +258,13 @@ Output `queues/{queue-id}.json`:
```json
{
"id": "QUE-20251227-143000",
"name": "Auth Feature Queue",
"status": "active",
"issue_ids": ["GH-123", "GH-124"],
"queue": [
"tasks": [
{
"queue_id": "Q-001",
"item_id": "T-1",
"issue_id": "GH-123",
"solution_id": "SOL-001",
"task_id": "T1",
@@ -271,8 +272,7 @@ Output `queues/{queue-id}.json`:
"execution_order": 1,
"execution_group": "P1",
"depends_on": [],
"semantic_priority": 0.7,
"queued_at": "2025-12-26T10:00:00Z"
"semantic_priority": 0.7
}
],
@@ -289,17 +289,16 @@ Output `queues/{queue-id}.json`:
],
"execution_groups": [
{ "id": "P1", "type": "parallel", "task_count": 3, "tasks": ["GH-123:T1", "GH-124:T1", "GH-125:T1"] },
{ "id": "S2", "type": "sequential", "task_count": 2, "tasks": ["GH-123:T2", "GH-124:T2"] }
{ "id": "P1", "type": "parallel", "task_count": 3, "tasks": ["T-1", "T-2", "T-3"] },
{ "id": "S2", "type": "sequential", "task_count": 2, "tasks": ["T-4", "T-5"] }
],
"_metadata": {
"version": "2.0",
"version": "2.1-optimized",
"total_tasks": 5,
"pending_count": 3,
"completed_count": 2,
"failed_count": 0,
"created_at": "2025-12-26T10:00:00Z",
"updated_at": "2025-12-26T11:00:00Z",
"source": "issue-queue-agent"
}