# Working on this repo with Claude CI runs on **Gitea Actions** at `git.wallacenet.ca/devadmin/ProLibertateGames` (workflows in `.github/workflows/`), publishing to Cloudflare Pages (`plg.wallacenet.ca`). The workflow below was measured on the dev machine (`~/ProLibertateGames`); it replaces the earlier GitHub-sandbox notes, which no longer apply. ## Build, test and lint locally Gradle works on the dev machine. Two environment variables are required: the default JDK is 25, which Gradle 8.14 cannot use, and the Android SDK lives in the home directory. ``` export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 export ANDROID_HOME=$HOME/android-sdk # platform 34, build-tools 34.0.0 ./gradlew testDebugUnitTest lintDebug assembleDebug ``` The Katan tests run in seconds; a clean build takes a few minutes. Run the full suite, lint and `assembleDebug` before pushing: they are what CI runs. There is no KVM, so no emulator; the engine and the UI logic are covered by unit tests (see `docs/UI-catan.md`). ## Shipping: branch, PR, CI, merge 1. Branch from `dev`; before pushing, merge `origin/dev` in (it moves between sessions). 2. Plain `git push` has no credentials. Use the Gitea token as a one-off header: `git -c "http.extraheader=Authorization: token $(cat ~/.config/gitea/token)" push -u origin `. 3. Open the PR with `POST /api/v1/repos/devadmin/ProLibertateGames/pulls` (same token, `Authorization: token ...`). CI (`ci.yml`) runs on the PR: build, unit tests and Android lint; it publishes nothing. Poll `/actions/runs` until it completes (about 7 minutes). 4. Merge with `POST .../pulls/N/merge {"Do":"merge"}`. The push to `dev` runs `ci.yml` again and publishes the build (about 9 minutes). ## Merging is a shipping action Both long-lived branches publish, so a merge is a release, not just an integration: - **`dev`** publishes a `v.N-dev` build to `https://plg.wallacenet.ca/dev/latest.json`, offered by the in-app updater to anyone on the Dev channel. - **`main`** runs `release.yml` and publishes a **production** release for everyone on the Production channel. Confirm before merging to `main`. `release.yml` runs `assembleDebug` only (no tests, no lint), so a `main` release is only as verified as the `dev` build it replays. ## Katan The island game is catalogue id `catan`, shown as **Katan**. It has two rule sets chosen at setup: the base game (`docs/RULES-catan.md`) and Cities & Knights (`docs/RULES-catan-ck.md`, code in `game/catan/CatanCk*.kt`, state behind `CatanState.ck`, which is null in the base game). The screen is layered; see `docs/UI-catan.md` before changing it. ## Bash output is filtered before it reaches the model `.claude/settings.json` registers a `PreToolUse` hook on `Bash` that rewrites each command through [rtk](https://github.com/rtk-ai/rtk): `git status` runs as `rtk git status`, and rtk compresses the output. Write commands normally — the rewrite is transparent and costs nothing to invoke. Measured on this repo rather than quoted from rtk's README: | Command | Raw | Filtered | Saved | | --- | --- | --- | --- | | `git diff HEAD~3` | 521 KB | 28 KB | 95% | | `grep -rn "fun " app/src/main` | 117 KB | 19 KB | 84% | | `find . -name "*.kt"` | 7.7 KB | 1.7 KB | 79% | | `git log -20` | 20 KB | 5.3 KB | 74% | | `git diff --stat` | 4.0 KB | 4.0 KB | 1% | | `ls` of a single package | 50 B | 62 B | −24% | The saving is all in the large outputs, which is the point — a full diff of a few commits is otherwise most of a context window. Already-compact output grows slightly, because rtk adds a header; at those sizes it does not matter. `rtk gain` reports what the current session has actually saved. Two limits are worth knowing. The `Read`, `Grep` and `Glob` tools are not `Bash`, so they never reach the hook and are unaffected; a deliberate `rg` or `find` in a shell *is* filtered. And filtering never truncates a file: `cat` becomes `rtk read`, which was checked byte-for-byte against this repo's longest Kotlin source (`GameSetupScreen.kt`, 946 lines) and came back identical. Commit messages survive the rewrite intact, quotes and colons included. To bypass filtering for one command, run `rtk proxy `. ## rtk is reinstalled at the start of every session The container starts empty, so `.claude/hooks/session-start.sh` installs rtk before the session does anything. It pins `RTK_VERSION`, because the installer otherwise asks `api.github.com` for the latest tag and the proxy answers 403 for every repository except this one — the lookup fails and takes the install with it. Bump that variable by hand. The hook is written not to matter when it fails. Outside the sandbox it installs nothing and just prints the command to run, since dropping a binary into someone's `~/.local/bin` uninvited is not its business; if the download fails it says so and exits clean. With rtk absent the rewrite hook no-ops, and the only consequence is verbose output.