Skip to content

[Feature Request] Allow configurable OpenSpec directory name #581

@diegohb

Description

@diegohb

Problem Statement

Currently, the OpenSpec directory name is hardcoded as OPENSPEC_DIR_NAME = 'openspec' in src/core/config.ts 1 . This prevents users from customizing the directory name to suit their project conventions or preferences.

Use Case

Users working in environments with specific naming conventions or those who want to integrate OpenSpec into existing project structures may need to use a different directory name than the default openspec/. For example, a team might prefer .openspec/, specs/, or docs/openspec/ to match their existing documentation structure.

Proposed Solution

Add configuration support for customizing the OpenSpec directory name through the existing configuration systems:

Option 1: Global Configuration

Add a directoryName field to the GlobalConfig interface 2 :

export interface GlobalConfig {
  featureFlags?: Record<string, boolean>;
  directoryName?: string; // New field
}

Option 2: Project Configuration

Add support for openspec.config.yaml (as discussed in docs/project-config-demo.md 3 ) with a directoryName field:

# openspec.config.yaml
directoryName: .openspec  # or any preferred name

Implementation Approach

  1. Modify config resolution: Update the configuration loading logic to check for a custom directory name, with precedence:

    • Project-level openspec.config.yaml (highest priority)
    • Global config 4
    • Default 'openspec' (fallback)
  2. Update directory references: Replace hardcoded OPENSPEC_DIR_NAME usage with a function that resolves the directory name based on configuration.

  3. Update CLI commands: Modify init, update, and other commands to use the configurable directory name.

Benefits

  • Flexibility: Teams can adopt OpenSpec without disrupting existing directory structures
  • Integration: Better compatibility with different project layouts and conventions
  • Backward compatibility: Default behavior remains unchanged for existing users

Files to Modify

  • src/core/config.ts - Add directory name resolution logic
  • src/core/global-config.ts - Extend GlobalConfig interface
  • src/core/init.ts - Use configurable directory name 5
  • src/core/update.ts - Update directory resolution 6
  • src/commands/config.ts - Add config management for directory name

Notes

This feature leverages the existing configuration infrastructure 7 and aligns with the project configuration work already documented in docs/project-config-demo.md 3 .

Citations

File: src/core/config.ts (L1-1)

export const OPENSPEC_DIR_NAME = 'openspec';

File: src/core/global-config.ts (L1-79)

import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';

// Constants
export const GLOBAL_CONFIG_DIR_NAME = 'openspec';
export const GLOBAL_CONFIG_FILE_NAME = 'config.json';
export const GLOBAL_DATA_DIR_NAME = 'openspec';

// TypeScript interfaces
export interface GlobalConfig {
  featureFlags?: Record<string, boolean>;
}

const DEFAULT_CONFIG: GlobalConfig = {
  featureFlags: {}
};

/**
 * Gets the global configuration directory path following XDG Base Directory Specification.
 *
 * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
 * - Unix/macOS fallback: ~/.config/openspec/
 * - Windows fallback: %APPDATA%/openspec/
 */
export function getGlobalConfigDir(): string {
  // XDG_CONFIG_HOME takes precedence on all platforms when explicitly set
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
  if (xdgConfigHome) {
    return path.join(xdgConfigHome, GLOBAL_CONFIG_DIR_NAME);
  }

  const platform = os.platform();

  if (platform === 'win32') {
    // Windows: use %APPDATA%
    const appData = process.env.APPDATA;
    if (appData) {
      return path.join(appData, GLOBAL_CONFIG_DIR_NAME);
    }
    // Fallback for Windows if APPDATA is not set
    return path.join(os.homedir(), 'AppData', 'Roaming', GLOBAL_CONFIG_DIR_NAME);
  }

  // Unix/macOS fallback: ~/.config
  return path.join(os.homedir(), '.config', GLOBAL_CONFIG_DIR_NAME);
}

/**
 * Gets the global data directory path following XDG Base Directory Specification.
 * Used for user data like schema overrides.
 *
 * - All platforms: $XDG_DATA_HOME/openspec/ if XDG_DATA_HOME is set
 * - Unix/macOS fallback: ~/.local/share/openspec/
 * - Windows fallback: %LOCALAPPDATA%/openspec/
 */
export function getGlobalDataDir(): string {
  // XDG_DATA_HOME takes precedence on all platforms when explicitly set
  const xdgDataHome = process.env.XDG_DATA_HOME;
  if (xdgDataHome) {
    return path.join(xdgDataHome, GLOBAL_DATA_DIR_NAME);
  }

  const platform = os.platform();

  if (platform === 'win32') {
    // Windows: use %LOCALAPPDATA%
    const localAppData = process.env.LOCALAPPDATA;
    if (localAppData) {
      return path.join(localAppData, GLOBAL_DATA_DIR_NAME);
    }
    // Fallback for Windows if LOCALAPPDATA is not set
    return path.join(os.homedir(), 'AppData', 'Local', GLOBAL_DATA_DIR_NAME);
  }

  // Unix/macOS fallback: ~/.local/share
  return path.join(os.homedir(), '.local', 'share', GLOBAL_DATA_DIR_NAME);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions