docs-compact · git:20261015.a329574 · 2026-10-15 · sha256 b78e79f399592ff6

docs-compact git:20261015.a329574A

Immutable. This exact content never changes and is served at /api/v1/blob/b78e79f399592ff6.

---
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, or when the branch
   holds a move of the tree's base: `git log --first-parent --format=%s
   origin/<main>..HEAD` lists a subject that starts `The scaffold base
   moves to`. Otherwise refuse. On a move's branch the skill's commits
   follow the move's, and the render of the base is the `scaffold`
   branch the move merged, which may not be pushed yet: wherever a step
   or `references/fold.md` names `origin/scaffold`, read `scaffold`.
   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. In a
     layer, the changelog is the one at its repository's root,
     `$(git rev-parse --show-toplevel)/CHANGELOG.md`, and step 4 cuts
     that 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.
   With a `scaffold` branch, a hit on a line the render of its base
   holds in that file stays as the render gave it, as the base's rows
   and ADRs do; this prints such a line:
   `git show origin/scaffold:./<file> | grep -F '<the line>'`. Only the
   tree's own lines are rewritten.
   - 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. Another checkout keeps the stack when
   `docker compose ls --format json` lists this tree's compose project
   (`COMPOSE_PROJECT_NAME` in `.env`, else the `name:` of
   `deployment/local/docker-compose.yml`) with its `ConfigFiles`
   outside this tree. Then `make infra-up` is skipped: from this tree
   it would recreate that stack under the same compose project.

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