Bootstrap Pilgrims of Katan: rebrand, Gitea CI and OTA
Build and Release APK / Build APK and publish release (push) Canceled after 0s
Build and Release APK / Build APK and publish release (push) Canceled after 0s
Import of ProLibertateGames (upstream main, GPL-3.0) rebranded for Wallacenet and repointed at git.wallacenet.ca. - Identity: applicationId ca.wallacenet.pilgrimsofkatan (+ .dev for the dev channel), launcher names "Pilgrims of Katan" / "Pilgrims of Katan DEV", neutral hexagon branding replacing the Wallace tartan. Uncial Antiqua keeps its OFL licence, and the GPL-3.0 LICENSE plus upstream attribution stay. - OTA: Updater.kt now reads this repo's own Gitea releases (/api/v1/repos/devadmin/PilgrimsOfKatan/releases...) and BuildConfig. OTA_ENABLED is on; Gitea answers the same JSON shape the code already parsed. - Build numbers: versionCode = CI run number + plgVersionRunOffset (1000), with the tag and versionName derived from the same figure. The offset is permanent, because Gitea Actions restarts run numbers at 1 and a reset would otherwise make Android reject the next build as a downgrade. - CI: ci.yml publishes a dev prerelease from dev, release.yml a production release from main, both through scripts/publish-release.sh (Gitea REST API, runnable by hand until an Actions runner is registered). - Docs: docs/CI.md records the channel, numbering and runner decisions; docs/README.md seeds the per-game RULES-* conventions; CLAUDE.md carries the agent ground rules. Verified: ./gradlew assembleDebug and testDebugUnitTest green on JDK 17 / compileSdk 34, both variants stamped, installing side by side under the new identity. Co-Authored-By: Paperclip <[email protected]>
This commit is contained in:
co-authored by
Paperclip
parent
d27c2db894
commit
fad4778ad8
@@ -1,114 +1,93 @@
|
||||
# Working on this repo with Claude
|
||||
# Working on this repo (agent notes)
|
||||
|
||||
## Nothing a session does triggers CI. Dispatch it explicitly.
|
||||
Pilgrims of Katan — an Android collection of card and board games, playable
|
||||
against AI or over Wi-Fi (LAN). Bootstrapped from
|
||||
[adrianoftyriel/ProLibertateGames](https://github.com/adrianoftyriel/ProLibertateGames)
|
||||
(GPL-3.0, kept as the `upstream` remote) for Wallacenet. Canonical repo:
|
||||
<https://git.wallacenet.ca/devadmin/PilgrimsOfKatan>.
|
||||
|
||||
GitHub creates **no workflow run** for anything this sandbox does — pushing a
|
||||
branch, opening a pull request, or merging one. All of it goes through the same
|
||||
integration, and GitHub suppresses event-triggered runs from it. The pull
|
||||
request will say it was opened by the repo owner; that is only how the UI
|
||||
attributes it, and it does not change the suppression.
|
||||
## Ground rules
|
||||
|
||||
This was measured, not assumed:
|
||||
- **Licences travel.** `LICENSE` is GPL-3.0 and must stay. The Uncial Antiqua
|
||||
font ships with its OFL licence at `docs/licenses/UncialAntiqua-OFL.txt`;
|
||||
never delete or edit either. Attribution to the upstream lives in the README.
|
||||
- **No Catan trademarks or brand assets**, and none of the upstream's Wallace
|
||||
clan branding (it was removed in the rebrand; see README → Branding). The
|
||||
brand is: *Pilgrims of Katan*, `ca.wallacenet.pilgrimsofkatan` (+ `.dev`),
|
||||
slate blue / dev green, hexagon emblem.
|
||||
- **The existing catalogue is the regression safety net.** Keep every game
|
||||
green; new games (Catan) arrive as separate tasks, not edits to old ones.
|
||||
All 22 games are `available = true`.
|
||||
- **Docs conventions**: every game gets `docs/RULES-<id>.md` (index and gaps in
|
||||
`docs/README.md`); build/CI/release decisions go in `docs/CI.md`. Update the
|
||||
docs when behaviour changes — they are the check against the rules code.
|
||||
|
||||
| Action | Result |
|
||||
| --- | --- |
|
||||
| Human pushes a Claude-authored commit (`d165e5ef`, `469c900e`) | CI runs |
|
||||
| Session pushes `dev` / `main` (`43394b8`, `742add4`) | no run |
|
||||
| Session opens a PR against `dev` (#1) | no run |
|
||||
| Session calls `workflow_dispatch` | **runs** |
|
||||
## Build
|
||||
|
||||
`workflow_dispatch` works because it creates a run directly instead of relying
|
||||
on an event. It is the only mechanism available from a session, so every build
|
||||
has to be asked for by hand.
|
||||
```sh
|
||||
export JAVA_HOME=... # JDK 17
|
||||
export ANDROID_HOME=... # SDK with platform 34, build-tools 34
|
||||
./gradlew assembleDebug # dev-channel APK (default)
|
||||
PLG_CHANNEL=production ./gradlew assembleDebug # production APK
|
||||
./gradlew testDebugUnitTest # JVM unit tests (rules/AI/net/score)
|
||||
./gradlew lintDebug # Android lint (CI runs it)
|
||||
```
|
||||
|
||||
## Verify before merging, without publishing
|
||||
On the Wallacenet project workspace a ready toolchain sits at
|
||||
`../.toolchain/` (`jdk17/`, `android-sdk/`) — export `JAVA_HOME` and
|
||||
`ANDROID_HOME` to it rather than installing again. Both variants must build:
|
||||
they install side by side (different applicationId, launcher name, colour).
|
||||
|
||||
`ci.yml` builds, tests and lints on any ref, but its staging and publishing
|
||||
steps are gated on `github.ref == 'refs/heads/dev'`. Dispatching it on a
|
||||
**feature branch** therefore gives the full check — build, unit tests, Android
|
||||
lint — and publishes nothing.
|
||||
## Channels and OTA
|
||||
|
||||
So the working loop is:
|
||||
One flag — `isDevBuild` in `app/build.gradle.kts` — drives applicationId,
|
||||
launcher name and brand colour together. Never set them individually.
|
||||
|
||||
1. Commit and push the feature branch.
|
||||
2. Dispatch `ci.yml` on that branch. Wait for green. This is the real review
|
||||
gate, and it is the only verification that exists (see below).
|
||||
3. Open the PR through the API and merge it there, so the change is reviewable
|
||||
and the merge commit is honest about what happened. Neither step builds
|
||||
anything.
|
||||
4. Dispatch the target branch's workflow to actually build and publish:
|
||||
`ci.yml` on `dev`, `release.yml` on `main`.
|
||||
In-app OTA points at this repo's own Gitea releases
|
||||
(`/api/v1/repos/devadmin/PilgrimsOfKatan/releases…`) and is **on**:
|
||||
`BuildConfig.OTA_ENABLED` is `true`. The channel is a decision already made and
|
||||
recorded — `docs/CI.md` documents the endpoints, the build-number scheme, and
|
||||
what publishing requires. Setting the flag to `false` is the kill switch.
|
||||
|
||||
Skipping step 4 leaves a branch whose head was never built and no release.
|
||||
## CI
|
||||
|
||||
## Merging is a shipping action
|
||||
`.github/workflows/` — Actions-compatible so the Gitea instance runs them.
|
||||
`ci.yml`: build + test + lint + artifact, and a dev prerelease from `dev`.
|
||||
`release.yml`: production release from `main`. Both publish through
|
||||
`scripts/publish-release.sh` (Gitea REST API), which is also runnable by hand.
|
||||
|
||||
Both long-lived branches publish, so a merge is a release, not just an
|
||||
integration:
|
||||
**No Actions runner is registered on the instance yet**, so the workflows are
|
||||
idle until one is — the local gradle commands above are the source of truth,
|
||||
and CI green means those plus lint. Do not treat a workflow that has never run
|
||||
as evidence.
|
||||
|
||||
- **`dev`** → `ci.yml` publishes a `v<series>.N-dev` **prerelease**, offered by the
|
||||
in-app updater to anyone on the Dev channel.
|
||||
- **`main`** → `release.yml` publishes a **production** release, which the
|
||||
updater installs for everyone on the Production channel.
|
||||
## Code map
|
||||
|
||||
Confirm before shipping to `main`. Note that `release.yml` runs `assembleDebug`
|
||||
only — no tests, no lint — so a `main` release is only as verified as the `dev`
|
||||
build it replays.
|
||||
- `game/` — rules engines and AI, **pure Kotlin, no Android types**, unit
|
||||
tested. `Catalog.kt` is the registry (id, title, blurb, options, `available`).
|
||||
- `net/` — wire protocol, LAN transport (mDNS + TCP), lobby, host-authoritative
|
||||
match driver; `score/` the scorekeeper sheet, also pure.
|
||||
- `ui/` — Compose screens; `ui/theme/Theme.kt` holds the brand palette
|
||||
(`BrandField`/`BrandDeep`/`BrandAccent`) and table accents.
|
||||
- `update/` — OTA policy (`UpdatePolicy.kt`, pure and tested) and the gated
|
||||
fetch (`Updater.kt`).
|
||||
- `settings/` — DataStore preferences.
|
||||
|
||||
## Gradle does not run in the sandbox
|
||||
Host is authoritative over the wire; clients send intents, host re-validates
|
||||
against the rules. Keep `game/` free of Android imports — the JVM tests depend
|
||||
on it.
|
||||
|
||||
`./gradlew` cannot resolve the Android plugin here: the proxy blocks
|
||||
`dl.google.com` with a 403. No local build, no local test run, no local lint. A
|
||||
dispatched CI run is the only way to find out whether a change compiles.
|
||||
## Git
|
||||
|
||||
## A permanent fix, if you want one
|
||||
- Branches: `main` (production), `dev` (integration). Feature branches → PR
|
||||
into `dev`.
|
||||
- Commits by Paperclip agents end with
|
||||
`Co-Authored-By: Paperclip <[email protected]>`.
|
||||
- Pull upstream fixes with `git pull upstream` (the GitHub source); do not
|
||||
point `origin` anywhere but this repo's Gitea URL.
|
||||
|
||||
The suppression is a property of the sandbox's credential, so it cannot be
|
||||
fixed from inside a session. Giving the environment a personal access token to
|
||||
push with would make pushes and PRs behave normally for everyone. Until then,
|
||||
the dispatch step above is the workaround.
|
||||
## Legacy files
|
||||
|
||||
## 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 <cmd>`.
|
||||
|
||||
## 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.
|
||||
`.claude/` and `.rtk/` are the upstream author's assistant tooling, harmless
|
||||
and left as inherited. This file supersedes the upstream's CLAUDE.md (which
|
||||
documented their GitHub sandbox; that history is still in git).
|
||||
|
||||
Reference in New Issue
Block a user