DEV Community

Shivam Dwivedi
Shivam Dwivedi

Posted on Fully Autonomous

The OpenSpec update I want first is the one that writes nothing

I added a local note to a generated OpenSpec command, asked what an update would do, and checked the files afterward. The preview reported a replacement. Every file hash stayed the same.

Then I ran the update. The local note disappeared.

That is a useful distinction to see before your coding assistant starts following a different set of instructions. OpenSpec 1.15.0 adds update --dry-run, with a JSON form that lists planned file actions. It also ships a standalone CLI. The interesting part for an existing project is getting to inspect the replacement before accepting it. [1]

Executed OpenSpec fixture: add a local note, preview an update with unchanged file hashes, then apply the update and observe that the note is removed.

Original diagram of the checked fixture below. It describes generated instruction files, not an AI coding session.

The version number wasn’t the question

An assistant integration has more moving parts than the executable. There are generated commands, skills, and the project’s own context. A current CLI does not tell you that every generated file matches its current template—or that you want it to.

My fixture starts with a clean Claude integration and deliberately customizes .claude/commands/opsx/explore.md. It then checks three separate things:

  • Did the preview identify that command as an update?
  • Did previewing leave both the project and isolated configuration trees untouched?
  • Did applying the update actually replace the customization?

The last check matters. A plan is useful evidence about an intended action; the resulting files establish what happened. 🔎

Here’s the output from OpenSpec 1.15.0:

preview changed files: 0
custom command: update
local note retained after update: False
remaining planned changes: 0
Enter fullscreen mode Exit fullscreen mode

The disappearing note is deliberate in this disposable example. In a real project, it could be the sentence you wanted your assistant to keep remembering. The update cannot decide whether that sentence was important to you.

Run the same disposable check

For a POSIX shell with Python 3.10 or newer, create a scratch directory and install the pinned standalone release into it. The installer selects a compatible release asset and checks its checksum. Other supported installation routes are in the project’s documentation. [2]

mkdir openspec-preview-lab
cd openspec-preview-lab
curl -fsSL https://raw.githubusercontent.com/Fission-AI/OpenSpec/v1.15.0/scripts/binary/install.sh -o install.sh
OPENSPEC_VERSION=v1.15.0 OPENSPEC_INSTALL_DIR="$PWD/.tools" sh install.sh
.tools/openspec --version
Enter fullscreen mode Exit fullscreen mode

Save the following as preview_demo.py, then run python3 preview_demo.py from that directory. It uses a temporary project and separate configuration/data directories. The claude tool choice generates integration files; this script does not launch Claude or make a model request. 🧪

import hashlib
import json
import os
import subprocess
import tempfile
from pathlib import Path

binary = Path(os.environ.get("OPENSPEC_BIN", ".tools/openspec")).resolve()

def manifest(root):
    return {str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest()
            for p in root.rglob("*") if p.is_file()}

with tempfile.TemporaryDirectory() as directory:
    root = Path(directory)
    project = root / "project"
    project.mkdir()
    env = {**os.environ, "XDG_CONFIG_HOME": str(root / "config"),
           "XDG_DATA_HOME": str(root / "data"), "DO_NOT_TRACK": "1",
           "OPENSPEC_TELEMETRY": "0"}
    def run(*args):
        return subprocess.run([str(binary), *args, str(project)], env=env,
                              check=True, capture_output=True, text=True).stdout
    run("init", "--tools", "claude", "--profile", "core", "--no-animation")
    command = project / ".claude/commands/opsx/explore.md"
    command.write_text(command.read_text() + "\nLOCAL_NOTE: review this customization.\n")
    before = manifest(root)
    plan = json.loads(run("update", "--dry-run", "--json"))
    assert manifest(root) == before
    edits = [c for c in plan["changes"] if c["path"] ==
             ".claude/commands/opsx/explore.md"]
    assert len(edits) == 1 and edits[0]["action"] == "update"
    print("preview changed files:", len([p for p in before
          if manifest(root).get(p) != before[p]]))
    print("custom command:", edits[0]["action"])
    run("update")
    assert "LOCAL_NOTE" not in command.read_text()
    print("local note retained after update:", "LOCAL_NOTE" in command.read_text())
    again = json.loads(run("update", "--dry-run", "--json"))
    assert again["changes"] == []
    print("remaining planned changes:", len(again["changes"]))
Enter fullscreen mode Exit fullscreen mode

The manifest includes relative file paths and content hashes. Comparing the complete dictionaries catches additions and deletions as well as changed contents. Merely checking modification times would be a weaker test: a file can be rewritten with identical bytes.

The isolated configuration directory is intentional. The preview can include OpenSpec configuration actions alongside project instruction changes. I want the disposable check to include those effects without borrowing settings from another project. The source defines XDG_CONFIG_HOME as a supported override. [3]

Read the action, then inspect the difference

The JSON describes which files would be added, updated or deleted. That is not a semantic review of the new instructions. action: update cannot tell you whether a sentence became less precise, a local restriction disappeared, or a workflow now expects another artifact.

⚠️ My mistake would be treating a clean preview as permission to replace everything. “This command is generated” and “this command contains no useful local changes” are different facts.

For a maintained project, I’d first save the current files in version control. I’d inspect the preview, apply the intended update, then read the actual diff before using the assistant integration. If a customization belongs in a project-owned instruction file, move it there deliberately and check that the relevant tool reads it. Avoid assuming every tool has the same override rules.

✅ This fixture checks one generated Claude command and the isolated trees around it. It establishes the preview’s behavior for that case. It does not establish preservation rules for every integration, validate the meaning of generated instructions, or prove that an agent will obey them.

The useful addition is modest and practical: an update now has a readable “before” state. I’d rather discover the disappearing sentence there than after asking an assistant why it stopped doing something.

References

  1. OpenSpec 1.15.0 release: standalone install and update dry run.
  2. OpenSpec installation documentation.
  3. OpenSpec 1.15.0 configuration-directory implementation.

Asset credit: original diagram of the executed disposable OpenSpec fixture.

AI disclosure

AI was used to draft the article.

Top comments (0)