Skip to content

Conversation

@noameron
Copy link

@noameron noameron commented Nov 6, 2025

Add Zsh Shell Completions

Adds tab completion support for the OpenSpec CLI with Zsh (including Oh My Zsh).

New Command

openspec completion install # Auto-installs Zsh completions
openspec completion uninstall # Removes completions
openspec completion generate # Outputs completion script

Key Files

Core Implementation

  • src/core/completions/command-registry.ts - Centralized registry of all CLI commands and flags with COMMON_FLAGS constant for reusable flag definitions
  • src/core/completions/completion-provider.ts - Discovers change/spec IDs from projects with 2s caching
  • src/core/completions/factory.ts - Factory pattern with SUPPORTED_SHELLS constant for shell support

Zsh-Specific

  • src/core/completions/generators/zsh-generator.ts - Generates Zsh completion scripts using _arguments and _describe
  • src/core/completions/installers/zsh-installer.ts - Handles installation to Oh My Zsh (/.oh-my-zsh/custom/completions/) or standard Zsh (/.zsh/completions/)

Command Interface

  • src/commands/completion.ts - CLI command implementation with shared resolveShellOrExit() logic

Features

  • Tab complete commands, flags, change IDs, and spec IDs
  • Auto-detects Oh My Zsh vs standard Zsh
  • Only activates in OpenSpec projects
  • Plugin architecture for future shells (bash, fish, PowerShell)

Summary by CodeRabbit

  • New Features
    • CLI "completion" command: generate/install/uninstall Zsh completions with dynamic, project-aware suggestions (changes/specs/archived), automatic shell detection, machine-readable completion output, Oh My Zsh & standard Zsh install/uninstall, and a 2s dynamic-cache for IDs.
  • Documentation
    • Design, proposal, and formal CLI completion spec covering architecture, UX, install/uninstall behavior, error messages, caching, and roadmap for other shells.
  • Tests
    • Extensive tests for generation, installer flows, detection, caching, and edge cases.
  • Chores
    • Postinstall auto-install wiring and test harness with opt-out behavior.

@noameron noameron requested a review from TabishB as a code owner November 6, 2025 18:12
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds a Zsh-first, plugin-based shell completion subsystem: new completion CLI (generate/install/uninstall), generator/installer factory, COMMAND_REGISTRY, dynamic CompletionProvider with 2s TTL, shell detection, postinstall automation, design/spec docs, and extensive tests and install/uninstall logic.

Changes

