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
38 changes: 31 additions & 7 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,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 @@ -148,13 +153,20 @@ 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
```

---
Expand Down Expand Up @@ -749,7 +761,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 @@ -758,7 +770,10 @@ 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 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`.

```
openspec config <subcommand> [options]
Expand Down Expand Up @@ -793,10 +808,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 @@ -809,6 +824,15 @@ 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

# 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 @@ -818,9 +842,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
28 changes: 26 additions & 2 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The `openspec/config.yaml` file is the easiest way to customize OpenSpec for 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,8 @@ 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.

### How It Works

**Default schema:**
Expand Down Expand Up @@ -80,6 +93,17 @@ 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 `profile`, `delivery` can still come from global config.

### Schema Resolution Order

When OpenSpec needs a schema, it checks in this order:
Expand Down Expand Up @@ -259,7 +283,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 +293,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.

---

Expand Down
12 changes: 10 additions & 2 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,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
Loading