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:
catlog22
2025-12-29 20:24:31 +08:00
parent bd11a538a7
commit 7d71f603fe
2 changed files with 81 additions and 5 deletions

View File

@@ -176,13 +176,22 @@ ${issueList}
4. Plan solution with tasks (see issue-plan-agent.md for details)
5. Write solutions to JSONL, bind if single solution
### Generate Files
\`.workflow/issues/solutions/{issue-id}.jsonl\` - Solution with tasks (schema: cat .claude/workflows/cli-templates/schemas/solution-schema.json)
### Solution Creation (via CLI endpoint)
```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
- **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
### Return Summary

View File

@@ -333,7 +333,9 @@ function readSolutions(issueId: string): Solution[] {
function writeSolutions(issueId: string, solutions: Solution[]): void {
const dir = join(getIssuesDir(), 'solutions');
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 {
@@ -363,6 +365,40 @@ function generateSolutionId(issueId: string, existingSolutions: Solution[] = [])
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) ============
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)
*/
@@ -1639,6 +1703,9 @@ export async function issueCommand(
case 'create':
await createAction(options);
break;
case 'solution':
await solutionAction(argsArray[0], options);
break;
case 'init':
await initAction(argsArray[0], options);
break;