Skip to content

Amend a spec

CLI onlyIntermediate~10 min

A spec is rarely right the first time. Thresholds move, an edge case turns up, a sentence turns out to be ambiguous. This guide is about changing a spec that already exists — and about the one decision you have to make first, because getting it wrong breaks citations.

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. And if you own the rules but not the tooling, let a coding agent run this page for you — see Manage product specs with a coding agent.

Prerequisites

  • The hadron CLI installed and signed in — see Install the hadron CLI. Check with hadron auth status.
  • Write access to the specs memory. The examples use acme.com:specs and the rule api:010:04.

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 number

If the spec has no Durable vs tunable section, that is the real bug. Add one while you are in there — spec lint will keep asking for it.

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

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. spec lint enforces the mandatory "What invalidates this spec" statement, the abstract's presence and length, the table-of-contents and inheritance edges, and data.version. 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

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.

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 atspec 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.