Skip to content
Closed
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-29
180 changes: 180 additions & 0 deletions openspec/changes/add-deterministic-sync-and-unarchive/design.md

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions openspec/changes/add-deterministic-sync-and-unarchive/proposal.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## ADDED Requirements

### Requirement: Applied-Delta Baseline Capture

When the archive operation rewrites main specs, it SHALL record a self-contained applied-delta baseline inside the change folder so that the operation can later be reversed deterministically by `openspec unarchive`. The baseline SHALL be forward-only and SHALL NOT alter how archive merges, moves, validates, or what it outputs.

#### Scenario: Baseline captured when specs are updated

- **WHEN** archiving a change applies delta specs to `openspec/specs/`
- **THEN** the command records, for each affected spec, its pre-merge content and its applied-result digest in an applied-delta baseline stored inside the change folder
- **AND** the baseline moves into `openspec/changes/archive/<prefix>-<name>/` together with the rest of the change

#### Scenario: Created specs marked absent

- **WHEN** archiving creates a new spec that did not previously exist
- **THEN** the baseline records the pre-merge state of that spec as absent
- **AND** unarchive can delete it to restore the pre-archive state

#### Scenario: No baseline when specs are not updated

- **WHEN** archiving runs with `--skip-specs`, or the change has no delta specs to apply
- **THEN** no spec content is changed
- **AND** no applied-delta baseline is written

#### Scenario: Archive behavior and output unchanged

- **WHEN** capturing the applied-delta baseline
- **THEN** the merge, move, validation, and confirmation behavior of archive are unchanged
- **AND** the command's human-readable and `--json` output are unchanged

### Requirement: Shared Deterministic Merge Engine

The archive operation SHALL apply delta specs using the same deterministic merge engine as `openspec sync`, so that archiving and syncing produce identical spec output for the same change.

#### Scenario: Archive merge matches sync

- **WHEN** archiving applies a change's deltas to `openspec/specs/`
- **THEN** the resulting spec content is identical to what `openspec sync` produces for the same change
- **AND** the merge is performed in code without AI inference
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
## ADDED Requirements

### Requirement: Spec Diff Command

The system SHALL provide an `openspec diff [target] [--base <ref>]` command that renders a deterministic, spec-aware diff of spec and delta files, splicing each changed requirement's provenance and rationale inline so a reviewer sees what changed and why together. It SHALL compute the rendering in pure code, without AI inference.

#### Scenario: Diff a change's deltas

- **WHEN** the user runs `openspec diff <change>`
- **THEN** the command shows the change's delta operations grouped by capability and requirement
- **AND** annotates each with the rationale drawn from the change's `proposal.md`

#### Scenario: Diff main specs against a base revision

- **WHEN** the user runs `openspec diff --base <ref>` over `openspec/specs/`
- **THEN** the command shows the requirement-level differences since `<ref>`
- **AND** annotates each changed requirement with the change and delta operation that produced it, drawn from the recorded applied-delta provenance

#### Scenario: No inference

- **WHEN** the command renders a diff
- **THEN** it composes the result from the git diff and the recorded provenance/rationale in code
- **AND** it does not call a language model

### Requirement: Inline Reasoning Annotation

The diff SHALL annotate each changed requirement with the originating change and its rationale, sourced from existing OpenSpec artifacts, and SHALL NOT invent rationale that is not recorded.

#### Scenario: Annotated with originating change and rationale

- **WHEN** a changed requirement can be attributed to a change via provenance
- **THEN** the diff shows the originating change and a reference to or excerpt of its recorded rationale

#### Scenario: Unattributable change shown honestly

- **WHEN** a changed requirement cannot be attributed (no provenance recorded, e.g. a pre-baseline edit)
- **THEN** the diff shows the change without inventing a rationale
- **AND** it indicates that provenance is unavailable

### Requirement: Reuses Existing Artifacts, No New Sidecar Store

The rationale and provenance the diff splices SHALL come from artifacts OpenSpec already maintains — the change's `proposal.md` (the why) and the recorded applied-delta provenance (the what/where) — rather than a separate reasoning database.

#### Scenario: Reasoning resolved from existing artifacts

- **WHEN** the diff needs the reasoning for a changed requirement
- **THEN** it resolves the rationale from the originating change's `proposal.md` and the recorded provenance
- **AND** it requires no separate reasoning-log store

### Requirement: Deterministic Rendering

The diff rendering SHALL be a pure function of its inputs, producing byte-identical output for the same inputs on every platform.

#### Scenario: Repeated runs are identical

- **WHEN** `openspec diff` runs more than once on the same inputs
- **THEN** the output bytes are identical every time

#### Scenario: Platform independence

- **WHEN** the diff runs on different operating systems with the same inputs
- **THEN** the output is identical regardless of line-ending or path-separator differences

### Requirement: Git Diff Driver Integration

The command SHALL be usable as a git diff driver for spec files, documented as an opt-in `.gitattributes` registration, so that `git diff` over spec and delta files renders the spec-aware view. OpenSpec SHALL NOT modify the user's git configuration without explicit consent.

#### Scenario: Registered as a diff driver

- **WHEN** the user opts in by registering the driver for spec paths in `.gitattributes`
- **THEN** `git diff` over those paths renders the spec-aware, annotated diff

#### Scenario: Opt-in only

- **WHEN** the user has not registered the driver
- **THEN** OpenSpec does not alter git behavior
- **AND** `openspec diff` remains available as a standalone command

### Requirement: JSON Output

The diff command SHALL support `--json`, emitting a machine-readable, per-requirement structure (change operation, provenance, rationale reference) for review tooling.

#### Scenario: JSON annotated diff

