(targetPath: string = '.', mode: 'changes' | 'specs' = 'changes', options: ListOptions = {})
| 78 | |
| 79 | export class ListCommand { |
| 80 | async execute(targetPath: string = '.', mode: 'changes' | 'specs' = 'changes', options: ListOptions = {}): Promise<void> { |
| 81 | const { sort = 'recent', json = false, root } = options; |
| 82 | |
| 83 | if (mode === 'changes') { |
| 84 | const changesDir = path.join(targetPath, 'openspec', 'changes'); |
| 85 | |
| 86 | // Check if changes directory exists |
| 87 | try { |
| 88 | await fs.access(changesDir); |
| 89 | } catch { |
| 90 | throw new Error("No OpenSpec changes directory found. Run 'openspec init' first."); |
| 91 | } |
| 92 | |
| 93 | // Get all directories in changes (excluding archive) |
| 94 | const entries = await fs.readdir(changesDir, { withFileTypes: true }); |
| 95 | const changeDirs = entries |
| 96 | .filter(entry => entry.isDirectory() && entry.name !== 'archive') |
| 97 | .map(entry => entry.name); |
| 98 | |
| 99 | if (changeDirs.length === 0) { |
| 100 | if (json) { |
| 101 | console.log(JSON.stringify({ changes: [], ...(root ? { root } : {}) }, null, 2)); |
| 102 | } else { |
| 103 | console.log('No active changes found.'); |
| 104 | } |
| 105 | return; |
| 106 | } |
| 107 | |
| 108 | // Collect information about each change |
| 109 | const changes: ChangeInfo[] = []; |
| 110 | |
| 111 | for (const changeDir of changeDirs) { |
| 112 | const progress = await getTaskProgressForChange(changesDir, changeDir); |
| 113 | const changePath = path.join(changesDir, changeDir); |
| 114 | const lastModified = await getLastModified(changePath); |
| 115 | changes.push({ |
| 116 | name: changeDir, |
| 117 | completedTasks: progress.completed, |
| 118 | totalTasks: progress.total, |
| 119 | lastModified |
| 120 | }); |
| 121 | } |
| 122 | |
| 123 | // Sort by preference (default: recent first) |
| 124 | if (sort === 'recent') { |
| 125 | changes.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime()); |
| 126 | } else { |
| 127 | changes.sort((a, b) => a.name.localeCompare(b.name)); |
| 128 | } |
| 129 | |
| 130 | // JSON output for programmatic use |
| 131 | if (json) { |
| 132 | const jsonOutput = changes.map(c => ({ |
| 133 | name: c.name, |
| 134 | completedTasks: c.completedTasks, |
| 135 | totalTasks: c.totalTasks, |
| 136 | lastModified: c.lastModified.toISOString(), |
| 137 | status: c.totalTasks === 0 ? 'no-tasks' : c.completedTasks === c.totalTasks ? 'complete' : 'in-progress' |
nothing calls this directly
no test coverage detected