Skip to content

Conversation

@noameron
Copy link
Contributor

@noameron noameron commented Dec 23, 2025

Added support for auto-completion for bash/fish/powershell

Summary by CodeRabbit

  • New Features

    • Multi-shell completions: generate/install/uninstall for Zsh, Bash, Fish, and PowerShell; installers return structured results and user instructions.
    • Uninstall accepts a yes (-y) option to skip confirmation.
  • Bug Fixes

    • Install/uninstall flows display shell-specific config paths, warnings, and tailored reload/uninstall instructions; error messages list supported shells.
  • Tests

    • Extensive unit tests added for generators, installers, and filesystem utilities across all supported shells.
  • Documentation

    • Specs and proposal updated for multi-shell workflows.

✏️ Tip: You can customize this high-level summary in your review settings.

@noameron noameron requested a review from TabishB as a code owner December 23, 2025 12:41
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

📝 Walkthrough

Walkthrough

Expanded CLI completions from Zsh-only to multi‑shell (zsh, bash, fish, powershell): added per‑shell generators and installers, centralized InstallationResult, updated CompletionFactory and CLI installer output/confirmation behavior, added shell templates, adjusted file-system writability logic, and added comprehensive tests.

Changes

Cohort / File(s) Change Summary
Factory & Core Types
src/core/completions/factory.ts, src/core/completions/installers/zsh-installer.ts
Centralized InstallationResult, expanded SUPPORTED_SHELLS to include `bash
Generators — New
src/core/completions/generators/*.ts
src/core/completions/generators/bash-generator.ts, src/core/completions/generators/fish-generator.ts, src/core/completions/generators/powershell-generator.ts
Added Bash, Fish, and PowerShell generators implementing generate(commands: CommandDefinition[]): string with per-command, flag, subcommand, positional, and dynamic helper logic.
Generator — Existing (Zsh)
src/core/completions/generators/zsh-generator.ts
Switched to template-based assembly reusing ZSH_DYNAMIC_HELPERS; adjusted positional shell mapping to include multiple shells.
Installers — New
src/core/completions/installers/*.ts
src/core/completions/installers/bash-installer.ts, .../fish-installer.ts, .../powershell-installer.ts
New per‑shell installers with path resolution, backups, marker/profile config management, install/uninstall flows, structured InstallationResult, warnings, and robust error handling.
CLI command behavior
src/commands/completion.ts
CLI now surfaces installer warnings, prints shell-specific updated config paths, emits shell-specific reload instructions (exec/. $PROFILE) or generic restart advice, and uses dynamic configPath in confirmations.
Templates — New/Refactor
src/core/completions/templates/*.ts
.../bash-templates.ts, .../fish-templates.ts, .../powershell-templates.ts, .../zsh-templates.ts
Added shell-specific dynamic/static helper template modules and refactored Zsh helpers into a template export.
File-system utils
src/utils/file-system.ts
Added helper to find first existing ancestor directory and updated canWriteFile ENOENT handling to test writability on that ancestor.
Command Registry
src/core/completions/command-registry.ts
Added --yes / -y flag to completion.uninstall to skip confirmation prompts.
Tests — Generators & Installers
test/core/completions/generators/*.test.ts, test/core/completions/installers/*.test.ts
Added extensive unit tests for Bash/Fish/PowerShell generators and installers; expanded Bash tests and installer coverage (backups, config markers, idempotence, permissions).
Tests — CLI & FS
test/commands/completion.test.ts, test/utils/file-system.test.ts
Updated CLI tests to expect the full supported-shell list; added comprehensive tests for FileSystemUtils.canWriteFile edge cases.
Specs & Docs
openspec/specs/cli-completion/spec.md, openspec/changes/archive/**
Spec and archived proposals updated to document multi‑shell architecture, new interfaces, factory, positional model changes, and expanded installation/testing scenarios.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as OpenSpec CLI
  participant Factory as CompletionFactory
  participant Gen as CompletionGenerator
  participant Installer as CompletionInstaller
  participant FS as FileSystem

  CLI->>Factory: createGenerator(selectedShell)
  Factory->>Gen: instantiate shell-specific generator
  CLI->>Gen: generate(commands)
  Gen-->>CLI: completion script
  CLI->>Factory: createInstaller(selectedShell)
  Factory->>Installer: instantiate shell-specific installer
  CLI->>Installer: install(completionScript)
  Installer->>FS: resolve path / check existing / backup
  FS-->>Installer: path & backup info
  Installer->>FS: write script / update shell config/profile (markers)
  FS-->>Installer: write result
  Installer-->>CLI: InstallationResult(success, installedPath, backupPath, instructions, warnings)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • TabishB

Poem

"I hopped through code with nimble paws,
Four shells now dance without a pause.
Bash, Fish, PowerShell, Zsh in view,
Scripts in hand — completions grew.
A rabbit's cheer: fresh code anew! 🐇"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feature/bash_fish_power_shells_completions' directly describes the main objective of adding bash, fish, and PowerShell shell completion support, which is clearly the primary change in this comprehensive changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/completion.ts (1)

189-193: Confirmation prompt hardcodes Zsh-specific path.

The uninstall confirmation message references ~/.zshrc regardless of which shell is being used. For Bash, Fish, or PowerShell, this message would be misleading to users.

🔎 Suggested fix
     if (!skipConfirmation) {
+      const configFiles: Record<string, string> = {
+        zsh: '~/.zshrc',
+        bash: '~/.bashrc',
+        fish: '~/.config/fish/config.fish',
+        powershell: '$PROFILE',
+      };
       const confirmed = await confirm({
-        message: 'Remove OpenSpec configuration from ~/.zshrc?',
+        message: `Remove OpenSpec ${shell} completion configuration${configFiles[shell] ? ` from ${configFiles[shell]}` : ''}?`,
         default: false,
       });
🧹 Nitpick comments (6)
src/core/completions/generators/bash-generator.ts (1)

132-155: Consider using the union type from CommandDefinition for type safety.

The positionalType parameter is typed as string | undefined, but CommandDefinition defines it as a specific union type. Using the exact type would provide compile-time exhaustiveness checking.

🔎 Proposed fix
-  private generatePositionalCompletion(positionalType: string | undefined, indent: string): string[] {
+  private generatePositionalCompletion(positionalType: CommandDefinition['positionalType'], indent: string): string[] {
test/core/completions/installers/fish-installer.test.ts (1)

220-227: Consider whether empty completion scripts should be allowed.

The test verifies that an empty string can be installed as a completion script. While this works, it may indicate a bug in the caller. Consider whether the installer should validate that the script content is non-empty, or if this is intentional for testing purposes.

test/core/completions/installers/powershell-installer.test.ts (1)

1-1: Unused import: vi from vitest.

The vi import is not used anywhere in this test file. All tests use real filesystem operations with temp directories rather than mocking.

🔎 Proposed fix
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
src/core/completions/installers/bash-installer.ts (3)

31-50: Consider checking user-local bash-completion directory.

The check only looks at system-level paths. Users who installed bash-completion via Homebrew or in user-local directories might have it at ~/.local/share/bash-completion. However, since this is only used to display a warning (not to block installation), the current implementation is acceptable.


189-192: Misleading comment: code removes leading lines, not trailing.

The comment says "Remove trailing empty lines" but lines.shift() removes from the front (leading lines). This appears intentional since marker blocks are typically at the beginning of the file, but the comment is incorrect.

🔎 Proposed fix
-      // Remove trailing empty lines
+      // Remove leading empty lines after marker block removal
       while (lines.length > 0 && lines[0].trim() === '') {
         lines.shift();
       }

258-269: Warning message is macOS-specific.

The bash-completion installation instructions mention brew install and /opt/homebrew paths which are macOS-specific. Linux users would typically use apt install bash-completion or similar package managers. Consider providing platform-specific instructions or more generic guidance.

🔎 Proposed fix
       if (!hasBashCompletion) {
         warnings.push(
           '⚠️  Warning: bash-completion package not detected',
           '',
           'The completion script requires bash-completion to function.',
-          'Install it with:',
-          '  brew install bash-completion@2',
+          'Install it with your package manager:',
+          '  macOS:  brew install bash-completion@2',
+          '  Debian/Ubuntu: sudo apt install bash-completion',
+          '  Fedora: sudo dnf install bash-completion',
           '',
-          'Then add to your ~/.bash_profile:',
-          '  [[ -r "/opt/homebrew/etc/profile.d/bash_completion.sh" ]] && . "/opt/homebrew/etc/profile.d/bash_completion.sh"'
+          'Then follow your distribution\'s instructions to enable it in your shell.'
         );
       }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2599b and ad53abc.

📒 Files selected for processing (20)
  • openspec/changes/extend-shell-completions/proposal.md
  • openspec/changes/extend-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/extend-shell-completions/tasks.md
  • src/commands/completion.ts
  • src/core/completions/factory.ts
  • src/core/completions/generators/bash-generator.ts
  • src/core/completions/generators/fish-generator.ts
  • src/core/completions/generators/powershell-generator.ts
  • src/core/completions/generators/zsh-generator.ts
  • src/core/completions/installers/bash-installer.ts
  • src/core/completions/installers/fish-installer.ts
  • src/core/completions/installers/powershell-installer.ts
  • src/core/completions/installers/zsh-installer.ts
  • test/commands/completion.test.ts
  • test/core/completions/generators/bash-generator.test.ts
  • test/core/completions/generators/fish-generator.test.ts
  • test/core/completions/generators/powershell-generator.test.ts
  • test/core/completions/installers/bash-installer.test.ts
  • test/core/completions/installers/fish-installer.test.ts
  • test/core/completions/installers/powershell-installer.test.ts
💤 Files with no reviewable changes (1)
  • src/core/completions/generators/zsh-generator.ts
🧰 Additional context used
📓 Path-based instructions (4)
openspec/changes/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Scaffold proposal using proposal.md, tasks.md, optional design.md, and delta specs under openspec/changes/<id>/

Files:

  • openspec/changes/extend-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/extend-shell-completions/proposal.md
  • openspec/changes/extend-shell-completions/tasks.md
openspec/changes/**/specs/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/spec.md: Use ## ADDED|MODIFIED|REMOVED|RENAMED Requirements headers in spec delta files
Include at least one #### Scenario: per requirement in spec delta files
Use #### Scenario: Name format (4 hashtags) for scenario headers, not bullets or bold text
Use ## ADDED Requirements for new orthogonal capabilities that can stand alone; use ## MODIFIED Requirements for behavior changes of existing requirements
When using MODIFIED Requirements, paste the full requirement block including header and all scenarios

Files:

  • openspec/changes/extend-shell-completions/specs/cli-completion/spec.md
openspec/changes/*/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Ensure proposal.md includes sections: Why (1-2 sentences), What Changes (bullet list with breaking change markers), and Impact (affected specs and code)

Files:

  • openspec/changes/extend-shell-completions/proposal.md
openspec/changes/*/tasks.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Ensure tasks.md contains implementation checklist with numbered sections and checkbox items

Files:

  • openspec/changes/extend-shell-completions/tasks.md
🧠 Learnings (5)
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` headers in spec delta files

