Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @fission-ai/openspec

## Unreleased

### Patch Changes

- Project-scoped `profile: custom` now requires project-scoped `workflows` for profile-driven commands. OpenSpec will fail with an actionable error instead of silently inheriting a developer's global workflows, while raw `openspec config --scope project set workflows ...` remains available to repair the project config.

- Project config filename resolution is now consistent across runtime reads and `openspec config --scope project` writes. OpenSpec preserves an existing `openspec/config.yml`, defaults new writes to `openspec/config.yaml`, and fails explicitly when both files exist.

## 1.3.1

### Patch Changes
Expand Down
49 changes: 42 additions & 7 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,12 @@ openspec/

### `openspec update`

Update OpenSpec instruction files after upgrading the CLI. Re-generates AI tool configuration files using your current global profile, selected workflows, and delivery mode.
Update OpenSpec instruction files after upgrading the CLI. Re-generates AI tool configuration files using effective profile settings resolved by precedence:

- CLI scope override (`--scope`) when provided
- Project config (`openspec/config.yaml` or `openspec/config.yml`)
- Global config
- Built-in defaults (`profile: core`, `delivery: both`)

```
openspec update [path] [options]
Expand All @@ -154,15 +159,26 @@ openspec update [path] [options]
| Option | Description |
|--------|-------------|
| `--force` | Force update even when files are up to date |
| `--scope <scope>` | Resolution scope override: `global` or `project` |

**Example:**

```bash
# Update instruction files after npm upgrade
npm update @fission-ai/openspec
openspec update

# Force global-only profile resolution for this run
openspec update --scope global

# Force project-prioritized resolution for this run
openspec update --scope project
```

If project config sets `profile: custom`, it must also define project `workflows`. Plain `openspec update` and `openspec update --scope project` will fail rather than inherit a developer's global workflows. Use `openspec update --scope global` to ignore project overrides for a run.

If both `openspec/config.yaml` and `openspec/config.yml` exist, `openspec update` fails with an explicit error instead of picking one implicitly. Remove one file to continue.

---

## Workspace Commands
Expand Down Expand Up @@ -889,7 +905,7 @@ spec-driven resolves from: package
**Schema precedence:**

1. Project: `openspec/schemas/<name>/`
2. User: `~/.local/share/openspec/schemas/<name>/`
2. Global: `~/.local/share/openspec/schemas/<name>/`
3. Package: Built-in schemas

---
Expand All @@ -898,7 +914,14 @@ spec-driven resolves from: package

### `openspec config`

View and modify global OpenSpec configuration.
View and modify OpenSpec configuration.

Default scope is `global`. Use `--scope project` to read/write project-scoped profile settings (`profile`, `delivery`, `workflows`) in `openspec/config.yaml` (or an existing `config.yml`).
If you already use global-only config, no migration is required: existing commands keep global behavior unless you explicitly opt into `--scope project`.

Project-scoped writes preserve the existing filename when the repo already uses `openspec/config.yml`, and default to creating `openspec/config.yaml` when neither file exists. If both files exist, project-scoped config commands fail until one file is removed.

If project config sets `profile: custom`, it must also define project `workflows`. Project-scoped commands that show effective settings, such as `list` and the interactive `profile` wizard, will fail until the workflows are added. Raw write commands such as `openspec config --scope project set workflows ...` still work so you can repair the config.

```
openspec config <subcommand> [options]
Expand Down Expand Up @@ -933,10 +956,10 @@ openspec config get telemetry.enabled
openspec config set telemetry.enabled false

# Set a string value explicitly
openspec config set user.name "My Name" --string
openspec config set custom.name "My Name" --string

# Remove a custom setting
openspec config unset user.name
openspec config unset custom.name

# Reset all configuration
openspec config reset --all --yes
Expand All @@ -949,6 +972,18 @@ openspec config profile

# Fast preset: switch workflows to core (keeps delivery mode)
openspec config profile core

# Set project-scoped profile override
openspec config --scope project set profile custom

# Repair a project-scoped custom profile by setting workflows explicitly
openspec config --scope project set workflows '["explore", "verify"]'

# Read project-scoped profile key
openspec config --scope project get profile

