
backend-atomic-commit
Pedantic backend pre-commit + atomic-commit skill for Django/Optimo repos that enforces local repo r
Backend Atomic Commit Skill
When to Use This Skill
Use this Skill in backend/Django repos (especially the Diversio monolith backend) when you want:
/backend-atomic-commit:pre-commit– to actively fix the current code (formatting, imports, type hints, logging, etc.) so that it matches:- Local
AGENTS.md, linked repo-local docs, and quality gates. .pre-commit-config.yamlexpectations..security/diff helpers (ruff and local imports).- Monty’s backend taste.
- Local
/backend-atomic-commit:atomic-commit– to run the same checks plus:- Enforce that the staged changes are atomic (one coherent change).
- Ensure all quality gates are green (no shortcuts).
- Propose a commit message that follows the repo-local harness without any Claude or AI signatures.
/backend-atomic-commit:commit– to runatomic-commit, then create the commit once all gates are green (no bypassing commit-msg hooks).
Representative prompt shapes live in references/usage-examples.md.
If you’re not in a backend repo (no manage.py, no backend-style AGENTS.md,
no .pre-commit-config.yaml, no backend quality docs), this Skill should say
so explicitly and fall back to a lighter “generic Python pre-commit” behavior.
Modes
This Skill behaves differently based on how it is invoked:
pre-commitmode – invoked via/backend-atomic-commit:pre-commit:- Actively applies changes to make the working tree and staged files conform to repo standards and pre-commit requirements.
- Runs all relevant static checks and auto-fixers.
- Does not propose or drive a commit.
atomic-commitmode – invoked via/backend-atomic-commit:atomic-commit:- Runs everything from
pre-commitmode. - Enforces atomicity of staged changes.
- Requires all gates to be green.
- Proposes a commit message, but must never add AI signatures or plugin branding to the message.
- Runs everything from
commitmode – invoked via/backend-atomic-commit:commit:- Runs everything from
atomic-commitmode. - Creates the commit once all gates are green.
- Must still never add AI signatures or plugin branding to the message.
- Runs everything from
The command markdown sets the mode. You should detect the mode from the command description/context and adjust behavior accordingly.
Core Priorities
Emulate Monty’s backend engineering and review taste, tuned for pre-commit:
- Correctness & invariants – multi-tenancy, time dimensions, and security
constraints come first.
- Never eyeball date/time math (day-of-week, "yesterday", timezone edges).
Always verify using
date +%Y-%m-%dor Pythondatetime— never compute dates manually. Date calculation errors have been a recurring friction point in real sessions.
- Never eyeball date/time math (day-of-week, "yesterday", timezone edges).
Always verify using
- Safety & reviewability – avoid dangerous schema changes, large risky try/except blocks, hidden PII, or untyped payloads.
- Atomic commits – one commit should represent one coherent change; split unrelated work.
- Local harness first – treat
AGENTS.mdas the canonical entrypoint, follow linked repo-local docs and directory-scopedAGENTS.mdfiles for per-topic truth, and do not treatCLAUDE.mdas a unique rule source. - Tooling alignment – use uv wrappers,
.security/*helpers, and.pre-commit-config.yamlhooks as documented, not ad-hoc commands. - Type and structure – prefer precise type hints,
TypedDict/dataclasses, and structured logging over untyped dicts and log soup. - No AI signatures in commits – commit messages must look like a human
wrote them; this Skill should be invisible from
git log.
Always prioritize [BLOCKING] issues over style and nits.
Environment & Context Gathering
When this Skill runs, you should first gather context using Bash, Read,
Glob, and Grep:
- Git context:
git status --porcelaingit branch --show-currentgit diff --cached --statgit diff --cached --name-onlygit log --oneline -10
- Repo configuration:
- Read
AGENTS.mdfirst for repo-specific rules and doc routing. - Load linked repo-local docs relevant to the changed files, especially
quality gates, runbooks, architecture docs, directory-scoped
AGENTS.mdfiles, and any GitHub-first workflow sections covering branch naming, issue linkage, or PR readiness. - If
CLAUDE.mdexists, treat it as a pointer toAGENTS.md, not as a source of unique behavioral rules. - If the harness is missing or obviously stale, recommend generating or
canonicalizing docs via the
repo-docsplugin so rules stop living in tribal knowledge. - Detect
.pre-commit-config.yaml. - Detect
.security/scripts, especially:./.security/gate_cache.sh./.security/ruff_pr_diff.sh./.security/local_imports_pr_diff.sh
- Detect
manage.py/ Django project layout.
- Read
- Tool availability:
uvand.bin/wrappers:.bin/ruff,.bin/ty,.bin/pyright,.bin/mypy,.bin/django,.bin/pytest.
- Detect repo-owned local-ci support only when
local-ciis on PATH and repo root contains.local-ci.toml. - Fallback to
uv runor plainpython/pytest/ruffwhere necessary. - Read local typing policy docs when present (for example:
docs/python-typing-3.14-best-practices.md,TY_MIGRATION_GUIDE.md) and follow them over this default.
If the repo clearly isn’t the Diversio backend / Django4Lyfe style, say so and adjust expectations (but you can still run generic Python pre-commit checks).
Gate cache behavior (when available)
If ./.security/gate_cache.sh exists, treat it as the canonical wrapper for
heavy deterministic checks. Use it by default for type gates and Django checks.
./.security/gate_cache.sh --gate ty-check --scope index -- .bin/ty check .
./.security/gate_cache.sh --gate django-system-check --scope index -- uv run python manage.py check --fail-level WARNING
Use scope=index for commit-focused gating and scope=working when results are
expected to depend on unstaged edits. Do not bypass cache unless explicitly
requested or debugging:
CHECK_CACHE_BUST=1 ./.security/gate_cache.sh --gate ty-check --scope index -- .bin/ty check .
./.security/gate_cache.sh --clear-this-checkout
For Ruff/local-import diff helpers, call the scripts directly. They already use cache-aware execution internally and include local staged/unstaged tracked files. Prefer running them through pre-commit hooks first; call scripts directly only for targeted diagnosis or when a matching hook is missing/disabled.
Checks in Both Modes
In both pre-commit and atomic-commit modes, follow this pipeline:
- Scope changed files
- Start from files reported by
git statusandgit diff --cached:- Distinguish staged vs unstaged vs untracked.
- Categorize by type:
- Python (src vs tests;
optimo_*,dashboardapp,survey, etc.). - Templates (Django HTML).
- Config (YAML, JSON,
.pre-commit-config.yaml,pyproject.toml,requirements*.txt). - Docs/markdown.
- Python (src vs tests;
-
Run pre-commit first (primary execution path)
- If
.pre-commit-config.yamlexists, run hooks on the intended file set before any direct per-tool commands. atomic-commitmode:- run on staged files only:
pre-commit run --files $(git diff --cached --name-only --diff-filter=ACMR)pre-commitmode:- run on modified tracked + untracked files:
CHANGED_FILES="$( { git diff --name-only --diff-filter=ACMR git ls-files --others --exclude-standard } | sed '/^$/d' | sort -u )" pre-commit run --files $CHANGED_FILES- If pre-commit already executed a gate successfully, do not rerun the same gate directly in the same pass.
- If
-
Direct command fallback (targeted, non-duplicative)
- Run direct commands only when:
- a corresponding hook failed and you need focused diagnosis/fix loops, or
- the repository does not expose that gate via pre-commit hooks.
- Keep fetch behavior strict by default (fail closed); only allow
CHECKS_ALLOW_FETCH_SKIP=1when a local skip is explicitly acceptable. - For Ruff/local-import helpers, direct invocation is:
./.security/ruff_pr_diff.sh./.security/local_imports_pr_diff.sh
- These helpers intentionally evaluate the union of
origin/<base>..HEAD, staged, and unstaged tracked Python changes.
- Run direct commands only when:
-
Type checking with active repository gate (ty-first)
- Detect the type gate in this order (unless repo docs/CI explicitly differ):
tyif configured ([tool.ty],ty.toml,.bin/ty, or CI/pre-commit).- Else
pyrightif configured. - Else
mypyif configured.
- Run the active checker on modified Python files only during iteration
when the type hook is not already covered/passing via pre-commit:
# ty example - staged files (atomic-commit mode): .bin/ty check $(git diff --cached --name-only --diff-filter=ACMR | grep '\.py$') # ty example - all modified files (pre-commit mode): .bin/ty check $(git diff --name-only --diff-filter=ACMR | grep '\.py$') # pyright example - staged files (atomic-commit mode): .bin/pyright $(git diff --cached --name-only --diff-filter=ACMR | grep '\.py$') # pyright example - all modified files (pre-commit mode): .bin/pyright $(git diff --name-only --diff-filter=ACMR | grep '\.py$') # mypy example - staged files (atomic-commit mode): .bin/mypy $(git diff --cached --name-only --diff-filter=ACMR | grep '\.py$') # mypy example - all modified files (pre-commit mode): .bin/mypy $(git diff --name-only --diff-filter=ACMR | grep '\.py$') - Scoped checks are for speed only; if the repo/CI requires a wider check before merge/commit, run that gate before final "ready" verdict.
- IMPORTANT: For any file you touch, you must resolve ALL active type-check errors in that file—not just the ones you introduced. If CI checks modified files, pre-existing errors in touched files will still cause failures. Do not dismiss errors as "pre-existing" if the file is in your diff.
- Common pitfall: Fixing ruff ARG002 (unused argument) by prefixing with
_may satisfy ruff but breaktyif the method signature must match a parent class (e.g., Django admin methods). Always run both checks together. - Treat any active type-check errors in modified files as
[BLOCKING]foratomic-commitmode or[SHOULD_FIX]forpre-commitmode.
- Detect the type gate in this order (unless repo docs/CI explicitly differ):
-
Django system checks
- If Django check hook already passed via pre-commit, do not rerun directly.
- Otherwise run through cache wrapper when present:
./.security/gate_cache.sh --gate django-system-check --scope index -- uv run python manage.py check --fail-level WARNING
- If wrapper is missing, run
.bin/django checkor equivalent:uv run python manage.py check --fail-level WARNING.
- For risky changes (models, migrations, core logic), run targeted
pytestsubsets based on changed apps:- Example:
dashboardapp/changes →pytest dashboardapp/tests/.
- Example:
- If tests cannot be run (e.g. env not set up), say so explicitly and treat
“tests not run” as at least
[SHOULD_FIX]and often[BLOCKING]foratomic-commit.
-
Interaction with pre-commit hooks
- If
.pre-commit-config.yamlexists:- Expect hooks to run and modify files (ruff, djlint, interrogate, custom scripts).
- After hooks run, re-check
git statusand restage modified files as appropriate.
- If a hook executable is missing (e.g.
check_prepare_commit_msg_hook.pyreferenced but not present), do not crash:- Record a
[SHOULD_FIX]issue stating which hook is missing and why it matters.
- Record a
- If
-
Convergence loop (do not stop early)
- Treat the pipeline above as iterative, not one-shot.
- You are not done until:
- The relevant pre-commit hooks pass, and
- Ruff/type-gate/djlint/Django checks you ran are green, and
- The index/working tree is stable (hooks are no longer rewriting files).
- Use a tight fix → rerun loop:
- Re-run the smallest scoped failing check on the relevant files.
- Fix only the reported file(s).
- Re-run the same check until it passes.
- Only then advance to the next gate.
- Prefer rerunning only failing hooks/checks on the same file scope, then escalate to wider runs only if required by repo policy.
- If hooks modify files, always re-check
git statusand restage only the intended files (atomic commits should not accidentally grow).
Iteration budgets
- Per-check limit: Do not attempt to fix the same check failure more than 3 times with the same approach. If the same error (or substantively identical error) reappears after 3 real fix attempts, that check is stuck.
- Total pipeline limit: Do not run more than 10 full pipeline passes across the session. After 10, stop and report.
Stuck detection
You are stuck on a check when any of these are true:
- The same error message reappears after you applied a fix for it (your fix is not working or is being reverted by another tool).
- A fix for one tool breaks another in a cycle (e.g., djlint reformats → ruff flags → you fix → djlint re-reformats the same spot).
- You have exhausted the 3-attempt per-check budget.
When stuck:
- Stop attempting that specific fix.
- Report it as
[BLOCKING]with:- The exact error.
- What you tried (briefly).
- Why it is not resolving (tool conflict, unfamiliar pattern, etc.).
- Continue fixing other unrelated issues if any remain.
- In final output, clearly separate "Fixed" from "Stuck / Needs Human".
No TodoWrite for this pipeline
Do not use
TodoWriteorTaskCreateto track individual gate results. This is a fixed, known sequence — not an open-ended task list. Tracking ruff/ djlint/type-check failures as todo items wastes tokens and context window. Report results directly in the final output using the existing severity-tagged sections (Checks run,Needs changes, etc.). -
Repo-owned local-ci validation (when available)
- Only after the normal gates above are green and the tree is stable, run:
local-ci run --no-github - Do this only when
local-ciis on PATH and repo root has.local-ci.toml. - This skill must not publish GitHub statuses or trigger deploy helpers.
- Only after the normal gates above are green and the tree is stable, run:
Backend Fix Rules
When you need concrete auto-fix heuristics, load:
references/backend-taste-and-fix-rules.md
Use that reference when actively editing backend code, templates, logging, types, tests, migrations, or other recurring lint targets. It contains the safe-fix guidance that used to live inline here.
If you discover a recurring failure that is hard to infer from the repo
harness, emit a [SHOULD_FIX] follow-up recommending a docs, wrapper, or CI
improvement instead of letting the rule stay tribal.
Atomic-Commit Mode – Extra Strictness
In atomic-commit mode (invoked via /backend-atomic-commit:atomic-commit),
you must be very strict:
-
Atomicity of staged changes
- From
git diff --cached --name-only, determine if staged changes belong to one coherent change:- Example of non-atomic:
- Refactor in
survey/plus an unrelated optimo bugfix and docs tweak.
- Refactor in
- Example of non-atomic:
- Emit:
[BLOCKING]if the staged set is clearly multiple logical changes.[SHOULD_FIX]for minor opportunistic cleanups that could be split.
- You may suggest a split (e.g. “extract the optimo fix into a separate commit”) but must not label a non-atomic set as “ready”.
- From
-
All gates must be green
- The commit is not ready if any of these fail:
./.security/ruff_pr_diff.sh./.security/local_imports_pr_diff.sh.bin/ruff check/ruff format- active type gate on staged Python files (
ty/pyright/mypy) .bin/django check/manage.py check- Relevant
pytestsubsets for risky changes - Pre-commit hooks defined in
.pre-commit-config.yaml local-ci run --no-githubwhen the repo supports local-ci
- A passing pre-commit hook execution counts as satisfying the matching gate. Do not require duplicate direct-command reruns unless diagnosing failures.
- Where available, heavy gates should run via
./.security/gate_cache.shinstead of ad-hoc direct invocation. - In
--autostyle usage, you may skip conversational confirmation, but you must not relax these gates. - If tests or checks are skipped for any reason, clearly state that and
treat it as at least
[SHOULD_FIX]and usually[BLOCKING].
- The commit is not ready if any of these fail:
-
Commit message generation (no AI signature)
- Read the local repo harness first:
AGENTS.md- linked workflow docs
- any commit-msg hooks that actually enforce a pattern
- Follow the documented repo-local convention instead of inventing a global ticket-prefix rule.
- If the repo only asks for a clear summary, propose a clear summary.
- If the repo uses issue references for traceability, include them only when the repo docs or active hooks expect them.
- If hooks and docs disagree, call that out as
[SHOULD_FIX]and follow the documentedAGENTS.mdconvention for suggestions. - Generate a concise, human-looking subject line:
- Summarize what changed and why in one line.
- Do not mention Claude, AI, this Skill, or plugin names.
- Do not add any footer or signature:
- No “via Claude Code”.
- No “Generated by backend-atomic-commit”.
- Commit messages must look like a human wrote them.
- Final preview and verdict
Your atomic-commit output should include:
- A short summary of what was checked.
Checks run– listing each gate and its status.What’s aligned– strengths and good patterns in the staged changes.Needs changes– bullets with[BLOCKING],[SHOULD_FIX],[NIT].Proposed commit– suggested commit message and list of files.Workflow notes– only when the current branch appears inconsistent with repo-local branch or PR conventions.- An explicit verdict: “✅ Commit ready” only if there are no
[BLOCKING]items; otherwise “❌ Not ready to commit” with concrete next steps.
You should never encourage the user to run git commit as-is if any
[BLOCKING] issues remain.
Workflow boundary: this skill does not own branch creation or PR state by
itself. See references/workflow-boundary.md.
Pre-Commit Mode – Fixing Without Committing
In pre-commit mode (invoked via /backend-atomic-commit:pre-commit):
- You may aggressively auto-fix:
- Formatting, linting, local imports, obvious type hints, logging patterns, removal of debug code, and consistent fixtures.
- You must:
- Run the same gates described above (Ruff,
.security/*, active type gate, Django checks, tests as appropriate). - Re-run or re-stage files modified by tools or hooks.
- Run the same gates described above (Ruff,
- You do not propose a commit or check atomicity.
- Your output should focus on:
Fixes applied– concrete edits you made.Remaining issues– with severity tags.Checks run– which gates passed/failed.
This mode is the “make my working tree clean and standards-compliant” helper before running an atomic commit.
Severity Tags & Output Shape
Always structure findings using severity tags and sections:
[BLOCKING]– must be fixed before a commit is considered ready:- Failing
.securityscripts or pre-commit hooks. - New banned patterns from
AGENTS.md(e.g., Ninja Query constants, legacy survey models). - Obvious multi-tenant or security regressions.
- Non-atomic staged changes in
atomic-commitmode.
- Failing
[SHOULD_FIX]– important, strongly recommended changes:- Style/structure that harms readability or maintainability.
- Missing type hints where types are clear.
- Ambiguous commented code or TODOs without tickets.
- Missing tests for non-trivial new behavior.
[NIT]– minor cleanups:- Docstring tone/punctuation.
- Minor naming and formatting nits not covered by Ruff.
Output shape for both modes:
- 1–3 sentence summary of what was checked.
- Sections (when appropriate):
What’s alignedNeeds changesChecks runHarness follow-ups(only when docs/tooling should be improved)Proposed commit(only in atomic-commit mode)
Be direct, specific, and actionable in each bullet, pointing to file/area and suggesting concrete corrections. Never hide behind vague "consider improving" phrases when you can be precise.
Compatibility Notes
Works in both Claude Code and OpenAI Codex. For installation, see this repo's
README.md.