claude-code-workflow 6.3.12 → 6.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/issue-plan-agent.md +77 -5
- package/.claude/agents/issue-queue-agent.md +122 -18
- package/.claude/commands/issue/execute.md +53 -40
- package/.claude/commands/issue/new.md +113 -11
- package/.claude/commands/issue/plan.md +112 -37
- package/.claude/commands/issue/queue.md +17 -89
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +141 -168
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +3 -2
- package/.codex/prompts/issue-execute.md +3 -3
- package/.codex/prompts/issue-queue.md +3 -3
- package/package.json +83 -83
|
@@ -128,40 +128,46 @@ if (flags.allPending) {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
//
|
|
132
|
-
function
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
131
|
+
// Semantic grouping via Gemini CLI (max 6 issues per group)
|
|
132
|
+
async function groupBySimilarityGemini(issues) {
|
|
133
|
+
const issueSummaries = issues.map(i => ({
|
|
134
|
+
id: i.id, title: i.title, tags: i.tags
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
const prompt = `
|
|
138
|
+
PURPOSE: Group similar issues by semantic similarity for batch processing; maximize within-group coherence; max 6 issues per group
|
|
139
|
+
TASK: • Analyze issue titles/tags semantically • Identify functional/architectural clusters • Assign each issue to one group
|
|
140
|
+
MODE: analysis
|
|
141
|
+
CONTEXT: Issue metadata only
|
|
142
|
+
EXPECTED: JSON with groups array, each containing max 6 issue_ids, theme, rationale
|
|
143
|
+
RULES: $(cat ~/.claude/workflows/cli-templates/protocols/analysis-protocol.md) | Each issue in exactly one group | Max 6 issues per group | Balance group sizes
|
|
144
|
+
|
|
145
|
+
INPUT:
|
|
146
|
+
${JSON.stringify(issueSummaries, null, 2)}
|
|
147
|
+
|
|
148
|
+
OUTPUT FORMAT:
|
|
149
|
+
{"groups":[{"group_id":1,"theme":"...","issue_ids":["..."],"rationale":"..."}],"ungrouped":[]}
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const taskId = Bash({
|
|
153
|
+
command: `ccw cli -p "${prompt}" --tool gemini --mode analysis`,
|
|
154
|
+
run_in_background: true, timeout: 600000
|
|
155
|
+
});
|
|
156
|
+
const output = TaskOutput({ task_id: taskId, block: true });
|
|
157
|
+
|
|
158
|
+
// Extract JSON from potential markdown code blocks
|
|
159
|
+
function extractJsonFromMarkdown(text) {
|
|
160
|
+
const jsonMatch = text.match(/```json\s*\n([\s\S]*?)\n```/) ||
|
|
161
|
+
text.match(/```\s*\n([\s\S]*?)\n```/);
|
|
162
|
+
return jsonMatch ? jsonMatch[1] : text;
|
|
159
163
|
}
|
|
160
|
-
|
|
164
|
+
|
|
165
|
+
const result = JSON.parse(extractJsonFromMarkdown(output));
|
|
166
|
+
return result.groups.map(g => g.issue_ids.map(id => issues.find(i => i.id === id)));
|
|
161
167
|
}
|
|
162
168
|
|
|
163
|
-
const batches =
|
|
164
|
-
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es) (
|
|
169
|
+
const batches = await groupBySimilarityGemini(issues);
|
|
170
|
+
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es) (Gemini semantic grouping, max 6 issues/agent)`);
|
|
165
171
|
|
|
166
172
|
TodoWrite({
|
|
167
173
|
todos: batches.map((_, i) => ({
|
|
@@ -177,6 +183,7 @@ TodoWrite({
|
|
|
177
183
|
```javascript
|
|
178
184
|
Bash(`mkdir -p .workflow/issues/solutions`);
|
|
179
185
|
const pendingSelections = []; // Collect multi-solution issues for user selection
|
|
186
|
+
const agentResults = []; // Collect all agent results for conflict aggregation
|
|
180
187
|
|
|
181
188
|
// Build prompts for all batches
|
|
182
189
|
const agentTasks = batches.map((batch, batchIndex) => {
|
|
@@ -200,13 +207,15 @@ ${issueList}
|
|
|
200
207
|
### Steps
|
|
201
208
|
1. Fetch: \`ccw issue status <id> --json\`
|
|
202
209
|
2. Load project context (project-tech.json + project-guidelines.json)
|
|
203
|
-
3. **If
|
|
210
|
+
3. **If extended_context exists**: Use extended_context (location, suggested_fix, notes) as planning hints
|
|
204
211
|
4. Explore (ACE) → Plan solution (respecting guidelines)
|
|
205
212
|
5. Register & bind: \`ccw issue bind <id> --solution <file>\`
|
|
206
213
|
|
|
207
214
|
### Generate Files
|
|
208
215
|
\`.workflow/issues/solutions/{issue-id}.jsonl\` - Solution with tasks (schema: cat .claude/workflows/cli-templates/schemas/solution-schema.json)
|
|
209
216
|
|
|
217
|
+
**Solution ID Format**: \`SOL-{issue-id}-{seq}\` (e.g., \`SOL-GH-123-1\`, \`SOL-ISS-20251229-1\`)
|
|
218
|
+
|
|
210
219
|
### Binding Rules
|
|
211
220
|
- **Single solution**: Auto-bind via \`ccw issue bind <id> --solution <file>\`
|
|
212
221
|
- **Multiple solutions**: Register only, return for user selection
|
|
@@ -216,7 +225,13 @@ ${issueList}
|
|
|
216
225
|
{
|
|
217
226
|
"bound": [{ "issue_id": "...", "solution_id": "...", "task_count": N }],
|
|
218
227
|
"pending_selection": [{ "issue_id": "...", "solutions": [{ "id": "...", "description": "...", "task_count": N }] }],
|
|
219
|
-
"conflicts": [{
|
|
228
|
+
"conflicts": [{
|
|
229
|
+
"type": "file_conflict|api_conflict|data_conflict|dependency_conflict|architecture_conflict",
|
|
230
|
+
"severity": "high|medium|low",
|
|
231
|
+
"summary": "brief description",
|
|
232
|
+
"recommended_resolution": "auto-resolution for low/medium",
|
|
233
|
+
"resolution_options": [{ "strategy": "...", "rationale": "..." }]
|
|
234
|
+
}]
|
|
220
235
|
}
|
|
221
236
|
\`\`\`
|
|
222
237
|
`;
|
|
@@ -247,7 +262,18 @@ for (let i = 0; i < agentTasks.length; i += MAX_PARALLEL) {
|
|
|
247
262
|
// Collect results from this chunk
|
|
248
263
|
for (const { taskId, batchIndex } of taskIds) {
|
|
249
264
|
const result = TaskOutput(task_id=taskId, block=true);
|
|
250
|
-
|
|
265
|
+
|
|
266
|
+
// Extract JSON from potential markdown code blocks (agent may wrap in ```json...```)
|
|
267
|
+
const jsonText = extractJsonFromMarkdown(result);
|
|
268
|
+
let summary;
|
|
269
|
+
try {
|
|
270
|
+
summary = JSON.parse(jsonText);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
console.log(`⚠ Batch ${batchIndex + 1}: Failed to parse agent result, skipping`);
|
|
273
|
+
updateTodo(`Plan batch ${batchIndex + 1}`, 'completed');
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
agentResults.push(summary); // Store for Phase 3 conflict aggregation
|
|
251
277
|
|
|
252
278
|
for (const item of summary.bound || []) {
|
|
253
279
|
console.log(`✓ ${item.issue_id}: ${item.solution_id} (${item.task_count} tasks)`);
|
|
@@ -258,17 +284,66 @@ for (let i = 0; i < agentTasks.length; i += MAX_PARALLEL) {
|
|
|
258
284
|
pendingSelections.push(pending);
|
|
259
285
|
}
|
|
260
286
|
if (summary.conflicts?.length > 0) {
|
|
261
|
-
console.log(`⚠ Conflicts: ${summary.conflicts.
|
|
287
|
+
console.log(`⚠ Conflicts: ${summary.conflicts.length} detected (will resolve in Phase 3)`);
|
|
262
288
|
}
|
|
263
289
|
updateTodo(`Plan batch ${batchIndex + 1}`, 'completed');
|
|
264
290
|
}
|
|
265
291
|
}
|
|
266
292
|
```
|
|
267
293
|
|
|
268
|
-
### Phase 3:
|
|
294
|
+
### Phase 3: Conflict Resolution & Solution Selection
|
|
269
295
|
|
|
270
296
|
```javascript
|
|
271
|
-
//
|
|
297
|
+
// Helper: Extract selected solution ID from AskUserQuestion answer
|
|
298
|
+
function extractSelectedSolutionId(answer, issueId) {
|
|
299
|
+
// answer format: { [header]: selectedLabel } or { answers: { [question]: label } }
|
|
300
|
+
const key = Object.keys(answer).find(k => k.includes(issueId));
|
|
301
|
+
if (!key) return null;
|
|
302
|
+
const selected = answer[key];
|
|
303
|
+
// Label format: "SOL-xxx (N tasks)" - extract solution ID
|
|
304
|
+
const match = selected.match(/^(SOL-[^\s]+)/);
|
|
305
|
+
return match ? match[1] : null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Phase 3a: Aggregate and resolve conflicts from all agents
|
|
309
|
+
const allConflicts = [];
|
|
310
|
+
for (const result of agentResults) {
|
|
311
|
+
if (result.conflicts?.length > 0) {
|
|
312
|
+
allConflicts.push(...result.conflicts);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (allConflicts.length > 0) {
|
|
317
|
+
console.log(`\n## Resolving ${allConflicts.length} conflict(s) detected by agents\n`);
|
|
318
|
+
|
|
319
|
+
// ALWAYS confirm high-severity conflicts (per user preference)
|
|
320
|
+
const highSeverity = allConflicts.filter(c => c.severity === 'high');
|
|
321
|
+
const lowMedium = allConflicts.filter(c => c.severity !== 'high');
|
|
322
|
+
|
|
323
|
+
// Auto-resolve low/medium severity
|
|
324
|
+
for (const conflict of lowMedium) {
|
|
325
|
+
console.log(` Auto-resolved: ${conflict.summary} → ${conflict.recommended_resolution}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ALWAYS require user confirmation for high severity
|
|
329
|
+
if (highSeverity.length > 0) {
|
|
330
|
+
const conflictAnswer = AskUserQuestion({
|
|
331
|
+
questions: highSeverity.slice(0, 4).map(conflict => ({
|
|
332
|
+
question: `${conflict.type}: ${conflict.summary}. How to resolve?`,
|
|
333
|
+
header: conflict.type.replace('_conflict', ''),
|
|
334
|
+
multiSelect: false,
|
|
335
|
+
options: conflict.resolution_options.map(opt => ({
|
|
336
|
+
label: opt.strategy,
|
|
337
|
+
description: opt.rationale
|
|
338
|
+
}))
|
|
339
|
+
}))
|
|
340
|
+
});
|
|
341
|
+
// Apply user-selected resolutions
|
|
342
|
+
console.log('Applied user-selected conflict resolutions');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Phase 3b: Multi-Solution Selection (MANDATORY when pendingSelections > 0)
|
|
272
347
|
if (pendingSelections.length > 0) {
|
|
273
348
|
console.log(`\n## User Selection Required: ${pendingSelections.length} issue(s) have multiple solutions\n`);
|
|
274
349
|
|
|
@@ -89,7 +89,7 @@ Queue formation command using **issue-queue-agent** that analyzes all bound solu
|
|
|
89
89
|
{
|
|
90
90
|
"item_id": "S-1",
|
|
91
91
|
"issue_id": "ISS-20251227-003",
|
|
92
|
-
"solution_id": "SOL-20251227-003",
|
|
92
|
+
"solution_id": "SOL-ISS-20251227-003-1",
|
|
93
93
|
"status": "pending",
|
|
94
94
|
"execution_order": 1,
|
|
95
95
|
"execution_group": "P1",
|
|
@@ -102,7 +102,7 @@ Queue formation command using **issue-queue-agent** that analyzes all bound solu
|
|
|
102
102
|
{
|
|
103
103
|
"item_id": "S-2",
|
|
104
104
|
"issue_id": "ISS-20251227-001",
|
|
105
|
-
"solution_id": "SOL-20251227-001",
|
|
105
|
+
"solution_id": "SOL-ISS-20251227-001-1",
|
|
106
106
|
"status": "pending",
|
|
107
107
|
"execution_order": 2,
|
|
108
108
|
"execution_group": "P1",
|
|
@@ -115,7 +115,7 @@ Queue formation command using **issue-queue-agent** that analyzes all bound solu
|
|
|
115
115
|
{
|
|
116
116
|
"item_id": "S-3",
|
|
117
117
|
"issue_id": "ISS-20251227-002",
|
|
118
|
-
"solution_id": "SOL-20251227-002",
|
|
118
|
+
"solution_id": "SOL-ISS-20251227-002-1",
|
|
119
119
|
"status": "pending",
|
|
120
120
|
"execution_order": 3,
|
|
121
121
|
"execution_group": "S2",
|
|
@@ -332,102 +332,30 @@ const summary = JSON.parse(result);
|
|
|
332
332
|
### Phase 5: Validation & Status Update
|
|
333
333
|
|
|
334
334
|
```javascript
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const queuesDir = '.workflow/issues/queues';
|
|
338
|
-
const indexPath = `${queuesDir}/index.json`;
|
|
339
|
-
const queuePath = `${queuesDir}/${queueId}.json`;
|
|
340
|
-
|
|
341
|
-
// 1. Validate index.json has active_queue_id
|
|
342
|
-
const indexContent = Bash(`cat "${indexPath}" 2>/dev/null || echo '{}'`);
|
|
343
|
-
const index = JSON.parse(indexContent);
|
|
344
|
-
|
|
345
|
-
if (index.active_queue_id !== queueId) {
|
|
346
|
-
console.log(`⚠ Fixing: index.json active_queue_id not set to ${queueId}`);
|
|
347
|
-
index.active_queue_id = queueId;
|
|
348
|
-
// Ensure queue entry exists in index
|
|
349
|
-
if (!index.queues) index.queues = [];
|
|
350
|
-
const existing = index.queues.find(q => q.id === queueId);
|
|
351
|
-
if (!existing) {
|
|
352
|
-
index.queues.unshift({
|
|
353
|
-
id: queueId,
|
|
354
|
-
status: 'active',
|
|
355
|
-
issue_ids: summary.issues_queued,
|
|
356
|
-
total_solutions: summary.total_solutions,
|
|
357
|
-
completed_solutions: 0,
|
|
358
|
-
created_at: new Date().toISOString()
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
Bash(`echo '${JSON.stringify(index, null, 2)}' > "${indexPath}"`);
|
|
362
|
-
console.log(`✓ Fixed: index.json updated with active_queue_id: ${queueId}`);
|
|
363
|
-
}
|
|
335
|
+
const queuePath = `.workflow/issues/queues/${queueId}.json`;
|
|
364
336
|
|
|
365
|
-
//
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (!queue.solutions || queue.solutions.length === 0) {
|
|
370
|
-
console.error(`✗ ERROR: Queue file ${queuePath} has no solutions array`);
|
|
371
|
-
console.error(' Agent did not generate queue correctly. Aborting.');
|
|
337
|
+
// 1. Validate queue has solutions
|
|
338
|
+
const solCount = Bash(`jq ".solutions | length" "${queuePath}"`).trim();
|
|
339
|
+
if (!solCount || solCount === '0') {
|
|
340
|
+
console.error(`✗ Queue has no solutions. Aborting.`);
|
|
372
341
|
return;
|
|
373
342
|
}
|
|
374
343
|
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (sol.status !== 'pending' && sol.status !== 'executing' && sol.status !== 'completed') {
|
|
379
|
-
console.log(`⚠ Fixing: ${sol.item_id} status "${sol.status}" → "pending"`);
|
|
380
|
-
sol.status = 'pending';
|
|
381
|
-
statusFixed++;
|
|
382
|
-
}
|
|
344
|
+
// 2. Update issue statuses
|
|
345
|
+
for (const id of summary.issues_queued) {
|
|
346
|
+
Bash(`ccw issue update ${id} --status queued`);
|
|
383
347
|
}
|
|
384
348
|
|
|
385
|
-
//
|
|
386
|
-
const entryPoints = queue.solutions.filter(s =>
|
|
387
|
-
s.status === 'pending' && (!s.depends_on || s.depends_on.length === 0)
|
|
388
|
-
);
|
|
389
|
-
|
|
390
|
-
if (entryPoints.length === 0) {
|
|
391
|
-
console.error(`✗ ERROR: No entry points found (all items have dependencies)`);
|
|
392
|
-
console.error(' This will cause "ccw issue next" to return empty.');
|
|
393
|
-
console.error(' Check depends_on fields for circular dependencies.');
|
|
394
|
-
// Try to fix by clearing first item's dependencies
|
|
395
|
-
if (queue.solutions.length > 0) {
|
|
396
|
-
console.log(`⚠ Fixing: Clearing depends_on for first item ${queue.solutions[0].item_id}`);
|
|
397
|
-
queue.solutions[0].depends_on = [];
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Write back fixed queue if any changes made
|
|
402
|
-
if (statusFixed > 0 || entryPoints.length === 0) {
|
|
403
|
-
Bash(`echo '${JSON.stringify(queue, null, 2)}' > "${queuePath}"`);
|
|
404
|
-
console.log(`✓ Queue file updated with ${statusFixed} status fixes`);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// ============ OUTPUT SUMMARY ============
|
|
408
|
-
|
|
349
|
+
// 3. Summary
|
|
409
350
|
console.log(`
|
|
410
|
-
## Queue
|
|
351
|
+
## Queue: ${summary.queue_id}
|
|
411
352
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
**Groups**: ${summary.execution_groups.map(g => `${g.id}(${g.count})`).join(', ')}
|
|
416
|
-
**Conflicts Resolved**: ${summary.conflicts_resolved}
|
|
417
|
-
**Entry Points**: ${entryPoints.length} (items ready for immediate execution)
|
|
353
|
+
- Solutions: ${summary.total_solutions}
|
|
354
|
+
- Tasks: ${summary.total_tasks}
|
|
355
|
+
- Issues: ${summary.issues_queued.join(', ')}
|
|
418
356
|
|
|
419
|
-
Next:
|
|
357
|
+
Next: /issue:execute
|
|
420
358
|
`);
|
|
421
|
-
|
|
422
|
-
// Update issue statuses via CLI
|
|
423
|
-
for (const issueId of summary.issues_queued) {
|
|
424
|
-
Bash(`ccw issue update ${issueId} --status queued`);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Final verification
|
|
428
|
-
const verifyResult = Bash(`ccw issue queue dag 2>/dev/null | head -20`);
|
|
429
|
-
console.log('\n### Verification (DAG Preview):');
|
|
430
|
-
console.log(verifyResult);
|
|
431
359
|
```
|
|
432
360
|
|
|
433
361
|
## Error Handling
|