Skip to content

Support project-scoped profile/workflow config via --scope project #914

Open

Description

@kitimark

Problem

OpenSpec profile/workflow configuration is currently global-only, which causes cross-project coupling.

Today:

  • openspec config advertises only global scope
  • openspec update reads global config and regenerates project skills/commands from it

This means enabling/disabling workflows (e.g. sync) for one repo can unintentionally affect other repos on the same machine.

Relevant source:

  • src/commands/config.ts:
    • --scope <scope> says only global is supported
    • non-global scope is rejected with:
      Error: Project-local config is not yet implemented
  • src/core/global-config.ts: config path is global ($XDG_CONFIG_HOME / ~/.config)
  • src/core/update.ts: update resolves workflows from global config

Proposed Fix

Add project-scoped config support for profile/workflows with explicit precedence.

Scope model

  • --scope global (existing)
  • --scope project (new, repository-scoped)

Precedence (highest -> lowest)

  1. CLI flags
  2. Project config (--scope project)
  3. Global config (--scope global)
  4. Built-in defaults (core, both)

CLI behavior

  • openspec config list --scope project
  • openspec config get <key> --scope project
  • openspec config set <key> <value> --scope project
  • openspec config profile --scope project
  • openspec update should use effective config from precedence above

Acceptance Criteria

  • --scope project is supported for config read/write commands
  • Project-scoped profile, delivery, and workflows can be persisted in-repo
  • openspec update uses project config when present
  • Global behavior remains unchanged when project config is absent
  • Docs clearly explain scope semantics and precedence
  • Existing users are not broken (project scope is opt-in)

Why this helps

