Thank you for your interest in contributing! This document covers everything you need to get started.
touchmark can write to every repository a hub targets, so changes are reviewed for safety first: what a change lets a target, a hub branch or a log reader do matters as much as what it adds. If you are unsure whether an idea fits, open an issue before the pull request.
You need Go 1.26 or newer (the go line of go.mod), git, uv
for the documentation site, and Docker for the image and the live tests.
git clone https://github.com/bedrock-python/touchmark.git
cd touchmark
make install
uv tool install pre-commit
pre-commit install --hook-type pre-commit --hook-type commit-msgdistribute needs git 2.45 or newer. On an older git the tests that run it skip; the suite
in Docker below runs them with a recent one.
make fmt # gofmt -w
make check # gofmt, go vet (with and without -tags e2e), golangci-lint, govulncheck
make test-unit # go test ./..., no Docker required
make test # all tests with the race detector and the coverage gate
make test-e2e # the live tests against Gitea, requires Docker
make build # bin/touchmark
make snapshot # the whole release into dist/ with goreleaser in Docker; publishes nothing
make docs-serve # local docs preview
make docs-build # the generated blocks of the docs, then the site into site/ with the Markdown copy of every page
make cleanmake check runs golangci-lint and govulncheck at the versions the Makefile pins, the ones
CI runs: a golangci-lint of that version on PATH as it is, otherwise go run
builds it once. make test holds the coverage of the whole suite to the gate in the Makefile
(COVERAGE_MIN), which CI measures on Linux with a recent git.
The tests of a few heavy cases run on Linux only; set TOUCHMARK_HEAVY_TESTS=1 to run them
on Windows or macOS. With git older than 2.45, the tests of distribute skip, and coverage
comes out below the gate. To run the whole suite with a recent git from any OS:
docker run --rm -v "$PWD:/src" -w /src golang:1.27 \
sh -c 'git config --global --add safe.directory /src && go test -race -timeout 45m ./...'In Git Bash on Windows, prefix it with MSYS_NO_PATHCONV=1 and use $(pwd -W) for $PWD.
- gofmt on every file —
make checkand pre-commit enforce it - golangci-lint with the linters of
.golangci.ymlon top ofgo vet - Doc comments on every exported package-level name: a full sentence that starts with
the name. Methods that only satisfy an interface (
Error,String) need none - No comments unless the why is non-obvious (a workaround, a subtle invariant, a security reason); when there is one, a full sentence
- Standard library first — the module depends on a YAML parser and a JSON Schema
validator and nothing else: touchmark holds write keys to many repositories, and every
dependency is part of that trust. A new dependency needs an issue first. Tools
(golangci-lint, govulncheck, goreleaser, actionlint) run pinned from their images or with
go run tool@version, never fromgo.mod - git runs with an argument list, never through a shell, and never with a credential on its command line
- Deterministic output — reports, schemas and messages sort what they list; tests pass on Linux, macOS and Windows
- Messages point at the site — an error, a flag's help, a schema description or a
generated file links a page of the documentation site (
internal/docsurl, whose test checks the page and its heading exist), never a design document or a milestone;TestNoInternalReferencesinscripts/docschecks every string literal, the help, the schemas and the pages - Pinned actions — the workflows shared with the other bedrock-python repositories
(
docs.yml,release-please.yml) stay byte-identical to theirs, tag pins included; touchmark's own workflows pin every action by commit with the version in a comment. Dependabot moves both
Conventional Commits are enforced by pre-commit:
| Prefix | Use for |
|---|---|
feat: |
New feature or behaviour |
fix: |
Bug fix |
docs: |
Documentation only |
test: |
Test additions or changes |
refactor: |
Code restructure, no behaviour change |
perf: |
Performance improvement |
chore: |
Build, tooling, CI |
Breaking changes: add ! after the type (feat!:) or include a BREAKING CHANGE: footer.
CHANGELOG.md is written from these by release-please: feat, fix, perf and revert
get a section of their own, the other types are left out.
- Fork the repository
- Create a branch from
master:git checkout -b feat/my-feature - Make your changes with tests
- Run
make check && make test-unitlocally - Open a PR against
master
Pull requests are squash-merged, so the title becomes the commit on master and the line
in the changelog: write it as a Conventional Commit.
Gitea, Forgejo and GitLab run in Docker through scripts/e2e/, against the real
platforms' APIs and a real git:
make test-e2e # one Gitea
bash scripts/e2e/gitea.sh all # every supported Gitea and Forgejo, about 35 minutes
bash scripts/e2e/gitlab.sh all # every supported GitLab with a runner; about 6 GB of memoryCI runs the Gitea and Forgejo tests on every pull request; a maintainer adds the label
e2e-gitlab to run the GitLab ones. A change to the image, the Action or what the hub
template's CI files rely on also gets a run of the template's pipelines (--template).
GitHub's tests run against a fake of its API; on github.com touchmark runs for real in the
bedrock-python hub.
End-to-end tests has the
details.
docs/agents.md is the whole tool on one page, written for a coding assistant: the
commands and their flags, the configuration files, the rules that break a hub when they
are broken, the mistakes models make, and a map of which page to fetch for the rest.
People hand it to an assistant instead of the site, which is what makes a stale one worse
than none — it teaches a model a command line that no longer exists.
It is part of the public interface, so it changes in the same pull request the interface
does: a command, flag, configuration key, environment variable or exit code added, renamed
or removed, a changed default, a new rule a hub has to obey. A new docs page means a new
row in the documentation map. The review check is mechanical — if the diff changes the
public surface and docs/agents.md is untouched, the pull request is not finished.
The page carries its own weight only if it stays fetchable as text. Every page of the site
is written a second time as raw Markdown next to its HTML by scripts/emit_markdown.py,
which the Docs workflow runs after the build; the Copy page control above each page
reads those files. A page whose Markdown would not read as the page declines both with
copy_page: false in its front matter.
Releases are cut by release-please from the
Conventional Commits on master: it keeps a release pull request open with the next
version, the generated changelog section and the version the Action runs (action.yml);
merging that PR tags the release vX.Y.Z and creates the GitHub release, and the publish
workflow takes it from there: goreleaser builds the binaries for Linux, macOS and Windows on
amd64 and arm64 and uploads them to that release with their checksums, signature and SBOMs;
the image is built for both architectures, pushed to GHCR by digest with its SBOM and build
provenance, tagged X.Y.Z, X.Y and latest, and signed with cosign.
Releases has every step and
how to verify a release.
The release pull request is opened with the workflow's own token, which starts no workflow, so the required check never reports on it: close the pull request and reopen it to run CI.
The first push creates the ghcr.io/bedrock-python/touchmark package private; make it
public in the organisation's package settings once, or every docker pull, every GitLab
and Gitea job and the Action are denied.
feat:bumps the minor version,fix:the patch version. touchmark is at 0.x, where a breaking change (feat!:, aBREAKING CHANGE:footer) bumps the minor version too (bump-minor-pre-majoris on). What counts as breaking is on the Compatibility page.- To force a specific version, add a
Release-As: x.y.zfooter to a commit. - Tags are
vX.Y.Z, without the component prefix the other bedrock-python repositories use: Go modules and the Action resolve barevtags only. - release-please writes each release above the first version heading of
CHANGELOG.md. Before the first release there is none, so the first release pull request moves the file's header below the 0.1.0 section; move it back to the top in that pull request just before merging it. Later releases leave it in place.