mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: Enhance issue management with batch processing and solution querying
- Updated issue loading process to create batches based on size (max 3 per batch). - Removed semantic grouping in favor of simple size-based batching. - Introduced a new command to batch query solutions for multiple issues. - Improved solution loading to fetch all planned issues with bound solutions in a single call. - Added detailed handling for multi-solution issues, including user selection for binding. - Created a new workflow command for multi-agent development with documented progress and incremental iteration support. - Added .gitignore for ace-tool directory to prevent unnecessary files from being tracked.
This commit is contained in:
@@ -59,7 +59,7 @@ Phase 1: Issue Loading
|
|||||||
├─ Parse input (single, comma-separated, or --all-pending)
|
├─ Parse input (single, comma-separated, or --all-pending)
|
||||||
├─ Fetch issue metadata (ID, title, tags)
|
├─ Fetch issue metadata (ID, title, tags)
|
||||||
├─ Validate issues exist (create if needed)
|
├─ Validate issues exist (create if needed)
|
||||||
└─ Group by similarity (shared tags or title keywords, max 3 per batch)
|
└─ Create batches by size (max 3 per batch by default)
|
||||||
|
|
||||||
Phase 2: Unified Explore + Plan (issue-plan-agent)
|
Phase 2: Unified Explore + Plan (issue-plan-agent)
|
||||||
├─ Launch issue-plan-agent per batch
|
├─ Launch issue-plan-agent per batch
|
||||||
@@ -119,46 +119,17 @@ if (useAllPending) {
|
|||||||
}
|
}
|
||||||
// Note: Agent fetches full issue content via `ccw issue status <id> --json`
|
// Note: Agent fetches full issue content via `ccw issue status <id> --json`
|
||||||
|
|
||||||
// Semantic grouping via Gemini CLI (max 4 issues per group)
|
// Simple size-based batching (max batchSize issues per group)
|
||||||
async function groupBySimilarityGemini(issues) {
|
function createBatches(issues, batchSize) {
|
||||||
const issueSummaries = issues.map(i => ({
|
const batches = [];
|
||||||
id: i.id, title: i.title, tags: i.tags
|
for (let i = 0; i < issues.length; i += batchSize) {
|
||||||
}));
|
batches.push(issues.slice(i, i + batchSize));
|
||||||
|
|
||||||
const prompt = `
|
|
||||||
PURPOSE: Group similar issues by semantic similarity for batch processing; maximize within-group coherence; max 4 issues per group
|
|
||||||
TASK: • Analyze issue titles/tags semantically • Identify functional/architectural clusters • Assign each issue to one group
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: Issue metadata only
|
|
||||||
EXPECTED: JSON with groups array, each containing max 4 issue_ids, theme, rationale
|
|
||||||
CONSTRAINTS: Each issue in exactly one group | Max 4 issues per group | Balance group sizes
|
|
||||||
|
|
||||||
INPUT:
|
|
||||||
${JSON.stringify(issueSummaries, null, 2)}
|
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
|
||||||
{"groups":[{"group_id":1,"theme":"...","issue_ids":["..."],"rationale":"..."}],"ungrouped":[]}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const taskId = Bash({
|
|
||||||
command: `ccw cli -p "${prompt}" --tool gemini --mode analysis`,
|
|
||||||
run_in_background: true, timeout: 600000
|
|
||||||
});
|
|
||||||
const output = TaskOutput({ task_id: taskId, block: true });
|
|
||||||
|
|
||||||
// Extract JSON from potential markdown code blocks
|
|
||||||
function extractJsonFromMarkdown(text) {
|
|
||||||
const jsonMatch = text.match(/```json\s*\n([\s\S]*?)\n```/) ||
|
|
||||||
text.match(/```\s*\n([\s\S]*?)\n```/);
|
|
||||||
return jsonMatch ? jsonMatch[1] : text;
|
|
||||||
}
|
}
|
||||||
|
return batches;
|
||||||
const result = JSON.parse(extractJsonFromMarkdown(output));
|
|
||||||
return result.groups.map(g => g.issue_ids.map(id => issues.find(i => i.id === id)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const batches = await groupBySimilarityGemini(issues);
|
const batches = createBatches(issues, batchSize);
|
||||||
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es) (max 4 issues/agent)`);
|
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es) (max ${batchSize} issues/agent)`);
|
||||||
|
|
||||||
TodoWrite({
|
TodoWrite({
|
||||||
todos: batches.map((_, i) => ({
|
todos: batches.map((_, i) => ({
|
||||||
@@ -207,7 +178,9 @@ ${issueList}
|
|||||||
- Add explicit verification steps to prevent same failure mode
|
- Add explicit verification steps to prevent same failure mode
|
||||||
6. **If github_url exists**: Add final task to comment on GitHub issue
|
6. **If github_url exists**: Add final task to comment on GitHub issue
|
||||||
7. Write solution to: .workflow/issues/solutions/{issue-id}.jsonl
|
7. Write solution to: .workflow/issues/solutions/{issue-id}.jsonl
|
||||||
8. Single solution → auto-bind; Multiple → return for selection
|
8. **CRITICAL - Binding Decision**:
|
||||||
|
- Single solution → **MUST execute**: ccw issue bind <issue-id> <solution-id>
|
||||||
|
- Multiple solutions → Return pending_selection only (no bind)
|
||||||
|
|
||||||
### Failure-Aware Planning Rules
|
### Failure-Aware Planning Rules
|
||||||
- **Extract failure patterns**: Parse issue.feedback where type='failure' and stage='execute'
|
- **Extract failure patterns**: Parse issue.feedback where type='failure' and stage='execute'
|
||||||
@@ -265,35 +238,55 @@ for (let i = 0; i < agentTasks.length; i += MAX_PARALLEL) {
|
|||||||
}
|
}
|
||||||
agentResults.push(summary); // Store for Phase 3 conflict aggregation
|
agentResults.push(summary); // Store for Phase 3 conflict aggregation
|
||||||
|
|
||||||
|
// Verify binding for bound issues (agent should have executed bind)
|
||||||
for (const item of summary.bound || []) {
|
for (const item of summary.bound || []) {
|
||||||
console.log(`✓ ${item.issue_id}: ${item.solution_id} (${item.task_count} tasks)`);
|
const status = JSON.parse(Bash(`ccw issue status ${item.issue_id} --json`).trim());
|
||||||
|
if (status.bound_solution_id === item.solution_id) {
|
||||||
|
console.log(`✓ ${item.issue_id}: ${item.solution_id} (${item.task_count} tasks)`);
|
||||||
|
} else {
|
||||||
|
// Fallback: agent failed to bind, execute here
|
||||||
|
Bash(`ccw issue bind ${item.issue_id} ${item.solution_id}`);
|
||||||
|
console.log(`✓ ${item.issue_id}: ${item.solution_id} (${item.task_count} tasks) [recovered]`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Collect and notify pending selections
|
// Collect pending selections for Phase 3
|
||||||
for (const pending of summary.pending_selection || []) {
|
for (const pending of summary.pending_selection || []) {
|
||||||
console.log(`⏳ ${pending.issue_id}: ${pending.solutions.length} solutions → awaiting selection`);
|
|
||||||
pendingSelections.push(pending);
|
pendingSelections.push(pending);
|
||||||
}
|
}
|
||||||
if (summary.conflicts?.length > 0) {
|
|
||||||
console.log(`⚠ Conflicts: ${summary.conflicts.length} detected (will resolve in Phase 3)`);
|
|
||||||
}
|
|
||||||
updateTodo(`Plan batch ${batchIndex + 1}`, 'completed');
|
updateTodo(`Plan batch ${batchIndex + 1}`, 'completed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 3: Conflict Resolution & Solution Selection
|
### Phase 3: Solution Selection (if pending)
|
||||||
|
|
||||||
**Conflict Handling:**
|
```javascript
|
||||||
- Collect `conflicts` from all agent results
|
// Handle multi-solution issues
|
||||||
- Low/Medium severity → auto-resolve with `recommended_resolution`
|
for (const pending of pendingSelections) {
|
||||||
- High severity → use `AskUserQuestion` to let user choose resolution
|
if (pending.solutions.length === 0) continue;
|
||||||
|
|
||||||
**Multi-Solution Selection:**
|
const options = pending.solutions.slice(0, 4).map(sol => ({
|
||||||
- If `pending_selection` contains issues with multiple solutions:
|
label: `${sol.id} (${sol.task_count} tasks)`,
|
||||||
- Use `AskUserQuestion` to present options (solution ID + task count + description)
|
description: sol.description || sol.approach || 'No description'
|
||||||
- Extract selected solution ID from user response
|
}));
|
||||||
- Verify solution file exists, recover from payload if missing
|
|
||||||
- Bind selected solution via `ccw issue bind <issue-id> <solution-id>`
|
const answer = AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Issue ${pending.issue_id}: which solution to bind?`,
|
||||||
|
header: pending.issue_id,
|
||||||
|
options: options,
|
||||||
|
multiSelect: false
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected = answer[Object.keys(answer)[0]];
|
||||||
|
if (!selected || selected === 'Other') continue;
|
||||||
|
|
||||||
|
const solId = selected.split(' ')[0];
|
||||||
|
Bash(`ccw issue bind ${pending.issue_id} ${solId}`);
|
||||||
|
console.log(`✓ ${pending.issue_id}: ${solId} bound`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Phase 4: Summary
|
### Phase 4: Summary
|
||||||
|
|
||||||
|
|||||||
@@ -28,12 +28,13 @@ Queue formation command using **issue-queue-agent** that analyzes all bound solu
|
|||||||
| Operation | Correct | Incorrect |
|
| Operation | Correct | Incorrect |
|
||||||
|-----------|---------|-----------|
|
|-----------|---------|-----------|
|
||||||
| List issues (brief) | `ccw issue list --status planned --brief` | `Read('issues.jsonl')` |
|
| List issues (brief) | `ccw issue list --status planned --brief` | `Read('issues.jsonl')` |
|
||||||
|
| **Batch solutions (NEW)** | `ccw issue solutions --status planned --brief` | Loop `ccw issue solution <id>` |
|
||||||
| List queue (brief) | `ccw issue queue --brief` | `Read('queues/*.json')` |
|
| List queue (brief) | `ccw issue queue --brief` | `Read('queues/*.json')` |
|
||||||
| Read issue details | `ccw issue status <id> --json` | `Read('issues.jsonl')` |
|
| Read issue details | `ccw issue status <id> --json` | `Read('issues.jsonl')` |
|
||||||
| Get next item | `ccw issue next --json` | `Read('queues/*.json')` |
|
| Get next item | `ccw issue next --json` | `Read('queues/*.json')` |
|
||||||
| Update status | `ccw issue update <id> --status ...` | Direct file edit |
|
| Update status | `ccw issue update <id> --status ...` | Direct file edit |
|
||||||
| Sync from queue | `ccw issue update --from-queue` | Direct file edit |
|
| Sync from queue | `ccw issue update --from-queue` | Direct file edit |
|
||||||
| **Read solution (brief)** | `ccw issue solution <id> --brief` | `Read('solutions/*.jsonl')` |
|
| Read solution (single) | `ccw issue solution <id> --brief` | `Read('solutions/*.jsonl')` |
|
||||||
|
|
||||||
**Output Options**:
|
**Output Options**:
|
||||||
- `--brief`: JSON with minimal fields (id, status, counts)
|
- `--brief`: JSON with minimal fields (id, status, counts)
|
||||||
@@ -131,24 +132,23 @@ Phase 7: Active Queue Check & Decision (REQUIRED)
|
|||||||
### Phase 1: Solution Loading & Distribution
|
### Phase 1: Solution Loading & Distribution
|
||||||
|
|
||||||
**Data Loading:**
|
**Data Loading:**
|
||||||
- Use `ccw issue list --status planned --brief` to get planned issues with `bound_solution_id`
|
- Use `ccw issue solutions --status planned --brief` to get all planned issues with solutions in **one call**
|
||||||
- If no planned issues found → display message, suggest `/issue:plan`
|
- Returns: Array of `{ issue_id, solution_id, is_bound, task_count, files_touched[], priority }`
|
||||||
|
- If no bound solutions found → display message, suggest `/issue:plan`
|
||||||
**Solution Brief Loading** (for each planned issue):
|
|
||||||
```bash
|
|
||||||
ccw issue solution <issue-id> --brief
|
|
||||||
# Returns: [{ solution_id, is_bound, task_count, files_touched[] }]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Build Solution Objects:**
|
**Build Solution Objects:**
|
||||||
```json
|
```javascript
|
||||||
{
|
// Single CLI call replaces N individual queries
|
||||||
"issue_id": "ISS-xxx",
|
const result = Bash(`ccw issue solutions --status planned --brief`).trim();
|
||||||
"solution_id": "SOL-ISS-xxx-1",
|
const solutions = result ? JSON.parse(result) : [];
|
||||||
"task_count": 3,
|
|
||||||
"files_touched": ["src/auth.ts", "src/utils.ts"],
|
if (solutions.length === 0) {
|
||||||
"priority": "medium"
|
console.log('No bound solutions found. Run /issue:plan first.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// solutions already in correct format:
|
||||||
|
// { issue_id, solution_id, is_bound, task_count, files_touched[], priority }
|
||||||
```
|
```
|
||||||
|
|
||||||
**Multi-Queue Distribution** (if `--queues > 1`):
|
**Multi-Queue Distribution** (if `--queues > 1`):
|
||||||
|
|||||||
1040
.claude/commands/workflow/develop-with-file.md
Normal file
1040
.claude/commands/workflow/develop-with-file.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1220,6 +1220,81 @@ async function solutionAction(issueId: string | undefined, options: IssueOptions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* solutions - Batch query solutions for multiple issues
|
||||||
|
* Usage: ccw issue solutions --status planned --brief
|
||||||
|
*/
|
||||||
|
async function solutionsAction(options: IssueOptions): Promise<void> {
|
||||||
|
// Get issues filtered by status
|
||||||
|
const issues = readIssues();
|
||||||
|
let targetIssues = issues;
|
||||||
|
|
||||||
|
if (options.status) {
|
||||||
|
const statuses = options.status.split(',').map((s: string) => s.trim());
|
||||||
|
targetIssues = issues.filter((i: Issue) => statuses.includes(i.status));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to only issues with bound_solution_id
|
||||||
|
const boundIssues = targetIssues.filter((i: Issue) => i.bound_solution_id);
|
||||||
|
|
||||||
|
if (boundIssues.length === 0) {
|
||||||
|
if (options.json || options.brief) {
|
||||||
|
console.log('[]');
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow('No bound solutions found'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect solutions for all bound issues
|
||||||
|
const allSolutions: Array<{
|
||||||
|
issue_id: string;
|
||||||
|
solution_id: string;
|
||||||
|
is_bound: boolean;
|
||||||
|
task_count: number;
|
||||||
|
files_touched: string[];
|
||||||
|
priority?: number;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const issue of boundIssues) {
|
||||||
|
const solutions = readSolutions(issue.id);
|
||||||
|
const boundSolution = solutions.find(s => s.id === issue.bound_solution_id);
|
||||||
|
|
||||||
|
if (boundSolution) {
|
||||||
|
const filesTouched = new Set<string>();
|
||||||
|
for (const task of boundSolution.tasks) {
|
||||||
|
if (task.modification_points) {
|
||||||
|
for (const mp of task.modification_points) {
|
||||||
|
if (mp.file) filesTouched.add(mp.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allSolutions.push({
|
||||||
|
issue_id: issue.id,
|
||||||
|
solution_id: boundSolution.id,
|
||||||
|
is_bound: true,
|
||||||
|
task_count: boundSolution.tasks.length,
|
||||||
|
files_touched: Array.from(filesTouched),
|
||||||
|
priority: issue.priority
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brief mode: already minimal
|
||||||
|
if (options.brief || options.json) {
|
||||||
|
console.log(JSON.stringify(allSolutions, null, 2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-readable output
|
||||||
|
console.log(chalk.bold.cyan(`\nBound Solutions (${allSolutions.length}):\n`));
|
||||||
|
for (const sol of allSolutions) {
|
||||||
|
console.log(`${chalk.green('◉')} ${sol.issue_id} → ${sol.solution_id}`);
|
||||||
|
console.log(chalk.gray(` Tasks: ${sol.task_count}, Files: ${sol.files_touched.length}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init - Initialize a new issue (manual ID)
|
* init - Initialize a new issue (manual ID)
|
||||||
*/
|
*/
|
||||||
@@ -2832,6 +2907,9 @@ export async function issueCommand(
|
|||||||
case 'solution':
|
case 'solution':
|
||||||
await solutionAction(argsArray[0], options);
|
await solutionAction(argsArray[0], options);
|
||||||
break;
|
break;
|
||||||
|
case 'solutions':
|
||||||
|
await solutionsAction(options);
|
||||||
|
break;
|
||||||
case 'init':
|
case 'init':
|
||||||
await initAction(argsArray[0], options);
|
await initAction(argsArray[0], options);
|
||||||
break;
|
break;
|
||||||
|
|||||||
1
ccw/src/core/routes/.gitignore
vendored
Normal file
1
ccw/src/core/routes/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.ace-tool/
|
||||||
Reference in New Issue
Block a user