Add unit tests for various components and stores in the terminal dashboard

- 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.
This commit is contained in:
catlog22
2026-03-08 21:38:20 +08:00
parent 9aa07e8d01
commit 62d8aa3623
157 changed files with 36544 additions and 71 deletions

View File

@@ -0,0 +1,329 @@
# 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 create` CLI 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 individual `EPIC-*.md` files)
- 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
```javascript
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
```javascript
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
```javascript
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
```javascript
// 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
```javascript
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
```javascript
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
```javascript
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-generated` and `spec:{session_id}`
- [ ] Issue `extended_context.notes.spec_documents` paths are correct
- [ ] Wave assignment matches MVP status (MVP → wave-1, non-MVP → wave-2)
- [ ] Epic dependencies mapped to issue dependency references
- [ ] `issue-export-report.md` generated with mapping table
- [ ] `spec-config.json` updated with `issue_ids` and `issues_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.