
Issue Discover
- 65 installs
- 2.1k repo stars
- Updated June 18, 2026
- catlog22/claude-code-workflow
Discover and categorize GitHub issues to understand project scope
About
Automatically discovers and analyzes GitHub issues to help teams understand project scope and prioritize work. Solo builders use this to quickly assess open issues and categorize them by type or urgency.
- Issue discovery
- Scope clarification
- Backlog analysis
Issue Discover by the numbers
- 65 all-time installs (skills.sh)
- Ranked #1,522 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/catlog22/claude-code-workflow --skill issue-discoverAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 2.1k |
| Last updated | June 18, 2026 |
| Repository | catlog22/claude-code-workflow ↗ |
What it does
Discover and categorize GitHub issues to understand project scope
Files
Issue Discover
Unified issue discovery and creation skill covering three entry points: manual issue creation, perspective-based discovery, and prompt-driven exploration.
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ Issue Discover Orchestrator (SKILL.md) │
│ → Action selection → Route to phase → Execute → Summary │
└───────────────┬─────────────────────────────────────────────────┘
│
├─ request_user_input: Select action
│
┌───────────┼───────────┬───────────┐
↓ ↓ ↓ │
┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ Phase 1 │ │ Phase 2 │ │ Phase 3 │ │
│ Create │ │Discover │ │Discover │ │
│ New │ │ Multi │ │by Prompt│ │
└─────────┘ └─────────┘ └─────────┘ │
↓ ↓ ↓ │
Issue Discoveries Discoveries │
(registered) (export) (export) │
│ │ │ │
│ ├───────────┤ │
│ ↓ │
│ ┌───────────┐ │
│ │ Phase 4 │ │
│ │Quick Plan │ │
│ │& Execute │ │
│ └─────┬─────┘ │
│ ↓ │
│ .task/*.json │
│ ↓ │
│ Direct Execution │
│ │ │
└───────────┴──────────────────────┘
↓ (fallback/remaining)
issue-resolve (plan/queue)
↓
/issue:executeKey Design Principles
1. Action-Driven Routing: request_user_input selects action, then load single phase 2. Progressive Phase Loading: Only read the selected phase document 3. CLI-First Data Access: All issue CRUD via ccw issue CLI commands 4. Auto Mode Support: -y flag skips action selection with auto-detection 5. Subagent Lifecycle: Explicit lifecycle management with spawn_agent → wait_agent → close_agent 6. Role Path Loading: Subagent roles loaded via path reference in MANDATORY FIRST STEPS
Auto Mode
When --yes or -y: Skip action selection, auto-detect action from input type.
Usage
issue-discover <input>
issue-discover [FLAGS] "<input>"
# Flags
-y, --yes Skip all confirmations (auto mode)
--action <type> Pre-select action: new|discover|discover-by-prompt
# Phase-specific flags
--priority <1-5> Issue priority (new mode)
--perspectives <list> Comma-separated perspectives (discover mode)
--external Enable Exa research (discover mode)
--scope <pattern> File scope (discover/discover-by-prompt mode)
--depth <level> standard|deep (discover-by-prompt mode)
--max-iterations <n> Max exploration iterations (discover-by-prompt mode)
# Examples
issue-discover https://github.com/org/repo/issues/42 # Create from GitHub
issue-discover "Login fails with special chars" # Create from text
issue-discover --action discover src/auth/** # Multi-perspective discovery
issue-discover --action discover src/api/** --perspectives=security,bug # Focused discovery
issue-discover --action discover-by-prompt "Check API contracts" # Prompt-driven discovery
issue-discover -y "auth broken" # Auto mode createExecution Flow
Input Parsing:
└─ Parse flags (--action, -y, --perspectives, etc.) and positional args
Action Selection:
├─ --action flag provided → Route directly
├─ Auto-detect from input:
│ ├─ GitHub URL or #number → Create New (Phase 1)
│ ├─ Path pattern (src/**, *.ts) → Discover (Phase 2)
│ ├─ Short text (< 80 chars) → Create New (Phase 1)
│ └─ Long descriptive text (≥ 80 chars) → Discover by Prompt (Phase 3)
└─ Otherwise → request_user_input to select action
└─ Initialize progress tracking: functions.update_plan([...phases])
Phase Execution (load one phase):
├─ Phase 1: Create New → phases/01-issue-new.md
├─ Phase 2: Discover → phases/02-discover.md
└─ Phase 3: Discover by Prompt → phases/03-discover-by-prompt.md
Post-Phase:
└─ Summary + Next steps recommendationPhase Reference Documents
| Phase | Document | Load When | Purpose |
|---|---|---|---|
| Phase 1 | phases/01-issue-new.md | Action = Create New | Create issue from GitHub URL or text description |
| Phase 2 | phases/02-discover.md | Action = Discover | Multi-perspective issue discovery (bug, security, test, etc.) |
| Phase 3 | phases/03-discover-by-prompt.md | Action = Discover by Prompt | Prompt-driven iterative exploration with Gemini planning |
| Phase 4 | phases/04-quick-execute.md | Post-Phase = Quick Plan & Execute | Convert high-confidence findings to tasks and execute directly |
Core Rules
1. Action Selection First: Always determine action before loading any phase 2. Single Phase Load: Only read the selected phase document, never load all phases 3. CLI Data Access: Use ccw issue CLI for all issue operations, NEVER read files directly 4. Content Preservation: Each phase contains complete execution logic from original commands 5. Auto-Detect Input: Smart input parsing reduces need for explicit --action flag 6. ⚠️ CRITICAL: DO NOT STOP: Continuous multi-phase workflow. After completing each phase, immediately proceed to next 7. Progressive Phase Loading: Read phase docs ONLY when that phase is about to execute 8. Explicit Lifecycle: Always close_agent after wait_agent completes to free resources
Input Processing
Auto-Detection Logic
function detectAction(input, flags) {
// 1. Explicit --action flag
if (flags.action) return flags.action;
const trimmed = input.trim();
// 2. GitHub URL → new
if (trimmed.match(/github\.com\/[\w-]+\/[\w-]+\/issues\/\d+/) || trimmed.match(/^#\d+$/)) {
return 'new';
}
// 3. Path pattern (contains **, /, or --perspectives) → discover
if (trimmed.match(/\*\*/) || trimmed.match(/^src\//) || flags.perspectives) {
return 'discover';
}
// 4. Short text (< 80 chars, no special patterns) → new
if (trimmed.length > 0 && trimmed.length < 80 && !trimmed.includes('--')) {
return 'new';
}
// 5. Long descriptive text → discover-by-prompt
if (trimmed.length >= 80) {
return 'discover-by-prompt';
}
// Cannot auto-detect → ask user
return null;
}Action Selection (request_user_input)
// When action cannot be auto-detected
const answer = functions.request_user_input({
questions: [{
header: "Action",
id: "action",
question: "What would you like to do?",
options: [
{
label: "Create New Issue (Recommended)",
description: "Create issue from GitHub URL, text description, or structured input"
},
{
label: "Discover Issues",
description: "Multi-perspective discovery: bug, security, test, quality, performance, etc."
},
{
label: "Discover by Prompt",
description: "Describe what to find — Gemini plans the exploration strategy iteratively"
}
]
}]
}); // BLOCKS (wait for user response)
// Route based on selection
// answer.answers.action.answers[0] → selected label
const actionMap = {
"Create New Issue (Recommended)": "new",
"Discover Issues": "discover",
"Discover by Prompt": "discover-by-prompt"
};
// Initialize progress tracking (MANDATORY)
functions.update_plan([
{ id: "action-select", title: "Action Selection", status: "completed" },
{ id: "phase-exec", title: `Phase: ${selectedAction}`, status: "in_progress" },
{ id: "post-phase", title: "Post-Phase: Next Steps", status: "pending" }
])Data Flow
User Input (URL / text / path pattern / descriptive prompt)
↓
[Parse Flags + Auto-Detect Action]
↓
[Action Selection] ← request_user_input (if needed)
↓
[Read Selected Phase Document]
↓
[Execute Phase Logic]
↓
[Summary + Next Steps]
├─ After Create → Suggest issue-resolve (plan solution)
└─ After Discover → Suggest export to issues, then issue-resolveSubagent API Reference
spawn_agent
Create a new subagent with task assignment.
const agentId = spawn_agent({
agent_type: "{agent_type}",
message: `
## TASK ASSIGNMENT
### MANDATORY FIRST STEPS (Agent Execute)
1. Execute: ccw spec load --category exploration
2. Execute: ccw spec load --category debug (known issues cross-reference)
## TASK CONTEXT
${taskContext}
## DELIVERABLES
${deliverables}
`
})wait_agent
Get results from subagent (only way to retrieve results).
const result = wait_agent({
timeout_ms: 1800000 // 30 minutes
})
if (result.timed_out) {
// Handle timeout via 4-step cascade: status probe → force finalize → close
}
// Check completion status
if (result.status[agentId].completed) {
const output = result.status[agentId].completed;
}followup_task
Assign new work to active subagent (for clarification or follow-up).
followup_task({
target: agentId,
message: `
## CLARIFICATION ANSWERS
${answers}
## NEXT STEP
Continue with plan generation.
`
})close_agent
Clean up subagent resources (irreversible).
close_agent({ target: agentId })Core Guidelines
Data Access Principle: Issues files can grow very large. To avoid context overflow:
| Operation | Correct | Incorrect |
|---|---|---|
| List issues (brief) | ccw issue list --status pending --brief | Read('issues.jsonl') |
| Read issue details | ccw issue status <id> --json | Read('issues.jsonl') |
| Create issue | `echo '...' \ | ccw issue create` |
| Update status | ccw issue update <id> --status ... | Direct file edit |
ALWAYS use CLI commands for CRUD operations. NEVER read entire issues.jsonl directly.
Error Handling
| Error | Resolution |
|---|---|
| No action detected | Show request_user_input with all 3 options |
| Invalid action type | Show available actions, re-prompt |
| Phase execution fails | Report error, suggest manual intervention |
| No files matched (discover) | Check target pattern, verify path exists |
| Gemini planning failed (discover-by-prompt) | Retry with qwen fallback |
| Agent lifecycle errors | Ensure close_agent in error paths to prevent resource leaks |
Post-Phase Next Steps
Progress: functions.update_plan([{id: "phase-exec", status: "completed"}, {id: "post-phase", status: "in_progress"}])
After successful phase execution, recommend next action:
// After Create New (issue created)
functions.request_user_input({
questions: [{
header: "Next Step",
id: "next_after_create",
question: "Issue created. What next?",
options: [
{ label: "Plan Solution (Recommended)", description: "Generate solution via issue-resolve" },
{ label: "Create Another", description: "Create more issues" },
{ label: "Done", description: "Exit workflow" }
]
}]
}); // BLOCKS (wait for user response)
// answer.answers.next_after_create.answers[0] → selected label
// After Discover / Discover by Prompt (discoveries generated)
functions.request_user_input({
questions: [{
header: "Next Step",
id: "next_after_discover",
question: `Discovery complete: ${findings.length} findings, ${executableFindings.length} executable. What next?`,
options: [
{ label: "Quick Plan & Execute (Recommended)", description: `Fix ${executableFindings.length} high-confidence findings directly` },
{ label: "Export to Issues", description: "Convert discoveries to issues" },
{ label: "Done", description: "Exit workflow" }
]
}]
}); // BLOCKS (wait for user response)
// answer.answers.next_after_discover.answers[0] → selected label
// If "Quick Plan & Execute (Recommended)" → Read phases/04-quick-execute.md, execute
// Mark workflow complete
functions.update_plan([{ id: "post-phase", status: "completed" }])Related Skills & Commands
issue-resolve- Plan solutions, convert artifacts, form queues, from brainstormissue-manage- Interactive issue CRUD operations/issue:execute- Execute queue with DAG-based parallel orchestrationccw issue list- List all issuesccw issue status <id>- View issue details
Phase 1: Create New Issue
来源: commands/issue/new.mdOverview
Create structured issue from GitHub URL or text description with clarity-based flow control.
Core workflow: Input Analysis → Clarity Detection → Data Extraction → Optional Clarification → GitHub Publishing → Create Issue
Input sources:
- GitHub URL -
https://github.com/owner/repo/issues/123or#123 - Structured text - Text with expected/actual/affects keywords
- Vague text - Short description that needs clarification
Output:
- Issue (GH-xxx or ISS-YYYYMMDD-HHMMSS) - Registered issue ready for planning
Prerequisites
ghCLI available (for GitHub URLs)ccw issueCLI available
Auto Mode
When --yes or -y: Skip clarification questions, create issue with inferred details.
Arguments
| Argument | Required | Type | Default | Description |
|---|---|---|---|---|
| input | Yes | String | - | GitHub URL, #number, or text description |
| --priority | No | Integer | auto | Priority 1-5 (auto-inferred if omitted) |
| -y, --yes | No | Flag | false | Skip all confirmations |
Issue Structure
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[];
// GitHub binding (for non-GitHub sources that publish to GitHub)
github_url?: string;
github_number?: number;
// Optional structured fields
expected_behavior?: string;
actual_behavior?: string;
affected_components?: string[];
// Feedback history
feedback?: {
type: 'failure' | 'clarification' | 'rejection';
stage: string;
content: string;
created_at: string;
}[];
bound_solution_id: string | null;
created_at: string;
updated_at: string;
}Execution Steps
Step 1.1: Input Analysis & Clarity Detection
const input = userInput.trim();
const flags = parseFlags(userInput);
// Detect input type and clarity
const isGitHubUrl = input.match(/github\.com\/[\w-]+\/[\w-]+\/issues\/\d+/);
const isGitHubShort = input.match(/^#(\d+)$/);
const hasStructure = input.match(/(expected|actual|affects|steps):/i);
// Clarity score: 0-3
let clarityScore = 0;
if (isGitHubUrl || isGitHubShort) clarityScore = 3; // GitHub = fully clear
else if (hasStructure) clarityScore = 2; // Structured text = clear
else if (input.length > 50) clarityScore = 1; // Long text = somewhat clear
else clarityScore = 0; // Vague
let issueData = {};Step 1.2: Data Extraction (GitHub or Text)
if (isGitHubUrl || isGitHubShort) {
// GitHub - fetch via gh CLI
const result = Bash(`gh issue view ${extractIssueRef(input)} --json number,title,body,labels,url`);
const gh = JSON.parse(result);
issueData = {
id: `GH-${gh.number}`,
title: gh.title,
source: 'github',
source_url: gh.url,
labels: gh.labels.map(l => l.name),
context: gh.body?.substring(0, 500) || gh.title,
...parseMarkdownBody(gh.body)
};
} else {
// Text description
issueData = {
id: `ISS-${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14)}`,
source: 'text',
...parseTextDescription(input)
};
}Step 1.3: Lightweight Context Hint (Conditional)
// ACE search ONLY for medium clarity (1-2) AND missing components
// Skip for: GitHub (has context), vague (needs clarification first)
if (clarityScore >= 1 && clarityScore <= 2 && !issueData.affected_components?.length) {
const keywords = extractKeywords(issueData.context);
if (keywords.length >= 2) {
try {
const aceResult = mcp__ace-tool__search_context({
project_root_path: process.cwd(),
query: keywords.slice(0, 3).join(' ')
});
issueData.affected_components = aceResult.files?.slice(0, 3) || [];
} catch {
// ACE failure is non-blocking
}
}
}Step 1.4: Conditional Clarification (Only if Unclear)
// ONLY ask questions if clarity is low
if (clarityScore < 2 && (!issueData.context || issueData.context.length < 20)) {
const answer = functions.request_user_input({
questions: [{
header: "Clarify",
id: "clarify",
question: "Please describe the issue in more detail.",
options: [
{ label: "Provide Details", description: "Describe what, where, and expected behavior" },
{ label: "Skip", description: "Create issue with current information" }
]
}]
}); // BLOCKS (wait for user response)
const selection = answer.answers.clarify.answers[0];
if (selection === "Provide Details") {
// User provides details via follow-up
issueData.context = selection;
issueData.title = selection.split(/[.\n]/)[0].substring(0, 60);
issueData.feedback = [{
type: 'clarification',
stage: 'new',
content: answer.customText,
created_at: new Date().toISOString()
}];
}
}Step 1.5: GitHub Publishing Decision (Non-GitHub Sources)
// For non-GitHub sources, ask if user wants to publish to GitHub
let publishToGitHub = false;
if (issueData.source !== 'github') {
// Yes → Create issue on GitHub and link it
// No → Store as local issue without GitHub sync
publishToGitHub = CONFIRM("Would you like to publish this issue to GitHub?"); // BLOCKS (wait for user response)
}Step 1.6: Create Issue
Issue Creation (via CLI endpoint):
# Option 1: Pipe input (recommended for complex JSON)
echo '{"title":"...", "context":"...", "priority":3}' | ccw issue create
# Option 2: Heredoc (for multi-line JSON)
ccw issue create << 'EOF'
{"title":"...", "context":"含\"引号\"的内容", "priority":3}
EOFGitHub Publishing (if user opted in):
// Step 1: Create local issue FIRST
const localIssue = createLocalIssue(issueData); // ccw issue create
// Step 2: Publish to GitHub if requested
if (publishToGitHub) {
const ghResult = Bash(`gh issue create --title "${issueData.title}" --body "${issueData.context}"`);
const ghUrl = ghResult.match(/https:\/\/github\.com\/[\w-]+\/[\w-]+\/issues\/\d+/)?.[0];
const ghNumber = parseInt(ghUrl?.match(/\/issues\/(\d+)/)?.[1]);
if (ghNumber) {
Bash(`ccw issue update ${localIssue.id} --github-url "${ghUrl}" --github-number ${ghNumber}`);
}
}Workflow:
1. Create local issue (ISS-YYYYMMDD-NNN) → stored in {projectRoot}/.workflow/issues.jsonl
2. If publishToGitHub:
a. gh issue create → returns GitHub URL
b. Update local issue with github_url + github_number binding
3. Both local and GitHub issues exist, linked togetherExecution Flow
Phase 1: Input Analysis
└─ Detect clarity score (GitHub URL? Structured text? Keywords?)
Phase 2: Data Extraction (branched by clarity)
┌────────────┬─────────────────┬──────────────┐
│ Score 3 │ Score 1-2 │ Score 0 │
│ GitHub │ Text + ACE │ Vague │
├────────────┼─────────────────┼──────────────┤
│ gh CLI │ Parse struct │ request_user_input │
│ → parse │ + quick hint │ (1 question) │
│ │ (3 files max) │ → feedback │
└────────────┴─────────────────┴──────────────┘
Phase 3: GitHub Publishing Decision (non-GitHub only)
├─ Source = github: Skip (already from GitHub)
└─ Source ≠ github: CONFIRM
├─ Yes → publishToGitHub = true
└─ No → publishToGitHub = false
Phase 4: Create Issue
├─ Score ≥ 2: Direct creation
└─ Score < 2: Confirm first → Create
└─ If publishToGitHub: gh issue create → link URL
Note: Deep exploration & lifecycle deferred to /issue:planHelper Functions
function extractKeywords(text) {
const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'was', 'were', 'not', 'with']);
return text
.toLowerCase()
.split(/\W+/)
.filter(w => w.length > 3 && !stopWords.has(w))
.slice(0, 5);
}
function parseTextDescription(text) {
const result = { title: '', context: '' };
const sentences = text.split(/\.(?=\s|$)/);
result.title = sentences[0]?.trim().substring(0, 60) || 'Untitled';
result.context = text.substring(0, 500);
const expected = text.match(/expected:?\s*([^.]+)/i);
const actual = text.match(/actual:?\s*([^.]+)/i);
const affects = text.match(/affects?:?\s*([^.]+)/i);
if (expected) result.expected_behavior = expected[1].trim();
if (actual) result.actual_behavior = actual[1].trim();
if (affects) {
result.affected_components = affects[1].split(/[,\s]+/).filter(c => c.includes('/') || c.includes('.'));
}
return result;
}
function parseMarkdownBody(body) {
if (!body) return {};
const result = {};
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.context = problem[2].trim().substring(0, 500);
if (expected) result.expected_behavior = expected[2].trim();
if (actual) result.actual_behavior = actual[2].trim();
return result;
}Error Handling
| Error | Message | Resolution |
|---|---|---|
| GitHub fetch failed | gh CLI error | Check gh auth, verify URL |
| Clarity too low | Input unclear | Ask clarification question |
| Issue creation failed | CLI error | Verify ccw issue endpoint |
| GitHub publish failed | gh issue create error | Create local-only, skip GitHub |
Examples
Clear Input (No Questions)
issue-discover https://github.com/org/repo/issues/42
# → Fetches, parses, creates immediately
issue-discover "Login fails with special chars. Expected: success. Actual: 500"
# → Parses structure, creates immediatelyVague Input (1 Question)
issue-discover "auth broken"
# → Asks: "Please describe the issue in more detail"
# → User provides details → saved to feedback[]
# → Creates issuePost-Phase Update
After issue creation:
- Issue created with
status: registered - Report: issue ID, title, source, affected components
- Show GitHub URL (if published)
- Recommend next step:
/issue:plan <id>orissue-resolve <id>
Phase 2: Discover Issues (Multi-Perspective)
来源: commands/issue/discover.mdOverview
Multi-perspective issue discovery orchestrator that explores code from different angles to identify potential bugs, UX improvements, test gaps, and other actionable items.
Core workflow: Initialize → Select Perspectives → Parallel Analysis → Aggregate → Generate Issues → User Action
Discovery Scope: Specified modules/files only Output Directory: {projectRoot}/.workflow/issues/discoveries/{discovery-id}/ Available Perspectives: bug, ux, test, quality, security, performance, maintainability, best-practices Exa Integration: Auto-enabled for security and best-practices perspectives CLI Tools: Gemini → Qwen → Codex (fallback chain)
Prerequisites
- Target file/module pattern (e.g.,
src/auth/**) ccw issueCLI available
Auto Mode
When --yes or -y: Auto-select all perspectives, skip confirmations.
Arguments
| Argument | Required | Type | Default | Description |
|---|---|---|---|---|
| target | Yes | String | - | File/module glob pattern (e.g., src/auth/**) |
| --perspectives | No | String | interactive | Comma-separated: bug,ux,test,quality,security,performance,maintainability,best-practices |
| --external | No | Flag | false | Enable Exa research for all perspectives |
| -y, --yes | No | Flag | false | Skip all confirmations |
Perspectives
| Perspective | Focus | Categories | Exa |
|---|---|---|---|
| bug | Potential Bugs | edge-case, null-check, resource-leak, race-condition, boundary, exception-handling | - |
| ux | User Experience | error-message, loading-state, feedback, accessibility, interaction, consistency | - |
| test | Test Coverage | missing-test, edge-case-test, integration-gap, coverage-hole, assertion-quality | - |
| quality | Code Quality | complexity, duplication, naming, documentation, code-smell, readability | - |
| security | Security Issues | injection, auth, encryption, input-validation, data-exposure, access-control | ✓ |
| performance | Performance | n-plus-one, memory-usage, caching, algorithm, blocking-operation, resource | - |
| maintainability | Maintainability | coupling, cohesion, tech-debt, extensibility, module-boundary, interface-design | - |
| best-practices | Best Practices | convention, pattern, framework-usage, anti-pattern, industry-standard | ✓ |
Execution Steps
Step 2.1: Discovery & Initialization
// Parse target pattern and resolve files
const resolvedFiles = await expandGlobPattern(targetPattern);
if (resolvedFiles.length === 0) {
throw new Error(`No files matched pattern: ${targetPattern}`);
}
// Generate discovery ID
const discoveryId = `DSC-${formatDate(new Date(), 'YYYYMMDD-HHmmss')}`;
// Create output directory
const outputDir = `${projectRoot}/.workflow/issues/discoveries/${discoveryId}`;
await mkdir(outputDir, { recursive: true });
await mkdir(`${outputDir}/perspectives`, { recursive: true });
// Initialize unified discovery state
await writeJson(`${outputDir}/discovery-state.json`, {
discovery_id: discoveryId,
target_pattern: targetPattern,
phase: "initialization",
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
target: { files_count: { total: resolvedFiles.length }, project: {} },
perspectives: [],
external_research: { enabled: false, completed: false },
results: { total_findings: 0, issues_generated: 0, priority_distribution: {} }
});Step 2.2: Interactive Perspective Selection
let selectedPerspectives = [];
if (args.perspectives) {
selectedPerspectives = args.perspectives.split(',').map(p => p.trim());
} else {
// Interactive selection via request_user_input
const response = functions.request_user_input({
questions: [{
header: "Focus",
id: "focus",
question: "Select primary discovery focus.",
options: [
{ label: "Bug + Test + Quality (Recommended)", description: "Quick scan: potential bugs, test gaps, code quality" },
{ label: "Security + Performance", description: "System audit: security issues, performance bottlenecks" },
{ label: "Full analysis", description: "All 8 perspectives (comprehensive, takes longer)" }
]
}]
}); // BLOCKS (wait for user response)
// response.answers.focus.answers[0] → selected label
selectedPerspectives = parseSelectedPerspectives(response);
}Step 2.3: Parallel Perspective Analysis
Launch N agents in parallel (one per selected perspective):
// Step 1: Spawn agents for each perspective (parallel creation)
const perspectiveAgents = [];
selectedPerspectives.forEach(perspective => {
const agentId = spawn_agent({
agent_type: "cli_explore_agent",
message: `
## TASK ASSIGNMENT
### MANDATORY FIRST STEPS (Agent Execute)
1. Read: {projectRoot}/.workflow/project-tech.json
2. Read: {projectRoot}/.workflow/specs/*.md
---
## Task Objective
Discover potential ${perspective} issues in specified module files.
## Discovery Context
- Discovery ID: ${discoveryId}
- Perspective: ${perspective}
- Target Pattern: ${targetPattern}
- Resolved Files: ${resolvedFiles.length} files
- Output Directory: ${outputDir}
## MANDATORY FIRST STEPS
1. Read discovery state: ${outputDir}/discovery-state.json
2. Read schema: ~/.ccw/workflows/cli-templates/schemas/discovery-finding-schema.json
3. Analyze target files for ${perspective} concerns
## Output Requirements
**1. Write JSON file**: ${outputDir}/perspectives/${perspective}.json
- Follow discovery-finding-schema.json exactly
- Each finding: id, title, priority, category, description, file, line, snippet, suggested_issue, confidence
**2. Return summary** (DO NOT write report file):
- Total findings, priority breakdown, key issues
## Perspective-Specific Guidance
${getPerspectiveGuidance(perspective)}
## Success Criteria
- [ ] JSON written to ${outputDir}/perspectives/${perspective}.json
- [ ] Summary returned with findings count and key issues
- [ ] Each finding includes actionable suggested_issue
- [ ] Priority uses lowercase enum: critical/high/medium/low
`
});
perspectiveAgents.push({ agentId, perspective });
});
// Step 2: Batch wait for all agents
const agentIds = perspectiveAgents.map(a => a.agentId);
const results = wait_agent({
timeout_ms: 1800000 // 30 minutes
});
// Step 3: Check for timeouts (4-step cascade)
if (results.timed_out) {
console.log('Some perspective analyses timed out, attempting status probe...');
// Status probe for timed-out agents
agentIds.forEach(id => {
if (!results.status[id]?.completed) {
followup_task({ target: id, message: "STATUS_CHECK: Report current progress, findings so far, and estimated remaining work." });
}
});
const statusResults = wait_agent({ timeout_ms: 180000 }); // 3 min
if (statusResults.timed_out) {
// Force finalize remaining agents
agentIds.forEach(id => {
if (!statusResults.status[id]?.completed) {
followup_task({ target: id, message: "FINALIZE: Output all current findings immediately. Time limit reached.", interrupt: true });
}
});
const forcedResults = wait_agent({ timeout_ms: 180000 }); // 3 min
if (forcedResults.timed_out) {
console.log('Some agents still timed out after force finalize, continuing with completed results');
}
}
}
// Step 4: Collect results
const completedResults = {};
perspectiveAgents.forEach(({ agentId, perspective }) => {
if (results.status[agentId].completed) {
completedResults[perspective] = results.status[agentId].completed;
}
});
// Step 5: Close all agents
agentIds.forEach(id => close_agent({ target: id }));Exa Research Agent (for security and best-practices)
// Only spawn if perspective requires external research
if (selectedPerspectives.includes('security') || selectedPerspectives.includes('best-practices') || args.external) {
const exaAgentId = spawn_agent({
agent_type: "cli_explore_agent",
message: `
## TASK ASSIGNMENT
### MANDATORY FIRST STEPS (Agent Execute)
1. Read: {projectRoot}/.workflow/project-tech.json
2. Read: {projectRoot}/.workflow/specs/*.md
---
## Task Objective
Research industry best practices for ${perspective} using Exa search
## Research Steps
1. Read project tech stack: {projectRoot}/.workflow/project-tech.json
2. Use Exa to search for best practices
3. Synthesize findings relevant to this project
## Output Requirements
**1. Write JSON file**: ${outputDir}/external-research.json
**2. Return summary** (DO NOT write report file)
## Success Criteria
- [ ] JSON written to ${outputDir}/external-research.json
- [ ] Findings are relevant to project's tech stack
`
});
const exaResult = wait_agent({
timeout_ms: 1800000 // 30 minutes
});
close_agent({ target: exaAgentId });
}Step 2.4: Aggregation & Prioritization
// Load all perspective JSON files written by agents
const allFindings = [];
for (const perspective of selectedPerspectives) {
const jsonPath = `${outputDir}/perspectives/${perspective}.json`;
if (await fileExists(jsonPath)) {
const data = await readJson(jsonPath);
allFindings.push(...data.findings.map(f => ({ ...f, perspective })));
}
}
// Deduplicate and prioritize
const prioritizedFindings = deduplicateAndPrioritize(allFindings);Step 2.5: Issue Generation & Summary
// Convert high-priority findings to issues
const issueWorthy = prioritizedFindings.filter(f =>
f.priority === 'critical' || f.priority === 'high' || f.priority_score >= 0.7
);
// Write discovery-issues.jsonl
await writeJsonl(`${outputDir}/discovery-issues.jsonl`, issues);
// Generate summary from agent returns
await writeSummaryFromAgentReturns(outputDir, completedResults, prioritizedFindings, issues);
// Update final state
await updateDiscoveryState(outputDir, {
phase: 'complete',
updated_at: new Date().toISOString(),
'results.issues_generated': issues.length
});Step 2.6: User Action Prompt
const hasHighPriority = issues.some(i => i.priority === 'critical' || i.priority === 'high');
await functions.request_user_input({
questions: [{
header: "Next Step",
id: "next_step",
question: `Discovery complete: ${issues.length} issues generated, ${prioritizedFindings.length} total findings. What next?`,
options: hasHighPriority ? [
{ label: "Export to Issues (Recommended)", description: `${issues.length} high-priority issues found - export to tracker` },
{ label: "Open Dashboard", description: "Review findings in ccw view before exporting" },
{ label: "Skip", description: "Complete discovery without exporting" }
] : [
{ label: "Open Dashboard (Recommended)", description: "Review findings in ccw view to decide which to export" },
{ label: "Export to Issues", description: `Export ${issues.length} issues to tracker` },
{ label: "Skip", description: "Complete discovery without exporting" }
]
}]
}); // BLOCKS (wait for user response)
// response.answers.next_step.answers[0] → selected label
if (response === "Export to Issues") {
await appendJsonl(`${projectRoot}/.workflow/issues/issues.jsonl`, issues);
}Perspective Guidance Reference
function getPerspectiveGuidance(perspective) {
const guidance = {
bug: `Focus: Null checks, edge cases, resource leaks, race conditions, boundary conditions, exception handling
Priority: Critical=data corruption/crash, High=malfunction, Medium=edge case issues, Low=minor`,
ux: `Focus: Error messages, loading states, feedback, accessibility, interaction patterns, form validation
Priority: Critical=inaccessible, High=confusing, Medium=inconsistent, Low=cosmetic`,
test: `Focus: Missing unit tests, edge case coverage, integration gaps, assertion quality, test isolation
Priority: Critical=no security tests, High=no core logic tests, Medium=weak coverage, Low=minor gaps`,
quality: `Focus: Complexity, duplication, naming, documentation, code smells, readability
Priority: Critical=unmaintainable, High=significant issues, Medium=naming/docs, Low=minor refactoring`,
security: `Focus: Input validation, auth/authz, injection, XSS/CSRF, data exposure, access control
Priority: Critical=auth bypass/injection, High=missing authz, Medium=weak validation, Low=headers`,
performance: `Focus: N+1 queries, memory leaks, caching, algorithm efficiency, blocking operations
Priority: Critical=memory leaks, High=N+1/inefficient, Medium=missing cache, Low=minor optimization`,
maintainability: `Focus: Coupling, interface design, tech debt, extensibility, module boundaries, configuration
Priority: Critical=unrelated code changes, High=unclear boundaries, Medium=coupling, Low=refactoring`,
'best-practices': `Focus: Framework conventions, language patterns, anti-patterns, deprecated APIs, coding standards
Priority: Critical=anti-patterns causing bugs, High=convention violations, Medium=style, Low=cosmetic`
};
return guidance[perspective] || 'General code discovery analysis';
}Output File Structure
{projectRoot}/.workflow/issues/discoveries/
├── index.json # Discovery session index
└── {discovery-id}/
├── discovery-state.json # Unified state
├── perspectives/
│ └── {perspective}.json # Per-perspective findings
├── external-research.json # Exa research results (if enabled)
├── discovery-issues.jsonl # Generated candidate issues
└── summary.md # Summary from agent returnsSchema References
| Schema | Path | Purpose |
|---|---|---|
| Discovery State | ~/.ccw/workflows/cli-templates/schemas/discovery-state-schema.json | Session state machine |
| Discovery Finding | ~/.ccw/workflows/cli-templates/schemas/discovery-finding-schema.json | Perspective analysis results |
Error Handling
| Error | Message | Resolution |
|---|---|---|
| No files matched | Pattern empty | Check target pattern, verify path exists |
| Agent failure | Perspective analysis error | Retry failed perspective, check agent logs |
| No findings | All perspectives clean | Report clean status, no issues to generate |
| Agent lifecycle error | Resource leak | Ensure close_agent in error paths |
Examples
# Quick scan with default perspectives
issue-discover --action discover src/auth/**
# Security-focused audit
issue-discover --action discover src/payment/** --perspectives=security,bug
# Full analysis with external research
issue-discover --action discover src/api/** --externalPost-Phase Update
After discovery:
- Findings aggregated with priority distribution
- Issue candidates written to discovery-issues.jsonl
- Report: total findings, issues generated, priority breakdown
- Recommend next step: Export to issues →
/issue:planorissue-resolve
Phase 3: Discover by Prompt
来源: commands/issue/discover-by-prompt.mdOverview
Prompt-driven issue discovery with intelligent planning. Instead of fixed perspectives, this command analyzes user intent via Gemini, plans exploration strategy dynamically, and executes iterative multi-agent exploration with ACE semantic search.
Core workflow: Prompt Analysis → ACE Context → Gemini Planning → Iterative Exploration → Cross-Analysis → Issue Generation
Core Difference from Phase 2 (Discover):
- Phase 2: Pre-defined perspectives (bug, security, etc.), parallel execution
- Phase 3: User-driven prompt, Gemini-planned strategy, iterative exploration
Prerequisites
- User prompt describing what to discover
ccw cliavailable (for Gemini planning)ccw issueCLI available
Auto Mode
When --yes or -y: Auto-continue all iterations, skip confirmations.
Arguments
| Argument | Required | Type | Default | Description |
|---|---|---|---|---|
| prompt | Yes | String | - | Natural language description of what to find |
| --scope | No | String | **/* | File pattern to explore |
| --depth | No | String | standard | standard (3 iterations) or deep (5+ iterations) |
| --max-iterations | No | Integer | 5 | Maximum exploration iterations |
| --plan-only | No | Flag | false | Stop after Gemini planning, show plan |
| -y, --yes | No | Flag | false | Skip all confirmations |
Use Cases
| Scenario | Example Prompt |
|---|---|
| API Contract | "Check if frontend calls match backend endpoints" |
| Error Handling | "Find inconsistent error handling patterns" |
| Migration Gap | "Compare old auth with new auth implementation" |
| Feature Parity | "Verify mobile has all web features" |
| Schema Drift | "Check if TypeScript types match API responses" |
| Integration | "Find mismatches between service A and service B" |
Execution Steps
Step 3.1: Prompt Analysis & Initialization
// Parse arguments
const { prompt, scope, depth, maxIterations } = parseArgs(args);
// Generate discovery ID
const discoveryId = `DBP-${formatDate(new Date(), 'YYYYMMDD-HHmmss')}`;
// Create output directory
const outputDir = `${projectRoot}/.workflow/issues/discoveries/${discoveryId}`;
await mkdir(outputDir, { recursive: true });
await mkdir(`${outputDir}/iterations`, { recursive: true });
// Detect intent type from prompt
const intentType = detectIntent(prompt);
// Returns: 'comparison' | 'search' | 'verification' | 'audit'
// Initialize discovery state
await writeJson(`${outputDir}/discovery-state.json`, {
discovery_id: discoveryId,
type: 'prompt-driven',
prompt: prompt,
intent_type: intentType,
scope: scope || '**/*',
depth: depth || 'standard',
max_iterations: maxIterations || 5,
phase: 'initialization',
created_at: new Date().toISOString(),
iterations: [],
cumulative_findings: [],
comparison_matrix: null
});Step 3.2: ACE Context Gathering
// Extract keywords from prompt for semantic search
const keywords = extractKeywords(prompt);
// Use ACE to understand codebase structure
const aceQueries = [
`Project architecture and module structure for ${keywords.join(', ')}`,
`Where are ${keywords[0]} implementations located?`,
`How does ${keywords.slice(0, 2).join(' ')} work in this codebase?`
];
const aceResults = [];
for (const query of aceQueries) {
const result = await mcp__ace-tool__search_context({
project_root_path: process.cwd(),
query: query
});
aceResults.push({ query, result });
}
// Build context package for Gemini (kept in memory)
const aceContext = {
prompt_keywords: keywords,
codebase_structure: aceResults[0].result,
relevant_modules: aceResults.slice(1).map(r => r.result),
detected_patterns: extractPatterns(aceResults)
};ACE Query Strategy by Intent Type:
| Intent | ACE Queries |
|---|---|
| comparison | "frontend API calls", "backend API handlers", "API contract definitions" |
| search | "{keyword} implementations", "{keyword} usage patterns" |
| verification | "expected behavior for {feature}", "test coverage for {feature}" |
| audit | "all {category} patterns", "{category} security concerns" |
Step 3.3: Gemini Strategy Planning
// Build Gemini planning prompt with ACE context
const planningPrompt = `
PURPOSE: Analyze discovery prompt and create exploration strategy based on codebase context
TASK:
• Parse user intent from prompt: "${prompt}"
• Use codebase context to identify specific modules and files to explore
• Create exploration dimensions with precise search targets
• Define comparison matrix structure (if comparison intent)
• Set success criteria and iteration strategy
MODE: analysis
CONTEXT: @${scope || '**/*'} | Discovery type: ${intentType}
## Codebase Context (from ACE semantic search)
${JSON.stringify(aceContext, null, 2)}
EXPECTED: JSON exploration plan:
{
"intent_analysis": { "type": "${intentType}", "primary_question": "...", "sub_questions": [...] },
"dimensions": [{ "name": "...", "description": "...", "search_targets": [...], "focus_areas": [...], "agent_prompt": "..." }],
"comparison_matrix": { "dimension_a": "...", "dimension_b": "...", "comparison_points": [...] },
"success_criteria": [...],
"estimated_iterations": N,
"termination_conditions": [...]
}
CONSTRAINTS: Use ACE context to inform targets | Focus on actionable plan
`;
// Execute Gemini planning
Bash({
command: `ccw cli -p "${planningPrompt}" --tool gemini --mode analysis`,
run_in_background: true,
timeout: 300000
});
// Parse and validate
const explorationPlan = await parseGeminiPlanOutput(geminiResult);Gemini Planning Output Schema:
{
"intent_analysis": {
"type": "comparison|search|verification|audit",
"primary_question": "string",
"sub_questions": ["string"]
},
"dimensions": [
{
"name": "frontend",
"description": "Client-side API calls and error handling",
"search_targets": ["src/api/**", "src/hooks/**"],
"focus_areas": ["fetch calls", "error boundaries", "response parsing"],
"agent_prompt": "Explore frontend API consumption patterns..."
}
],
"comparison_matrix": {
"dimension_a": "frontend",
"dimension_b": "backend",
"comparison_points": [
{"aspect": "endpoints", "frontend_check": "fetch URLs", "backend_check": "route paths"},
{"aspect": "methods", "frontend_check": "HTTP methods used", "backend_check": "methods accepted"},
{"aspect": "payloads", "frontend_check": "request body structure", "backend_check": "expected schema"},
{"aspect": "responses", "frontend_check": "response parsing", "backend_check": "response format"},
{"aspect": "errors", "frontend_check": "error handling", "backend_check": "error responses"}
]
},
"success_criteria": ["All API endpoints mapped", "Discrepancies identified with file:line"],
"estimated_iterations": 3,
"termination_conditions": ["All comparison points verified", "Confidence > 0.8"]
}Step 3.4: Iterative Agent Exploration (with ACE)
let iteration = 0;
let cumulativeFindings = [];
let sharedContext = { aceDiscoveries: [], crossReferences: [] };
let shouldContinue = true;
while (shouldContinue && iteration < maxIterations) {
iteration++;
const iterationDir = `${outputDir}/iterations/${iteration}`;
await mkdir(iterationDir, { recursive: true });
// ACE-assisted iteration planning
const iterationAceQueries = iteration === 1
? explorationPlan.dimensions.map(d => d.focus_areas[0])
: deriveQueriesFromFindings(cumulativeFindings);
const iterationAceResults = [];
for (const query of iterationAceQueries) {
const result = await mcp__ace-tool__search_context({
project_root_path: process.cwd(),
query: `${query} in ${explorationPlan.scope}`
});
iterationAceResults.push({ query, result });
}
sharedContext.aceDiscoveries.push(...iterationAceResults);
// Plan this iteration
const iterationPlan = planIteration(iteration, explorationPlan, cumulativeFindings, iterationAceResults);
// Step 1: Spawn dimension agents (parallel creation)
const dimensionAgents = [];
iterationPlan.dimensions.forEach(dimension => {
const agentId = spawn_agent({
agent_type: "cli_explore_agent",
message: buildDimensionPromptWithACE(dimension, iteration, cumulativeFindings, iterationAceResults, iterationDir)
});
dimensionAgents.push({ agentId, dimension });
});
// Step 2: Batch wait for all dimension agents
const dimensionAgentIds = dimensionAgents.map(a => a.agentId);
const iterationResults = wait_agent({
timeout_ms: 1800000 // 30 minutes
});
// Step 3: Check for timeouts (4-step cascade)
if (iterationResults.timed_out) {
console.log(`Iteration ${iteration}: some agents timed out, attempting status probe...`);
// Status probe for timed-out agents
dimensionAgentIds.forEach(id => {
if (!iterationResults.status[id]?.completed) {
followup_task({ target: id, message: "STATUS_CHECK: Report current progress, findings so far, and estimated remaining work." });
}
});
const statusResults = wait_agent({ timeout_ms: 180000 }); // 3 min
if (statusResults.timed_out) {
// Force finalize remaining agents
dimensionAgentIds.forEach(id => {
if (!statusResults.status[id]?.completed) {
followup_task({ target: id, message: "FINALIZE: Output all current findings immediately. Time limit reached.", interrupt: true });
}
});
const forcedResults = wait_agent({ timeout_ms: 180000 }); // 3 min
if (forcedResults.timed_out) {
console.log(`Iteration ${iteration}: some agents still timed out after force finalize, using completed results`);
}
}
}
// Step 4: Close all dimension agents
dimensionAgentIds.forEach(id => close_agent({ target: id }));
// Collect and analyze iteration findings
const iterationFindings = await collectIterationFindings(iterationDir, iterationPlan.dimensions);
// Cross-reference findings between dimensions
if (iterationPlan.dimensions.length > 1) {
const crossRefs = findCrossReferences(iterationFindings, iterationPlan.dimensions);
sharedContext.crossReferences.push(...crossRefs);
}
cumulativeFindings.push(...iterationFindings);
// Decide whether to continue
const convergenceCheck = checkConvergence(iterationFindings, cumulativeFindings, explorationPlan);
shouldContinue = !convergenceCheck.converged;
// Update state
await updateDiscoveryState(outputDir, {
iterations: [...state.iterations, {
number: iteration,
findings_count: iterationFindings.length,
ace_queries: iterationAceQueries.length,
cross_references: sharedContext.crossReferences.length,
new_discoveries: convergenceCheck.newDiscoveries,
confidence: convergenceCheck.confidence,
continued: shouldContinue
}],
cumulative_findings: cumulativeFindings
});
}Iteration Loop:
┌─────────────────────────────────────────────────────────────┐
│ Iteration Loop │
├─────────────────────────────────────────────────────────────┤
│ 1. Plan: What to explore this iteration │
│ └─ Based on: previous findings + unexplored areas │
│ │
│ 2. Execute: Spawn agents for this iteration │
│ └─ Each agent: explore → collect → return summary │
│ └─ Lifecycle: spawn_agent → batch wait_agent → close_agent │
│ │
│ 3. Analyze: Process iteration results │
│ └─ New findings? Gaps? Contradictions? │
│ │
│ 4. Decide: Continue or terminate │
│ └─ Terminate if: max iterations OR convergence OR │
│ high confidence on all questions │
└─────────────────────────────────────────────────────────────┘Step 3.5: Cross-Analysis & Synthesis
// For comparison intent, perform cross-analysis
if (intentType === 'comparison' && explorationPlan.comparison_matrix) {
const comparisonResults = [];
for (const point of explorationPlan.comparison_matrix.comparison_points) {
const dimensionAFindings = cumulativeFindings.filter(f =>
f.related_dimension === explorationPlan.comparison_matrix.dimension_a &&
f.category.includes(point.aspect)
);
const dimensionBFindings = cumulativeFindings.filter(f =>
f.related_dimension === explorationPlan.comparison_matrix.dimension_b &&
f.category.includes(point.aspect)
);
const discrepancies = findDiscrepancies(dimensionAFindings, dimensionBFindings, point);
comparisonResults.push({
aspect: point.aspect,
dimension_a_count: dimensionAFindings.length,
dimension_b_count: dimensionBFindings.length,
discrepancies: discrepancies,
match_rate: calculateMatchRate(dimensionAFindings, dimensionBFindings)
});
}
await writeJson(`${outputDir}/comparison-analysis.json`, {
matrix: explorationPlan.comparison_matrix,
results: comparisonResults,
summary: {
total_discrepancies: comparisonResults.reduce((sum, r) => sum + r.discrepancies.length, 0),
overall_match_rate: average(comparisonResults.map(r => r.match_rate)),
critical_mismatches: comparisonResults.filter(r => r.match_rate < 0.5)
}
});
}
const prioritizedFindings = prioritizeFindings(cumulativeFindings, explorationPlan);Step 3.6: Issue Generation & Summary
// Convert high-confidence findings to issues
const issueWorthy = prioritizedFindings.filter(f =>
f.confidence >= 0.7 || f.priority === 'critical' || f.priority === 'high'
);
const issues = issueWorthy.map(finding => ({
id: `ISS-${discoveryId}-${finding.id}`,
title: finding.title,
description: finding.description,
source: { discovery_id: discoveryId, finding_id: finding.id, dimension: finding.related_dimension },
file: finding.file,
line: finding.line,
priority: finding.priority,
category: finding.category,
confidence: finding.confidence,
status: 'discovered',
created_at: new Date().toISOString()
}));
await writeJsonl(`${outputDir}/discovery-issues.jsonl`, issues);
// Update final state
await updateDiscoveryState(outputDir, {
phase: 'complete',
updated_at: new Date().toISOString(),
results: {
total_iterations: iteration,
total_findings: cumulativeFindings.length,
issues_generated: issues.length,
comparison_match_rate: comparisonResults
? average(comparisonResults.map(r => r.match_rate))
: null
}
});
// Prompt user for next action
await functions.request_user_input({
questions: [{
header: "Next Step",
id: "next_step",
question: `Discovery complete: ${issues.length} issues from ${cumulativeFindings.length} findings across ${iteration} iterations. What next?`,
options: [
{ label: "Export to Issues (Recommended)", description: `Export ${issues.length} issues for planning` },
{ label: "Review Details", description: "View comparison analysis and iteration details" },
{ label: "Run Deeper", description: "Continue with more iterations" }
]
}]
}); // BLOCKS (wait for user response)
// answer.answers.next_step.answers[0] → selected labelDimension Agent Prompt Template
function buildDimensionPromptWithACE(dimension, iteration, previousFindings, aceResults, outputDir) {
const relevantAceResults = aceResults.filter(r =>
r.query.includes(dimension.name) || dimension.focus_areas.some(fa => r.query.includes(fa))
);
return `
## TASK ASSIGNMENT (agent_type: cli_explore_agent)
### MANDATORY FIRST STEPS (Agent Execute)
1. Read: {projectRoot}/.workflow/project-tech.json
2. Read: {projectRoot}/.workflow/specs/*.md
---
## Task Objective
Explore ${dimension.name} dimension for issue discovery (Iteration ${iteration})
## Context
- Dimension: ${dimension.name}
- Description: ${dimension.description}
- Search Targets: ${dimension.search_targets.join(', ')}
- Focus Areas: ${dimension.focus_areas.join(', ')}
## ACE Semantic Search Results (Pre-gathered)
${JSON.stringify(relevantAceResults.map(r => ({ query: r.query, files: r.result.slice(0, 5) })), null, 2)}
**Use ACE for deeper exploration**: mcp__ace-tool__search_context available.
${iteration > 1 ? `
## Previous Findings to Build Upon
${summarizePreviousFindings(previousFindings, dimension.name)}
## This Iteration Focus
- Explore areas not yet covered
- Verify/deepen previous findings
- Follow leads from previous discoveries
` : ''}
## MANDATORY FIRST STEPS
1. Read schema: ~/.ccw/workflows/cli-templates/schemas/discovery-finding-schema.json
2. Review ACE results above for starting points
3. Explore files identified by ACE
## Exploration Instructions
${dimension.agent_prompt}
## Output Requirements
**1. Write JSON file**: ${outputDir}/${dimension.name}.json
- findings: [{id, title, category, description, file, line, snippet, confidence, related_dimension}]
- coverage: {files_explored, areas_covered, areas_remaining}
- leads: [{description, suggested_search}]
- ace_queries_used: [{query, result_count}]
**2. Return summary**: Total findings, key discoveries, recommended next areas
`;
}Output File Structure
{projectRoot}/.workflow/issues/discoveries/
└── {DBP-YYYYMMDD-HHmmss}/
├── discovery-state.json # Session state with iteration tracking
├── iterations/
│ ├── 1/
│ │ └── {dimension}.json # Dimension findings
│ ├── 2/
│ │ └── {dimension}.json
│ └── ...
├── comparison-analysis.json # Cross-dimension comparison (if applicable)
└── discovery-issues.jsonl # Generated issue candidatesConfiguration Options
| Flag | Default | Description |
|---|---|---|
--scope | **/* | File pattern to explore |
--depth | standard | standard (3 iterations) or deep (5+ iterations) |
--max-iterations | 5 | Maximum exploration iterations |
--tool | gemini | Planning tool (gemini/qwen) |
--plan-only | false | Stop after Gemini planning, show plan |
Schema References
| Schema | Path | Used By |
|---|---|---|
| Discovery State | discovery-state-schema.json | Orchestrator (state tracking) |
| Discovery Finding | discovery-finding-schema.json | Dimension agents (output) |
| Exploration Plan | exploration-plan-schema.json | Gemini output validation (memory only) |
Error Handling
| Error | Message | Resolution |
|---|---|---|
| Gemini planning failed | CLI error | Retry with qwen fallback |
| ACE search failed | No results | Fall back to file glob patterns |
| No findings after iterations | Convergence at 0 | Report clean status |
| Agent timeout | Exploration too large | Narrow scope, reduce iterations |
| Agent lifecycle error | Resource leak | Ensure close_agent in error paths |
Examples
# Single module deep dive
issue-discover --action discover-by-prompt "Find all potential issues in auth" --scope=src/auth/**
# API contract comparison
issue-discover --action discover-by-prompt "Check if API calls match implementations" --scope=src/**
# Plan only mode
issue-discover --action discover-by-prompt "Find inconsistent patterns" --plan-onlyPost-Phase Update
After prompt-driven discovery:
- Findings aggregated across iterations with confidence scores
- Comparison analysis generated (if comparison intent)
- Issue candidates written to discovery-issues.jsonl
- Report: total iterations, findings, issues, match rate
- Recommend next step: Export → issue-resolve (plan solutions)
Phase 4: Quick Plan & Execute
来源: 分析会话 ANL-issue-discover规划执行能力-2026-02-11Overview
直接将高置信度 discovery findings 转换为 .task/*.json 并内联执行。 跳过 issue 注册和完整规划流程,适用于明确可修复的问题。
Core workflow: Load Findings → Filter → Convert to Tasks → Pre-Execution → User Confirmation → Execute → Finalize Trigger: Phase 2/3 完成后,用户选择 "Quick Plan & Execute" Output Directory: 继承 discovery session 的 {outputDir} Filter: confidence ≥ 0.7 AND priority ∈ {critical, high}
Prerequisites
- Phase 2 (Discover) 或 Phase 3 (Discover by Prompt) 已完成
{outputDir}下存在 discovery 输出 (perspectives/*.json 或 discovery-issues.jsonl)
Auto Mode
When --yes or -y: 自动过滤 → 自动生成任务 → 自动确认执行 → 失败自动跳过 → 自动 Done。
Execution Steps
Step 4.1: Load & Filter Findings
加载优先级 (按顺序尝试):
1. perspectives/*.json — Phase 2 多视角发现 (每个文件含 findings[])
2. discovery-issues.jsonl — Phase 2/3 聚合输出 (每行一个 JSON finding)
3. iterations/*.json — Phase 3 迭代输出 (每个文件含 findings[])
→ 如果全部为空: 报错 "No discoveries found. Run discover first." 并退出过滤规则:
executableFindings = allFindings.filter(f =>
(f.confidence || 0) >= 0.7 &&
['critical', 'high'].includes(f.priority)
)- 如果 0 个可执行 findings → 提示 "No executable findings (all below threshold)",建议用户走 "Export to Issues" 路径
- 如果超过 10 个 findings → request_user_input 确认是否全部执行或选择子集 (Auto mode: 全部执行)
同文件聚合:
按 finding.file 聚合:
- 同文件 1 个 finding → 生成 1 个独立 task
- 同文件 2+ findings → 合并为 1 个 task (mergeFindingsToTask)Step 4.2: Generate .task/*.json
对每个 filtered finding (或 file group),生成 task-schema.json 格式的任务文件。
单 Finding 转换 (convertFindingToTask)
Finding 字段 → Task-Schema 字段 → 转换逻辑
─────────────────────────────────────────────────────────────
id (dsc-bug-001-...) → id (TASK-001) → 重新编号: TASK-{sequential:3}
title → title → 直接使用
description+impact+rec → description → 拼接: "{description}\n\nImpact: {impact}\nRecommendation: {recommendation}"
(无) → depends_on → 默认 []
(推导) → convergence → 按 perspective/category 模板推导 (见下表)
suggested_issue.type → type → 映射: bug→fix, feature→feature, enhancement→enhancement, refactor→refactor, test→testing
priority → priority → 直接使用 (已匹配 enum)
file + line → files[] → [{path: file, action: "modify", changes: [recommendation], target: "line:{line}"}]
snippet + file:line → evidence[] → ["{file}:{line}", snippet]
recommendation → implementation[] → [recommendation]
(固定) → source → {tool: "issue-discover", session_id: discoveryId, original_id: finding.id}Type 映射:
suggested_issue.type → task type:
bug → fix, feature → feature, enhancement → enhancement,
refactor → refactor, test → testing, docs → enhancement
perspective fallback (无 suggested_issue.type 时):
bug/security → fix, test → testing, quality/maintainability/best-practices → refactor,
performance/ux → enhancementEffort 推导:
critical priority → large
high priority → medium
其他 → small合并 Finding 转换 (mergeFindingsToTask)
同文件 2+ findings 合并为一个 task:
1. 按 priority 排序: critical > high > medium > low
2. 取最高优先级 finding 的 priority 作为 task priority
3. 取最高优先级 finding 的 type 作为 task type
4. title: "Fix {findings.length} issues in {basename(file)}"
5. description: 按 finding 编号逐条列出 (### Finding N: title + description + impact + recommendation + line)
6. convergence.criteria: 每个 finding 独立生成 criterion
7. verification: 选择最严格的验证命令 (jest > eslint > tsc > Manual)
8. definition_of_done: "修复 {file} 中的 {N} 个问题: {categories.join(', ')}"
9. effort: 1个=原始, 2个=medium, 3+=large
10. source.original_id: findings.map(f => f.id).join(',')Convergence 模板 (按 perspective/category 推导)
| Perspective | criteria 模板 | verification | definition_of_done |
|---|---|---|---|
| bug | "修复 {file}:{line} 的 {category} 问题", "相关模块测试通过" | npx tsc --noEmit | "消除 {impact} 风险" |
| security | "修复 {file} 的 {category} 漏洞", "安全检查通过" | npx eslint {file} --rule 'security/*' | "消除 {impact} 安全风险" |
| test | "新增测试覆盖 {file}:{line} 场景", "新增测试通过" | npx jest --testPathPattern={testFile} | "提升 {file} 模块的测试覆盖" |
| quality | "重构 {file}:{line} 降低 {category}", "lint 检查通过" | npx eslint {file} | "改善代码 {category}" |
| performance | "优化 {file}:{line} 的 {category} 问题", "无性能回退" | npx tsc --noEmit | "改善 {impact} 的性能表现" |
| maintainability | "重构 {file}:{line} 改善 {category}", "构建通过" | npx tsc --noEmit | "降低模块间的 {category}" |
| ux | "改善 {file}:{line} 的 {category}", "界面测试验证" | Manual: 检查 UI 行为 | "改善用户感知的 {category}" |
| best-practices | "修正 {file}:{line} 的 {category}", "lint 通过" | npx eslint {file} | "符合 {category} 最佳实践" |
低置信度处理: confidence < 0.8 的 findings,verification 前缀 Manual:
输出: 写入 {outputDir}/.task/TASK-{seq}.json,验证 convergence 非空且非 vague。
Step 4.3: Pre-Execution Analysis
Reference: analyze-with-file/EXECUTE.md Step 2-3
复用 EXECUTE.md 的 Pre-Execution 逻辑:
1. 依赖检测: 检查 depends_on 引用是否存在 2. 循环检测: 无环 → 拓扑排序确定执行顺序 3. 文件冲突分析: 检查多个 tasks 是否修改同一文件 (同文件已聚合,此处检测跨 task 冲突) 4. 生成 execution.md: 任务列表、执行顺序、冲突报告 5. 生成 execution-events.md: 空事件日志,后续记录执行过程
Step 4.4: User Confirmation
展示任务概要:
Quick Execute Summary:
- Total findings: {allFindings.length}
- Executable (filtered): {executableFindings.length}
- Tasks generated: {tasks.length}
- File conflicts: {conflicts.length}request_user_input:
functions.request_user_input({
questions: [{
header: "Confirm",
id: "confirm_execute",
question: `${tasks.length} tasks ready. Start execution?`,
options: [
{ label: "Start Execution (Recommended)", description: "Execute all tasks" },
{ label: "Adjust Filter", description: "Change confidence/priority threshold" },
{ label: "Cancel", description: "Skip execution, return to post-phase options" }
]
}]
});
// answer.answers.confirm_execute.answers[0] → selected label
// Auto mode: Start Execution- "Adjust Filter" → 重新 request_user_input 输入 confidence 和 priority 阈值,返回 Step 4.1
- "Cancel" → 退出 Phase 4
Step 4.5: Direct Inline Execution
Reference: analyze-with-file/EXECUTE.md Step 5
逐任务执行 (按拓扑排序):
for each task in sortedTasks:
1. Read target file(s)
2. Analyze current state vs task.description
3. Apply changes (Edit/Write)
4. Verify convergence:
- Execute task.convergence.verification command
- Check criteria fulfillment
5. Record event to execution-events.md:
- TASK_START → TASK_COMPLETE / TASK_FAILED
6. Update .task/TASK-{id}.json _execution status
7. If failed:
- Auto mode: Skip & Continue
- Interactive: request_user_input → Retry / Skip / Abort可选 auto-commit: 每个成功 task 后 git add {files} && git commit -m "fix: {task.title}"
Step 4.6: Finalize
Reference: analyze-with-file/EXECUTE.md Step 6-7
1. 更新 execution.md: 执行统计 (成功/失败/跳过) 2. *更新 .task/.json: `_execution.status` = completed/failed/skipped 3. Post-Execute 选项**:
// 计算未执行 findings
const remainingFindings = allFindings.filter(f => !executedFindingIds.has(f.id))
functions.request_user_input({
questions: [{
header: "Post Execute",
id: "post_quick_execute",
question: `Quick Execute: ${completedCount}/${tasks.length} succeeded. ${remainingFindings.length} findings not executed.`,
options: [
{ label: "Done (Recommended)", description: "End workflow" },
{ label: "Retry Failed", description: `Re-execute ${failedCount} failed tasks` },
{ label: "Export Remaining", description: `Export ${remainingFindings.length} remaining findings to issues` }
]
}]
});
// answer.answers.post_quick_execute.answers[0] → selected label
// Auto mode: Done"Export Remaining" 逻辑: 将未执行的 findings 通过现有 Phase 2/3 的 "Export to Issues" 流程注册为 issues,进入 issue-resolve 完整管道。
Edge Cases
| 边界情况 | 处理策略 |
|---|---|
| 0 个可执行 findings | 提示 "No executable findings",建议 Export to Issues |
| 只有 1 个 finding | 正常生成 1 个 TASK-001.json,简化确认对话 |
| 超过 10 个 findings | request_user_input 确认全部执行或选择子集 |
| finding 缺少 recommendation | criteria 退化为 "Review and fix {category} in {file}:{line}" |
| finding 缺少 confidence | 默认 confidence=0.5,不满足过滤阈值 → 排除 |
| discovery 输出不存在 | 报错 "No discoveries found. Run discover first." |
| .task/ 目录已存在 | request_user_input 追加 (TASK-{max+1}) 或覆盖 |
| 执行中文件被外部修改 | convergence verification 检测到差异,标记为 FAIL |
| 所有 tasks 执行失败 | 建议 "Export to Issues → issue-resolve" 完整路径 |
| finding 来自不同 perspective 但同文件 | 仍合并为一个 task,convergence.criteria 保留各自标准 |