Skip to content

Exploration: should OpenSpec stop merging specs deterministically in openspec archive? #2033

Description

@TabishB

This is an open question, not a proposal ready to implement. Please don't open a PR from this issue yet. It needs more careful thought and discussion first, and we'd like to hear from the community.

The question

openspec archive merges delta specs into main specs by matching requirement headers. We're starting to think a deterministic merge isn't realistic to support well, and that the merge belongs with an agent instead. We don't yet know what should replace it, or what that would break.

What we're seeing

Directions we're thinking about

None of these are decided. Each one has trade-offs we haven't worked through.

  1. Agent merges, CLI checks. Drop the deterministic merge. The agent updates the specs, and the CLI keeps checks that must hold. For example: specs parse, no leftover delta sections, tasks are done. Close to Proposal: prepare → agent work → validate → confirm → finalize flow for archive #1460.
  2. Sync as you go, no archive step. Spec updates become part of the task list. Each task group updates the specs it implemented, and a final task checks that everything was updated. Specs land in the same PR as the code, so there's no separate archive PR. See Exploration: OpenSpec without archive #1968, and feat(cli): archive a completed change without a separate step #1831 for a related angle.
  3. Keep the deterministic merge, fix what it matches on. For example, stable requirement IDs instead of names. This is the main option that keeps openspec archive as it is.

Open questions

  1. What breaks for people who run openspec archive from CI or scripts?
  2. If the CLI no longer merges, what can it still guarantee? Any "specs are in sync" check still has to match requirements somehow.
  3. How does this work with several changes in progress at once, or specs in a separate repo?
  4. What happens to bulk archive, and to hooks that run around archive (Feat : Add Extensible Hook Capability to OpenSpec Archive Operation #682, Feature Request: Support on_archive (post-archive hooks) in schema for custom artifact moves/copies #704, Add optional lifecycle hooks for sync and archive #1910)?
  5. Should the command be removed, kept as a plain "move to archive" step, or kept as it is?

How you can help

  • Tell us how you archive today, and whether the CLI merge has worked for you or against you.
  • Share workflows where removing it would hurt.
  • Suggest directions we've missed.

Related

Activity

  1. TimZander commented on Oct 4, 2026

    @TimZander

    Thanks for opening this up. Here's one data point, from a small production site that moved to
    OpenSpec 1.14 a few days ago. The whole app is regenerated from the specs, so the main specs
    have to stay exactly right.

    How we archive (Q1). One PR carries a change's deltas plus the code. After that PR
    merges, a separate PR does nothing except run openspec archive <name> --yes (several
    changes in name order). Archiving inside the change's own PR doesn't scale: parallel changes
    conflict in specs/. Archiving later by hand gets forgotten.

    To make that archive PR safe to merge without review, CI re-runs openspec archive on the
    base and checks the PR is exactly that output, plus a few more conditions. This is where
    the deterministic merge earns its keep for us.
    It makes the archive step verifiable,
    which matters more to us than making it automatic. If an agent did the merge, we'd lose that
    oracle and have to review every archive by hand.

    What the CLI could still guarantee (Q2), and parallel changes (Q3). The silent overwrite
    you describe is the case we worried about most. Change B, written against the old text of a
    requirement, archives after change A and replaces A's edit. We guard against it with
    base fingerprints, essentially Phase 0 of your
    parallel delta plan:

    • each change stores a SHA-256 of every requirement block it modifies, removes or renames,
      as the block read when the delta was written (null for ADDED). Blocks are canonicalised
      first: line endings, trailing whitespace, runs of blank lines;
    • a PR check fails if a change's fingerprints are already stale, while the author can
      still fix the delta;
    • the archive check compares fingerprints before archiving each change, so both cases are
      caught: two changes active at once, and B started before A was archived.

    It needs no git history (squash merges and rebases don't matter), and it behaves the same
    on GitHub and Azure DevOps. Whatever happens to the merge, the CLI could still own this
    guarantee
    : "this delta was written against the requirement as it is now".

    Other sharp edges we hit on 1.14.0, in case they're useful:

    • archiving a change that creates a capability writes a placeholder Purpose, which then
      fails validate --strict, unless the delta has a ## Purpose;
    • archive --yes archives with open tasks;
    • archiving several changes in different orders gives a different requirement order;
    • validate --specs doesn't check SHALL/MUST in main specs (only in deltas);
    • a change folder named -y is parsed as an option.

    On Q5 and the three directions, for what it's worth:

    • we'd keep a deterministic merge, anchored on stable IDs (direction 3), and add
      fingerprints;
    • we already prefix every requirement name with a permanent ID (NAV-5 …), which in
      practice avoids most header drift;
    • an agent-performed merge (direction 1) would be fine as a helper for resolving a
      fingerprint conflict. We'd still want the CLI to be the one that can re-run and check the
      result.

    We plan to run this on a second, larger project. If it holds up, we'd be glad to contribute
    the fingerprint piece, coordinating with #1387 (concord), which detects the same drift from
    git history.

  2. littletuna4 commented on Oct 11, 2026

    @littletuna4

    Tim's comment is the CI case I was missing. A small production site on OpenSpec 1.14: one PR carries a change's deltas plus the code, and after it merges a follow-up PR only runs openspec archive --yes (several changes, in name order). CI re-runs archive on the base and checks the PR is exactly that output, so the deterministic merge is the oracle. Prefixing names with a permanent ID (NAV-5 …) already dodges most of the header drift. I think that's Direction 3 still living inside the heading. The hole I still see is that archive matches the heading string, so an agent rename can break the join or grow a twin SHALL while validate stays green.

    I've been modelling the address as something that isn't the heading: one named idea in reqlan, rq: pins and proven by edges under the OpenSpec markdown folder, checked by graph static analysis. Honest ceiling: named bindings, not SMT. Not Spec Kit, not SpecGraph or GraphSpec, and this .rq is not SPARQL. The delta and main prose stay OpenSpec's. Fingerprints (the delta was written against the requirement as it is now) answer a different question than a stable ID. I think they stack; they don't replace the address. That address is what archive, or a beforeArchive hook (#1910), would key off. Not proposing a PR. You asked for discussion first, and I'm not suggesting OpenSpec adopt another process kit. Our side of the under-folder pattern is still unfinished. If Direction 3 stays on the table, happy to share a concrete pattern once you've narrowed the archive model.

    Disclosure: I build reqlan, so weigh this with that bias.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    design-reviewNeeds product/design decision

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions