198 lines
9.5 KiB
Markdown
198 lines
9.5 KiB
Markdown
# 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
|
|
|
|
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.
|
|
|
|
`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 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 that its gate is a
|
|
build, a secret scan and a doc-header check rather than pretending those are
|
|
tests.
|
|
|
|
**`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:
|
|
|
|
```bash
|
|
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.
|
|
|
|
## Two checks that are run by hand
|
|
|
|
Neither is adopted into `scripts/`, so neither runs in `verify.sh`. Both are
|
|
worth running when the documents change a lot.
|
|
|
|
**`doc-claims.sh` — every path a document names must exist.** Run from the
|
|
template, and **exclude `docs/history/`**:
|
|
|
|
```bash
|
|
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.**
|
|
|
|
**`prove-guard.sh` is deliberately absent.** It breaks what a guard protects and
|
|
requires the guard to go red. This project has three guards, all shell scripts
|
|
that fail visibly, so §1 of `architecture/GUARDS.md` was performed by hand
|
|
instead — see `docs/history/DEVELOPMENT_LOG.md` for 2026-08-18.
|
|
|
|
## 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.
|