mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
test(cli-executor): enhance solution registration and binding process
This commit is contained in:
@@ -182,17 +182,37 @@ function decomposeTasks(issue, exploration) {
|
|||||||
- Task validation (all 5 phases present)
|
- Task validation (all 5 phases present)
|
||||||
- Conflict detection (cross-issue file modifications)
|
- Conflict detection (cross-issue file modifications)
|
||||||
|
|
||||||
**Solution Registration** (CRITICAL: check solution count first):
|
**Solution Registration** (TWO-STEP: Write first, then bind):
|
||||||
```javascript
|
```javascript
|
||||||
for (const issue of issues) {
|
for (const issue of issues) {
|
||||||
const solutions = generatedSolutions[issue.id];
|
const solutions = generatedSolutions[issue.id];
|
||||||
|
const solPath = `.workflow/issues/solutions/${issue.id}.jsonl`;
|
||||||
|
|
||||||
|
// Step 1: ALWAYS write ALL solutions to JSONL (append mode)
|
||||||
|
// Each solution on a new line, preserving existing solutions
|
||||||
|
for (const sol of solutions) {
|
||||||
|
// Ensure Solution ID format: SOL-{issue-id}-{seq}
|
||||||
|
const solutionJson = JSON.stringify({
|
||||||
|
id: sol.id, // e.g., SOL-GH-123-1, SOL-GH-123-2
|
||||||
|
description: sol.description,
|
||||||
|
approach: sol.approach,
|
||||||
|
tasks: sol.tasks,
|
||||||
|
exploration_context: sol.exploration_context,
|
||||||
|
analysis: sol.analysis,
|
||||||
|
score: sol.score,
|
||||||
|
is_bound: false,
|
||||||
|
created_at: new Date().toISOString()
|
||||||
|
});
|
||||||
|
Bash(`echo '${solutionJson}' >> "${solPath}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Bind decision based on solution count
|
||||||
if (solutions.length === 1) {
|
if (solutions.length === 1) {
|
||||||
// Single solution → auto-bind
|
// Single solution → auto-bind by ID (NOT --solution flag)
|
||||||
Bash(`ccw issue bind ${issue.id} --solution ${solutions[0].file}`);
|
Bash(`ccw issue bind ${issue.id} ${solutions[0].id}`);
|
||||||
bound.push({ issue_id: issue.id, solution_id: solutions[0].id, task_count: solutions[0].tasks.length });
|
bound.push({ issue_id: issue.id, solution_id: solutions[0].id, task_count: solutions[0].tasks.length });
|
||||||
} else {
|
} else {
|
||||||
// Multiple solutions → DO NOT BIND, return for user selection
|
// Multiple solutions → already written, return for user selection
|
||||||
pending_selection.push({
|
pending_selection.push({
|
||||||
issue_id: issue.id,
|
issue_id: issue.id,
|
||||||
solutions: solutions.map(s => ({ id: s.id, description: s.description, task_count: s.tasks.length }))
|
solutions: solutions.map(s => ({ id: s.id, description: s.description, task_count: s.tasks.length }))
|
||||||
@@ -332,8 +352,9 @@ Each line is a solution JSON containing tasks. Schema: `cat .claude/workflows/cl
|
|||||||
4. Quantify acceptance.criteria with testable conditions
|
4. Quantify acceptance.criteria with testable conditions
|
||||||
5. Validate DAG before output
|
5. Validate DAG before output
|
||||||
6. Evaluate each solution with `analysis` and `score`
|
6. Evaluate each solution with `analysis` and `score`
|
||||||
7. Single solution → auto-bind; Multiple → return `pending_selection`
|
7. **TWO-STEP registration**: Write JSONL first, then bind (see Phase 4)
|
||||||
8. For HIGH complexity: generate 2-3 candidate solutions
|
8. For HIGH complexity: generate 2-3 candidate solutions
|
||||||
|
9. **Solution ID format**: `SOL-{issue-id}-{seq}` (e.g., `SOL-GH-123-1`, `SOL-GH-123-2`)
|
||||||
|
|
||||||
**NEVER**:
|
**NEVER**:
|
||||||
1. Execute implementation (return plan only)
|
1. Execute implementation (return plan only)
|
||||||
@@ -341,8 +362,9 @@ Each line is a solution JSON containing tasks. Schema: `cat .claude/workflows/cl
|
|||||||
3. Create circular dependencies
|
3. Create circular dependencies
|
||||||
4. Generate more than 10 tasks per issue
|
4. Generate more than 10 tasks per issue
|
||||||
5. **Bind when multiple solutions exist** - MUST check `solutions.length === 1` before calling `ccw issue bind`
|
5. **Bind when multiple solutions exist** - MUST check `solutions.length === 1` before calling `ccw issue bind`
|
||||||
|
6. **Skip JSONL write** - ALL solutions must be written to disk before returning
|
||||||
|
|
||||||
**OUTPUT**:
|
**OUTPUT** (Two-Step):
|
||||||
1. Register solutions via `ccw issue bind <id> --solution <file>`
|
1. **Step 1**: Write ALL solutions to `.workflow/issues/solutions/{issue-id}.jsonl` (one JSON per line)
|
||||||
2. Return JSON with `bound`, `pending_selection`, `conflicts`
|
2. **Step 2**: Single solution → `ccw issue bind <id> <solution-id>`; Multiple → return only
|
||||||
3. Solutions written to `.workflow/issues/solutions/{issue-id}.jsonl`
|
3. Return JSON with `bound`, `pending_selection`, `conflicts`
|
||||||
|
|||||||
@@ -359,10 +359,27 @@ if (pendingSelections.length > 0) {
|
|||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bind user-selected solutions
|
// Bind user-selected solutions (with file validation)
|
||||||
for (const { issue_id } of pendingSelections) {
|
for (const { issue_id, solutions } of pendingSelections) {
|
||||||
const selectedId = extractSelectedSolutionId(answer, issue_id);
|
const selectedId = extractSelectedSolutionId(answer, issue_id);
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
|
// Verify solution file exists and contains the selected ID
|
||||||
|
const solPath = `.workflow/issues/solutions/${issue_id}.jsonl`;
|
||||||
|
const fileExists = Bash(`test -f "${solPath}" && echo "yes" || echo "no"`).trim() === 'yes';
|
||||||
|
|
||||||
|
if (!fileExists) {
|
||||||
|
console.log(`⚠ ${issue_id}: Solution file missing, attempting recovery...`);
|
||||||
|
// Recovery: write solution from pending_selection payload
|
||||||
|
const selectedSol = solutions.find(s => s.id === selectedId);
|
||||||
|
if (selectedSol) {
|
||||||
|
Bash(`mkdir -p .workflow/issues/solutions`);
|
||||||
|
const solJson = JSON.stringify({ id: selectedId, ...selectedSol, is_bound: false, created_at: new Date().toISOString() });
|
||||||
|
Bash(`echo '${solJson}' >> "${solPath}"`);
|
||||||
|
console.log(` Recovered ${selectedId} to ${solPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now bind
|
||||||
Bash(`ccw issue bind ${issue_id} ${selectedId}`);
|
Bash(`ccw issue bind ${issue_id} ${selectedId}`);
|
||||||
console.log(`✓ ${issue_id}: ${selectedId} bound`);
|
console.log(`✓ ${issue_id}: ${selectedId} bound`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,6 +271,11 @@ function getBoundSolution(issueId: string): Solution | undefined {
|
|||||||
return readSolutions(issueId).find(s => s.is_bound);
|
return readSolutions(issueId).find(s => s.is_bound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate fallback solution ID (timestamp-based).
|
||||||
|
* Note: Prefer agent-generated IDs in format `SOL-{issue-id}-{seq}` (e.g., SOL-GH-123-1).
|
||||||
|
* This function is only used when no ID is provided via CLI or file content.
|
||||||
|
*/
|
||||||
function generateSolutionId(): string {
|
function generateSolutionId(): string {
|
||||||
const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
||||||
return `SOL-${ts}`;
|
return `SOL-${ts}`;
|
||||||
@@ -802,8 +807,10 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
|
|||||||
try {
|
try {
|
||||||
const content = readFileSync(options.solution, 'utf-8');
|
const content = readFileSync(options.solution, 'utf-8');
|
||||||
const data = JSON.parse(content);
|
const data = JSON.parse(content);
|
||||||
|
// Priority: CLI arg > file content ID > generate new
|
||||||
|
// This ensures agent-generated IDs (SOL-{issue-id}-{seq}) are preserved
|
||||||
const newSol: Solution = {
|
const newSol: Solution = {
|
||||||
id: solutionId || generateSolutionId(),
|
id: solutionId || data.id || generateSolutionId(),
|
||||||
description: data.description || data.approach_name || 'Imported solution',
|
description: data.description || data.approach_name || 'Imported solution',
|
||||||
tasks: data.tasks || [],
|
tasks: data.tasks || [],
|
||||||
exploration_context: data.exploration_context,
|
exploration_context: data.exploration_context,
|
||||||
|
|||||||
Reference in New Issue
Block a user