(rootProgram: typeof program)
| 123 | } |
| 124 | |
| 125 | export function registerSpecCommand(rootProgram: typeof program) { |
| 126 | const specCommand = rootProgram |
| 127 | .command('spec') |
| 128 | .description('Manage and view OpenSpec specifications'); |
| 129 | |
| 130 | // Deprecation notice for noun-based commands |
| 131 | specCommand.hook('preAction', () => { |
| 132 | console.error('Warning: The "openspec spec ..." commands are deprecated. Prefer verb-first commands (e.g., "openspec show", "openspec validate --specs").'); |
| 133 | }); |
| 134 | |
| 135 | specCommand |
| 136 | .command('show [spec-id]') |
| 137 | .description('Display a specific specification') |
| 138 | .option('--json', 'Output as JSON') |
| 139 | .option('--requirements', 'JSON only: Show only requirements (exclude scenarios)') |
| 140 | .option('--no-scenarios', 'JSON only: Exclude scenario content') |
| 141 | .option('-r, --requirement <id>', 'JSON only: Show specific requirement by ID (1-based)') |
| 142 | .option('--no-interactive', 'Disable interactive prompts') |
| 143 | .action(async (specId: string | undefined, options: ShowOptions & { noInteractive?: boolean }) => { |
| 144 | try { |
| 145 | const cmd = new SpecCommand(); |
| 146 | await cmd.show(specId, options as any); |
| 147 | } catch (error) { |
| 148 | console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`); |
| 149 | process.exitCode = 1; |
| 150 | } |
| 151 | }); |
| 152 | |
| 153 | specCommand |
| 154 | .command('list') |
| 155 | .description('List all available specifications') |
| 156 | .option('--json', 'Output as JSON') |
| 157 | .option('--long', 'Show id and title with counts') |
| 158 | .action((options: { json?: boolean; long?: boolean }) => { |
| 159 | try { |
| 160 | if (!existsSync(SPECS_DIR)) { |
| 161 | console.log('No items found'); |
| 162 | return; |
| 163 | } |
| 164 | |
| 165 | const specs = readdirSync(SPECS_DIR, { withFileTypes: true }) |
| 166 | .filter(dirent => dirent.isDirectory()) |
| 167 | .map(dirent => { |
| 168 | const specPath = join(SPECS_DIR, dirent.name, 'spec.md'); |
| 169 | if (existsSync(specPath)) { |
| 170 | try { |
| 171 | const spec = parseSpecFromFile(specPath, dirent.name); |
| 172 | |
| 173 | return { |
| 174 | id: dirent.name, |
| 175 | title: spec.name, |
| 176 | requirementCount: spec.requirements.length |
| 177 | }; |
| 178 | } catch { |
| 179 | return { |
| 180 | id: dirent.name, |
| 181 | title: dirent.name, |
| 182 | requirementCount: 0 |
no test coverage detected