# Docs Promote

> Use when a document in a repository with docs/index.json must change authority or place — promote a reference document into product or engineering scope, move a file between tiers, take a plan from draft to active to shipped, supersede or archive one. Not for a stray with no frontmatter (docs-sort) or a red gate (docs-gate).

- Skill: `magnifito/docs-promote` (Agent Skill)
- Install (CLI): `npx skillmds@latest add magnifito/docs-promote`
- Raw SKILL.md: https://api.skillmd.com/api/skills/magnifito/docs-promote/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: magnifito (https://skillmd.com/u/magnifito)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/magnifito/docs-promote

---


# Promote, move, or retire a document

Authority is not a folder. A document's tier and status say how much weight a reader gives it, so
a move is a claim, and `check --base` verifies the claim against the branch's history.

## 1. Nothing in `reference/` may be implemented directly

Installed as a plugin, `docs-notary`'s `reference-read` hook says so on every Read of a document
whose status is `reference`: not a commitment, never a build spec, and here is the `mv` that
promotes it. The hooks run the engine the host repository installed (`node_modules/@puralex/docs-notary`), or the plugin's own copy only in a clone where `yaml` resolves; a plugin install is a bare clone, so on the vendored route both hooks are silent. Promote it first, then **rewrite the prose to describe this product**:

```bash
docs-notary mv docs/reference/<name>.md docs/product/<name>.md
```

Run these as `npx docs-notary …` (npm install) or `node scripts/<script>.mjs` (vendored — the script names differ from the command names: `check` is `check-docs.mjs`, `new` is `new-doc.mjs`, `mv` is `mv-doc.mjs`, `gen` is `gen-docs-index.mjs`); a bare
`docs-notary` is on PATH only inside an npm script.

`mv` is `git mv` plus a restamp: it sets `kind` and `module` for the destination, and sets `status`
to the destination tier's forced status if it has one, else `--status`, else `draft` when leaving
`reference`, else the status the document already had. `product/` forces nothing and no `--status`
is passed above, so this example lands on `draft` — the `reference → draft` edge. `mv` also sets
`updated` to today (a promotion is a substantive change and the rewrite is mandatory) and records
`promoted_from`. That field is what lets `check --base` tell a promotion from a copy. A bare
`git mv` across tiers records no `promoted_from` and fails `promoted-verbatim` under `check --base`.

Then rewrite. Promoting a captured document verbatim is how someone else's assumptions become your
requirements. `check --base <ref>` compares the promoted body with the origin's body before the
branch and fails `promoted-verbatim` when they are identical. Rewriting the prose is the half no
command can do.

## 2. Status transitions the gate allows

`check --base <ref>` compares each changed document's status with its status at the merge base and
allows only these edges. Any other edge is a `transition` error.

| From | To |
|---|---|
| `reference` | `draft`, `superseded` |
| `draft` | `active`, `superseded` |
| `active` | `shipped`, `superseded`, `draft` |
| `shipped` | `superseded` |
| `superseded` | nothing |

An unchanged status is always legal. A status the project added in its config is in no graph and
moves freely. `reference/` forces `reference`; `archive/` forces `superseded`.

- **`draft` → `active`**: the work is agreed. Bump `updated`.
- **`active` → `shipped`**: convention, not enforcement — name the paths in `code:` and give
  `evidence:` the gate can check anyway; `shipped` without `code:` only warns (`shipped-code`), and
  the gate requires `evidence:` on no kind by default. Never ship on the strength of the plan's own
  "done" prose; if in doubt, docs-audit runs `verify` first.
- **`active` → `draft`**: the plan was reopened. Say why in the body.

## 3. Supersede, then archive

When a newer document replaces an older one:

1. Set `superseded_by: docs/<tier>/<new-name>.md` on the old file. Leave `status` alone —
   `superseded` outside `archive/` is a `vocabulary` error.
2. Move it: `docs-notary mv docs/<tier>/<old-name>.md docs/archive/<old-name>.md`. The
   `archive/` tier forces `status: superseded`. The prose stays as it was — an archived document is
   a record, and `check --base` owes it no rewrite.
3. Fix every link that pointed at it. The gate catches a link still naming the old path (`link`:
   the target is gone) but not one you retarget at the archived copy — `link` never reads a
   target's status, so a pointer at a superseded document is yours to notice.

A document whose own first line says SUPERSEDED belongs in `archive/` whatever its frontmatter
says. A "closed" plan that still lists untaken steps is a live backlog and stays `active`.

## 4. Moves within a tier and hand-made moves

A rename inside one tier keeps status and needs no `promoted_from`; `mv` handles it, and sets
`updated` to today as it does on any move. After a move made by hand, `docs-notary fix` restamps
`kind` and `module` — the gate asserts they agree with the path. `git mv` renames for case alone in
one step. A file with no frontmatter at all is a stray, not a move: docs-sort.

## 5. Before you finish

```bash
docs-notary gen
docs-notary check --base <default-branch>   # transition + promoted-verbatim run only here
docs-notary impact --base <default-branch>  # documents whose claims cover the changed code
```

Update every link the move broke, then every document `impact` lists. A red gate is docs-gate.

