Skip to content

Tooling

The tools each stack uses, their default settings, and how releases are built. usage.md lists the files each stack creates, and ci-baseline.md lists every workflow check.

Tools per stack

Stack Lint and format Types Tests Packaging
Python ruff ty pytest uv, locked, uv_build backend
TypeScript biome tsc vitest, plus Playwright and axe for a web-app pnpm, plus Vite for a web-app
Rust rustfmt, clippy rustc cargo nextest, plus cargo test --doc cargo, checked by cargo-deny
Go golangci-lint (gofumpt, goimports) go vet, via golangci-lint go test -race, Examples included Go modules, checked by govulncheck

Rust lints

Clippy lints live in [lints] in Cargo.toml. The policy enables clippy's pedantic group plus restriction lints against shortcuts: unwrap_used, expect_used, dbg_macro, todo, unimplemented, and allow_attributes_without_reason. unsafe_code is forbidden. Libraries also warn on missing_docs and prints to standard output or error.

clippy.toml permits unwrap, expect, and dbg! in tests. deny.toml limits dependencies to crates.io and to the licenses the project's license can take (ci-baseline.md).

Install cargo-nextest and cargo-deny once per machine. A Rust library's api job runs cargo-semver-checks against the last v* tag.

Go lints

Lints live in .golangci.yml for golangci-lint v2. The policy enables standard linters plus bodyclose, errorlint, gocritic, gosec, misspell, modernize, nilerr, noctx, revive, sloglint, unconvert, unparam, and usestdlibvars. gosec and noctx are disabled in tests. gofumpt and goimports handle formatting.

Install golangci-lint once per machine. The template pins the golangci-lint version, which copier update bumps. govulncheck is configured as a tool dependency in go.mod and requires no separate installation.

Git hooks

prek, a drop-in pre-commit replacement, runs the hooks. One .pre-commit-config.yaml covers all stacks. Python projects install prek with uv tool install prek; TypeScript-only projects use the @j178/prek devDependency.

Area Hooks
Every project file hygiene, check-toml, committed (commit messages), typos, lychee, rumdl (Markdown), betterleaks (secrets), shellcheck, yamlfmt, yamllint, jsonvet, zizmor, jactionlint
Public or licensed dco-signoff (public), reuse (licensed)
Citable check-citation-file-format, citesync
Python ruff-check, ruff-format, ty, nbstripout, sp-repo-review
TypeScript biome, tsc, and vitest in a web app
Rust cargo-fmt, cargo-clippy
Go golangci-lint-fmt, golangci-lint
Python image hadolint-docker, which needs a running Docker daemon

Two git hooks validate prose:

  • typos: detects spelling errors in code, comments, and documentation. Add project-specific terms to _typos.toml at the repository root:
[default.extend-words]
ue = "ue"

typos knows English only. The hook skips gettext .po files and anything in a locales/ directory; list other files with text in other languages under [files] extend-exclude in _typos.toml.

  • lychee: validates links. The local git hook runs offline and checks relative paths. The weekly workflow .github/workflows/links.yml checks both internal and external web links. In a web app, both read lychee.toml, which resolves root-relative links such as /favicon.svg in the Vite public/ directory.

Documentation rules

  • Full URLs in package READMEs: PyPI, npm, and crates.io render the project's README.md on their package pages. Relative links break when viewed on registry sites, so links to repository files and directories use absolute GitHub URLs (https://github.com/<owner>/<repo>/...). A render with publish_to_pypi or publish_to_npm writes its README links this way.
  • Stable links to documentation on main: When documentation or CLI error messages link to rules or guides, point to main. Rule identifiers are permanent; if a rule is deprecated or removed, keep a stub heading so historical links continue to resolve.
  • Single source of truth: The README holds the overview and quickstart; code comments and docstrings define API contracts; docs/ holds extended guides and references.

Tasks

just runs the tasks. The root justfile imports a just/<stack>.just module per stack. Recipes carry a stack prefix (py-, ts-, rs-, go-, and docker- in a Compose-only repo). The exceptions are kind-specific: fetch, build, validate and pipeline (data-pipeline), serve (web-service) and up (docker).

