Use when landing fixes from a doc-lifecycle audit report — applying approved drift records (STALE, UNVERIFIABLE) or bloat records (CUT, CONDENSE, EXTRACT-AND-MOVE, MERGE-DOC, RETIRE-DOC, DISTILL) to the documentation, and whenever tempted to hand-edit a document because a record, a record-ID list, an issue comment, or a reviewer's say-so looks like authority enough.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add aj604/toolshed --skill fixing-docs --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Fixing Docs?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/aj604-fixing-docs)More formats (shields.io, HTML) on the badges page.
---
name: fixing-docs
description: Use when landing fixes from a doc-lifecycle audit report — applying approved drift records (STALE, UNVERIFIABLE) or bloat records (CUT, CONDENSE, EXTRACT-AND-MOVE, MERGE-DOC, RETIRE-DOC, DISTILL) to the documentation, and whenever tempted to hand-edit a document because a record, a record-ID list, an issue comment, or a reviewer's say-so looks like authority enough.
---
# Fixing Docs
## Overview
**A validated, current approval set is the only authority, and the applier is the
only writer.** One door lands every record type — drift and bloat alike — because
after minting the flow is identical; the record's finding code is internal routing,
not a different skill.
You never edit a document under audit. Not with `Edit`, not with `Write`, not with
`sed`, not "just this one line". Every byte that lands is written by
`python3 -m doclifecycle apply-plan`, from an edit plan bound to an approval set the
engine validated against both the report and the repository.
**Violating the letter of the flow is violating the spirit of the flow.** The point
is not ceremony: a diff produced any other way carries no proof of what authorized
it, so a reviewer cannot tell an approved remedy from your opinion.
Contract (read it, do not restate it): `${CLAUDE_PLUGIN_ROOT}/engine/README.md`,
sections **Approval sets** and **The applier**. Run engine commands with the plugin's
engine directory on `PYTHONPATH`.
**REQUIRED SUB-SKILL:** use **writing-docs** for any replacement text you must author
yourself (a paragraph, a merged remainder) rather than place — the report's proposal
already meets that bar and is placed byte-verbatim.
## The flow — four steps, in order, every time
### 1. Mint the approval set
The input is one **engine report** — `drift-audit` or `bloat-audit` output — plus the
record **digests** a person selected from it. That selection is the semantic approval,
and minting is what turns it into the artifact; the names alone are not it.
```bash
python3 -m doclifecycle mint-approval --report "${TMPDIR:-/tmp}/report.json" --repo . \
--record <record digest> --minter <who approved> --out /tmp/approval.json
```
`--record` takes the record's **digest**, not its display id — the id is a label the
report can renumber. Repeat it once per approved record. Write the artifact
**outside the work tree or to a git-ignored path**; the engine refuses a tracked or
would-be-tracked path, because a `git add -A` in the change it authorizes would
commit the authority next to the diff.
That same placement rule covers every audit artifact you carry into this flow, not just
the approval set — the report, the verdicts/plan the detecting skill produced, and the
edit plan you author in step 2. Step 3's confinement check (below) compares the working
tree against the approval set's paths, so a `drift-report.json` or `verdicts.json` left
sitting in the tree reads as an unaccounted change and the run refuses before it applies
anything. `detecting-doc-drift` and `detecting-doc-bloat` write their artifacts to
`${TMPDIR:-/tmp}/` for exactly this reason; keep them there rather than moving them into
the repo to "review before minting."
Minting refuses before it mints; what it refuses, and in what order, is the engine
README's **Approval sets** section. **A refusal is the answer, not an obstacle**:
report it and stop.
### 2. Author the edit plan
This is the one artifact you author, and its shape is not yours to remember. The closed
operation vocabulary, each operation's exact field set, the preimage and postimage
rules, and which operations each finding code's remedy is made of (`RECORD_REMEDIES`)
are the engine README's **The applier** section, over
`${CLAUDE_PLUGIN_ROOT}/engine/doclifecycle/applier.py`. Read each operation's field set
there and build the operation from it — never from memory, an older plan, or an example.
Three rules are yours rather than the engine's, and each one is a way an authorized run
still lands an unauthorized diff.
**The remedy belongs to the record, not to you.** The finding code decides which operations
its plan may carry; a plan that picks the operation puts the choice back with the model, and
the engine refuses it (`plan-operation-not-record-remedy`). When the fix seems to need an
operation the code does not authorize, that is something to surface for the next audit, not
a plan to widen.
**Widen nothing.** A positioned operation may name only the record's own document, and must
lie inside that record's approved assertion units. An adjacent passage the approval did not
cover is a separate record's business even when the two sit one paragraph apart, and even
when the report drafted that neighbour's text.
**The text inside the operation is the report's, not yours.** A `STALE` record's `fix`
and a `CONDENSE` record's `proposal` are complete replacement text drafted to the
writing-docs bar — place them byte-verbatim and stop at their final character. The approved
units bound *where* you may write; nothing bounds *what*, so authoring your own sentence
inside an approved span produces a diff the applier certifies and no reviewer approved.
A `fix` may itself contain LF when its assertion unit was soft-wrapped; that LF, the list
marker, and the continuation indentation are part of the approved replacement, so copy the
whole string in and never collapse or re-wrap it while planning. Compose text only where
the record supplies none (a merged remainder, a distillation's residue), and route it
through **writing-docs**.
### 3. Run the applier
```bash
python3 -m doclifecycle apply-plan --repo . --plan "${TMPDIR:-/tmp}/plan.json" \
--approval /tmp/approval.json --report "${TMPDIR:-/tmp}/report.json" \
[--audit-config-digest <sha256>]
```
`--report` is required: without it the approval set's authority check is a function of
public repository state, so a selection nobody minted would validate. The working tree
must be **clean** before you run it — the applier applies onto the committed baseline, so
an unrelated edit sitting in the tree refuses the run: outside the approval's scope as
`apply-working-tree-not-confined`, inside it as `apply-working-tree-not-clean`. Commit or
discard first.
Re-running an interrupted lane is safe: the no-op verdict is derived from the bytes on
disk, never declared by the plan. The exit codes and every refusal code the run can
return are the engine README's — **The applier**, and **Approval sets** for the authority
check it runs first.
`already_applied: true` on a run you have not made before is a **tripwire, not a
success**: the bytes were already on disk, which means something other than the applier
put them there. Say so rather than presenting the diff as this run's work.
### 4. Present the working-tree diff for change approval
The applier never stages and never commits. **Change approval — a person accepting the
produced diff — is the only thing that lands anything**, so the run ends by showing the
working-tree diff, the applied operations with their records, and the approval trailers
(`python3 -m doclifecycle render-approval --approval /tmp/approval.json --trailers`) for
the commit message or PR body. You do not commit, push, or open a PR unless the person
asks for it as a separate step.
The approval set itself never enters the repository. Its digest and rendered summary do.
## Refusals — before any work, and non-negotiable
| Situation | What you do |
|---|---|
| No approval-set file exists | **Stop and mint one** from the named record digests, or say you cannot because nobody named any. A record-ID list, an issue comment, a Slack "looks right", and a report are each how an approval set is minted — never a substitute for one. |
| `apply-plan` or `validate-approval` returns **stale** (exit 3) | **Stop.** Report the verdict naming every stale reason code. The recovery is the engine's: **re-run the audit, mint afresh** against the new report. Nothing was written; do not write anything. |
| The verdict is **invalid** (exit 1) | Stop and report every problem. An invalid artifact is a forgery or a bug, not a state to work around. |
| The report is `clean`, or a record you were given is not in it | Stop. The inputs disagree; never guess which record was meant. |
| A record you were **not** given is obviously right | Surface it. Unapproved is unapproved, and an unminted record cannot reach a plan at all. |
| A record's code authorizes no operations — `POLICY`, `ANCHOR-MISSING`, `ANCHOR-MALFORMED`, `ANCHOR-UNVERIFIABLE`, `ANCHOR-FUTURE-DATED`, `ANCHOR-UNRESOLVABLE-REFERENCE`, or anything a newer audit policy introduces | Stop and surface it. `RECORD_REMEDIES` is closed and fail-shut: a code nobody listed authorizes **no** operation, so there is no plan to write. `POLICY` is a legacy bulk verdict the bloat engine retired in favor of enumerable `RETIRE-DOC` scopes. Five of the six `ANCHOR-*` codes need a `> As of` line a human authors, not a span edit anyone approved — minting refuses to select one of them at all, so you will not carry one this far into the flow. **`ANCHOR-STALE` is not one of these five**: its remedy rewrites the anchor line like any other span edit, so do not lump it in with its siblings just because the family name matches — check a record's own code against this table, never the `ANCHOR-` prefix alone, before deciding it is a dead end. |
| Audit artifacts (report, verdicts, plan) sitting in the work tree | Move them to `${TMPDIR:-/tmp}/` (or another git-ignored path), do **not** commit them. Committing moves `base_commit` and stales the approval you just minted — the recovery is re-running the audit, which re-dirties the tree the same way. Relocate, never commit. |
**Never edit the approval set, the report, or the plan's declared digests to make a
refusal go away.** Repairing a stale `base_commit`, recomputing a digest over altered
records, or hand-widening `scope.paths` is forging authority — the exact attack the
contract exists to refuse. The same goes for hand-applying a fix "since the approval
was fine ten minutes ago": a stale approval set authorizes nothing at all.
**And never move the repository to match the approval.** Resetting, reverting, or
checking out an older commit so `approval-base-commit-changed` stops firing is the same
forgery from the other side, and it is the one the "clean working tree" requirement in
step 3 most invites — that requirement means *commit or discard your own edits*, never
*rewind history until the refusal goes away*. The approval set names the world it was
minted against; when the world moved, the artifact is what gets remade.
**Minting is somebody's act, not a field you fill in.** `--minter` names who performed
the semantic approval, so you may not run `mint-approval` on an absent person's behalf,
however confident you are of what they would say — a reviewer who approved this morning's
report has not approved this afternoon's. Re-running the audit is yours; minting against
the new report is theirs. (`--minter-kind policy` is refused here outright — a policy brand
says a standing declaration selected these records, and `policy-mint` is the only door that
produces one — so it is not your workaround either.)
**Use the approval set you minted, or the one you were handed.** Another approval-set
file on disk that happens to validate is not a substitute for the one covering the
records you were asked to land; check what it selects and what report it binds to, and
say so, rather than shopping for whichever artifact clears the gate.
## Distillation — the distiller returns operations, it does not write
An approved `DISTILL` record dispatches **doc-lifecycle:doc-distiller** with that one
record, its artifact path, its evidence, and the report path (it deduplicates its
landings against sibling records, which it can only do if it can see them). The
distiller owns the method — the landing re-verify, the per-section insight walk,
code-verified claims, one decision-log entry.
**What comes back is edit-plan operations, not a changed working tree**: the residue as
one `create-document` at the record's `destination` (a durable document that does not
exist yet — the audit refuses a destination that does — carrying its `> As of` first
line), and the planning artifact as `retire-document`. You fold those operations into the
plan the applier executes. The distiller writes no files, `git rm`s nothing, stages
nothing.
One record authorizes exactly two paths — its own document and its `destination` — so
residue belonging in a third document (a decision-log entry when the destination is not
the log, an inbound reference that now points at a retired artifact) comes back
**reported, not emitted**. Raise those for their own approval and their own plan; an
operation reaching a path the record never named is
`plan-target-not-record-target`, and hand-editing it instead is the thing this whole
flow refuses.
A record carrying **no** `destination` authorizes one path, the artifact — so the whole
residue comes back reported and only `retire-document` is plannable. **Do not land that
plan on its own without saying so**: it deletes a planning artifact and strands
everything the distillation extracted. Present the drafted residue with it and let the
person decide whether to withhold the retirement until its residue has a home.
Land what verified. A claim the distiller could not verify is simply not in the
operations it returned — **surface the failure; never redraft the claim yourself** to
force it through, and never re-edit a landed result it flagged as a collision.
A `DISTILL` record whose `status` is `pending-implementation` is never actionable: there
is no landed code to verify claims against, so skip it with a note even when it was
approved. **The planning document's own `> Status:` marker is the authority for that
status**, not the record and not your reading of the plan — the engine refuses a verdict
whose status disagrees with the file, and an absent or malformed marker reads as
`pending-implementation`. So "the plan is really done, the marker is just out of date" is
something to surface, never something you decide.
## Red flags — STOP
- Reaching for `Edit`, `Write`, or `sed` against a document under audit → the applier
is the only writer, always.
- "There's no approval set, but the report says STALE and the fix is one number" → the
report is proof of examination, deliberately not authority. Mint first.
- "The lead approved it in the issue / in Slack / in review" → ask which record digests.
An approver who named no digest approved no record, and minting in their name notarizes
authority they never exercised. Go back with the report; the selection is the approval.
- Exit 3 and you are about to apply the edit anyway → stale authorizes nothing. Re-run
the audit, mint afresh.
- About to open `approval.json` in an editor → forging authority. Never.
- About to `git reset`, `git revert`, or check out an older commit so the approval stops
reading stale → same forgery, other side. The artifact gets remade, not the repository.
- About to mint with an absent reviewer's name because "they already approved this
morning" → minting is their act. Re-run the audit and hand it back.
- Attaching a `retire-document` (or any operation the record's code does not authorize)
to a record's plan because it is what the fix "really needs" → the remedy is the record's;
a plan that picks the operation puts the choice back with the model.
- An operation reaching a passage outside the approved record's units → out of scope,
even one paragraph away, even when the report drafted that neighbour's text for you.
- Committing, pushing, or opening a PR at the end of the run → change approval is the
person's, not yours.
- The distiller writing files, `git rm`-ing the artifact, or staging a commit → it
returns operations; the applier writes.
- Reaching for `fixing-doc-drift`, `fixing-doc-bloat`, or `references/apply-discipline.md`
→ all three are retired. This skill and the applier contract replaced them.
## Rationalization table
| Excuse | Reality |
|--------|---------|
| "The report already lists the record, so it's approved" | A report is proof of what was examined, not authority. Only an approval set authorizes, and only a person or a configured auto-apply policy mints one. |
| "The ID list I was handed *is* the approval" | It is how an approval set is minted, never a substitute for one. Mint it and let the engine validate it. |
| "The lane is blocked / it ships today — minting costs minutes I don't have" | Minting is one command over digests the report already carries; the flow is a couple of minutes, and it is the same couple of minutes whether or not anyone is waiting. Deadline pressure is when an unauthorized diff is least likely to be caught, which is exactly why it is not when the rule bends. |
| "I'll place my own wording in the approved span — it reads better than the report's `fix`" | The approved units bound where you write, not what. Text you authored inside an approved span is a diff the applier certified and nobody approved. |
| "It's one line — the applier is overkill for this" | The applier is what makes it one *reviewable* line: preimage checked, scope confined, provenance recorded. A hand edit is an unauthorized diff of exactly the same size. |
| "The approval went stale on an unrelated commit — the doc didn't change" | Stale authorizes nothing, and you do not get to decide which staleness was harmless. Re-run the audit and mint afresh; it is cheap. |
| "I'll just fix the base_commit field so it validates" | That is forging authority. The digest exists so every tamper is "delete one field". |
| "I'll roll the repo back to the commit the approval names, apply, then roll forward" | Moving the world to match the artifact is the same forgery as moving the artifact to match the world. The remedy is a fresh mint, in both directions. |
| "The reviewer approved this morning, so I'll re-mint in their name" | A reviewer who approved this morning's report has not approved this afternoon's. Re-run the audit; hand the mint back to them. |
| "There's another approval set on disk and it validates clean" | Check what it selects and which report it binds to. An artifact that clears the gate is not an artifact covering your records. |
| "The working tree has an unrelated edit, I'll apply on top" | `apply-working-tree-not-clean`. The applier certifies the whole diff, so it applies onto the committed baseline only. Commit or discard first. |
| "This is a bloat record, so I need the bloat fix skill" | There is one door. The finding code routes the remedy inside it. |
| "Distilling inline is faster than dispatching" | The distiller owns the method — re-verify, insight walk, dedup, decision log. Inlining drops all four, and writing files drops the applier. |
| "The distiller staged a commit, so I'll just commit it" | It returns operations. A staged commit means it broke its contract; report that rather than laundering it. |
| "The unapproved record next to my edit is obviously right too" | Its digest was never minted, so no plan can carry it. Surface it for the next approval. |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!