Skip to content

Conversation

@TabishB
Copy link
Contributor

@TabishB TabishB commented Dec 21, 2025

Summary

Fixes #367

  • Convert static @inquirer/prompts imports to dynamic imports, loaded only when interactive prompts are actually needed
  • Dynamically import InitCommand (which uses @inquirer/core) to prevent loading at startup
  • Update isInteractive() to accept options object supporting both noInteractive and Commander's negated interactive property
  • Handle empty validation queue with proper JSON output and exit code

Root Cause

The CLI hung when run as a pre-commit hook because @inquirer/prompts was statically imported at module load time. Even when prompts were never called (e.g., openspec validate --specs --no-interactive), the import could set up stdin references that prevented clean process exit when stdin was piped.

The workaround discovered by @greenkiwi (0<&- to close stdin) confirmed the issue was stdin-related.

Test plan

  • Run openspec validate --specs --strict --no-interactive in a pre-commit hook - should complete without hanging
  • Verify interactive mode still works when running from terminal
  • Run existing test suite to ensure no regressions

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Refactor

    • Optimized prompt initialization for improved performance by deferring module loading until needed.
  • New Features

    • Enhanced validation output with comprehensive result summaries.
    • Improved JSON output structure for validation results.
  • Bug Fixes

    • Better handling of edge cases when no items are available for validation.
    • Improved consistency of exit codes and output formatting across validation modes.

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

Fixes #367

The CLI was hanging when run as a pre-commit hook because @inquirer/prompts
was statically imported at module load time. Even when prompts were never
called (e.g., `openspec validate --specs --no-interactive`), the import
itself could set up stdin references that prevented clean process exit
when stdin was piped.

Changes:
- Convert all static `@inquirer/prompts` imports to dynamic imports
- Dynamically import `InitCommand` (which uses `@inquirer/core`)
- Update `isInteractive()` to accept options object with both
  `noInteractive` and Commander's negated `interactive` property
- Handle empty validation queue with proper exit code

Now when running in non-interactive mode, the inquirer modules are never
loaded, allowing the process to exit cleanly after completion.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Walkthrough

This pull request refactors how inquirer prompt modules are imported and how interactive mode is detected across the codebase. Static top-level imports of select and confirm from @inquirer/prompts are replaced with dynamic imports that load only when needed. The interactive mode detection logic is consolidated, allowing the isInteractive function to accept either a boolean or an options object instead of just the noInteractive flag, addressing issues with prompts hanging in non-interactive environments like pre-commit hooks.

Changes

Cohort / File(s) Summary
Lazy-loaded prompt imports
src/commands/change.ts, src/commands/completion.ts, src/commands/show.ts, src/commands/spec.ts, src/core/archive.ts
Removed top-level static imports of select and/or confirm from @inquirer/prompts and replaced with dynamic imports within conditional code paths that require user prompts.
Interactive mode detection refactor
src/cli/index.ts, src/commands/change.ts, src/commands/show.ts, src/commands/spec.ts, src/commands/validate.ts, src/utils/interactive.ts
Updated calls to isInteractive() to pass the entire options object instead of just options?.noInteractive. Expanded the isInteractive function signature to accept value?: boolean | InteractiveOptions and introduced new internal types and resolver logic.
Validation command restructuring
src/commands/validate.ts
Added lazy import of select, early exit handling for empty item lists, new private methods normalizeType() and runInteractiveSelector(), and improved result accumulation with JSON output structure including version field.
Color flag handling
src/cli/index.ts
Changed global preAction color flag check from opts.noColor to opts.color === false for flag interpretation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Areas requiring extra attention:

  • src/commands/validate.ts: Contains the most substantial logic changes including new private methods, early exit handling, result aggregation, and JSON output structure—requires verification that all validation flows (bulk, interactive, single-item) correctly handle empty and non-empty cases.
  • src/utils/interactive.ts: The expanded signature and new resolveNoInteractive logic should be verified against all call sites to ensure the parameter passing from refactored commands correctly interprets interactive/non-interactive state.
  • Dynamic import error handling: Across all refactored files, verify that the lazy-loaded imports handle potential failures gracefully (especially in paths where prompts are conditionally loaded).

Poem

