(openspecDir: string)
| 79 | } |
| 80 | |
| 81 | private async getChangesData(openspecDir: string): Promise<{ |
| 82 | draft: Array<{ name: string }>; |
| 83 | active: Array<{ name: string; progress: { total: number; completed: number } }>; |
| 84 | completed: Array<{ name: string }>; |
| 85 | }> { |
| 86 | const changesDir = path.join(openspecDir, 'changes'); |
| 87 | |
| 88 | if (!fs.existsSync(changesDir)) { |
| 89 | return { draft: [], active: [], completed: [] }; |
| 90 | } |
| 91 | |
| 92 | const draft: Array<{ name: string }> = []; |
| 93 | const active: Array<{ name: string; progress: { total: number; completed: number } }> = []; |
| 94 | const completed: Array<{ name: string }> = []; |
| 95 | |
| 96 | const entries = fs.readdirSync(changesDir, { withFileTypes: true }); |
| 97 | |
| 98 | for (const entry of entries) { |
| 99 | if (entry.isDirectory() && entry.name !== 'archive') { |
| 100 | const progress = await getTaskProgressForChange(changesDir, entry.name); |
| 101 | |
| 102 | if (progress.total === 0) { |
| 103 | // No tasks defined yet - still in planning/draft phase |
| 104 | draft.push({ name: entry.name }); |
| 105 | } else if (progress.completed === progress.total) { |
| 106 | // All tasks complete |
| 107 | completed.push({ name: entry.name }); |
| 108 | } else { |
| 109 | // Has tasks but not all complete |
| 110 | active.push({ name: entry.name, progress }); |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // Sort all categories by name for deterministic ordering |
| 116 | draft.sort((a, b) => a.name.localeCompare(b.name)); |
| 117 | |
| 118 | // Sort active changes by completion percentage (ascending) and then by name |
| 119 | active.sort((a, b) => { |
| 120 | const percentageA = a.progress.total > 0 ? a.progress.completed / a.progress.total : 0; |
| 121 | const percentageB = b.progress.total > 0 ? b.progress.completed / b.progress.total : 0; |
| 122 | |
| 123 | if (percentageA < percentageB) return -1; |
| 124 | if (percentageA > percentageB) return 1; |
| 125 | return a.name.localeCompare(b.name); |
| 126 | }); |
| 127 | completed.sort((a, b) => a.name.localeCompare(b.name)); |
| 128 | |
| 129 | return { draft, active, completed }; |
| 130 | } |
| 131 | |
| 132 | private async getSpecsData(openspecDir: string): Promise<Array<{ name: string; requirementCount: number }>> { |
| 133 | const specsDir = path.join(openspecDir, 'specs'); |
no test coverage detected