Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
69 changes: 69 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Docs site

# The documentation site (website/) mirrors docs/*.md via scripts/sync-docs.mjs,
# which runs as the first step of `npm run build`. This workflow rebuilds that
# mirror and deploys the static export to Cloudflare Pages:
# - on every push to main that touches docs/ or website/ (deploy immediately),
# - on a daily schedule (re-mirror the latest docs even if nothing pushed),
# - manually via the Actions tab,
# - and as a build-only check on pull requests.
#
# Deploys require two repository secrets: CLOUDFLARE_API_TOKEN and
# CLOUDFLARE_ACCOUNT_ID. Set the site's public URL via the DOCS_SITE_URL
# repository variable (used for OG/sitemap absolute URLs).

on:
push:
branches: [main]
paths:
- 'docs/**'
- 'website/**'
- '.github/workflows/deploy-docs.yml'
pull_request:
paths:
- 'docs/**'
- 'website/**'
- '.github/workflows/deploy-docs.yml'
schedule:
# Daily at 06:00 UTC — picks up any docs changes merged since the last run.
- cron: '0 6 * * *'

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.

[note] The daily cron redeploys byte-identical content.

The build is fully deterministic from repo state (every published page comes from docs/**, and every push to main touching docs/** or website/** already deploys immediately), so the cron only serves as self-healing. Downside: if the Cloudflare token is ever rotated or removed, this produces a failing run every day. Weekly would give the same self-heal with less noise — or drop it entirely. Fine to keep as-is if the liveness signal is wanted.

workflow_dispatch:

# Never run two deploys at once; let an in-flight deploy finish.
concurrency:
group: deploy-docs

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.

[nit] PR checks share this concurrency group with deploys — pending PR runs can get canceled.

GitHub keeps one running + at most one pending run per group, and cancel-in-progress: false only protects the running one. Scenario: a main deploy is running, PR A's build check queues, then PR B (or the cron) triggers → PR A's run is canceled and its check shows as canceled on the PR, which reads like a failure.

Consider scoping PR builds per-ref and only serializing real deploys, e.g.:

concurrency:
  group: deploy-docs-${{ github.event_name == 'pull_request' && github.ref || 'deploy' }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

cancel-in-progress: false

jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
cache-dependency-path: website/package-lock.json

- name: Install dependencies
working-directory: website
run: npm ci

- name: Build site (mirrors docs/*.md, then next build)
working-directory: website
env:
NEXT_PUBLIC_SITE_URL: ${{ vars.DOCS_SITE_URL }}
run: npm run build

- name: Deploy to Cloudflare Pages
# Skip on pull requests (build-only check) and on forks without secrets.
if: ${{ github.event_name != 'pull_request' && github.repository == 'Fission-AI/OpenSpec' }}

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.

[fix] A workflow_dispatch from any branch deploys that branch to production.

This condition only excludes pull_request, and the wrangler command below hardcodes --branch=main — which marks the deployment as production regardless of which git ref was actually built. So manually dispatching this workflow on a feature branch (e.g. “just to test the build”) silently overwrites the live docs site.

Suggested change
if: ${{ github.event_name != 'pull_request' && github.repository == 'Fission-AI/OpenSpec' }}
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && github.repository == 'Fission-AI/OpenSpec' }}

Push, schedule, and main-dispatch runs all still satisfy this; non-main dispatches become build-only.

uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: website
command: pages deploy out --project-name=openspec-docs --branch=main
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ AI: Archived to openspec/changes/archive/2025-01-23-add-dark-mode/

</details>

## Why teams adopt OpenSpec

Solo, OpenSpec keeps you and your AI honest on a single repo. On a team, the hard part moves: a feature spans the API server, the web app, and a shared library; requirements are owned by one team and consumed by others; planning starts before any code exists.

**[Stores](docs/stores-beta/user-guide.md)** are the answer — planning in a repo of its own. The same `openspec/` shape you already know (specs and changes), shared by `git push` like anything else. One source of truth your whole team and every coding agent can read, across every repo.

- **Cross-repo features** — one change, one plan, even when the code lands in three repos.
- **Shared requirements** — a platform team owns the specs; product teams reference them read-only, right where their coding agent can read them. No drifting wiki.
- **Plan before code** — capture the plan in the store now; the code repos catch up later.

> Stores are in **beta**. Start with the [Stores User Guide](docs/stores-beta/user-guide.md).

## Quick Start

**Requires Node.js 20.19.0 or higher.**
Expand Down
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ That second one matters more than it looks. OpenSpec has two halves: a command l

**I learn by example.** The [Examples & Recipes](examples.md) page walks through real changes start to finish: a small feature, a bug fix, a refactor, an exploration.

**The AI just drafted a plan — now what?** Read it. [Reviewing a Change](reviewing-changes.md) shows the two-minute pass that catches a wrong turn while it's still cheap, and [Writing Good Specs](writing-specs.md) covers what a plan worth approving is made of.

**I work on a team.** [OpenSpec on a Team](team-workflow.md) shows how a change maps onto a branch and a pull request, and how teammates review a plan before the code.

**I'm coming from the old workflow.** The [Migration Guide](migration-guide.md) explains what changed and why, and promises your existing work is safe.

**I want to bend it to my team's process.** [Customization](customization.md) covers project config, custom schemas, and shared context.
Expand All @@ -49,6 +53,9 @@ That second one matters more than it looks. OpenSpec has two halves: a command l
|-----|-------------------|
| [Workflows](workflows.md) | Common patterns and when to reach for each command |
| [Examples & Recipes](examples.md) | Full walkthroughs of real changes, copy-pasteable |
| [Writing Good Specs](writing-specs.md) | What a strong requirement and scenario look like, and how to right-size a change |
| [Reviewing a Change](reviewing-changes.md) | The two-minute pass on a drafted plan before any code is written |
| [OpenSpec on a Team](team-workflow.md) | How changes fit branches, pull requests, and review |
| [Using OpenSpec in an Existing Project](existing-projects.md) | Adopting OpenSpec on a large brownfield codebase |
| [Editing & Iterating on a Change](editing-changes.md) | Update artifacts, go back, reconcile manual edits |
| [Commands](commands.md) | Reference for every `/opsx:*` slash command |
Expand Down
1 change: 1 addition & 0 deletions docs/editing-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ There's a full flowchart and worked examples in [Workflows: When to Update vs St
## Where to go next

- [Workflows](workflows.md) - patterns, plus the update-vs-new decision guide
- [Reviewing a Change](reviewing-changes.md) - the two-minute pass on a plan before you build it
- [Explore First](explore.md) - the place to step back to when an idea needs rethinking
- [Commands](commands.md) - `/opsx:continue`, `/opsx:apply`, and `/opsx:verify` in detail
- [Concepts: Artifacts](concepts.md#artifacts) - what each artifact is for
2 changes: 2 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ openspec view
## Next Steps

- [Explore First](explore.md) - Use `/opsx:explore` to think through an idea before you commit
- [Reviewing a Change](reviewing-changes.md) - What to check in the plan the AI drafts, before any code
- [Writing Good Specs](writing-specs.md) - What a strong requirement and scenario look like
- [Using OpenSpec in an Existing Project](existing-projects.md) - Start on a large brownfield codebase
- [Editing & Iterating on a Change](editing-changes.md) - Update artifacts, go back, reconcile manual edits
- [Core Concepts at a Glance](overview.md) - The whole mental model on one page
Expand Down
143 changes: 143 additions & 0 deletions docs/reviewing-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Reviewing a Change

OpenSpec's whole promise is that you and your AI **agree on what to build before any code is written.** That agreement only means something if you actually read what the AI drafted. This page is about the two minutes where you do that — what to open, in what order, and what to look for.

The bet is simple: catching a wrong turn in a one-paragraph plan is nearly free. Catching the same wrong turn in 300 lines of code is not. Review is where you collect on that bet.

## The two moments you review

There are exactly two:

```
/opsx:propose ──► REVIEW THE PLAN ──► /opsx:apply ──► REVIEW THE CODE ──► /opsx:archive
(before any code) (/opsx:verify)
```

1. **After `/opsx:propose`** (or `/opsx:ff`), before `/opsx:apply` — read the plan while it's still just words.
2. **After building**, with `/opsx:verify` — check that the code actually did what the plan said.

The first review is the one that saves you the most, and the one people skip. This page spends most of its time there.

## Read it in this order

A change is a folder of plain Markdown in `openspec/changes/<name>/`. Read the files in the order that lets you quit earliest if something's wrong:

```
openspec/changes/add-dark-mode/
├── proposal.md 1. the intent and scope ← if this is wrong, stop here
├── specs/…/spec.md 2. the requirements ← the heart of the review
├── design.md (only for bigger changes) — the technical approach
└── tasks.md 3. the plan of work
```

You don't need to read every line. You need to answer three questions, one per file.

## The proposal: is this the right problem?

Open `proposal.md` first. It captures the "why" and "what" — the intent, the scope, the approach in a paragraph or two.

**What good looks like:** one clear intent, a scope you recognize, and a reason this is worth doing now.

**Red flags:**

- It solves a slightly *different* problem than the one you asked for.
- The scope has grown — you asked for a theme toggle and the proposal also touches auth "while we're in there."
- It's vague. "Improve the settings page" is not a scope; "add a dark-mode toggle that respects the OS preference" is.

**The question to answer:** *Does this match what I actually asked for, and is anything sneaking in?* If the answer is no, stop — don't read further, fix the proposal (see [Pushing back](#pushing-back-is-cheap)).

## The spec deltas: is "done" defined correctly?

This is the heart of the review. The delta specs under `specs/` say what will be *true* when the change ships — as requirements and the scenarios that prove them:

```markdown
## ADDED Requirements

### Requirement: Dark Mode Toggle
The system SHALL let a user switch between light and dark themes.

#### Scenario: Respects the OS preference on first load
- GIVEN a user who has never set a theme
- WHEN they open the app on a device set to dark mode
- THEN the app renders in dark mode
```

**What a good requirement looks like:** one clear `SHALL`/`MUST` statement you could hand to a tester, and at least one scenario whose GIVEN/WHEN/THEN actually exercises that statement.

**Red flags:**

- **A vague requirement.** "The system SHALL be fast" can't be built or tested. What's fast?
- **A requirement with no scenario**, or a scenario that doesn't test the requirement it sits under.
- **The most valuable catch of all: what's missing.** The AI faithfully writes down what you *said*. Your job is to notice what you *forgot* to say. If you cared most about the OS-preference case and no scenario mentions it, that's the review paying for itself.

Read the deltas asking *would I be happy if the system did exactly — and only — this?* Nothing here is about code yet, so it stays cheap to change.

## The tasks: is the plan of work sane?

Open `tasks.md` last. It's the implementation checklist the AI will work through.

**What good looks like:** ordered steps, each traceable to a requirement, nothing mysterious.

**Red flags:**

- A task with no matching requirement (where did that come from?).
- One giant "implement the feature" task that hides all the real decisions.
- A task that touches something outside the scope you just approved.

You're not estimating or micromanaging here — you're checking that the plan matches the requirements you already accepted.

## Pushing back is cheap

If any of the three questions came back wrong, say so. There are no phases and nothing is locked — you fix it and move on. Two ways, exactly as in [Editing a change](editing-changes.md):

- **Edit the file yourself.** It's plain Markdown; change the scope line, tighten a requirement, delete a task.
- **Tell the AI what's wrong** and let it revise: *"drop the auth changes — out of scope,"* *"add a scenario for when the user has already picked a theme,"* *"split task 3 into schema and UI."*

Then re-read the part you changed. Re-draft until it's a plan you'd sign your name to. That back-and-forth *is* the product working.

## After the code: verify

Once the work is built, `/opsx:verify` is your second review. It re-reads the artifacts and the code and reports mismatches across three dimensions:

| Dimension | What it checks |
|-----------|----------------|
| **Completeness** | Every task done, every requirement implemented, scenarios covered |
| **Correctness** | The implementation matches the spec's intent, edge cases handled |
| **Coherence** | Design decisions actually show up in the code |

```
You: /opsx:verify

AI: Verifying add-dark-mode...

COMPLETENESS
✓ All 8 tasks in tasks.md are checked
✓ All requirements in specs have corresponding code
⚠ Scenario "Respects the OS preference on first load" has no test coverage
```

It flags issues as CRITICAL, WARNING, or SUGGESTION, and it does **not** block archiving — it surfaces the gaps and leaves the call to you. This is the difference between "did the AI write code" and "did it build what we agreed."

`/opsx:verify` is in the expanded profile. If you don't have it, turn it on with `openspec config profile` (then `openspec update`), or just re-read the change and the diff yourself.

## Right-size the review

Not every change earns the full pass. A one-file typo fix deserves a twenty-second skim. A change that touches auth, payments, or data you can't recover deserves every question above. The point was never ceremony — it's spending your attention where a mistake would be expensive, and skimming where it wouldn't.

## The two-minute checklist

- [ ] The proposal's intent matches what I asked for.
- [ ] Nothing extra has crept into the scope.
- [ ] Every requirement is specific enough to test.
- [ ] Every requirement has a scenario that actually exercises it.
- [ ] The case I care about most is covered.
- [ ] Tasks map to requirements; nothing is mysterious or out of scope.
- [ ] I'd be comfortable if the AI built exactly this and nothing more.

If all seven pass, run `/opsx:apply` with confidence. If any fail, that's not a setback — it's the two minutes doing its job.

## Where to go next

- [Writing Good Specs](writing-specs.md) — the flip side: how to draft requirements and scenarios worth approving.
- [Editing & Iterating on a Change](editing-changes.md) — the mechanics of changing a plan after you've started.
- [Workflows](workflows.md) — where review fits in the larger loop.
74 changes: 74 additions & 0 deletions docs/team-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# OpenSpec on a Team

Everything in the other guides works the same whether you're solo or on a team of twenty. What changes on a team is the questions around the edges: where do the specs live, how do teammates review a plan, and how does any of this fit the pull-request flow we already have?

The short answer: a change is just files, and OpenSpec never touches git. So it fits your existing workflow instead of replacing it. This page spells out the conventions that work well.

## One rule: OpenSpec doesn't touch git

OpenSpec reads and writes plain Markdown under `openspec/`. It never commits, branches, pushes, or pulls — same as it never does for a [store](stores-beta/user-guide.md). That means:

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.

[fix] Slight overstatement: openspec store create does commit by default.

Store creation runs git init plus an initial commit (src/commands/store.ts --init-git, default on; src/core/store/git.ts describes “setup-time git init plus one initial commit” as the entire write surface). The stores guide is deliberately narrower (“never clones, syncs, or pushes anything on its own”) — it omits “commits” for exactly this reason.

Suggested change
OpenSpec reads and writes plain Markdown under `openspec/`. It never commits, branches, pushes, or pulls — same as it never does for a [store](stores-beta/user-guide.md). That means:
OpenSpec reads and writes plain Markdown under `openspec/`. It never commits, branches, pushes, or pulls in your project — and it never clones or syncs a [store](stores-beta/user-guide.md) on its own. That means:


- **You commit `openspec/` like any source.** Specs, active changes, and the archive are part of your project's history. (Yes, commit the whole folder — see the [FAQ](faq.md#should-i-commit-the-openspec-folder-to-git).)
- **A change is a folder you version like code.** `openspec/changes/add-dark-mode/` is just files on a branch.
- **Everything below is convention, not enforcement.** OpenSpec won't make you do it this way; it just fits cleanly.

## The everyday loop

The workflow that works well maps a change onto a branch and a pull request:

```
git switch -c add-dark-mode start a branch, as usual
/opsx:propose add-dark-mode draft the plan (proposal + specs + tasks)
REVIEW THE PLAN you read it before any code — see Reviewing a Change
/opsx:apply build it; artifacts + code change together
git commit && open a PR the PR contains the spec delta AND the code
teammate reviews, merges
/opsx:archive fold the delta into specs/, move the change to archive/
```

The plan and the code live side by side in the same branch, so your teammates review both together, and six months later the archived spec still explains why the code looks the way it does.

## Reviewing specs in a pull request

This is where a team feels the payoff. When a PR includes the change's delta spec, the reviewer gets something a raw diff never gives them: **a plain-language statement of what this change is supposed to do**, before they read a single line of code.

A good review order for the reviewer:

1. **Read `proposal.md`** — is this the right problem and scope?
2. **Read the delta under `specs/`** — is "done" defined correctly? (This is the [Reviewing a Change](reviewing-changes.md) two-minute pass, now happening in the PR.)
3. **Then read the code diff** — does it deliver exactly those requirements?

A reviewer who disagrees with the *approach* can say so against the proposal, cheaply, instead of relitigating it across 300 lines of code. Put the delta spec near the top of the PR description, or point reviewers at the change folder, so they start there.

## When to archive

Archiving folds a change's deltas into your main `openspec/specs/` and moves the change folder to `openspec/changes/archive/YYYY-MM-DD-<name>/`. Because `specs/` is the **shared source of truth**, the timing matters on a team. Two workable conventions:

- **Archive after the PR merges (recommended).** The branch carries the active change; once it's merged to your main branch, archive there (often a tiny follow-up commit or a scheduled cleanup). This keeps the shared `specs/` moving forward only with work that actually shipped.
- **Archive inside the PR.** Simpler for small teams: the same PR that adds the code also syncs and archives. The tradeoff is that your `specs/` diff and your code diff land together, which can make the PR noisier.

Pick one and be consistent. Either way, `/opsx:archive` checks that tasks are complete and offers to sync first, so nothing merges half-finished by accident.

## Two people, parallel changes

Because changes are separate folders, they don't collide:

- **Different changes, different people — no problem.** `add-dark-mode` and `rate-limit-login` are different folders on different branches; they never touch each other until they both archive.
- **One change, one owner.** Two people editing the same change folder conflict exactly like two people editing the same file. Keep a change to a single author, or split it into two changes (another reason to [right-size](writing-specs.md#right-size-the-change)).
- **The one place conflicts show up is `specs/`.** If two changes both modify the *same* requirement, archiving the second one will conflict in `openspec/specs/…/spec.md` — resolve it like any merge conflict, keeping the requirement that reflects reality. This is rare, and it's a feature: it's git telling you two changes disagreed about how the system should behave.

## When planning outgrows one repo

Everything above assumes the plan lives in the code repo's own `openspec/` folder, which is the right default. When your planning genuinely spans several repos or teams — one feature touching three services, or requirements one team owns and others consume — that's what the beta **stores** feature is for: planning gets its own repo that any code repo can point at. Start with the [Stores User Guide](stores-beta/user-guide.md).

## Where to go next

- [Reviewing a Change](reviewing-changes.md) — the review pass, now inside your PR.
- [Writing Good Specs](writing-specs.md) — including how to right-size a change so it fits one branch.
- [Stores User Guide](stores-beta/user-guide.md) — planning that spans repos and teams.
Loading
Loading