mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat(issue): add solution endpoint with auto-increment ID
- Add `ccw issue solution <id> --data '{...}'` for solution creation
- Add createSolution() with proper JSONL handling (trailing newline)
- Fix writeSolutions() to always add trailing newline
- Update plan.md to use CLI endpoint
Supports multiple solutions per issue with sequential IDs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -176,13 +176,22 @@ ${issueList}
|
|||||||
4. Plan solution with tasks (see issue-plan-agent.md for details)
|
4. Plan solution with tasks (see issue-plan-agent.md for details)
|
||||||
5. Write solutions to JSONL, bind if single solution
|
5. Write solutions to JSONL, bind if single solution
|
||||||
|
|
||||||
### Generate Files
|
### Solution Creation (via CLI endpoint)
|
||||||
\`.workflow/issues/solutions/{issue-id}.jsonl\` - Solution with tasks (schema: cat .claude/workflows/cli-templates/schemas/solution-schema.json)
|
```bash
|
||||||
|
ccw issue solution <issue-id> --data '{"description":"...", "approach":"...", "tasks":[...]}'
|
||||||
|
```
|
||||||
|
|
||||||
**Solution ID Format**: \`SOL-{issue-id}-{seq}\` (e.g., \`SOL-GH-123-1\`, \`SOL-ISS-20251229-1\`)
|
**CLI Endpoint Features:**
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| Auto-increment ID | `SOL-{issue-id}-{seq}` (e.g., `SOL-GH-123-1`) |
|
||||||
|
| Multi-solution | Appends to existing JSONL, supports multiple per issue |
|
||||||
|
| JSON output | Returns created solution with ID |
|
||||||
|
|
||||||
|
**Schema Reference:** `cat .claude/workflows/cli-templates/schemas/solution-schema.json`
|
||||||
|
|
||||||
### Binding Rules
|
### Binding Rules
|
||||||
- **Single solution**: Auto-bind via \`ccw issue bind <id> --solution <file>\`
|
- **Single solution**: Auto-bind via `ccw issue bind <issue-id> <solution-id>`
|
||||||
- **Multiple solutions**: Register only, return for user selection
|
- **Multiple solutions**: Register only, return for user selection
|
||||||
|
|
||||||
### Return Summary
|
### Return Summary
|
||||||
|
|||||||
@@ -333,7 +333,9 @@ function readSolutions(issueId: string): Solution[] {
|
|||||||
function writeSolutions(issueId: string, solutions: Solution[]): void {
|
function writeSolutions(issueId: string, solutions: Solution[]): void {
|
||||||
const dir = join(getIssuesDir(), 'solutions');
|
const dir = join(getIssuesDir(), 'solutions');
|
||||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||||
writeFileSync(getSolutionsPath(issueId), solutions.map(s => JSON.stringify(s)).join('\n'), 'utf-8');
|
// Always add trailing newline for proper JSONL format
|
||||||
|
const content = solutions.map(s => JSON.stringify(s)).join('\n');
|
||||||
|
writeFileSync(getSolutionsPath(issueId), content ? content + '\n' : '', 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
function findSolution(issueId: string, solutionId: string): Solution | undefined {
|
function findSolution(issueId: string, solutionId: string): Solution | undefined {
|
||||||
@@ -363,6 +365,40 @@ function generateSolutionId(issueId: string, existingSolutions: Solution[] = [])
|
|||||||
return `SOL-${issueId}-${maxSeq + 1}`;
|
return `SOL-${issueId}-${maxSeq + 1}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new solution with proper JSONL handling
|
||||||
|
* Auto-generates ID if not provided
|
||||||
|
*/
|
||||||
|
function createSolution(issueId: string, data: Partial<Solution>): Solution {
|
||||||
|
const issue = findIssue(issueId);
|
||||||
|
if (!issue) {
|
||||||
|
throw new Error(`Issue "${issueId}" not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const solutions = readSolutions(issueId);
|
||||||
|
const solutionId = data.id || generateSolutionId(issueId, solutions);
|
||||||
|
|
||||||
|
if (solutions.some(s => s.id === solutionId)) {
|
||||||
|
throw new Error(`Solution "${solutionId}" already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSolution: Solution = {
|
||||||
|
id: solutionId,
|
||||||
|
description: data.description || '',
|
||||||
|
approach: data.approach || '',
|
||||||
|
tasks: data.tasks || [],
|
||||||
|
exploration_context: data.exploration_context,
|
||||||
|
analysis: data.analysis,
|
||||||
|
score: data.score,
|
||||||
|
is_bound: false,
|
||||||
|
created_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
solutions.push(newSolution);
|
||||||
|
writeSolutions(issueId, solutions);
|
||||||
|
return newSolution;
|
||||||
|
}
|
||||||
|
|
||||||
// ============ Queue Management (Multi-Queue) ============
|
// ============ Queue Management (Multi-Queue) ============
|
||||||
|
|
||||||
function getQueuesDir(): string {
|
function getQueuesDir(): string {
|
||||||
@@ -542,6 +578,34 @@ async function createAction(options: IssueOptions): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* solution - Create solution from JSON data
|
||||||
|
* Usage: ccw issue solution <issue-id> --data '{"tasks":[...]}'
|
||||||
|
* Output: JSON with created solution (includes auto-generated ID)
|
||||||
|
*/
|
||||||
|
async function solutionAction(issueId: string | undefined, options: IssueOptions): Promise<void> {
|
||||||
|
if (!issueId) {
|
||||||
|
console.error(chalk.red('Issue ID required'));
|
||||||
|
console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.data) {
|
||||||
|
console.error(chalk.red('JSON data required'));
|
||||||
|
console.error(chalk.gray('Usage: ccw issue solution <issue-id> --data \'{"tasks":[...]}\''));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(options.data);
|
||||||
|
const solution = createSolution(issueId, data);
|
||||||
|
console.log(JSON.stringify(solution, null, 2));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(chalk.red((err as Error).message));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init - Initialize a new issue (manual ID)
|
* init - Initialize a new issue (manual ID)
|
||||||
*/
|
*/
|
||||||
@@ -1639,6 +1703,9 @@ export async function issueCommand(
|
|||||||
case 'create':
|
case 'create':
|
||||||
await createAction(options);
|
await createAction(options);
|
||||||
break;
|
break;
|
||||||
|
case 'solution':
|
||||||
|
await solutionAction(argsArray[0], options);
|
||||||
|
break;
|
||||||
case 'init':
|
case 'init':
|
||||||
await initAction(argsArray[0], options);
|
await initAction(argsArray[0], options);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user