Skip to content
Open
5 changes: 5 additions & 0 deletions .changeset/create-main-spec-for-new-capability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fission-ai/openspec": patch
---

Agent-driven archive and sync workflows now create a missing main spec from `ADDED` requirements instead of treating it as already synced. They stop rather than inventing `MODIFIED` or `RENAMED` requirements or writing an empty spec for a `REMOVED`-only delta. Fixes #1222 and #1264.
2 changes: 1 addition & 1 deletion docs-lab/reference/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ OpenSpec reuses words that mean something else in git, CI, and agent tooling. Ea
| **Fast-forward** | Create a change proposal with every planning artifact in one pass, ready to implement. Skill: `openspec-ff-change`. Not a git fast-forward. | [Skills](skills.md) |
| **Legacy workflow** | The pre-OPSX `/openspec:*` commands. | [Migration](../help/legacy/migration.md) |
| **Loop** | The cycle a change proposal moves through: explore, propose, review, apply, archive. | [Quickstart](../start/quickstart.md) |
| **Main specs** | The `openspec/specs/` tree: the current, agreed behavior of your system. Archiving merges deltas into it. | [Concepts](../guides/concepts.md) |
| **Main specs** | The `openspec/specs/` tree: the current behavior of your system. Sync or archive creates a new capability's spec from its `ADDED` requirements. | [Concepts](../guides/concepts.md) |
| **OpenSpec root** | The `openspec/` tree a command resolves to and operates on: your repo's, or a store's. | [Stores](configuration/stores.md) |
| **OPSX** | The current OpenSpec workflow system, and the command prefix it installs (`/opsx:`). | [Architecture](architecture/index.md) |
| **Profile** | Which workflows init installs: `core` or `custom`. | [Profiles](../customize/profiles.md) |
Expand Down
27 changes: 27 additions & 0 deletions openspec/specs/opsx-archive-skill/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,33 @@ The skill SHALL prompt to sync delta specs before archiving if specs exist.
- **AND** stop without archiving if the sync fails or any capability does not verify
- **AND** archive only after verification passes, or when the user explicitly chose to archive without syncing or to archive already-synced specs

#### Scenario: Applicable ADDED delta whose main spec does not exist yet

- **WHEN** agent compares a delta spec against its main spec at `openspec/specs/<capability-path>/spec.md`
- **AND** that main spec does not exist yet
- **AND** the delta has `## ADDED Requirements`
- **AND** the delta has no `## MODIFIED Requirements` or `## RENAMED Requirements`
- **THEN** count that capability as needing sync rather than as already synced
- **AND** name it in the summary as a main spec the sync will create
- **AND** never treat the missing main spec as nothing to apply
- **AND** if the delta also has `## REMOVED Requirements`, warn that they will be ignored because there is no main spec to remove them from
- **AND** create the main spec from only the delta's `## ADDED Requirements`

#### Scenario: Unsupported delta operation whose main spec does not exist yet

- **WHEN** a delta targets a capability whose main spec does not exist yet
- **AND** the delta has `## MODIFIED Requirements` or `## RENAMED Requirements`
- **THEN** report that only ADDED requirements can create a new main spec
- **AND** stop instead of prompting to sync

#### Scenario: Nothing to put in a missing main spec

- **WHEN** a delta targets a capability whose main spec does not exist yet
- **AND** the delta has no `## ADDED Requirements`
- **THEN** report that no sync is possible
- **AND** if the delta has only `## REMOVED Requirements`, warn that there is no main spec to remove them from and leave the main-spec tree unchanged
- **AND** stop instead of prompting to sync, since the verification pass would re-read the same missing spec

Comment thread
coderabbitai[bot] marked this conversation as resolved.
#### Scenario: No delta specs

- **WHEN** agent checks for delta specs
Expand Down
14 changes: 14 additions & 0 deletions openspec/specs/specs-sync-skill/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,26 @@
- **AND** copy the delta's `## Purpose` body into it when the delta has one, matching what `openspec archive` does
- **AND** write a brief TBD placeholder Purpose only when the delta has none

#### Scenario: MODIFIED or RENAMED against a capability with no main spec
- **WHEN** delta contains `## MODIFIED Requirements` or `## RENAMED Requirements`
- **AND** the capability has no main spec yet
- **THEN** stop the sync for that capability and report that only ADDED requirements are allowed for a new spec, matching what `openspec archive` does
- **AND** never invent the missing requirement
- **AND** skip any `## REMOVED Requirements` with a warning, since there is nothing to remove