Cohort / File(s) Summary
Design & Specs
openspec/changes/archive/2025-11-06-add-shell-completions/design.md, openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md, openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md, openspec/specs/cli-completion/spec.md
New design, proposal, and spec documents describing architecture, Zsh-first behavior, interfaces, dynamic provider caching, install/uninstall flows, error handling, testing plan, and roadmap.
Shell Detection
src/utils/shell-detection.ts
Adds SupportedShell type and detectShell() to infer shell from environment (zsh, bash, fish, powershell).
Core Types & Registry
src/core/completions/types.ts, src/core/completions/command-registry.ts
Adds typed interfaces (FlagDefinition, CommandDefinition, CompletionGenerator) and a strongly-typed COMMAND_REGISTRY enumerating commands, flags, subcommands, and positional types.
Dynamic Provider
src/core/completions/completion-provider.ts
New CompletionProvider with per-entity caches and 2s TTL; methods: getChangeIds(), getSpecIds(), getAllIds(), clearCache(), getCacheStats().
Zsh Generator
src/core/completions/generators/zsh-generator.ts
New ZshGenerator implementing CompletionGenerator; emits Zsh completion scripts using _arguments, _describe, per-command functions, and dynamic ID helpers.
Zsh Installer
src/core/completions/installers/zsh-installer.ts
New ZshInstaller and InstallationResult; detects Oh My Zsh, writes/backups completion file, marker-based .zshrc auto-config, uninstall/cleanup, and status/info methods.
Factory & Installer Interface
src/core/completions/factory.ts
New CompletionFactory with createGenerator/createInstaller, isSupported, getSupportedShells; adds CompletionInstaller interface and re-exports InstallationResult.
CLI Command Integration
src/cli/index.ts, src/commands/completion.ts
Adds top-level completion command with generate, install, uninstall subcommands and CompletionCommand orchestration (resolve shell, generate, install/uninstall, machine-readable __complete).
Postinstall Automation
package.json, scripts/postinstall.js, scripts/test-postinstall.sh
Adds postinstall wiring in package.json, scripts/postinstall.js to auto-install completions (skippable via CI or env), and scripts/test-postinstall.sh test harness.
Item discovery helper
src/utils/item-discovery.ts
Adds getArchivedChangeIds(root?) to list archived change IDs from openspec/changes/archive.
Tests
test/utils/shell-detection.test.ts, test/core/completions/*.test.ts, test/commands/completion.test.ts
Extensive tests covering shell detection, CompletionProvider caching, ZshGenerator output, ZshInstaller filesystem/.zshrc behavior, CLI behaviors, and postinstall scenarios.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as CLI User
    participant Cmd as CompletionCommand
    participant Detect as ShellDetection
    participant Factory as CompletionFactory
    participant Gen as ZshGenerator
    participant Registry as COMMAND_REGISTRY
    participant Provider as CompletionProvider
    participant Installer as ZshInstaller

    Note over User,Cmd: User runs "openspec completion install [shell?]"
    User->>Cmd: completion install [shell?]
    Cmd->>Detect: detectShell()
    Detect-->>Cmd: 'zsh'
    Cmd->>Factory: createGenerator('zsh')
    Factory-->>Gen: instantiate
    Cmd->>Gen: generate(COMMAND_REGISTRY)
    Gen->>Registry: read command metadata
    Gen->>Provider: fetch dynamic IDs (changes/specs)
    Provider-->>Gen: {changeIds, specIds}
    Gen-->>Cmd: zsh completion script
    Cmd->>Factory: createInstaller('zsh')
    Factory-->>Installer: instantiate
    Cmd->>Installer: install(script)
    Installer->>Installer: detect OMZ / ensure dirs / backup
    Installer->>Installer: write completion file
    Installer->>Installer: optionally modify ~/.zshrc (markers)
    Installer-->>Cmd: InstallationResult
    Cmd-->>User: success message & instructions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas needing extra attention:

  • src/core/completions/generators/zsh-generator.ts — Zsh script syntax, escaping, and dynamic helper correctness.
  • src/core/completions/installers/zsh-installer.ts — filesystem operations, backups, marker-based .zshrc edits, permission/error handling.
  • src/commands/completion.ts and src/cli/index.ts — shell resolution, CLI wiring, and user-visible error flows.
  • scripts/postinstall.js — CI/opt-out detection, dynamic imports, and ensuring non-failing npm installs.
  • Tests manipulating filesystem and env — isolation, teardown, and cross-platform behavior.

Possibly related issues

Suggested reviewers

  • TabishB

Poem

🐰 I hopped through flags and dashed through names,
I stitched up scripts for zsh’s games.
Backups snug, markers placed just so,
Tab-completions hum — off we go! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'feature/oh-my-zsh-completions' is vague and branch-like rather than a descriptive summary of the main change. It uses a generic naming pattern that doesn't clearly convey the primary functionality being added. Consider rephrasing to a more descriptive title like 'Add Zsh shell completion support with Oh My Zsh integration' or 'Implement shell completions CLI command for Zsh'. This would better communicate the main feature to reviewers scanning the history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent 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 e978946 and 7043cf4.

📒 Files selected for processing (1)
  • package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

Tip

📝 Customizable high-level summaries are now available!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide custom instructions to shape the summary (bullet lists, tables, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example:

"Create a concise high-level summary as a bullet-point list. Then include a Markdown table showing lines added and removed by each contributing author."


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: 4

🧹 Nitpick comments (2)
src/core/completions/installers/zsh-installer.ts (2)

180-227: Consider extracting marker removal to FileSystemUtils for consistency.

The configureZshrc method uses FileSystemUtils.updateFileWithMarkers (Line 145) while removeZshrcConfig implements custom removal logic. This creates potential for inconsistency if the marker handling in FileSystemUtils changes.

Additionally, the logic at lines 215-217 unconditionally removes leading empty lines, but the comment suggests this should only happen "if the markers were at the top." The code doesn't actually verify the markers were at the top before removing these lines.

Consider:

  1. Add a removeMarkerSection method to FileSystemUtils to centralize marker management
  2. Clarify the empty line removal logic - either add a condition to check if startIndex === 0, or update the comment if unconditional removal is intentional

Example for the empty line fix:

-      // Remove trailing empty lines at the start if the markers were at the top
-      while (lines.length > 0 && lines[0].trim() === '') {
-        lines.shift();
-      }
+      // Remove leading empty lines if the markers were at the top of the file
+      if (startIndex === 0) {
+        while (lines.length > 0 && lines[0].trim() === '') {
+          lines.shift();
+        }
+      }

235-278: Consider validating the completion script parameter.

The method writes completionScript directly without validating that it's non-empty or contains valid content. While the caller should provide valid input, defensive validation would prevent installing corrupt completions.

Consider adding validation before writing:

 async install(completionScript: string): Promise<InstallationResult> {
   try {
+    if (!completionScript || completionScript.trim().length === 0) {
+      return {
+        success: false,
+        isOhMyZsh: false,
+        message: 'Invalid completion script: content is empty',
+      };
+    }
+
     const { path: targetPath, isOhMyZsh } = await this.getInstallationPath();
📜 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 d32e50f and 1784ffa.

📒 Files selected for processing (22)
  • openspec/changes/archive/2025-11-06-add-shell-completions/design.md (1 hunks)
  • openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md (1 hunks)
  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md (1 hunks)
  • openspec/changes/archive/2025-11-06-add-shell-completions/tasks.md (1 hunks)
  • openspec/specs/cli-completion/spec.md (1 hunks)
  • package.json (2 hunks)
  • scripts/postinstall.js (1 hunks)
  • scripts/test-postinstall.sh (1 hunks)
  • src/cli/index.ts (2 hunks)
  • src/commands/completion.ts (1 hunks)
  • src/core/completions/command-registry.ts (1 hunks)
  • src/core/completions/completion-provider.ts (1 hunks)
  • src/core/completions/factory.ts (1 hunks)
  • src/core/completions/generators/zsh-generator.ts (1 hunks)
  • src/core/completions/installers/zsh-installer.ts (1 hunks)
  • src/core/completions/types.ts (1 hunks)
  • src/utils/shell-detection.ts (1 hunks)
  • test/commands/completion.test.ts (1 hunks)
  • test/core/completions/completion-provider.test.ts (1 hunks)
  • test/core/completions/generators/zsh-generator.test.ts (1 hunks)
  • test/core/completions/installers/zsh-installer.test.ts (1 hunks)
  • test/utils/shell-detection.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
openspec/changes/*/**

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Choose a unique kebab-case, verb-led change-id (add-, update-, remove-, refactor-) for openspec/changes//

