diff --git a/README.md b/README.md index abbfbf475..732594b59 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,6 @@ AI: "Following the tasks in openspec/changes/add-user-profile-api/tasks.md: # View active changes (what's being worked on) openspec list -# See the difference between proposed and current specs -openspec diff add-2fa - # Validate your changes are properly formatted openspec validate add-2fa --strict @@ -137,7 +134,6 @@ openspec list # See what changes you're working on openspec archive # Mark a change as complete after deployment # Also useful: -openspec diff # See what specs will change openspec validate # Check formatting before committing openspec show # View change details ``` diff --git a/openspec/README.md b/openspec/README.md index 580f7fe9f..7b65ad25b 100644 --- a/openspec/README.md +++ b/openspec/README.md @@ -55,7 +55,6 @@ After deployment, create separate PR to: openspec list # List active changes openspec list --specs # List specifications openspec show [item] # Display change or spec -openspec diff [change] # Show spec differences openspec validate [item] # Validate changes or specs openspec archive [change] # Archive after deployment @@ -290,7 +289,6 @@ Only add complexity with: ```bash openspec list # What's in progress? openspec show [item] # View details -openspec diff [change] # What's changing? openspec validate --strict # Is it correct? openspec archive [change] # Mark complete ``` diff --git a/openspec/changes/remove-diff-command/proposal.md b/openspec/changes/remove-diff-command/proposal.md new file mode 100644 index 000000000..27cfaf59f --- /dev/null +++ b/openspec/changes/remove-diff-command/proposal.md @@ -0,0 +1,81 @@ +# Remove Diff Command + +## Problem + +The `openspec diff` command adds unnecessary complexity to the OpenSpec CLI for several reasons: + +1. **Redundant functionality**: The `openspec show` command already provides comprehensive visualization of changes through structured JSON output and markdown rendering +2. **Maintenance burden**: The diff command requires a separate dependency (jest-diff) and additional code complexity (~227 lines) +3. **Limited value**: Developers can achieve better diff visualization using existing tools: + - Git diff for actual file changes + - The `show` command for structured change viewing + - Standard diff utilities for comparing spec files directly +4. **Inconsistent with verb-noun pattern**: The command doesn't follow the preferred verb-first command structure that other commands are migrating to + +## Solution + +Remove the `openspec diff` command entirely and guide users to more appropriate alternatives: + +1. **For viewing change content**: Use `openspec show ` which provides: + - Structured JSON output with `--json` flag + - Markdown rendering for human-readable format + - Delta-only views with `--deltas-only` flag + - Full spec content visualization + +2. **For comparing files**: Use standard tools: + - `git diff` for version control comparisons + - System diff utilities for file-by-file comparisons + - IDE diff viewers for visual comparisons + +## Benefits + +- **Reduced complexity**: Removes ~227 lines of code and the jest-diff dependency +- **Clearer user journey**: Directs users to the canonical `show` command for viewing changes +- **Lower maintenance**: Fewer commands to maintain and test +- **Better alignment**: Focuses on the core OpenSpec workflow without redundant features + +## Implementation + +### Files to Remove +- `/src/core/diff.ts` - The entire diff command implementation +- `/openspec/specs/cli-diff/spec.md` - The diff command specification + +### Files to Update +- `/src/cli/index.ts` - Remove diff command registration (lines 8, 84-96) +- `/package.json` - Remove jest-diff dependency +- `/README.md` - Remove diff command documentation +- `/openspec/README.md` - Remove diff command references +- Various documentation files mentioning `openspec diff` + +### Migration Guide for Users + +Users currently using `openspec diff` should transition to: + +```bash +# Before +openspec diff add-feature + +# After - view the change proposal +openspec show add-feature + +# After - view only the deltas +openspec show add-feature --json --deltas-only + +# After - use git for file comparisons +git diff openspec/specs openspec/changes/add-feature/specs +``` + +## Risks + +- **User disruption**: Existing users may have workflows depending on the diff command + - Mitigation: Provide clear migration guide and deprecation period + +- **Loss of visual diff**: The colored, unified diff format will no longer be available + - Mitigation: Users can use git diff or other tools for visual comparisons + +## Success Metrics + +- Successful removal with no broken dependencies +- Documentation updated to reflect the change +- Tests passing without the diff command +- Reduced package size from removing jest-diff dependency \ No newline at end of file diff --git a/openspec/changes/remove-diff-command/tasks.md b/openspec/changes/remove-diff-command/tasks.md new file mode 100644 index 000000000..621d2fb60 --- /dev/null +++ b/openspec/changes/remove-diff-command/tasks.md @@ -0,0 +1,41 @@ +# Remove Diff Command - Tasks + +## 1. Remove Core Implementation +- [x] Delete `/src/core/diff.ts` +- [x] Remove DiffCommand import from `/src/cli/index.ts` +- [x] Remove diff command registration from CLI + +## 2. Remove Specifications +- [x] Delete `/openspec/specs/cli-diff/spec.md` +- [x] Archive the spec for historical reference if needed + +## 3. Update Dependencies +- [x] Remove jest-diff from package.json dependencies +- [x] Run pnpm install to update lock file + +## 4. Update Documentation +- [x] Update main README.md to remove diff command references +- [x] Update openspec/README.md to remove diff command from command list +- [x] Update CLAUDE.md template if it mentions diff command +- [x] Update any example workflows that use diff command + +## 5. Update Related Files +- [x] Search and update any remaining references to "openspec diff" in: + - Template files + - Test files (if any exist for diff command) + - Archive documentation + - Change proposals + +## 6. Add Deprecation Notice (Optional Phase) +- [ ] Consider adding a deprecation warning before full removal +- [ ] Provide helpful message directing users to `openspec show` command + +## 7. Testing +- [x] Ensure all tests pass after removal +- [x] Verify CLI help text no longer shows diff command +- [x] Test that show command provides adequate replacement functionality + +## 8. Documentation of Alternative Workflows +- [x] Document how to use `openspec show` for viewing changes +- [x] Document how to use git diff for file comparisons +- [x] Add migration guide to help text or documentation \ No newline at end of file diff --git a/openspec/specs/cli-diff/spec.md b/openspec/specs/cli-diff/spec.md deleted file mode 100644 index 720569fbc..000000000 --- a/openspec/specs/cli-diff/spec.md +++ /dev/null @@ -1,123 +0,0 @@ -# CLI Diff Command Specification - -## Purpose - -The `openspec diff` command provides developers with a visual comparison between proposed spec changes and the current deployed specs. - -## Command Syntax - -```bash -openspec diff [change-name] -``` -## Requirements -### Requirement: Without Arguments - -The command SHALL provide an interactive selection when no change is specified. - -#### Scenario: Running without arguments - -- **WHEN** running `openspec diff` without arguments -- **THEN** list all available changes in the `changes/` directory (excluding archive) -- **AND** prompt user to select a change - -### Requirement: With Change Name - -The command SHALL compare specs when a specific change is provided. - -#### Scenario: Running with change name - -- **WHEN** running `openspec diff ` -- **THEN** compare all spec files in `changes//specs/` with corresponding files in `specs/` - -### Requirement: Diff Output - -The command SHALL show a requirement-level comparison displaying only changed requirements. - -#### Scenario: Side-by-side comparison of changes - -- **WHEN** running `openspec diff ` -- **THEN** display only requirements that have changed -- **AND** show them in a side-by-side format that: - - Clearly shows the current version on the left - - Shows the future version on the right - - Indicates new requirements (not in current) - - Indicates removed requirements (not in future) - - Aligns modified requirements for easy comparison - -### Requirement: Color Support - -The command SHALL enhance readability with colors when supported. - -#### Scenario: Terminal with color support - -- **WHEN** terminal supports colors -- **THEN** display: - - Removed lines in red - - Added lines in green - - File headers in bold - - Context lines in default color - -### Requirement: Error Handling - -The command SHALL provide clear error messages for various failure conditions. - -#### Scenario: Change not found - -- **WHEN** specified change doesn't exist -- **THEN** display error "Change '' not found" - -#### Scenario: No specs in change - -- **WHEN** no specs directory in change -- **THEN** display "No spec changes found for ''" - -#### Scenario: Missing changes directory - -- **WHEN** changes directory doesn't exist -- **THEN** display "No OpenSpec changes directory found" - -### Requirement: Validation - -The command SHALL validate that changes can be applied successfully. - -#### Scenario: Invalid delta references - -- **WHEN** delta references non-existent requirement -- **THEN** show error message with specific requirement -- **AND** continue showing other valid changes -- **AND** clearly mark failed changes in the output - -### Requirement: Diff Command Enhancement - -The diff command SHALL validate change structure before displaying differences. - -#### Scenario: Validate before diff - -- **WHEN** executing `openspec diff change-name` -- **THEN** validate change structure -- **AND** show validation warnings if present -- **AND** continue with diff display - -## Examples - -```bash -# View diff for specific change -$ openspec diff add-auth-feature - ---- specs/user-auth/spec.md -+++ changes/add-auth-feature/specs/user-auth/spec.md -@@ -10,6 +10,8 @@ - Users SHALL authenticate with email and password. - -+Users MAY authenticate with OAuth providers. -+ - WHEN credentials are valid THEN issue JWT token. - -# List all changes and select -$ openspec diff -Available changes: - 1. add-auth-feature - 2. update-payment-flow - 3. add-status-command -Select a change (1-3): -``` \ No newline at end of file diff --git a/package.json b/package.json index 4c2538eaa..479e9209a 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "@inquirer/prompts": "^7.8.0", "chalk": "^5.5.0", "commander": "^14.0.0", - "jest-diff": "^30.0.5", "ora": "^8.2.0", "zod": "^4.0.17" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d03d1ec8..809988fc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,6 @@ importers: commander: specifier: ^14.0.0 version: 14.0.0 - jest-diff: - specifier: ^30.0.5 - version: 30.0.5 ora: specifier: ^8.2.0 version: 8.2.0 @@ -390,18 +387,6 @@ packages: '@types/node': optional: true - '@jest/diff-sequences@30.0.1': - resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/get-type@30.0.1': - resolution: {integrity: sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - - '@jest/schemas@30.0.5': - resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jridgewell/sourcemap-codec@1.5.4': resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} @@ -526,9 +511,6 @@ packages: cpu: [x64] os: [win32] - '@sinclair/typebox@0.34.38': - resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==} - '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -598,10 +580,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -629,10 +607,6 @@ packages: resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} engines: {node: '>=18'} - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - chalk@5.5.0: resolution: {integrity: sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -793,10 +767,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - human-id@4.1.1: resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} hasBin: true @@ -852,10 +822,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jest-diff@30.0.5: - resolution: {integrity: sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} @@ -996,19 +962,12 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - pretty-format@30.0.5: - resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -1107,10 +1066,6 @@ packages: strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -1608,14 +1563,6 @@ snapshots: optionalDependencies: '@types/node': 24.2.0 - '@jest/diff-sequences@30.0.1': {} - - '@jest/get-type@30.0.1': {} - - '@jest/schemas@30.0.5': - dependencies: - '@sinclair/typebox': 0.34.38 - '@jridgewell/sourcemap-codec@1.5.4': {} '@manypkg/find-root@1.1.0': @@ -1708,8 +1655,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.46.2': optional: true - '@sinclair/typebox@0.34.38': {} - '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -1791,8 +1736,6 @@ snapshots: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -1819,11 +1762,6 @@ snapshots: loupe: 3.2.0 pathval: 2.0.1 - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - chalk@5.5.0: {} chardet@0.7.0: {} @@ -1985,8 +1923,6 @@ snapshots: graceful-fs@4.2.11: {} - has-flag@4.0.0: {} - human-id@4.1.1: {} iconv-lite@0.4.24: @@ -2023,13 +1959,6 @@ snapshots: isexe@2.0.0: {} - jest-diff@30.0.5: - dependencies: - '@jest/diff-sequences': 30.0.1 - '@jest/get-type': 30.0.1 - chalk: 4.1.2 - pretty-format: 30.0.5 - js-tokens@9.0.1: {} js-yaml@3.14.1: @@ -2143,18 +2072,10 @@ snapshots: prettier@2.8.8: {} - pretty-format@30.0.5: - dependencies: - '@jest/schemas': 30.0.5 - ansi-styles: 5.2.0 - react-is: 18.3.1 - quansync@0.2.11: {} queue-microtask@1.2.3: {} - react-is@18.3.1: {} - read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -2264,10 +2185,6 @@ snapshots: dependencies: js-tokens: 9.0.1 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - term-size@2.2.1: {} tinybench@2.9.0: {} diff --git a/src/cli/index.ts b/src/cli/index.ts index 32b6f0080..7573a6035 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -5,7 +5,6 @@ import path from 'path'; import { promises as fs } from 'fs'; import { InitCommand } from '../core/init.js'; import { UpdateCommand } from '../core/update.js'; -import { DiffCommand } from '../core/diff.js'; import { ListCommand } from '../core/list.js'; import { ArchiveCommand } from '../core/archive.js'; import { registerSpecCommand } from '../commands/spec.js'; @@ -81,20 +80,6 @@ program } }); -program - .command('diff [change-name]') - .description('Show differences between proposed spec changes and current specs (includes validation warnings)') - .action(async (changeName?: string) => { - try { - const diffCommand = new DiffCommand(); - await diffCommand.execute(changeName); - } catch (error) { - console.log(); // Empty line for spacing - ora().fail(`Error: ${(error as Error).message}`); - process.exit(1); - } - }); - program .command('list') .description('List items (changes by default). Use --specs to list specs.') diff --git a/src/core/diff.ts b/src/core/diff.ts deleted file mode 100644 index 40367bade..000000000 --- a/src/core/diff.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import chalk from 'chalk'; -import { diffStringsUnified } from 'jest-diff'; -import { select } from '@inquirer/prompts'; -import { Validator } from './validation/validator.js'; - -// Constants -const ARCHIVE_DIR = 'archive'; -const MARKDOWN_EXT = '.md'; -const OPENSPEC_DIR = 'openspec'; -const CHANGES_DIR = 'changes'; -const SPECS_DIR = 'specs'; - -export class DiffCommand { - private filesChanged: number = 0; - private linesAdded: number = 0; - private linesRemoved: number = 0; - - async execute(changeName?: string): Promise { - const changesDir = path.join(process.cwd(), OPENSPEC_DIR, CHANGES_DIR); - - try { - await fs.access(changesDir); - } catch { - throw new Error('No OpenSpec changes directory found'); - } - - if (!changeName) { - changeName = await this.selectChange(changesDir); - if (!changeName) return; - } - - const changeDir = path.join(changesDir, changeName); - - try { - await fs.access(changeDir); - } catch { - throw new Error(`Change '${changeName}' not found`); - } - - const changeSpecsDir = path.join(changeDir, SPECS_DIR); - - try { - await fs.access(changeSpecsDir); - } catch { - console.log(`No spec changes found for '${changeName}'`); - return; - } - - // Validate specs and show warnings (non-blocking) - const validator = new Validator(); - let hasWarnings = false; - - try { - const entries = await fs.readdir(changeSpecsDir, { withFileTypes: true }); - - for (const entry of entries) { - if (entry.isDirectory()) { - const specFile = path.join(changeSpecsDir, entry.name, 'spec.md'); - - try { - await fs.access(specFile); - const report = await validator.validateSpec(specFile); - - if (report.issues.length > 0) { - const warnings = report.issues.filter(i => i.level === 'WARNING'); - const errors = report.issues.filter(i => i.level === 'ERROR'); - - if (errors.length > 0 || warnings.length > 0) { - if (!hasWarnings) { - console.log(chalk.yellow('\nāš ļø Validation warnings found:')); - hasWarnings = true; - } - - console.log(chalk.yellow(`\n ${entry.name}/spec.md:`)); - for (const issue of errors) { - console.log(chalk.red(` āœ— ${issue.message}`)); - } - for (const issue of warnings) { - console.log(chalk.yellow(` ⚠ ${issue.message}`)); - } - } - } - } catch { - // Spec file doesn't exist, skip validation - } - } - } - - if (hasWarnings) { - console.log(chalk.yellow('\nConsider fixing these issues before archiving.\n')); - } - } catch { - // No specs directory, skip validation - } - - // Reset counters - this.filesChanged = 0; - this.linesAdded = 0; - this.linesRemoved = 0; - - await this.showDiffs(changeSpecsDir); - - // Show summary - if (this.filesChanged > 0) { - console.log(chalk.bold(`\nšŸ“Š Summary: ${this.filesChanged} file(s) changed, ${chalk.green(`+${this.linesAdded}`)} ${chalk.red(`-${this.linesRemoved}`)}`)); - } - } - - private async selectChange(changesDir: string): Promise { - const entries = await fs.readdir(changesDir, { withFileTypes: true }); - const changes = entries - .filter(entry => entry.isDirectory() && entry.name !== ARCHIVE_DIR) - .map(entry => entry.name); - - if (changes.length === 0) { - console.log('No changes found'); - return undefined; - } - - console.log('Available changes:'); - const choices = changes.map((name) => ({ - name: name, - value: name - })); - - const answer = await select({ - message: 'Select a change', - choices - }); - - return answer as string; - } - - private async showDiffs(changeSpecsDir: string): Promise { - const currentSpecsDir = path.join(process.cwd(), OPENSPEC_DIR, SPECS_DIR); - await this.walkAndDiff(changeSpecsDir, currentSpecsDir, ''); - } - - private async walkAndDiff(changeDir: string, currentDir: string, relativePath: string): Promise { - const entries = await fs.readdir(path.join(changeDir, relativePath), { withFileTypes: true }); - - for (const entry of entries) { - const entryPath = path.join(relativePath, entry.name); - - if (entry.isDirectory()) { - await this.walkAndDiff(changeDir, currentDir, entryPath); - } else if (entry.isFile() && entry.name.endsWith(MARKDOWN_EXT)) { - await this.diffFile( - path.join(changeDir, entryPath), - path.join(currentDir, entryPath), - entryPath - ); - } - } - } - - private async diffFile(changePath: string, currentPath: string, displayPath: string): Promise { - let changeContent = ''; - let currentContent = ''; - let isNewFile = false; - let isDeleted = false; - - try { - changeContent = await fs.readFile(changePath, 'utf-8'); - } catch { - changeContent = ''; - } - - try { - currentContent = await fs.readFile(currentPath, 'utf-8'); - } catch { - currentContent = ''; - isNewFile = true; - } - - if (changeContent === currentContent) { - return; - } - - if (changeContent === '' && currentContent !== '') { - isDeleted = true; - } - - // Enhanced header with file status - console.log(chalk.bold.cyan(`\n${'═'.repeat(60)}`)); - console.log(chalk.bold.cyan(`šŸ“„ ${displayPath}`)); - - if (isNewFile) { - console.log(chalk.green(` Status: NEW FILE`)); - } else if (isDeleted) { - console.log(chalk.red(` Status: DELETED`)); - } else { - console.log(chalk.yellow(` Status: MODIFIED`)); - } - - // Use jest-diff for the actual diff with custom options - const diffOptions = { - aAnnotation: 'Current', - bAnnotation: 'Proposed', - aColor: chalk.red, - bColor: chalk.green, - commonColor: chalk.gray, - contextLines: 3, - expand: false, - includeChangeCounts: true, - }; - - const diff = diffStringsUnified(currentContent, changeContent, diffOptions); - - // Count lines for statistics (approximate) - const addedLines = (diff.match(/^\+[^+]/gm) || []).length; - const removedLines = (diff.match(/^-[^-]/gm) || []).length; - - console.log(chalk.gray(` Lines: ${chalk.green(`+${addedLines}`)} ${chalk.red(`-${removedLines}`)}`)); - console.log(chalk.bold.cyan(`${'─'.repeat(60)}\n`)); - - // Display the diff - console.log(diff); - - // Update counters - this.filesChanged++; - this.linesAdded += addedLines; - this.linesRemoved += removedLines; - } -} \ No newline at end of file diff --git a/src/core/templates/claude-template.ts b/src/core/templates/claude-template.ts index f5219bcfb..78439799a 100644 --- a/src/core/templates/claude-template.ts +++ b/src/core/templates/claude-template.ts @@ -35,7 +35,6 @@ After deployment, use \`openspec archive [change]\` (add \`--skip-specs\` for to openspec list # Active changes openspec list --specs # Existing specifications openspec show [item] # View details -openspec diff [change] # Show spec differences openspec validate --strict # Validate thoroughly openspec archive [change] # Archive after deployment