just check runs the checks of every enabled stack, then the local-check recipe in just/local.just if there is one. In a Compose-only repo it validates the Compose file and lints the shell scripts.

Coverage is opt-in. just py-cov and just ts-cov print summaries without threshold gates. With the codecov extra, CI uploads coverage to Codecov, and just check runs the TypeScript tests with coverage.

Documentation site

With the docs_site extra, Zensical builds docs/ into a site, and mkdocstrings renders a Python library's API reference.

Docker images

The Dockerfile follows upstream uv patterns: multi-stage builds on -slim base images, BuildKit cache mounts, a non-root runtime user, and OCI labels.

Dependencies

Generated projects use Dependabot by default. dependency_bot=renovate writes equivalent rules to renovate.json5. Both bots follow these policies:

  • Weekly version updates grouped into three PRs: actions and hooks, dev tooling, and minor/patch runtime updates.
  • Runtime major versions get separate individual PRs.
  • Updates wait one week before opening, majors included.
  • Security updates bypass the cooldown.

Dependabot also combines npm security updates into one PR. Renovate refreshes lockfiles weekly on Monday.

uv's exclude-newer = "1 week" and pnpm's minimumReleaseAge hold back local installs to match the bot cooldown. Go has no equivalent cooldown.

To lock an urgent security patch before the cooldown expires, add a temporary exemption:

  • uv: add the package under [tool.uv] in pyproject.toml with a timestamp after the patch release, then run uv lock --upgrade-package <pkg>:
exclude-newer-package = { certifi = "2026-10-21T00:00:00Z" }

Do not rely solely on uv lock --exclude-newer-package <pkg>=<timestamp>; later uv sync calls reset it. Remove the entry once the patch is a week old.

  • pnpm: run pnpm audit --fix=update. This adds an entry under minimumReleaseAgeExclude in pnpm-workspace.yaml. You can also add it manually and run pnpm install:
minimumReleaseAgeExclude:
  - [email protected]

minimumReleaseAgeExcludePrune removes the entry automatically once the lockfile moves past that version.

The Docker base image tag (python_version) is a Copier answer. Rebuilds pull the latest security patches for that tag.

Fresh renders generate without lockfiles. The initial uv lock or pnpm install locks the newest versions past the one-week cooldown. Once committed, CI installs strictly from lockfiles.

Both just check and CI run dependency audits:

  • uv audit for Python dependencies.
  • pnpm audit --prod for production TypeScript packages. Astro site workflows use pnpm audit to include build tools.
  • cargo deny check for Rust crates and licenses.
  • govulncheck for Go code paths.
  • A license check per stack, unless license=none: licensecheck, pnpm run licenses, cargo deny list and go-licenses. An Astro site runs the same production dependency check as TypeScript during just site; Astro itself is build tooling in devDependencies. Each checks only the dependencies that ship: Python's runtime dependencies without dependency groups, TypeScript's --prod packages, Rust without dev-dependencies, and the Go packages a build imports, not test-only ones. TypeScript, Rust and Go refuse an LGPL-2.1-only dependency in every project, since GPL-3.0 cannot take it; Go refuses every LGPL-2.1 dependency, because go-licenses cannot tell -only from -or-later. Python follows licensecheck's own table, which lets LGPL-2.1 in.
  • Knip for unused TypeScript files, exports, and packages.

Release workflows

Release workflows separate artifact build from artifact publication:

  1. Build: re-runs checks and tests the packaged artifact directly. Installs the Python wheel into a clean environment to run tests, or verifies npm packages with publint.
  2. Publish: downloads the built artifact and uploads it using OpenID Connect (id-token: write). This job runs no project code.

With PyPI publishing, a final job attaches wheels, a CycloneDX SBOM and provenance bundles to the GitHub release, after gh attestation verify passes on each. Images pushed to GHCR include an SBOM and a signed provenance attestation.