Applied to files:

  • openspec/changes/extend-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/*.md : Scaffold proposal using `proposal.md`, `tasks.md`, optional `design.md`, and delta specs under `openspec/changes/<id>/`

Applied to files:

  • openspec/changes/extend-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Include at least one `#### Scenario:` per requirement in spec delta files

Applied to files:

  • openspec/changes/extend-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/*/proposal.md : Ensure `proposal.md` includes sections: Why (1-2 sentences), What Changes (bullet list with breaking change markers), and Impact (affected specs and code)

Applied to files:

  • openspec/changes/extend-shell-completions/proposal.md
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/*/tasks.md : Ensure `tasks.md` contains implementation checklist with numbered sections and checkbox items

Applied to files:

  • openspec/changes/extend-shell-completions/tasks.md
🧬 Code graph analysis (11)
test/core/completions/generators/bash-generator.test.ts (2)
src/core/completions/generators/bash-generator.ts (1)
  • BashGenerator (7-192)
src/core/completions/types.ts (1)
  • CommandDefinition (36-72)
test/core/completions/installers/bash-installer.test.ts (2)
scripts/postinstall.js (1)
  • installer (84-84)
src/core/completions/installers/bash-installer.ts (1)
  • BashInstaller (11-364)
src/core/completions/generators/powershell-generator.ts (2)
src/core/completions/types.ts (2)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
scripts/postinstall.js (1)
  • script (81-81)
test/core/completions/installers/powershell-installer.test.ts (2)
scripts/postinstall.js (1)
  • installer (84-84)
src/core/completions/installers/powershell-installer.ts (1)
  • PowerShellInstaller (11-326)
src/core/completions/generators/bash-generator.ts (2)
src/core/completions/types.ts (1)
  • CommandDefinition (36-72)
scripts/postinstall.js (1)
  • script (81-81)
test/core/completions/generators/powershell-generator.test.ts (3)
scripts/postinstall.js (2)
  • generator (80-80)
  • script (81-81)
src/core/completions/generators/powershell-generator.ts (1)
  • PowerShellGenerator (7-224)
src/core/completions/types.ts (1)
  • CommandDefinition (36-72)
src/core/completions/factory.ts (1)
src/utils/shell-detection.ts (1)
  • SupportedShell (4-4)
test/core/completions/installers/fish-installer.test.ts (3)
scripts/postinstall.js (1)
  • installer (84-84)
src/core/completions/installers/fish-installer.ts (1)
  • FishInstaller (10-152)
src/utils/file-system.ts (1)
  • fileExists (76-86)
src/core/completions/generators/fish-generator.ts (1)
src/core/completions/types.ts (3)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
  • FlagDefinition (6-31)
src/core/completions/installers/bash-installer.ts (2)
src/utils/file-system.ts (1)
  • FileSystemUtils (44-187)
src/core/completions/factory.ts (1)
  • InstallationResult (15-27)
src/core/completions/installers/fish-installer.ts (1)
src/core/completions/factory.ts (1)
  • InstallationResult (15-27)
🪛 GitHub Actions: CI
test/core/completions/installers/powershell-installer.test.ts

[error] 153-153: configureProfile: should create profile with markers when file does not exist (expected true, received false)


[error] 243-243: ENOENT: no such file or directory, open '/tmp/openspec-powershell-test-6b713d6e-a264-40d9-b000-4b0111399956/.config/powershell/Microsoft.PowerShell_profile.ps1'

🪛 GitHub Check: Test
test/core/completions/installers/powershell-installer.test.ts

[failure] 243-243: test/core/completions/installers/powershell-installer.test.ts > PowerShellInstaller > configureProfile > should generate correct PowerShell syntax in config
Error: ENOENT: no such file or directory, open '/tmp/openspec-powershell-test-6b713d6e-a264-40d9-b000-4b0111399956/.config/powershell/Microsoft.PowerShell_profile.ps1'
❯ test/core/completions/installers/powershell-installer.test.ts:243:23

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'open', path: '/tmp/openspec-powershell-test-6b713d6e-a264-40d9-b000-4b0111399956/.config/powershell/Microsoft.PowerShell_profile.ps1' }


[failure] 153-153: test/core/completions/installers/powershell-installer.test.ts > PowerShellInstaller > configureProfile > should create profile with markers when file does not exist
AssertionError: expected false to be true // Object.is equality

  • Expected
  • Received
  • true
  • false

❯ test/core/completions/installers/powershell-installer.test.ts:153:22

🔇 Additional comments (30)
src/core/completions/generators/fish-generator.ts (1)

1-230: LGTM!

The FishGenerator implementation correctly follows Fish shell completion conventions:

  • Proper use of complete -c with conditions
  • Helper functions for subcommand detection are well-structured
  • Dynamic completion helpers integrate with the CLI's __complete interface
  • Correct escaping of single quotes for Fish syntax
src/core/completions/generators/powershell-generator.ts (1)

1-224: LGTM!

The PowerShellGenerator implementation correctly follows PowerShell completion patterns:

  • Proper use of Register-ArgumentCompleter with scriptblock
  • Token parsing and command/subcommand detection logic is sound
  • Dynamic completion helpers use appropriate PowerShell constructs
  • Double-quote escaping is correct for PowerShell string literals
src/commands/completion.ts (1)

153-159: LGTM!

The warning display logic correctly handles the new warnings field in InstallationResult. The null check and length check prevent unnecessary output, and the blank line provides visual separation.

test/commands/completion.test.ts (4)

80-87: LGTM!

Test correctly updated to use tcsh as an example of an unsupported shell, and the expected error message properly lists all four now-supported shells.


137-144: LGTM!

Install subcommand test correctly updated to match the new supported shell list.


186-193: LGTM!

Uninstall subcommand test correctly updated to match the new supported shell list.


247-257: LGTM!

Shell detection integration test correctly updated to simulate detection of an unsupported shell (tcsh) and verify the appropriate error message.

test/core/completions/generators/fish-generator.test.ts (1)

1-429: LGTM!

Comprehensive test suite covering:

  • Interface compliance verification
  • Script header and helper function generation
  • Flag handling (short/long, boolean vs value-taking, enum values)
  • Subcommand structures
  • All positional argument types
  • Dynamic completion helpers
  • Edge cases (empty commands, escaping)
test/core/completions/generators/bash-generator.test.ts (1)

1-419: LGTM!

Comprehensive test suite with good coverage of:

  • Interface compliance
  • Bash-specific constructs (_init_completion, compgen, COMPREPLY)
  • All flag and positional argument variations
  • Dynamic completion helpers
  • Script registration verification
  • Edge cases
src/core/completions/installers/zsh-installer.ts (1)

5-5: No circular dependency issue. The change centralizes the InstallationResult type in factory.ts, which is the source of truth for this interface. All installers (bash, fish, powershell, zsh) correctly import this shared type from the factory. Since InstallationResult is only a type definition, not a runtime class, TypeScript strips these imports during compilation. This pattern is safe and follows TypeScript best practices.

src/core/completions/generators/bash-generator.ts (1)

166-188: LGTM!

The dynamic completion helpers are well-structured. They correctly invoke the openspec __complete command, redirect stderr to suppress errors, and use cut -f1 to extract the first field (ID) from tab-separated output.

src/core/completions/factory.ts (2)

12-27: LGTM!

The centralized InstallationResult interface with shell-specific optional fields (isOhMyZsh, zshrcConfigured, bashrcConfigured, profileConfigured) is a clean approach that allows each installer to use only the relevant fields while maintaining a unified interface.


51-64: LGTM!

The factory methods correctly handle all four supported shells with proper switch-case exhaustiveness. The default case throwing an error provides a safety net for any future type mismatches.

Also applies to: 73-86

test/core/completions/installers/fish-installer.test.ts (2)

12-20: LGTM!

Good test isolation pattern using randomUUID() for unique temp directories and proper cleanup in afterEach.


195-208: LGTM!

Good testing pattern for permission errors: restoring permissions before assertions ensures proper cleanup even if assertions fail.

src/core/completions/installers/powershell-installer.ts (2)

32-46: LGTM!

Good cross-platform profile path resolution with $PROFILE environment variable check and sensible fallbacks for Windows and Unix-like systems.


85-92: LGTM!

The Test-Path check before sourcing the script is a safe pattern that prevents errors if the script is removed while the profile config remains.

test/core/completions/generators/powershell-generator.test.ts (2)

1-10: LGTM!

Comprehensive test suite with good coverage of interface compliance, script generation, flag handling, subcommands, positional arguments, dynamic helpers, and edge cases.


347-365: LGTM!

Good test for PowerShell quote escaping - verifying that double quotes are escaped by doubling ("") which is the correct PowerShell convention.

openspec/changes/extend-shell-completions/tasks.md (1)

1-49: LGTM!

The tasks.md follows the expected format with numbered sections (phases) and checkbox items. The implementation checklist is comprehensive, covering foundation work, shell-specific implementations, documentation, testing, and validation. All tasks are marked complete, aligning with the code changes in this PR.

Based on learnings, this file correctly implements the expected format.

src/core/completions/installers/fish-installer.ts (2)

1-15: LGTM!

Clean implementation with minimal dependencies. The constructor pattern with optional homeDir parameter enables testability.


52-115: LGTM!

The install flow correctly handles:

  • Idempotence (same content detection)
  • Directory creation with recursive: true
  • Backup on updates
  • Appropriate messaging for different scenarios

Fish's auto-loading behavior means no profile configuration is needed, keeping this installer simpler than Bash/PowerShell.

test/core/completions/installers/bash-installer.test.ts (3)

12-22: LGTM!

Good test isolation pattern with unique temp directories and proper cleanup.


109-139: LGTM!

Proper environment variable handling - saves the original value, sets the test value, and restores it in all code paths (including implicit exception paths via the finally-like pattern).


260-326: LGTM!

Thorough testing of marker-based .bashrc configuration including:

  • Creating new file with markers
  • Prepending to existing file without markers
  • Updating existing markers with new config
  • Preserving user content outside markers
openspec/changes/extend-shell-completions/specs/cli-completion/spec.md (1)

1-328: Spec delta file follows coding guidelines correctly.

The file properly uses:

  • ## MODIFIED Requirements header format
  • ### Requirement: for requirement blocks
  • #### Scenario: format (4 hashtags) for all scenarios
  • Each requirement contains at least one scenario with WHEN/THEN/AND structure

The multi-shell specification is comprehensive and well-structured.

test/core/completions/installers/powershell-installer.test.ts (1)

375-554: Comprehensive install tests with good coverage.

The install test suite covers:

  • First-time installation
  • Directory creation
  • Content verification
  • Idempotent behavior (already installed with same content)
  • Update scenarios with backup
  • Profile configuration (auto and manual)
  • Paths with spaces
  • Permission errors
  • Edge cases (empty script, special characters)

Good test design using real filesystem operations with temporary directories.

src/core/completions/installers/bash-installer.ts (3)

1-24: Clean class structure following established patterns.

The BashInstaller class follows the same structure as other installers (PowerShell, Zsh) with:

  • Dependency injection for homeDir (testability)
  • Consistent marker format for config management
  • Proper TypeScript typing with InstallationResult import from factory

57-63: Method works correctly for user-level installation.

Always using ~/.local/share/bash-completion/completions/openspec is the right choice for user-level installation without requiring root permissions. The comment on line 58 ("Try user-local bash-completion directory first") is slightly misleading since no alternatives are tried, but the implementation is correct.


333-363: Uninstall method correctly delegates confirmation to command layer.

The options.yes parameter is accepted but not used internally, as the JSDoc indicates confirmation is handled by the command layer. This matches the interface contract and allows for consistent behavior across installers.

Comment on lines 25 to 28
script.push('_openspec_completion() {');
script.push(' local cur prev words cword');
script.push(' _init_completion || return');
script.push('');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the bash-generator.ts file to understand the complete context
fd -t f bash-generator.ts -x wc -l {}

Repository: Fission-AI/OpenSpec

Length of output: 118


🏁 Script executed:

# Search for _init_completion and bash-completion references
rg -n "_init_completion|bash-completion" --type=ts -A 2 -B 2

Repository: Fission-AI/OpenSpec

Length of output: 7773


🏁 Script executed:

# Check if there are other shell generators for comparison
fd -t f -path "*/generators/*" ".*-generator\.ts$"

Repository: Fission-AI/OpenSpec

Length of output: 231


🏁 Script executed:

# Look for any documentation about dependencies or setup
fd -t f "README|INSTALL|SETUP|package\.json" -x grep -l "bash-completion" {} \;

Repository: Fission-AI/OpenSpec

Length of output: 45


🏁 Script executed:

# Let's look at the full context of bash-generator.ts around lines 25-28
sed -n '1,50p' src/core/completions/generators/bash-generator.ts

Repository: Fission-AI/OpenSpec

Length of output: 1791


🏁 Script executed:

# Check the bash-installer.ts warnings about bash-completion
sed -n '210,275p' src/core/completions/installers/bash-installer.ts

Repository: Fission-AI/OpenSpec

Length of output: 2577


🏁 Script executed:

# Look at the beginning of bash-generator.ts to see if there's any documentation
head -n 30 src/core/completions/generators/bash-generator.ts

Repository: Fission-AI/OpenSpec

Length of output: 1115


Document bash-completion requirement in generated script header.

The _init_completion function depends on the bash-completion package. While the BashInstaller already checks for this dependency and warns users during installation if it's missing, the generated script header could document this requirement to make the dependency explicit in the script itself.

Consider adding a comment in the generated script header (e.g., # Requires: bash-completion package) for clarity.

🤖 Prompt for AI Agents
In src/core/completions/generators/bash-generator.ts around lines 25 to 28, the
generated completion script calls _init_completion but does not document that
this requires the bash-completion package; add a one-line comment at the top of
the generated script header such as "# Requires: bash-completion package" (or
similar) immediately before or after the function declaration to make the
dependency explicit in the output script.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openspec/specs/cli-completion/spec.md (1)

345-346: Outdated error message contradicts the updated spec.

Line 345's scenario header states users may request bash, fish, or powershell completions, but the error message at line 346 claims "Currently supported: zsh" only. This conflicts with the expanded multi-shell support throughout this spec.

Update the error message to reflect all four supported shells:

"Shell '<name>' is not supported. Supported shells: zsh, bash, fish, powershell"

Alternatively, verify whether this scenario describes a genuinely unsupported shell (e.g., ksh) versus shells now explicitly supported by this PR.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad53abc and 07cad96.

📒 Files selected for processing (1)
  • openspec/specs/cli-completion/spec.md
🧰 Additional context used
📓 Path-based instructions (1)
openspec/specs/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Use SHALL/MUST for normative requirements in spec files; avoid should/may unless intentionally non-normative

Files:

  • openspec/specs/cli-completion/spec.md
🔇 Additional comments (1)
openspec/specs/cli-completion/spec.md (1)

8-8: Multi-shell support spec is comprehensive and well-structured.

The expansion from Zsh-only to bash, fish, and powershell is consistently applied across detection, generation, installation, uninstallation, and architecture patterns. Normative requirements use "SHALL" correctly per spec guidelines. Scenarios follow WHEN/THEN format clearly, with explicit shell-specific behaviors (e.g., single vs. double TAB triggers, completion syntaxes). Factory pattern and interface definitions are precise for implementers.

Also applies to: 18-47, 70-94, 98-135, 170-221, 232-271, 280-338

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/commands/completion.ts (1)

152-158: Consider adding visual distinction for warning messages.

Warnings are currently printed as plain text without any prefix or icon. For better user experience, consider prefixing warnings with a visual indicator (e.g., or Warning:) to distinguish them from regular instructions, similar to how success messages use and errors use .

🔎 Suggested enhancement
         // Display warnings if present
         if (result.warnings && result.warnings.length > 0) {
           console.log('');
           for (const warning of result.warnings) {
-            console.log(warning);
+            console.log(`⚠ ${warning}`);
           }
         }
openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md (2)

123-184: Installation scenarios are comprehensive but lack marker-based update definition.

The scenarios reference "marker-based updates" (lines 133, 141, 151, 167) for shell config modifications, but the marker pattern itself is not formally defined in the spec. Ensure this pattern is documented (e.g., using begin/end markers like # BEGIN openspec-completion / # END openspec-completion) either in a separate requirement or as implementation guidance.


233-292: Architecture patterns have implementation-specific details that should be generalized.

  1. Line 272 — Caching TTL is overly specific: Specifying "2-second TTL" is a fine-grained implementation choice; the spec should instead state something like "implement caching to avoid redundant filesystem operations" and let implementation decide appropriate duration based on trade-offs.

  2. Line 282 — positionalType values are underspecified: The spec defines positionalType: string but doesn't enumerate valid values. The example mentions "change-id, spec-id, path, shell" — these should be formally listed as a union type or enum.

🔎 Suggested refinements
- **AND** implement caching with 2-second TTL using class properties
+ **AND** implement caching to avoid redundant filesystem operations during rapid successive calls

- **AND** `positionalType: string` - Type of positional (change-id, spec-id, path, shell)
+ **AND** `positionalType: 'change-id' | 'spec-id' | 'path' | 'shell'` - Type of positional argument
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07cad96 and 9dd9962.

📒 Files selected for processing (6)
  • openspec/changes/archive/2025-12-23-extend-shell-completions/proposal.md
  • openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/archive/2025-12-23-extend-shell-completions/tasks.md
  • src/commands/completion.ts
  • src/utils/file-system.ts
  • test/utils/file-system.test.ts
✅ Files skipped from review due to trivial changes (1)
  • openspec/changes/archive/2025-12-23-extend-shell-completions/proposal.md
🧰 Additional context used
📓 Path-based instructions (2)
openspec/changes/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Scaffold proposal using proposal.md, tasks.md, optional design.md, and delta specs under openspec/changes/<id>/

Files:

  • openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/archive/2025-12-23-extend-shell-completions/tasks.md
openspec/changes/**/specs/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/spec.md: Use ## ADDED|MODIFIED|REMOVED|RENAMED Requirements headers in spec delta files
Include at least one #### Scenario: per requirement in spec delta files
Use #### Scenario: Name format (4 hashtags) for scenario headers, not bullets or bold text
Use ## ADDED Requirements for new orthogonal capabilities that can stand alone; use ## MODIFIED Requirements for behavior changes of existing requirements
When using MODIFIED Requirements, paste the full requirement block including header and all scenarios