# Run profile wizard against project scope
openspec config --scope project profile
```

`openspec config profile` starts with a current-state summary, then lets you choose:
Expand All @@ -958,9 +993,9 @@ openspec config profile core
- Keep current settings (exit)

If you keep current settings, no changes are written and no update prompt is shown.
If there are no config changes but the current project files are out of sync with your global profile/delivery, OpenSpec will show a warning and suggest running `openspec update`.
If there are no config changes but the current project files are out of sync with your active scope settings, OpenSpec will show a warning and suggest running `openspec update`.
Pressing `Ctrl+C` also cancels the flow cleanly (no stack trace) and exits with code `130`.
In the workflow checklist, `[x]` means the workflow is selected in global config. To apply those selections to project files, run `openspec update` (or choose `Apply changes to this project now?` when prompted inside a project).
In the workflow checklist, `[x]` means the workflow is selected in the effective scope state for this command. To apply those selections to project files, run `openspec update` (or choose `Apply changes to this project now?` when prompted inside a project).

**Interactive examples:**

Expand Down
40 changes: 36 additions & 4 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ OpenSpec provides three levels of customization:

## Project Configuration

The `openspec/config.yaml` file is the easiest way to customize OpenSpec for your team. It lets you:
The `openspec/config.yaml` file, or an existing `openspec/config.yml`, is the easiest way to customize OpenSpec for your team. It lets you:

- **Set a default schema** - Skip `--schema` on every command
- **Inject project context** - AI sees your tech stack, conventions, etc.
- **Add per-artifact rules** - Custom rules for specific artifacts
- **Override profile settings per project** - Set `profile`, `delivery`, and `workflows` locally

### Quick Setup

Expand All @@ -30,6 +31,16 @@ This walks you through creating a config interactively. Or create one manually:
# openspec/config.yaml
schema: spec-driven

# Optional: project-scoped profile settings
profile: custom
delivery: both
workflows:
- propose
- explore
- apply
- verify
- archive

context: |
Tech stack: TypeScript, React, Node.js, PostgreSQL
API style: RESTful, documented in docs/api.md
Expand All @@ -45,6 +56,12 @@ rules:
- Reference existing patterns before inventing new ones
```

`workflows` is applied only when `profile: custom`. If `profile: core`, OpenSpec always uses the core workflow set.

If project config sets `profile: custom`, it must also set project `workflows`. OpenSpec will not silently inherit workflows from a developer's global config for that case, because that would make shared repo behavior non-deterministic.

OpenSpec preserves `openspec/config.yml` when that is the file already present in a repo, and defaults new writes to `openspec/config.yaml` when neither filename exists. If both files exist at once, project-scoped config commands and `openspec update` fail until you remove one.

### How It Works

**Default schema:**
Expand Down Expand Up @@ -80,13 +97,28 @@ Tech stack: TypeScript, React, Node.js, PostgreSQL
- **Context** appears in ALL artifacts
- **Rules** ONLY appear for the matching artifact

### Profile Resolution Order

For profile-driven behavior (for example `openspec update`), OpenSpec resolves settings in this order:

1. CLI scope override (if `--scope` is provided)
2. Project config (`openspec/config.yaml` or existing `openspec/config.yml`)
3. Global config (`openspec config ...`)
4. Defaults (`profile: core`, `delivery: both`, profile-derived workflows)

This is key-by-key fallback, so partial project settings are valid. Example: if project config only sets `delivery`, the active profile can still come from global config.

One exception is `profile: custom` in project config: project `workflows` must also be present. If they are missing, profile-driven commands such as `openspec update`, `openspec config --scope project list`, and `openspec config --scope project profile` fail with an actionable error instead of inheriting global workflows.

