CI pipeline

A home-grown CI runner. No GitHub Actions, no CircleCI — just scripts/ci.sh.

Run

cd /var/www/krowforge
scripts/ci.sh           # full
scripts/ci.sh --quick   # subset (~37s)

The quick subset runs 22 jobs in ~37 seconds. The full pipeline adds slow jobs (full unit suite, browser smoke, lighthouse).

What's checked

JobWhat it does
py-syntaxCompile every .py with python -m compileall.
js-syntaxParse every .js with Node --check.
css-hex-auditFind raw hex colors not behind a CSS variable.
theme-contrastComputes contrast ratios for theme combinations; fails below threshold.
keydown-handlersStatic check for keydown listeners that don't call preventDefault correctly.
secrets-scanregex scan for committed secrets.
brief-presentbuild_brief.md must exist and not have shrunk.
app-importimport app must succeed; prints registered route count.
unit-testspytest -q.
usability-walkthroughAsserts the Tier-3 walkthrough recipe is present in the brief.
xterm-frontendAsserts PTY default-on is wired correctly.
_and more_

Each job reports its latency. Total under 60s for the full pipeline.

Pre-push hook

Install once:

scripts/install_git_hooks.sh

Adds a pre-push hook that runs ci.sh --quick. Push fails if any job fails.

To bypass (don't): git push --no-verify. Reviewable in the receipt log.

Adding a job

ci/jobs.py defines each job as a function returning a JobResult. To add:

def job_my_check():
    # do something
    return JobResult(name="my-check", ok=True, message="…", elapsed=0.1)

Register in JOBS list. Pick --quick membership (cheap fast jobs only).

CI in CI

For people running KrowForge on remote CI (GitHub Actions, etc.), scripts/ci.sh is designed to run cleanly in any Linux environment with Python + Node available. Set NO_COLOR=1 to disable ANSI codes in logs.

Output format

Plain text by default. --json for machine-readable. Each job line:

[ ✓ ] py-syntax            0.42s
[ ✓ ] js-syntax            0.18s
[ ✗ ] secrets-scan         0.05s   2 findings — see report.json
…
PASS 21/22 in 37.69s

Exit code: 0 if all green, 1 otherwise.