#### Scenario: Nothing to put in a new spec
- **WHEN** a delta targets a capability with no main spec
- **AND** the delta has no `## ADDED Requirements` to seed it with
- **THEN** create no main spec and leave the specs directory untouched
- **AND** report it, matching `openspec archive`, which aborts the same case with `Spec must have at least one requirement`
- **AND** never write an empty `## Requirements` section

#### Scenario: Merged main spec keeps canonical structure
- **WHEN** the agent writes a main spec during sync
- **THEN** every requirement lives under a single `## Requirements` section
- **AND** the main spec contains no delta operation headers (`## ADDED/MODIFIED/REMOVED/RENAMED Requirements`)

### Requirement: Skill Output

Check warning on line 97 in openspec/specs/specs-sync-skill/spec.md

View check run for this annotation

OpenSpec Cloud / OpenSpec Cloud PR Scan

Update requirement: Skill Output (wording)

**Expected** — `openspec/specs/specs-sync-skill/spec.md:97` > The skill SHALL provide clear feedback on what was applied. **Observed** — `src/core/archive.ts:1901` > : 'Specs already in sync; no files changed.' **First observed in retained OpenSpec Cloud history** — [`da4d4c8`](https://github.com/Fission-AI/OpenSpec/commit/da4d4c837c757fb18691b1f31481a53f5b86f3a9) in [PR #1701](https://github.com/Fission-AI/OpenSpec/pull/1701). **Next → update the requirement** to match the code.
The skill SHALL provide clear feedback on what was applied.

#### Scenario: Show applied changes
Expand Down
4 changes: 4 additions & 0 deletions skills/openspec-archive-change/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ Archive a completed change in the experimental workflow.

**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `<planningHome.root>/openspec/specs/<capability-path>/spec.md` (use the store-aware `planningHome.root` from step 2, not a hardcoded repo path)
- A main spec that does not exist yet is **not** "already synced". For a new capability, the main spec is an *output* of the sync, not an input:
- If the delta has MODIFIED or RENAMED requirements, report that only ADDED requirements can create a new main spec and stop instead of prompting to sync. Never invent a requirement that has no current version.
- If the delta has no ADDED requirements, report that no sync is possible and stop instead of prompting to sync. For a REMOVED-only delta, warn that there is no main spec to remove from and leave the main-spec tree unchanged. `openspec archive` refuses the same case with `Spec must have at least one requirement`.
- Otherwise, count the capability as needing sync and name it in the summary (`<capability-path>: new main spec will be created`). If the delta also has REMOVED requirements, warn that they will be ignored because there is no main spec to remove from. The sync creates the main spec from only the delta's ADDED requirements, exactly as `openspec archive` does.
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting

Expand Down
12 changes: 12 additions & 0 deletions skills/openspec-sync-specs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ This is an **agent-driven** operation - you will read delta specs and directly e

b. **Read the main spec** at `<planningHome.root>/openspec/specs/<capability-path>/spec.md` (may not exist yet)

**If it does not exist yet** (a new capability), match what `openspec archive` does:
only ADDED requirements may be applied - step d creates the spec from them.
MODIFIED and RENAMED have no requirement to act on, so stop the sync for that
capability and report that its main spec does not exist and only ADDED is allowed
for a new spec; never invent the missing requirement. REMOVED has nothing to
remove - skip it and warn.

c. **Apply changes intelligently**:

**ADDED Requirements:**
Expand Down Expand Up @@ -142,6 +149,11 @@ This is an **agent-driven** operation - you will read delta specs and directly e
(this is what `openspec archive` does; it warns and moves on)

d. **Create new main spec** if capability doesn't exist yet:
- Only when the delta has ADDED requirements to put in it. With nothing to add -
a REMOVED-only delta against a capability that has no main spec - create nothing,
leave the specs directory untouched, and report it; `openspec archive` aborts the
same case with `Spec must have at least one requirement`. Never write an empty
`## Requirements` section.
- Create `<planningHome.root>/openspec/specs/<capability-path>/spec.md`
- Add Purpose section: copy the delta's `## Purpose` body verbatim when it has one
(this is what `openspec archive` does); only write a brief TBD placeholder when it does not
Expand Down
8 changes: 8 additions & 0 deletions src/core/templates/workflows/archive-change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ ${STORE_SELECTION_GUIDANCE}

**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\` (use the store-aware \`planningHome.root\` from step 2, not a hardcoded repo path)
- A main spec that does not exist yet is **not** "already synced". For a new capability, the main spec is an *output* of the sync, not an input:
- If the delta has MODIFIED or RENAMED requirements, report that only ADDED requirements can create a new main spec and stop instead of prompting to sync. Never invent a requirement that has no current version.
- If the delta has no ADDED requirements, report that no sync is possible and stop instead of prompting to sync. For a REMOVED-only delta, warn that there is no main spec to remove from and leave the main-spec tree unchanged. \`openspec archive\` refuses the same case with \`Spec must have at least one requirement\`.
- Otherwise, count the capability as needing sync and name it in the summary (\`<capability-path>: new main spec will be created\`). If the delta also has REMOVED requirements, warn that they will be ignored because there is no main spec to remove from. The sync creates the main spec from only the delta's ADDED requirements, exactly as \`openspec archive\` does.
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting

Expand Down Expand Up @@ -278,6 +282,10 @@ ${STORE_SELECTION_GUIDANCE}

**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\` (use the store-aware \`planningHome.root\` from step 2, not a hardcoded repo path)
- A main spec that does not exist yet is **not** "already synced". For a new capability, the main spec is an *output* of the sync, not an input:
- If the delta has MODIFIED or RENAMED requirements, report that only ADDED requirements can create a new main spec and stop instead of prompting to sync. Never invent a requirement that has no current version.
- If the delta has no ADDED requirements, report that no sync is possible and stop instead of prompting to sync. For a REMOVED-only delta, warn that there is no main spec to remove from and leave the main-spec tree unchanged. \`openspec archive\` refuses the same case with \`Spec must have at least one requirement\`.
- Otherwise, count the capability as needing sync and name it in the summary (\`<capability-path>: new main spec will be created\`). If the delta also has REMOVED requirements, warn that they will be ignored because there is no main spec to remove from. The sync creates the main spec from only the delta's ADDED requirements, exactly as \`openspec archive\` does.
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting

Expand Down
24 changes: 24 additions & 0 deletions src/core/templates/workflows/sync-specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ ${STORE_SELECTION_GUIDANCE}

b. **Read the main spec** at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\` (may not exist yet)

**If it does not exist yet** (a new capability), match what \`openspec archive\` does:
only ADDED requirements may be applied - step d creates the spec from them.
MODIFIED and RENAMED have no requirement to act on, so stop the sync for that
capability and report that its main spec does not exist and only ADDED is allowed
for a new spec; never invent the missing requirement. REMOVED has nothing to
remove - skip it and warn.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
c. **Apply changes intelligently**:

**ADDED Requirements:**
Expand Down Expand Up @@ -144,6 +151,11 @@ ${STORE_SELECTION_GUIDANCE}
(this is what \`openspec archive\` does; it warns and moves on)

d. **Create new main spec** if capability doesn't exist yet:
- Only when the delta has ADDED requirements to put in it. With nothing to add -
a REMOVED-only delta against a capability that has no main spec - create nothing,
leave the specs directory untouched, and report it; \`openspec archive\` aborts the
same case with \`Spec must have at least one requirement\`. Never write an empty
\`## Requirements\` section.
- Create \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`
- Add Purpose section: copy the delta's \`## Purpose\` body verbatim when it has one
(this is what \`openspec archive\` does); only write a brief TBD placeholder when it does not
Expand Down Expand Up @@ -359,6 +371,13 @@ ${STORE_SELECTION_GUIDANCE}

b. **Read the main spec** at \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\` (may not exist yet)

**If it does not exist yet** (a new capability), match what \`openspec archive\` does:
only ADDED requirements may be applied - step d creates the spec from them.
MODIFIED and RENAMED have no requirement to act on, so stop the sync for that
capability and report that its main spec does not exist and only ADDED is allowed
for a new spec; never invent the missing requirement. REMOVED has nothing to
remove - skip it and warn.

c. **Apply changes intelligently**:

**ADDED Requirements:**
Expand Down Expand Up @@ -406,6 +425,11 @@ ${STORE_SELECTION_GUIDANCE}
(this is what \`openspec archive\` does; it warns and moves on)

d. **Create new main spec** if capability doesn't exist yet:
- Only when the delta has ADDED requirements to put in it. With nothing to add -
a REMOVED-only delta against a capability that has no main spec - create nothing,
leave the specs directory untouched, and report it; \`openspec archive\` aborts the
same case with \`Spec must have at least one requirement\`. Never write an empty
\`## Requirements\` section.
- Create \`<planningHome.root>/openspec/specs/<capability-path>/spec.md\`
- Add Purpose section: copy the delta's \`## Purpose\` body verbatim when it has one
(this is what \`openspec archive\` does); only write a brief TBD placeholder when it does not
Expand Down
Loading
Loading