🐰 Lazy loading hops in light,
No prompts hanging in the night,
Dynamic imports, sleek and keen,
Interactive paths now clean,
Pre-commit hooks skip the delay—
Prompts only when we need to play!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR: converting static imports to dynamic imports to prevent CLI hangs in pre-commit hooks.
Linked Issues check ✅ Passed The PR implementation addresses all coding requirements from #367: dynamic imports for @inquirer/prompts and InitCommand, updated isInteractive() logic, and proper handling of empty validation queues.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives. The refactoring of interactive mode checks to use options objects instead of just noInteractive flag is a necessary supporting change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch TabishB/issue367-debug

📜 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 f39cc5c and 03ea323.

📒 Files selected for processing (8)
  • src/cli/index.ts (2 hunks)
  • src/commands/change.ts (2 hunks)
  • src/commands/completion.ts (1 hunks)
  • src/commands/show.ts (2 hunks)
  • src/commands/spec.ts (3 hunks)
  • src/commands/validate.ts (3 hunks)
  • src/core/archive.ts (4 hunks)
  • src/utils/interactive.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/commands/change.ts (2)
src/utils/interactive.ts (1)
  • isInteractive (17-21)
src/utils/item-discovery.ts (1)
  • getActiveChangeIds (4-23)
src/commands/spec.ts (2)
src/utils/interactive.ts (1)
  • isInteractive (17-21)
src/utils/item-discovery.ts (1)
  • getSpecIds (25-44)
src/commands/show.ts (1)
src/utils/interactive.ts (1)
  • isInteractive (17-21)
src/commands/validate.ts (1)
src/utils/interactive.ts (1)
  • isInteractive (17-21)
🔇 Additional comments (17)
src/cli/index.ts (2)

32-34: LGTM — Correct handling of Commander's negated option pattern.

Commander transforms --no-color into opts.color = false, so checking opts.color === false is the correct approach for detecting when color should be disabled.


65-69: LGTM — Dynamic import prevents stdin hang in non-interactive contexts.

Lazy-loading InitCommand at runtime (instead of statically importing at module load) ensures that @inquirer/core dependencies within InitCommand are not loaded when the CLI runs in non-interactive environments like pre-commit hooks. This directly addresses the root cause in issue #367.

src/utils/interactive.ts (1)

1-21: LGTM — Clean abstraction for dual-path interactive detection.

The resolveNoInteractive helper elegantly handles both the legacy noInteractive: true pattern and Commander's negated option (--no-interactiveinteractive: false). This enables callers to pass the entire options object without manual property extraction.

One note: the environment variable is OPEN_SPEC_INTERACTIVE here, while the issue mentions OPENSPEC_NO_INTERACTIVE. Verify this is intentional and document the supported env var(s) if both are valid.

src/commands/change.ts (2)

32-40: LGTM — Consistent migration to dynamic import pattern.

The isInteractive(options) call correctly leverages the new API that accepts the full options object, and the dynamic import of select is properly guarded within the interactive code path.


189-197: LGTM — Same dynamic import pattern applied consistently.

The validate method follows the identical pattern as show, ensuring @inquirer/prompts is only loaded when interactive prompts are actually needed.

src/commands/completion.ts (1)

180-191: LGTM — Dynamic import correctly scoped to confirmation path.

The confirm prompt is only imported when confirmation is actually needed (i.e., when skipConfirmation is false), preventing unnecessary module loading when --yes is passed.

src/commands/spec.ts (2)

72-79: LGTM — Dynamic import pattern applied to spec show.

The isInteractive(options) call and dynamic select import are correctly implemented within the interactive code path.


207-214: LGTM — Consistent pattern in spec validate action.

Same correct pattern applied to the validate command's interactive selector.

src/commands/show.ts (2)

15-27: LGTM — Dynamic import in execute's interactive path.

The isInteractive(options) call and the dynamic select import are correctly placed within the interactive code path.


47-55: LGTM — Dynamic import in runInteractiveByType.

The select prompt is dynamically imported at the start of this method, which is only called from interactive paths. The import is used for both the change and spec selection prompts within this method.

src/core/archive.ts (4)

127-141: LGTM — Dynamic import for validation skip confirmation.

The confirm prompt is correctly imported within the confirmation path when validation is skipped.


151-164: LGTM — Dynamic import for incomplete tasks confirmation.

Same pattern correctly applied for the incomplete tasks warning prompt.


182-191: LGTM — Dynamic import for spec update confirmation.

