Queue-North-Website/docs/TOOLS.md

13 KiB

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

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
node scripts/validate-content.js        # whether the copy in src/data is publishable
node scripts/audit-html.js              # what the built pages actually say

npm run verify runs that scan too, as guard 20-secrets, together with --built dist/ over the bundle the build just produced. A line that has to show a credential shape, such as a usage example, carries secrets-ok: and a reason on the same line; that excuses the one line, visibly.

Then architecture/GUARDS.md before you write a check of your own — how to write one that can actually fail.

The hooks

Four, 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
pre-push refuses a push that leaves uncommitted or staged edits behind

pre-push is not the template's — it is the useful half of a hook that was already sitting in this checkout's .git/hooks before adoption. Setting core.hooksPath would have silently stopped that one running, which is exactly the failure this directory exists to prevent, so it was moved here instead. Its third check went: it refused whenever the branch was ahead of its remote, which is the precondition for pushing at all, so it fired on every real push and told you to re-run with --no-verify. A guard that can never pass teaches people to bypass the ones beside it.

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.

It builds the working tree, not the commit, so it says so when they differ, counting untracked files as well as unstaged edits. An untracked module that the staged code imports makes the build pass and the commit itself unbuildable, and docker build would ship the file anyway while the tag could not rebuild it. npm run release refuses outright while untracked files are present, for the same reason.

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, and it refuses rather than emit a wrong page: a render that throws, a Suspense fallback, or a template that already carries a canonical each fail the build, naming the route
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 release publish an image. Bump, guards, build, verify its version label, push :vX.Y.Z, commit last, tag. -- --dry-run first
npm run deploy move the running stack to a published numbered version — the newest by default, -- --tag vX.Y.Z to pin or roll back. Backs up first, and refuses a floating tag. -- --dry-run first

| 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 — and it is why npm run release says out loud what its gate actually is: a build that validates the content layer, an audit of the built HTML, a secret scan of the tree and the bundle, and a doc-header check. None of those exercises a form, an API response, or a page in a browser.

release and deploy are two commands on purpose. Publishing an image and running it are separate decisions; see docs/OPERATIONS.md. A deploy recreates the container and takes both public front doors down together.

A liveness check by hand, when you want one without the script:

curl -s https://queuenorth.com/api/health    # production
curl -s https://qn.isnull.dev/api/health     # same container, other ingress
# both -> {"status":"ok","db":"ok","timestamp":"…"}

If those two disagree, the container is fine and the problem is in front of it. docs/OPERATIONS.md has the topology.

Three checks that are run by hand

None of them runs in verify.sh. The first is not adopted into scripts/ at all; the other two are, and the reason they still do not gate is below.

doc-claims.sh — every path a document names must exist. Run from the template, and exclude docs/history/:

T=~/.openclaw/Projects/Template
DOC_CLAIMS_EXCLUDE='docs/proposed/|project-template/|vendor/|docs/history/' \
  bash $T/docs/architecture/scripts/doc-claims.sh docs/ README.md

That exclusion is not a way of quietening a failure. docs/history/DEVELOPMENT_LOG.md is dated and append-only, and its entries name files that existed when they were written — src/pages/8x8.jsx, removed at 0.6.6; src/App.css, gone in a later refactor. Both are named without backticks here for the same reason the exclusion exists: a backticked path is a claim the file is present. Those are receipts, not claims about now, and correcting them would rewrite the record of what was known at the time, which is the one thing that file is for. Checking a history log for present-tense accuracy is a category error, so it is excluded rather than edited.

Without the exclusion it reports three findings in that file, every time, forever. Last run 2026-08-18: 240 claimed paths, all present, across 20 files.

qa-browser.mjs is in scripts/ but not in verify.sh. It needs playwright, which is installed globally on this machine rather than being a dependency of the project — adding ~300MB of browser to every clone for a check nobody runs per-commit is the wrong trade. Reach for it when a UI defect is filed, and before writing one:

node scripts/qa-browser.mjs                                    # production
node scripts/qa-browser.mjs --url http://localhost:3099        # a local build
node scripts/qa-browser.mjs --paths /contact --viewports 320

Exit 2 means playwright was missing or the site was unreachable — nothing was checked, which is not a pass.

device-sweep.mjs renders every page on ten emulated phones and tablets. Same playwright trade as above, plus it needs something already serving the build, so it is a tool you reach for rather than a gate.

npm run preview &                                              # serves dist/ on 3001
node scripts/device-sweep.mjs                                  # all ten devices
node scripts/device-sweep.mjs --devices "iPad Mini"            # one, while iterating
node scripts/device-sweep.mjs --url https://queuenorth.com --report /tmp/sweep.md

It measures each box against its nearest clipping ancestor rather than document.scrollWidth, and that distinction is the point: this site's body carries overflow-x: hidden, so a page can slice content off its right edge and still report a scrollWidth equal to the viewport. Eight kinds of finding, of which clipped, past_viewport and document_scrolls are blocking. Exit 0 clean, 1 findings, 2 nothing swept.

It exists because #214, the header CTA clipped at iPad portrait, was fixed, checked in a desktop window sized to 768, and released still broken. A desktop window at 768 has a scrollbar, so the layout viewport was ~753px and the md breakpoint the fix was about never engaged. A device profile has no scrollbar inset, so 768 means 768. The engine is a copy of the Privacy LLC site's scripts/css-qc.mjs; its provenance and the ways this driver differs are in the header of scripts/lib/css-audit.js.

prove-guard.sh is deliberately absent. It breaks what a guard protects and requires the guard to go red. This project has four guards, all shell scripts that fail visibly, so §1 of architecture/GUARDS.md is performed by hand instead: see docs/history/DEVELOPMENT_LOG.md for 2026-08-18 and 2026-09-10. The four are 10-build (which also runs the content check and the route-drift check inside the prerenderer), 15-built-html, 20-secrets (tracked tree and bundle) and 30-doc-headers.

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.