Files:

  • openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md
🧠 Learnings (2)
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` headers in spec delta files

Applied to files:

  • openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/*/tasks.md : Ensure `tasks.md` contains implementation checklist with numbered sections and checkbox items

Applied to files:

  • openspec/changes/archive/2025-12-23-extend-shell-completions/tasks.md
🔇 Additional comments (11)
openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md (4)

5-46: Shell-specific scenarios are well-formed.

The Native Shell Behavior Integration requirement correctly delineates shell-specific UX patterns with WHEN/THEN structure. The negation scenario (lines 41–46) is a good boundary-setting constraint.


48-81: Shell detection scenarios are comprehensive and well-specified.

The detection logic appropriately handles platform-specific environment variables ($SHELL for Unix shells, $PSModulePath for PowerShell). Scenarios are clear and the error message provides useful context.


185-231: Uninstallation scenarios are well-structured and symmetric with installation.

The scenarios properly mirror installation flows with appropriate confirmations and error handling. Marker-based removal pattern (referenced in lines 196, 204, 218) shares the definition concern flagged in the Installation section above.


294-327: Testing support scenarios provide appropriate guidance for multi-shell coverage.

The scenarios correctly emphasize dependency injection, temporary test directories, and cross-shell consistency validation. This requirement appears entirely new (not a modification of existing testing), which supports the earlier recommendation to separate ADDED vs MODIFIED requirements.

openspec/changes/archive/2025-12-23-extend-shell-completions/tasks.md (1)

1-49: Implementation tasks correctly reference existing codebase artifacts and follow required structure.

The tasks.md file uses proper scaffolding with numbered phases (1–5) and checkbox items as required. Verification confirms all referenced implementation artifacts exist: generators and installers for bash, fish, and PowerShell; the CompletionFactory and interfaces; updated SupportedShell type; and corresponding test files. The structure is sound and complete.

Likely an incorrect or invalid review comment.

src/utils/file-system.ts (2)

88-121: LGTM! Well-structured directory traversal logic.

The new helper correctly walks up the directory tree to find the first existing directory. The infinite loop is safe due to explicit exit conditions (lines 100, 104, 111), and the root detection check at line 109 (parentDir === currentDir) properly prevents infinite traversal. Edge cases are appropriately handled with debug logging.


140-156: Robust handling of non-existent file paths.

The updated ENOENT handling correctly uses the new helper to traverse up to the first existing directory, then checks its write permissions. This properly handles deeply nested non-existent paths that the previous implementation may not have covered.

Note: There's an inherent TOCTOU (time-of-check to time-of-use) limitation where permissions could change between this check and actual write operations, but this is acceptable for the method's purpose of checking current state.

test/utils/file-system.test.ts (4)

227-236: Excellent edge case test for file-in-path scenario.

This test cleverly validates the behavior when an intermediate path component is a file rather than a directory. The implementation correctly handles this: findFirstExistingDirectory will find the file, detect it's not a directory (line 99 in the implementation), and return null, causing canWriteFile to return false. This prevents false positives in edge cases where filesystem structure is unusual.


238-252: Good coverage of symlinks and cross-platform paths.

The symlink test correctly validates that canWriteFile follows symbolic links (since fs.stat dereferences symlinks by default). The platform-specific separator test using joinPath ensures the method works correctly with platform-appropriate path construction.


199-210: This test's cross-platform reliability is uncertain due to Windows directory permission semantics.

The test uses fs.chmod(readOnlyDir, 0o555) to create a read-only directory. On Windows, this sets the read-only file attribute, but for directories, this attribute has different semantics than Unix permissions—it may not prevent writes in the way the test expects. Although the canWriteFile() implementation correctly uses fs.access() with W_OK instead of relying on stats.mode, the test setup itself may not reliably simulate the intended read-only condition on Windows since the test runs in CI on windows-latest.

Consider either: (1) adding a skip condition for Windows (skipIf(process.platform === 'win32')), or (2) verifying that fs.access(W_OK) on the readOnlyDir actually returns false after chmod(0o555) on Windows before finalizing this test.


165-197: Comprehensive test coverage for basic scenarios.

The tests for writable files (lines 165-171), non-existent files in writable directories (lines 185-190), and deeply nested non-existent paths (lines 192-197) provide excellent coverage and will validate the new findFirstExistingDirectory helper.

The read-only file test (lines 173-183) is properly cross-platform compatible. The canWriteFile implementation uses fs.access() with W_OK to check write permissions rather than relying on stats.mode, which handles both Unix chmod-based permissions and Windows NTFS attributes correctly. The test will work as expected on all platforms.

noameron and others added 3 commits December 27, 2025 11:42
  - Add _init_completion fallback for systems without bash-completion
  - Fix command injection escaping in Fish/PowerShell generators
  - Add Bash command name escaping for security
  - Add comprehensive security tests for all generators
  - Fix test placement issues in bash/powershell test files
…ompletions' into feature/bash_fish_power_shells_completions
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
test/core/completions/installers/powershell-installer.test.ts (2)

147-159: Test fails because parent directory is not created before calling configureProfile.

The test needs to create the parent directory structure before calling configureProfile. Other tests in this block correctly create the directory with await fs.mkdir(path.dirname(profilePath), { recursive: true }) before the test.

🔎 Proposed fix
    it('should create profile with markers when file does not exist', async () => {
      delete process.env.OPENSPEC_NO_AUTO_CONFIG;
      const profilePath = installer.getProfilePath();
+     await fs.mkdir(path.dirname(profilePath), { recursive: true });

      const result = await installer.configureProfile(mockScriptPath);

237-248: Same issue: missing parent directory creation causes test failure.

This test also needs to create the parent directory before calling configureProfile. The subsequent file read operation fails because the profile cannot be created without its parent directory.

🔎 Proposed fix
    it('should generate correct PowerShell syntax in config', async () => {
      delete process.env.OPENSPEC_NO_AUTO_CONFIG;
      const profilePath = installer.getProfilePath();
+     await fs.mkdir(path.dirname(profilePath), { recursive: true });

      await installer.configureProfile(mockScriptPath);
🧹 Nitpick comments (2)
src/core/completions/generators/fish-generator.ts (1)

178-180: Consider deriving shell list from a central source.

The hardcoded list 'zsh bash fish powershell' could become out of sync if new shells are added. This is acceptable for now, but consider importing from a shared constant (e.g., from types.ts where SupportedShell is defined) for consistency.

src/core/completions/installers/bash-installer.ts (1)

259-269: Consider making bash-completion installation instructions cross-platform.

The warning currently provides macOS/Homebrew-specific instructions. While helpful, users on Linux distributions would need different commands (e.g., apt install bash-completion or yum install bash-completion).

This is acceptable as-is since the warning alerts users to the dependency, but could be enhanced to detect the platform and provide appropriate instructions for the user's system.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f69feab and 71f434b.

📒 Files selected for processing (9)
  • src/core/completions/generators/bash-generator.ts
  • src/core/completions/generators/fish-generator.ts
  • src/core/completions/generators/powershell-generator.ts
  • src/core/completions/installers/bash-installer.ts
  • src/core/completions/installers/powershell-installer.ts
  • test/core/completions/generators/bash-generator.test.ts
  • test/core/completions/generators/fish-generator.test.ts
  • test/core/completions/generators/powershell-generator.test.ts
  • test/core/completions/installers/powershell-installer.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/completions/generators/bash-generator.ts
  • test/core/completions/generators/bash-generator.test.ts
🧰 Additional context used
🧬 Code graph analysis (6)
test/core/completions/generators/fish-generator.test.ts (3)
scripts/postinstall.js (2)
  • generator (80-80)
  • script (81-81)
src/core/completions/generators/fish-generator.ts (1)
  • FishGenerator (7-234)
src/core/completions/types.ts (1)
  • CommandDefinition (36-72)
src/core/completions/installers/bash-installer.ts (1)
src/core/completions/factory.ts (1)
  • InstallationResult (15-27)
src/core/completions/installers/powershell-installer.ts (2)
src/utils/file-system.ts (1)
  • FileSystemUtils (44-252)
src/core/completions/factory.ts (1)
  • InstallationResult (15-27)
src/core/completions/generators/powershell-generator.ts (2)
src/core/completions/types.ts (2)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
scripts/postinstall.js (1)
  • script (81-81)
src/core/completions/generators/fish-generator.ts (1)
src/core/completions/types.ts (3)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
  • FlagDefinition (6-31)
test/core/completions/installers/powershell-installer.test.ts (1)
src/core/completions/installers/powershell-installer.ts (1)
  • PowerShellInstaller (11-326)
🔇 Additional comments (9)
test/core/completions/generators/fish-generator.test.ts (1)

1-531: Comprehensive test suite for FishGenerator - LGTM!

The test file provides excellent coverage including:

  • Interface compliance verification
  • Command and flag generation with various configurations
  • Positional argument handling for different types
  • Dynamic completion helpers
  • Security-focused tests for shell metacharacter escaping

The security tests (lines 431-531) are particularly valuable, ensuring that command injection vectors like $(), backticks, and other shell metacharacters are properly escaped.

src/core/completions/generators/fish-generator.ts (2)

16-63: Well-structured generate method.

The main generate method is well-organized with clear sections for header, helper functions, top-level commands, and command-specific completions. The use of script.join('\n') for output construction is clean and maintainable.


227-233: Escape method correctly handles shell metacharacters.

The ordering is correct (backslashes first to avoid double-escaping), and the key metacharacters for Fish single-quoted strings are covered. This aligns well with the security tests in the test file.

test/core/completions/generators/powershell-generator.test.ts (1)

448-531: Excellent security test coverage!

The security tests comprehensively validate escaping of PowerShell metacharacters that could lead to command injection vulnerabilities. Testing $() subexpressions, backticks, dollar signs, and double quotes together (as in the final test) ensures the escaping logic handles real-world dangerous inputs.

src/core/completions/installers/powershell-installer.ts (2)

100-130: Well-designed profile configuration with proper safeguards.

The implementation correctly:

  • Respects the OPENSPEC_NO_AUTO_CONFIG flag for user control
  • Checks write permissions before attempting modifications
  • Uses marker-based updates for idempotent configuration
  • Fails gracefully without breaking the installation

194-263: Robust installation implementation with excellent UX.

The install method demonstrates good engineering:

  • Detects and handles both fresh installs and updates appropriately
  • Creates backups before modifying existing installations
  • Attempts auto-configuration with graceful fallback to manual instructions
  • Returns comprehensive status information including paths, backup location, and user guidance
src/core/completions/generators/powershell-generator.ts (2)

221-226: Security-critical escaping is correctly implemented.

The escape sequence order is correct for PowerShell:

  1. Backticks first (escape character itself)
  2. Dollar signs (prevents variable expansion and $() injection)
  3. Double quotes (string delimiters)

This prevents command injection while preserving description text. The comprehensive test coverage validates this logic.


187-216: Well-structured dynamic completion helpers.

The helper functions correctly:

  • Shell out to the CLI's __complete command for dynamic data
  • Parse the tab-delimited output to extract IDs
  • Suppress errors with 2>$null to avoid polluting completion output
  • Return properly formatted arrays for PowerShell's completion system
src/core/completions/installers/bash-installer.ts (1)

117-147: Consistent and well-structured configuration management.

The .bashrc configuration follows the same reliable pattern as the PowerShell installer:

  • Respects user opt-out via OPENSPEC_NO_AUTO_CONFIG
  • Checks write permissions before modifications
  • Uses markers for safe, idempotent updates
  • Fails gracefully without breaking installation

@TabishB
Copy link
Contributor

TabishB commented Dec 27, 2025

!reviewfast

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

Copy link
Contributor

@TabishB TabishB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main comment I have is we mention zsh only in a lot of places even though we're now supporting multiple shells.

From the AI Code review I ran to check this:

Spec drift still claims Zsh-only in a few places: purpose (openspec/specs/cli-completion/spec.md:4), command list (openspec/specs/cli-completion/spec.md:58), unsupported-shell message (openspec/specs/cli-completion/spec.md:346), and “non‑Zsh shell” wording (openspec/specs/cli-completion/spec.md:364).

Zsh generator completes shell positional as zsh only (src/core/completions/generators/zsh-generator.ts:296); should list zsh|bash|fish|powershell like the other generators.

Completion registry omits completion uninstall --yes/-y even though the CLI supports it (src/core/completions/command-registry.ts:283, compare src/cli/index.ts:294).

UX is still zsh-centric:

  • Install “next steps” only print for zshrcConfigured (src/commands/completion.ts:166) so bash/profile auto-config can end with no reload hint.
  • Verbose output only mentions ~/.zshrc (src/commands/completion.ts:147).
  • Uninstall prompt always says ~/.zshrc (src/commands/completion.ts:191), even for bash/fish/powershell.


/**
* Generate dynamic completion helper functions for change and spec IDs
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor don't need to fix

lol I just realised there's a tab completion for openspec completion generate <TAB> that was added as part of generatePositionalSpec . <- This only returns zsh

If we're supporting more shells would make sense to add more in ... but this is obviously a little awkward as the tab completion only works if the user already has a completion installed.

}
}

// Display warnings if present
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a bunch of zshrcConfigured checks in this file, do we need to extend for other shells here too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly I think there's a few places where we focus on zsh heavily. For example there's a restart hint for zsh but not for other shells.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think even when uninstalling we mention zshrc

* @returns true if bash-completion directories exist
*/
async isBashCompletionInstalled(): Promise<boolean> {
const paths = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are these paths determined? Does it work if you use homebrew too for example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have static paths to check for, for example:
'/usr/share/bash-completion',
'/usr/local/share/bash-completion',
'/etc/bash_completion.d'

I'll also add these for homebrew:
'/opt/homebrew/etc/bash_completion.d',
'/usr/local/etc/bash_completion.d',

// Fall back to platform-specific defaults
if (process.platform === 'win32') {
// Windows: Documents/PowerShell/Microsoft.PowerShell_profile.ps1
return path.join(this.homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documents/PowerShell/ is the profile location for PowerShell Core (6+). Windows PowerShell 5.1 uses Documents/WindowsPowerShell/ instead. Since the spec says "support both Windows PowerShell 5.1 and PowerShell Core 7+", this needs to handle both paths

noameron and others added 2 commits January 1, 2026 15:36
Extract static template literals from generators into separate template files.
Standardize naming to {SHELL}_STATIC_HELPERS and {SHELL}_DYNAMIC_HELPERS.

- Create bash/fish/powershell/zsh template files
- Rename constants: BASH_HELPERS → BASH_DYNAMIC_HELPERS,
  FISH_HELPER_FUNCTIONS → FISH_STATIC_HELPERS,
  POWERSHELL_HELPERS → POWERSHELL_DYNAMIC_HELPERS
- Update generator imports
- Remove ~99 lines of boilerplate from generators

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/core/completions/generators/zsh-generator.ts (1)

247-247: Update shell completions to include newly supported shells.

The shell completion only offers zsh, but this PR adds support for bash, fish, and powershell. Update the completion list to reflect all supported shells.

🔎 Proposed fix
       case 'shell':
-        return "'*:shell:(zsh)'";
+        return "'*:shell:(zsh bash fish powershell)'";
       default:
src/core/completions/generators/powershell-generator.ts (1)

168-172: Hard-coded shell list creates maintenance burden.

This is the same issue as in bash-generator.ts line 156. The shell list is duplicated here, creating a maintenance risk if new shells are added.

See the refactoring suggestion in bash-generator.ts (lines 155-157) to centralize the shell list in types.ts. Apply the same pattern here:

+import { SUPPORTED_SHELLS } from '../types.js';
...
       case 'shell':
-        lines.push(`${indent}$shells = @("zsh", "bash", "fish", "powershell")`);
+        lines.push(`${indent}$shells = @(${SUPPORTED_SHELLS.map(s => `"${s}"`).join(', ')})`);
         lines.push(`${indent}$shells | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {`);
🧹 Nitpick comments (4)
src/core/completions/templates/zsh-templates.ts (1)

1-4: Clarify documentation - these are dynamic helpers, not static.

The comment describes these as "static template strings" and "helper functions that never change," but ZSH_DYNAMIC_HELPERS contains functions that dynamically invoke openspec __complete commands. Consider renaming or clarifying the comment to reflect that while the template structure is static, the helpers perform dynamic completion.

🔎 Proposed documentation fix
 /**
- * Static template strings for Zsh completion scripts.
- * These are Zsh-specific helper functions that never change.
+ * Dynamic helper template for Zsh completion scripts.
+ * These Zsh-specific functions dynamically fetch completion data from openspec.
  */
src/core/completions/templates/powershell-templates.ts (1)

1-4: Clarify documentation - these are dynamic helpers, not static.

The comment describes these as "static template strings" and "helper functions that never change," but the helpers dynamically execute openspec __complete commands. This same issue appears in bash-templates.ts and zsh-templates.ts. Consider updating the documentation to clarify that while the template is static, the functions perform dynamic operations.

🔎 Proposed documentation fix
 /**
- * Static template strings for PowerShell completion scripts.
- * These are PowerShell-specific helper functions that never change.
+ * Dynamic helper template for PowerShell completion scripts.
+ * These PowerShell-specific functions dynamically fetch completion data from openspec.
  */
src/core/completions/templates/bash-templates.ts (1)

1-4: Clarify documentation - these are dynamic helpers, not static.

Same documentation inconsistency as in zsh-templates.ts and powershell-templates.ts: the comment says "static template strings" and "never change," but these are dynamic helpers that execute commands.

🔎 Proposed documentation fix
 /**
- * Static template strings for Bash completion scripts.
- * These are Bash-specific helper functions that never change.
+ * Dynamic helper template for Bash completion scripts.
+ * These Bash-specific functions dynamically fetch completion data from openspec.
  */
src/core/completions/generators/bash-generator.ts (1)

155-157: Hard-coded shell list creates maintenance burden.

The shell list "zsh bash fish powershell" is hard-coded here and duplicated in powershell-generator.ts (line 169). If a new shell is added to the project, both locations must be updated manually, risking inconsistency.

🔎 Centralize shell list in types.ts

In src/core/completions/types.ts, add:

export const SUPPORTED_SHELLS = ['zsh', 'bash', 'fish', 'powershell'] as const;
export const SUPPORTED_SHELLS_STRING = SUPPORTED_SHELLS.join(' ');

Then import and use in this file:

+import { SUPPORTED_SHELLS_STRING } from '../types.js';
...
       case 'shell':
-        lines.push(`${indent}local shells="zsh bash fish powershell"`);
+        lines.push(`${indent}local shells="${SUPPORTED_SHELLS_STRING}"`);
         lines.push(`${indent}COMPREPLY=($(compgen -W "$shells" -- "$cur"))`);

Apply the same change in powershell-generator.ts.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71f434b and f624d3f.

📒 Files selected for processing (8)
  • src/core/completions/generators/bash-generator.ts
  • src/core/completions/generators/fish-generator.ts
  • src/core/completions/generators/powershell-generator.ts
  • src/core/completions/generators/zsh-generator.ts
  • src/core/completions/templates/bash-templates.ts
  • src/core/completions/templates/fish-templates.ts
  • src/core/completions/templates/powershell-templates.ts
  • src/core/completions/templates/zsh-templates.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/completions/generators/fish-generator.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/core/completions/generators/bash-generator.ts (2)
src/core/completions/types.ts (2)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
src/core/completions/templates/bash-templates.ts (1)
  • BASH_DYNAMIC_HELPERS (6-24)
src/core/completions/generators/zsh-generator.ts (1)
src/core/completions/templates/zsh-templates.ts (1)
  • ZSH_DYNAMIC_HELPERS (6-36)
src/core/completions/generators/powershell-generator.ts (2)
src/core/completions/types.ts (2)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
src/core/completions/templates/powershell-templates.ts (1)
  • POWERSHELL_DYNAMIC_HELPERS (6-25)
🔇 Additional comments (12)
src/core/completions/templates/fish-templates.ts (1)

6-21: LGTM!

The static helper functions are well-structured and follow Fish shell conventions correctly.

src/core/completions/templates/zsh-templates.ts (1)

6-36: LGTM!

The Zsh helper functions are correctly implemented with proper process substitution, tab-separated parsing, and _describe usage.

src/core/completions/generators/zsh-generator.ts (1)

17-81: LGTM!

The refactoring to template-based assembly with explicit array building improves readability and maintainability. The use of ZSH_DYNAMIC_HELPERS from the template module properly separates concerns.

src/core/completions/templates/bash-templates.ts (1)

6-24: LGTM!

The Bash helper functions are correctly implemented using standard Bash completion patterns with compgen, proper command substitution, and tab-delimited field extraction via cut. The inclusion of all three helper functions (changes, specs, and items) maintains consistency with Fish and Zsh templates.

src/core/completions/generators/bash-generator.ts (4)

17-28: Code structure addresses prior feedback.

The use of push() for loop-based construction (lines 22-27) followed by template literal assembly (lines 34-75) is a reasonable pattern for complex logic with loops and conditionals. While template literals throughout (as TabishB suggested) could work, the current approach offers better clarity when building conditional blocks programmatically.


81-107: LGTM!

The command case generation logic correctly handles both subcommand scenarios and direct argument completion. The recursive delegation to generateArgumentCompletion() for subcommands is clean and maintainable.


112-137: LGTM!

The argument completion logic properly prioritizes flag completion (when cur starts with -) before falling back to positional completions. This matches standard Bash completion behavior.


171-174: LGTM!

The escape logic correctly handles Bash shell metacharacters (", $, `, \) to prevent command injection. The replacement pattern is secure and comprehensive.

