pre-commit-validation skill
Commit-boundary validation ("git police") — verify the staged changeset is clean, coherent, and safe on a shared multi-session workstation. Expected-changeset staging hygiene, secrets scan, diff sanity, concurrent-commit detection, and committing on the local branch (never building on the remote and pushing around it). Loaded by the implement skills at commit time, before presenting or creating any commit.
- GitHub stars
- 7
- Files
- 1
- Last commit
- Nov 14, 2026
- License
- MIT
- Allowed tools
- —
What does the pre-commit-validation skill do?
pre-commit-validation is an agent skill. Commit-boundary validation ("git police") — verify the staged changeset is clean, coherent, and safe on a shared multi-session workstation. Expected-changeset staging hygiene, secrets scan, diff sanity, concurrent-commit detection, and committing on the local branch (never building on the remote and pushing around it). Loaded by the implement skills at commit time, before presenting or creating any commit.
How to install the pre-commit-validation skill
Each command copies the skill folder into the client's skills directory. Restart the client or start a new session to load it.Install the pre-commit-validation skill in Plugin
/plugin marketplace add EnsoDynamics/enso-method /plugin install enso-method@enso-method
Install the pre-commit-validation skill in Claude Code
~/.claude/skills/pre-commit-validation/SKILL.md
$ npx degit EnsoDynamics/enso-method/skills/pre-commit-validation ~/.claude/skills/pre-commit-validationInstall the pre-commit-validation skill in Claude Code (project)
.claude/skills/pre-commit-validation/SKILL.md
$ npx degit EnsoDynamics/enso-method/skills/pre-commit-validation .claude/skills/pre-commit-validationInstall the pre-commit-validation skill in Codex
~/.agents/skills/pre-commit-validation/SKILL.md
$ npx degit EnsoDynamics/enso-method/skills/pre-commit-validation ~/.agents/skills/pre-commit-validationInstall the pre-commit-validation skill in Gemini CLI
~/.gemini/skills/pre-commit-validation/SKILL.md
$ npx degit EnsoDynamics/enso-method/skills/pre-commit-validation ~/.gemini/skills/pre-commit-validationInstall the pre-commit-validation skill in Claude.ai
npx degit EnsoDynamics/enso-method/skills/pre-commit-validation pre-commit-validation && zip -r pre-commit-validation.zip pre-commit-validation # claude.ai → Customize → Skills → + → Create skill → Upload a skill → pre-commit-validation.zip
SKILL.md
View on GitHubPre-Commit Validation ("Git Police")
Metadata
- Type: reference (loaded by the implement skills at commit time)
- Trigger: Follow these guidelines at the commit boundary — after testing, deliverable review and test hardening, before committing
- Purpose: Ensure the staged changeset is clean, coherent, and safe — especially important on a shared workstation with concurrent sessions
Why This Exists
This runs in a checkout where several agent sessions (Claude Code, Codex or others), manual edits, and other development work can happen concurrently in the same repo and branch. Files can appear in the staging area or working tree from unrelated work at any moment. Another session can commit to the same branch while you're validating. The autonomous workflow cannot assume that everything visible in git status belongs to it, or that the branch state is stable.
The deliverable review validates that the work is correct. This validates that the commit is correct — that only the right files are in it, nothing dangerous is in the diff, and the commit is a clean atomic unit. These are different concerns, and the state of the working tree can change between when the deliverable review runs and when you commit.
This is not optional. Every commit — interactive or autonomous — must follow these steps.
Who Commits
The implement chain commits its own work by default, and pushes it when the push lands on a feature branch. A session running implement-from-requirements or implement-from-discovery commits the feature work once these checks pass, runs refactor-pass (which makes its own refactor(scope): commit when it changes anything), then pushes under the default push below. refactor-pass run on its own follows the same default, and so does test-audit, whose output is tests and code like the chain's; it runs no refactor pass. The default covers finished work: the full suite green apart from failures that predate this work, and the session ending in any terminal state but Blocked (implementation-lifecycle, "Terminal States"). A Blocked session stages its work and presents the commit for the user.
The requirements skills commit their documents by default, and never push by default. requirements-intake (the material it brings into source-material/, then its record), initiative-breakdown, prd-writing-standards (the draft), autonomous-prd-refinement (or, for a small PRD, the one deliverable-review round that stands in for it), technical-design-writing-standards (the draft), review-technical-design (its revision), autonomous-design-refinement (each pass, its completion and its consolidation), phase-split run on its own, and delivery-audit (its record commit: completion phases, criterion amendments, or an empty commit when it changed nothing) commit their own documents once these checks pass. Each stage's change is then one diff that can be read or reverted alone, and a run nobody is watching leaves its work in history rather than in a shared index. A later push of the branch carries them. The stages using-the-enso-method reads from history, the PRD refinement (or a small PRD's one review round), the design review and the delivery audit, still commit when they ran and changed no file, empty, under their usual subject, so it can see they ran: git commit --allow-empty --only -m "<message>" commits nothing else and leaves another session's staged files staged.
Every other skill that ends at a commit — deliverable review (other than a small PRD's refinement round) or test hardening run on their own — stages its files, runs these checks, and presents the commit message for the user to make.
The user's instructions override either default, in this order: what the user says in this chat or in the prompt that started it, then the user's instructions file (a global or repository AGENTS.md or CLAUDE.md; where two disagree, the more restrictive wins), then the skill's default. "Don't commit until I say so" stops the implement chain or a requirements skill at a staged changeset with its message presented; "don't push" stops it after its commits; "commit and push this", or a prompt from an orchestrator that authorizes pushing, lets any skill do both, including a push to an integration branch. A phase's **Early merge:** line authorizes that one merge when the permission is recorded in the technical design's Delivery section (implementation-lifecycle, "One Branch for a Split PRD"). For the skills that present their commit, finishing the work or passing every check is not an instruction to commit.
What the default push is. git push of the current branch to its upstream (a branch with no upstream goes to origin under the same name, with -u), after Step 9's own-commits check — only when the push lands on a feature branch. A push that would land on an integration branch — develop, main, master, any branch the repository's instructions name as shared, or any branch whose push releases to production — is held unless the user or an orchestrator authorized it. That covers a shared checkout on develop and a worktree chat landing its work on develop alike (implementation-lifecycle, "Parallel Sessions: Where to Work"). A held push stops after the commits and says so in one line ("committed, not pushed — say push when ready"), so the user reviews before anything reaches the shared branch; the commits stay local and revertible meanwhile. The risk is what a push publishes, not where the chat runs. Never a force push, never another branch, never a merge or pull request. A split PRD's closing pull request and a named early merge are separate steps (implementation-lifecycle, "One Branch for a Split PRD"). In the shared checkout, a rejected push is reported with the commits left local.
Another session's staged files stay staged. Never unstage a file you did not stage: the shared index is how each session hands its commit to the user, and unstaging silently undoes another chat's hand-off. Stage your own files beside them and limit the commit to your paths — git commit -m "<message>" -- <your paths> — which commits exactly those files and leaves the rest staged. If another session has also changed one of your files, git cannot split the two sets of edits: say so rather than committing either. Whether the session commits or hands the commit to the user, it uses the same path-limited commit (or Step 9's temporary index).
When to Follow These Guidelines
At the point in your workflow where you're ready to stage and commit — after deliverable review passes and before committing or presenting the commit message. In implement-from-requirements and implement-from-discovery, it's the Phase 8 wrap-up step that stages and commits. This skill encompasses staging; there is no separate staging step.
Steps
Step 1: Record HEAD and Inventory What You Changed
Bring the local branch current first: Run git pull --ff-only before anything else (in a worktree, git fetch and rebase onto origin/<branch> instead), so the moved-HEAD check below sees the other sessions' commits. On a split PRD's branch, git fetch and git merge --ff-only @{u} instead, and never rebase. If the pull refuses, see "If the local branch cannot fast-forward" in Step 9 and stop there. A branch with no upstream has nothing to pull: skip this.
Record the current HEAD: Run git rev-parse HEAD and note the hash. You'll need this later to detect if another session committed while you were validating.
Build your expected changeset: Before looking at git status, make a list from memory of every file you intentionally modified, created, or deleted during this task. Be specific — full relative paths from the repo root. File renames count as a deletion + an addition; include both paths.
For a typical fix, common expected files include:
- The source code file(s) you changed
- Assumptions document (if updated)
Step 2: Check the Staging Area
Run git status. Before staging anything, check for contamination:
- Files that are already staged but aren't in your expected changeset — another session or manual work staged these. Leave them staged (see "Who Commits"). Do NOT commit them. Do NOT silently include them: your commit is limited to your own paths.
- Files that are modified but unstaged and aren't in your expected changeset — someone else's work-in-progress. Leave them alone. Do not stage, do not revert.
- Files you expected to see modified but that aren't showing up — Did you forget to save? Did another process revert your change? Investigate before proceeding.
- Merge conflict markers in any file — if
git statusshows merge conflicts, or if any file contains<<<<<<<,=======,>>>>>>>markers, do NOT proceed. Resolve conflicts, re-stage, and restart from Step 1.
Step 3: Stage Your Files
Stage only the files from your expected changeset. Use explicit file paths — never git add . or git add -A.
Step 4: Verify Change Coherence
Now that your files are staged, verify each one belongs:
- For each of your staged files, state why it's in the commit. If you can't articulate the connection to the task in one sentence, it doesn't belong.
- Would reverting this commit cleanly undo exactly one logical change? If reverting would also undo unrelated work, the commit is polluted.
- Version bump, if required. If the repo's own instructions require a version bump with each change, verify it is staged.
Step 5: Scan the Staged Diff for Secrets
Run git diff --cached -- <your paths> and scan every addition (lines starting with +) for:
- API keys, tokens, passwords (patterns like
sk-,Bearer,password=,secret=,token=,key=followed by what looks like an actual value rather than a variable reference) .envfile contents or environment variable assignments with real values- AWS access keys (
AKIA...), secret keys, session tokens - Private keys (
-----BEGIN) - Connection strings with embedded credentials
- Password-manager references that resolved to actual secrets instead of reference URIs
- Hardcoded URLs with authentication parameters
If anything suspicious is found: stop. Remove the secret from the source file, re-stage, and verify the diff is clean before proceeding. Flag it to the user in both interactive and autonomous mode — even if you can fix it mechanically, the user should know it happened so they can investigate how a secret ended up in the code.
Step 6: Diff Sanity Check
Review git diff --cached -- <your paths> with fresh eyes for artifacts that shouldn't be committed:
- Debugging artifacts —
print()statements,console.log(),import pdb,breakpoint(), temporary exploration scripts or code you added during investigation - Commented-out code you added — if code was intentionally removed as part of the fix, remove it cleanly rather than commenting it out. Exception: if commenting out code IS the fix (e.g., temporarily disabling a problematic feature), that's fine as long as the re-enablement is recorded in the governing PRD or phase document.
- Unintended whitespace changes — large whitespace-only diffs suggest an editor reformatted a file you only meant to touch in one place; revert the whitespace changes if unintentional
- TODO/FIXME/HACK comments you added — follow-up work goes into the governing docs per
implementation-lifecycle's disposition rule (a future phase document, the Technical Design, or the assumptions document), not buried in code comments
Step 7: Commit Message Format Check
Verify the commit message follows the project's convention before presenting it — its house engineering standards, or failing that the style of recent git log. Where neither sets one, use:
- Format:
type(scope): description - Common types:
feat,fix,docs,refactor,test,chore - Scope should identify the component (e.g.,
user-accounts,notifications,billing-sync) - Description should describe what actually changed — not reference ephemeral project artifacts as the primary subject
- Ticket or issue references (e.g.,
(PROJ-123)) go at the end as a suffix - In autonomous mode, include
Co-Authored-By:trailer with the current model
Step 8: Final Staging Check
Right before committing (or presenting the commit to the user), do one last verification:
- Run
git diff --cached --name-onlyand compare against your expected changeset. If any file appears that you didn't stage, another session staged it between your Step 2 check and now. Leave it staged and keep it out of your commit — the path-limited commit ("Who Commits") does that. - Run
git rev-parse HEADand compare against the hash you recorded in Step 1. If HEAD has changed, another session committed while you were validating. In this case:- Your commit will still work (git handles concurrent commits to the same branch), but be aware that your changes sit on top of untested work
- In autonomous mode, if the other commit touched the same files, flag this to the user rather than committing blindly
This step exists because the staging area is shared state that can change at any time on a multi-session workstation. Steps 2-6 are not atomic — this final check catches anything that slipped in between.
Step 9: Commit on the Local Branch, and Push Only From It
The commit's parent is the local branch, and the local branch moves to it. The local branch was brought current in Step 1.
-
A path-limited commit or a temporary index is fine; a different parent is not. The path-limited commit ("Who Commits") keeps another session's staged files out. So does committing through a temporary index built from
HEAD:export GIT_INDEX_FILE=$(mktemp),git read-tree HEAD,git add <your paths>,git commit, thenunset GIT_INDEX_FILE. Agent tools usually start a fresh shell per call, so anexportdoes not survive to the next call: run the whole sequence as one command, or record the temp path once and prefix every git command in it withGIT_INDEX_FILE=<path>.git commitbuilds on the current branch and advances it safely even if another session commits at the same moment. Steps 2, 3, 5 and 8 then run against that temporary index rather than the shared one. After unsetting it, rungit reset -q -- <your paths>so the shared index matches the newHEAD; otherwisegit statusshows your own commit as staged changes in reverse. -
Never build a commit on
origin/<branch>and push it by refspec (git push origin <sha>:refs/heads/<branch>). The commit reaches the remote, but the checkout's branch never moves, so every file the user opens shows the old state. In one real case this left a shared checkout dozens of commits behind, and the user saw a finished item as unfinished in their own editor. -
The one exception is a chat working in a worktree (
implementation-lifecycle, "Parallel Sessions: Where to Work"). A worktree cannot check out the shared branch, so the chat commits on its ownwt/<slug>branch and lands each piece by pushinggit push origin HEAD:<branch>as a fast-forward after rebasing. It then fast-forwards the shared checkout (git -C <shared checkout> merge --ff-only origin/<branch>) so the editor shows the work; that fast-forward is what keeps this from being the failure above. If the push is rejected, fetch, rebase, re-test and push again, and never force. If the fast-forward is refused, say so in the wrap-up. In a worktree the index is private, so the temporary index above is unnecessary. A chat on a split PRD's branch commits and pushes that branch and lands nothing (implementation-lifecycle, "One Branch for a Split PRD"). -
Push only when the default or the user's instructions call for it ("Who Commits"), and only your own commits. Before pushing, run
git log --oneline @{u}..HEAD(git log --oneline HEAD --not --remoteson a branch with no upstream). If any commit listed is not yours, another session has unpushed work on this branch; stop and tell the user rather than publishing it with yours. On a split PRD's branch, rungit log --oneline HEAD --not --remotesinstead, and count commits by earlier chats on this PRD as yours. -
If the local branch cannot fast-forward, stop and tell the user. Name the cause and let the user decide. It is one of three:
- another session's modified or untracked files are in the way: list them, and for each say whether it already matches the remote (
git diff --quiet origin/<branch> -- <file>); - your own uncommitted edits touch files the remote changed;
- the branch has diverged (local commits not on the remote, and the remote has moved on).
Do not route around the local branch, and do not delete, revert or stash another session's files yourself.
- another session's modified or untracked files are in the way: list them, and for each say whether it already matches the remote (
There is no exception to this step beyond the worktree case above. The known cases that tempted a session to push around the local branch were all blocked fast-forwards, and those have their own answer above.
Output
Produce a brief validation summary as part of the commit presentation:
Pre-commit validation:
- Staged files: [list of files, each with one-line justification]
- Contamination check: [clean | N files staged by other work, left staged and excluded — list them]
- Secrets scan: clean
- Version bump: [verified | N/A — only if the repo requires one]
- Diff review: clean [or list what was fixed]
- Commit message: fix(scope): description ✓
- Final staging check: [clean | issues found — describe]
- Local branch: current with remote before commit; commit built on HEAD [push: only own commits in @{u}..HEAD (no upstream: HEAD --not --remotes) | not pushed — <reason>]
When the user commits (a skill that presents its commit, unless the user told it to commit; a Blocked session; or the user said not to commit): Include this summary alongside the testing evidence when presenting the commit message to the user, and give the path-limited command to run: git commit -m "<message>" -- <your paths>.
When this session commits (the implement chain's or a requirements skill's default, or the user told it to): If all checks pass, announce the repo and message, then commit with the path-limited command (or Step 9's temporary index, or, for a stage that changed nothing, the empty commit in "Who Commits"). If any check fails and can be fixed mechanically (a debug print left in), fix it and re-validate. If a check fails and requires judgment (suspicious content, files you can't explain, HEAD moved with overlapping changes), stop and flag to the user.
What This Does NOT Do
- Does not run tests — that's the calling workflow's responsibility
- Does not validate business logic — that's the deliverable review's job
- Does not choose which branch to work on — that is the user's branching rule; this skill only requires that the commit lands on the local branch (Step 9)
- Does not replace human review — this is a safety net, not a substitute for the user looking at the commit in interactive mode
Relationship to Other Skills
| Skill | Relationship |
|---|---|
implement-from-requirements / implement-from-discovery |
Callers — follow these guidelines before committing or presenting the final commit |
deliverable-review |
Complementary — deliverable review validates work quality earlier in the pipeline; this validates commit hygiene at the commit boundary, after which the working tree state may have shifted due to concurrent work |
engineering-principles |
Referenced — its "Git Safety When Several Sessions Share One Clone" is the underlying rule for the staging checks here; coding standards come from it plus the house engineering standards the project names |
Files in this skill
1pre-commit-validation skill questions
How do I install the pre-commit-validation skill in Claude Code?
Run npx degit EnsoDynamics/enso-method/skills/pre-commit-validation ~/.claude/skills/pre-commit-validation. Claude Code loads skills from ~/.claude/skills (all projects) or .claude/skills (one project) and uses a skill when its description matches the task.
Which clients can use the pre-commit-validation skill?
Any client that reads the Agent Skills format: Claude Code, Codex, Gemini CLI and claude.ai (upload as a ZIP). Scripts in the skill run only where the client allows code execution.