Same pattern correctly applied for the spec updates confirmation prompt.


260-302: LGTM — Dynamic import in selectChange.

The select prompt is dynamically imported within selectChange, which is only called when interactive change selection is needed.

src/commands/validate.ts (3)

31-31: LGTM — Updated to use new isInteractive API.

Correctly passes the full options object to isInteractive().


65-92: LGTM — Dynamic import in interactive selector.

The select prompt is dynamically imported within the interactive selector method, preventing module load at startup.


215-235: LGTM — Proper handling of empty validation queue.

This correctly addresses the PR objective of handling empty validation queues with:

  • Proper JSON output structure (including version field and empty items array)
  • Clear text message for non-JSON mode
  • Exit code 0 (success) since "nothing to validate" is not an error condition

This ensures the CLI exits cleanly in pre-commit hooks when no items match the validation criteria.


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.

@TabishB TabishB merged commit 68e0a7e into main Dec 21, 2025
7 checks passed
appboypov added a commit to appboypov/pew-pew-plx that referenced this pull request Dec 21, 2025
* fix(global-config): respect XDG_CONFIG_HOME on all platforms (Fission-AI#378)

Prioritize XDG_CONFIG_HOME on Windows to fix test environment overrides.
Previously, Windows would always use APPDATA regardless of XDG_CONFIG_HOME,
causing tests to fail. Now XDG_CONFIG_HOME is checked first on all platforms
before falling back to platform-specific defaults.

Also update the Windows APPDATA test to explicitly clear XDG_CONFIG_HOME
when testing the fallback behavior.

* fix(cli): prevent hang in pre-commit hooks by using dynamic imports (Fission-AI#380)

Fixes Fission-AI#367

The CLI was hanging when run as a pre-commit hook because @inquirer/prompts
was statically imported at module load time. Even when prompts were never
called (e.g., `openspec validate --specs --no-interactive`), the import
itself could set up stdin references that prevented clean process exit
when stdin was piped.

Changes:
- Convert all static `@inquirer/prompts` imports to dynamic imports
- Dynamically import `InitCommand` (which uses `@inquirer/core`)
- Update `isInteractive()` to accept options object with both
  `noInteractive` and Commander's negated `interactive` property
- Handle empty validation queue with proper exit code

Now when running in non-interactive mode, the inquirer modules are never
loaded, allowing the process to exit cleanly after completion.

* feat(cli): add plx command alias and rebrand as OpenSplx (OpenSplx-#1)

- Add `plx` as an alias command alongside `openspec`
- Create OpenSplx pixel art logo assets (light/dark themes)
- Update README with fork notice and Quick Start section
- Make CLI command name dynamic based on invocation
- Update completion system to support both command names
- Add command-name utility for detecting invoked command

* feat(cli): add external issue tracking support

- Add YAML frontmatter parsing for tracked issues in proposal.md
- Display issue identifiers in `openspec list` output
- Include trackedIssues in `openspec show --json` output
- Report tracked issues when archiving changes
- Add External Issue Tracking section to AGENTS.md template
- Add TrackedIssue schema and type exports

* fix(cli): address PR review feedback and archive external issue tracking

- Fix list.ts alignment to include issue display in width calculation
- Fix command-name.ts to handle Windows extensions and cross-platform paths
- Fix postinstall.js to install completions for both openspec and plx
- Fix change.ts issue display format (after title in long format)
- Add comprehensive unit tests for all fixes
- Archive add-external-issue-tracking change with spec updates

---------

Co-authored-by: Tabish Bidiwale <[email protected]>
appboypov added a commit to appboypov/pew-pew-plx that referenced this pull request Dec 21, 2025
* fix(global-config): respect XDG_CONFIG_HOME on all platforms (Fission-AI#378)

Prioritize XDG_CONFIG_HOME on Windows to fix test environment overrides.
Previously, Windows would always use APPDATA regardless of XDG_CONFIG_HOME,
causing tests to fail. Now XDG_CONFIG_HOME is checked first on all platforms
before falling back to platform-specific defaults.

Also update the Windows APPDATA test to explicitly clear XDG_CONFIG_HOME
when testing the fallback behavior.

* fix(cli): prevent hang in pre-commit hooks by using dynamic imports (Fission-AI#380)

Fixes Fission-AI#367

The CLI was hanging when run as a pre-commit hook because @inquirer/prompts
was statically imported at module load time. Even when prompts were never
called (e.g., `openspec validate --specs --no-interactive`), the import
itself could set up stdin references that prevented clean process exit
when stdin was piped.

Changes:
- Convert all static `@inquirer/prompts` imports to dynamic imports
- Dynamically import `InitCommand` (which uses `@inquirer/core`)
- Update `isInteractive()` to accept options object with both
  `noInteractive` and Commander's negated `interactive` property
- Handle empty validation queue with proper exit code

Now when running in non-interactive mode, the inquirer modules are never
loaded, allowing the process to exit cleanly after completion.

* feat(cli): add plx command alias and rebrand as OpenSplx (OpenSplx-#1)

- Add `plx` as an alias command alongside `openspec`
- Create OpenSplx pixel art logo assets (light/dark themes)
- Update README with fork notice and Quick Start section
- Make CLI command name dynamic based on invocation
- Update completion system to support both command names
- Add command-name utility for detecting invoked command

* feat(cli): add external issue tracking support

- Add YAML frontmatter parsing for tracked issues in proposal.md
- Display issue identifiers in `openspec list` output
- Include trackedIssues in `openspec show --json` output
- Report tracked issues when archiving changes
- Add External Issue Tracking section to AGENTS.md template
- Add TrackedIssue schema and type exports

* fix(cli): address PR review feedback and archive external issue tracking

- Fix list.ts alignment to include issue display in width calculation
- Fix command-name.ts to handle Windows extensions and cross-platform paths
- Fix postinstall.js to install completions for both openspec and plx
- Fix change.ts issue display format (after title in long format)
- Add comprehensive unit tests for all fixes
- Archive add-external-issue-tracking change with spec updates

---------

Co-authored-by: Tabish Bidiwale <[email protected]>
TabishB added a commit that referenced this pull request Dec 22, 2025
The config command (added in #382) reintroduced the pre-commit hook hang
issue that was fixed in #380. The static import of @inquirer/prompts at
module load time causes stdin event listeners to be registered even when
running non-interactive commands, preventing clean process exit when
stdin is piped (as pre-commit does).

Convert the static import to a dynamic import that only loads inquirer
when the `config reset` command is actually used interactively.

Fixes #367
TabishB added a commit that referenced this pull request Dec 22, 2025
…392)

* fix(cli): use dynamic import for @inquirer/prompts in config command

The config command (added in #382) reintroduced the pre-commit hook hang
issue that was fixed in #380. The static import of @inquirer/prompts at
module load time causes stdin event listeners to be registered even when
running non-interactive commands, preventing clean process exit when
stdin is piped (as pre-commit does).

Convert the static import to a dynamic import that only loads inquirer
when the `config reset` command is actually used interactively.

Fixes #367

* chore: add ESLint with no-restricted-imports rule for @InQuirer

Add ESLint configuration that prevents static imports of @inquirer/*
modules. This prevents future regressions of the pre-commit hook hang
issue fixed in this PR.

The rule shows a helpful error message pointing to issue #367 for context.
init.ts is exempted since it's already dynamically imported from the CLI.

* ci: add ESLint step to lint job

Run `pnpm lint` in CI to enforce the no-restricted-imports rule
that prevents static @InQuirer imports.
appboypov added a commit to appboypov/pew-pew-plx that referenced this pull request Dec 24, 2025
* feat(cli): add openspec config command for global configuration management (Fission-AI#382)

* feat(cli): add openspec config command for global configuration management

Implements the `openspec config` command with subcommands:
- `path`: Show config file location
- `list [--json]`: Show all current settings
- `get <key>`: Get a specific value (raw output for scripting)
- `set <key> <value> [--string]`: Set a value with auto type coercion
- `unset <key>`: Remove a key (revert to default)
- `reset --all [-y]`: Reset configuration to defaults
- `edit`: Open config in $EDITOR/$VISUAL

Key features:
- Dot notation for nested key access (e.g., featureFlags.someFlag)
- Auto type coercion (true/false → boolean, numbers → number)
- --string flag to force string storage
- Zod schema validation with unknown field passthrough
- Reserved --scope flag for future project-local config
- Windows-compatible editor spawning with proper path quoting
- Shell completion registry integration

* test(config): add additional unit tests for validation and coercion

- Add tests for unknown fields with various types
- Add test to verify error message path for featureFlags
- Add test for number values rejection in featureFlags
- Add config set simulation tests to verify full coerce → set → validate flow

* fix(config): avoid shell parsing in config edit to handle paths with spaces

Use spawn with shell: false and pass configPath as an argument instead
of building a shell command string. This correctly handles spaces in
both the EDITOR path and config file path on all platforms.

* chore(openspec): archive add-config-command and create cli-config spec

Move completed change to archive and apply spec deltas to create
the cli-config specification documenting the config command interface.

* Validate config keys on set

* Add changeset for config command and shell completions (Fission-AI#388)

* chore(release): version packages (Fission-AI#389)

* Version Packages

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <[email protected]>

* feat(ci): migrate to npm OIDC trusted publishing (Fission-AI#390)

Replace classic npm token authentication with OIDC trusted publishing:

- Add `id-token: write` permission for OIDC token generation
- Upgrade to Node 24 (includes npm 11.5.1+ required for OIDC)
- Remove NPM_TOKEN/NODE_AUTH_TOKEN env vars (OIDC replaces them)

This eliminates the need for rotating npm access tokens and provides
cryptographically verified publisher identity with automatic provenance
attestation.

Requires configuring trusted publisher on npmjs.com:
- Organization: Fission-AI
- Repository: OpenSpec
- Workflow: release-prepare.yml

* fix(cli): use dynamic import for @inquirer/prompts in config command (Fission-AI#392)

* fix(cli): use dynamic import for @inquirer/prompts in config command

The config command (added in Fission-AI#382) reintroduced the pre-commit hook hang
issue that was fixed in Fission-AI#380. The static import of @inquirer/prompts at
module load time causes stdin event listeners to be registered even when
running non-interactive commands, preventing clean process exit when
stdin is piped (as pre-commit does).

Convert the static import to a dynamic import that only loads inquirer
when the `config reset` command is actually used interactively.

Fixes Fission-AI#367

* chore: add ESLint with no-restricted-imports rule for @InQuirer

Add ESLint configuration that prevents static imports of @inquirer/*
modules. This prevents future regressions of the pre-commit hook hang
issue fixed in this PR.

The rule shows a helpful error message pointing to issue Fission-AI#367 for context.
init.ts is exempted since it's already dynamically imported from the CLI.

* ci: add ESLint step to lint job

Run `pnpm lint` in CI to enforce the no-restricted-imports rule
that prevents static @InQuirer imports.

* Add changeset for config command dynamic import fix (Fission-AI#393)

* chore(release): version packages (Fission-AI#394)

* Version Packages

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <[email protected]>

* fix(cli): respect --no-interactive flag in validate command (Fission-AI#395)

* fix(cli): respect --no-interactive flag in validate command

The validate command's spinner was starting regardless of the
--no-interactive flag, causing hangs in pre-commit hooks.

Changes:
- Pass noInteractive option to runBulkValidation
- Handle Commander.js --no-* flag syntax (sets interactive=false)
- Only start ora spinner when in interactive mode
- Add CI environment variable check to isInteractive() for industry
  standard compliance

* test: add unit tests for interactive utilities and CLI flag

- Export resolveNoInteractive() helper for reuse
- Add InteractiveOptions type export for testing
- Refactor validate.ts to use resolveNoInteractive()
- Add 17 unit tests for isInteractive() and resolveNoInteractive()
- Add CLI integration test for --no-interactive flag

This prevents future regressions where Commander.js --no-* flag
parsing is not properly handled.

* Add changeset for --no-interactive flag fix (Fission-AI#396)

* chore(release): version packages (Fission-AI#397)

* Version Packages

* chore: trigger CI

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <[email protected]>

* docs: add artifact POC analysis document (Fission-AI#398)

Add internal documentation for the artifact-based approach to OpenSpec
core. This document outlines design decisions, terminology, and the
philosophy behind treating dependencies as enablers rather than gates.

* fix(archive): allow REMOVED requirements when creating new spec files (Fission-AI#403) (Fission-AI#404)

When creating a new spec file, REMOVED requirements are now ignored
with a warning instead of causing archive to fail. This enables
refactoring scenarios where old fields are removed while documenting
a capability for the first time.

Fixes Fission-AI#403

---------

Co-authored-by: Tabish Bidiwale <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Tabish Bidiwale <[email protected]>
Co-authored-by: Eunsong-Park <[email protected]>
appboypov pushed a commit to appboypov/pew-pew-plx that referenced this pull request Dec 24, 2025
…ission-AI#392)

* fix(cli): use dynamic import for @inquirer/prompts in config command

The config command (added in Fission-AI#382) reintroduced the pre-commit hook hang
issue that was fixed in Fission-AI#380. The static import of @inquirer/prompts at
module load time causes stdin event listeners to be registered even when
running non-interactive commands, preventing clean process exit when
stdin is piped (as pre-commit does).

Convert the static import to a dynamic import that only loads inquirer
when the `config reset` command is actually used interactively.

Fixes Fission-AI#367

* chore: add ESLint with no-restricted-imports rule for @InQuirer

Add ESLint configuration that prevents static imports of @inquirer/*
modules. This prevents future regressions of the pre-commit hook hang
issue fixed in this PR.

The rule shows a helpful error message pointing to issue Fission-AI#367 for context.
init.ts is exempted since it's already dynamically imported from the CLI.

* ci: add ESLint step to lint job

Run `pnpm lint` in CI to enforce the no-restricted-imports rule
that prevents static @InQuirer imports.
appboypov pushed a commit to appboypov/pew-pew-plx that referenced this pull request Dec 24, 2025
…ission-AI#392)

* fix(cli): use dynamic import for @inquirer/prompts in config command

The config command (added in Fission-AI#382) reintroduced the pre-commit hook hang
issue that was fixed in Fission-AI#380. The static import of @inquirer/prompts at
module load time causes stdin event listeners to be registered even when
running non-interactive commands, preventing clean process exit when
stdin is piped (as pre-commit does).

Convert the static import to a dynamic import that only loads inquirer
when the `config reset` command is actually used interactively.

Fixes Fission-AI#367

* chore: add ESLint with no-restricted-imports rule for @InQuirer

Add ESLint configuration that prevents static imports of @inquirer/*
modules. This prevents future regressions of the pre-commit hook hang
issue fixed in this PR.

The rule shows a helpful error message pointing to issue Fission-AI#367 for context.
init.ts is exempted since it's already dynamically imported from the CLI.

* ci: add ESLint step to lint job

Run `pnpm lint` in CI to enforce the no-restricted-imports rule
that prevents static @InQuirer imports.
Kh05ifr4nD pushed a commit to Kh05ifr4nD/sorapec that referenced this pull request Jan 6, 2026
…ission-AI#380)

Fixes Fission-AI#367

The CLI was hanging when run as a pre-commit hook because @inquirer/prompts
was statically imported at module load time. Even when prompts were never
called (e.g., `openspec validate --specs --no-interactive`), the import
itself could set up stdin references that prevented clean process exit
when stdin was piped.

Changes:
- Convert all static `@inquirer/prompts` imports to dynamic imports
- Dynamically import `InitCommand` (which uses `@inquirer/core`)
- Update `isInteractive()` to accept options object with both
  `noInteractive` and Commander's negated `interactive` property
- Handle empty validation queue with proper exit code

Now when running in non-interactive mode, the inquirer modules are never
loaded, allowing the process to exit cleanly after completion.
Kh05ifr4nD pushed a commit to Kh05ifr4nD/sorapec that referenced this pull request Jan 6, 2026
…ission-AI#392)

* fix(cli): use dynamic import for @inquirer/prompts in config command

The config command (added in Fission-AI#382) reintroduced the pre-commit hook hang
issue that was fixed in Fission-AI#380. The static import of @inquirer/prompts at
module load time causes stdin event listeners to be registered even when
running non-interactive commands, preventing clean process exit when
stdin is piped (as pre-commit does).

Convert the static import to a dynamic import that only loads inquirer
when the `config reset` command is actually used interactively.

Fixes Fission-AI#367

* chore: add ESLint with no-restricted-imports rule for @InQuirer

Add ESLint configuration that prevents static imports of @inquirer/*
modules. This prevents future regressions of the pre-commit hook hang
issue fixed in this PR.

The rule shows a helpful error message pointing to issue Fission-AI#367 for context.
init.ts is exempted since it's already dynamically imported from the CLI.

* ci: add ESLint step to lint job

Run `pnpm lint` in CI to enforce the no-restricted-imports rule
that prevents static @InQuirer imports.
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.

openspec validate hangs when runnings as pre-commit hook

2 participants