mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Implement dynamic test-fix execution phase with adaptive task generation
- Added Phase 2: Test-Cycle Execution documentation outlining the process for dynamic test-fix execution, including agent roles, core responsibilities, intelligent strategy engine, and progressive testing. - Introduced new PowerShell scripts for analyzing TypeScript errors, focusing on error categorization and reporting. - Created end-to-end tests for the Help Page, ensuring content visibility, documentation navigation, internationalization support, and accessibility compliance.
This commit is contained in:
701
.codex/skills/issue-resolve/phases/02-convert-to-plan.md
Normal file
701
.codex/skills/issue-resolve/phases/02-convert-to-plan.md
Normal file
@@ -0,0 +1,701 @@
|
||||
# Phase 2: Convert from Artifact
|
||||
|
||||
## Overview
|
||||
|
||||
Converts various planning artifact formats into issue workflow solutions with intelligent detection and automatic binding.
|
||||
|
||||
**Supported Sources** (auto-detected):
|
||||
- **lite-plan**: `.workflow/.lite-plan/{slug}/plan.json`
|
||||
- **workflow-session**: `WFS-xxx` ID or `.workflow/active/{session}/` folder
|
||||
- **markdown**: Any `.md` file with implementation/task content
|
||||
- **json**: Direct JSON files matching plan-json-schema
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Source artifact path or WFS-xxx ID provided
|
||||
- `ccw issue` CLI available
|
||||
- `.workflow/issues/` directory exists or will be created
|
||||
|
||||
## Auto Mode
|
||||
|
||||
When `--yes` or `-y`: Skip confirmation, auto-create issue and bind solution.
|
||||
|
||||
## Command Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `<SOURCE>` | Planning artifact path or WFS-xxx ID | Required |
|
||||
| `--issue <id>` | Bind to existing issue instead of creating new | Auto-create |
|
||||
| `--supplement` | Add tasks to existing solution (requires --issue) | false |
|
||||
| `-y, --yes` | Skip all confirmations | false |
|
||||
|
||||
## Core Data Access Principle
|
||||
|
||||
**Important**: Use CLI commands for all issue/solution operations.
|
||||
|
||||
| Operation | Correct | Incorrect |
|
||||
|-----------|---------|-----------|
|
||||
| Get issue | `ccw issue status <id> --json` | Read issues.jsonl directly |
|
||||
| Create issue | `ccw issue init <id> --title "..."` | Write to issues.jsonl |
|
||||
| Bind solution | `ccw issue bind <id> <sol-id>` | Edit issues.jsonl |
|
||||
| List solutions | `ccw issue solutions --issue <id> --brief` | Read solutions/*.jsonl |
|
||||
|
||||
## Solution Schema Reference
|
||||
|
||||
Target format for all extracted data (from solution-schema.json):
|
||||
|
||||
```typescript
|
||||
interface Solution {
|
||||
id: string; // SOL-{issue-id}-{4-char-uid}
|
||||
description?: string; // High-level summary
|
||||
approach?: string; // Technical strategy
|
||||
tasks: Task[]; // Required: at least 1 task
|
||||
exploration_context?: object; // Optional: source context
|
||||
analysis?: { risk, impact, complexity };
|
||||
score?: number; // 0.0-1.0
|
||||
is_bound: boolean;
|
||||
created_at: string;
|
||||
bound_at?: string;
|
||||
}
|
||||
|
||||
interface Task {
|
||||
id: string; // T1, T2, T3... (pattern: ^T[0-9]+$)
|
||||
title: string; // Required: action verb + target
|
||||
scope: string; // Required: module path or feature area
|
||||
action: Action; // Required: Create|Update|Implement|...
|
||||
description?: string;
|
||||
modification_points?: Array<{file, target, change}>;
|
||||
implementation: string[]; // Required: step-by-step guide
|
||||
test?: { unit?, integration?, commands?, coverage_target? };
|
||||
acceptance: { criteria: string[], verification: string[] }; // Required
|
||||
commit?: { type, scope, message_template, breaking? };
|
||||
depends_on?: string[];
|
||||
priority?: number; // 1-5 (default: 3)
|
||||
}
|
||||
|
||||
type Action = 'Create' | 'Update' | 'Implement' | 'Refactor' | 'Add' | 'Delete' | 'Configure' | 'Test' | 'Fix';
|
||||
```
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 2.1: Parse Arguments & Detect Source Type
|
||||
|
||||
```javascript
|
||||
const input = userInput.trim();
|
||||
const flags = parseFlags(userInput); // --issue, --supplement, -y/--yes
|
||||
|
||||
// Extract source path (first non-flag argument)
|
||||
const source = extractSourceArg(input);
|
||||
|
||||
// Detect source type
|
||||
function detectSourceType(source) {
|
||||
// Check for WFS-xxx pattern (workflow session ID)
|
||||
if (source.match(/^WFS-[\w-]+$/)) {
|
||||
return { type: 'workflow-session-id', path: `.workflow/active/${source}` };
|
||||
}
|
||||
|
||||
// Check if directory
|
||||
const isDir = Bash(`test -d "${source}" && echo "dir" || echo "file"`).trim() === 'dir';
|
||||
|
||||
if (isDir) {
|
||||
// Check for lite-plan indicator
|
||||
const hasPlanJson = Bash(`test -f "${source}/plan.json" && echo "yes" || echo "no"`).trim() === 'yes';
|
||||
if (hasPlanJson) {
|
||||
return { type: 'lite-plan', path: source };
|
||||
}
|
||||
|
||||
// Check for workflow session indicator
|
||||
const hasSession = Bash(`test -f "${source}/workflow-session.json" && echo "yes" || echo "no"`).trim() === 'yes';
|
||||
if (hasSession) {
|
||||
return { type: 'workflow-session', path: source };
|
||||
}
|
||||
}
|
||||
|
||||
// Check file extensions
|
||||
if (source.endsWith('.json')) {
|
||||
return { type: 'json-file', path: source };
|
||||
}
|
||||
if (source.endsWith('.md')) {
|
||||
return { type: 'markdown-file', path: source };
|
||||
}
|
||||
|
||||
// Check if path exists at all
|
||||
const exists = Bash(`test -e "${source}" && echo "yes" || echo "no"`).trim() === 'yes';
|
||||
if (!exists) {
|
||||
throw new Error(`E001: Source not found: ${source}`);
|
||||
}
|
||||
|
||||
return { type: 'unknown', path: source };
|
||||
}
|
||||
|
||||
const sourceInfo = detectSourceType(source);
|
||||
if (sourceInfo.type === 'unknown') {
|
||||
throw new Error(`E002: Unable to detect source format for: ${source}`);
|
||||
}
|
||||
|
||||
console.log(`Detected source type: ${sourceInfo.type}`);
|
||||
```
|
||||
|
||||
### Step 2.2: Extract Data Using Format-Specific Extractor
|
||||
|
||||
```javascript
|
||||
let extracted = { title: '', approach: '', tasks: [], metadata: {} };
|
||||
|
||||
switch (sourceInfo.type) {
|
||||
case 'lite-plan':
|
||||
extracted = extractFromLitePlan(sourceInfo.path);
|
||||
break;
|
||||
case 'workflow-session':
|
||||
case 'workflow-session-id':
|
||||
extracted = extractFromWorkflowSession(sourceInfo.path);
|
||||
break;
|
||||
case 'markdown-file':
|
||||
extracted = await extractFromMarkdownAI(sourceInfo.path);
|
||||
break;
|
||||
case 'json-file':
|
||||
extracted = extractFromJsonFile(sourceInfo.path);
|
||||
break;
|
||||
}
|
||||
|
||||
// Validate extraction
|
||||
if (!extracted.tasks || extracted.tasks.length === 0) {
|
||||
throw new Error('E006: No tasks extracted from source');
|
||||
}
|
||||
|
||||
// Ensure task IDs are normalized to T1, T2, T3...
|
||||
extracted.tasks = normalizeTaskIds(extracted.tasks);
|
||||
|
||||
console.log(`Extracted: ${extracted.tasks.length} tasks`);
|
||||
```
|
||||
|
||||
#### Extractor: Lite-Plan
|
||||
|
||||
```javascript
|
||||
function extractFromLitePlan(folderPath) {
|
||||
const planJson = Read(`${folderPath}/plan.json`);
|
||||
const plan = JSON.parse(planJson);
|
||||
|
||||
return {
|
||||
title: plan.summary?.split('.')[0]?.trim() || 'Untitled Plan',
|
||||
description: plan.summary,
|
||||
approach: plan.approach,
|
||||
tasks: plan.tasks.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
scope: t.scope || '',
|
||||
action: t.action || 'Implement',
|
||||
description: t.description || t.title,
|
||||
modification_points: t.modification_points || [],
|
||||
implementation: Array.isArray(t.implementation) ? t.implementation : [t.implementation || ''],
|
||||
test: t.verification ? {
|
||||
unit: t.verification.unit_tests,
|
||||
integration: t.verification.integration_tests,
|
||||
commands: t.verification.manual_checks
|
||||
} : {},
|
||||
acceptance: {
|
||||
criteria: Array.isArray(t.acceptance) ? t.acceptance : [t.acceptance || ''],
|
||||
verification: t.verification?.manual_checks || []
|
||||
},
|
||||
depends_on: t.depends_on || [],
|
||||
priority: 3
|
||||
})),
|
||||
metadata: {
|
||||
source_type: 'lite-plan',
|
||||
source_path: folderPath,
|
||||
complexity: plan.complexity,
|
||||
estimated_time: plan.estimated_time,
|
||||
exploration_angles: plan._metadata?.exploration_angles || [],
|
||||
original_timestamp: plan._metadata?.timestamp
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### Extractor: Workflow Session
|
||||
|
||||
```javascript
|
||||
function extractFromWorkflowSession(sessionPath) {
|
||||
// Load session metadata
|
||||
const sessionJson = Read(`${sessionPath}/workflow-session.json`);
|
||||
const session = JSON.parse(sessionJson);
|
||||
|
||||
// Load IMPL_PLAN.md for approach (if exists)
|
||||
let approach = '';
|
||||
const implPlanPath = `${sessionPath}/IMPL_PLAN.md`;
|
||||
const hasImplPlan = Bash(`test -f "${implPlanPath}" && echo "yes" || echo "no"`).trim() === 'yes';
|
||||
if (hasImplPlan) {
|
||||
const implPlan = Read(implPlanPath);
|
||||
// Extract overview/approach section
|
||||
const overviewMatch = implPlan.match(/##\s*(?:Overview|Approach|Strategy)\s*\n([\s\S]*?)(?=\n##|$)/i);
|
||||
approach = overviewMatch?.[1]?.trim() || implPlan.split('\n').slice(0, 10).join('\n');
|
||||
}
|
||||
|
||||
// Load all task JSONs from .task folder
|
||||
const taskFiles = Glob({ pattern: `${sessionPath}/.task/IMPL-*.json` });
|
||||
const tasks = taskFiles.map(f => {
|
||||
const taskJson = Read(f);
|
||||
const task = JSON.parse(taskJson);
|
||||
return {
|
||||
id: task.id?.replace(/^IMPL-0*/, 'T') || 'T1', // IMPL-001 → T1
|
||||
title: task.title,
|
||||
scope: task.scope || inferScopeFromTask(task),
|
||||
action: capitalizeAction(task.type) || 'Implement',
|
||||
description: task.description,
|
||||
modification_points: task.implementation?.modification_points || [],
|
||||
implementation: task.implementation?.steps || [],
|
||||
test: task.implementation?.test || {},
|
||||
acceptance: {
|
||||
criteria: task.acceptance_criteria || [],
|
||||
verification: task.verification_steps || []
|
||||
},
|
||||
commit: task.commit,
|
||||
depends_on: (task.depends_on || []).map(d => d.replace(/^IMPL-0*/, 'T')),
|
||||
priority: task.priority || 3
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title: session.name || session.description?.split('.')[0] || 'Workflow Session',
|
||||
description: session.description || session.name,
|
||||
approach: approach || session.description,
|
||||
tasks: tasks,
|
||||
metadata: {
|
||||
source_type: 'workflow-session',
|
||||
source_path: sessionPath,
|
||||
session_id: session.id,
|
||||
created_at: session.created_at
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function inferScopeFromTask(task) {
|
||||
if (task.implementation?.modification_points?.length) {
|
||||
const files = task.implementation.modification_points.map(m => m.file);
|
||||
// Find common directory prefix
|
||||
const dirs = files.map(f => f.split('/').slice(0, -1).join('/'));
|
||||
return [...new Set(dirs)][0] || '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function capitalizeAction(type) {
|
||||
if (!type) return 'Implement';
|
||||
const map = { feature: 'Implement', bugfix: 'Fix', refactor: 'Refactor', test: 'Test', docs: 'Update' };
|
||||
return map[type.toLowerCase()] || type.charAt(0).toUpperCase() + type.slice(1);
|
||||
}
|
||||
```
|
||||
|
||||
#### Extractor: Markdown (AI-Assisted via Gemini)
|
||||
|
||||
```javascript
|
||||
async function extractFromMarkdownAI(filePath) {
|
||||
const fileContent = Read(filePath);
|
||||
|
||||
// Use Gemini CLI for intelligent extraction
|
||||
const cliPrompt = `PURPOSE: Extract implementation plan from markdown document for issue solution conversion. Must output ONLY valid JSON.
|
||||
TASK: • Analyze document structure • Identify title/summary • Extract approach/strategy section • Parse tasks from any format (lists, tables, sections, code blocks) • Normalize each task to solution schema
|
||||
MODE: analysis
|
||||
CONTEXT: Document content provided below
|
||||
EXPECTED: Valid JSON object with format:
|
||||
{
|
||||
"title": "extracted title",
|
||||
"approach": "extracted approach/strategy",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "T1",
|
||||
"title": "task title",
|
||||
"scope": "module or feature area",
|
||||
"action": "Implement|Update|Create|Fix|Refactor|Add|Delete|Configure|Test",
|
||||
"description": "what to do",
|
||||
"implementation": ["step 1", "step 2"],
|
||||
"acceptance": ["criteria 1", "criteria 2"]
|
||||
}
|
||||
]
|
||||
}
|
||||
CONSTRAINTS: Output ONLY valid JSON - no markdown, no explanation | Action must be one of: Create, Update, Implement, Refactor, Add, Delete, Configure, Test, Fix | Tasks must have id, title, scope, action, implementation (array), acceptance (array)
|
||||
|
||||
DOCUMENT CONTENT:
|
||||
${fileContent}`;
|
||||
|
||||
// Execute Gemini CLI
|
||||
const result = Bash(`ccw cli -p '${cliPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis`, { timeout: 120000 });
|
||||
|
||||
// Parse JSON from result (may be wrapped in markdown code block)
|
||||
let jsonText = result.trim();
|
||||
const jsonMatch = jsonText.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
if (jsonMatch) {
|
||||
jsonText = jsonMatch[1].trim();
|
||||
}
|
||||
|
||||
try {
|
||||
const extracted = JSON.parse(jsonText);
|
||||
|
||||
// Normalize tasks
|
||||
const tasks = (extracted.tasks || []).map((t, i) => ({
|
||||
id: t.id || `T${i + 1}`,
|
||||
title: t.title || 'Untitled task',
|
||||
scope: t.scope || '',
|
||||
action: validateAction(t.action) || 'Implement',
|
||||
description: t.description || t.title,
|
||||
modification_points: t.modification_points || [],
|
||||
implementation: Array.isArray(t.implementation) ? t.implementation : [t.implementation || ''],
|
||||
test: t.test || {},
|
||||
acceptance: {
|
||||
criteria: Array.isArray(t.acceptance) ? t.acceptance : [t.acceptance || ''],
|
||||
verification: t.verification || []
|
||||
},
|
||||
depends_on: t.depends_on || [],
|
||||
priority: t.priority || 3
|
||||
}));
|
||||
|
||||
return {
|
||||
title: extracted.title || 'Extracted Plan',
|
||||
description: extracted.summary || extracted.title,
|
||||
approach: extracted.approach || '',
|
||||
tasks: tasks,
|
||||
metadata: {
|
||||
source_type: 'markdown',
|
||||
source_path: filePath,
|
||||
extraction_method: 'gemini-ai'
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
// Provide more context for debugging
|
||||
throw new Error(`E005: Failed to extract tasks from markdown. Gemini response was not valid JSON. Error: ${e.message}. Response preview: ${jsonText.substring(0, 200)}...`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateAction(action) {
|
||||
const validActions = ['Create', 'Update', 'Implement', 'Refactor', 'Add', 'Delete', 'Configure', 'Test', 'Fix'];
|
||||
if (!action) return null;
|
||||
const normalized = action.charAt(0).toUpperCase() + action.slice(1).toLowerCase();
|
||||
return validActions.includes(normalized) ? normalized : null;
|
||||
}
|
||||
```
|
||||
|
||||
#### Extractor: JSON File
|
||||
|
||||
```javascript
|
||||
function extractFromJsonFile(filePath) {
|
||||
const content = Read(filePath);
|
||||
const plan = JSON.parse(content);
|
||||
|
||||
// Detect if it's already solution format or plan format
|
||||
if (plan.tasks && Array.isArray(plan.tasks)) {
|
||||
// Map tasks to normalized format
|
||||
const tasks = plan.tasks.map((t, i) => ({
|
||||
id: t.id || `T${i + 1}`,
|
||||
title: t.title,
|
||||
scope: t.scope || '',
|
||||
action: t.action || 'Implement',
|
||||
description: t.description || t.title,
|
||||
modification_points: t.modification_points || [],
|
||||
implementation: Array.isArray(t.implementation) ? t.implementation : [t.implementation || ''],
|
||||
test: t.test || t.verification || {},
|
||||
acceptance: normalizeAcceptance(t.acceptance),
|
||||
depends_on: t.depends_on || [],
|
||||
priority: t.priority || 3
|
||||
}));
|
||||
|
||||
return {
|
||||
title: plan.summary?.split('.')[0] || plan.title || 'JSON Plan',
|
||||
description: plan.summary || plan.description,
|
||||
approach: plan.approach,
|
||||
tasks: tasks,
|
||||
metadata: {
|
||||
source_type: 'json',
|
||||
source_path: filePath,
|
||||
complexity: plan.complexity,
|
||||
original_metadata: plan._metadata
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('E002: JSON file does not contain valid plan structure (missing tasks array)');
|
||||
}
|
||||
|
||||
function normalizeAcceptance(acceptance) {
|
||||
if (!acceptance) return { criteria: [], verification: [] };
|
||||
if (typeof acceptance === 'object' && acceptance.criteria) return acceptance;
|
||||
if (Array.isArray(acceptance)) return { criteria: acceptance, verification: [] };
|
||||
return { criteria: [String(acceptance)], verification: [] };
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.3: Normalize Task IDs
|
||||
|
||||
```javascript
|
||||
function normalizeTaskIds(tasks) {
|
||||
return tasks.map((t, i) => ({
|
||||
...t,
|
||||
id: `T${i + 1}`,
|
||||
// Also normalize depends_on references
|
||||
depends_on: (t.depends_on || []).map(d => {
|
||||
// Handle various ID formats: IMPL-001, T1, 1, etc.
|
||||
const num = d.match(/\d+/)?.[0];
|
||||
return num ? `T${parseInt(num)}` : d;
|
||||
})
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.4: Resolve Issue (Create or Find)
|
||||
|
||||
```javascript
|
||||
let issueId = flags.issue;
|
||||
let existingSolution = null;
|
||||
|
||||
if (issueId) {
|
||||
// Validate issue exists
|
||||
let issueCheck;
|
||||
try {
|
||||
issueCheck = Bash(`ccw issue status ${issueId} --json 2>/dev/null`).trim();
|
||||
if (!issueCheck || issueCheck === '') {
|
||||
throw new Error('empty response');
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`E003: Issue not found: ${issueId}`);
|
||||
}
|
||||
|
||||
const issue = JSON.parse(issueCheck);
|
||||
|
||||
// Check if issue already has bound solution
|
||||
if (issue.bound_solution_id && !flags.supplement) {
|
||||
throw new Error(`E004: Issue ${issueId} already has bound solution (${issue.bound_solution_id}). Use --supplement to add tasks.`);
|
||||
}
|
||||
|
||||
// Load existing solution for supplement mode
|
||||
if (flags.supplement && issue.bound_solution_id) {
|
||||
try {
|
||||
const solResult = Bash(`ccw issue solution ${issue.bound_solution_id} --json`).trim();
|
||||
existingSolution = JSON.parse(solResult);
|
||||
console.log(`Loaded existing solution with ${existingSolution.tasks.length} tasks`);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load existing solution: ${e.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create new issue via ccw issue create (auto-generates correct ID)
|
||||
// Smart extraction: title from content, priority from complexity
|
||||
const title = extracted.title || 'Converted Plan';
|
||||
const context = extracted.description || extracted.approach || title;
|
||||
|
||||
// Auto-determine priority based on complexity
|
||||
const complexityMap = { high: 2, medium: 3, low: 4 };
|
||||
const priority = complexityMap[extracted.metadata.complexity?.toLowerCase()] || 3;
|
||||
|
||||
try {
|
||||
// Use heredoc to avoid shell escaping issues
|
||||
const createResult = Bash(`ccw issue create << 'EOF'
|
||||
{
|
||||
"title": ${JSON.stringify(title)},
|
||||
"context": ${JSON.stringify(context)},
|
||||
"priority": ${priority},
|
||||
"source": "converted"
|
||||
}
|
||||
EOF`).trim();
|
||||
|
||||
// Parse result to get created issue ID
|
||||
const created = JSON.parse(createResult);
|
||||
issueId = created.id;
|
||||
console.log(`Created issue: ${issueId} (priority: ${priority})`);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to create issue: ${e.message}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.5: Generate Solution
|
||||
|
||||
```javascript
|
||||
// Generate solution ID
|
||||
function generateSolutionId(issueId) {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let uid = '';
|
||||
for (let i = 0; i < 4; i++) {
|
||||
uid += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return `SOL-${issueId}-${uid}`;
|
||||
}
|
||||
|
||||
let solution;
|
||||
const solutionId = generateSolutionId(issueId);
|
||||
|
||||
if (flags.supplement && existingSolution) {
|
||||
// Supplement mode: merge with existing solution
|
||||
const maxTaskId = Math.max(...existingSolution.tasks.map(t => parseInt(t.id.slice(1))));
|
||||
|
||||
const newTasks = extracted.tasks.map((t, i) => ({
|
||||
...t,
|
||||
id: `T${maxTaskId + i + 1}`
|
||||
}));
|
||||
|
||||
solution = {
|
||||
...existingSolution,
|
||||
tasks: [...existingSolution.tasks, ...newTasks],
|
||||
approach: existingSolution.approach + '\n\n[Supplementary] ' + (extracted.approach || ''),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log(`Supplementing: ${existingSolution.tasks.length} existing + ${newTasks.length} new = ${solution.tasks.length} total tasks`);
|
||||
} else {
|
||||
// New solution
|
||||
solution = {
|
||||
id: solutionId,
|
||||
description: extracted.description || extracted.title,
|
||||
approach: extracted.approach,
|
||||
tasks: extracted.tasks,
|
||||
exploration_context: extracted.metadata.exploration_angles ? {
|
||||
exploration_angles: extracted.metadata.exploration_angles
|
||||
} : undefined,
|
||||
analysis: {
|
||||
risk: 'medium',
|
||||
impact: 'medium',
|
||||
complexity: extracted.metadata.complexity?.toLowerCase() || 'medium'
|
||||
},
|
||||
is_bound: false,
|
||||
created_at: new Date().toISOString(),
|
||||
_conversion_metadata: {
|
||||
source_type: extracted.metadata.source_type,
|
||||
source_path: extracted.metadata.source_path,
|
||||
converted_at: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.6: Confirm & Persist
|
||||
|
||||
```javascript
|
||||
// Display preview
|
||||
console.log(`
|
||||
## Conversion Summary
|
||||
|
||||
**Issue**: ${issueId}
|
||||
**Solution**: ${flags.supplement ? existingSolution.id : solutionId}
|
||||
**Tasks**: ${solution.tasks.length}
|
||||
**Mode**: ${flags.supplement ? 'Supplement' : 'New'}
|
||||
|
||||
### Tasks:
|
||||
${solution.tasks.map(t => `- ${t.id}: ${t.title} [${t.action}]`).join('\n')}
|
||||
`);
|
||||
|
||||
// Confirm if not auto mode
|
||||
if (!flags.yes && !flags.y) {
|
||||
const confirm = AskUserQuestion({
|
||||
questions: [{
|
||||
question: `Create solution for issue ${issueId} with ${solution.tasks.length} tasks?`,
|
||||
header: 'Confirm',
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: 'Yes, create solution', description: 'Create and bind solution' },
|
||||
{ label: 'Cancel', description: 'Abort without changes' }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
if (!confirm.answers?.['Confirm']?.includes('Yes')) {
|
||||
console.log('Cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Persist solution (following issue-plan-agent pattern)
|
||||
Bash(`mkdir -p .workflow/issues/solutions`);
|
||||
|
||||
const solutionFile = `.workflow/issues/solutions/${issueId}.jsonl`;
|
||||
|
||||
if (flags.supplement) {
|
||||
// Supplement mode: update existing solution line atomically
|
||||
try {
|
||||
const existingContent = Read(solutionFile);
|
||||
const lines = existingContent.trim().split('\n').filter(l => l);
|
||||
const updatedLines = lines.map(line => {
|
||||
const sol = JSON.parse(line);
|
||||
if (sol.id === existingSolution.id) {
|
||||
return JSON.stringify(solution);
|
||||
}
|
||||
return line;
|
||||
});
|
||||
// Atomic write: write entire content at once
|
||||
Write({ file_path: solutionFile, content: updatedLines.join('\n') + '\n' });
|
||||
console.log(`Updated solution: ${existingSolution.id}`);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to update solution: ${e.message}`);
|
||||
}
|
||||
|
||||
// Note: No need to rebind - solution is already bound to issue
|
||||
} else {
|
||||
// New solution: append to JSONL file (following issue-plan-agent pattern)
|
||||
try {
|
||||
const solutionLine = JSON.stringify(solution);
|
||||
|
||||
// Read existing content, append new line, write atomically
|
||||
const existing = Bash(`test -f "${solutionFile}" && cat "${solutionFile}" || echo ""`).trim();
|
||||
const newContent = existing ? existing + '\n' + solutionLine + '\n' : solutionLine + '\n';
|
||||
Write({ file_path: solutionFile, content: newContent });
|
||||
|
||||
console.log(`Created solution: ${solutionId}`);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to write solution: ${e.message}`);
|
||||
}
|
||||
|
||||
// Bind solution to issue
|
||||
try {
|
||||
Bash(`ccw issue bind ${issueId} ${solutionId}`);
|
||||
console.log(`Bound solution to issue`);
|
||||
} catch (e) {
|
||||
// Cleanup: remove solution file on bind failure
|
||||
try {
|
||||
Bash(`rm -f "${solutionFile}"`);
|
||||
} catch (cleanupError) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
throw new Error(`Failed to bind solution: ${e.message}`);
|
||||
}
|
||||
|
||||
// Update issue status to planned
|
||||
try {
|
||||
Bash(`ccw issue update ${issueId} --status planned`);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to update issue status: ${e.message}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.7: Summary
|
||||
|
||||
```javascript
|
||||
console.log(`
|
||||
## Done
|
||||
|
||||
**Issue**: ${issueId}
|
||||
**Solution**: ${flags.supplement ? existingSolution.id : solutionId}
|
||||
**Tasks**: ${solution.tasks.length}
|
||||
**Status**: planned
|
||||
|
||||
### Next Steps:
|
||||
- \`/issue:queue\` → Form execution queue
|
||||
- \`ccw issue status ${issueId}\` → View issue details
|
||||
- \`ccw issue solution ${flags.supplement ? existingSolution.id : solutionId}\` → View solution
|
||||
`);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Code | Resolution |
|
||||
|-------|------|------------|
|
||||
| Source not found | E001 | Check path exists |
|
||||
| Invalid source format | E002 | Verify file contains valid plan structure |
|
||||
| Issue not found | E003 | Check issue ID or omit --issue to create new |
|
||||
| Solution already bound | E004 | Use --supplement to add tasks |
|
||||
| AI extraction failed | E005 | Check markdown structure, try simpler format |
|
||||
| No tasks extracted | E006 | Source must contain at least 1 task |
|
||||
|
||||
## Post-Phase Update
|
||||
|
||||
After conversion completion:
|
||||
- Issue created/updated with `status: planned` and `bound_solution_id` set
|
||||
- Solution persisted in `.workflow/issues/solutions/{issue-id}.jsonl`
|
||||
- Report: issue ID, solution ID, task count, mode (new/supplement)
|
||||
- Recommend next step: Form execution queue via Phase 4
|
||||
391
.codex/skills/issue-resolve/phases/03-from-brainstorm.md
Normal file
391
.codex/skills/issue-resolve/phases/03-from-brainstorm.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# Phase 3: From Brainstorm
|
||||
|
||||
## Overview
|
||||
|
||||
Bridge command that converts **brainstorm-with-file** session output into executable **issue + solution** for parallel-dev-cycle consumption.
|
||||
|
||||
**Core workflow**: Load Session → Select Idea → Convert to Issue → Generate Solution → Bind & Ready
|
||||
|
||||
**Input sources**:
|
||||
- **synthesis.json** - Main brainstorm results with top_ideas
|
||||
- **perspectives.json** - Multi-CLI perspectives (creative/pragmatic/systematic)
|
||||
- **.brainstorming/** - Synthesis artifacts (clarifications, enhancements from role analyses)
|
||||
|
||||
**Output**:
|
||||
- **Issue** (ISS-YYYYMMDD-NNN) - Full context with clarifications
|
||||
- **Solution** (SOL-{issue-id}-{uid}) - Structured tasks for parallel-dev-cycle
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Brainstorm session ID or path (e.g., `SESSION="BS-rate-limiting-2025-01-28"`)
|
||||
- `synthesis.json` must exist in session directory
|
||||
- `ccw issue` CLI available
|
||||
|
||||
## Auto Mode
|
||||
|
||||
When `--yes` or `-y`: Auto-select highest-scored idea, skip confirmations, create issue directly.
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Required | Type | Default | Description |
|
||||
|----------|----------|------|---------|-------------|
|
||||
| SESSION | Yes | String | - | Session ID or path to `.workflow/.brainstorm/BS-xxx` |
|
||||
| --idea | No | Integer | - | Pre-select idea by index (0-based) |
|
||||
| --auto | No | Flag | false | Auto-select highest-scored idea |
|
||||
| -y, --yes | No | Flag | false | Skip all confirmations |
|
||||
|
||||
## Data Structures
|
||||
|
||||
### Issue Schema (Output)
|
||||
|
||||
```typescript
|
||||
interface Issue {
|
||||
id: string; // ISS-YYYYMMDD-NNN
|
||||
title: string; // From idea.title
|
||||
status: 'planned'; // Auto-set after solution binding
|
||||
priority: number; // 1-5 (derived from idea.score)
|
||||
context: string; // Full description with clarifications
|
||||
source: 'brainstorm';
|
||||
labels: string[]; // ['brainstorm', perspective, feasibility]
|
||||
|
||||
// Structured fields
|
||||
expected_behavior: string; // From key_strengths
|
||||
actual_behavior: string; // From main_challenges
|
||||
affected_components: string[]; // Extracted from description
|
||||
|
||||
_brainstorm_metadata: {
|
||||
session_id: string;
|
||||
idea_score: number;
|
||||
novelty: number;
|
||||
feasibility: string;
|
||||
clarifications_count: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Solution Schema (Output)
|
||||
|
||||
```typescript
|
||||
interface Solution {
|
||||
id: string; // SOL-{issue-id}-{4-char-uid}
|
||||
description: string; // idea.title
|
||||
approach: string; // idea.description
|
||||
tasks: Task[]; // Generated from idea.next_steps
|
||||
|
||||
analysis: {
|
||||
risk: 'low' | 'medium' | 'high';
|
||||
impact: 'low' | 'medium' | 'high';
|
||||
complexity: 'low' | 'medium' | 'high';
|
||||
};
|
||||
|
||||
is_bound: boolean; // true
|
||||
created_at: string;
|
||||
bound_at: string;
|
||||
}
|
||||
|
||||
interface Task {
|
||||
id: string; // T1, T2, T3...
|
||||
title: string; // Actionable task name
|
||||
scope: string; // design|implementation|testing|documentation
|
||||
action: string; // Implement|Design|Research|Test|Document
|
||||
description: string;
|
||||
|
||||
implementation: string[]; // Step-by-step guide
|
||||
acceptance: {
|
||||
criteria: string[]; // What defines success
|
||||
verification: string[]; // How to verify
|
||||
};
|
||||
|
||||
priority: number; // 1-5
|
||||
depends_on: string[]; // Task dependencies
|
||||
}
|
||||
```
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 3.1: Session Loading
|
||||
|
||||
```
|
||||
Phase 1: Session Loading
|
||||
├─ Validate session path
|
||||
├─ Load synthesis.json (required)
|
||||
├─ Load perspectives.json (optional - multi-CLI insights)
|
||||
├─ Load .brainstorming/** (optional - synthesis artifacts)
|
||||
└─ Validate top_ideas array exists
|
||||
```
|
||||
|
||||
### Step 3.2: Idea Selection
|
||||
|
||||
```
|
||||
Phase 2: Idea Selection
|
||||
├─ Auto mode: Select highest scored idea
|
||||
├─ Pre-selected: Use --idea=N index
|
||||
└─ Interactive: Display table, ask user to select
|
||||
```
|
||||
|
||||
### Step 3.3: Enrich Issue Context
|
||||
|
||||
```
|
||||
Phase 3: Enrich Issue Context
|
||||
├─ Base: idea.description + key_strengths + main_challenges
|
||||
├─ Add: Relevant clarifications (Requirements/Architecture/Feasibility)
|
||||
├─ Add: Multi-perspective insights (creative/pragmatic/systematic)
|
||||
└─ Add: Session metadata (session_id, completion date, clarification count)
|
||||
```
|
||||
|
||||
### Step 3.4: Create Issue
|
||||
|
||||
```
|
||||
Phase 4: Create Issue
|
||||
├─ Generate issue data with enriched context
|
||||
├─ Calculate priority from idea.score (0-10 → 1-5)
|
||||
├─ Create via: ccw issue create (heredoc for JSON)
|
||||
└─ Returns: ISS-YYYYMMDD-NNN
|
||||
```
|
||||
|
||||
### Step 3.5: Generate Solution Tasks
|
||||
|
||||
```
|
||||
Phase 5: Generate Solution Tasks
|
||||
├─ T1: Research & Validate (if main_challenges exist)
|
||||
├─ T2: Design & Specification (if key_strengths exist)
|
||||
├─ T3+: Implementation tasks (from idea.next_steps)
|
||||
└─ Each task includes: implementation steps + acceptance criteria
|
||||
```
|
||||
|
||||
### Step 3.6: Bind Solution
|
||||
|
||||
```
|
||||
Phase 6: Bind Solution
|
||||
├─ Write solution to .workflow/issues/solutions/{issue-id}.jsonl
|
||||
├─ Bind via: ccw issue bind {issue-id} {solution-id}
|
||||
├─ Update issue status to 'planned'
|
||||
└─ Returns: SOL-{issue-id}-{uid}
|
||||
```
|
||||
|
||||
### Step 3.7: Next Steps
|
||||
|
||||
```
|
||||
Phase 7: Next Steps
|
||||
└─ Offer: Form queue | Convert another idea | View details | Done
|
||||
```
|
||||
|
||||
## Context Enrichment Logic
|
||||
|
||||
### Base Context (Always Included)
|
||||
|
||||
- **Description**: `idea.description`
|
||||
- **Why This Idea**: `idea.key_strengths[]`
|
||||
- **Challenges to Address**: `idea.main_challenges[]`
|
||||
- **Implementation Steps**: `idea.next_steps[]`
|
||||
|
||||
### Enhanced Context (If Available)
|
||||
|
||||
**From Synthesis Artifacts** (`.brainstorming/*/analysis*.md`):
|
||||
- Extract clarifications matching categories: Requirements, Architecture, Feasibility
|
||||
- Format: `**{Category}** ({role}): {question} → {answer}`
|
||||
- Limit: Top 3 most relevant
|
||||
|
||||
**From Perspectives** (`perspectives.json`):
|
||||
- **Creative**: First insight from `perspectives.creative.insights[0]`
|
||||
- **Pragmatic**: First blocker from `perspectives.pragmatic.blockers[0]`
|
||||
- **Systematic**: First pattern from `perspectives.systematic.patterns[0]`
|
||||
|
||||
**Session Metadata**:
|
||||
- Session ID, Topic, Completion Date
|
||||
- Clarifications count (if synthesis artifacts loaded)
|
||||
|
||||
## Task Generation Strategy
|
||||
|
||||
### Task 1: Research & Validation
|
||||
**Trigger**: `idea.main_challenges.length > 0`
|
||||
- **Title**: "Research & Validate Approach"
|
||||
- **Scope**: design
|
||||
- **Action**: Research
|
||||
- **Implementation**: Investigate blockers, review similar implementations, validate with team
|
||||
- **Acceptance**: Blockers documented, feasibility assessed, approach validated
|
||||
|
||||
### Task 2: Design & Specification
|
||||
**Trigger**: `idea.key_strengths.length > 0`
|
||||
- **Title**: "Design & Create Specification"
|
||||
- **Scope**: design
|
||||
- **Action**: Design
|
||||
- **Implementation**: Create design doc, define success criteria, plan phases
|
||||
- **Acceptance**: Design complete, metrics defined, plan outlined
|
||||
|
||||
### Task 3+: Implementation Tasks
|
||||
**Trigger**: `idea.next_steps[]`
|
||||
- **Title**: From `next_steps[i]` (max 60 chars)
|
||||
- **Scope**: Inferred from keywords (test→testing, api→backend, ui→frontend)
|
||||
- **Action**: Detected from verbs (implement, create, update, fix, test, document)
|
||||
- **Implementation**: Execute step + follow design + write tests
|
||||
- **Acceptance**: Step implemented + tests passing + code reviewed
|
||||
|
||||
### Fallback Task
|
||||
**Trigger**: No tasks generated from above
|
||||
- **Title**: `idea.title`
|
||||
- **Scope**: implementation
|
||||
- **Action**: Implement
|
||||
- **Generic implementation + acceptance criteria**
|
||||
|
||||
## Priority Calculation
|
||||
|
||||
### Issue Priority (1-5)
|
||||
```
|
||||
idea.score: 0-10
|
||||
priority = max(1, min(5, ceil((10 - score) / 2)))
|
||||
|
||||
Examples:
|
||||
score 9-10 → priority 1 (critical)
|
||||
score 7-8 → priority 2 (high)
|
||||
score 5-6 → priority 3 (medium)
|
||||
score 3-4 → priority 4 (low)
|
||||
score 0-2 → priority 5 (lowest)
|
||||
```
|
||||
|
||||
### Task Priority (1-5)
|
||||
- Research task: 1 (highest)
|
||||
- Design task: 2
|
||||
- Implementation tasks: 3 by default, decrement for later tasks
|
||||
- Testing/documentation: 4-5
|
||||
|
||||
### Complexity Analysis
|
||||
```
|
||||
risk: main_challenges.length > 2 ? 'high' : 'medium'
|
||||
impact: score >= 8 ? 'high' : score >= 6 ? 'medium' : 'low'
|
||||
complexity: main_challenges > 3 OR tasks > 5 ? 'high'
|
||||
tasks > 3 ? 'medium' : 'low'
|
||||
```
|
||||
|
||||
## CLI Integration
|
||||
|
||||
### Issue Creation
|
||||
```bash
|
||||
# Uses heredoc to avoid shell escaping
|
||||
ccw issue create << 'EOF'
|
||||
{
|
||||
"title": "...",
|
||||
"context": "...",
|
||||
"priority": 3,
|
||||
"source": "brainstorm",
|
||||
"labels": ["brainstorm", "creative", "feasibility-high"],
|
||||
...
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Solution Binding
|
||||
```bash
|
||||
# Append solution to JSONL file
|
||||
echo '{"id":"SOL-xxx","tasks":[...]}' >> .workflow/issues/solutions/{issue-id}.jsonl
|
||||
|
||||
# Bind to issue
|
||||
ccw issue bind {issue-id} {solution-id}
|
||||
|
||||
# Update status
|
||||
ccw issue update {issue-id} --status planned
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Message | Resolution |
|
||||
|-------|---------|------------|
|
||||
| Session not found | synthesis.json missing | Check session ID, list available sessions |
|
||||
| No ideas | top_ideas array empty | Complete brainstorm workflow first |
|
||||
| Invalid idea index | Index out of range | Check valid range 0 to N-1 |
|
||||
| Issue creation failed | ccw issue create error | Verify CLI endpoint working |
|
||||
| Solution binding failed | Bind error | Check issue exists, retry |
|
||||
|
||||
## Examples
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
```bash
|
||||
codex -p "@.codex/prompts/issue-resolve.md --source brainstorm SESSION=\"BS-rate-limiting-2025-01-28\""
|
||||
|
||||
# Output:
|
||||
# | # | Title | Score | Feasibility |
|
||||
# |---|-------|-------|-------------|
|
||||
# | 0 | Token Bucket Algorithm | 8.5 | High |
|
||||
# | 1 | Sliding Window Counter | 7.2 | Medium |
|
||||
# | 2 | Fixed Window | 6.1 | High |
|
||||
|
||||
# User selects: #0
|
||||
|
||||
# Result:
|
||||
# Created issue: ISS-20250128-001
|
||||
# Created solution: SOL-ISS-20250128-001-ab3d
|
||||
# Bound solution to issue
|
||||
# → Next: /issue:queue
|
||||
```
|
||||
|
||||
### Auto Mode
|
||||
|
||||
```bash
|
||||
codex -p "@.codex/prompts/issue-resolve.md --source brainstorm SESSION=\"BS-caching-2025-01-28\" --auto"
|
||||
|
||||
# Result:
|
||||
# Auto-selected: Redis Cache Layer (Score: 9.2/10)
|
||||
# Created issue: ISS-20250128-002
|
||||
# Solution with 4 tasks
|
||||
# → Status: planned
|
||||
```
|
||||
|
||||
## Integration Flow
|
||||
|
||||
```
|
||||
brainstorm-with-file
|
||||
│
|
||||
├─ synthesis.json
|
||||
├─ perspectives.json
|
||||
└─ .brainstorming/** (optional)
|
||||
│
|
||||
▼
|
||||
Phase 3: From Brainstorm ◄─── This phase
|
||||
│
|
||||
├─ ISS-YYYYMMDD-NNN (enriched issue)
|
||||
└─ SOL-{issue-id}-{uid} (structured solution)
|
||||
│
|
||||
▼
|
||||
Phase 4: Form Queue
|
||||
│
|
||||
▼
|
||||
/issue:execute
|
||||
│
|
||||
▼
|
||||
RA → EP → CD → VAS
|
||||
```
|
||||
|
||||
## Session Files Reference
|
||||
|
||||
### Input Files
|
||||
|
||||
```
|
||||
.workflow/.brainstorm/BS-{slug}-{date}/
|
||||
├── synthesis.json # REQUIRED - Top ideas with scores
|
||||
├── perspectives.json # OPTIONAL - Multi-CLI insights
|
||||
├── brainstorm.md # Reference only
|
||||
└── .brainstorming/ # OPTIONAL - Synthesis artifacts
|
||||
├── system-architect/
|
||||
│ └── analysis.md # Contains clarifications + enhancements
|
||||
├── api-designer/
|
||||
│ └── analysis.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Output Files
|
||||
|
||||
```
|
||||
.workflow/issues/
|
||||
├── solutions/
|
||||
│ └── ISS-YYYYMMDD-001.jsonl # Created solution (JSONL)
|
||||
└── (managed by ccw issue CLI)
|
||||
```
|
||||
|
||||
## Post-Phase Update
|
||||
|
||||
After brainstorm conversion:
|
||||
- Issue created with `status: planned`, enriched context from brainstorm session
|
||||
- Solution bound with structured tasks derived from idea.next_steps
|
||||
- Report: issue ID, solution ID, task count, idea score
|
||||
- Recommend next step: Form execution queue via Phase 4
|
||||
453
.codex/skills/issue-resolve/phases/04-issue-queue.md
Normal file
453
.codex/skills/issue-resolve/phases/04-issue-queue.md
Normal file
@@ -0,0 +1,453 @@
|
||||
# Phase 4: Form Execution Queue
|
||||
|
||||
## Overview
|
||||
|
||||
Queue formation command using **issue-queue-agent** that analyzes all bound solutions, resolves **inter-solution** conflicts, and creates an ordered execution queue at **solution level**.
|
||||
|
||||
**Design Principle**: Queue items are **solutions**, not individual tasks. Each executor receives a complete solution with all its tasks.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Issues with `status: planned` and `bound_solution_id` exist
|
||||
- Solutions written in `.workflow/issues/solutions/{issue-id}.jsonl`
|
||||
- `ccw issue` CLI available
|
||||
|
||||
## Auto Mode
|
||||
|
||||
When `--yes` or `-y`: Auto-confirm queue formation, use recommended conflict resolutions.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
- **Agent-driven**: issue-queue-agent handles all ordering logic
|
||||
- **Solution-level granularity**: Queue items are solutions, not tasks
|
||||
- **Conflict clarification**: High-severity conflicts prompt user decision
|
||||
- Semantic priority calculation per solution (0.0-1.0)
|
||||
- Parallel/Sequential group assignment for solutions
|
||||
|
||||
## Core Guidelines
|
||||
|
||||
**Data Access Principle**: Issues and queue files can grow very large. To avoid context overflow:
|
||||
|
||||
| Operation | Correct | Incorrect |
|
||||
|-----------|---------|-----------|
|
||||
| List issues (brief) | `ccw issue list --status planned --brief` | `Read('issues.jsonl')` |
|
||||
| **Batch solutions (NEW)** | `ccw issue solutions --status planned --brief` | Loop `ccw issue solution <id>` |
|
||||
| List queue (brief) | `ccw issue queue --brief` | `Read('queues/*.json')` |
|
||||
| Read issue details | `ccw issue status <id> --json` | `Read('issues.jsonl')` |
|
||||
| Get next item | `ccw issue next --json` | `Read('queues/*.json')` |
|
||||
| Update status | `ccw issue update <id> --status ...` | Direct file edit |
|
||||
| Sync from queue | `ccw issue update --from-queue` | Direct file edit |
|
||||
| Read solution (single) | `ccw issue solution <id> --brief` | `Read('solutions/*.jsonl')` |
|
||||
|
||||
**Output Options**:
|
||||
- `--brief`: JSON with minimal fields (id, status, counts)
|
||||
- `--json`: Full JSON (agent use only)
|
||||
|
||||
**Orchestration vs Execution**:
|
||||
- **Command (orchestrator)**: Use `--brief` for minimal context
|
||||
- **Agent (executor)**: Fetch full details → `ccw issue status <id> --json`
|
||||
|
||||
**ALWAYS** use CLI commands for CRUD operations. **NEVER** read entire `issues.jsonl` or `queues/*.json` directly.
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--queues <n>` | Number of parallel queues | 1 |
|
||||
| `--issue <id>` | Form queue for specific issue only | All planned |
|
||||
| `--append <id>` | Append issue to active queue (don't create new) | - |
|
||||
| `--force` | Skip active queue check, always create new queue | false |
|
||||
|
||||
## CLI Subcommands Reference
|
||||
|
||||
```bash
|
||||
ccw issue queue list List all queues with status
|
||||
ccw issue queue add <issue-id> Add issue to queue (interactive if active queue exists)
|
||||
ccw issue queue add <issue-id> -f Add to new queue without prompt (force)
|
||||
ccw issue queue merge <src> --queue <target> Merge source queue into target queue
|
||||
ccw issue queue switch <queue-id> Switch active queue
|
||||
ccw issue queue archive Archive current queue
|
||||
ccw issue queue delete <queue-id> Delete queue from history
|
||||
```
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 4.1: Solution Loading & Distribution
|
||||
|
||||
**Data Loading:**
|
||||
- Use `ccw issue solutions --status planned --brief` to get all planned issues with solutions in **one call**
|
||||
- Returns: Array of `{ issue_id, solution_id, is_bound, task_count, files_touched[], priority }`
|
||||
- If no bound solutions found → display message, suggest running plan/convert/brainstorm first
|
||||
|
||||
**Build Solution Objects:**
|
||||
```javascript
|
||||
// Single CLI call replaces N individual queries
|
||||
const result = Bash(`ccw issue solutions --status planned --brief`).trim();
|
||||
const solutions = result ? JSON.parse(result) : [];
|
||||
|
||||
if (solutions.length === 0) {
|
||||
console.log('No bound solutions found. Run /issue:plan first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// solutions already in correct format:
|
||||
// { issue_id, solution_id, is_bound, task_count, files_touched[], priority }
|
||||
```
|
||||
|
||||
**Multi-Queue Distribution** (if `--queues > 1`):
|
||||
- Use `files_touched` from brief output for partitioning
|
||||
- Group solutions with overlapping files into same queue
|
||||
|
||||
**Output:** Array of solution objects (or N arrays if multi-queue)
|
||||
|
||||
### Step 4.2: Agent-Driven Queue Formation
|
||||
|
||||
**Generate Queue IDs** (command layer, pass to agent):
|
||||
```javascript
|
||||
const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
||||
const numQueues = args.queues || 1;
|
||||
const queueIds = numQueues === 1
|
||||
? [`QUE-${timestamp}`]
|
||||
: Array.from({length: numQueues}, (_, i) => `QUE-${timestamp}-${i + 1}`);
|
||||
```
|
||||
|
||||
**Agent Prompt** (same for each queue, with assigned solutions):
|
||||
```
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/issue-queue-agent.md (MUST read first)
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
---
|
||||
|
||||
## Order Solutions into Execution Queue
|
||||
|
||||
**Queue ID**: ${queueId}
|
||||
**Solutions**: ${solutions.length} from ${issues.length} issues
|
||||
**Project Root**: ${cwd}
|
||||
**Queue Index**: ${queueIndex} of ${numQueues}
|
||||
|
||||
### Input
|
||||
${JSON.stringify(solutions)}
|
||||
// Each object: { issue_id, solution_id, task_count, files_touched[], priority }
|
||||
|
||||
### Workflow
|
||||
|
||||
Step 1: Build dependency graph from solutions (nodes=solutions, edges=file conflicts via files_touched)
|
||||
Step 2: Use Gemini CLI for conflict analysis (5 types: file, API, data, dependency, architecture)
|
||||
Step 3: For high-severity conflicts without clear resolution → add to `clarifications`
|
||||
Step 4: Calculate semantic priority (base from issue priority + task_count boost)
|
||||
Step 5: Assign execution groups: P* (parallel, no overlaps) / S* (sequential, shared files)
|
||||
Step 6: Write queue JSON + update index
|
||||
|
||||
### Output Requirements
|
||||
|
||||
**Write files** (exactly 2):
|
||||
- `.workflow/issues/queues/${queueId}.json` - Full queue with solutions, conflicts, groups
|
||||
- `.workflow/issues/queues/index.json` - Update with new queue entry
|
||||
|
||||
**Return JSON**:
|
||||
\`\`\`json
|
||||
{
|
||||
"queue_id": "${queueId}",
|
||||
"total_solutions": N,
|
||||
"total_tasks": N,
|
||||
"execution_groups": [{"id": "P1", "type": "parallel", "count": N}],
|
||||
"issues_queued": ["ISS-xxx"],
|
||||
"clarifications": [{"conflict_id": "CFT-1", "question": "...", "options": [...]}]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
### Rules
|
||||
- Solution granularity (NOT individual tasks)
|
||||
- Queue Item ID format: S-1, S-2, S-3, ...
|
||||
- Use provided Queue ID (do NOT generate new)
|
||||
- `clarifications` only present if high-severity unresolved conflicts exist
|
||||
- Use `files_touched` from input (already extracted by orchestrator)
|
||||
|
||||
### Done Criteria
|
||||
- [ ] Queue JSON written with all solutions ordered
|
||||
- [ ] Index updated with active_queue_id
|
||||
- [ ] No circular dependencies
|
||||
- [ ] Parallel groups have no file overlaps
|
||||
- [ ] Return JSON matches required shape
|
||||
```
|
||||
|
||||
**Launch Agents** (parallel if multi-queue):
|
||||
```javascript
|
||||
const numQueues = args.queues || 1;
|
||||
|
||||
if (numQueues === 1) {
|
||||
// Single queue: single agent call
|
||||
const agentId = spawn_agent({
|
||||
message: buildPrompt(queueIds[0], solutions)
|
||||
});
|
||||
|
||||
// Wait for completion
|
||||
const result = wait({
|
||||
ids: [agentId],
|
||||
timeout_ms: 600000 // 10 minutes
|
||||
});
|
||||
|
||||
// Close agent
|
||||
close_agent({ id: agentId });
|
||||
} else {
|
||||
// Multi-queue: parallel agent calls
|
||||
const agentIds = [];
|
||||
|
||||
// Step 1: Spawn all agents
|
||||
solutionGroups.forEach((group, i) => {
|
||||
const agentId = spawn_agent({
|
||||
message: buildPrompt(queueIds[i], group, i + 1, numQueues)
|
||||
});
|
||||
agentIds.push(agentId);
|
||||
});
|
||||
|
||||
// Step 2: Batch wait for all agents
|
||||
const results = wait({
|
||||
ids: agentIds,
|
||||
timeout_ms: 600000 // 10 minutes
|
||||
});
|
||||
|
||||
if (results.timed_out) {
|
||||
console.log('Some queue agents timed out, continuing with completed results');
|
||||
}
|
||||
|
||||
// Step 3: Collect results (see Step 4.3 for clarification handling)
|
||||
|
||||
// Step 4: Batch cleanup - close all agents
|
||||
agentIds.forEach(id => close_agent({ id }));
|
||||
}
|
||||
```
|
||||
|
||||
**Multi-Queue Index Update:**
|
||||
- First queue sets `active_queue_id`
|
||||
- All queues added to `queues` array with `queue_group` field linking them
|
||||
|
||||
### Step 4.3: Conflict Clarification
|
||||
|
||||
**Collect Agent Results** (multi-queue):
|
||||
```javascript
|
||||
// Collect clarifications from all agents
|
||||
const allClarifications = [];
|
||||
agentIds.forEach((agentId, i) => {
|
||||
const agentStatus = results.status[agentId];
|
||||
if (agentStatus && agentStatus.completed) {
|
||||
const parsed = JSON.parse(agentStatus.completed);
|
||||
for (const c of parsed.clarifications || []) {
|
||||
allClarifications.push({ ...c, queue_id: queueIds[i], agent_id: agentId });
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Check Agent Return:**
|
||||
- Parse agent result JSON (or all results if multi-queue)
|
||||
- If any `clarifications` array exists and non-empty → user decision required
|
||||
|
||||
**Clarification Flow:**
|
||||
```javascript
|
||||
if (allClarifications.length > 0) {
|
||||
for (const clarification of allClarifications) {
|
||||
// Present to user via AskUserQuestion
|
||||
const answer = AskUserQuestion({
|
||||
questions: [{
|
||||
question: `[${clarification.queue_id}] ${clarification.question}`,
|
||||
header: clarification.conflict_id,
|
||||
options: clarification.options,
|
||||
multiSelect: false
|
||||
}]
|
||||
});
|
||||
|
||||
// Re-spawn agent with user decision (original agent already closed)
|
||||
// Create new agent with previous context + resolution
|
||||
const resolveAgentId = spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/issue-queue-agent.md (MUST read first)
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
---
|
||||
|
||||
## Conflict Resolution Update
|
||||
|
||||
**Queue ID**: ${clarification.queue_id}
|
||||
**Conflict**: ${clarification.conflict_id} resolved: ${answer.selected}
|
||||
|
||||
### Instructions
|
||||
1. Read existing queue file: .workflow/issues/queues/${clarification.queue_id}.json
|
||||
2. Update conflict resolution with user decision
|
||||
3. Re-order affected solutions if needed
|
||||
4. Write updated queue file
|
||||
`
|
||||
});
|
||||
|
||||
const resolveResult = wait({
|
||||
ids: [resolveAgentId],
|
||||
timeout_ms: 300000 // 5 minutes
|
||||
});
|
||||
|
||||
close_agent({ id: resolveAgentId });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4.4: Status Update & Summary
|
||||
|
||||
**Status Update** (MUST use CLI command, NOT direct file operations):
|
||||
|
||||
```bash
|
||||
# Option 1: Batch update from queue (recommended)
|
||||
ccw issue update --from-queue [queue-id] --json
|
||||
ccw issue update --from-queue --json # Use active queue
|
||||
ccw issue update --from-queue QUE-xxx --json # Use specific queue
|
||||
|
||||
# Option 2: Individual issue update
|
||||
ccw issue update <issue-id> --status queued
|
||||
```
|
||||
|
||||
**IMPORTANT**: Do NOT directly modify `issues.jsonl`. Always use CLI command to ensure proper validation and history tracking.
|
||||
|
||||
**Output** (JSON):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"queue_id": "QUE-xxx",
|
||||
"queued": ["ISS-001", "ISS-002"],
|
||||
"queued_count": 2,
|
||||
"unplanned": ["ISS-003"],
|
||||
"unplanned_count": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Updates issues in queue to `status: 'queued'` (skips already queued/executing/completed)
|
||||
- Identifies planned issues with `bound_solution_id` NOT in queue → `unplanned` array
|
||||
- Optional `queue-id`: defaults to active queue if omitted
|
||||
|
||||
**Summary Output:**
|
||||
- Display queue ID, solution count, task count
|
||||
- Show unplanned issues (planned but NOT in queue)
|
||||
- Show next step: `/issue:execute`
|
||||
|
||||
### Step 4.5: Active Queue Check & Decision
|
||||
|
||||
**After agent completes, check for active queue:**
|
||||
|
||||
```bash
|
||||
ccw issue queue list --brief
|
||||
```
|
||||
|
||||
**Decision:**
|
||||
- If `active_queue_id` is null → `ccw issue queue switch <new-queue-id>` (activate new queue)
|
||||
- If active queue exists → Use **AskUserQuestion** to prompt user
|
||||
|
||||
**AskUserQuestion:**
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "Active queue exists. How would you like to proceed?",
|
||||
header: "Queue Action",
|
||||
options: [
|
||||
{ label: "Merge into existing queue", description: "Add new items to active queue, delete new queue" },
|
||||
{ label: "Use new queue", description: "Switch to new queue, keep existing in history" },
|
||||
{ label: "Cancel", description: "Delete new queue, keep existing active" }
|
||||
],
|
||||
multiSelect: false
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
**Action Commands:**
|
||||
|
||||
| User Choice | Commands |
|
||||
|-------------|----------|
|
||||
| **Merge into existing** | `ccw issue queue merge <new-queue-id> --queue <active-queue-id>` then `ccw issue queue delete <new-queue-id>` |
|
||||
| **Use new queue** | `ccw issue queue switch <new-queue-id>` |
|
||||
| **Cancel** | `ccw issue queue delete <new-queue-id>` |
|
||||
|
||||
## Storage Structure (Queue History)
|
||||
|
||||
```
|
||||
.workflow/issues/
|
||||
├── issues.jsonl # All issues (one per line)
|
||||
├── queues/ # Queue history directory
|
||||
│ ├── index.json # Queue index (active + history)
|
||||
│ ├── {queue-id}.json # Individual queue files
|
||||
│ └── ...
|
||||
└── solutions/
|
||||
├── {issue-id}.jsonl # Solutions for issue
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Queue Index Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"active_queue_id": "QUE-20251227-143000",
|
||||
"active_queue_group": "QGR-20251227-143000",
|
||||
"queues": [
|
||||
{
|
||||
"id": "QUE-20251227-143000-1",
|
||||
"queue_group": "QGR-20251227-143000",
|
||||
"queue_index": 1,
|
||||
"total_queues": 3,
|
||||
"status": "active",
|
||||
"issue_ids": ["ISS-xxx", "ISS-yyy"],
|
||||
"total_solutions": 3,
|
||||
"completed_solutions": 1,
|
||||
"created_at": "2025-12-27T14:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Multi-Queue Fields:**
|
||||
- `queue_group`: Links queues created in same batch (format: `QGR-{timestamp}`)
|
||||
- `queue_index`: Position in group (1-based)
|
||||
- `total_queues`: Total queues in group
|
||||
- `active_queue_group`: Current active group (for multi-queue execution)
|
||||
|
||||
**Note**: Queue file schema is produced by `issue-queue-agent`. See agent documentation for details.
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Resolution |
|
||||
|-------|------------|
|
||||
| No bound solutions | Display message, suggest phases 1-3 (plan/convert/brainstorm) |
|
||||
| Circular dependency | List cycles, abort queue formation |
|
||||
| High-severity conflict | Return `clarifications`, prompt user decision |
|
||||
| User cancels clarification | Abort queue formation |
|
||||
| **index.json not updated** | Auto-fix: Set active_queue_id to new queue |
|
||||
| **Queue file missing solutions** | Abort with error, agent must regenerate |
|
||||
| **User cancels queue add** | Display message, return without changes |
|
||||
| **Merge with empty source** | Skip merge, display warning |
|
||||
| **All items duplicate** | Skip merge, display "All items already exist" |
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before completing, verify:
|
||||
|
||||
- [ ] All planned issues with `bound_solution_id` are included
|
||||
- [ ] Queue JSON written to `queues/{queue-id}.json` (N files if multi-queue)
|
||||
- [ ] Index updated in `queues/index.json` with `active_queue_id`
|
||||
- [ ] Multi-queue: All queues share same `queue_group`
|
||||
- [ ] No circular dependencies in solution DAG
|
||||
- [ ] All conflicts resolved (auto or via user clarification)
|
||||
- [ ] Parallel groups have no file overlaps
|
||||
- [ ] Cross-queue: No file overlaps between queues
|
||||
- [ ] Issue statuses updated to `queued`
|
||||
- [ ] All spawned agents are properly closed via close_agent
|
||||
|
||||
## Post-Phase Update
|
||||
|
||||
After queue formation:
|
||||
- All planned issues updated to `status: queued`
|
||||
- Queue files written and index updated
|
||||
- Report: queue ID(s), solution count, task count, execution groups
|
||||
- Recommend next step: `/issue:execute` to begin execution
|
||||
Reference in New Issue
Block a user