mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
从子目录执行 skill 时,相对路径 .workflow/ 会导致产物落到错误位置。
通过 git rev-parse --show-toplevel || pwd 检测项目根目录,
所有 .workflow/ 路径引用统一加上 {projectRoot} 前缀确保路径正确。
涉及 72 个文件,覆盖 20+ 个 skill。
10 KiB
10 KiB
Phase 1: Create New Issue
来源:
commands/issue/new.md
Overview
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 = ASK_USER([{
id: "clarify",
type: "input",
prompt: "Please describe the issue in more detail:",
description: "Describe what, where, and expected behavior"
}]); // BLOCKS (wait for user response)
if (answer.customText) {
issueData.context = answer.customText;
issueData.title = answer.customText.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}
EOF
GitHub 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 together
Execution 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 │ ASK_USER │
│ → 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:plan
Helper 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 immediately
Vague Input (1 Question)
issue-discover "auth broken"
# → Asks: "Please describe the issue in more detail"
# → User provides details → saved to feedback[]
# → Creates issue
Post-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>