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.
@@ -128,40 +128,46 @@ if (flags.allPending) {
128
128
  }
129
129
  }
130
130
 
131
- // Intelligent grouping by similarity (tags title keywords)
132
- function groupBySimilarity(issues, maxSize) {
133
- const batches = [];
134
- const used = new Set();
135
-
136
- for (const issue of issues) {
137
- if (used.has(issue.id)) continue;
138
-
139
- const batch = [issue];
140
- used.add(issue.id);
141
- const issueTags = new Set(issue.tags);
142
- const issueWords = new Set(issue.title.toLowerCase().split(/\s+/));
143
-
144
- // Find similar issues
145
- for (const other of issues) {
146
- if (used.has(other.id) || batch.length >= maxSize) continue;
147
-
148
- // Similarity: shared tags or shared title keywords
149
- const sharedTags = other.tags.filter(t => issueTags.has(t)).length;
150
- const otherWords = other.title.toLowerCase().split(/\s+/);
151
- const sharedWords = otherWords.filter(w => issueWords.has(w) && w.length > 3).length;
152
-
153
- if (sharedTags > 0 || sharedWords >= 2) {
154
- batch.push(other);
155
- used.add(other.id);
156
- }
157
- }
158
- batches.push(batch);
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
- return batches;
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 = groupBySimilarity(issues, batchSize);
164
- console.log(`Processing ${issues.length} issues in ${batches.length} batch(es) (grouped by similarity)`);
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 source=discovery**: Use discovery_context (file, line, snippet, suggested_fix) as planning hints
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": [{ "file": "...", "issues": [...] }]
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
- const summary = JSON.parse(result);
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.map(c => c.file).join(', ')}`);
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: Multi-Solution Selection (MANDATORY when pendingSelections > 0)
294
+ ### Phase 3: Conflict Resolution & Solution Selection
269
295
 
270
296
  ```javascript
271
- // MUST trigger user selection when multiple solutions exist
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
- // ============ VALIDATION: Prevent "next returns empty" issues ============
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
- // 2. Validate queue file exists and has correct structure
366
- const queueContent = Bash(`cat "${queuePath}" 2>/dev/null || echo '{}'`);
367
- const queue = JSON.parse(queueContent);
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
- // 3. Validate all solutions have status: "pending" (not "queued" or other)
376
- let statusFixed = 0;
377
- for (const sol of queue.solutions) {
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
- // 4. Validate at least one item has no dependencies (DAG entry point)
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 Formed: ${summary.queue_id}
351
+ ## Queue: ${summary.queue_id}
411
352
 
412
- **Solutions**: ${summary.total_solutions}
413
- **Tasks**: ${summary.total_tasks}
414
- **Issues**: ${summary.issues_queued.join(', ')}
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: \`/issue:execute\` or \`ccw issue 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