- **WHEN** the user runs `openspec diff --json`
- **THEN** it emits, per changed requirement, the operation, the originating change, and a reference to the rationale
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
## ADDED Requirements

### Requirement: Spec Format Command

The system SHALL provide an `openspec format [target]` command that rewrites OpenSpec spec files and delta spec files to a single deterministic canonical form, in pure code without AI inference. By default it writes; `--check` runs read-only.

#### Scenario: Format main and delta specs

- **WHEN** the user runs `openspec format` with no target
- **THEN** the command formats the main specs under `openspec/specs/` and the delta specs under active changes' `specs/` directories
- **AND** it reports which files were reformatted

#### Scenario: Format a specific target

- **WHEN** the user runs `openspec format <path>` for a spec or delta file or directory
- **THEN** the command formats only that target

#### Scenario: No inference

- **WHEN** the command formats a file
- **THEN** it computes the canonical form in code
- **AND** it does not call a language model or otherwise depend on non-deterministic input

### Requirement: Deterministic Canonical Form

The formatter SHALL produce byte-identical output for the same input on every platform, and SHALL be idempotent: formatting already-canonical content changes nothing.

#### Scenario: Same input yields identical output

- **WHEN** the formatter runs more than once on the same input
- **THEN** the output bytes are identical every time

#### Scenario: Idempotent

- **WHEN** the formatter is applied to content it has already formatted
- **THEN** the content is unchanged

#### Scenario: Line endings normalized

- **WHEN** the input contains CRLF or mixed line endings
- **THEN** the canonical output uses normalized line endings regardless of the platform

### Requirement: Behavior-Preserving Normalization

The formatter SHALL change only presentation — whitespace, blank-line policy, list markers and indentation, and heading spacing — and SHALL NOT change the meaning of a spec. It SHALL NOT reorder requirements or scenarios, rewrite prose, or add, remove, merge, or split requirements or scenarios.

#### Scenario: Requirement and scenario order preserved

- **WHEN** the formatter runs on a spec
- **THEN** the order of requirements and of scenarios within each requirement is unchanged

#### Scenario: Prose is not rewritten

- **WHEN** the formatter normalizes a requirement
- **THEN** the requirement's wording and scenario text are byte-for-byte unchanged except for surrounding whitespace normalization

#### Scenario: Parsed content is identical before and after

- **WHEN** a spec is parsed before formatting and after formatting
- **THEN** the parsed requirements, scenarios, and delta operations are identical

### Requirement: Canonical Section Organization

The formatter SHALL normalize the structural presentation of a spec deterministically — heading levels and nesting, the spacing between sections, and the canonical headers for delta sections — without changing which sections are present or their order.

#### Scenario: Canonical headings and spacing

- **WHEN** a spec uses inconsistent heading spacing or blank-line separation between requirements and scenarios
- **THEN** the formatter rewrites them to the canonical spacing defined by the conventions

#### Scenario: Canonical delta section headers

- **WHEN** a delta file contains `## ADDED/MODIFIED/REMOVED/RENAMED Requirements` sections
- **THEN** the formatter normalizes those headers to their canonical form
- **AND** it does not move requirements between sections

### Requirement: Shared Canonicalization With The Merge Engine

The canonical form produced by `openspec format` SHALL be the same canonical form emitted by the deterministic merge engine used by `openspec sync` and `openspec archive`, so that synced or archived specs are already canonical.

#### Scenario: Merge output is already formatted

- **WHEN** `openspec sync` or `openspec archive` writes a spec
- **THEN** running `openspec format --check` on that spec passes without changes

#### Scenario: One canonicalizer

- **WHEN** the same spec content is produced by the formatter and by the merge engine
- **THEN** the two results are byte-identical

### Requirement: Check Mode

The format command SHALL support a read-only `--check` mode that exits non-zero when any target is not in canonical form, naming the offending files and modifying nothing, so it can gate commits and CI as a plain binary.

#### Scenario: Unformatted file detected

- **WHEN** `openspec format --check` finds a file that is not in canonical form
- **THEN** the command reports the file
- **AND** it exits with a non-zero status code and modifies no files

#### Scenario: All formatted

- **WHEN** every target is already in canonical form
- **THEN** the command exits zero and modifies no files

### Requirement: Fix Mode

The format command SHALL, by default (or with `--fix`), rewrite targets to canonical form, suitable for use as an auto-fixer in a pre-commit hook.

#### Scenario: Fix writes canonical form

- **WHEN** the user runs `openspec format` (or `openspec format --fix`)
- **THEN** the command writes each target's canonical form
- **AND** a subsequent `openspec format --check` passes

### Requirement: Incremental Checking

The format check MAY use recorded content digests to skip files whose content is unchanged since they were last checked, re-checking only what changed. A skip SHALL be permitted only when it cannot change the result versus a full check.

#### Scenario: Unchanged file skipped

- **WHEN** `--check` runs and a file's current content digest matches the recorded digest
- **THEN** the command may skip re-checking that file
- **AND** the overall result is identical to checking it fully

#### Scenario: Changed or unknown file fully checked

- **WHEN** a file's digest does not match, no digest is recorded, or the recorded digest uses an unrecognized scheme
- **THEN** the command performs the full check for that file

### Requirement: JSON Output

The format command SHALL support `--json` for non-interactive use, emitting machine-readable results and diagnostics.

#### Scenario: JSON reports unformatted files

- **WHEN** `openspec format --check --json` finds files not in canonical form
- **THEN** it emits the list of offending files as JSON
- **AND** exits with a non-zero status code

#### Scenario: JSON reports written files

- **WHEN** `openspec format --json` rewrites files
- **THEN** it emits the list of changed files as JSON
Loading
Loading