Commit & test discipline
Overview
Loose during the work, strict at the merge boundary. While building inside a
worktree, optimise for momentum: WIP checkpoints and targeted test runs. Before
the branch merges to main, pay the full cost once: clean atomic history and the
whole suite green. The two phases have different rules — don't apply the strict
ones mid-work (wastes time) or the loose ones at merge (ships junk).
This governs commit/test conduct inside the worktree lifecycle. The lifecycle
itself (worktree per change, user-gated git merge --no-ff, cleanup) lives in
docs/contributing-workflow.md (TFE) / that same doc referenced from dj-automation
CLAUDE.md. REQUIRED BACKGROUND: read that lifecycle before applying this skill.
Violating the letter of these rules is violating the spirit of them.
The two phases
Phase 1 — During the work (inside the worktree): go fast
- WIP commits are allowed and encouraged. Prefix them
wip: so they are
greppable and unmistakable. Use them as save-points before risky edits.
- Do NOT run the full suite per commit. You decide when a test run earns its
cost — after a meaningful logical unit lands, before a risky refactor. Not after
every one-line checkpoint.
- Run only the tests relevant to what changed — the touched module's tests, its
direct importers, and the one e2e path that covers the touched flow. Full-suite-
every-time is the waste this skill exists to remove.
- TFE:
apps/desktop/venv/bin/python -m unittest tests.<module> with
QT_QPA_PLATFORM=offscreen (Qt tests). Pick the module(s) covering your change.
- dj-automation:
./run_tests.sh <module> [<module>...] (never bare pytest
from the repo root — it reports bogus collision errors).
Phase 2 — Preparing to merge (the boundary): go strict
- Clean up WIP → atomic history. Reorganise the branch into coherent commits
(see Commit hygiene). A
wip: commit must NEVER reach main. rebase -i is
unavailable in the agent shell (no interactive editor); build the final sequence
non-interactively — git reset --soft to unstage back to a base, then stage and
commit -m each atomic unit in order, or drive the rebase via
GIT_SEQUENCE_EDITOR/--exec.
- Now run the FULL suite, and it must be green. This is the one place "all
green" is mandatory and non-negotiable.
- TFE: the relevant
tests.* modules for the surface (full desktop discover
boots a real render server on :47765 — see docs/dev-environment.md).
- dj-automation:
./run_tests.sh (all modules).
- For bisectability, prefer green at each final commit (
git rebase --exec '<test cmd>'). If per-commit is too costly, full-suite-at-tip + relevant-subset-
per-commit is the accepted fallback — but say which bar you set, because it
changes how far git bisect can be trusted.
Commit hygiene (applies to the cleaned-up commits)
- One logical change per commit. Never mix a refactor with a behaviour change —
that is the classic
git bisect killer.
- Separate mechanical/generated changes (formatting, codegen, generated
Theme.qml, lockfiles) into their own commits so review and blame skip them.
- Stage explicit paths — never
git add -A/git add <dir>. main and its
worktrees are shared; -A sweeps concurrent agents' untracked WIP into your
commit. Run git diff --staged and read it before writing the message.
- Message: imperative subject ≤ ~50 chars with a scope that names the real
surface (
fix(desktop): …, not a web-* scope on a desktop diff); body explains
why, not just what. Reference ADR/change/issue ids so history is greppable.
- Trailers as the repo requires (
Co-Authored-By:, session link). These make
history auditable and git log --grep-searchable.
- Smell test: if the message needs "and" or a bullet list of unrelated items,
split the commit.
Authoring tests (so Phase-1 speed is even possible)
Targeted subsets and parallel runs only work if tests are built for them.
- Independent & state-isolated. Every test/case runs in any order with no shared
mutable state: fresh fixtures/DB/temp dirs per test, no ordering dependency, no
leaked globals, no fixed shared ports/servers. State leaks are exactly what break
subsetting and parallelism (this repo has been burned by tests that boot a
shared render server on a fixed port and leave it listening).
- Build the pyramid — it is what makes "relevant subset" meaningful:
- Thin end-to-end layer — the most common and crucial flows: the primary happy
path plus the error paths whose failure is most expensive/user-visible. These
are the smoke tests that must pass before every merge.
- Fast-feedback layer — unit/small-integration, run constantly during Phase 1
for quick signal; cheap to run as a subset.
- Detailed layer — edge cases, branches, less-common paths for high coverage;
run when its area is touched, and before merge.
Quick reference
| Situation |
Do |
| Mid-work checkpoint |
wip: commit, no test gate |
| Which tests during work |
Only those covering the change (module + importers + its e2e) |
| Risky refactor ahead |
wip: save-point first, run relevant tests after |
| Preparing to merge |
Squash WIP → atomic commits, then full suite green |
| Any commit |
explicit paths, read git diff --staged, why-in-body |
| New test |
isolated, no shared state, placed in the right pyramid layer |
Rationalization table — STOP if you think any of these
| Excuse |
Reality |
"I'll just squash the WIP later, one wip: on main is fine" |
No wip: commit ever reaches main. Clean up before the merge, not after. |
| "Full suite per commit is thorough" |
During work it's waste. Run the relevant subset; full suite only at the boundary. |
| "Skipping the full suite before merge saves time" |
The merge boundary is the one place it's mandatory. No exceptions. |
"git add -A is faster" |
On a shared checkout it steals other agents' untracked WIP. Stage explicit paths. |
| "Refactor + fix in one commit is cleaner" |
It's the #1 bisect killer. Split them. |
| "This test is fine reusing the shared server/port/tmp" |
State leak → breaks parallel + subset runs. Isolate it. |
| "I'll write only unit tests / only an e2e" |
Missing a pyramid layer. Cover the crucial flow e2e and keep fast unit feedback. |
| "The change is trivial, no test run needed before merge" |
Trivial changes break suites. Full suite green at the boundary, always. |
Red flags — you are about to violate this skill
- A
wip: commit on the branch you're about to merge
git add -A / git add <dir> staged
- Committing without reading
git diff --staged
- A commit that both refactors and changes behaviour
- Merging without having run the full suite green
- A new test that touches a shared server, fixed port, or shared tmp/DB
- Running the whole suite after every tiny edit during Phase 1
1---2name: commit-and-test-discipline3description: Use when making any code change in track-feature-extractor or dj-automation — before committing, when choosing which tests to run, when authoring tests, and when preparing a worktree branch for merge. Covers WIP vs clean commits, atomic history, relevant-subset vs full-suite test runs, and state-isolated parallel-safe tests.4---56# Commit & test discipline78## Overview910**Loose during the work, strict at the merge boundary.** While building inside a11worktree, optimise for momentum: WIP checkpoints and targeted test runs. Before12the branch merges to `main`, pay the full cost once: clean atomic history and the13whole suite green. The two phases have *different* rules — don't apply the strict14ones mid-work (wastes time) or the loose ones at merge (ships junk).1516This governs commit/test conduct **inside** the worktree lifecycle. The lifecycle17itself (worktree per change, user-gated `git merge --no-ff`, cleanup) lives in18`docs/contributing-workflow.md` (TFE) / that same doc referenced from dj-automation19`CLAUDE.md`. **REQUIRED BACKGROUND:** read that lifecycle before applying this skill.2021**Violating the letter of these rules is violating the spirit of them.**2223## The two phases2425### Phase 1 — During the work (inside the worktree): go fast2627- **WIP commits are allowed and encouraged.** Prefix them `wip:` so they are28 greppable and unmistakable. Use them as save-points before risky edits.29- **Do NOT run the full suite per commit.** *You* decide when a test run earns its30 cost — after a meaningful logical unit lands, before a risky refactor. Not after31 every one-line checkpoint.32- **Run only the tests relevant to what changed** — the touched module's tests, its33 direct importers, and the one e2e path that covers the touched flow. Full-suite-34 every-time is the waste this skill exists to remove.35 - **TFE:** `apps/desktop/venv/bin/python -m unittest tests.<module>` with36 `QT_QPA_PLATFORM=offscreen` (Qt tests). Pick the module(s) covering your change.37 - **dj-automation:** `./run_tests.sh <module> [<module>...]` (never bare `pytest`38 from the repo root — it reports bogus collision errors).3940### Phase 2 — Preparing to merge (the boundary): go strict4142- **Clean up WIP → atomic history.** Reorganise the branch into coherent commits43 (see Commit hygiene). **A `wip:` commit must NEVER reach `main`.** `rebase -i` is44 unavailable in the agent shell (no interactive editor); build the final sequence45 non-interactively — `git reset --soft` to unstage back to a base, then stage and46 `commit -m` each atomic unit in order, or drive the rebase via47 `GIT_SEQUENCE_EDITOR`/`--exec`.48- **Now run the FULL suite, and it must be green.** This is the one place "all49 green" is mandatory and non-negotiable.50 - **TFE:** the relevant `tests.*` modules for the surface (full desktop discover51 boots a real render server on :47765 — see `docs/dev-environment.md`).52 - **dj-automation:** `./run_tests.sh` (all modules).53- **For bisectability, prefer green at *each* final commit** (`git rebase --exec54 '<test cmd>'`). If per-commit is too costly, full-suite-at-tip + relevant-subset-55 per-commit is the accepted fallback — but say which bar you set, because it56 changes how far `git bisect` can be trusted.5758## Commit hygiene (applies to the cleaned-up commits)5960- **One logical change per commit.** Never mix a refactor with a behaviour change —61 that is the classic `git bisect` killer.62- **Separate mechanical/generated changes** (formatting, codegen, generated63 `Theme.qml`, lockfiles) into their own commits so review and blame skip them.64- **Stage explicit paths — never `git add -A`/`git add <dir>`.** `main` and its65 worktrees are shared; `-A` sweeps concurrent agents' untracked WIP into your66 commit. Run `git diff --staged` and *read it* before writing the message.67- **Message:** imperative subject ≤ ~50 chars with a scope that names the real68 surface (`fix(desktop): …`, not a `web-*` scope on a desktop diff); body explains69 **why**, not just what. Reference ADR/change/issue ids so history is greppable.70- **Trailers** as the repo requires (`Co-Authored-By:`, session link). These make71 history auditable and `git log --grep`-searchable.72- Smell test: if the message needs "and" or a bullet list of unrelated items,73 **split the commit.**7475## Authoring tests (so Phase-1 speed is even possible)7677Targeted subsets and parallel runs only work if tests are built for them.7879- **Independent & state-isolated.** Every test/case runs in any order with no shared80 mutable state: fresh fixtures/DB/temp dirs per test, no ordering dependency, no81 leaked globals, no fixed shared ports/servers. State leaks are exactly what break82 subsetting *and* parallelism (this repo has been burned by tests that boot a83 shared render server on a fixed port and leave it listening).84- **Build the pyramid** — it is what makes "relevant subset" meaningful:85 - **Thin end-to-end layer** — the most common and crucial flows: the primary happy86 path plus the error paths whose failure is most expensive/user-visible. These87 are the smoke tests that must pass before every merge.88 - **Fast-feedback layer** — unit/small-integration, run constantly during Phase 189 for quick signal; cheap to run as a subset.90 - **Detailed layer** — edge cases, branches, less-common paths for high coverage;91 run when its area is touched, and before merge.9293## Quick reference9495| Situation | Do |96|---|---|97| Mid-work checkpoint | `wip:` commit, no test gate |98| Which tests during work | Only those covering the change (module + importers + its e2e) |99| Risky refactor ahead | `wip:` save-point first, run relevant tests after |100| Preparing to merge | Squash WIP → atomic commits, then **full suite green** |101| Any commit | explicit paths, read `git diff --staged`, why-in-body |102| New test | isolated, no shared state, placed in the right pyramid layer |103104## Rationalization table — STOP if you think any of these105106| Excuse | Reality |107|---|---|108| "I'll just squash the WIP later, one `wip:` on main is fine" | No `wip:` commit ever reaches `main`. Clean up *before* the merge, not after. |109| "Full suite per commit is thorough" | During work it's waste. Run the relevant subset; full suite only at the boundary. |110| "Skipping the full suite before merge saves time" | The merge boundary is the one place it's mandatory. No exceptions. |111| "`git add -A` is faster" | On a shared checkout it steals other agents' untracked WIP. Stage explicit paths. |112| "Refactor + fix in one commit is cleaner" | It's the #1 bisect killer. Split them. |113| "This test is fine reusing the shared server/port/tmp" | State leak → breaks parallel + subset runs. Isolate it. |114| "I'll write only unit tests / only an e2e" | Missing a pyramid layer. Cover the crucial flow e2e *and* keep fast unit feedback. |115| "The change is trivial, no test run needed before merge" | Trivial changes break suites. Full suite green at the boundary, always. |116117## Red flags — you are about to violate this skill118119- A `wip:` commit on the branch you're about to merge120- `git add -A` / `git add <dir>` staged121- Committing without reading `git diff --staged`122- A commit that both refactors and changes behaviour123- Merging without having run the full suite green124- A new test that touches a shared server, fixed port, or shared tmp/DB125- Running the whole suite after every tiny edit during Phase 1