Contributing
We welcome bug reports, fixes, new tests and improvements to the games. When you take part, you agree to follow the Code of Conduct.
- Bugs and ideas. For anything larger than a small fix, open an issue first. Use the bug report or feature request form. This lets us agree on the approach before you spend time on it. Ask questions in Discussions.
- Security problems. Do not open a public issue. Follow the security policy.
- Design and conventions. The architecture, scope and milestones are in docs/PLAN.md. The day-to-day rules are in AGENTS.md: the package rules, the single clock, how to add a game, golden files, and change notes. AGENTS.md applies to human and AI contributors alike.
Set up
You need Git, Go and prek. The race detector also needs a C compiler. You need nothing else. The repository pins the linters, the release tools and Hugo, which builds this site, as Go tools. They build themselves on first use.
Go. The repository pins its Go version in go.mod. Any Go from 1.21 on downloads that version the
first time you build. A Go set to GOTOOLCHAIN=local does not download it (see the Linux tab).
prek runs the formatters and linters before each commit, and the tests before each push. It is a single program. prek’s README lists every way to get it.
Install Git, Go and a C compiler with your distribution’s packages, for example:
sudo apt install git golang-go gcc # Debian, Ubuntu
sudo dnf install git golang gcc # Fedora
sudo pacman -S git go gcc # ArchIf your distribution’s Go is older than 1.21, install Go from go.dev
instead. Some distributions set their Go not to download another version. If go test says that
go.mod needs a newer Go and mentions GOTOOLCHAIN=local, run go env -w GOTOOLCHAIN=auto once, or
install Go from go.dev. Then install prek with its installer, or with brew install prek if you use
Homebrew:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/download/v0.5.5/prek-installer.sh | shThen get the code, install the hooks, and check that everything works:
git clone https://github.com/GhostofGoes/WOPR.git
cd WOPR
prek install
go test ./...
go run ./cmd/woprprek install installs both the pre-commit and the pre-push hooks.
Commands
All commands run from the repository root. They work on Linux, macOS and Windows, unless a row names a system. There is no Makefile.
| Task | Command |
|---|---|
| Run | go run ./cmd/wopr |
| Test | go test ./... |
| Test with the race detector | go test -race ./... (Linux and macOS. Windows amd64 needs a C compiler. It does not run on windows/arm64) |
| End-to-end tests (real binary in a pty) | go test -tags e2e ./internal/e2e |
| Lint rules self-test | WOPR_LINT_SELFTEST=1 go test -run TestLintRulesFire ./internal/archtest |
| Regenerate golden files | WOPR_UPDATE_GOLDEN=1 go test ./..., then review the diff |
| All hooks | prek run --all-files (install once with prek install) |
| Lint | go tool -modfile=tools/lint/go.mod golangci-lint run ./... |
| Format | go tool -modfile=tools/lint/go.mod golangci-lint fmt ./... |
| Vulnerabilities | go tool -modfile=tools/go.mod govulncheck ./... |
| Dependency cooldown (no module newer than 14 days) | go run ./internal/tools/cooldown |
| Secret scan (full history) | go tool -modfile=tools/go.mod gitleaks git --redact . |
Third-party notices and the .deb’s copyright file | go run ./internal/tools/notices (writes THIRD_PARTY_NOTICES.txt and packaging/debian/copyright). CI runs it with -check |
Manual page (docs/man/wopr.6) | go run ./internal/tools/manpage. CI runs it with -check. Lint it with mandoc -T lint -W all docs/man/wopr.6 |
Icons (packaging/icons/, the docs site’s favicons) | go run ./internal/tools/icons (draws every icon file from packaging/icons/src/. packaging/icons/README.md says how to change the icon). CI runs it with -check |
| Add a change note | go tool -modfile=tools/release/go.mod changie new (see Change notes) |
Check the change notes and CHANGELOG.md | go run ./internal/tools/relnotes -check (a prek hook) |
| A release’s notes | go run ./internal/tools/relnotes -version X.Y.Z -out build/notes (or -snapshot) |
| Release build (local dry run) | go run ./internal/tools/relnotes -snapshot -out build/notes, then, with WOPR_NOTES_DIR=build/notes in the environment, go tool -modfile=tools/release/go.mod goreleaser release --snapshot --clean |
| Size gate | go run ./internal/tools/sizegate -expect 6 -packages 4. -files <file>... gates the installers (the collect jobs) |
| Stage binaries and e2e tests | go run ./internal/tools/stage. With -archives -assets dist/release, it also checks the archives and the Linux packages, and it collects every release file into dist/release. With -merge -assets dist/release <file>..., it adds the installers to them and to checksums.txt, sorted as GoReleaser sorts it |
| Lint the Linux packages (Linux, after a release build. The tools are not pinned) | lintian --pedantic dist/*.deb and, with Fedora’s rpmlint configuration, rpmlint -r packaging/rpmlintrc dist/*.rpm. See docs/PLAN.md §8 for what they report. List an .rpm’s files with rpm -qlvp, or unpack it with bsdtar -xf X.rpm -C dir. Never pipe rpm2cpio into a plain cpio -idm, which writes into / (the payload’s paths are absolute) |
| Check the menu entries and the AppStream metadata (Linux, after a release build. The tools are not pinned) | desktop-file-validate build/pkg/deb/*.desktop build/pkg/rpm/*.desktop (prints nothing when they pass), appstreamcli validate --no-net --pedantic build/pkg/*.metainfo.xml and appstream-util validate-relax --nonet build/pkg/*.metainfo.xml. The desktop-files hook runs them on what pkgdocs writes (its TestValidators, which skips a validator that is not installed unless WOPR_DESKTOP_VALIDATORS=1). The smoke jobs run them on the installed .deb |
Windows installer and MSIX packages (Windows, PowerShell 7, after a release build and stage -archives -assets dist/release) | pwsh -File packaging/windows/check-release-files.ps1 -Version <V> -BinDir dist/release, then pwsh -File packaging/windows/build-installer.ps1 -Version <V> -BinDir dist/release -OutputDir build/installer (downloads the pinned Inno Setup) and pwsh -File packaging/windows/build-msix.ps1 -Version <V> -BinDir dist/release -OutputDir build/msix (needs the Windows SDK) |
| Test them (Windows, only on a machine without WOPR, such as a CI runner) | pwsh -File packaging/windows/test-installer.ps1 -Installer build/installer/wopr_<V>_windows_setup.exe -Version <V> (changes the user’s PATH while it runs) and, as an administrator, pwsh -File packaging/windows/test-msix.ps1 -PackageDir build/msix -Version <V> (trusts a throwaway certificate while it runs) |
wopr.exe’s icon, manifest and version details by hand | go tool -modfile=tools/release/go.mod go-winres make --in packaging/windows/winres.json --out cmd/wopr/rsrc --arch amd64,arm64 (GoReleaser’s before hook runs it. Git ignores the .syso files go-winres writes) |
| macOS app bundle (any OS) | go run ./internal/tools/macapp -binary <darwin program> -version <V> -out build/WOPR.app (-notices dist/release takes the notices from the release files) |
macOS disk image (macOS, after a release build and stage -archives -assets dist/release) | packaging/macos/build-dmg.sh dist/release <V> build/dmg, then packaging/macos/test-dmg.sh build/dmg/wopr_<V>_macos.dmg <V> [stage/darwin_<arch>/e2e.test]. packaging/macos/test-launch.sh build/dmg/wopr_<V>_macos.dmg opens the app as Finder does, and it stops Terminal afterwards. Run it on a CI runner, or on a Mac where that does not matter |
Snaps (Linux with snapd, after a release build and stage -archives -assets dist/release) | go run ./internal/tools/snapdir -dist dist/release -version <V> -arch amd64 -out build/snap/amd64, then snap pack build/snap/amd64 build/snaps (without snapd, mksquashfs build/snap/amd64 build/snaps/wopr_<V>_amd64.snap -noappend -comp xz -no-fragments -all-root -no-xattrs packs it as snapd does). The Store’s checks: sudo snap install review-tools, then review-tools.snap-review <snap> |
| Test a snap (Ubuntu, sudo. The script installs and removes the snap) | packaging/snap/test-snap.sh build/snaps/wopr_<V>_amd64.snap <V> [stage/linux_amd64/e2e.test] |
| Docs site: preview (localhost:1313/WOPR/) | go tool -modfile=tools/docs/go.mod hugo server --source site |
| Docs site: build as CI does | go tool -modfile=tools/docs/go.mod hugo --source site --panicOnWarning --printPathWarnings --minify (into site/public/) |
<V> is the build’s version as GoReleaser writes it in dist/metadata.json: 1.2.3, or
1.2.4-snapshot.abc1234 for a snapshot. The installers’ scripts run only on their own systems. CI runs
them (windows.yml, macos.yml, snap.yml).
go.mod pins Go 1.27.1 (toolchain go1.27.1). GOTOOLCHAIN=auto downloads it, and a newer local Go is
fine (CI checks the exact version). Four modules pin the tools: tools/go.mod (gitleaks, govulncheck),
tools/lint/go.mod (golangci-lint), tools/release/go.mod (GoReleaser, changie, go-winres) and
tools/docs/go.mod (Hugo, the standard edition). Never use go run …@latest. The four modules are
separate because their dependency graphs conflict. The docs site’s theme, Hextra, is a Hugo module that
site/go.mod pins. No tool module’s go line, nor site/go.mod’s, may be newer than the root toolchain
line (a test checks). Bump the toolchain first, then the tool. Inno Setup is not a Go tool.
build-installer.ps1 pins it by URL, SHA-256 and Authenticode signer. snap.yml installs snapcraft from
its 9.x/stable channel, and review-tools from latest/stable.
Making a change
- Branch from
main. Keep each change focused. New functionality must come with automated tests. A bug fix must come with a regression test. - Go code must follow Effective Go and the Go Code Review Comments, and AI agents must follow the rules in AGENTS.md. The formatters and linters in the hooks and in CI enforce them.
- When a screen changes on purpose, regenerate golden files with
WOPR_UPDATE_GOLDEN=1 go test ./.... Then read the diff. - Run
prek run --all-filesandgo test ./...before you push. On Linux and macOS, also rungo test -race ./.... - If players will notice the change, add a change note (see below).
- Open a pull request. Write the title and description to say what changed and why. We squash-merge
pull requests, so the title and description become the commit on
main.
Pull requests
mainis PR-only. The required check isci-ok. Merges are squash merges.- A change players can notice adds a change note (Change notes).
- CI (
ci.yml) runs on every branch push, so a branch is checked before its PR. That branch’s push run checks a PR from a branch here (itspull_requestrun skips every job). A PR from a fork runs in full. The push run tests the branch as it is, not merged withmain, so the ruleset requires branches to be up to date. Updating one is a push, which tests the result. - Run
prek run --all-filesandgo test ./...before pushing. - OpenSSF Best Practices compliance is a project goal, so check each change against the criteria before you open a PR.
- Never re-tag a release. Fix a bad release with the next patch version and a
retractingo.mod.
Change notes
Every change a player could notice gets a short note, kept with changie in the
.changes/unreleased/ folder. A release collects the notes into the Changelog, the GitHub
Release, and the changelogs of the .deb and .rpm packages. Add a note with changie’s new command,
which the repository pins as a Go tool.
AGENTS.md’s Change notes gives
the exact command.
Write a note for players, in plain words at an 8th-grade reading level. Say what changed, not why. For a deep technical fix, say only what the player saw. For example: “Fixed a crash in some cases”, “Fixed an issue with the Chess game”. Tests, CI, tooling and contributor docs need no note.
Film text and other third-party content
This project quotes short pieces of the film’s on-screen text. Each quoted line carries a provenance tag:
film, reconstructed, third-party:..., original or prompt. A test fails when a line has no tag.
- Do not copy text, ASCII art or code from other projects unless their licence allows it. When it does, add the credit to NOTICE.md in the same pull request.
- Write new screen text and art yourself, and tag it
original. - Do not add film stills, audio or other media.
This site
Hugo and the Hextra theme build the site. The
repository pins both. The site’s pages are in site/content/. Hugo builds each game’s page from
site/data/games/<game>.json, which the manual page uses too. Preview the site at
http://localhost:1313/WOPR/ with:
go tool -modfile=tools/docs/go.mod hugo server --source siteEvery page has an “Edit this page” link that opens its source on GitHub. A change to the site goes
live when its pull request merges into main.
Licence
By contributing, you agree that your contributions are licensed under the MIT License. That grant does not cover the film quotations and third-party text listed in Credits and notices.