If you need to ignore project overrides for a run, use `openspec update --scope global`. If you need to repair the project config, write the missing workflows with `openspec config --scope project set workflows '["explore"]'` (replace the array with your project's workflow list).

### Schema Resolution Order

When OpenSpec needs a schema, it checks in this order:

1. CLI flag: `--schema <name>`
2. Change metadata (`.openspec.yaml` in the change folder)
3. Project config (`openspec/config.yaml`)
3. Project config (`openspec/config.yaml` or existing `openspec/config.yml`)
4. Default (`spec-driven`)

---
Expand Down Expand Up @@ -259,7 +291,7 @@ openspec schema which my-workflow
openspec schema which --all
```

Output shows whether it's from your project, user directory, or the package:
Output shows whether it's from your project, global directory, or the package:

```text
Schema: my-workflow
Expand All @@ -269,7 +301,7 @@ Path: /path/to/project/openspec/schemas/my-workflow

---

> **Note:** OpenSpec also supports user-level schemas at `~/.local/share/openspec/schemas/` for sharing across projects, but project-level schemas in `openspec/schemas/` are recommended since they're version-controlled with your code.
> **Note:** OpenSpec also supports global schemas at `~/.local/share/openspec/schemas/` for sharing across projects, but project-level schemas in `openspec/schemas/` are recommended since they're version-controlled with your code.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a platform-neutral global schema location in docs.

Line 304 hardcodes a Linux-specific path (~/.local/share/openspec/schemas/). In cross-platform docs, this can send users to the wrong location on macOS/Windows. Prefer the neutral <global-data-dir>/schemas/ wording (or list OS-specific examples).

✏️ Suggested doc tweak
-> **Note:** OpenSpec also supports global schemas at `~/.local/share/openspec/schemas/` for sharing across projects, but project-level schemas in `openspec/schemas/` are recommended since they're version-controlled with your code.
+> **Note:** OpenSpec also supports global schemas at `<global-data-dir>/schemas/` for sharing across projects, but project-level schemas in `openspec/schemas/` are recommended since they're version-controlled with your code.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> **Note:** OpenSpec also supports global schemas at `~/.local/share/openspec/schemas/` for sharing across projects, but project-level schemas in `openspec/schemas/` are recommended since they're version-controlled with your code.
> **Note:** OpenSpec also supports global schemas at `<global-data-dir>/schemas/` for sharing across projects, but project-level schemas in `openspec/schemas/` are recommended since they're version-controlled with your code.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/customization.md` at line 304, Replace the Linux-specific path in the
sentence that mentions global schemas (the line containing
"~/.local/share/openspec/schemas/") with a platform-neutral placeholder such as
"<global-data-dir>/schemas/" or provide OS-specific examples (e.g., Linux:
"~/.local/share/openspec/schemas/", macOS: "~/Library/Application
Support/openspec/schemas/", Windows: "%APPDATA%\\openspec\\schemas\\") so the
docs are cross-platform; update the surrounding sentence to recommend
project-level schemas while referencing the neutral placeholder or the listed OS
examples.


---

Expand Down
16 changes: 12 additions & 4 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,18 @@ program
.command('update [path]')
.description('Update OpenSpec instruction files')
.option('--force', 'Force update even when tools are up to date')
.action(async (targetPath = '.', options?: { force?: boolean }) => {
.option('--scope <scope>', 'Profile resolution scope override (global or project)')
.action(async (targetPath = '.', options?: { force?: boolean; scope?: string }) => {
try {
if (options?.scope && options.scope !== 'global' && options.scope !== 'project') {
throw new Error(`Invalid scope "${options.scope}". Use "global" or "project".`);
}

const resolvedPath = path.resolve(targetPath);
const updateCommand = new UpdateCommand({ force: options?.force });
const updateCommand = new UpdateCommand({
force: options?.force,
scope: options?.scope as 'global' | 'project' | undefined,
});
await updateCommand.execute(resolvedPath);
} catch (error) {
console.log(); // Empty line for spacing
Expand Down Expand Up @@ -426,7 +434,7 @@ program
.command('status')
.description('Display artifact completion status for a change')
.option('--change <id>', 'Change name to show status for')
.option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
.option('--schema <name>', 'Schema override (auto-detected from project config)')
.option('--json', 'Output as JSON')
.action(async (options: StatusOptions) => {
try {
Expand All @@ -443,7 +451,7 @@ program
.command('instructions [artifact]')
.description('Output enriched instructions for creating an artifact or applying tasks')
.option('--change <id>', 'Change name')
.option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
.option('--schema <name>', 'Schema override (auto-detected from project config)')
.option('--json', 'Output as JSON')
.action(async (artifactId: string | undefined, options: InstructionsOptions) => {
try {
Expand Down
Loading