cyberine
UsageRun recipes

Sweep stamp and pre-push hook — .githooks/pre-push (2026-09-07, gate removed 2026-09-30)

Operator decision: CI catches code; real tests run on the developer machine, which has every CLI and key. A full npm run sweep (no --only / --security) records its verdict in .local/selftest-stamp/<host>/<HEAD sha>.json. From 2026-09-07 the tracked pre-push hook refused a push to main whose commit had no ok: true stamp from THIS host; on 2026-09-30 the operator removed that check because the sweep made every push wait ~15 minutes. The sweep now runs on the operator's word through /cyberine-project-selftest, and the stamp is the record of what was swept.

git commit ...                 # the stamp is keyed by the COMMITTED sha
npm run sweep                  # ... sweep stamp: .local/selftest-stamp/<host>/<sha>.json (ok -- ...)
node scripts/sweep-stamp.mjs host                           # the host token the stamp is keyed by
node scripts/sweep-stamp.mjs verify "$(git rev-parse HEAD)"  # was this sha swept clean on this host (the skill's --sha-check)
git push origin main           # the hook checks only the skip-ci token now

The stamp carries sha, host, platform, at, the PASS/SKIP/FAIL counts, unexpectedSkips, staleExpectations, dirty and ok. ok is true only for a clean pass on a clean tree (git status --porcelain --untracked-files=no empty): a dirty tree still gets a stamp, marked ok: false, so the reason is visible rather than silently absent.

The stamp is bound to the machine that swept it (kr0q2, 2026-09-16). .local/ is rsync-mirrored between machines, so a stamp arriving from another machine would otherwise let this one push a SHA it never swept. host is os.hostname() lowercased with anything outside [a-z0-9._-] replaced by -; sweep-stamp.mjs verify refuses, naming the host involved, a stamp that exists only under another host's directory, a legacy flat .local/selftest-stamp/<sha>.json from before this change, a stamp in this host's directory whose recorded host is another machine's or missing, and a stamp whose recorded sha differs from its filename. Guarded by npm run selftest:stamp-host-binding, which drives sweep-stamp.mjs verify in a throwaway repo and checks that the hook no longer reads the stamp.

The hook is wired by npm run prepare (git config core.hooksPath .githooks), which npm ci / npm install run automatically; a fresh clone gets it on first install. CI runs npm ci too and the setting is harmless there (nothing pushes from a runner).

The same hook guards against a silently skipped run: GitHub scans the WHOLE message of the push's head commit for skip tokens, body included, and creates no run at all when it finds one. Commit cfa184c (2026-09-07) quoted the token in its body while documenting this very escape hatch and produced zero runs. The hook now refuses a push to main whose head commit carries a token while the push changes a path a gate reads (src/, scripts/, site/, docs/, .github/, package*.json, USAGE.md, CLAUDE.md, .claude/rules/push-gates.md); a token on a doc-only commit still passes. Positive-controlled against the real commits: cfa184c refused, the doc-only a1f0d57 accepted.

Known limitations and deferred work: with the gate gone, a push to main can land code no sweep has run on; CI still runs typecheck, lint and the static drift smokes, and a runtime regression surfaces at the next sweep the operator asks for. Nothing verifies that the sweep ran on THIS machine's real environment rather than a stripped one — the stamp records the platform and host, not the installed CLI set. The host binding trusts os.hostname(): two machines sharing a hostname share a stamp directory, and a machine whose hostname changes (renamed, or a network-assigned name) must re-sweep before it can push. Stamps written before 2026-09-16 sit flat in .local/selftest-stamp/ and are never accepted again; nothing deletes them.