The things this repo runs by hand
What each one is for, when you run it, who else calls it, and what it must never do.
The table#
Three of these run in CI on every push, ci-citations.mjs, build-do-nots.mjs
(as a regenerate-and-diff) and check-notebook.mjs (advisory without a
cross-repo token), so a docs commit that skips the first two fails there.
dev-safe runs the guard for you. The rest you run when the row says. Every
file in the directory has a row here; a script without one is a defect in
this page, not a private tool.
| Script | What it does | Run it when | It must never |
|---|---|---|---|
build-do-nots.mjs | Regenerates docs/DO-NOTS.md from every contract doc's Banned combinations section, the design canon, and the app-patterns recipes | after editing any of those three sources | be hand-edited around; the page has no opinions of its own |
check-citations.mjs | Walks every file.ext:N citation in the docs and reports BROKEN, OUT-OF-RANGE, and ROT-SUSPECT | after any canon or doc change; the docs-wide baseline lives in citations-baseline.json | be read as pass or fail on its own; ci-citations.mjs is the gate |
ci-citations.mjs | The citation ratchet CI runs: docs-wide BROKEN and OUT-OF-RANGE may not rise above citations-baseline.json, and the canon, contract docs and breakdowns must carry zero BROKEN | in CI on every push, and locally before a docs commit | be silenced by raising the baseline; lower it when the count drops |
check-notebook.mjs | Keeps the canon curator honest: the notebook's last read: line may not fall more than 14 days behind the newest CANON-NOTES.md entry in a joined repo, read over gh api | in CI on every push (advisory until a cross-repo read token is a repo secret), and by hand before a curator read | pass silently on a repo it cannot reach; an unreachable repo is reported |
check-prop-reach.mjs with prop-reach-baseline.json | The prop-reach ledger: every prop a kit component declares should reach a rendered difference a test can see; counts unexercised props and exits 1 when the count rises above the baseline | after adding a prop or a test to the kit; --list prints every unexercised prop | be read as proof a prop works; it checks presence in a rendering test, not the rendered effect |
kit-inventory.mjs | One inventory of what the kit provides, derived from the showcase registry, the contract docs and the do-nots page: writes packages/ui/docs/KIT-INVENTORY.md and llms.txt, answers --find <term>, and --check exits 1 when the written inventory drifts from the registry | after any registry change; --find any time "does the kit already have X" comes up | be edited by hand around; the inventory has no opinions of its own, the reader prompt (packages/ui/prompts/claims.md) is the judging half |
sync-method-docs.mjs | Copies the design canon, the app-patterns recipes and the do-nots page into packages/ui/method/ so they ship in the package; --check exits 1 when a copy drifts from its source or when a doc in a published tree is missing from mkdocs.yml's nav | after any canon or recipe edit, and before a docs commit (the nav half is the check CONTRIBUTING section 7 names) | be run in CI; the nav check is a by-hand check by owner ruling (D7a, 2026-09-03) |
dev-guard.mjs with dev-guard.md | Runs the playground dev server and recycles it when it is over a memory ceiling and idle | only when a human asks for it (npm run dev-safe) | be started by an agent on its own |
push-triad.sh | Pushes the three Versable repos in one approved run, refusing any repo it cannot account for | when the owner wants all three mains pushed at once | force anything; a repo that fails a check is skipped |
check-export-drift.mjs | Compares the kit barrel's exports against the published tarball of the same version and fails on a difference (two artifacts under one version string, the 0.2.0 lesson) | BY HAND before a bump, with a read token. It ran in publish-kit's drift job until that job was removed on 2026-08-24 (23fb48c); publish-kit.yml now has only check and publish. Do not re-wire it into CI without the owner asking | pass on an auth failure; no registry read means no verdict (exit 2) |
ship-kit.sh | Pushes main and watches the publish-kit workflow until the bumped kit version is on GitHub Packages; refuses when the version is already published, the tree under packages/ is dirty, or nothing is ahead of origin | after the bump commit, the owner's one release command (--check rehearses without pushing) | publish anything itself; the workflow does |
release-kit.sh | Bumps and publishes @versable-git/ui to GitHub Packages from a laptop | superseded; the publish-kit workflow publishes on push to main | be used to publish; a hand publish races the workflow |
Each one, from its own header#
build-do-nots.mjs#
node scripts/build-do-nots.mjs
Builds docs/DO-NOTS.md by re-reading every Banned combinations section, the
design canon, and the app-patterns recipes, "so the page can only go stale by
its sources going stale, never by a fourth copy nobody updated"
(build-do-nots.mjs:3-6). Every line on that page cites the source it came
from. Named in CLAUDE.md as the step after editing a Banned section, and in
CONTRIBUTING.md section 7.
check-citations.mjs#
node scripts/check-citations.mjs [--verbose] [docs-dir ...]
Walks every file.ext:N[-M] citation in the doc corpus and reports what a
compiler never will: BROKEN (file missing), OUT-OF-RANGE (line past EOF),
ROT-SUSPECT (file committed after its citing doc; --verbose lists them).
Exits 1 on the first two only. Ambiguous bare basenames are listed, never
guessed (check-citations.mjs:3-8). Deliberately not wired into CI: it is a
reading aid for the person changing a doc, and the canon rule is that every
canon change re-runs it (CLAUDE.md, "Canon edits stay one pen"). CONTRIBUTING.md
section 3 explains the scan set and why the number does not have to be zero.
check-notebook.mjs#
node scripts/check-notebook.mjs
The canon feedback loop has a curator who reads every joined repo's
CANON-NOTES.md; docs/canon/NOTEBOOK.md carries a last read: line, and
this script fails when that line is more than 14 days behind the newest note
in any joined repo (check-notebook.mjs:2-5). It reads the repos over gh api,
so on a token that cannot see a private sibling it reports the repo as
unreachable rather than passing; CI runs it with the default token, which is
why it is advisory there until a cross-repo read token is a repo secret
(docs/plan/71-canon-feedback-loop-plan.md, Q1).
check-prop-reach.mjs and prop-reach-baseline.json#
node scripts/check-prop-reach.mjs [--list]
Every prop a component declares should reach a rendered difference a test can
see, because a declared prop that silently does nothing blames the caller
forever (check-prop-reach.mjs:3-7). This v0 counts a prop as exercised when
some rendering test passes it; it does not prove the test asserts the effect,
and it does not see props inherited through extends. The unexercised count
is a ratchet against prop-reach-baseline.json: lower the baseline as tests
land, and the script exits 1 when the count rises. In-repo only.
kit-inventory.mjs#
node scripts/kit-inventory.mjs [--find <term> [--json] | --check]
"Does the kit already have X" as a command instead of a multi-grep that picks
the wrong surface (kit-inventory.mjs:3-7). Derives from three sources it
never edits (the showcase registry, the contract docs, the generated do-nots
page), writes packages/ui/docs/KIT-INVENTORY.md and llms.txt, and answers
--find with ranked matches across every surface. --check exits 1 when the
written inventory drifts from the registry. No deps, no network, no model
call; the judging half is packages/ui/prompts/claims.md. The same script
ships in the package as the kit-inventory bin, where it answers against the
consumer's own resolved tree.
sync-method-docs.mjs#
node scripts/sync-method-docs.mjs [--check]
The package carries its method, not only its exports: verbatim copies of
docs/design-language, docs/app-patterns and docs/DO-NOTS.md under
packages/ui/method/, so a consumer's install says how to build with the kit.
The repo copies are the single pen and this script is the copies' one writer.
--check exits 1 on a drifted copy and, since 2026-09-03, on any doc in the
four published trees (canon, app patterns, breakdowns, kit contracts) that
mkdocs.yml's nav does not name, because the in-app browser sweeps
directories and the published site reads a hand-kept list, and the list had
fallen seventeen docs behind. By hand before a docs commit; not a CI gate by
owner ruling.
dev-guard.mjs and dev-guard.md#
npm run dev-safe (in apps/playground)
The Next dev server's memory grows with every route visited and never lets go.
The guard owns npm run dev and recycles it only when it is both over a memory
ceiling and idle, so the recompile lands while you are away from the keyboard,
never mid-edit (dev-guard.md:16-19). Tuning knobs are env vars,
DEV_GUARD_MAX_MB, DEV_GUARD_MAX_PCT, DEV_GUARD_IDLE_CPU, DEV_GUARD_IDLE_N,
DEV_GUARD_MIN_UP, DEV_GUARD_INTERVAL, all documented in dev-guard.md.
Opt-in and local: agents do not start it on their own; they may offer it when
someone hits dev-server memory pain (dev-guard.md:5).
push-triad.sh#
scripts/push-triad.sh [--dry-run]
Pushes versable-builder, walmart-mvp, and speedway in one run, "written so
a human approves once instead of three times" (push-triad.sh:4). It refuses to
push anything it cannot account for: every repo must be on main, clean of
staged work, and tracking a remote that has not moved underneath it. A repo that
fails a check is skipped, never forced, and the script still tries the others.
--dry-run shows what would go and pushes nothing. The repo paths are fixed at
the top of the file for this machine.
release-kit.sh (superseded)#
Not the release path. The kit, the toolkit and qsync publish from the
publish-kit workflow on a version bump pushed to main (packages/ui/README.md,
Releasing); a hand publish races it and the workflow then skips. This script
is the pre-workflow laptop publish, kept only as the fallback if the workflow
is down; --dry-run is safe to read, nothing else in it should run.
Adding a script#
A script here carries a header comment that says what it is for in a sentence a
human can read, a Usage: line, and any env knobs it reads; then it gets a row
in the table above and a section below it. If something else calls it (a
package.json script, a workflow, a rule in CLAUDE.md), the section names the
caller. Zero em dashes, the same as every file in this repo.
Related#
CONTRIBUTING.mdsection 3, running things, and section 7, docs.CLAUDE.md, the prose rules and the release rule these scripts serve.README.md, the repo explained for a human reader: who reads what, the map by what you came to do.