Drop-in pre-commit hook for running Agents Shipgate locally on every commit that touches a tool-surface artifact.
The canonical hook manifest lives at the repository root (/.pre-commit-hooks.yaml) — that's where pre-commit looks when a downstream repo points at this project. This directory only contains the longer write-up.
In your repo's .pre-commit-config.yaml:
repos:
- repo: https://github.com/ThreeMoonsLab/agents-shipgate
rev: v1.2.0
hooks:
- id: agents-shipgatepre-commit autoupdate will keep the rev: pin current. pre-commit clones this repo, reads /.pre-commit-hooks.yaml from its root, installs the agents-shipgate package, and invokes the binary.
Three hook IDs are exposed from the root manifest:
| Hook ID | Mode | Stage(s) | When it fires |
|---|---|---|---|
agents-shipgate |
advisory (never blocks) | pre-commit, pre-push |
Any staged tool-surface artifact |
agents-shipgate-strict |
strict (--fail-on critical) |
pre-push |
Any staged tool-surface artifact |
agents-shipgate-validate |
manifest doctor only | pre-commit |
Only shipgate.yaml |
Pick one based on whether you want the commit/push to block (-strict) or just surface findings (agents-shipgate).
For repos that prefer to manage the agents-shipgate install themselves (e.g., via pipx install agents-shipgate in a setup step), use a repo: local entry that calls the binary directly:
repos:
- repo: local
hooks:
- id: agents-shipgate
name: Agents Shipgate release-readiness gate
entry: agents-shipgate verify --config shipgate.yaml --ci-mode advisory --format text
language: system
pass_filenames: false
# pre-commit's default `types: [file]` drops a tracked symlink
# before `files:` runs; governance paths can be symlinks.
types: []
types_or: [file, symlink]
files: |
(?ix)^(
(.*/)?shipgate\.yaml|
.*tools.*\.json|
.*mcp.*\.json|
.*n8n.*\.json|
(.*/)?\.n8n(/.*)?|
(.*/)?conductor/.*\.json|
(.*/)?ai/examples/.*\.json|
(.*/)?\.codex/(config\.toml|hooks\.json|requirements\.toml)|
(.*/)?\.claude/(settings(\.local)?\.json|commands(/.*)?|hooks/hooks\.json)|
(.*/)?\.cursor/(cli\.json|mcp\.json|rules(/.*)?)|
(.*/)?\.vscode/mcp\.json|
(.*/)?\.shipgate/(agent-contract|openshell)\.json|
(.*/)?(AGENTS(\.override)?|CLAUDE)\.md|
\.(agents|claude)/skills/.*|
(.*/)?\.codex-plugin(/.*)?|
(.*/)?\.agents/plugins(/.*)?|
.*\.app\.json|
(.*/)?SKILL\.md|
.*openapi.*\.(yaml|yml|json)|
.*swagger.*\.(yaml|yml|json)|
\.agents-shipgate/.*\.json|
(.*/)?prompts(/.*)?|
(.*/)?policies(/.*)?|
(.*/)?\.github/workflows/.*\.(yaml|yml)
)$The files: regex in /.pre-commit-hooks.yaml covers the path leg of every trigger in docs/triggers.json that a path alone can decide, so the hook activates when a staged change touches:
shipgate.yaml(TRIGGER-SHIPGATE-MANIFEST)- MCP exports —
**/*mcp*.json,.agents-shipgate/*.json(TRIGGER-MCP-EXPORT-CHANGED) - OpenAPI/Swagger specs —
**/*openapi*.{yaml,yml,json},**/*swagger*.{yaml,yml,json}(TRIGGER-OPENAPI-SPEC-CHANGED) - Static tool inventories —
**/*tools*.json(TRIGGER-STATIC-TOOL-INVENTORY-CHANGED) - n8n workflow JSON —
**/*n8n*.json,**/.n8n/**(TRIGGER-N8N-WORKFLOW-CHANGED, path leg) - Conductor OSS workflow JSON —
**/conductor/**/*.json,**/ai/examples/**/*.json(TRIGGER-CONDUCTOR-WORKFLOW-CHANGED, path leg) - Coding-agent host configuration and instructions — Codex/Claude/Cursor/VS Code config,
AGENTS.md,AGENTS.override.md,CLAUDE.md, skills (TRIGGER-*-BOUNDARY-*) - Codex plugin package files —
.codex-plugin/**,.agents/plugins/**,**/.app.json,**/.mcp.json,**/SKILL.md(TRIGGER-CODEX-PLUGIN-CHANGED) - Prompts and policies —
prompts/**,policies/**(TRIGGER-PROMPTS-OR-POLICIES) - Shipgate CI workflow —
.github/workflows/agents-shipgate.{yml,yaml}(TRIGGER-SHIPGATE-CI-WORKFLOW, path leg)
Matching is case-insensitive, and every clause whose catalog glob is recursive is recursive too: services/foo/policies/refund.yaml, enterprise/lib/captain/Prompts/system.md, and a nested AGENTS.md all stage the same as a repo-root copy. A dir/** glob also matches a tracked path named exactly dir, so the regex does too. That coverage claim is pinned exhaustively — a catalog rule with a path leg must be listed in the hook fixture table or in its documented exclusion set, so a new rule cannot land uncovered and unnoticed.
pre-commit's files: regex is purely path-based; it cannot inspect diff content. Several triggers in docs/triggers.json need the diff body and are therefore not covered by this hook:
TRIGGER-FUNCTION-TOOL-DECORATOR— fires when@function_tool,@tool, orFunctionTool(is added to a diff.TRIGGER-GOOGLE-ADK-AGENT-TOOLS-CHANGED— fires when agoogle.adkmodule path, anAgent(/LlmAgent(construction, and atools=[...]argument all appear in a diff.TRIGGER-FRAMEWORK-VERSION-BUMP— fires whenopenai-agents,langchain,crewai,google-adk, orconductor-ossappears in the diff and a dependency manifest (pyproject.toml,requirements*.txt,package.json, a lockfile, …) is among the changed files. The path leg alone can't decide it, so the regex can't either.TRIGGER-SHIPGATE-CI-WORKFLOWalso has a diff-only leg (ThreeMoonsLab/agents-shipgatestring match) that this hook doesn't see.
For full trigger coverage on PRs, rely on the GitHub Action — it runs agents-shipgate verify on the configured event and evaluates the full trigger catalog (including the diff-only legs) itself, so diff content doesn't gate discovery the way the path-based files: regex does. For local diff-aware checks, python -m agents_shipgate.triggers --git-diff HEAD (or --diff-text "...") evaluates the full catalog against any file set or diff payload.
A pure docs/test commit doesn't trigger the scan — same semantic as the AGENTS.md trigger table (TRIGGER-DOCS-ONLY-NEGATIVE).
Use the agents-shipgate-strict hook ID for the strict variant, or override the entry: in your .pre-commit-config.yaml:
repos:
- repo: https://github.com/ThreeMoonsLab/agents-shipgate
rev: v1.2.0
hooks:
- id: agents-shipgate
entry: agents-shipgate verify --config shipgate.yaml --ci-mode strict --fail-on critical --format textPair strict mode with a baseline (baseline.md) so existing accepted findings don't fail every commit.
The pre-commit hook and the GitHub Action are independent. The hook gives you a fast local check; the Action is the authoritative gate on PR. Most teams run both — the hook catches obvious regressions before push, the Action enforces the team-wide policy on the merge.
See docs/integrations.md for the GitHub Action recipe.