Files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/design.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/tasks.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md
openspec/changes/archive/**

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Archive completed changes under openspec/changes/archive/YYYY-MM-DD-/ and run 'openspec validate --strict' post-archive; update specs/ if capabilities changed

Files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/design.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/tasks.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md
openspec/{specs/**/spec.md,changes/*/specs/**/spec.md}

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/{specs/**/spec.md,changes/*/specs/**/spec.md}: Write requirements using SHALL/MUST for normative wording
Every requirement must include at least one scenario, formatted exactly with a '#### Scenario: ' header (no bullets or bold)
Under ADDED, introduce new capabilities as standalone requirements rather than altering existing ones

Files:

  • openspec/specs/cli-completion/spec.md
openspec/specs/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Keep current, deployed specs in openspec/specs//spec.md (one focused capability per directory)

Files:

  • openspec/specs/cli-completion/spec.md
🧠 Learnings (8)
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/{specs/**/spec.md,changes/*/specs/**/spec.md} : Under ADDED, introduce new capabilities as standalone requirements rather than altering existing ones

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
  • openspec/specs/cli-completion/spec.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/changes/archive/** : Archive completed changes under openspec/changes/archive/YYYY-MM-DD-<name>/ and run 'openspec validate --strict' post-archive; update specs/ if capabilities changed

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/changes/*/{proposal.md,tasks.md,design.md,specs/**/spec.md} : Scaffold each change with proposal.md, tasks.md, optional design.md, and delta spec files under openspec/changes/<change-id>/specs/<capability>/spec.md

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/tasks.md
  • openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/specs/**/spec.md : Keep current, deployed specs in openspec/specs/<capability>/spec.md (one focused capability per directory)

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/changes/*/specs/**/spec.md : Use ADDED, MODIFIED, REMOVED, or RENAMED sections when authoring deltas; place changed requirements under the correct operation header

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/{specs/**/spec.md,changes/*/specs/**/spec.md} : Write requirements using SHALL/MUST for normative wording

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/changes/*/specs/**/spec.md : For MODIFIED requirements, paste the full existing requirement block (header through scenarios) and ensure the header text matches exactly (whitespace-insensitive)

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/changes/*/tasks.md : Track implementation steps in tasks.md and mark each completed item with '- [x]' once finished

Applied to files:

  • openspec/changes/archive/2025-11-06-add-shell-completions/tasks.md
🧬 Code graph analysis (14)
test/commands/completion.test.ts (2)
src/commands/completion.ts (1)
  • CompletionCommand (22-185)
src/core/completions/installers/zsh-installer.ts (1)
  • ZshInstaller (23-412)
test/utils/shell-detection.test.ts (1)
src/utils/shell-detection.ts (2)
  • detectShell (11-48)
  • SupportedShell (4-4)
scripts/postinstall.js (3)
src/utils/shell-detection.ts (1)
  • detectShell (11-48)
src/core/completions/factory.ts (1)
  • CompletionFactory (21-74)
src/core/completions/command-registry.ts (1)
  • COMMAND_REGISTRY (35-291)
src/core/completions/command-registry.ts (1)
src/core/completions/types.ts (2)
  • FlagDefinition (6-31)
  • CommandDefinition (36-72)
src/cli/index.ts (2)
scripts/postinstall.js (1)
  • shell (127-127)
src/commands/completion.ts (1)
  • CompletionCommand (22-185)
src/core/completions/completion-provider.ts (1)
src/utils/item-discovery.ts (1)
  • getActiveChangeIds (4-23)
src/commands/completion.ts (4)
scripts/postinstall.js (5)
  • shell (127-127)
  • generator (79-79)
  • script (80-80)
  • installer (83-83)
  • result (84-84)
src/utils/shell-detection.ts (2)
  • SupportedShell (4-4)
  • detectShell (11-48)
src/core/completions/factory.ts (1)
  • CompletionFactory (21-74)
src/core/completions/command-registry.ts (1)
  • COMMAND_REGISTRY (35-291)
src/core/completions/generators/zsh-generator.ts (2)
src/core/completions/types.ts (3)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
  • FlagDefinition (6-31)
scripts/postinstall.js (1)
  • script (80-80)
test/core/completions/completion-provider.test.ts (1)
src/core/completions/completion-provider.ts (1)
  • CompletionProvider (16-128)
src/core/completions/factory.ts (3)
src/core/completions/installers/zsh-installer.ts (2)
  • InstallationResult (9-17)
  • ZshInstaller (23-412)
src/utils/shell-detection.ts (1)
  • SupportedShell (4-4)
src/core/completions/types.ts (1)
  • CompletionGenerator (77-90)
test/core/completions/generators/zsh-generator.test.ts (3)
scripts/postinstall.js (2)
  • generator (79-79)
  • script (80-80)
src/core/completions/generators/zsh-generator.ts (1)
  • ZshGenerator (7-373)
src/core/completions/types.ts (1)
  • CommandDefinition (36-72)
src/core/completions/types.ts (1)
src/utils/shell-detection.ts (1)
  • SupportedShell (4-4)
test/core/completions/installers/zsh-installer.test.ts (2)
scripts/postinstall.js (3)
  • installer (83-83)
  • result (84-84)
  • stat (44-44)
src/core/completions/installers/zsh-installer.ts (2)
  • ZshInstaller (23-412)
  • isInstalled (378-386)
src/core/completions/installers/zsh-installer.ts (2)
src/core/completions/factory.ts (1)
  • InstallationResult (15-15)
src/utils/file-system.ts (1)
  • FileSystemUtils (44-187)
🔇 Additional comments (22)
openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md (1)

1-301: LGTM! Well-structured specification.

The specification follows all coding guidelines:

  • Properly archived under the correct date-based path format
  • Uses SHALL/MUST normative language throughout
  • Organizes new capabilities under ADDED section
  • Scenarios follow WHEN/THEN/AND structure
  • Comprehensive coverage of requirements including native shell behavior, installation flows, error handling, and testing

The architecture patterns defined here (factory, generators, installers, command registry) align well with the implementation files in this PR.

Based on learnings

package.json (1)

48-51: LGTM! Postinstall integration follows best practices.

The postinstall hook enables automatic shell completion installation after package install. The implementation (scripts/postinstall.js) includes:

  • CI detection to skip in automated environments
  • OPENSPEC_NO_COMPLETIONS=1 opt-out mechanism
  • Graceful failure that never breaks npm install
  • Silent skips when dist/ doesn't exist (dev scenarios)

This aligns with the specification requirements for installation automation.

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

1-96: LGTM! Comprehensive test coverage for generate subcommand.

The test suite properly covers:

  • Explicit shell parameter
  • Auto-detection scenarios
  • Error cases (unsupported shells, detection failures)
  • Case-insensitive shell handling

Mocking strategy is appropriate with shell-detection and ZshInstaller properly isolated.


98-153: LGTM! Install subcommand tests cover key scenarios.

Tests validate:

  • Explicit and auto-detected shell installation
  • Verbose output mode
  • Error handling for detection failures
  • Unsupported shell errors
  • Installation instruction display

196-268: LGTM! Error handling and integration tests are thorough.

The tests cover:

  • Installation/uninstallation failure scenarios with appropriate error messages
  • Shell detection fallback behavior
  • Explicit shell parameter precedence over auto-detection

Good use of dynamic imports and mock re-implementation for failure scenarios.

src/cli/index.ts (1)

16-16: LGTM! CLI integration follows consistent patterns.

The completion command integration:

  • Follows the same structure as existing commands (change, spec, validate)
  • Properly passes options (shell, verbose) to CompletionCommand methods
  • Uses consistent error handling with ora and process.exit(1)
  • Subcommands align with specification requirements

Also applies to: 254-300

scripts/postinstall.js (1)

1-146: LGTM! Postinstall script follows best practices.

The implementation demonstrates excellent postinstall hygiene:

  • ✓ Never fails npm install (all errors caught, exits 0)
  • ✓ Skips silently in CI environments
  • ✓ Respects opt-out via OPENSPEC_NO_COMPLETIONS=1
  • ✓ Guards against missing dist/ (dev setup scenario)
  • ✓ Gracefully handles detection and installation failures
  • ✓ Provides helpful tips when auto-install isn't possible
  • ✓ Dynamic imports prevent crashes when modules unavailable

The success messages appropriately vary by installation type (Oh My Zsh vs. standard Zsh vs. manual configuration needed).

src/utils/shell-detection.ts (1)

1-48: LGTM! Shell detection logic handles common scenarios correctly.

The implementation:

  • Detects Unix shells (zsh, bash, fish) via $SHELL environment variable
  • Handles PowerShell detection via PSModulePath (works for WSL scenarios)
  • Returns undefined for unsupported shells (cmd.exe, tcsh, etc.)
  • Uses case-insensitive matching to handle variations

The substring matching approach (e.g., shellName.includes('zsh')) is appropriate given standard shell path conventions.

test/utils/shell-detection.test.ts (1)

1-158: LGTM! Comprehensive test coverage with proper isolation.

The test suite thoroughly validates:

  • Detection for zsh, bash, fish across multiple common paths
  • Case-insensitive detection
  • PowerShell detection via PSModulePath on both Windows and non-Windows
  • Undefined behavior for unsupported shells and missing configuration
  • Platform-specific logic (Windows vs. Unix-like)

Environment restoration (lines 10-43) ensures test isolation. The use of Object.defineProperty for mocking process.platform is appropriate.

scripts/test-postinstall.sh (1)

1-57: LGTM! Well-designed integration test harness.

The test script validates three critical postinstall scenarios:

  1. Normal installation flow
  2. CI environment skip (silent)
  3. Opt-out flag skip (silent)

Good practices:

  • set -e for strict error handling
  • Environment preservation and restoration
  • Clear output with headers and expected behavior notes
  • Tests the actual behavior rather than implementation details
src/core/completions/command-registry.ts (1)

1-291: LGTM! Well-structured command registry.

The centralized registry follows clean architecture principles and provides excellent type safety. The COMMON_FLAGS pattern effectively reduces duplication.

test/core/completions/completion-provider.test.ts (1)

1-288: Excellent test coverage!

The test suite comprehensively validates caching behavior, TTL expiration, cache invalidation, and data retrieval. The use of temporary directories with proper cleanup is good practice.

src/core/completions/types.ts (1)

1-90: LGTM! Clean and well-documented type definitions.

The interfaces provide strong typing for the completion system with clear documentation. The positionalType union ensures type safety for dynamic completions.

src/core/completions/completion-provider.ts (1)

1-128: LGTM! Solid caching implementation.

The CompletionProvider implements efficient 2-second TTL caching to balance performance with freshness during tab completion. The parallel fetching in getAllIds() using Promise.all is a good optimization.

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

1-74: LGTM! Clean factory pattern with good extensibility.

The factory pattern makes it straightforward to add support for additional shells in future PRs. The defensive copy in getSupportedShells() and the type guard in isSupported() follow good practices.

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

1-381: Excellent test coverage!

The test suite thoroughly validates the ZshGenerator implementation with comprehensive coverage of:

  • Interface compliance
  • Script structure and syntax
  • Command, flag, and subcommand handling
  • Positional argument types
  • Special character escaping
  • Edge cases and complex nested structures
src/core/completions/installers/zsh-installer.ts (6)

1-17: LGTM! Clean interface definition.

The imports are appropriate and the InstallationResult interface is well-structured with clear optional fields for different installation scenarios.


23-36: LGTM! Good testability design.

The constructor allows dependency injection of homeDir for testing while defaulting to os.homedir(). The marker-based configuration approach is a standard pattern for managing auto-generated config sections.


43-95: LGTM! Appropriate path detection and backup strategy.

The path detection correctly distinguishes between Oh My Zsh and standard Zsh installations. The timestamped backup approach prevents overwriting existing backups and the graceful failure handling is appropriate for this use case.


128-157: LGTM! Robust configuration with appropriate safeguards.

Good defensive programming with permission checks and the OPENSPEC_NO_AUTO_CONFIG opt-out mechanism. The graceful failure approach ensures installation doesn't break if .zshrc configuration fails.


287-315: LGTM! Clear and actionable user instructions.

The instructions appropriately differ between Oh My Zsh and standard Zsh setups, providing users with exactly the steps needed to enable completions.


322-411: LGTM! Comprehensive uninstallation and status checking.

The uninstall logic correctly handles partial installations (script exists but no config, or vice versa) and provides accurate feedback. The status checking methods offer a clean API for querying installation state.

Comment on lines +31 to +35
- **WHEN** user executes `openspec completion --help`
- **THEN** display available subcommands:
- `zsh` - Generate Zsh completion script
- `install [shell]` - Install completion for Zsh (auto-detects or requires explicit shell)
- `uninstall [shell]` - Remove completion for Zsh (auto-detects or requires explicit shell)
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

Fix the documented subcommand list.

The CLI exposes completion generate/install/uninstall, not a zsh subcommand. The requirement should match the shipped interface to keep the spec authoritative. Please update the scenario accordingly.
[suggested diff]

-  - `zsh` - Generate Zsh completion script
-  - `install [shell]` - Install completion for Zsh (auto-detects or requires explicit shell)
-  - `uninstall [shell]` - Remove completion for Zsh (auto-detects or requires explicit shell)
+  - `generate [shell]` - Output the completion script (defaults to detected shell)
+  - `install [shell]` - Install completion for Zsh (auto-detects or requires explicit shell)
+  - `uninstall [shell]` - Remove completion for Zsh (auto-detects or requires explicit shell)
🤖 Prompt for AI Agents
In openspec/specs/cli-completion/spec.md around lines 31 to 35, the scenario
lists a non-existent `zsh` subcommand; update the expected subcommand list to
reflect the actual CLI interface `completion generate/install/uninstall` (and
describe their purposes, e.g., `generate` - produce completion script, `install
[shell]` - install for a shell, `uninstall [shell]` - remove for a shell).
Ensure the wording replaces `zsh` with `generate` and updates the bullet
descriptions to match the shipped commands.

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 (1)
src/core/completions/generators/zsh-generator.ts (1)

328-343: Consider using SUPPORTED_SHELLS constant for shell completion.

Line 339 hardcodes '*:shell:(zsh)', but the PR description mentions the architecture is prepared for additional shells (bash, fish, PowerShell). Consider referencing the SUPPORTED_SHELLS constant from factory.ts to keep the completion list in sync with supported shells.

Example refactor:

import { SUPPORTED_SHELLS } from '../factory.js';

// In generatePositionalSpec:
case 'shell':
  const shells = SUPPORTED_SHELLS.join(' ');
  return `'*:shell:(${shells})'`;
📜 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 b0bb9ad and b53f12d.

📒 Files selected for processing (1)
  • src/core/completions/generators/zsh-generator.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/core/completions/generators/zsh-generator.ts (2)
src/core/completions/types.ts (3)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
  • FlagDefinition (6-31)
scripts/postinstall.js (1)
  • script (80-80)
🔇 Additional comments (9)
src/core/completions/generators/zsh-generator.ts (9)

1-8: LGTM!

The class correctly implements the CompletionGenerator interface, and the const assertion on the shell property provides proper type safety.


16-83: Excellent fix from previous review!

The completion script structure correctly follows Zsh conventions. The critical issue from the previous review has been properly addressed—line 79 now uses compdef _openspec openspec for registration instead of invoking _openspec "$@" during load, which prevents errors when sourcing the completion file.


94-126: LGTM!

The helper correctly generates Zsh completion functions with proper handling of single and multi-line command arrays.


131-173: LGTM!

The dynamic completion helpers correctly use command substitution with openspec list and awk to format change and spec IDs with descriptions. The Zsh parameter expansion syntax ${(f)"..."} properly splits the output into array elements.


178-257: LGTM with minor edge case.

The function correctly handles both commands with and without subcommands, properly generating Zsh completion logic. The trailing backslash removal (lines 240-242) is necessary for valid Zsh syntax.

Edge case: If a command has neither flags nor positionals, the result would be _arguments with no arguments, which is invalid Zsh syntax. However, this is unlikely in practice as commands typically have at least one flag.


262-288: LGTM!

The subcommand function generation follows the same pattern as command functions with consistent handling of flags and positionals.


293-323: LGTM!

The flag specification generation correctly implements Zsh syntax for both mutually exclusive short/long forms and value completion.


348-365: LGTM!

The escaping methods correctly handle special characters for Zsh completion syntax, with backslash properly escaped first to avoid double-escaping issues.


370-372: LGTM!

The function correctly sanitizes command names for use in Zsh function identifiers by replacing dashes with underscores.

noameron and others added 2 commits November 6, 2025 20:39
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  - Add __complete endpoint with tab-separated output for safe parsing
  - Replace brittle awk parsing with __complete in completion helpers
  - Add uninstall confirmation prompt with --yes flag to skip
  - Prefer $ZSH env var for Oh My Zsh detection before dir check
  - Add fpath verification guidance for OMZ installations
  - Update cli-completion spec to document generate subcommand
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 (2)
src/utils/item-discovery.ts (1)

4-65: Consider extracting common discovery logic to reduce duplication.

All three functions (getActiveChangeIds, getSpecIds, getArchivedChangeIds) share nearly identical logic—they differ only in the base path, validation file name, and filter criteria. Consider refactoring into a parameterized helper function:

async function discoverItems(
  basePath: string,
  validationFile: string,
  excludeDirs: string[] = []
): Promise<string[]> {
  try {
    const entries = await fs.readdir(basePath, { withFileTypes: true });
    const result: string[] = [];
    for (const entry of entries) {
      if (!entry.isDirectory() || entry.name.startsWith('.') || excludeDirs.includes(entry.name)) continue;
      const filePath = path.join(basePath, entry.name, validationFile);
      try {
        await fs.access(filePath);
        result.push(entry.name);
      } catch {
        // skip directories without validation file
      }
    }
    return result.sort();
  } catch {
    return [];
  }
}

export async function getActiveChangeIds(root: string = process.cwd()): Promise<string[]> {
  return discoverItems(
    path.join(root, 'openspec', 'changes'),
    'proposal.md',
    ['archive']
  );
}

export async function getSpecIds(root: string = process.cwd()): Promise<string[]> {
  return discoverItems(
    path.join(root, 'openspec', 'specs'),
    'spec.md'
  );
}

export async function getArchivedChangeIds(root: string = process.cwd()): Promise<string[]> {
  return discoverItems(
    path.join(root, 'openspec', 'changes', 'archive'),
    'proposal.md'
  );
}

This would also help consolidate similar logic found in src/commands/change.ts (getActiveChanges method).

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

186-233: Consider using FileSystemUtils for marker removal.

The removeZshrcConfig method manually implements marker-based content removal, which duplicates some logic from FileSystemUtils.updateFileWithMarkers. If FileSystemUtils provides a method to remove marker-enclosed content (or if updateFileWithMarkers can be extended to handle removal by passing empty content), consider using it to reduce code duplication and ensure consistent marker handling.

📜 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 b53f12d and e978946.

📒 Files selected for processing (7)
  • src/cli/index.ts (2 hunks)
  • src/commands/completion.ts (1 hunks)
  • src/core/completions/generators/zsh-generator.ts (1 hunks)
  • src/core/completions/installers/zsh-installer.ts (1 hunks)
  • src/utils/item-discovery.ts (1 hunks)
  • test/commands/completion.test.ts (1 hunks)
  • test/core/completions/generators/zsh-generator.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/core/completions/generators/zsh-generator.test.ts
  • src/commands/completion.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-22T06:05:06.615Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-10-22T06:05:06.615Z
Learning: Applies to openspec/changes/archive/** : Archive completed changes under openspec/changes/archive/YYYY-MM-DD-<name>/ and run 'openspec validate --strict' post-archive; update specs/ if capabilities changed

Applied to files:

  • src/utils/item-discovery.ts
📚 Learning: 2025-10-21T03:05:07.856Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-21T03:05:07.856Z
Learning: Always open @/openspec/AGENTS.md when the request is ambiguous and the authoritative spec is needed before coding

Applied to files:

  • src/core/completions/generators/zsh-generator.ts
🧬 Code graph analysis (5)
src/utils/item-discovery.ts (2)
src/commands/change.ts (1)
  • getActiveChanges (241-259)
src/core/view.ts (1)
  • getChangesData (71-110)
src/cli/index.ts (2)
scripts/postinstall.js (1)
  • shell (127-127)
src/commands/completion.ts (1)
  • CompletionCommand (30-254)
src/core/completions/generators/zsh-generator.ts (2)
src/core/completions/types.ts (3)
  • CompletionGenerator (77-90)
  • CommandDefinition (36-72)
  • FlagDefinition (6-31)
scripts/postinstall.js (1)
  • script (80-80)
src/core/completions/installers/zsh-installer.ts (2)
src/core/completions/factory.ts (1)
  • InstallationResult (15-15)
src/utils/file-system.ts (1)
  • FileSystemUtils (44-187)
test/commands/completion.test.ts (2)
src/commands/completion.ts (1)
  • CompletionCommand (30-254)
src/core/completions/installers/zsh-installer.ts (1)
  • ZshInstaller (23-442)
🔇 Additional comments (1)
src/utils/item-discovery.ts (1)

46-65: LGTM! Logic is correct and consistent.

The function correctly discovers archived change IDs by scanning the archive directory, validating the presence of proposal.md, and returning a sorted list. The error handling and filtering logic align perfectly with the existing getActiveChangeIds and getSpecIds functions.

@noameron
Copy link
Author

@TabishB Hi, this PR is abit big partly due to the specs used

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.

Hey this is amazing @noameron! Nice stuff man, comments belows are mostly nits. It's mainly comments around difference in what you've said in the spec vs what was implemented.

const targetDir = path.dirname(targetPath);
await fs.mkdir(targetDir, { recursive: true });

// Backup existing file 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 always overwrite _openspec and report success even if the script was already installed. Spec calls for detecting the “already installed” case and letting the user know/offer reinstall.

Can we check for an existing file (and maybe diff) before writing, and emit the spec-mandated messaging?

Copy link
Author

Choose a reason for hiding this comment

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

@TabishB Do you prefer prompting the user if the file exists but has different setting that needs to be made? or should just alert the user file exists and what he changed? pure visiblity

@noameron noameron requested a review from TabishB November 20, 2025 10:11
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