src/core/completions/generators/powershell-generator.ts (4)

17-68: LGTM!

The PowerShell completion script structure is well-organized. The use of push() for loop-based construction followed by template literal assembly provides good clarity. The script correctly uses Register-ArgumentCompleter and handles the top-level command/subcommand flow with appropriate token parsing.


73-106: LGTM!

The command case generation logic correctly handles subcommand scenarios with proper indentation and delegation. The nested switch statement for subcommands (lines 91-99) is clean and matches PowerShell conventions.


111-143: LGTM!

The argument completion logic properly handles flags (checking for -* prefix) and includes both long and short flag forms when available. The delegation to positional completion is clean.


185-190: LGTM!

The escape logic correctly handles PowerShell-specific special characters:

  • Backticks (PowerShell's escape character)
  • Dollar signs (prevent variable expansion)
  • Double quotes (string delimiters)

The escaping is secure and comprehensive for PowerShell string contexts.

Comment on lines +6 to +25
export const POWERSHELL_DYNAMIC_HELPERS = `# Dynamic completion helpers
function Get-OpenSpecChanges {
$output = openspec __complete changes 2>$null
if ($output) {
$output | ForEach-Object {
($_ -split "\\t")[0]
}
}
}
function Get-OpenSpecSpecs {
$output = openspec __complete specs 2>$null
if ($output) {
$output | ForEach-Object {
($_ -split "\\t")[0]
}
}
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a combined items function for consistency with other shells.

All other shell templates (Bash, Fish, and Zsh) provide a combined function that returns both changes and specs (e.g., _openspec_complete_items, __fish_openspec_items). PowerShell is missing this combined helper, which creates an inconsistency in the multi-shell completion architecture.

🔎 Proposed combined items function
 function Get-OpenSpecSpecs {
     $output = openspec __complete specs 2>$null
     if ($output) {
         $output | ForEach-Object {
             ($_ -split "\\t")[0]
         }
     }
 }
+
+function Get-OpenSpecItems {
+    $changes = openspec __complete changes 2>$null
+    $specs = openspec __complete specs 2>$null
+    $combined = @()
+    if ($changes) {
+        $combined += $changes | ForEach-Object { ($_ -split "\\t")[0] }
+    }
+    if ($specs) {
+        $combined += $specs | ForEach-Object { ($_ -split "\\t")[0] }
+    }
+    $combined
+}
 `;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const POWERSHELL_DYNAMIC_HELPERS = `# Dynamic completion helpers
function Get-OpenSpecChanges {
$output = openspec __complete changes 2>$null
if ($output) {
$output | ForEach-Object {
($_ -split "\\t")[0]
}
}
}
function Get-OpenSpecSpecs {
$output = openspec __complete specs 2>$null
if ($output) {
$output | ForEach-Object {
($_ -split "\\t")[0]
}
}
}
`;
export const POWERSHELL_DYNAMIC_HELPERS = `# Dynamic completion helpers
function Get-OpenSpecChanges {
$output = openspec __complete changes 2>$null
if ($output) {
$output | ForEach-Object {
($_ -split "\\t")[0]
}
}
}
function Get-OpenSpecSpecs {
$output = openspec __complete specs 2>$null
if ($output) {
$output | ForEach-Object {
($_ -split "\\t")[0]
}
}
}
function Get-OpenSpecItems {
$changes = openspec __complete changes 2>$null
$specs = openspec __complete specs 2>$null
$combined = @()
if ($changes) {
$combined += $changes | ForEach-Object { ($_ -split "\\t")[0] }
}
if ($specs) {
$combined += $specs | ForEach-Object { ($_ -split "\\t")[0] }
}
$combined
}
`;
🤖 Prompt for AI Agents
In src/core/completions/templates/powershell-templates.ts around lines 6 to 25,
PowerShell is missing a combined items helper (like other shells) that returns
both changes and specs; add a new function Get-OpenSpecItems that calls
Get-OpenSpecChanges and Get-OpenSpecSpecs, merges their outputs (preserving
order or deduplicating as needed), and writes the combined list to stdout so the
completion logic can consume a single unified items helper consistent with
Bash/Fish/Zsh.

@TabishB
Copy link
Contributor

TabishB commented Jan 5, 2026

@noameron Could you check the 2 critical coderabbit errors highlighted

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openspec/specs/cli-completion/spec.md (1)

4-41: Spec: tighten normative language + avoid overly absolute “double TAB” claim for Bash.
Per openspec/specs/**/spec.md guidelines, the new normative bullets should consistently use SHALL/MUST (several say “support …” / “leverage …” without SHALL). Also, “completions SHALL trigger on double TAB” for Bash is not reliably true (single TAB can complete unambiguous tokens; double TAB typically lists options).

Example wording adjustments
- - **AND** completions SHALL trigger on double TAB (standard Bash behavior)
+ - **AND** completions SHALL follow standard Bash behavior (TAB completes; a second TAB may list options)

- - **AND** support both bash-completion v1 and v2 patterns
+ - **AND** completions SHALL support both bash-completion v1 and v2 patterns

- - **AND** leverage Fish's built-in caching automatically
+ - **AND** completions SHALL leverage Fish's built-in caching automatically
🤖 Fix all issues with AI agents
In @openspec/specs/cli-completion/spec.md:
- Around line 70-95: Summary: The spec's validation/error bullets are
non-normative; change them to mandatory normative language. Update the
enumerated bullets and scenario outcomes (e.g., the lines that currently read
“validate the shell is one of: `zsh`, `bash`, `fish`, `powershell`” and “throw
an error if the shell is not supported”, and the scenario THEN/THROW lines such
as "THEN throw error: "Shell '<name>' is not supported. Supported shells: zsh,
bash, fish, powershell"") to use SHALL/MUST wording (e.g., "The system SHALL
validate that the shell is one of …" and "The system SHALL throw an error 'Shell
'<name>' is not supported. Supported shells: zsh, bash, fish, powershell' when
not supported"), and apply the same normative phrasing to each scenario outcome
for Bash, Fish, and PowerShell detection so the behavior is unambiguous and
mandatory.

In @src/core/completions/generators/bash-generator.ts:
- Around line 79-105: generateCommandCase currently, when cmd.subcommands
exists, only completes subcommands at cword == 2 and never offers parent-level
flags; modify generateCommandCase so that before emitting the subcommands list
for cword == 2 it also builds and offers parent flags (cmd.flags) combined with
subcommands (or offers flags first), by including cmd.flags (properly escaped)
in the local completions string or by calling generateArgumentCompletion(cmd,
indent) for the parent at that cword check; ensure you reference cmd.flags and
reuse escapeCommandName and generateArgumentCompletion so parent flags like
--scope are offered at word 2 while keeping the per-subcommand case block
intact.
🧹 Nitpick comments (2)
src/core/completions/command-registry.ts (1)

287-293: Good addition: completion uninstall --yes/-y makes non-interactive flows scriptable.
Small consistency nit: there are multiple ad-hoc { name: 'yes', short: 'y' } flag definitions in this registry (e.g., archive, config reset, now here). Consider promoting a COMMON_FLAGS.yes to avoid drift.

src/core/completions/generators/bash-generator.ts (1)

18-29: Consider escaping/quoting command names consistently in Bash case patterns.
Top-level command list uses escapeCommandName() (Line 19), but caseLines.push(\ ${cmd.name})`)(Line 24) uses raw names, which become glob patterns incase`. Probably safe with your current fixed command set, but easy to harden by escaping or using a literal match pattern.

Also applies to: 63-66, 169-172

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f624d3f and b9255cf.

📒 Files selected for processing (4)
  • openspec/specs/cli-completion/spec.md
  • src/core/completions/command-registry.ts
  • src/core/completions/generators/bash-generator.ts
  • src/core/completions/generators/zsh-generator.ts
🧰 Additional context used
📓 Path-based instructions (1)
openspec/specs/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Use SHALL/MUST for normative requirements in spec files; avoid should/may unless intentionally non-normative

Files:

  • openspec/specs/cli-completion/spec.md
🧬 Code graph analysis (2)
src/core/completions/generators/zsh-generator.ts (1)
src/core/completions/templates/zsh-templates.ts (1)
  • ZSH_DYNAMIC_HELPERS (6-36)
src/core/completions/generators/bash-generator.ts (2)
src/core/completions/types.ts (2)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
src/core/completions/templates/bash-templates.ts (1)
  • BASH_DYNAMIC_HELPERS (6-24)
🔇 Additional comments (2)
src/core/completions/generators/zsh-generator.ts (2)

246-248: shell positional completion updated correctly to include all supported shells.


2-80: Clarify: top-level dispatch is correct, but subcommand dispatch may use wrong word index.

The top-level dispatcher correctly uses case $words[1] in (line 70)—$words[1] is the first argument after openspec (the subcommand name), not openspec itself. However, in subcommand dispatch (line 122), the generated code also uses case $words[1] when it should likely use $words[2] for nested subcommands. When a user types openspec change show <TAB>, $words[1] refers to "change", but the code tries to match against subcommand names like "show". Tests do not validate actual zsh completion execution, only that strings are generated; this could be a "works locally but fails under real zsh" scenario.

Comment on lines +79 to +105
private generateCommandCase(cmd: CommandDefinition, indent: string): string[] {
const lines: string[] = [];

// Handle subcommands
if (cmd.subcommands && cmd.subcommands.length > 0) {
lines.push(`${indent}if [[ $cword -eq 2 ]]; then`);
lines.push(`${indent} local subcommands="` + cmd.subcommands.map(s => this.escapeCommandName(s.name)).join(' ') + '"');
lines.push(`${indent} COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))`);
lines.push(`${indent} return 0`);
lines.push(`${indent}fi`);
lines.push('');
lines.push(`${indent}case "$subcmd" in`);

for (const subcmd of cmd.subcommands) {
lines.push(`${indent} ${subcmd.name})`);
lines.push(...this.generateArgumentCompletion(subcmd, indent + ' '));
lines.push(`${indent} ;;`);
}

lines.push(`${indent}esac`);
} else {
// No subcommands, just complete arguments
lines.push(...this.generateArgumentCompletion(cmd, indent));
}

return lines;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: parent flags are not completable for commands with subcommands (e.g., openspec config --scope ...).
generateCommandCase() enters the “has subcommands” branch (Line 83) and, at cword -eq 2, only offers subcommands (Line 84-88). It never offers cmd.flags at the parent level in that flow. This undermines “same commands/flags across shells” expectations for commands like config that have both flags and subcommands.

Proposed fix (complete parent flags at word 2 before subcommand)
   private generateCommandCase(cmd: CommandDefinition, indent: string): string[] {
     const lines: string[] = [];

     // Handle subcommands
     if (cmd.subcommands && cmd.subcommands.length > 0) {
       lines.push(`${indent}if [[ $cword -eq 2 ]]; then`);
+      if (cmd.flags.length > 0) {
+        const flags = cmd.flags.map(f => {
+          const parts: string[] = [];
+          if (f.short) parts.push(`-${f.short}`);
+          parts.push(`--${f.name}`);
+          return parts.join(' ');
+        }).join(' ');
+        lines.push(`${indent}  if [[ "$cur" == -* ]]; then`);
+        lines.push(`${indent}    local flags="${flags}"`);
+        lines.push(`${indent}    COMPREPLY=($(compgen -W "$flags" -- "$cur"))`);
+        lines.push(`${indent}    return 0`);
+        lines.push(`${indent}  fi`);
+      }
       lines.push(`${indent}  local subcommands="` + cmd.subcommands.map(s => this.escapeCommandName(s.name)).join(' ') + '"');
       lines.push(`${indent}  COMPREPLY=($(compgen -W "$subcommands" -- "$cur"))`);
       lines.push(`${indent}  return 0`);
       lines.push(`${indent}fi`);
       lines.push('');
       lines.push(`${indent}case "$subcmd" in`);

Also applies to: 110-135

🤖 Prompt for AI Agents
In @src/core/completions/generators/bash-generator.ts around lines 79 - 105,
generateCommandCase currently, when cmd.subcommands exists, only completes
subcommands at cword == 2 and never offers parent-level flags; modify
generateCommandCase so that before emitting the subcommands list for cword == 2
it also builds and offers parent flags (cmd.flags) combined with subcommands (or
offers flags first), by including cmd.flags (properly escaped) in the local
completions string or by calling generateArgumentCompletion(cmd, indent) for the
parent at that cword check; ensure you reference cmd.flags and reuse
escapeCommandName and generateArgumentCompletion so parent flags like --scope
are offered at word 2 while keeping the per-subcommand case block intact.

noameron and others added 3 commits January 9, 2026 13:06
Fish's echo doesn't interpret escape sequences, so \t outputs
literally instead of as a tab character. Use printf for proper
tab-separated completion output.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (9)
src/core/completions/installers/powershell-installer.ts (6)

4-4: Unused import: FileSystemUtils

The FileSystemUtils is imported but never used in this file. Unlike BashInstaller which uses FileSystemUtils.canWriteFile() and FileSystemUtils.updateFileWithMarkers(), this installer implements its own logic directly with fs.

🔧 Suggested fix
 import { promises as fs } from 'fs';
 import path from 'path';
 import os from 'os';
-import { FileSystemUtils } from '../../../utils/file-system.js';
 import { InstallationResult } from '../factory.js';

109-116: Unused private method: generateProfileConfig

This method generates a profile configuration block but is never called anywhere in the class. The configureProfile method at lines 148-154 constructs a different block inline instead.

🔧 Either use this method or remove it

Option 1 - Remove the unused method:

-  /**
-   * Generate PowerShell profile configuration content
-   *
-   * @param scriptPath - Path to the completion script
-   * @returns Configuration content
-   */
-  private generateProfileConfig(scriptPath: string): string {
-    return [
-      '# OpenSpec shell completions configuration',
-      `if (Test-Path "${scriptPath}") {`,
-      `    . "${scriptPath}"`,
-      '}',
-    ].join('\n');
-  }

Option 2 - Use the method in configureProfile for consistency with the generated instructions.


141-154: Inconsistency between marker check and marker block format.

The check at line 143 looks for . "${scriptPath}", but the block inserted at lines 148-154 includes surrounding markers and comments. If a user manually adds the source line without the markers, subsequent installs would skip configuration (line 144), but removeProfileConfig wouldn't find the markers to clean up. This creates an inconsistent state.

Consider checking for the markers themselves rather than just the script line:

🔧 Suggested approach
         // Check if already configured
-        const scriptLine = `. "${scriptPath}"`;
-        if (profileContent.includes(scriptLine)) {
+        if (profileContent.includes(this.PROFILE_MARKERS.start)) {
           continue; // Already configured, skip
         }

159-162: Bare console.warn may not align with the project's logging strategy.

The installer uses console.warn directly here and at line 199, 213. If the project has a structured logging utility, consider using it for consistency. If not, this is acceptable for user-facing warnings.


248-251: Debug logging uses console.debug which may not be visible by default.

The console.debug call at line 250 logs when the file can't be read, which is expected for new installations. Consider removing this debug statement or ensuring debug output is consistently handled across all installers.


320-357: The options parameter is unused in the uninstall method.

The options?.yes parameter is documented but never used within the method body. The confirmation prompt is handled at the command layer, making this parameter redundant here.

🔧 Consider removing unused parameter or documenting why it exists

If the parameter is kept for interface consistency with other installers, add a comment explaining that confirmation is handled by the command layer. Otherwise, simplify:

-  async uninstall(options?: { yes?: boolean }): Promise<{ success: boolean; message: string }> {
+  async uninstall(): Promise<{ success: boolean; message: string }> {
src/core/completions/installers/bash-installer.ts (1)

335-365: Unused options parameter in uninstall method.

Same issue as in PowerShellInstaller - the options?.yes parameter is documented but not used. Confirmation is handled at the command layer.

src/commands/completion.ts (2)

147-160: Duplicate code: configWasUpdated logic is repeated.

The same configWasUpdated check and configPaths mapping appears at lines 149-159 and again at lines 178-191. Consider extracting to a helper.

🔧 Extract shared logic
private getConfigInfo(shell: SupportedShell, result: InstallationResult): { updated: boolean; path: string } {
  const configPaths: Record<string, string> = {
    zsh: '~/.zshrc',
    bash: '~/.bashrc',
    fish: '~/.config/fish/config.fish',
    powershell: '$PROFILE',
  };
  return {
    updated: !!(result.zshrcConfigured || result.bashrcConfigured || result.profileConfigured),
    path: configPaths[shell] || 'config file',
  };
}

171-171: Stale comment references only zsh.

The comment says "only shown if .zshrc wasn't auto-configured" but the code now handles multiple shells. Update for clarity.

🔧 Update comment
-        // Print instructions (only shown if .zshrc wasn't auto-configured)
+        // Print instructions (only shown if shell config wasn't auto-configured)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f47632a and 28f47db.

📒 Files selected for processing (3)
  • src/commands/completion.ts
  • src/core/completions/installers/bash-installer.ts
  • src/core/completions/installers/powershell-installer.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/commands/completion.ts (1)
scripts/postinstall.js (3)
  • result (57-57)
  • result (85-85)
  • shell (128-128)
src/core/completions/installers/powershell-installer.ts (1)
src/core/completions/factory.ts (1)
  • InstallationResult (15-27)
🔇 Additional comments (9)
src/core/completions/installers/powershell-installer.ts (2)

26-46: Profile path resolution looks correct for cross-platform support.

The logic correctly prioritizes the $PROFILE environment variable (set when running inside PowerShell) and falls back to platform-specific defaults for Windows and Unix systems.


83-101: Backup implementation is clean and handles missing files gracefully.

The timestamp-based backup naming and error handling for non-existent files follows best practices.

src/core/completions/installers/bash-installer.ts (4)

31-52: Good coverage of bash-completion paths including Homebrew locations.

The detection includes both Intel (/usr/local/...) and Apple Silicon (/opt/homebrew/...) Homebrew paths, aligning with the commit message "fix: add Homebrew paths for bash-completion detection."


59-65: getInstallationPath always returns user-local path regardless of bash-completion availability.

The method ignores system-wide bash-completion directories and always installs to ~/.local/share/bash-completion/completions/openspec. This is a safe choice for user installations without requiring elevated privileges.


119-149: Good use of FileSystemUtils for marker-based config management.

Unlike the PowerShell installer which implements its own logic, this correctly delegates to shared utilities, reducing code duplication and ensuring consistent behavior.


179-186: No issue found - marker format is consistent between write and removal operations.

The removal logic at lines 179-186 will correctly match markers. FileSystemUtils.updateFileWithMarkers writes markers on their own lines (# OPENSPEC:START\n), and the removal method splits by newlines then trims each line before comparison. Since the marker string # OPENSPEC:START matches exactly after split and trim, the search will succeed reliably.

src/commands/completion.ts (3)

216-227: Shell-specific config paths for uninstall prompt look good.

The mapping correctly handles all supported shells with appropriate paths. The Fish entry notes that it "doesn't modify profile, just removes script file" which is helpful context.


185-193: Reload commands are shell-appropriate.

The mapping provides correct reload commands for each shell (exec zsh, exec bash, exec fish, . $PROFILE), with a sensible fallback for unknown shells.


163-169: Warnings are displayed correctly.

Each warning is printed on its own line after a blank separator, which improves readability for multi-line warning messages like the bash-completion warning.

noameron and others added 2 commits January 9, 2026 13:19
Add -n : option to _init_completion to prevent colons from being
treated as word separators. This is important for spec/change IDs
that may contain colons.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Updated bash-generator test to expect `-n :` flag in _init_completion call.
Updated powershell-installer tests to match refactored implementation that
supports both PowerShell Core and Windows PowerShell 5.1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
test/core/completions/installers/powershell-installer.test.ts (4)

157-172: Misleading test name: implementation appends, not prepends.

The test name says "should prepend markers and config when file exists without markers", but the implementation actually appends the OpenSpec block to the end of the existing profile content (profileContent + openspecBlock).

Consider renaming to "should append markers and config when file exists without markers" for accuracy.


138-256: Consider adding test coverage for Windows dual-profile configuration.

The implementation's getAllProfilePaths() returns both PowerShell Core and Windows PowerShell 5.1 profile paths on Windows (as seen in relevant snippets). However, there's no test that verifies configureProfile correctly updates both profiles on Windows. This is important functionality that should be tested.

Suggested test case
it('should configure both PowerShell Core and Windows PowerShell 5.1 profiles on Windows', async () => {
  delete process.env.PROFILE;
  Object.defineProperty(process, 'platform', {
    value: 'win32',
  });

  const coreProfilePath = path.join(testHomeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
  const legacyProfilePath = path.join(testHomeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');

  const result = await installer.configureProfile(mockScriptPath);

  expect(result).toBe(true);
  
  // Verify both profiles are configured
  const coreContent = await fs.readFile(coreProfilePath, 'utf-8');
  expect(coreContent).toContain('# OPENSPEC:START');
  expect(coreContent).toContain(`. "${mockScriptPath}"`);
  
  const legacyContent = await fs.readFile(legacyProfilePath, 'utf-8');
  expect(legacyContent).toContain('# OPENSPEC:START');
  expect(legacyContent).toContain(`. "${mockScriptPath}"`);
});

365-537: Unnecessary environment variable cleanup throughout install tests.

Many tests delete process.env.OPENSPEC_NO_AUTO_CONFIG (lines 375, 385, 395, 404, 415, 427, 442, 455, 474, 517, 527), but the comment at lines 451-452 explicitly states: "Note: OPENSPEC_NO_AUTO_CONFIG support was removed from PowerShell installer."

Since this environment variable is no longer used by the implementation, these cleanup lines can be removed to reduce noise and improve test clarity.


539-645: Unnecessary environment variable cleanup throughout uninstall tests.

Similar to the install tests, many uninstall tests delete process.env.OPENSPEC_NO_AUTO_CONFIG (lines 546, 556, 567, 586, 596, 618), but this environment variable is no longer used by the PowerShell installer implementation.

These cleanup lines can be removed for consistency with the comment at lines 451-452.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28f47db and 6e6903a.

📒 Files selected for processing (3)
  • src/core/completions/generators/bash-generator.ts
  • test/core/completions/generators/bash-generator.test.ts
  • test/core/completions/installers/powershell-installer.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/completions/generators/bash-generator.ts
🧰 Additional context used
🧬 Code graph analysis (2)
test/core/completions/installers/powershell-installer.test.ts (2)
scripts/postinstall.js (1)
  • installer (84-84)
src/core/completions/installers/powershell-installer.ts (1)
  • PowerShellInstaller (11-358)
test/core/completions/generators/bash-generator.test.ts (3)
scripts/postinstall.js (2)
  • generator (80-80)
  • script (81-81)
src/core/completions/generators/bash-generator.ts (1)
  • BashGenerator (8-175)
src/core/completions/types.ts (1)
  • CommandDefinition (36-72)
🔇 Additional comments (7)
test/core/completions/generators/bash-generator.test.ts (6)

1-10: LGTM: Clean test setup.

The imports and test setup follow standard Vitest patterns with proper ESM paths and a beforeEach hook for generator initialization.


12-20: LGTM: Interface compliance checks.

These tests ensure the generator properly implements the CompletionGenerator interface with the correct shell identifier and required methods.


22-289: Excellent coverage of script generation scenarios.

This comprehensive test suite validates:

  • Header and initialization (including the critical _init_completion -n : on line 37, which aligns with the PR's colon-handling fix)
  • Command list generation
  • Flag variations (boolean, value-taking, with/without short options, enum values)
  • Subcommands
  • All positional argument types (change-id, spec-id, change-or-spec-id, shell, path)

The tests appropriately use .toContain() assertions to verify key script fragments without being overly rigid about formatting.


291-344: LGTM: Dynamic completion helper validation.

These tests properly verify that:

  • Dynamic completion helpers are generated for change-id, spec-id, and change-or-spec-id types
  • Helpers invoke openspec __complete commands with appropriate arguments
  • Generated functions follow Bash completion conventions (COMPREPLY, command substitution, error suppression)

346-418: LGTM: Edge cases and complex scenarios covered.

These tests validate:

  • Complex nested subcommands with flags and positional arguments work together correctly
  • The script properly ends with complete -F _openspec_completion openspec registration (line 393 uses .trim().endsWith() for robust checking)
  • Edge cases like empty command lists and commands with no flags are handled gracefully

421-524: Excellent defense-in-depth security testing.

This comprehensive security suite validates proper escaping of shell metacharacters in both command and subcommand names:

  • Dollar signs ($)
  • Backticks (`)
  • Double quotes (")
  • Backslashes (\)

The comment on line 439 appropriately notes that while command names are likely validated upstream, the escaping provides defense-in-depth protection against potential injection attacks. This is a best practice for shell script generation.

test/core/completions/installers/powershell-installer.test.ts (1)

1-647: Excellent comprehensive test coverage for PowerShellInstaller!

This test suite is well-structured and thoroughly exercises the PowerShellInstaller across multiple dimensions:

  • ✅ Platform-specific behavior (Windows, macOS, Linux)
  • ✅ Environment variable handling (PROFILE env var)
  • ✅ Edge cases (spaces in paths, special characters, empty scripts)
  • ✅ Error scenarios (permission errors, missing files, invalid marker placement)
  • ✅ Installation flows (first install, updates, backups)
  • ✅ Profile configuration and cleanup
  • ✅ Proper test isolation with temp directories and cleanup

The test organization is clear with descriptive test names (with one minor exception noted), and the setup/teardown properly isolates tests. Great work on comprehensive coverage!

Copy link
Contributor

@TabishB TabishB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I think this looks good! :) Keen to try it out!

@TabishB TabishB merged commit 38d2356 into Fission-AI:main Jan 9, 2026
7 checks passed
TabishB added a commit that referenced this pull request Jan 10, 2026
fs.chmod() on directories doesn't restrict write access on Windows since
Windows uses ACLs that Node.js doesn't control. Additionally, admin users
and CI runners can bypass read-only attributes. Skip these tests on Windows
and use platform-specific invalid paths in cross-platform tests.

Fixes #401 (bash/pwsh completion commit breaking Windows e2e tests).
TabishB added a commit that referenced this pull request Jan 10, 2026
fs.chmod() on directories doesn't restrict write access on Windows since
Windows uses ACLs that Node.js doesn't control. Additionally, admin users
and CI runners can bypass read-only attributes. Skip these tests on Windows
and use platform-specific invalid paths in cross-platform tests.

Fixes #401 (bash/pwsh completion commit breaking Windows e2e tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants