Skip to content

Amend a spec

CLI onlyIntermediate~10 min

Fix a spec's wording, sharpen an example, or record an edge case you've found — without breaking anyone's reference to it: the citation stays the same, so every ticket, code comment and other spec that points at it keeps pointing at the right rule. You'll need an agent that can run commands — a coding agent such as Claude Code, Codex or Cursor, with the hadron CLI (v0.17.0 or later) installed and signed in — or a terminal of your own.

A chat-only app connected to Hadron can read specs and draft the change with you, but can't save it through the spec tooling. The one decision that stays yours is the first one below.

If you are creating a spec, or curating the corpus as a whole, see Maintain product specs. If you only need to find and read one, see Read and cite product specs.

First: amend or supersede?

A citation is a permanent handle. Someone has written api:010:04 in a code comment, a ticket, or another spec, and that reference has to keep meaning what it meant when they wrote it.

So the test is not "how big is this change?" but does it change what the spec guarantees? Every spec's rubric has a Durable vs tunable section, and it is exactly the answer:

The change touches… Do this
Wording, a clearer example, a newly-discovered edge case, a value the spec itself labels tunable Amend in place
The durable half — what the spec promises, an enum's members, who the rule applies to, a guarantee being withdrawn Supersede: mint a new citation (the next number, or a path you name with --to)

If the spec has no Durable vs tunable section, that is the real bug. Add one while you are in there. No check will ask for it — spec lint doesn't look for that section, on any spec — so this is on you.

Superseding is covered in Replace a spec without renumbering. The rest of this page is the amend path.

Ask your agent to amend it

Say which spec, what to change, and that you want to see the change before it is saved:

In acme.com:specs, spec api:010:04's second example says "one week" where
the rule itself says "7 days". Make the example match the rule — it's a
wording fix, so amend it, don't supersede it. Show me exactly what will
change, before and after, and wait for my OK before you save.

What you get back: the spec's current text, the proposed text, and which parts change — the body, the abstract, or both. "Wait for my OK" matters: a coding agent working on its own can otherwise show you the change and save it in the same step. Ask for the before and after explicitly: the tool's own preview only lists which fields would change, so a good agent pulls the text and shows you the difference itself.

What to check before you approve:

  • It's still an amendment. If the change alters what the spec promises, stop — that's a supersede.
  • The abstract still describes the body. If the body changed, the abstract usually should too; they are one unit.
  • Nothing else moved. The preview should report only the fields you meant to change.

When you approve, the agent saves the change and re-reads the spec to confirm it. The tooling doesn't warn you if someone else changed the spec after the preview, and saving replaces what's there — so if anyone else might be editing it, ask the agent to re-read it just before saving, compare it with the version it previewed, and stop and show you a fresh before-and-after if anything differs.

What you need for this path: write access to the specs memory, and the agent set up as described above. To do it by hand instead, read on.

Why not just edit it in the portal?

You can open a spec node in the portal and edit it, and for a typo that is fine. But the portal edits a node; it knows nothing about specs. Three things it will not do for you:

  • Keep the abstract honest. The portal has an abstract field, so you can change it — but nothing connects it to the body you just rewrote. Change the body, leave the abstract, and the spec quietly drifts: its abstract was authored against different content, and the corpus checks below start reporting it as stale-abstract. That matters more than it sounds, because the abstract is the vector-search retrieval surface — a stale one means a reader finds a spec that no longer says what they just read.
  • Check the rubric. On numbered rules and flows, spec lint enforces the mandatory "What invalidates this spec" statement, the abstract's presence, and data.version; on every spec it checks the abstract's length. A spec named by path gets none of the rubric checks, so read its sections yourself. A portal edit runs none of it.
  • Stop you renumbering. Nothing in a node editor knows that a citation is permanent, or that the change you are making needed a new number.

hadron spec edit exists because the body and the abstract are one logical unit. It opens both.

Amend it with the hadron CLI

You'll need the hadron CLI, v0.17.0 or later for specs named by path and for supersede --to, installed and signed in — see Install the hadron CLI — and write access to the specs memory. The examples use acme.com:specs and the rule api:010:04.

Read the current state first — spec get prints the abstract, the body, the edges, and a lint summary in one go:

hadron spec get api:010:04 -m acme.com:specs

Then edit:

hadron spec edit api:010:04 -m acme.com:specs

This opens $EDITOR (then $VISUAL, else vi) pre-loaded with the current abstract and body in a single buffer, divided by sentinel comment lines:

<!-- === ABSTRACT === one paragraph; the spec's RAG retrieval surface. Edit below this line. -->
Re-engage users who signed up but never finished setup, 7 days in.
<!-- === BODY === the spec markdown. Edit below this line. -->
# api:010:04 — W4 — 7-day check-in
...

Pre-loading is the point: you change the lines you mean to, instead of reconstructing the node in a temp file and risking a transcription slip on a full replace. Keep the body divider — it is how the buffer is split back apart, and spec edit refuses to write without it rather than guessing. A field you do not touch is not rewritten; an unchanged buffer writes nothing.

A worked example

Say api:010:04 sets a 7-day check-in, its Durable vs tunable section lists the window as tunable, and product wants 14 days. Tunable → amend.

In the buffer, the body changes:

 ## Rule & examples

-A user who has not completed setup is sent a check-in **7 days** after signup.
+A user who has not completed setup is sent a check-in **14 days** after signup.

and — in the same pass — so does the abstract:

-Re-engage users who signed up but never finished setup, 7 days in.
+Re-engage users who signed up but never finished setup, 14 days in.

Updating both together is the habit worth building. It is not bookkeeping: the abstract is what semantic search matches on, so an abstract still promising "7 days" will keep answering questions about a rule that now says 14.

Save, and confirm:

hadron spec lint api:010:04 -m acme.com:specs --strict

Editing without an editor

For scripts, CI, or an agent, replace the fields non-interactively — in one call, so the body and abstract land together:

echo "Re-engage users who never finished setup, 14 days in." \
  | hadron spec edit api:010:04 -m acme.com:specs \
      --content-file rule.md \
      --abstract -

That is a single write: spec edit sends both fields in one updateNode mutation, so the spec is never briefly left with a new body and its old abstract. Two sequential calls would have exactly that window — and if the second one fails, the drift is permanent.

--content / --abstract take the value inline; --content-file / --abstract-file read from a path; - reads stdin (only one field can use it, which is why the example pipes the shorter one). A field whose flag you omit is preserved untouched, and a field that did not actually change is not rewritten. --dry-run previews without writing.

To change the description shown by spec list and search, pass --description "New summary" or --description-file summary.md. It uses the same guarded write and can be combined with body and abstract changes. The description is not in the default editor buffer. A replacement abstract may contain at most 2000 UTF-16 code units, counting whitespace and newlines; --dry-run rejects an over-limit abstract before showing a proposal.

Save a proposal approved earlier

If someone reviews the proposed change before you save it, preview the exact files that you plan to use and keep the three values reported by the preview:

hadron spec edit api:010:04 -m acme.com:specs \
  --content-file rule.md --abstract-file abstract.md \
  --dry-run --json

Review the diff in changes, then save with the same files and the preview's nodeId, revision, and proposalHash. Replace the uppercase placeholders with those three values from the same preview:

hadron spec edit api:010:04 -m acme.com:specs \
  --content-file rule.md --abstract-file abstract.md \
  --expected-node-id NODE_ID_FROM_PREVIEW \
  --expected-revision REVISION_FROM_PREVIEW \
  --expected-proposal-hash PROPOSAL_HASH_FROM_PREVIEW

Every save checks the node ID and revision, including an ordinary editor save. The three --expected-* flags also ensure that a later save matches the proposal that was reviewed. If the spec or files changed, the command exits 5 without writing. Re-read the spec, reconcile the change, and get a new preview approved before saving.

Changing only the body leaves the abstract stale

Passing --content-file alone is legitimate — for a typo, or a change that does not alter what the spec says. But if the meaning moved, refresh the abstract in the same call. Otherwise you have created the drift the next section is about.

Find specs whose abstract has drifted

Two tools, for two different questions.

Auditing the corpus — every drifted spec in the memory:

hadron memory validate acme.com:specs --check stale-abstract

Checking the specs your code points at — spec citations scans a source tree for the // Spec: <citation> pointers the authoring workflow asks for, and normally reports only broken or superseded ones. --stale-abstracts adds drift on the specs it found:

hadron spec citations -m acme.com:specs --src src/ --stale-abstracts

That is the narrower, more useful check when you are working in a codebase: it flags drift in exactly the specs this code depends on, rather than the whole corpus.

Read this signal narrowly

stale-abstract is a hash comparison — the abstract's origin hash against the current content hash. It fires on any body edit made since the abstract was last written, including one that changed nothing the abstract says. It means "the body moved under this abstract", not "this abstract is wrong".

On a corpus that has never been audited it will flag a large fraction of the specs, and most of those abstracts are fine. Treat it as a worklist to skim, not a defect count — and reach for it when you are already editing a spec, rather than as a cleanup project. spec citations keeps it behind an opt-in flag for exactly this reason.