refactor(issue): simplify Issue schema and remove lifecycle_requirements

Schema changes:
- Remove lifecycle_requirements (deferred to plan agent)
- Remove solution_count (computed dynamically from solutions/*.jsonl)
- Keep context as single source of truth (remove problem_statement)
- Add feedback[] for failure history + human clarifications
- Add source, source_url, labels, expected/actual_behavior

Fields removed (unused downstream):
- lifecycle_requirements.test_strategy
- lifecycle_requirements.regression_scope
- lifecycle_requirements.acceptance_type
- lifecycle_requirements.commit_strategy
- solution_count (denormalized)

Fields added:
- feedback[]: { type, stage, content, created_at }
- source, source_url, labels
- expected_behavior, actual_behavior, affected_components

🤖 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 19:25:58 +08:00
parent 5914b1c5fc
commit 88724a4df9
2 changed files with 84 additions and 62 deletions

View File

@@ -16,6 +16,41 @@ Clear Input (GitHub URL, structured text) → Direct creation
Unclear Input (vague description) → Minimal clarifying questions Unclear Input (vague description) → Minimal clarifying questions
``` ```
## Issue Structure (Simplified)
```typescript
interface Issue {
id: string; // GH-123 or ISS-YYYYMMDD-HHMMSS
title: string;
status: 'registered' | 'planned' | 'queued' | 'in_progress' | 'completed' | 'failed';
priority: number; // 1 (critical) to 5 (low)
context: string; // Problem description (single source of truth)
source: 'github' | 'text' | 'discovery';
source_url?: string;
labels?: string[];
// Optional structured fields
expected_behavior?: string;
actual_behavior?: string;
affected_components?: string[];
// Feedback history (failures + human clarifications)
feedback?: {
type: 'failure' | 'clarification' | 'rejection';
stage: string; // new/plan/execute
content: string;
created_at: string;
}[];
// Solution binding
bound_solution_id: string | null;
// Timestamps
created_at: string;
updated_at: string;
}
```
## Quick Reference ## Quick Reference
```bash ```bash
@@ -63,7 +98,7 @@ if (isGitHubUrl || isGitHubShort) {
source: 'github', source: 'github',
source_url: gh.url, source_url: gh.url,
labels: gh.labels.map(l => l.name), labels: gh.labels.map(l => l.name),
problem_statement: gh.body?.substring(0, 500) || gh.title, context: gh.body?.substring(0, 500) || gh.title,
...parseMarkdownBody(gh.body) ...parseMarkdownBody(gh.body)
}; };
} else { } else {
@@ -84,19 +119,17 @@ if (isGitHubUrl || isGitHubShort) {
// Note: Deep exploration happens in /issue:plan, this is just a quick hint // Note: Deep exploration happens in /issue:plan, this is just a quick hint
if (clarityScore >= 1 && clarityScore <= 2 && !issueData.affected_components?.length) { if (clarityScore >= 1 && clarityScore <= 2 && !issueData.affected_components?.length) {
const keywords = extractKeywords(issueData.problem_statement); const keywords = extractKeywords(issueData.context);
if (keywords.length >= 2) { // Need at least 2 keywords for meaningful search if (keywords.length >= 2) {
try { try {
const aceResult = mcp__ace-tool__search_context({ const aceResult = mcp__ace-tool__search_context({
project_root_path: process.cwd(), project_root_path: process.cwd(),
query: keywords.slice(0, 3).join(' ') // Quick search, max 3 keywords query: keywords.slice(0, 3).join(' ')
}); });
// Only take top 3 files as hints
issueData.affected_components = aceResult.files?.slice(0, 3) || []; issueData.affected_components = aceResult.files?.slice(0, 3) || [];
} catch { } catch {
// ACE failure is non-blocking, continue without hints // ACE failure is non-blocking
} }
} }
} }
@@ -107,16 +140,9 @@ if (clarityScore >= 1 && clarityScore <= 2 && !issueData.affected_components?.le
```javascript ```javascript
// ONLY ask questions if clarity is low // ONLY ask questions if clarity is low
if (clarityScore < 2) { if (clarityScore < 2) {
const missingFields = []; if (!issueData.title || issueData.title.length < 10 ||
!issueData.context || issueData.context.length < 20) {
if (!issueData.title || issueData.title.length < 10) {
missingFields.push('title');
}
if (!issueData.problem_statement || issueData.problem_statement.length < 20) {
missingFields.push('problem');
}
if (missingFields.length > 0) {
const answer = AskUserQuestion({ const answer = AskUserQuestion({
questions: [{ questions: [{
question: `Input unclear. What is the issue about?`, question: `Input unclear. What is the issue about?`,
@@ -131,36 +157,22 @@ if (clarityScore < 2) {
}] }]
}); });
// Use answer to enrich issue data // Save clarification as feedback
if (answer.customText) { if (answer.customText) {
issueData.problem_statement = answer.customText; issueData.context = answer.customText;
issueData.title = answer.customText.split('.')[0].substring(0, 60); issueData.title = answer.customText.split('.')[0].substring(0, 60);
issueData.feedback = [{
type: 'clarification',
stage: 'new',
content: answer.customText,
created_at: new Date().toISOString()
}];
} }
} }
} }
``` ```
### Phase 5: Auto-Detect Lifecycle (No Questions) ### Phase 5: Create Issue
```javascript
// Smart defaults based on affected files - NO USER QUESTIONS
function detectLifecycle(components) {
const hasTests = components.some(c => c.includes('test') || c.includes('spec'));
const hasApi = components.some(c => c.includes('api') || c.includes('route'));
const hasUi = components.some(c => c.includes('component') || c.match(/\.(tsx|jsx)$/));
return {
test_strategy: hasTests ? 'unit' : (hasApi ? 'integration' : 'auto'),
regression_scope: 'affected',
acceptance_type: 'automated',
commit_strategy: 'per-task'
};
}
issueData.lifecycle_requirements = detectLifecycle(issueData.affected_components || []);
```
### Phase 6: Create Issue (Minimal Confirmation)
```javascript ```javascript
// Show summary and create // Show summary and create
@@ -191,24 +203,20 @@ if (clarityScore < 2) {
} }
if (proceed) { if (proceed) {
// Construct and save
const newIssue = { const newIssue = {
id: issueData.id, id: issueData.id,
title: issueData.title || 'Untitled Issue', title: issueData.title || 'Untitled Issue',
status: 'registered', status: 'registered',
priority: flags.priority ? parseInt(flags.priority) : 3, priority: flags.priority ? parseInt(flags.priority) : 3,
context: issueData.problem_statement, context: issueData.context,
source: issueData.source, source: issueData.source,
source_url: issueData.source_url || null, source_url: issueData.source_url || null,
labels: issueData.labels || [], labels: issueData.labels || [],
problem_statement: issueData.problem_statement,
expected_behavior: issueData.expected_behavior || null, expected_behavior: issueData.expected_behavior || null,
actual_behavior: issueData.actual_behavior || null, actual_behavior: issueData.actual_behavior || null,
affected_components: issueData.affected_components || [], affected_components: issueData.affected_components || [],
extended_context: issueData.extended_context || null, feedback: issueData.feedback || [],
lifecycle_requirements: issueData.lifecycle_requirements,
bound_solution_id: null, bound_solution_id: null,
solution_count: 0,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}; };
@@ -234,24 +242,20 @@ Phase 2: Data Extraction (branched by clarity)
├────────────┼─────────────────┼──────────────┤ ├────────────┼─────────────────┼──────────────┤
│ gh CLI │ Parse struct │ AskQuestion │ │ gh CLI │ Parse struct │ AskQuestion │
│ → parse │ + quick hint │ (1 question) │ │ → parse │ + quick hint │ (1 question) │
│ │ (3 files max) │ │ │ (3 files max) │ → feedback
└────────────┴─────────────────┴──────────────┘ └────────────┴─────────────────┴──────────────┘
Phase 3: Lifecycle Auto-Detection Phase 3: Create Issue
└─ Infer test_strategy from affected files (NO questions)
Phase 4: Create Issue
├─ Score ≥ 2: Direct creation ├─ Score ≥ 2: Direct creation
└─ Score < 2: Confirm first → Create └─ Score < 2: Confirm first → Create
Note: Deep exploration deferred to /issue:plan Note: Deep exploration & lifecycle deferred to /issue:plan
``` ```
## Helper Functions ## Helper Functions
```javascript ```javascript
function extractKeywords(text) { function extractKeywords(text) {
// Extract meaningful keywords for ACE search
const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'not', 'with']); const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'not', 'with']);
return text return text
.toLowerCase() .toLowerCase()
@@ -261,11 +265,11 @@ function extractKeywords(text) {
} }
function parseTextDescription(text) { function parseTextDescription(text) {
const result = { title: '', problem_statement: '' }; const result = { title: '', context: '' };
const sentences = text.split(/\.(?=\s|$)/); const sentences = text.split(/\.(?=\s|$)/);
result.title = sentences[0]?.trim().substring(0, 60) || 'Untitled'; result.title = sentences[0]?.trim().substring(0, 60) || 'Untitled';
result.problem_statement = text.substring(0, 500); result.context = text.substring(0, 500);
// Extract structured fields if present // Extract structured fields if present
const expected = text.match(/expected:?\s*([^.]+)/i); const expected = text.match(/expected:?\s*([^.]+)/i);
@@ -285,12 +289,11 @@ function parseMarkdownBody(body) {
if (!body) return {}; if (!body) return {};
const result = {}; const result = {};
// Extract sections
const problem = body.match(/##?\s*(problem|description)[:\s]*([\s\S]*?)(?=##|$)/i); const problem = body.match(/##?\s*(problem|description)[:\s]*([\s\S]*?)(?=##|$)/i);
const expected = body.match(/##?\s*expected[:\s]*([\s\S]*?)(?=##|$)/i); const expected = body.match(/##?\s*expected[:\s]*([\s\S]*?)(?=##|$)/i);
const actual = body.match(/##?\s*actual[:\s]*([\s\S]*?)(?=##|$)/i); const actual = body.match(/##?\s*actual[:\s]*([\s\S]*?)(?=##|$)/i);
if (problem) result.problem_statement = problem[2].trim().substring(0, 500); if (problem) result.context = problem[2].trim().substring(0, 500);
if (expected) result.expected_behavior = expected[2].trim(); if (expected) result.expected_behavior = expected[2].trim();
if (actual) result.actual_behavior = actual[2].trim(); if (actual) result.actual_behavior = actual[2].trim();
@@ -315,7 +318,7 @@ function parseMarkdownBody(body) {
```bash ```bash
/issue:new "auth broken" /issue:new "auth broken"
# → Asks: "Input unclear. What is the issue about?" # → Asks: "Input unclear. What is the issue about?"
# → User selects "Bug fix" or provides details # → User provides details → saved to feedback[]
# → Creates issue # → Creates issue
``` ```

View File

@@ -18,14 +18,34 @@ process.stdout.on('error', (err: NodeJS.ErrnoException) => {
// ============ Interfaces ============ // ============ Interfaces ============
interface IssueFeedback {
type: 'failure' | 'clarification' | 'rejection';
stage: string; // new/plan/execute
content: string;
created_at: string;
}
interface Issue { interface Issue {
id: string; id: string;
title: string; title: string;
status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused'; status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused';
priority: number; priority: number;
context: string; context: string; // Problem description (single source of truth)
source?: 'github' | 'text' | 'discovery';
source_url?: string;
labels?: string[];
// Optional structured fields
expected_behavior?: string;
actual_behavior?: string;
affected_components?: string[];
// Feedback history (failures + human clarifications)
feedback?: IssueFeedback[];
// Solution binding
bound_solution_id: string | null; bound_solution_id: string | null;
solution_count: number;
// Timestamps // Timestamps
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@@ -472,7 +492,6 @@ async function initAction(issueId: string | undefined, options: IssueOptions): P
priority: options.priority ? parseInt(options.priority) : 3, priority: options.priority ? parseInt(options.priority) : 3,
context: options.description || '', context: options.description || '',
bound_solution_id: null, bound_solution_id: null,
solution_count: 0,
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}; };
@@ -531,7 +550,8 @@ async function listAction(issueId: string | undefined, options: IssueOptions): P
'paused': chalk.magenta 'paused': chalk.magenta
}[issue.status] || chalk.white; }[issue.status] || chalk.white;
const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${issue.solution_count}`; const solutionCount = readSolutions(issue.id).length;
const bound = issue.bound_solution_id ? `[${issue.bound_solution_id}]` : `${solutionCount}`;
console.log( console.log(
issue.id.padEnd(20) + issue.id.padEnd(20) +
statusColor(issue.status.padEnd(15)) + statusColor(issue.status.padEnd(15)) +
@@ -867,7 +887,6 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
writeSolutions(issueId, solutions); writeSolutions(issueId, solutions);
updateIssue(issueId, { updateIssue(issueId, {
bound_solution_id: solutionId, bound_solution_id: solutionId,
solution_count: solutions.length,
status: 'planned', status: 'planned',
planned_at: new Date().toISOString() planned_at: new Date().toISOString()
}); });