mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
```bash
|
||||
@@ -63,7 +98,7 @@ if (isGitHubUrl || isGitHubShort) {
|
||||
source: 'github',
|
||||
source_url: gh.url,
|
||||
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)
|
||||
};
|
||||
} else {
|
||||
@@ -84,19 +119,17 @@ if (isGitHubUrl || isGitHubShort) {
|
||||
// Note: Deep exploration happens in /issue:plan, this is just a quick hint
|
||||
|
||||
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 {
|
||||
const aceResult = mcp__ace-tool__search_context({
|
||||
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) || [];
|
||||
} 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
|
||||
// ONLY ask questions if clarity is low
|
||||
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({
|
||||
questions: [{
|
||||
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) {
|
||||
issueData.problem_statement = answer.customText;
|
||||
issueData.context = answer.customText;
|
||||
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)
|
||||
|
||||
```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)
|
||||
### Phase 5: Create Issue
|
||||
|
||||
```javascript
|
||||
// Show summary and create
|
||||
@@ -191,24 +203,20 @@ if (clarityScore < 2) {
|
||||
}
|
||||
|
||||
if (proceed) {
|
||||
// Construct and save
|
||||
const newIssue = {
|
||||
id: issueData.id,
|
||||
title: issueData.title || 'Untitled Issue',
|
||||
status: 'registered',
|
||||
priority: flags.priority ? parseInt(flags.priority) : 3,
|
||||
context: issueData.problem_statement,
|
||||
context: issueData.context,
|
||||
source: issueData.source,
|
||||
source_url: issueData.source_url || null,
|
||||
labels: issueData.labels || [],
|
||||
problem_statement: issueData.problem_statement,
|
||||
expected_behavior: issueData.expected_behavior || null,
|
||||
actual_behavior: issueData.actual_behavior || null,
|
||||
affected_components: issueData.affected_components || [],
|
||||
extended_context: issueData.extended_context || null,
|
||||
lifecycle_requirements: issueData.lifecycle_requirements,
|
||||
feedback: issueData.feedback || [],
|
||||
bound_solution_id: null,
|
||||
solution_count: 0,
|
||||
created_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 │
|
||||
│ → parse │ + quick hint │ (1 question) │
|
||||
│ │ (3 files max) │ │
|
||||
│ │ (3 files max) │ → feedback │
|
||||
└────────────┴─────────────────┴──────────────┘
|
||||
|
||||
Phase 3: Lifecycle Auto-Detection
|
||||
└─ Infer test_strategy from affected files (NO questions)
|
||||
|
||||
Phase 4: Create Issue
|
||||
Phase 3: Create Issue
|
||||
├─ Score ≥ 2: Direct creation
|
||||
└─ Score < 2: Confirm first → Create
|
||||
|
||||
Note: Deep exploration deferred to /issue:plan
|
||||
Note: Deep exploration & lifecycle deferred to /issue:plan
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
```javascript
|
||||
function extractKeywords(text) {
|
||||
// Extract meaningful keywords for ACE search
|
||||
const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'not', 'with']);
|
||||
return text
|
||||
.toLowerCase()
|
||||
@@ -261,11 +265,11 @@ function extractKeywords(text) {
|
||||
}
|
||||
|
||||
function parseTextDescription(text) {
|
||||
const result = { title: '', problem_statement: '' };
|
||||
const result = { title: '', context: '' };
|
||||
const sentences = text.split(/\.(?=\s|$)/);
|
||||
|
||||
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
|
||||
const expected = text.match(/expected:?\s*([^.]+)/i);
|
||||
@@ -285,12 +289,11 @@ function parseMarkdownBody(body) {
|
||||
if (!body) return {};
|
||||
const result = {};
|
||||
|
||||
// Extract sections
|
||||
const problem = body.match(/##?\s*(problem|description)[:\s]*([\s\S]*?)(?=##|$)/i);
|
||||
const expected = body.match(/##?\s*expected[:\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 (actual) result.actual_behavior = actual[2].trim();
|
||||
|
||||
@@ -315,7 +318,7 @@ function parseMarkdownBody(body) {
|
||||
```bash
|
||||
/issue:new "auth broken"
|
||||
# → Asks: "Input unclear. What is the issue about?"
|
||||
# → User selects "Bug fix" or provides details
|
||||
# → User provides details → saved to feedback[]
|
||||
# → Creates issue
|
||||
```
|
||||
|
||||
|
||||
@@ -18,14 +18,34 @@ process.stdout.on('error', (err: NodeJS.ErrnoException) => {
|
||||
|
||||
// ============ Interfaces ============
|
||||
|
||||
interface IssueFeedback {
|
||||
type: 'failure' | 'clarification' | 'rejection';
|
||||
stage: string; // new/plan/execute
|
||||
content: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface Issue {
|
||||
id: string;
|
||||
title: string;
|
||||
status: 'registered' | 'planning' | 'planned' | 'queued' | 'executing' | 'completed' | 'failed' | 'paused';
|
||||
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;
|
||||
solution_count: number;
|
||||
|
||||
// Timestamps
|
||||
created_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,
|
||||
context: options.description || '',
|
||||
bound_solution_id: null,
|
||||
solution_count: 0,
|
||||
created_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
|
||||
}[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(
|
||||
issue.id.padEnd(20) +
|
||||
statusColor(issue.status.padEnd(15)) +
|
||||
@@ -867,7 +887,6 @@ async function bindAction(issueId: string | undefined, solutionId: string | unde
|
||||
writeSolutions(issueId, solutions);
|
||||
updateIssue(issueId, {
|
||||
bound_solution_id: solutionId,
|
||||
solution_count: solutions.length,
|
||||
status: 'planned',
|
||||
planned_at: new Date().toISOString()
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user