fix: preserve classic openspec version passthrough - #300
Conversation
Reviewer's GuideRoutes the Sequence diagram for classic command routing in runClisequenceDiagram
actor User
participant NodeProcess as node
participant CometCli as runCli
participant ClassicFacade as runClassicGroupFacade
participant Commander as program
User->>NodeProcess: comet classic openspec -- --version
NodeProcess->>CometCli: runCli()
CometCli->>CometCli: [process.argv[2] === classic]
CometCli->>ClassicFacade: runClassicGroupFacade(process.argv.slice(3))
ClassicFacade-->>CometCli: exitCode
CometCli->>NodeProcess: set process.exitCode
CometCli-->>NodeProcess: return
alt non-classic command
CometCli->>Commander: program.parseAsync()
Commander-->>CometCli: parse result
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe CLI now dispatches top-level ChangesClassic version passthrough
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
runCli, the directprocess.argv[2] === 'classic'check is a bit brittle; consider using a more explicit argument parsing or Commander hook so subcommand routing stays consistent if the CLI invocation pattern changes (e.g., prefixed global flags). - The new test spies on
process.stdout.writebut never restores the spy; adding explicit restoration (e.g., viastdout.mockRestore()orvi.restoreAllMocks()in anafterEach) would reduce potential side effects across tests.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `runCli`, the direct `process.argv[2] === 'classic'` check is a bit brittle; consider using a more explicit argument parsing or Commander hook so subcommand routing stays consistent if the CLI invocation pattern changes (e.g., prefixed global flags).
- The new test spies on `process.stdout.write` but never restores the spy; adding explicit restoration (e.g., via `stdout.mockRestore()` or `vi.restoreAllMocks()` in an `afterEach`) would reduce potential side effects across tests.
## Individual Comments
### Comment 1
<location path="test/app/classic-command.test.ts" line_range="107-127" />
<code_context>
}
});
+
+ it('routes Classic group argv before global version parsing', async () => {
+ const stdout = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
+ runClassicCli.mockResolvedValue({ exitCode: 0, stdout: 'openspec version\n' });
+ const originalArgv = process.argv;
+ const originalExitCode = process.exitCode;
+ process.argv = [process.execPath, 'comet', 'classic', 'openspec', '--', '--version'];
+ process.exitCode = undefined;
+ vi.resetModules();
+
+ try {
+ await import('../../app/cli/index.js');
+ await vi.waitFor(() => {
+ expect(runClassicCli).toHaveBeenCalledWith(['openspec', '--', '--version']);
+ expect(stdout).toHaveBeenCalledWith('openspec version\n');
+ expect(process.exitCode).toBe(0);
+ });
+ } finally {
+ process.argv = originalArgv;
+ process.exitCode = originalExitCode;
+ }
+ });
});
</code_context>
<issue_to_address>
**suggestion (testing):** Restore the stdout spy after the test to avoid side effects on subsequent tests
This test mocks `process.stdout.write` but never restores it. Even if Vitest resets mocks between tests, we should explicitly restore `stdout` in the `finally` block alongside `process.argv` and `process.exitCode` to avoid unintended interactions with other tests that depend on the original `stdout.write` behavior.
```suggestion
it('routes Classic group argv before global version parsing', async () => {
const stdout = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
runClassicCli.mockResolvedValue({ exitCode: 0, stdout: 'openspec version\n' });
const originalArgv = process.argv;
const originalExitCode = process.exitCode;
process.argv = [process.execPath, 'comet', 'classic', 'openspec', '--', '--version'];
process.exitCode = undefined;
vi.resetModules();
try {
await import('../../app/cli/index.js');
await vi.waitFor(() => {
expect(runClassicCli).toHaveBeenCalledWith(['openspec', '--', '--version']);
expect(stdout).toHaveBeenCalledWith('openspec version\n');
expect(process.exitCode).toBe(0);
});
} finally {
stdout.mockRestore();
process.argv = originalArgv;
process.exitCode = originalExitCode;
}
});
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| it('routes Classic group argv before global version parsing', async () => { | ||
| const stdout = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); | ||
| runClassicCli.mockResolvedValue({ exitCode: 0, stdout: 'openspec version\n' }); | ||
| const originalArgv = process.argv; | ||
| const originalExitCode = process.exitCode; | ||
| process.argv = [process.execPath, 'comet', 'classic', 'openspec', '--', '--version']; | ||
| process.exitCode = undefined; | ||
| vi.resetModules(); | ||
|
|
||
| try { | ||
| await import('../../app/cli/index.js'); | ||
| await vi.waitFor(() => { | ||
| expect(runClassicCli).toHaveBeenCalledWith(['openspec', '--', '--version']); | ||
| expect(stdout).toHaveBeenCalledWith('openspec version\n'); | ||
| expect(process.exitCode).toBe(0); | ||
| }); | ||
| } finally { | ||
| process.argv = originalArgv; | ||
| process.exitCode = originalExitCode; | ||
| } | ||
| }); |
There was a problem hiding this comment.
suggestion (testing): Restore the stdout spy after the test to avoid side effects on subsequent tests
This test mocks process.stdout.write but never restores it. Even if Vitest resets mocks between tests, we should explicitly restore stdout in the finally block alongside process.argv and process.exitCode to avoid unintended interactions with other tests that depend on the original stdout.write behavior.
| it('routes Classic group argv before global version parsing', async () => { | |
| const stdout = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); | |
| runClassicCli.mockResolvedValue({ exitCode: 0, stdout: 'openspec version\n' }); | |
| const originalArgv = process.argv; | |
| const originalExitCode = process.exitCode; | |
| process.argv = [process.execPath, 'comet', 'classic', 'openspec', '--', '--version']; | |
| process.exitCode = undefined; | |
| vi.resetModules(); | |
| try { | |
| await import('../../app/cli/index.js'); | |
| await vi.waitFor(() => { | |
| expect(runClassicCli).toHaveBeenCalledWith(['openspec', '--', '--version']); | |
| expect(stdout).toHaveBeenCalledWith('openspec version\n'); | |
| expect(process.exitCode).toBe(0); | |
| }); | |
| } finally { | |
| process.argv = originalArgv; | |
| process.exitCode = originalExitCode; | |
| } | |
| }); | |
| it('routes Classic group argv before global version parsing', async () => { | |
| const stdout = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); | |
| runClassicCli.mockResolvedValue({ exitCode: 0, stdout: 'openspec version\n' }); | |
| const originalArgv = process.argv; | |
| const originalExitCode = process.exitCode; | |
| process.argv = [process.execPath, 'comet', 'classic', 'openspec', '--', '--version']; | |
| process.exitCode = undefined; | |
| vi.resetModules(); | |
| try { | |
| await import('../../app/cli/index.js'); | |
| await vi.waitFor(() => { | |
| expect(runClassicCli).toHaveBeenCalledWith(['openspec', '--', '--version']); | |
| expect(stdout).toHaveBeenCalledWith('openspec version\n'); | |
| expect(process.exitCode).toBe(0); | |
| }); | |
| } finally { | |
| stdout.mockRestore(); | |
| process.argv = originalArgv; | |
| process.exitCode = originalExitCode; | |
| } | |
| }); |
dab1f6f to
fde8b95
Compare
fde8b95 to
20a386a
Compare
Summary
comet classic ...directly to the Classic facade before top-level Commander parsingcomet classic openspec -- --version0.4.0-beta.18Fixes #296.
Tests
npx vitest run test/app/classic-command.test.tsnpx prettier --check CHANGELOG.md app/cli/index.ts package.json test/app/classic-command.test.tspnpm buildCOMET_OPENSPEC=node node bin/comet.js classic openspec -- --versionSummary by Sourcery
Route classic CLI invocations directly to the Classic facade before global option parsing to preserve OpenSpec version passthrough behavior and bump the package version.
Bug Fixes:
comet classic openspec -- --versionis handled by the Classic facade so the OpenSpec CLI version is reported instead of the main Comet version.Enhancements:
classiccommand group in the top-level CLI entrypoint.Build:
Documentation:
Tests:
comet classic openspec -- --versionbypasses global version parsing and is handled by the Classic facade.Summary by CodeRabbit
Bug Fixes
comet classic openspec -- --versionso it reports the OpenSpec CLI version instead of Comet’s version.Chores
0.4.0-beta.18.