mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-11 17:21:03 +08:00
- Implement tests for AssociationHighlight, DashboardToolbar, QueuePanel, SessionGroupTree, and TerminalDashboardPage to ensure proper functionality and state management. - Create tests for cliSessionStore, issueQueueIntegrationStore, queueExecutionStore, queueSchedulerStore, sessionManagerStore, and terminalGridStore to validate state resets and workspace scoping. - Mock necessary dependencies and state management hooks to isolate tests and ensure accurate behavior.
10 KiB
10 KiB
Phase 7: Issue Export
Map specification Epics to issues, create them via ccw issue create, and generate an export report with spec document links.
Execution Mode: Inline This phase runs in the main orchestrator context (not delegated to agent) for direct access to
ccw issue createCLI and interactive handoff options.
Objective
- Read all EPIC-*.md files from Phase 5 output
- Assign waves: MVP epics → wave-1, non-MVP → wave-2
- Create one issue per Epic via
ccw issue create - Map Epic dependencies to issue dependencies
- Generate issue-export-report.md with mapping table and spec links
- Present handoff options for execution
Input
- Dependency:
{workDir}/epics/_index.md(and individualEPIC-*.mdfiles) - Reference:
{workDir}/readiness-report.md,{workDir}/spec-config.json - Reference:
{workDir}/product-brief.md,{workDir}/requirements/_index.md,{workDir}/architecture/_index.md
Execution Steps
Step 1: Load Epic Files
const specConfig = JSON.parse(Read(`${workDir}/spec-config.json`));
const epicFiles = Glob(`${workDir}/epics/EPIC-*.md`);
const epicsIndex = Read(`${workDir}/epics/_index.md`);
// Parse each Epic file
const epics = epicFiles.map(epicFile => {
const content = Read(epicFile);
const fm = parseFrontmatter(content);
const title = extractTitle(content);
const description = extractSection(content, "Description");
const stories = extractSection(content, "Stories");
const reqRefs = extractSection(content, "Requirements");
const adrRefs = extractSection(content, "Architecture");
const deps = fm.dependencies || [];
return {
file: epicFile,
id: fm.id, // e.g., EPIC-001
title,
description,
stories,
reqRefs,
adrRefs,
priority: fm.priority,
mvp: fm.mvp || false,
dependencies: deps, // other EPIC IDs this depends on
size: fm.size
};
});
Step 2: Wave Assignment
const epicWaves = epics.map(epic => ({
...epic,
wave: epic.mvp ? 1 : 2
}));
// Log wave assignment
const wave1 = epicWaves.filter(e => e.wave === 1);
const wave2 = epicWaves.filter(e => e.wave === 2);
// wave-1: MVP epics (must-have, core functionality)
// wave-2: Post-MVP epics (should-have, enhancements)
Step 3: Issue Creation Loop
const createdIssues = [];
const epicToIssue = {}; // EPIC-ID -> Issue ID mapping
for (const epic of epicWaves) {
// Build issue JSON matching roadmap-with-file schema
const issueData = {
title: `[${specConfig.session_id}] ${epic.title}`,
status: "pending",
priority: epic.wave === 1 ? 2 : 3, // wave-1 = higher priority
context: `## ${epic.title}
${epic.description}
## Stories
${epic.stories}
## Spec References
- Epic: ${epic.file}
- Requirements: ${epic.reqRefs}
- Architecture: ${epic.adrRefs}
- Product Brief: ${workDir}/product-brief.md
- Full Spec: ${workDir}/`,
source: "text",
tags: [
"spec-generated",
`spec:${specConfig.session_id}`,
`wave-${epic.wave}`,
epic.mvp ? "mvp" : "post-mvp",
`epic:${epic.id}`
],
extended_context: {
notes: {
session: specConfig.session_id,
spec_dir: workDir,
source_epic: epic.id,
wave: epic.wave,
depends_on_issues: [], // Filled in Step 4
spec_documents: {
product_brief: `${workDir}/product-brief.md`,
requirements: `${workDir}/requirements/_index.md`,
architecture: `${workDir}/architecture/_index.md`,
epic: epic.file
}
}
},
lifecycle_requirements: {
test_strategy: "acceptance",
regression_scope: "affected",
acceptance_type: "manual",
commit_strategy: "per-epic"
}
};
// Create issue via ccw issue create (pipe JSON to avoid shell escaping)
const result = Bash(`echo '${JSON.stringify(issueData)}' | ccw issue create`);
// Parse returned issue ID
const issueId = JSON.parse(result).id; // e.g., ISS-20260308-001
epicToIssue[epic.id] = issueId;
createdIssues.push({
epic_id: epic.id,
epic_title: epic.title,
issue_id: issueId,
wave: epic.wave,
priority: issueData.priority,
mvp: epic.mvp
});
}
Step 4: Epic Dependency → Issue Dependency Mapping
// Map EPIC dependencies to Issue dependencies
for (const epic of epicWaves) {
if (epic.dependencies.length === 0) continue;
const issueId = epicToIssue[epic.id];
const depIssueIds = epic.dependencies
.map(depEpicId => epicToIssue[depEpicId])
.filter(Boolean);
if (depIssueIds.length > 0) {
// Update issue's extended_context.notes.depends_on_issues
// This is informational — actual dependency enforcement is in execution phase
// Note: ccw issue create already created the issue; dependency info is in the context
}
}
Step 5: Generate issue-export-report.md
const timestamp = new Date().toISOString();
const reportContent = `---
session_id: ${specConfig.session_id}
phase: 7
document_type: issue-export-report
status: complete
generated_at: ${timestamp}
stepsCompleted: ["load-epics", "wave-assignment", "issue-creation", "dependency-mapping", "report-generation"]
version: 1
dependencies:
- epics/_index.md
- readiness-report.md
---
# Issue Export Report
## Summary
- **Session**: ${specConfig.session_id}
- **Issues Created**: ${createdIssues.length}
- **Wave 1 (MVP)**: ${wave1.length} issues
- **Wave 2 (Post-MVP)**: ${wave2.length} issues
- **Export Date**: ${timestamp}
## Issue Mapping
| Epic ID | Epic Title | Issue ID | Wave | Priority | MVP |
|---------|-----------|----------|------|----------|-----|
${createdIssues.map(i =>
`| ${i.epic_id} | ${i.epic_title} | ${i.issue_id} | ${i.wave} | ${i.priority} | ${i.mvp ? 'Yes' : 'No'} |`
).join('\n')}
## Spec Document Links
| Document | Path | Description |
|----------|------|-------------|
| Product Brief | ${workDir}/product-brief.md | Vision, goals, scope |
| Requirements | ${workDir}/requirements/_index.md | Functional + non-functional requirements |
| Architecture | ${workDir}/architecture/_index.md | Components, ADRs, tech stack |
| Epics | ${workDir}/epics/_index.md | Epic/Story breakdown |
| Readiness Report | ${workDir}/readiness-report.md | Quality validation |
| Spec Summary | ${workDir}/spec-summary.md | Executive summary |
## Dependency Map
| Issue ID | Depends On |
|----------|-----------|
${createdIssues.map(i => {
const epic = epicWaves.find(e => e.id === i.epic_id);
const deps = (epic.dependencies || []).map(d => epicToIssue[d]).filter(Boolean);
return `| ${i.issue_id} | ${deps.length > 0 ? deps.join(', ') : 'None'} |`;
}).join('\n')}
## Next Steps
1. **team-planex**: Execute all issues via coordinated team workflow
2. **Wave 1 only**: Execute MVP issues first (${wave1.length} issues)
3. **View issues**: Browse created issues via \`ccw issue list --tag spec:${specConfig.session_id}\`
4. **Manual review**: Review individual issues before execution
`;
Write(`${workDir}/issue-export-report.md`, reportContent);
Step 6: Update spec-config.json
specConfig.issue_ids = createdIssues.map(i => i.issue_id);
specConfig.issues_created = createdIssues.length;
specConfig.phasesCompleted.push({
phase: 7,
name: "issue-export",
output_file: "issue-export-report.md",
issues_created: createdIssues.length,
wave_1_count: wave1.length,
wave_2_count: wave2.length,
completed_at: timestamp
});
Write(`${workDir}/spec-config.json`, JSON.stringify(specConfig, null, 2));
Step 7: Handoff Options
AskUserQuestion({
questions: [
{
question: `${createdIssues.length} issues created from ${epicWaves.length} Epics. What would you like to do next?`,
header: "Next Step",
multiSelect: false,
options: [
{
label: "Execute via team-planex",
description: `Execute all ${createdIssues.length} issues with coordinated team workflow`
},
{
label: "Wave 1 only",
description: `Execute ${wave1.length} MVP issues first`
},
{
label: "View issues",
description: "Browse created issues before deciding"
},
{
label: "Done",
description: "Export complete, handle manually"
}
]
}
]
});
// Based on user selection:
if (selection === "Execute via team-planex") {
const issueIds = createdIssues.map(i => i.issue_id).join(',');
Skill({ skill: "team-planex", args: `--issues ${issueIds}` });
}
if (selection === "Wave 1 only") {
const wave1Ids = createdIssues.filter(i => i.wave === 1).map(i => i.issue_id).join(',');
Skill({ skill: "team-planex", args: `--issues ${wave1Ids}` });
}
if (selection === "View issues") {
Bash(`ccw issue list --tag spec:${specConfig.session_id}`);
}
Output
- File:
issue-export-report.md— Issue mapping table + spec links + next steps - Updated:
.workflow/issues/issues.jsonl— New issue entries appended - Updated:
spec-config.json— Phase 7 completion + issue IDs
Quality Checklist
- All MVP Epics have corresponding issues created
- All non-MVP Epics have corresponding issues created
- Issue tags include
spec-generatedandspec:{session_id} - Issue
extended_context.notes.spec_documentspaths are correct - Wave assignment matches MVP status (MVP → wave-1, non-MVP → wave-2)
- Epic dependencies mapped to issue dependency references
issue-export-report.mdgenerated with mapping tablespec-config.jsonupdated withissue_idsandissues_created- Handoff options presented
Error Handling
| Error | Blocking? | Action |
|---|---|---|
ccw issue create fails for one Epic |
No | Log error, continue with remaining Epics, report partial creation |
| No EPIC files found | Yes | Error and return to Phase 5 |
| All issue creations fail | Yes | Error with CLI diagnostic, suggest manual creation |
| Dependency EPIC not found in mapping | No | Skip dependency link, log warning |
Completion
Phase 7 is the final phase. The specification package has been fully converted to executable issues ready for team-planex or manual execution.