* 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
7.0 KiB
cli-config Specification
Purpose
Provide a user-friendly CLI interface for viewing and modifying global OpenSpec configuration settings without manually editing JSON files.
Requirements
Requirement: Command Structure
The config command SHALL provide subcommands for all configuration operations.
Scenario: Available subcommands
- WHEN user executes
openspec config --help - THEN display available subcommands:
path- Show config file locationlist- Show all current settingsget <key>- Get a specific valueset <key> <value>- Set a valueunset <key>- Remove a key (revert to default)reset- Reset configuration to defaultsedit- Open config in editor
Requirement: Config Path
The config command SHALL display the config file location.
Scenario: Show config path
- WHEN user executes
openspec config path - THEN print the absolute path to the config file
- AND exit with code 0
Requirement: Config List
The config command SHALL display all current configuration values.
Scenario: List config in human-readable format
- WHEN user executes
openspec config list - THEN display all config values in YAML-like format
- AND show nested objects with indentation
Scenario: List config as JSON
- WHEN user executes
openspec config list --json - THEN output the complete config as valid JSON
- AND output only JSON (no additional text)
Requirement: Config Get
The config command SHALL retrieve specific configuration values.
Scenario: Get top-level key
- WHEN user executes
openspec config get <key>with a valid top-level key - THEN print the raw value only (no labels or formatting)
- AND exit with code 0
Scenario: Get nested key with dot notation
- WHEN user executes
openspec config get featureFlags.someFlag - THEN traverse the nested structure using dot notation
- AND print the value at that path
Scenario: Get non-existent key
- WHEN user executes
openspec config get <key>with a key that does not exist - THEN print nothing (empty output)
- AND exit with code 1
Scenario: Get object value
- WHEN user executes
openspec config get <key>where the value is an object - THEN print the object as JSON
Requirement: Config Set
The config command SHALL set configuration values with automatic type coercion.
Scenario: Set string value
- WHEN user executes
openspec config set <key> <value> - AND value does not match boolean or number patterns
- THEN store value as a string
- AND display confirmation message
Scenario: Set boolean value
- WHEN user executes
openspec config set <key> trueoropenspec config set <key> false - THEN store value as boolean (not string)
- AND display confirmation message
Scenario: Set numeric value
- WHEN user executes
openspec config set <key> <value> - AND value is a valid number (integer or float)
- THEN store value as number (not string)
Scenario: Force string with --string flag
- WHEN user executes
openspec config set <key> <value> --string - THEN store value as string regardless of content
- AND this allows storing literal "true" or "123" as strings
Scenario: Set nested key
- WHEN user executes
openspec config set featureFlags.newFlag true - THEN create intermediate objects if they don't exist
- AND set the value at the nested path
Requirement: Config Unset
The config command SHALL remove configuration overrides.
Scenario: Unset existing key
- WHEN user executes
openspec config unset <key> - AND the key exists in the config
- THEN remove the key from the config file
- AND the value reverts to its default
- AND display confirmation message
Scenario: Unset non-existent key
- WHEN user executes
openspec config unset <key> - AND the key does not exist in the config
- THEN display message indicating key was not set
- AND exit with code 0
Requirement: Config Reset
The config command SHALL reset configuration to defaults.
Scenario: Reset all with confirmation
- WHEN user executes
openspec config reset --all - THEN prompt for confirmation before proceeding
- AND if confirmed, delete the config file or reset to defaults
- AND display confirmation message
Scenario: Reset all with -y flag
- WHEN user executes
openspec config reset --all -y - THEN reset without prompting for confirmation
Scenario: Reset without --all flag
- WHEN user executes
openspec config resetwithout--all - THEN display error indicating
--allis required - AND exit with code 1
Requirement: Config Edit
The config command SHALL open the config file in the user's editor.
Scenario: Open editor successfully
- WHEN user executes
openspec config edit - AND
$EDITORor$VISUALenvironment variable is set - THEN open the config file in that editor
- AND create the config file with defaults if it doesn't exist
- AND wait for the editor to close before returning
Scenario: No editor configured
- WHEN user executes
openspec config edit - AND neither
$EDITORnor$VISUALis set - THEN display error message suggesting to set
$EDITOR - AND exit with code 1
Requirement: Key Naming Convention
The config command SHALL use camelCase keys matching the JSON structure.
Scenario: Keys match JSON structure
- WHEN accessing configuration keys via CLI
- THEN use camelCase matching the actual JSON property names
- AND support dot notation for nested access (e.g.,
featureFlags.someFlag)
Requirement: Schema Validation
The config command SHALL validate configuration writes against the config schema using zod, while rejecting unknown keys for config set unless explicitly overridden.
Scenario: Unknown key rejected by default
- WHEN user executes
openspec config set someFutureKey 123 - THEN display a descriptive error message indicating the key is invalid
- AND do not modify the config file
- AND exit with code 1
Scenario: Unknown key accepted with override
- WHEN user executes
openspec config set someFutureKey 123 --allow-unknown - THEN the value is saved successfully
- AND exit with code 0
Scenario: Invalid feature flag value rejected
- WHEN user executes
openspec config set featureFlags.someFlag notABoolean - THEN display a descriptive error message
- AND do not modify the config file
- AND exit with code 1
Requirement: Reserved Scope Flag
The config command SHALL reserve the --scope flag for future extensibility.
Scenario: Scope flag defaults to global
- WHEN user executes any config command without
--scope - THEN operate on global configuration (default behavior)
Scenario: Project scope not yet implemented
- WHEN user executes
openspec config --scope project <subcommand> - THEN display error message: "Project-local config is not yet implemented"
- AND exit with code 1