Activity

  1. 21st-connector commented on Apr 3, 2026

    @21st-connector
    Contributor

    🤖 Automation triggered: Enrich Github Issue

    I'm starting to work on this automatically.


    View live progress

    Powered by 1Code

  2. 21st-connector commented on Apr 3, 2026

    @21st-connector
    Contributor

    Issue Analysis

    Codebase References

    The issue correctly identifies the key files. Here are the exact locations:

    Scope gate (the placeholder to remove):

    • src/commands/config.ts:214-220 — preAction hook rejects non-global scope with "Error: Project-local config is not yet implemented". This is the explicit placeholder for this feature.

    Global config storage:

    • src/core/global-config.ts:35-56 — getGlobalConfigDir() resolves to $XDG_CONFIG_HOME/openspec/ or ~/.config/openspec/
    • src/core/global-config.ts:92-94 — getGlobalConfigPath() returns [configDir]/config.json
    • src/core/global-config.ts:101-139 — getGlobalConfig() loads and deep-merges with defaults
    • src/core/global-config.ts:145-155 — saveGlobalConfig() writes the full config

    Config schema & validation:

    • src/core/config-schema.ts:7-25 — Zod schema defining GlobalConfig shape (profile, delivery, workflows, featureFlags), uses .passthrough() for forward compatibility
    • src/core/config-schema.ts:44-68 — validateConfigKeyPath() restricts valid key paths to known top-level keys

    Update workflow resolution (reads global-only today):

    • src/core/update.ts:96-106 — Reads getGlobalConfig(), resolves profile/delivery/workflows, derives desiredWorkflows and delivery flags
    • src/core/update.ts:139-144 — getToolsNeedingProfileSync() compares desired state against installed artifacts

    Profile system:

    • src/core/profiles.ts:14 — CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive']
    • src/core/profiles.ts:19-31 — ALL_WORKFLOWS (11 total)
    • src/core/profiles.ts:42-50 — getProfileWorkflows() resolves workflows by profile type

    Profile drift detection:

    • src/core/profile-sync-drift.ts:14-26 — WORKFLOW_TO_SKILL_DIR mapping
    • src/core/profile-sync-drift.ts:166-176 — getToolsNeedingProfileSync()
    • src/core/profile-sync-drift.ts:218-240 — hasProjectConfigDrift() — checks all tools for drift

    Existing project config (for context/rules only, not profile):

    • src/core/project-config.ts:19-41 — ProjectConfig interface: schema, context, rules
    • src/core/project-config.ts:66-161 — readProjectConfig() reads openspec/config.yaml
    • Note: This file already reads project-level YAML config but only for schema/context/rules — not for profile/delivery/workflows.

    Config command subcommands affected:

    • config list — src/commands/config.ts:231-268
    • config get — src/commands/config.ts:271-288
    • config set — src/commands/config.ts:291-330
    • config unset — src/commands/config.ts:332-346
    • config profile — src/commands/config.ts:452-644 (interactive profile wizard)

    Related Issues & Prior Art

    Directly referenced:

    Related closed issues/PRs (precedence model):

    Foundational PRs:

    Other related open issues:

    Not a duplicate. No existing issue or PR addresses project-scoped profile/delivery/workflow config specifically.


    Existing Precedence Pattern

    The codebase already implements a multi-level precedence model for schemas (docs/opsx.md:110-114, docs/customization.md:83-90):

    1. CLI flag (--schema <name>)
    2. Change metadata (.openspec.yaml)
    3. Project config (openspec/config.yaml)
    4. Default (spec-driven)

    The proposed profile/workflow precedence model mirrors this existing pattern, which is a good sign for consistency.


    Implementation Approach

    Based on codebase analysis, here is a suggested approach:

    1. Extend openspec/config.yaml (project-config.ts)

    The project config file already exists and is read by readProjectConfig() at src/core/project-config.ts:66-161. The natural place to store project-scoped profile/delivery/workflows is to extend this file:

    # openspec/config.yaml
    schema: spec-driven
    context: |
      ...
    # New fields:
    profile: custom
    delivery: both
    workflows:
      - propose
      - explore
      - apply
      - archive
      - sync

    This requires:

    • Extending ProjectConfig interface (src/core/project-config.ts:19-41) with optional profile, delivery, workflows fields
    • Extending the Zod schema in readProjectConfig() to validate these new fields

    2. Create an effective config resolver

    A new function (e.g., resolveEffectiveConfig()) that merges configs with the proposed precedence:

    1. CLI flags (already handled per-command)
    2. Project config (openspec/config.yaml profile/delivery/workflows)
    3. Global config (~/.config/openspec/config.json)
    4. Built-in defaults (core, both)

    This could live in a new file like src/core/effective-config.ts or be added to src/core/global-config.ts.

    3. Update src/core/update.ts:96-106

    Replace the direct getGlobalConfig() call with the new effective config resolver. The update command already receives resolvedProjectPath (line 82), so it has access to the project root for reading project config.

    4. Update config command (src/commands/config.ts)

    • Remove the preAction scope gate at lines 214-220
    • For --scope project: read/write to openspec/config.yaml instead of ~/.config/openspec/config.json
    • config list should show effective (merged) config with source annotations (e.g., (project), (global), (default))
    • config profile --scope project should write to project config

    5. Update resolveCurrentProfileState() (src/commands/config.ts:99-108)

    This function currently takes only GlobalConfig. It would need to accept the effective merged config or take both global and project configs.

    Key consideration: The config set/unset commands use src/core/config-schema.ts:44-68 for key validation, which currently only allows featureFlags, profile, delivery, workflows. For project scope, the same keys should be valid, but they need to be written to the YAML project config instead of the JSON global config.


    Testing Considerations

    • Verify that openspec update picks up project config when present and falls back to global when absent
    • Verify that config list correctly shows source annotations
    • Verify that modifying project config does not affect global config
    • Verify precedence: project profile: custom + workflows: [sync] overrides global profile: core
    • Verify that projects without project-scoped config are completely unaffected (backwards compatibility)
    • Edge case: what happens when project config has profile: core but also has workflows: [sync, propose]? The getProfileWorkflows() function at src/core/profiles.ts:42-50 ignores custom workflows for the core profile — this interaction needs to be defined clearly.
  3. jamessawle commented on Apr 24, 2026

    @jamessawle

    We're hitting this exact problem. We have a shared repo where we want a specific subset of workflows (9 of the 11), but since config is global-only, each developer has to configure their machine individually. This makes onboarding friction and causes drift when someone has a different profile set globally.

    Would you be open to a community PR for this? Happy to contribute but wanted to check before investing the effort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    design-reviewNeeds product/design decision

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions