mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
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:
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user