Release checklist¶
uv run coach dev release-check runs the automatable part of this list and exits 1 until everything holds. The same command runs in CI (.github/workflows/ci.yml) with --skip-tests --skip-web: the suites run in the CI jobs it depends on, not a second time.
Owner decisions (taken on 2026-10-06)¶
| Decision | Where |
|---|---|
| The licence: MIT (decided on 2026-10-06) | LICENSE, license in pyproject.toml, the README |
| The reporting address: GitHub's private vulnerability reporting of the repository | SECURITY.md, CODE_OF_CONDUCT.md |
The public repository: https://github.com/mmornati/ai-finance-coach |
pyproject.toml [project.urls], the README clone line |
| The git history starts at the public release (the earlier, unversioned life of the project is not published) | git init on 2026-10-06 |
What release-check verifies¶
| Check | What passes |
|---|---|
| LICENSE file | a LICENSE (or COPYING) file of real content exists at the root |
| Owner placeholders | no TODO(license), TODO(contact) or TODO(repo-url) in any publishable file |
| Version | pyproject.toml version = coach.__version__ = the top ## [x.y.z] - YYYY-MM-DD section of CHANGELOG.md (dated) |
| Web app build | src/coach/api/static/index.html exists, references only files that exist, and is not older than web/src |
| Personal data scan | the structural rules (keys, home folders, valid IBANs, e-mails) and, with the local term file, the real-data rule find nothing in any publishable file or in the sdist member list (and in dist/ archives when present) |
| Real-data terms | the LOCAL term file exists, is 0600 and non-empty: a missing file fails (--ci skips this rule, says so, and the run is not release-grade) |
| Package metadata | name, version, description, readme, requires-python, classifiers, keywords, the coach script |
| Wheel contents | artifacts for the built web app, force-include for the configuration example and import profiles |
| Shipped templates | the memory skeleton under src/coach/templates/memory/ |
| Project URLs | [project.urls] has a Homepage or Repository |
| Documentation | README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CHANGELOG, docs/architecture.md, docs/docker.md, docs/release.md, docs/claude-settings.example.json (valid JSON) |
| .gitignore | the personal-data paths are ignored |
| Docker files (static) | Dockerfile, docker-compose.yml, .dockerignore pass coach/dockerlint.py (not built) |
| CI workflow | .github/workflows/ci.yml runs pytest, the web tests, ruff and release-check |
| pytest, ruff, vitest, type check | they pass (--skip-tests, --skip-web skip them; the run is then reported as NOT release-grade) |
What publishable means¶
The files that .gitignore does not exclude. The scan applies git's rules itself (the project may not be a repository yet), so a path ignored there is a path nobody
reviews: keep .gitignore and this list consistent. Ignored on purpose: memory/* except the README and the two templates, data/, backups/, config.toml,
config/taxonomy.yaml and config/rules.yaml (your copies), .claude/settings.json (absolute paths; the template is docs/claude-settings.example.json), *.pem,
*.key, .env*, *.db, *.enc, secrets/, coach-home/, web/node_modules/, and the built web app src/coach/api/static/ (built by CI, the Dockerfile and step 4 below).
Procedure¶
- Decide the open items above. Save
LICENSE; remove the placeholders. - Bump the version in
pyproject.tomlandsrc/coach/__init__.py; add the## [x.y.z] - datesection toCHANGELOG.md; refresh the lock (uv lock). - Pin the Docker base images: replace each tag in the
Dockerfilebytag@sha256:<digest>(seedocs/docker.md) and build the image once. cd web && pnpm install --frozen-lockfile && pnpm build.- On the machine that holds your data:
uv run coach dev hygiene --build-terms(writeshygiene-terms.txt, 0600, git-ignored, never printed), thenuv run coach dev release-check(everything, no skips, no--ci) until it prints READY. The terms come from your database and memory, so rebuild them whenever the household changes. - Read the diff of
docs/BACKLOG.mdandREADME.mdonce more for anything personal that a scan cannot know (a family situation, an employer, a town). The scan only knows the terms of the local file; it cannot know a situation that is in no term (the number of children, an employer you never declared). - Build:
uv build(the wheel must contain the web app:unzip -l dist/*.whl | grep api/static, andcoach/templates/). Install it in a clean environment (uv tool install ./dist/*.whl;coach init,coach doctor) before uploading. - Tag: merge the bump of step 2 in a pull request (main takes pull requests only), wait for CI to go green on main, then Actions > Tag a release >
Run workflow on
main, ticking the box that says step 5 printed READY on that commit.tag.ymlchecks thatpyproject.toml,coach.__version__and the first dated changelog section agree, that the tag does not exist yet and that CI passed on the commit, pushesv<version>, and callsrelease.yml, which builds the web app, the wheel and the sdist, runsrelease-check --cion them, and creates the GitHub release with the changelog section as notes (then the merged pull requests, sorted by.github/release.yml) and both archives attached. A version like0.3.0rc1gives a pre-release. A tag pushed by hand (git push origin v0.3.0) runsrelease.ymlalone. - Publish, if you want to:
uv publish(with a token that only you hold) and the image. No workflow does it.
After a release¶
Add an empty ## [Unreleased] section at the top of the changelog while you work: release-check skips it and judges the first dated section. When you cut the next release, rename it to the new version and date it.