mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
fix(issue-plan-agent): Update acceptance criteria terminology and enhance issue loading process with metadata
This commit is contained in:
@@ -26,7 +26,7 @@ color: green
|
|||||||
- Dependency DAG validation
|
- Dependency DAG validation
|
||||||
- Auto-bind for single solution, return for selection on multiple
|
- Auto-bind for single solution, return for selection on multiple
|
||||||
|
|
||||||
**Key Principle**: Generate tasks conforming to schema with quantified delivery_criteria.
|
**Key Principle**: Generate tasks conforming to schema with quantified acceptance criteria.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -71,11 +71,17 @@ function analyzeIssue(issue) {
|
|||||||
issue_id: issue.id,
|
issue_id: issue.id,
|
||||||
requirements: extractRequirements(issue.description),
|
requirements: extractRequirements(issue.description),
|
||||||
scope: inferScope(issue.title, issue.description),
|
scope: inferScope(issue.title, issue.description),
|
||||||
complexity: determineComplexity(issue) // Low | Medium | High
|
complexity: determineComplexity(issue), // Low | Medium | High
|
||||||
|
lifecycle: issue.lifecycle_requirements // User preferences for test/commit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Step 3**: Apply lifecycle requirements to tasks
|
||||||
|
- `lifecycle.test_strategy` → Configure `test.unit`, `test.commands`
|
||||||
|
- `lifecycle.commit_strategy` → Configure `commit.type`, `commit.scope`
|
||||||
|
- `lifecycle.regression_scope` → Configure `regression` array
|
||||||
|
|
||||||
**Complexity Rules**:
|
**Complexity Rules**:
|
||||||
| Complexity | Files | Tasks |
|
| Complexity | Files | Tasks |
|
||||||
|------------|-------|-------|
|
|------------|-------|-------|
|
||||||
@@ -147,18 +153,29 @@ Generate multiple candidate solutions when:
|
|||||||
```javascript
|
```javascript
|
||||||
function decomposeTasks(issue, exploration) {
|
function decomposeTasks(issue, exploration) {
|
||||||
return groups.map(group => ({
|
return groups.map(group => ({
|
||||||
id: `TASK-${String(taskId++).padStart(3, '0')}`,
|
id: `T${taskId++}`, // Pattern: ^T[0-9]+$
|
||||||
title: group.title,
|
title: group.title,
|
||||||
type: inferType(group), // feature | bug | refactor | test | chore | docs
|
scope: inferScope(group), // Module path
|
||||||
|
action: inferAction(group), // Create | Update | Implement | ...
|
||||||
description: group.description,
|
description: group.description,
|
||||||
file_context: group.files,
|
modification_points: mapModificationPoints(group),
|
||||||
|
implementation: generateSteps(group), // Step-by-step guide
|
||||||
|
test: {
|
||||||
|
unit: generateUnitTests(group),
|
||||||
|
commands: ['npm test']
|
||||||
|
},
|
||||||
|
acceptance: {
|
||||||
|
criteria: generateCriteria(group), // Quantified checklist
|
||||||
|
verification: generateVerification(group)
|
||||||
|
},
|
||||||
|
commit: {
|
||||||
|
type: inferCommitType(group), // feat | fix | refactor | ...
|
||||||
|
scope: inferScope(group),
|
||||||
|
message_template: generateCommitMsg(group)
|
||||||
|
},
|
||||||
depends_on: inferDependencies(group, tasks),
|
depends_on: inferDependencies(group, tasks),
|
||||||
delivery_criteria: generateDeliveryCriteria(group), // Quantified checklist
|
|
||||||
pause_criteria: identifyBlockers(group),
|
|
||||||
status: 'pending',
|
|
||||||
current_phase: 'analyze',
|
|
||||||
executor: inferExecutor(group),
|
executor: inferExecutor(group),
|
||||||
priority: calculatePriority(group)
|
priority: calculatePriority(group) // 1-5 (1=highest)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -126,6 +126,16 @@ function detectConflicts(fileModifications, graph) {
|
|||||||
|
|
||||||
### 2.4 Semantic Priority
|
### 2.4 Semantic Priority
|
||||||
|
|
||||||
|
**Base Priority Mapping** (task.priority 1-5 → base score):
|
||||||
|
| task.priority | Base Score | Meaning |
|
||||||
|
|---------------|------------|---------|
|
||||||
|
| 1 | 0.8 | Highest |
|
||||||
|
| 2 | 0.65 | High |
|
||||||
|
| 3 | 0.5 | Medium |
|
||||||
|
| 4 | 0.35 | Low |
|
||||||
|
| 5 | 0.2 | Lowest |
|
||||||
|
|
||||||
|
**Action-based Boost** (applied to base score):
|
||||||
| Factor | Boost |
|
| Factor | Boost |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| Create action | +0.2 |
|
| Create action | +0.2 |
|
||||||
@@ -138,6 +148,8 @@ function detectConflicts(fileModifications, graph) {
|
|||||||
| Test action | -0.1 |
|
| Test action | -0.1 |
|
||||||
| Delete action | -0.15 |
|
| Delete action | -0.15 |
|
||||||
|
|
||||||
|
**Formula**: `semantic_priority = clamp(baseScore + sum(boosts), 0.0, 1.0)`
|
||||||
|
|
||||||
### 2.5 Group Assignment
|
### 2.5 Group Assignment
|
||||||
|
|
||||||
- **Parallel (P*)**: Tasks with no dependencies or conflicts between them
|
- **Parallel (P*)**: Tasks with no dependencies or conflicts between them
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ Unified planning command using **issue-plan-agent** that combines exploration an
|
|||||||
```
|
```
|
||||||
Phase 1: Issue Loading
|
Phase 1: Issue Loading
|
||||||
├─ Parse input (single, comma-separated, or --all-pending)
|
├─ Parse input (single, comma-separated, or --all-pending)
|
||||||
├─ Load issues from .workflow/issues/issues.jsonl
|
├─ Fetch issue metadata (ID, title, tags)
|
||||||
├─ Validate issues exist (create if needed)
|
├─ Validate issues exist (create if needed)
|
||||||
└─ Group into batches (max 3 per batch)
|
└─ Group by similarity (shared tags or title keywords, max 3 per batch)
|
||||||
|
|
||||||
Phase 2: Unified Explore + Plan (issue-plan-agent)
|
Phase 2: Unified Explore + Plan (issue-plan-agent)
|
||||||
├─ Launch issue-plan-agent per batch
|
├─ Launch issue-plan-agent per batch
|
||||||
@@ -97,41 +97,71 @@ Phase 4: Summary
|
|||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
|
|
||||||
### Phase 1: Issue Loading (IDs Only)
|
### Phase 1: Issue Loading (ID + Title + Tags)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const batchSize = flags.batchSize || 3;
|
const batchSize = flags.batchSize || 3;
|
||||||
let issueIds = [];
|
let issues = []; // {id, title, tags}
|
||||||
|
|
||||||
if (flags.allPending) {
|
if (flags.allPending) {
|
||||||
// Get pending issue IDs directly via CLI
|
// Get pending issues with metadata via CLI (JSON output)
|
||||||
const ids = Bash(`ccw issue list --status pending,registered --ids`).trim();
|
const result = Bash(`ccw issue list --status pending,registered --json`).trim();
|
||||||
issueIds = ids ? ids.split('\n').filter(Boolean) : [];
|
const parsed = result ? JSON.parse(result) : [];
|
||||||
|
issues = parsed.map(i => ({ id: i.id, title: i.title || '', tags: i.tags || [] }));
|
||||||
|
|
||||||
if (issueIds.length === 0) {
|
if (issues.length === 0) {
|
||||||
console.log('No pending issues found.');
|
console.log('No pending issues found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`Found ${issueIds.length} pending issues`);
|
console.log(`Found ${issues.length} pending issues`);
|
||||||
} else {
|
} else {
|
||||||
// Parse comma-separated issue IDs
|
// Parse comma-separated issue IDs, fetch metadata
|
||||||
issueIds = userInput.includes(',')
|
const ids = userInput.includes(',')
|
||||||
? userInput.split(',').map(s => s.trim())
|
? userInput.split(',').map(s => s.trim())
|
||||||
: [userInput.trim()];
|
: [userInput.trim()];
|
||||||
|
|
||||||
// Create if not exists
|
for (const id of ids) {
|
||||||
for (const id of issueIds) {
|
|
||||||
Bash(`ccw issue init ${id} --title "Issue ${id}" 2>/dev/null || true`);
|
Bash(`ccw issue init ${id} --title "Issue ${id}" 2>/dev/null || true`);
|
||||||
|
const info = Bash(`ccw issue status ${id} --json`).trim();
|
||||||
|
const parsed = info ? JSON.parse(info) : {};
|
||||||
|
issues.push({ id, title: parsed.title || '', tags: parsed.tags || [] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group into batches
|
// Intelligent grouping by similarity (tags → title keywords)
|
||||||
const batches = [];
|
function groupBySimilarity(issues, maxSize) {
|
||||||
for (let i = 0; i < issueIds.length; i += batchSize) {
|
const batches = [];
|
||||||
batches.push(issueIds.slice(i, i + batchSize));
|
const used = new Set();
|
||||||
|
|
||||||
|
for (const issue of issues) {
|
||||||
|
if (used.has(issue.id)) continue;
|
||||||
|
|
||||||
|
const batch = [issue];
|
||||||
|
used.add(issue.id);
|
||||||
|
const issueTags = new Set(issue.tags);
|
||||||
|
const issueWords = new Set(issue.title.toLowerCase().split(/\s+/));
|
||||||
|
|
||||||
|
// Find similar issues
|
||||||
|
for (const other of issues) {
|
||||||
|
if (used.has(other.id) || batch.length >= maxSize) continue;
|
||||||
|
|
||||||
|
// Similarity: shared tags or shared title keywords
|
||||||
|
const sharedTags = other.tags.filter(t => issueTags.has(t)).length;
|
||||||
|
const otherWords = other.title.toLowerCase().split(/\s+/);
|
||||||
|
const sharedWords = otherWords.filter(w => issueWords.has(w) && w.length > 3).length;
|
||||||
|
|
||||||
|
if (sharedTags > 0 || sharedWords >= 2) {
|
||||||
|
batch.push(other);
|
||||||
|
used.add(other.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batches.push(batch);
|
||||||
|
}
|
||||||
|
return batches;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Processing ${issueIds.length} issues in ${batches.length} batch(es)`);
|
const batches = groupBySimilarity(issues, batchSize);
|
||||||
|
console.log(`Processing ${issues.length} issues in ${batches.length} batch(es) (grouped by similarity)`);
|
||||||
|
|
||||||
TodoWrite({
|
TodoWrite({
|
||||||
todos: batches.map((_, i) => ({
|
todos: batches.map((_, i) => ({
|
||||||
@@ -151,11 +181,16 @@ const pendingSelections = []; // Collect multi-solution issues for user selecti
|
|||||||
for (const [batchIndex, batch] of batches.entries()) {
|
for (const [batchIndex, batch] of batches.entries()) {
|
||||||
updateTodo(`Plan batch ${batchIndex + 1}`, 'in_progress');
|
updateTodo(`Plan batch ${batchIndex + 1}`, 'in_progress');
|
||||||
|
|
||||||
|
// Build issue list with metadata for agent context
|
||||||
|
const issueList = batch.map(i => `- ${i.id}: ${i.title}${i.tags.length ? ` [${i.tags.join(', ')}]` : ''}`).join('\n');
|
||||||
|
|
||||||
// Build minimal prompt - agent handles exploration, planning, and binding
|
// Build minimal prompt - agent handles exploration, planning, and binding
|
||||||
const issuePrompt = `
|
const issuePrompt = `
|
||||||
## Plan Issues
|
## Plan Issues
|
||||||
|
|
||||||
**Issue IDs**: ${batch.join(', ')}
|
**Issues** (grouped by similarity):
|
||||||
|
${issueList}
|
||||||
|
|
||||||
**Project Root**: ${process.cwd()}
|
**Project Root**: ${process.cwd()}
|
||||||
|
|
||||||
### Steps
|
### Steps
|
||||||
@@ -181,10 +216,11 @@ for (const [batchIndex, batch] of batches.entries()) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Launch issue-plan-agent - agent writes solutions directly
|
// Launch issue-plan-agent - agent writes solutions directly
|
||||||
|
const batchIds = batch.map(i => i.id);
|
||||||
const result = Task(
|
const result = Task(
|
||||||
subagent_type="issue-plan-agent",
|
subagent_type="issue-plan-agent",
|
||||||
run_in_background=false,
|
run_in_background=false,
|
||||||
description=`Explore & plan ${batch.length} issues`,
|
description=`Explore & plan ${batch.length} issues: ${batchIds.join(', ')}`,
|
||||||
prompt=issuePrompt
|
prompt=issuePrompt
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -244,7 +280,7 @@ const plannedIds = Bash(`ccw issue list --status planned --ids`).trim();
|
|||||||
const plannedCount = plannedIds ? plannedIds.split('\n').length : 0;
|
const plannedCount = plannedIds ? plannedIds.split('\n').length : 0;
|
||||||
|
|
||||||
console.log(`
|
console.log(`
|
||||||
## Done: ${issueIds.length} issues → ${plannedCount} planned
|
## Done: ${issues.length} issues → ${plannedCount} planned
|
||||||
|
|
||||||
Next: \`/issue:queue\` → \`/issue:execute\`
|
Next: \`/issue:queue\` → \`/issue:execute\`
|
||||||
`);
|
`);
|
||||||
|
|||||||
Reference in New Issue
Block a user