docs-compact · git:20261014.56717c8 · 2026-10-14 · sha256 fe097a7f8b8315b7
docs-compact git:20261014.56717c8A
Immutable. This exact content never changes and is served at /api/v1/blob/fe097a7f8b8315b7.
---
name: docs-compact
description: "Compact the tree's documents to what holds at the head of the main branch: rewrite each ADR to its present decision with a status of one date, remove the ADRs that constrain nothing, cut the changelog to the latest release, clean the rows of specs/architecture.md, and rewrite the comments that tell what the code did before. With --migrations, fold the revisions each role's chain owns into one revision under the head's revision id, shown equal by a schema dump; in a copy or a layer, on its base's head, with the base's files left as rendered. Works on a branch, pushes nothing, and reports what it removed, rewrote, and kept."
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(git status:*), Bash(git fetch:*), Bash(git symbolic-ref:*), Bash(git switch:*), Bash(git log:*), Bash(git diff:*), Bash(git rev-parse:*), Bash(git merge-base:*), Bash(git ls-files:*), Bash(git ls-tree:*), Bash(git rm:*), Bash(git mv:*), Bash(git add:*), Bash(git commit:*), Bash(git restore:*), Bash(git clean:*), Bash(git grep:*), Bash(diff:*), Bash(mkdir:*), Bash(gh release:*), Bash(gh run:*), Bash(gh api:*), Bash(docker info:*), Bash(docker compose:*), Bash(uv run:*), Bash(make setup), Bash(make openapi), Bash(make check), Bash(make infra-up), Bash(make migrate)
---
# docs-compact
A document an agent reads says what holds at the head of the main
branch, in the present tense: a README, an ADR, a spec, a comment. What
was stays in git, the pull request, and the release notes (the
guideline's Documentation as Code). An agent acts on what it reads, so
text that also carries what was makes it sort the adopted from the
rejected before it acts. This skill cuts the tree's documents back to
what holds.
One test decides a rejected option. Could a reader at the head reach it
without knowing the past? A simpler design, a library's default, the
obvious other way: yes, so it stays as one sentence in the Decision, as
the rule's near miss with its reason ("a lock bound, never a statement
deadline: a long backfill would fail for running"). A former value, or
an option only the history explains: no, so it goes.
## Input
`[--migrations]`
Without the flag the skill rewrites text only, and never opens
`om/migrations/`: a search lists lines of it, as step 6's does, and no
file there is read whole or changed. With it, it also folds the
revisions each role's chain owns into one revision, as step 7 says:
every revision, or in a copy or a layer, those the render of its base
does not hold.
It runs at the root of the tree, the folder that holds the tree's
Makefile, and every command it gives runs there. In a copy, that folder
is the root of the checkout. In a layer, it is the folder of the
layer's repository that holds its tree. In the guideline's repository,
it is the folder that holds this scaffold, never the repository's root;
that repository has no `scaffold` branch, so the fold there holds the
whole chain.
## Role and credential
None of the system's: it reads no environment. On the local stack it
logs in to the databases it makes and drops, one for the gates and two
for the fold, and to the stack's own for one thing: the fold's
precondition brings it to its head (`make migrate`). It reads the
repository host through `gh`, under the person's own sign-in: each
release's notes, and for the fold each environment's last deploy.
## One-time preparations
Each is a person's to do, once. A part whose preparation is missing is
left as it is, and the report names it under Kept.
- **The changelog.** Every release the changelog lists below its latest
has its notes on the repository host (`gh release view v<x.y.z>` exits
0). A release with none keeps its section. The skill saves that
section as `release-v<x.y.z>.md` in the evidence folder, and the
report gives the command that publishes it: `gh release create
v<x.y.z> --verify-tag --notes-file <that file>`.
- **The fold.** Every environment has deployed the commit that holds the
newest migration. The local stack is up (`make infra-up`), with `make
migrate` run once.
## Procedure
1. **Start clean, on a branch.** `git status --porcelain` prints
nothing, or refuse. `git fetch origin`. The main branch is the one
`git symbolic-ref --short refs/remotes/origin/HEAD` names. On it, cut
the work branch, named for the day: `git switch --no-track -c
docs-compact-<yyyy-mm-dd> origin/<main>`. The name is taken when
`git rev-parse --verify --quiet <name>` or `git rev-parse --verify
--quiet origin/<name>` prints a commit: add a suffix then (`-2`). On
another branch, work there only when `git log
--oneline origin/<main>..HEAD` prints nothing; otherwise refuse.
The evidence folder is `~/Downloads/acme_docs_compact_<yyyy-mm-dd>/`
and the report `~/Downloads/acme_docs_compact_<yyyy-mm-dd>.md`. When
either exists, both take the next free suffix (`_2`), so a run never
overwrites another's. Make the folder (`mkdir -p`).
2. **Take the inventory**, and keep it for the report. Read nothing
under `node_modules`, a lock file, a generated file (what `make
openapi` writes: `clients/openapi/`, `clients/typescript/src/schema/`,
and `clients/python/src/acme/client/schema/`), or a skill folder that
is a link.
- Every `docs/adr/NNNN-*.md`: its status line, its `##` headings, and
who cites it, in the code, the config, and the Markdown. A citation
takes one of four forms. `ADR NNNN`, `ADR-NNNN`, or `ADRNNNN`, in a
sentence or a comment: the checker reads all three. `adr/NNNN` in
a path. `NNNN-` where the file's name begins, as in a link from
the ADR beside it. And `NNNN` at a line's start, under a line that
ends in `ADR`: a citation the margin wrapped. The digits alone are
no citation.
```bash
git grep -nE 'ADR[- ]?NNNN|adr/NNNN|NNNN-[a-z]' -- . ':!docs/adr/NNNN-*'
git grep -nE -B1 '^[^[:alnum:]]*NNNN([^0-9]|$)' -- . ':!docs/adr/NNNN-*'
```
A hit of the second search is a citation when the line above it,
which the search prints, ends in `ADR`.
- `CHANGELOG.md`: its release sections, when the tree has one.
- The Substitutions and Deviations rows of `specs/architecture.md`;
with a `scaffold` branch, those below each table's marker.
- The sentences that tell a past, in the Markdown and in the code's
comments and docstrings. `references/phrases.txt`, in this skill's
folder, lists the phrases that mark one, a pattern a line, each
matched as whole words. The pathspec leaves out what the skill
never edits: its own folder, `om/migrations/`, the three generated
folders, and the lock files.
```bash
git grep -nIiwE -f .agents/skills/docs-compact/references/phrases.txt -- . ':!.agents/skills/docs-compact' ':!om/migrations' ':!clients/openapi' ':!clients/typescript/src/schema' ':!clients/python/src/acme/client/schema' ':!*.lock' ':!*.lock.hcl' ':!pnpm-lock.yaml'
```
The list is the sweep: the skill reads each hit in its paragraph,
and does not read every file.
3. **Compact the ADRs**, each once, then commit them alone.
- **The tree's own alone.** With a `scaffold` branch (`git rev-parse
--verify --quiet origin/scaffold` prints its id), each ADR that
`git ls-tree -r --name-only origin/scaffold -- docs/adr` lists is
its base's, and stays as the render gave it: a copy never edits an
ADR its base gave, and the next move rewrites it with no conflict.
None is rewritten, removed, or the target of a move or a fold
below; a decision of the tree's own that extends one stays in the
tree's ADR that cites it.
- **Rewrite in place** an ADR whose decision holds. Its status is
`**Status**: accepted (<date>)` and nothing more, with the date
the old status gives for its acceptance, wherever in the line it
stands. When it gives none, the date is the day the file was added
(`git log --follow --diff-filter=A --format=%as -- <file>`). Every
other date in the status goes. It keeps Context, Decision, and
Consequences, in that order, and any section that states a present
fact, such as a measurement. A section named for a past (Amended,
Closed, Alternatives, History) goes: what still holds in it moves
into the Decision or the Consequences, and a rejected option goes
through the test above. The text states the decision as it stands,
with no "since", "no longer", or former value, and names no
ticket, pull request, or person. A migration's revision id or file
name that it cites stays as it is: only a fold moves one, in
step 7. An ADR that records an expand and contract takes step 6's
test here, before its rewrite: in flight, it keeps every fact of
the contract, and with its piece dropped, what it says of the
contract's steps goes. That is its one rewrite, in this step's
commit.
- **Remove** an ADR that constrains nothing at the head: one replaced
whole, one that is closed, one that records what a release asked or
that a pin moved. A decision in it that still stands moves first,
into the ADR that records that decision. A decision no other ADR
records stays in this file, rewritten, under its number. When
several do, one stays: the one most citations of the number mean,
or the first the file states when nothing cites the number or two
tie. The file then takes that decision's name (`git mv`, to
`NNNN-<the decision, as a slug>.md`), as any rewritten ADR does
whose name says another subject: the number stays, and every link
and every `adr =` path follows. Each other one gets a new ADR of
its own, numbered in turn: the first one above the highest the tree
held when the run began, the next one above that. A citation of the
kept number that meant a moved decision names the ADR that now
holds it. An ADR with no decision standing goes whole.
- **Fold** a substitution that has an ADR of its own into the one ADR
that lists the substitutions, as one row and the sentences that row
needs, and remove the ADR.
- **Re-point every citation** of a removed number before its file
goes (`git rm`): a link in Markdown, an `adr =` entry in
`pyproject.toml`, and an `ADR NNNN` in a comment, a docstring, or
a sentence of prose, a README's or the changelog's. Each names the
ADR that now holds the decision, or goes when what it explained is
gone. `arch-check` fails on a number that code cites and no file
has. A citation of a rewritten ADR for a part its text leaves out
follows the same rule: it names the ADR or the file that states
the fact now, or it goes. A citation in a place the run never
edits stays as it is: a file under `om/migrations/`, or a string a
statement carries. The ADR it cites is never removed, and keeps
the part the citation is for: one this step would remove stays as
the run found it, and the report lists it under Kept with each
such place.
- An ADR that only restates a rule of the guideline, and decides
nothing of the tree's own, constrains nothing. It is removed when
no code or config cites its number, its links in Markdown going
with it. While one does, it is kept, and listed. An ADR the test
leaves in doubt is kept and listed too. Either is rewritten in
place as this step's first item says, its status one date.
- A file under `docs/adr/` that is not an ADR, such as a picture,
goes when no ADR links it; the folder's `README.md` stays. An ADR
never goes for want of a link.
4. **Cut the changelog** to its latest release: the lines above the
first release, that one section, and one line that says every
release's notes stay on the repository host. Each older section goes
when its preparation holds. A line above the first release that the
cut makes false, such as one that says each release has an entry
here, is rewritten to what the file holds after the cut. Commit.
5. **Clean the rows** of `specs/architecture.md`: each says what the
substitution or the deviation is, in the present tense, with its end
condition when it has one. A date, a release, and "ended with" go. A
row whose ADR step 3 removed goes, or names the ADR that holds it
now. The pin line stays as it is, and so does each table's marker
line, `<!-- A copy adds its entries below this line. -->`. With a
`scaffold` branch, the rows above a marker are the base's and stay
as the render gave them: only the tree's own table below it is
cleaned. Commit.
6. **Rewrite the sentences** of step 2's sweep, each hit judged once.
- A sentence about the code's past goes, or is rewritten to what the
code does: what a former release did, what changed, what a value
once held.
- A sentence about a running system stays: "raises `LeaseLost` when
the item is no longer this worker's" is state, not history. So does
a rule's near miss, and a lens's or a skill's own words for a
breach.
- A comment on code that serves the release before, in an expand and
contract still in flight, stays, in the present tense: what the
code tolerates, and what ends it. The test reads the tree alone,
and counts no release and no deploy. Name the piece the contract
keeps for the release before. While the head holds it, the step
that ends the contract has not landed, and it is in flight.
- A piece of the schema (a column, a default, a constraint, a
trigger, a function): the last up file that names it decides,
in the chain's order: each file after the one its wrapper's
`down_revision` names, never by the stamp its name begins with.
The head holds the piece unless that file drops it and does not
make it again. This lists the lines to read, and the command
below it prints each role's order:
`git grep -nwF '<its name>' -- 'om/migrations/sql/*.up.sql'`.
```bash
uv run --package acme-om python -c "from acme.om.storage.migrate import chain; from acme.om.storage.roles import DatabaseRole; [print(r.value, *chain(r)) for r in DatabaseRole]"
```
- Any other piece (a field of a response, a setting, a branch of
the code) is the code the comment sits on: the head holds it.
In doubt, it is in flight.
- Code whose piece that file dropped serves a release long gone. It
is listed in the report and left as it is, its comment with it:
the comment says why the code is there, and whoever removes the
code removes both. Its ADR changed in step 3, by this test, and is
not edited here.
- Only text changes: a comment, a docstring, a document, and the
`reason` of a checker exception in `pyproject.toml`, which is prose
the checker only repeats. A name in code is code, a test's name
included: it is the test's identity. So is a string a statement
carries: an assert's message, and a field's `description`, which
is the wire's document. A client reads it in the API document, so
it changes with the API, never with a compaction.
Commit.
7. **With `--migrations`, fold each chain.** Read `references/fold.md`
first, and follow it: the precondition and how each part of it is
read, the two dumps that prove the fold, what else a fold moves, and
its commit. When the precondition does not hold, the chain stays as
it is and the report says which part failed.
8. **Run the gates.** `make setup` first, which installs the tree and
formats its Python; commit what it changes, apart. Then `make
openapi`, which writes each namespace's API document and its two
schemas from the code, a docstring step 6 rewrote included; commit
what it changes, apart. Then `make check`. In the guideline's
repository, the scaffold pins the last release's checker, older than
the scaffold it checks, so `make check` runs the checkout's instead,
through the Makefile's override: `make check ARCH_CHECK="uv run
--no-project --python $(cat .python-version) python
$(git rev-parse --show-toplevel)/checkers/arch_check.py"`. When
`docker info` exits 0, also `make
infra-up`, and then `make migrate`, `make migrate-check`, and `make
test-integration` on a database the run makes and drops, never the
stack's own: the integration tests empty every table of the
database they run on.
```bash
uv run python ops/audit/auditdb.py create audit_docs_compact_<yyyymmdd>
uv run python -c "import os, subprocess, sys; sys.path.insert(0, 'ops/audit'); import auditdb; os.environ.update(auditdb.urls('audit_docs_compact_<yyyymmdd>')); sys.exit(subprocess.call(['make', 'migrate', 'migrate-check', 'test-integration']))"
uv run python ops/audit/auditdb.py drop audit_docs_compact_<yyyymmdd>
```
`<yyyymmdd>` is today's date in UTC. The name is chosen once,
before the run's first create: when `uv run python
ops/audit/auditdb.py list` shows it taken then, another run holds
it, so add a suffix (`_2`), and never drop a database this run did
not make. From its first create on, the name is this run's own. A
create that fails can leave the database behind: the targets do not
run then, and the drop still does. The second command runs the three
targets with the four `ACME_DATABASE_*` URLs set to that database,
which the Makefile takes over `.env`. The URLs live in that one
command: never export them in the shell, where the unit tests of
`make check` would read them. The drop runs whatever the create and
the targets answered, so a rerun finds the name free. When `docker
info` does not exit 0, the report names each of the four skipped.
A gate that fails is fixed in a commit of its own, and the gate runs
again from its first command: the first run plus at most 3 reruns,
then stop and say which gate fails and why.
9. **Write the report.**
## Bounds
- One pass: each document is rewritten once in a run. What a second
reading would change is a second run's.
- The gates, and the fold's proof, each take the first run plus at most
3 reruns.
- Its Next is the person's to run, never the session's: it pushes
nothing.
## What it never does
- Never drops a deviation's end condition: "until an operator task
needs a screen" is a trigger, not history.
- Never trims an expand and contract in flight, as step 6 tells one:
its comments, the column, the default, or the trigger it keeps stay
until the contract step lands. Its ADR is rewritten as any other is,
to the present tense and one date, and keeps every fact of the
contract: what the release before still reads or writes, the piece
that serves it, and the step that ends it.
- Never renumbers an ADR, and never gives a removed ADR's number to
another: a removed ADR leaves a gap.
- Never drops a reason that stops a plausible wrong change: it stays as
a near miss.
- Never changes a statement of code. A test changes only with
`--migrations`, and only one that pins a revision the fold removes,
tests a backfill it removes, or steps down from the head.
- Never opens `om/migrations/` without `--migrations`, and never edits
an applied migration file: a fold replaces the revisions it owns
whole, and leaves every file the render of its base holds as the
render gave it.
- Never edits or removes an ADR the render of its base holds.
- Never writes to a shared database or to an environment, and never
runs a test on the local stack's own database: its gates and its fold
run on databases it made on the local stack, and drops.
- Never pushes, never opens a pull request, never publishes or edits a
release. It moves no pin.
## Output
The work branch, one commit per category, and the report, under the
name step 1 gave it:
```markdown
# Acme: documents compacted to what holds
Branch <name> at <commit>, cut from <main> at <commit>. Migrations: <folded | not asked | kept: which part of the precondition failed>.
| category | removed | rewritten | kept |
|---|---|---|---|
| ADRs | | | |
| Changelog | | | |
| Table rows | | | |
| Comments and documents | | | |
| Migrations | | | |
## Removed
- <ADR NNNN, or a section, a row, a file>: <what held it, and where its live part went>
## Rewritten
- <file>: <what it says now, in a line>
## Kept
- <what, and why: a trigger, an expand and contract in flight, a near miss, a preparation still missing, a doubt, with the question>
## Code that serves a release long gone
- <file:line>: <what it does, and what would remove it>
## The fold (with `--migrations`)
| role | head id | parent | revisions folded | backfills left out | dump of the chain against the fold's |
|---|---|---|---|---|---|
The last commit before the fold: <sha>. A snapshot older than the fold is restored at that commit's release, which migrates it to the head, and then moves to the current one.
## Gates
- <gate>: <result, or skipped and why>
## Next
Push the branch and open the pull request: a person's to run, never the session's.
```