Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/skills/docs-reader-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Run `scripts/check-plain.sh` on every changed page. It fails on the words in `re

Run `scripts/check-staccato.py` on every changed page. It flags paragraphs with three or more consecutive sentences under nine words, the usual signature of a page whose long sentences were split without keeping the connectives. Read each flagged paragraph aloud. Rejoin sentences that are cause and effect, contrast, or condition and result with "because", "so", "but", "while", or a colon, and leave apart the ones that are separate ideas. The script only finds the worst runs; a paragraph of eleven-word sentences that all land the same way is still staccato, so read the whole page for rhythm, not only the hits. Reference entries that are fragments by design (a `Payload:` line, a one-line table note) are not prose and do not count.

### 1c. Read for explanation, not statement

Read `references/explain-not-state.md` before fixing anything. It names the habit that a plain-language pass and a word budget both produce: true, short sentences that state a fact and never say what it means for the reader. The staccato check flags the mechanical signs (counting lead-ins like "Four things change it:", fragment openers like "One name is special."), but most of the work is the desk test in that file: read each paragraph aloud as if to a colleague, and add the sentence you would say out loud. A page may grow when it gains explanation. Word budgets are for repetition only.

### 2. Dispatch the reader

Hand the page to a fresh reviewer that has no memory of writing it, using `references/reader-persona.md` verbatim as its instructions. The reviewer reads the page once, top to bottom, with nothing else open, and reports:
Expand Down
18 changes: 18 additions & 0 deletions .claude/skills/docs-reader-review/references/banned-terms.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ These words come from the source code and the release notes. Readers do not have

Also banned as verbs and phrases: "unblocks", "surfaces", "carries", "wires up", "sits", "lives at" (write "is at"), "spells", "hands back", "rides on", "routes on", "keys on", "the shape" (unexplained), "story" (as in "the `DATABASE_URL` story").

## Migration vocabulary

Migrations invite movement metaphors: the database travels, the ref moves, the run goes forward. Readers do not picture a database moving. Say what changes, and what it ends up matching. The metaphor is fine where it carries meaning a literal sentence loses, such as a path through the migration graph, or "roll back", which is the name of the thing.

| Do not write | Write | Why |
| --- | --- | --- |
| `db migrate` moves your database from where it is to where you want it | `db migrate` applies the migrations you planned, until your database matches your contract | `db migrate` runs migration files. Only `db update` reconciles a database against the contract with no migration |
| moves the database to the contract state | applies migrations until the database matches the contract state | |
| move the database back to an earlier state | the database needs an earlier version of your contract | "roll back" stays: it is the name of the operation |
| the marker moves | `db migrate` updates the marker; the marker still records the last contract state applied | The marker is a record in the database, not a position |
| moves the `db` ref | points the `db` ref at that state; updates the `db` ref | A ref is a name for one contract state |
| the database is behind and has to catch up | the database is several contract states out of date | |
| it takes the shortest path to the contract state | it runs the fewest migrations that end at that state | Keep "path" where the page is about the graph itself |

`from` and `to` are field names in `migration.json`, so "starts `from`" and "ends at, `to`" are literal, not metaphors.

`scripts/check-plain.sh` enforces the database, marker and ref rows. "Database schema" is the ordinary term for the tables, columns and constraints, and stays allowed. It does not enforce "path", because the graph page uses that word correctly, or "the database is behind", because sample prompts quote a user's own words. The reader review judges those two.

## Sentence shapes to avoid

- A noun with three or more modifiers: "per-request cursor-enabled serverless facade".
Expand Down
51 changes: 51 additions & 0 deletions .claude/skills/docs-reader-review/references/explain-not-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Explain, do not state

## The problem

Pages written by an agent, and pages that went through word budgets, drift into prose that states facts without explaining them. Each sentence is true, short, and correct, and the reader still has to work out what it means for them. The habit shows up as:

- **Fragment openers.** A two-to-five word sentence that announces the next one: "One name is special." "Recompile." "Drift shows up in two ways." The announcement carries no information, and the sentence after it has to start over.
- **Counting lead-ins.** A list introduced by how many items it has instead of what the items are for: "Four things change it:", "Two things cause this:", "Three things make a failure easy to recover from:", "'Baseline' means three things:". The count is the writer's bookkeeping. The reader wants to know what kind of thing is coming and why it matters.
- **Mechanism before meaning.** The sentence says what the system does and never says what that means for the reader: "A ref called `db` is what `migration plan` starts from when you do not pass `--from`." True, and the reader still does not know what the `db` ref is for.
- **Facts side by side.** Two sentences that are cause and effect, or condition and result, sit next to each other with no "so", "because", "when", or "if" between them. The reader has to infer the link.
- **Lists doing the explaining.** Reasoning that belongs in a paragraph is chopped into bullets with bold labels, so the connective tissue is gone and each bullet is a spec line.
- **Sentences trimmed to the fact.** Every clause that would have said "which means", "in other words", or "you would notice this when" was cut to meet a budget.

Word budgets cause the last two directly, and make all the others worse. A budget is for cutting repetition, never for cutting explanation.

## The model

The `migration ref` page said:

> One name is special. A ref called `db` is what `migration plan` starts from when you do not pass `--from`, so keeping it current is what keeps plans incremental.
>
> Four things change it:

Will rewrote it as:

> The `db` ref has a special meaning in Prisma 8: it tells the system where you expect your local database to be. When you run a command like `prisma migrate`, if you don't explicitly say `--from`, Prisma will assume you mean from the `db` ref.
>
> The `db` ref will be automatically updated in the following situations:

What changed:

1. **Meaning first.** The first sentence says what the thing is for, from the reader's side: it records what you expect about your own database. The mechanism comes second.
2. **Conditions as scenarios.** "When you run X, if you don't say Y, Prisma will assume Z." The reader can picture themselves doing it. Compare "is what X starts from when you do not pass Y", which describes the system from outside.
3. **The lead-in says what the list is.** "will be automatically updated in the following situations" tells the reader what kind of items follow and what they have in common. The count is gone.
4. **Longer, and connected.** Every sentence has a "when", "if", or "so". Nothing is left for the reader to infer.
5. **Plain words at the moments that matter.** "has a special meaning", "tells the system", "will assume you mean". These are how a colleague talks, not how a spec reads.

## The rules

1. **Say what it means before you say how it works.** For every term, command, file, or flag a page introduces, the first sentence about it answers "what is this for, from my side of the screen?" The mechanism follows.
2. **Walk conditions as scenarios.** Write "When you run X without Y, Prisma ORM does Z", not "Z happens unless Y is passed to X". Put the reader in the sentence.
3. **Connect adjacent facts.** If two sentences are cause and effect, condition and result, or contrast, join them with "so", "because", "when", "if", "but", or "which means". If they are separate ideas, leave them apart, and check that the paragraph still says why they are next to each other.
4. **Introduce a list by what it is, not how long it is.** Never count the items in the lead-in. Say what the items have in common and why the reader is about to read them: "The `db` ref is updated automatically in the following situations:", "A failure is easy to recover from because:".
5. **No fragment openers.** Fold a two-to-five word announcing sentence into the sentence it announces, or cut it.
6. **Explain in prose, list to enumerate.** A bulleted list is for items of the same kind that the reader will scan. Reasoning, trade-offs, and "this happens because" go in paragraphs.
7. **Budgets cut repetition, not explanation.** A sentence that explains what something means may be 25 words. A page may grow when it gains explanation. A definition repeated three times is still cut.
8. **The desk test.** Read the paragraph aloud as if explaining it to a colleague at the next desk. Wherever you would say "basically, what this means is...", the page needs that sentence. Wherever you would never say the sentence aloud ("Four things change it."), rewrite it.

## What the checker catches

`scripts/check-staccato.py` flags runs of short sentences, counting lead-ins, and fragment openers. It cannot judge whether a sentence explains or only states. The reader review and the desk test do that.
4 changes: 2 additions & 2 deletions .claude/skills/docs-reader-review/scripts/check-plain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# inline code, link targets, pinned heading anchors, href attributes, the front-matter url line, and any line carrying the marker {/* plain-language:defined */}, which
# an author adds only when the sentence itself defines the term in plain words.
set -u
pat='\bterminal (calls?|methods?|verbs?)\b|\b(read|write|query) terminals?\b|\bcodecs?\b|\benvelopes?\b|\btype position\b|\brelease (line|cycle)\b|\bruns ahead\b|\b(sql|raw|orm) lanes?\b|\blane object\b|\bfacades?\b|\bjunction (model|record|table)\b|\bjoin model\b|\bquery plans?\b|\blower(ed|ing)\b|\bnamespace-qualified\b|\bnamespace coordinate\b|\breducers?\b|\brefinement callback\b|\bdotted codes?\b|\b(CLI|config) envelope\b|\bdev loop\b|\bbrownfield\b|\bgreenfield\b|\bdeterministic\b|\bidempotent\b|\btopology\b|\bcapabilit(y|ies)\b|\bbuffer(s|ed)? (the|every|all)\b|\bmaterializ(e|es|ed)\b|\bunblocks?\b|\bAPI surface\b|\bsurfaces? (the|a|an|as)\b|\bwires? up\b|\blives at\b|\bhands back\b|\brides on\b|\broutes on\b|\bkeys on\b'
pat='\bterminal (calls?|methods?|verbs?)\b|\b(read|write|query) terminals?\b|\bcodecs?\b|\benvelopes?\b|\btype position\b|\brelease (line|cycle)\b|\bruns ahead\b|\b(sql|raw|orm) lanes?\b|\blane object\b|\bfacades?\b|\bjunction (model|record|table)\b|\bjoin model\b|\bquery plans?\b|\blower(ed|ing)\b|\bnamespace-qualified\b|\bnamespace coordinate\b|\breducers?\b|\brefinement callback\b|\bdotted codes?\b|\b(CLI|config) envelope\b|\bdev loop\b|\bbrownfield\b|\bgreenfield\b|\bdeterministic\b|\bidempotent\b|\btopology\b|\bcapabilit(y|ies)\b|\bbuffer(s|ed)? (the|every|all)\b|\bmaterializ(e|es|ed)\b|\bunblocks?\b|\bAPI surface\b|\bsurfaces? (the|a|an|as)\b|\bwires? up\b|\blives at\b|\bhands back\b|\brides on\b|\broutes on\b|\bkeys on\b|\bmoves? (your|the|a) database (to|from) (the|an|a|where|one|its)\b|\bmoves? (your|the|a) database (to|from)[,.]|\bmoves? (your|the|a) database (back|forward)\b|\bmoves? the marker\b|\bmarker moves\b|\bmoves? (the|your) +refs?\b|\bspaces? moves? to\b|\bdatabases? (to )?catch(es)? up\b|\bwhere (the|a|your|my) database is\b'
status=0
for f in "$@"; do
hits=$(awk '
Expand All @@ -19,7 +19,7 @@ for f in "$@"; do
/plain-language:defined/ { next }
/^url: / { next }
{ print NR": "$0 }
' "$f" | sed -E 's/`[^`]*`//g; s/\]\([^)]*\)/]/g; s/\[#[^]]*\]//g; s/href="[^"]*"//g' | grep -E -i "$pat" || true)
' "$f" | sed -E 's/`(to|from)`/\1/g; s/`[^`]*`//g; s/\]\([^)]*\)/]/g; s/\[#[^]]*\]//g; s/href="[^"]*"//g' | grep -E -i "$pat" || true)
if [ -n "$hits" ]; then status=1; echo "== $f"; echo "$hits" | cut -c1-160; fi
done
if [ $status -eq 0 ]; then echo "plain-language check: clean"; else echo "plain-language check: banned terms found"; fi
Expand Down
27 changes: 22 additions & 5 deletions .claude/skills/docs-reader-review/scripts/check-staccato.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#!/usr/bin/env python3
"""Flags staccato prose: runs of short, one-clause sentences with no connective between them.
"""Flags staccato prose: runs of short, one-clause sentences with no connective between them,
counting lead-ins ("Four things change it:"), and fragment openers ("One name is special.").

Usage: check-staccato.py <file.mdx> [more files...] Exit 1 on any hit.

A hit is a paragraph containing three or more consecutive sentences that are each
under MAX_WORDS words. Fenced code, tables, headings, list items, front matter, and
A run hit is a paragraph containing three or more consecutive sentences that are each
under MAX_WORDS words. A lead-in hit is a paragraph ending in a colon that introduces its
list by counting the items. An opener hit is a paragraph whose first sentence has
OPENER_WORDS words or fewer and is followed by more sentences. See
references/explain-not-state.md for why these read badly and what to write instead. Fenced code, tables, headings, list items, front matter, and
import lines are skipped. Colons that introduce a code span or example are not
treated as sentence ends. The heuristic is deliberately simple: it finds the
paragraphs a human should read aloud, and the reader decides whether the sentences
Expand All @@ -17,6 +21,14 @@

MAX_WORDS = 9
RUN = 3
OPENER_WORDS = 5
COUNT = r"(one|two|three|four|five|six|seven|eight|nine|ten|several|a few|a couple of|\d+)"
COUNT_LEADIN = re.compile(
r"(\b" + COUNT + r" (things|commands|cases|reasons|situations|ways|fixes|steps|parts|rules|options|"
r"kinds|forms|labels|files|conditions|causes|places|questions|points)\b[^.:]*:$"
r"|\bmeans " + COUNT + r" things\b|^(here are|there are) " + COUNT + r"\b)",
re.I,
)

CODE = re.compile(r"`[^`]*`")
LINK = re.compile(r"\]\([^)]*\)")
Expand Down Expand Up @@ -75,8 +87,13 @@ def check(path):
run = run + 1 if is_short else 0
if run == RUN:
first = j - RUN + 1
hits.append((lineno, " | ".join(sents[first : j + 1])))
hits.append((lineno, "run: " + " | ".join(sents[first : j + 1])))
break
plain = LINK.sub("]", CODE.sub("CODE", para)).strip()
if COUNT_LEADIN.search(plain):
hits.append((lineno, "counting lead-in: " + plain))
if len(sents) > 1 and len(sents[0].split()) <= OPENER_WORDS and sents[0].endswith("."):
hits.append((lineno, "fragment opener: " + sents[0] + " | " + sents[1]))
return hits


Expand All @@ -89,7 +106,7 @@ def main(argv):
print(f"== {path}")
for lineno, snippet in hits:
print(f"{lineno}: {snippet[:180]}")
print("staccato check: clean" if status == 0 else "staccato check: clipped runs found, read them aloud")
print("staccato check: clean" if status == 0 else "staccato check: hits found, read them aloud (references/explain-not-state.md)")
return status


Expand Down
15 changes: 8 additions & 7 deletions apps/docs/content/docs/cli/migration-ref.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ Refs live on disk as `migrations/app/refs/<name>.json`, so they are versioned wi

## The db ref

One name is special. A ref called `db` is what [`migration plan`](/cli/migration-plan) starts from when you do not pass `--from`, so keeping it current is what keeps plans incremental.
The `db` ref has a special meaning: it records the contract state you expect your local database to match. When you run [`migration plan`](/cli/migration-plan) without `--from`, Prisma ORM assumes you mean from the `db` ref, so as long as the ref is kept up to date, each plan contains only your latest change.

Four things move it:
The `db` ref is updated automatically in the following situations:

- [`db init`](/cli/db-init) and [`db update`](/cli/db-update) advance it automatically when you run them without `--db` and let the connection come from `prisma.config.ts`. Passing `--db` suppresses that unless you also pass `--advance-ref db`.
- [`db sign`](/cli/db-sign) advances it after a successful signature, with or without `--db`. `--no-advance-ref` turns that off.
- [`db migrate --advance-ref db`](/cli/db-migrate) advances it after an apply. Plain `db migrate` never advances anything, which is deliberate: a deploy or CI apply should not move a repository ref.
- `migration ref set db <contract>` sets it by hand.
- [`db init`](/cli/db-init) and [`db update`](/cli/db-update) update it when you run them without `--db`, so that the connection comes from `prisma.config.ts`. With `--db`, they leave it alone unless you also pass `--advance-ref db`.
- [`db sign`](/cli/db-sign) updates it after a successful signature, with or without `--db`. `--no-advance-ref` turns that off.
- [`db migrate --advance-ref db`](/cli/db-migrate) updates it after an apply. Plain `db migrate` never touches it, on purpose: a deploy or CI run should not change a file in your repository.

You can also set it by hand with `migration ref set db <contract>`.

## Usage

Expand All @@ -45,4 +46,4 @@ npx prisma@latest migration status --db "$DATABASE_URL" --to production
npx prisma@latest db migrate --db "$DATABASE_URL" --to production
```

Use refs when environments need named targets instead of always applying to the latest migration on disk. Several commands can also move a ref forward for you after they succeed when you pass `--advance-ref <name>`.
Use refs when you want to name the contract state an environment should match, instead of always applying up to the latest migration on disk. If you pass `--advance-ref <name>` to a command that changes the database, that command also points the ref at the state it applied, once it succeeds.
Loading
Loading