Testing¶
Chalk is tested without Docker, without the Claude CLI and without spending anything: fakes stand in for every external program.
make check # shellcheck, convention lint, unit tests, end-to-end test with fakes
make test-db # the end-to-end test with SQL run against a real Postgres (FAKE_PG_URL)
scripts/ci-db-upgrade.sh # a real Postgres 16 -> 17 `chalk db upgrade` on Docker, on throwaway containers
The tests need bash 5.3 first on your PATH. Setting up, the rules for a
change and how to open a pull request are in
CONTRIBUTING.md.
make check needs bash 5.3 and ShellCheck 0.11.0 or newer (for bash 5.3
syntax). It runs:
| Step | What it checks |
|---|---|
shellcheck |
bin/chalk, lib/, the sandbox scripts, scripts/*.sh, the tests and the fakes |
scripts/lint-conventions.sh |
The bash style rules ShellCheck cannot see, marked (lint) there |
tests/unit/*_test.sh |
One module each |
tests/e2e.sh |
The whole workflow, against the fakes |
To run the same end-to-end test with every SQL statement executed against
a real Postgres (with pg_trgm; CI uses pgvector/pgvector:pg17, the image
Chalk runs):
Everything the test writes goes to a schema of its own, made afresh on
every run, so make test-db can be repeated against the same database.
scripts/ci-db-upgrade.sh does a real chalk db upgrade against Docker:
it sets up an install from before Postgres 17 with representative rows,
checks that an upgrade that fails (the new container cannot be created,
or a timeout interrupts it) puts the Postgres 16 container back
unchanged, then upgrades, compares every row and runs --cleanup. It uses
only containers and volumes of its own (named from CHALK_CI_DB_PREFIX,
chalk-ci by default), refuses the names a real install uses, and
removes them at the end unless CHALK_CI_DB_KEEP=1. The container,
volume and image names in lib/db.sh can be set from the environment for
this test only. CI runs it as the db-upgrade job.
make lint-sandbox parses the sandbox scripts with a bash 5.2
(SANDBOX_BASH, /usr/bin/bash by default); CI runs it on Ubuntu.
The fakes¶
tests/fakes/ is put first on PATH. Each fake logs its calls under
$FAKE_STATE, where tests check them.
| Fake | Stands in for | Knobs |
|---|---|---|
docker |
Containers are directories under $FAKE_STATE; exec runs the command on the host with container paths rewritten. chalk-db answers canned SQL results, or runs the SQL against FAKE_PG_URL |
FAKE_DB_MODEL=1 models database containers and volumes, for chalk db upgrade; FAKE_DB_BROKEN, FAKE_DB_RESTORE_FAIL, FAKE_SANDBOX_BASH="5 1" |
claude |
Reads the prompt, works out which Chalk prompt it is from its tags, and answers in that prompt's JSON shape. A loop ticks the next checkpoint and appends to work.txt |
FAKE_CLAUDE_MODE=break, churn (fails the same way on a changing tree), blocked or slow; FAKE_SPEC=fail; FAKE_REVIEW=fail-once or fail |
gh, glab |
Log their arguments | |
curl |
No test reaches the network. A fake decider at /v1/systemone, which checks each request against the protocol and answers from a script, and a fake chalk-embed at /v1/embeddings with fixed vectors. Anything else is logged and answered {} |
FAKE_DECIDER_ANSWERS, FAKE_DECIDER_DELAY, FAKE_DECIDER_DOWN, FAKE_DECIDER_STATUS, FAKE_DECIDER_PROTOCOL, FAKE_DECIDER_TOKEN, FAKE_EMBED_INDEX |
timeout |
Drops the time limit, since stock macOS has no timeout |
The fakes cannot catch a wrong CLI flag. Say in a pull request what you
tested against real Docker and the real claude CLI, and what you did
not.
tests/unit/decider_test.sh also runs the real share/decider/chalk-embed.py
with a stand-in model (CHALK_EMBED_FAKE=1, plain python3, no download)
and checks that it and the fake answer in the same shape. The real
strands-decider and models run only by hand, with chalk decider up.
Adding a test¶
- A module's behaviour: add a
checkto itstests/unit/<module>_test.sh. Tests sourcetests/unit/testlib.shand load modules withload core/log repo …. - A workflow: add a numbered case to
tests/e2e.sh. Each case creates a ticket withchalk new, runs commands with fake knobs set, and checks the branches, the logs under$FAKE_STATEand the run files under$XDG_STATE_HOME:
chalk new PROJ-30 Example >/dev/null
cd "$tmp/demo.worktrees/PROJ-30"
git add -A && git commit -q -m "spec"
if FAKE_CLAUDE_MODE=blocked chalk run > "$tmp/run30.log" 2>&1; then fail "should stop"; fi
check "a blocker goes to detention" grep -q 'needs the staging API key' "$tmp/run30.log"
cd "$tmp/demo"
A change to behaviour comes with a test in one of them.
The docs site¶
python3 -m venv .venv && .venv/bin/pip install -r docs/requirements.txt
.venv/bin/mkdocs serve # or: make docs (with mkdocs on PATH)
.venv/bin/mkdocs build --strict # what CI runs
scripts/check-links.sh # needs lychee; checks the built site/
The build fails on a broken internal link or anchor, and on a
configuration template that disagrees with lib/config.sh
(scripts/mkdocs_hooks.py). Pages under docs/designs/, docs/specs/
and docs/superpowers/ are working notes and are not published; link to
them by their full GitHub URL.
The pictures are made by scripts, so they can be made again after a change:
| Picture | Made by |
|---|---|
The demo recording, docs/assets/demo.{cast,txt,svg} and demo-poster.svg |
make demo: scripts/demo.sh runs the real chalk against the fakes and records it, then scripts/render-demo.py draws it |
The report card, docs/assets/report-card-{light,dark}.png |
scripts/screenshots.sh, from the sample in docs/assets/report-card.json (needs Chrome or Chromium) |
The site banner, docs/assets/banner-{light,dark}.svg |
scripts/render-banner.py (needs fonttools and brotli) |
The README art, docs/assets/run-banner.svg, run-diagram.svg and social-preview.png |
scripts/render-readme-art.py (needs fonttools and brotli; rsvg-convert for the PNG). Keep its transcript in step with the log format in lib/run.sh |