From 780acb0ec02ca2e8b15b267ffb6228da27e2a38b Mon Sep 17 00:00:00 2001 From: Tabish Bidiwale Date: Sat, 20 Dec 2025 19:43:30 +1100 Subject: [PATCH] feat(spec): add XDG global config directory and config command proposals Create two OpenSpec change proposals: 1. add-global-config-dir: Foundation for user-level configuration following XDG Base Directory Specification with cross-platform support 2. add-config-command: User-facing CLI command for viewing and managing global settings Both proposals are minimal and focused on providing a clean, extensible base for OpenSpec settings and future feature flags. --- .../changes/add-config-command/proposal.md | 39 +++++++ .../changes/add-global-config-dir/design.md | 105 ++++++++++++++++++ .../changes/add-global-config-dir/proposal.md | 20 ++++ .../specs/global-config/spec.md | 76 +++++++++++++ .../changes/add-global-config-dir/tasks.md | 25 +++++ 5 files changed, 265 insertions(+) create mode 100644 openspec/changes/add-config-command/proposal.md create mode 100644 openspec/changes/add-global-config-dir/design.md create mode 100644 openspec/changes/add-global-config-dir/proposal.md create mode 100644 openspec/changes/add-global-config-dir/specs/global-config/spec.md create mode 100644 openspec/changes/add-global-config-dir/tasks.md diff --git a/openspec/changes/add-config-command/proposal.md b/openspec/changes/add-config-command/proposal.md new file mode 100644 index 00000000..8374ef4a --- /dev/null +++ b/openspec/changes/add-config-command/proposal.md @@ -0,0 +1,39 @@ +## Why + +Users need a way to view and modify their global OpenSpec settings without manually editing JSON files. The `add-global-config-dir` change provides the foundation, but there's no user-facing interface to interact with the config. A dedicated `openspec config` command provides discoverability and ease of use. + +## What Changes + +Add `openspec config` subcommand with the following operations: + +```bash +openspec config path # Show config file location +openspec config list # Show all current settings +openspec config get # Get a specific value +openspec config set # Set a value +openspec config reset [key] # Reset to defaults (all or specific key) +``` + +**Example usage:** +```bash +$ openspec config path +/Users/me/.config/openspec/config.json + +$ openspec config list +enableTelemetry: true +featureFlags: {} + +$ openspec config set enableTelemetry false +Set enableTelemetry = false + +$ openspec config get enableTelemetry +false +``` + +## Impact + +- Affected specs: New `cli-config` capability +- Affected code: + - New `src/commands/config.ts` + - Update CLI entry point to register config command +- Dependencies: Requires `add-global-config-dir` to be implemented first diff --git a/openspec/changes/add-global-config-dir/design.md b/openspec/changes/add-global-config-dir/design.md new file mode 100644 index 00000000..3f6ec5be --- /dev/null +++ b/openspec/changes/add-global-config-dir/design.md @@ -0,0 +1,105 @@ +## Context + +OpenSpec needs a standard location for user-level configuration that works across platforms and follows established conventions. This will serve as the foundation for settings, feature flags, and future artifacts like workflows or templates. + +## Goals / Non-Goals + +**Goals:** +- Provide a single, well-defined location for global config +- Follow XDG Base Directory Specification (widely adopted by CLI tools) +- Support cross-platform usage (Unix, macOS, Windows) +- Keep implementation minimal - just the foundation +- Enable future expansion (cache, state, workflows) + +**Non-Goals:** +- Project-local config override (not in scope) +- Config file migration tooling +- Config validation CLI commands +- Multiple config profiles + +## Decisions + +### Path Resolution Strategy + +**Decision:** Use XDG Base Directory Specification with platform fallbacks. + +``` +Unix/macOS: $XDG_CONFIG_HOME/openspec/ or ~/.config/openspec/ +Windows: %APPDATA%/openspec/ +``` + +**Rationale:** +- XDG is the de facto standard for CLI tools (used by gh, bat, ripgrep, etc.) +- Environment variable override allows user customization +- Windows uses its native convention (%APPDATA%) for better integration + +**Alternatives considered:** +- `~/.openspec/` - Simple but clutters home directory +- `~/Library/Application Support/` on macOS - Overkill for a CLI tool + +### Config File Format + +**Decision:** JSON (`config.json`) + +**Rationale:** +- Native Node.js support (no dependencies) +- Human-readable and editable +- Type-safe with TypeScript +- Matches project.md's "minimal dependencies" principle + +**Alternatives considered:** +- YAML - Requires dependency, more error-prone to edit +- TOML - Less common in Node.js ecosystem +- Environment variables only - Too limited for structured settings + +### Config Schema + +**Decision:** Flat structure with typed fields, start minimal. + +```typescript +interface GlobalConfig { + featureFlags?: Record; +} +``` + +**Rationale:** +- `featureFlags` enables controlled rollout of new features +- Optional fields with defaults avoid breaking changes +- Flat structure is easy to understand and extend + +### Loading Strategy + +**Decision:** Read from disk on each call, no caching. + +```typescript +export function getGlobalConfig(): GlobalConfig { + return loadConfigFromDisk(); +} +``` + +**Rationale:** +- CLI commands are short-lived; caching adds complexity without benefit +- Reading a small JSON file is ~1ms; negligible overhead +- Always returns fresh data; no cache invalidation concerns +- Simpler implementation + +### Directory Creation + +**Decision:** Create directory only when saving, not when reading. + +**Rationale:** +- Don't create empty directories on read operations +- Users who never save config won't have unnecessary directories +- Aligns with principle of least surprise + +## Risks / Trade-offs + +| Risk | Mitigation | +|------|------------| +| Config file corruption | Return defaults on parse error, log warning | +| Permissions issues | Check write permissions before save, clear error message | +| Future schema changes | Use optional fields, add version field if needed later | + +## Open Questions + +None - this proposal is intentionally minimal. diff --git a/openspec/changes/add-global-config-dir/proposal.md b/openspec/changes/add-global-config-dir/proposal.md new file mode 100644 index 00000000..35cc14b5 --- /dev/null +++ b/openspec/changes/add-global-config-dir/proposal.md @@ -0,0 +1,20 @@ +## Why + +OpenSpec currently has no mechanism for user-level global settings or feature flags. As the CLI grows, we need a standard location to store user preferences, experimental features, and other configuration that persists across projects. Following XDG Base Directory Specification provides a well-understood, cross-platform approach. + +## What Changes + +- Add new `src/core/global-config.ts` module with: + - Path resolution following XDG Base Directory spec (`$XDG_CONFIG_HOME/openspec/` or fallback) + - Cross-platform support (Unix, macOS, Windows) + - Lazy config loading with sensible defaults + - TypeScript types for config shape +- Export a global config directory path getter for future use (workflows, templates, cache) +- Initial config schema supports 1-2 settings/feature flags only + +## Impact + +- Affected specs: New `global-config` capability (no existing specs modified) +- Affected code: + - New `src/core/global-config.ts` + - Update `src/core/index.ts` to export new module diff --git a/openspec/changes/add-global-config-dir/specs/global-config/spec.md b/openspec/changes/add-global-config-dir/specs/global-config/spec.md new file mode 100644 index 00000000..87e0106d --- /dev/null +++ b/openspec/changes/add-global-config-dir/specs/global-config/spec.md @@ -0,0 +1,76 @@ +## ADDED Requirements + +### Requirement: Global Config Directory Path + +The system SHALL resolve the global configuration directory path following XDG Base Directory Specification with platform-specific fallbacks. + +#### Scenario: Unix/macOS with XDG_CONFIG_HOME set +- **WHEN** `$XDG_CONFIG_HOME` environment variable is set to `/custom/config` +- **THEN** `getGlobalConfigDir()` returns `/custom/config/openspec` + +#### Scenario: Unix/macOS without XDG_CONFIG_HOME +- **WHEN** `$XDG_CONFIG_HOME` environment variable is not set +- **AND** the platform is Unix or macOS +- **THEN** `getGlobalConfigDir()` returns `~/.config/openspec` (expanded to absolute path) + +#### Scenario: Windows platform +- **WHEN** the platform is Windows +- **AND** `%APPDATA%` is set to `C:\Users\User\AppData\Roaming` +- **THEN** `getGlobalConfigDir()` returns `C:\Users\User\AppData\Roaming\openspec` + +### Requirement: Global Config Loading + +The system SHALL load global configuration from the config directory with sensible defaults when the config file does not exist or cannot be parsed. + +#### Scenario: Config file exists and is valid +- **WHEN** `config.json` exists in the global config directory +- **AND** the file contains valid JSON matching the config schema +- **THEN** `getGlobalConfig()` returns the parsed configuration + +#### Scenario: Config file does not exist +- **WHEN** `config.json` does not exist in the global config directory +- **THEN** `getGlobalConfig()` returns the default configuration +- **AND** no directory or file is created + +#### Scenario: Config file is invalid JSON +- **WHEN** `config.json` exists but contains invalid JSON +- **THEN** `getGlobalConfig()` returns the default configuration +- **AND** a warning is logged to stderr + +### Requirement: Global Config Saving + +The system SHALL save global configuration to the config directory, creating the directory if it does not exist. + +#### Scenario: Save config to new directory +- **WHEN** `saveGlobalConfig(config)` is called +- **AND** the global config directory does not exist +- **THEN** the directory is created +- **AND** `config.json` is written with the provided configuration + +#### Scenario: Save config to existing directory +- **WHEN** `saveGlobalConfig(config)` is called +- **AND** the global config directory already exists +- **THEN** `config.json` is written (overwriting if exists) + +### Requirement: Default Configuration + +The system SHALL provide a default configuration that is used when no config file exists. + +#### Scenario: Default config structure +- **WHEN** no config file exists +- **THEN** the default configuration includes an empty `featureFlags` object + +### Requirement: Config Schema Evolution + +The system SHALL merge loaded configuration with default values to ensure new config fields are available even when loading older config files. + +#### Scenario: Config file missing new fields +- **WHEN** `config.json` exists with `{ "featureFlags": {} }` +- **AND** the current schema includes a new field `defaultAiTool` +- **THEN** `getGlobalConfig()` returns `{ featureFlags: {}, defaultAiTool: }` +- **AND** the loaded values take precedence over defaults for fields that exist in both + +#### Scenario: Config file has extra unknown fields +- **WHEN** `config.json` contains fields not in the current schema +- **THEN** the unknown fields are preserved in the returned configuration +- **AND** no error or warning is raised diff --git a/openspec/changes/add-global-config-dir/tasks.md b/openspec/changes/add-global-config-dir/tasks.md new file mode 100644 index 00000000..5fecd731 --- /dev/null +++ b/openspec/changes/add-global-config-dir/tasks.md @@ -0,0 +1,25 @@ +## 1. Core Implementation + +- [ ] 1.1 Create `src/core/global-config.ts` with path resolution + - Implement `getGlobalConfigDir()` following XDG spec + - Support `$XDG_CONFIG_HOME` environment variable override + - Platform-specific fallbacks (Unix: `~/.config/`, Windows: `%APPDATA%`) +- [ ] 1.2 Define TypeScript interfaces for config shape + - `GlobalConfig` interface with optional fields + - Start minimal: just `featureFlags?: Record` +- [ ] 1.3 Implement config loading with defaults + - `getGlobalConfig()` - reads config.json if exists, merges with defaults + - No directory/file creation on read (lazy initialization) +- [ ] 1.4 Implement config saving + - `saveGlobalConfig(config)` - writes config.json, creates directory if needed + +## 2. Integration + +- [ ] 2.1 Export new module from `src/core/index.ts` +- [ ] 2.2 Add constants for config file name and directory name + +## 3. Testing + +- [ ] 3.1 Manual testing of path resolution on current platform +- [ ] 3.2 Test with/without `$XDG_CONFIG_HOME` set +- [ ] 3.3 Test config load when file doesn't exist (should return defaults)