Skip to content

Commit 2e49327

Browse files
author
catlog22
committed
feat(issue-queue): Enhance task execution flow with priority handling and stuck task reset option
1 parent 8b19edd commit 2e49327

File tree

2 files changed

+194
-112
lines changed

2 files changed

+194
-112
lines changed

.claude/commands/issue/plan.md

Lines changed: 139 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: plan
33
description: Batch plan issue resolution using issue-plan-agent (explore + plan closed-loop)
4-
argument-hint: "<issue-id>[,<issue-id>,...] [--batch-size 3]"
4+
argument-hint: "<issue-id>[,<issue-id>,...] [--batch-size 3] --all-pending"
55
allowed-tools: TodoWrite(*), Task(*), SlashCommand(*), AskUserQuestion(*), Bash(*), Read(*), Write(*)
66
---
77

@@ -78,24 +78,49 @@ Phase 4: Summary
7878
### Phase 1: Issue Loading
7979

8080
```javascript
81-
// Parse input
82-
const issueIds = userInput.includes(',')
83-
? userInput.split(',').map(s => s.trim())
84-
: [userInput.trim()];
85-
86-
// Read issues.jsonl
81+
// Parse input and flags
8782
const issuesPath = '.workflow/issues/issues.jsonl';
88-
const allIssues = Bash(`cat "${issuesPath}" 2>/dev/null || echo ''`)
89-
.split('\n')
90-
.filter(line => line.trim())
91-
.map(line => JSON.parse(line));
83+
const batchSize = flags.batchSize || 3;
84+
85+
// Key fields for planning (avoid loading full issue data)
86+
const PLAN_FIELDS = 'id,title,status,context,affected_components,lifecycle_requirements,priority,bound_solution_id';
87+
88+
let issueIds = [];
89+
90+
if (flags.allPending) {
91+
// Use jq to filter pending/registered issues - extract only IDs
92+
const pendingIds = Bash(`
93+
cat "${issuesPath}" 2>/dev/null | \\
94+
jq -r 'select(.status == "pending" or .status == "registered") | .id' 2>/dev/null || echo ''
95+
`).trim();
96+
97+
issueIds = pendingIds ? pendingIds.split('\n').filter(Boolean) : [];
98+
99+
if (issueIds.length === 0) {
100+
console.log('No pending issues found.');
101+
return;
102+
}
103+
console.log(`Found ${issueIds.length} pending issues`);
104+
} else {
105+
// Parse comma-separated issue IDs
106+
issueIds = userInput.includes(',')
107+
? userInput.split(',').map(s => s.trim())
108+
: [userInput.trim()];
109+
}
92110

93-
// Load and validate issues
111+
// Load issues using jq to extract only key fields
94112
const issues = [];
95113
for (const id of issueIds) {
96-
let issue = allIssues.find(i => i.id === id);
97-
98-
if (!issue) {
114+
// Use jq to find issue by ID and extract only needed fields
115+
const issueJson = Bash(`
116+
cat "${issuesPath}" 2>/dev/null | \\
117+
jq -c 'select(.id == "${id}") | {${PLAN_FIELDS}}' 2>/dev/null | head -1
118+
`).trim();
119+
120+
let issue;
121+
if (issueJson) {
122+
issue = JSON.parse(issueJson);
123+
} else {
99124
console.log(`Issue ${id} not found. Creating...`);
100125
issue = {
101126
id,
@@ -114,12 +139,13 @@ for (const id of issueIds) {
114139
}
115140

116141
// Group into batches
117-
const batchSize = flags.batchSize || 3;
118142
const batches = [];
119143
for (let i = 0; i < issues.length; i += batchSize) {
120144
batches.push(issues.slice(i, i + batchSize));
121145
}
122146

147+
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es)`);
148+
123149
TodoWrite({
124150
todos: batches.flatMap((batch, i) => [
125151
{ content: `Plan batch ${i+1}`, status: 'pending', activeForm: `Planning batch ${i+1}` }
@@ -130,10 +156,13 @@ TodoWrite({
130156
### Phase 2: Unified Explore + Plan (issue-plan-agent)
131157

132158
```javascript
159+
// Ensure solutions directory exists
160+
Bash(`mkdir -p .workflow/issues/solutions`);
161+
133162
for (const [batchIndex, batch] of batches.entries()) {
134163
updateTodo(`Plan batch ${batchIndex + 1}`, 'in_progress');
135164

136-
// Build issue prompt for agent with lifecycle requirements
165+
// Build issue prompt for agent - agent writes solutions directly
137166
const issuePrompt = `
138167
## Issues to Plan (Closed-Loop Tasks Required)
139168
@@ -152,7 +181,29 @@ ${batch.map((issue, i) => `
152181
## Project Root
153182
${process.cwd()}
154183
155-
## Requirements - CLOSED-LOOP TASKS
184+
## Output Requirements
185+
186+
**IMPORTANT**: Write solutions DIRECTLY to files, do NOT return full solution content.
187+
188+
### 1. Write Solution Files
189+
For each issue, write solution to: \`.workflow/issues/solutions/{issue-id}.jsonl\`
190+
- Append one JSON line per solution
191+
- Solution must include all closed-loop task fields (see Solution Format below)
192+
193+
### 2. Return Summary Only
194+
After writing solutions, return ONLY a brief JSON summary:
195+
\`\`\`json
196+
{
197+
"planned": [
198+
{ "issue_id": "XXX", "solution_id": "SOL-xxx", "task_count": 3, "description": "Brief description" }
199+
],
200+
"conflicts": [
201+
{ "file": "path/to/file", "issues": ["ID1", "ID2"], "suggested_order": ["ID1", "ID2"] }
202+
]
203+
}
204+
\`\`\`
205+
206+
## Closed-Loop Task Requirements
156207
157208
Each task MUST include ALL lifecycle phases:
158209
@@ -168,12 +219,10 @@ Each task MUST include ALL lifecycle phases:
168219
169220
### 3. Regression
170221
- regression: string[] (commands to run for regression check)
171-
- Based on issue's regression_scope setting
172222
173223
### 4. Acceptance
174224
- acceptance.criteria: string[] (testable acceptance criteria)
175225
- acceptance.verification: string[] (how to verify each criterion)
176-
- acceptance.manual_checks: string[] (manual checks if needed)
177226
178227
### 5. Commit
179228
- commit.type: feat|fix|refactor|test|docs|chore
@@ -188,32 +237,26 @@ Each task MUST include ALL lifecycle phases:
188237
4. Infer commit scope from affected files
189238
`;
190239
191-
// Launch issue-plan-agent (combines explore + plan)
240+
// Launch issue-plan-agent - agent writes solutions directly
192241
const result = Task(
193242
subagent_type="issue-plan-agent",
194243
run_in_background=false,
195244
description=`Explore & plan ${batch.length} issues`,
196245
prompt=issuePrompt
197246
);
198247
199-
// Parse agent output
200-
const agentOutput = JSON.parse(result);
201-
202-
// Register solutions for each issue (append to solutions/{issue-id}.jsonl)
203-
for (const item of agentOutput.solutions) {
204-
const solutionPath = `.workflow/issues/solutions/${item.issue_id}.jsonl`;
248+
// Parse brief summary from agent
249+
const summary = JSON.parse(result);
205250
206-
// Ensure solutions directory exists
207-
Bash(`mkdir -p .workflow/issues/solutions`);
208-
209-
// Append solution as new line
210-
Bash(`echo '${JSON.stringify(item.solution)}' >> "${solutionPath}"`);
251+
// Display planning results
252+
for (const item of summary.planned || []) {
253+
console.log(`${item.issue_id}: ${item.solution_id} (${item.task_count} tasks) - ${item.description}`);
211254
}
212255
213256
// Handle conflicts if any
214-
if (agentOutput.conflicts?.length > 0) {
257+
if (summary.conflicts?.length > 0) {
215258
console.log(`\n⚠ File conflicts detected:`);
216-
agentOutput.conflicts.forEach(c => {
259+
summary.conflicts.forEach(c => {
217260
console.log(` ${c.file}: ${c.issues.join(', ')} → suggested: ${c.suggested_order.join('')}`);
218261
});
219262
}
@@ -225,90 +268,86 @@ Each task MUST include ALL lifecycle phases:
225268
### Phase 3: Solution Binding
226269
227270
```javascript
228-
// Re-read issues.jsonl
229-
let allIssuesUpdated = Bash(`cat "${issuesPath}"`)
230-
.split('\n')
231-
.filter(line => line.trim())
232-
.map(line => JSON.parse(line));
271+
// Collect issues needing user selection (multiple solutions)
272+
const needSelection = [];
233273
234274
for (const issue of issues) {
235275
const solPath = `.workflow/issues/solutions/${issue.id}.jsonl`;
236-
const solutions = Bash(`cat "${solPath}" 2>/dev/null || echo ''`)
237-
.split('\n')
238-
.filter(line => line.trim())
239-
.map(line => JSON.parse(line));
240-
241-
if (solutions.length === 0) {
242-
console.log(`⚠ No solutions for ${issue.id}`);
243-
continue;
244-
}
245276
246-
let selectedSolId;
277+
// Use jq to count solutions
278+
const count = parseInt(Bash(`cat "${solPath}" 2>/dev/null | jq -s 'length' 2>/dev/null || echo '0'`).trim()) || 0;
247279
248-
if (solutions.length === 1) {
280+
if (count === 0) continue; // No solutions - skip silently (agent already reported)
281+
282+
if (count === 1) {
249283
// Auto-bind single solution
250-
selectedSolId = solutions[0].id;
251-
console.log(`✓ Auto-bound ${selectedSolId} to ${issue.id} (${solutions[0].tasks?.length || 0} tasks)`);
284+
const solId = Bash(`cat "${solPath}" | jq -r '.id' | head -1`).trim();
285+
bindSolution(issue.id, solId);
252286
} else {
253-
// Multiple solutions - ask user
254-
const answer = AskUserQuestion({
255-
questions: [{
256-
question: `Select solution for ${issue.id}:`,
257-
header: issue.id,
258-
multiSelect: false,
259-
options: solutions.map(s => ({
260-
label: `${s.id}: ${s.description || 'Solution'}`,
261-
description: `${s.tasks?.length || 0} tasks`
262-
}))
263-
}]
264-
});
265-
266-
selectedSolId = extractSelectedSolutionId(answer);
267-
console.log(`✓ Bound ${selectedSolId} to ${issue.id}`);
287+
// Multiple solutions - collect for batch selection
288+
const options = Bash(`cat "${solPath}" | jq -c '{id, description, task_count: (.tasks | length)}'`).trim();
289+
needSelection.push({ issue, options: options.split('\n').map(s => JSON.parse(s)) });
268290
}
291+
}
269292
270-
// Update issue in allIssuesUpdated
271-
const issueIndex = allIssuesUpdated.findIndex(i => i.id === issue.id);
272-
if (issueIndex !== -1) {
273-
allIssuesUpdated[issueIndex].bound_solution_id = selectedSolId;
274-
allIssuesUpdated[issueIndex].status = 'planned';
275-
allIssuesUpdated[issueIndex].planned_at = new Date().toISOString();
276-
allIssuesUpdated[issueIndex].updated_at = new Date().toISOString();
293+
// Batch ask user for multiple-solution issues
294+
if (needSelection.length > 0) {
295+
const answer = AskUserQuestion({
296+
questions: needSelection.map(({ issue, options }) => ({
297+
question: `Select solution for ${issue.id}:`,
298+
header: issue.id,
299+
multiSelect: false,
300+
options: options.map(s => ({
301+
label: `${s.id} (${s.task_count} tasks)`,
302+
description: s.description || 'Solution'
303+
}))
304+
}))
305+
});
306+
307+
// Bind selected solutions
308+
for (const { issue } of needSelection) {
309+
const selectedSolId = extractSelectedSolutionId(answer, issue.id);
310+
if (selectedSolId) bindSolution(issue.id, selectedSolId);
277311
}
278-
279-
// Mark solution as bound in solutions file
280-
const updatedSolutions = solutions.map(s => ({
281-
...s,
282-
is_bound: s.id === selectedSolId,
283-
bound_at: s.id === selectedSolId ? new Date().toISOString() : s.bound_at
284-
}));
285-
Write(solPath, updatedSolutions.map(s => JSON.stringify(s)).join('\n'));
286312
}
287313
288-
// Write updated issues.jsonl
289-
Write(issuesPath, allIssuesUpdated.map(i => JSON.stringify(i)).join('\n'));
314+
// Helper: bind solution to issue
315+
function bindSolution(issueId, solutionId) {
316+
const now = new Date().toISOString();
317+
const solPath = `.workflow/issues/solutions/${issueId}.jsonl`;
318+
319+
// Update issue status
320+
Bash(`
321+
tmpfile=$(mktemp) && \\
322+
cat "${issuesPath}" | jq -c 'if .id == "${issueId}" then . + {
323+
bound_solution_id: "${solutionId}", status: "planned",
324+
planned_at: "${now}", updated_at: "${now}"
325+
} else . end' > "$tmpfile" && mv "$tmpfile" "${issuesPath}"
326+
`);
327+
328+
// Mark solution as bound
329+
Bash(`
330+
tmpfile=$(mktemp) && \\
331+
cat "${solPath}" | jq -c 'if .id == "${solutionId}" then . + {
332+
is_bound: true, bound_at: "${now}"
333+
} else . + {is_bound: false} end' > "$tmpfile" && mv "$tmpfile" "${solPath}"
334+
`);
335+
}
290336
```
291337
292338
### Phase 4: Summary
293339
294340
```javascript
341+
// Brief summary using jq
342+
const stats = Bash(`
343+
cat "${issuesPath}" 2>/dev/null | \\
344+
jq -s '[.[] | select(.status == "planned")] | length' 2>/dev/null || echo '0'
345+
`).trim();
346+
295347
console.log(`
296-
## Planning Complete
297-
298-
**Issues Planned**: ${issues.length}
299-
300-
### Bound Solutions
301-
${issues.map(i => {
302-
const issue = allIssuesUpdated.find(a => a.id === i.id);
303-
return issue?.bound_solution_id
304-
? `${i.id}: ${issue.bound_solution_id}`
305-
: `${i.id}: No solution bound`;
306-
}).join('\n')}
307-
308-
### Next Steps
309-
1. Review: \`ccw issue status <issue-id>\`
310-
2. Form queue: \`/issue:queue\`
311-
3. Execute: \`/issue:execute\`
348+
## Done: ${issues.length} issues → ${stats} planned
349+
350+
Next: \`/issue:queue\`\`/issue:execute\`
312351
`);
313352
```
314353

0 commit comments

Comments
 (0)