# Tools — where the scripts are, and which ones can stop you ``` Status: Current Owner: _null Last reviewed: 2026-08-18 Governs: scripts/**, .githooks/**, package.json — the tooling, and which of it can stop you Review trigger: Any script added to, removed from or repurposed in scripts/; any change to which of them gates; any change to the npm scripts ``` > A signpost, deliberately. Every project that adopts this template has a > `docs/TOOLS.md`, so "read `docs/TOOLS.md` first" is an instruction that works > without knowing anything about the project — which is the whole reason this > file exists at a fixed path. ## The list is not here **[`architecture/README.md`](architecture/README.md)** holds the table of what ships in `scripts/` and what each one is. That is the one copy. A second table here would be two records of one fact, and the other one would never hear that a script was renamed — the failure `DOC_TRUST_MAP.md` exists to prevent, applied to the tooling instead of the documents. So this file answers the questions that table does not, and points at it for everything else. ## If this project has fewer scripts than the template lists That is the intended state, not a broken copy. The template's `scaffold.sh` writes the documents and deliberately leaves the scripts behind — *"an unconfigured `release.sh` landing in every new repository is a loaded gun, not a head start"* — so they are taken one at a time, having been read. This project took ten of them on 2026-08-18 and declined the rest. What it declined, and why, is in `docs/history/DEVELOPMENT_LOG.md` under that date. The short version: no `release.sh` or `deploy.py` until the roll-forward path to nebula is written down, and no `controls.sh` until there are backups for it to report on. ## Which ones can stop you Not in a table, because the honest answer lives in each script's own header and would go stale here. The rule that matters: **Exit code `2` is never a pass.** These scripts distinguish "the check ran and found nothing" from "the check did not run", because those look identical from the outside and only one of them is evidence. A CI step or a hook that treats a `2` as success has quietly turned the check off. Each script states its codes at the top; read them there. In this repository specifically, `check-env.sh` and `verify.sh` will both exit 2 if you gut their configuration, and `preflight.sh` exits 2 when the site is simply unreachable — which is the case you most want to tell apart from a pass. The hooks are the other place work gets stopped. See below. ## Where to start in a fresh clone ```bash npm install git config core.hooksPath .githooks # per clone. Not optional. See below bash scripts/check-env.sh --file .env # what is configured, before anything reads it bash scripts/secrets.sh --tracked # what is already committed ``` Then [`architecture/GUARDS.md`](architecture/GUARDS.md) before you write a check of your own — how to write one that can actually fail. ## The hooks Three, in `.githooks/`, because `.git/hooks` is not versioned and a hook living there protects exactly one clone. | Hook | What it runs here | | --- | --- | | `pre-commit` | `scripts/secrets.sh` on the staged diff, then `npm run build` when source is staged | | `commit-msg` | refuses a message with no conventional type | | `post-commit` | **pushes to `origin`** | `git config core.hooksPath .githooks` is per clone, so every checkout runs it once. An uninstalled hook fails silently, which is the same class of problem the hooks exist to prevent. **Two things worth knowing before you rely on them.** `pre-commit` is **not** the template's version. That one runs `npx tsc --noEmit` and `npx vitest run`; this project has neither TypeScript nor a test runner, so installing it unchanged would have refused every commit. It runs the secret scan — which is the reason the hook earns its place here at all, given the Zoho form tokens that reached four commits before anyone noticed — and then `npm run build` when `src/`, `server/`, `index.html`, `vite.config.js` or `package.json` is staged. **That is a build, not a test.** It catches a broken import and will not catch a broken behaviour. `post-commit` pushes, and that is the intent — but it has a consequence worth holding on to: whatever documentation was not in that commit is now behind the code by one push. That is the mechanical reason `docs/WORK_CYCLE.md` asks for doc edits in the same commit rather than in a tidy-up afterwards. With this hook installed, *"I will document it next commit"* means the site has already published the version without it. Escape hatches, both loud on purpose: `SKIP_GUARDS=1 git commit …` and `SKIP_PUSH=1 git commit …`. ## This project's npm scripts Run from the repository root. | Command | What it does | | --- | --- | | `npm install` | dependencies | | `npm run dev` | Vite and the Express API together, via `concurrently`. Frontend on 5173, API on 3001 | | `npm run build` | **three steps**: the client bundle, then an SSR bundle from `src/entry-server.jsx`, then `scripts/prerender.js`, which writes static HTML for every route. This is the only real gate this project has | | `npm run build:client` | the client bundle alone. Does **not** prerender — do not use it to produce a release | | `npm run preview` | serve the built client | | `npm start` / `npm run server` | the Express server alone, serving `dist/` | | `npm run docker:build` / `docker:run` | build and run the image locally | | `npm run docker:compose:up` / `:down` / `:logs` | the compose stack | | `npm run docker:push` | build, tag and push `queue-north-website:dev` to the Forgejo registry | | `npm run docker:test` | build the image and smoke-test it on 3001 | **There is no `npm test`, and that is not an omission in this table.** There is no test runner in the project. `docs/qa/ClaudeQACoverage.md` carries it as a standing gap. A liveness check by hand, when you want one without the script: ```bash curl -s https://qn.isnull.dev/api/health # {"status":"ok","db":"ok","timestamp":"…"} ``` ## Adding a script Put it in `scripts/`, give it a header saying what it does and **which incident motivated it**, state its exit codes, and add a row to `architecture/README.md`'s table — this file's `Review trigger` fires on exactly that. The bar, from the scripts that are already here: **done by hand three times, or once with a consequence.** A script written before either of those has no failure to describe in its header, which is the part that stops the next person deleting it.