From 0034f5c6723fac4623dd6f180afddd85c7a61830 Mon Sep 17 00:00:00 2001 From: cgong Date: Fri, 1 May 2026 08:46:22 +1200 Subject: [PATCH] add: pre-commit hooks golden standard (SREP-4485) Adds .pre-commit-config.yaml with Tier 1 common hooks mirroring ci/prow/lint. Golden rules: SREP-4450 Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .claude/commands/pre-commit.md | 94 +++++++++++++++++++++++ .pre-commit-config.yaml | 136 +++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 .claude/commands/pre-commit.md create mode 100644 .pre-commit-config.yaml diff --git a/.claude/commands/pre-commit.md b/.claude/commands/pre-commit.md new file mode 100644 index 0000000..56c2d48 --- /dev/null +++ b/.claude/commands/pre-commit.md @@ -0,0 +1,94 @@ +Run pre-commit hooks on this repository following the agentic SDLC golden rules (SREP-4450). + +## Usage +- `/pre-commit` — run on staged files (default, fastest) +- `/pre-commit --all-files` — run on all files (first-time setup, CI equivalent) +- `/pre-commit ` — run a single hook by ID (targeted debugging) + +## What you must do + +### Step 1 — Preflight checks + +1. Confirm `.pre-commit-config.yaml` exists in the repo root. If not, tell the user and stop. +2. Confirm `pre-commit` is installed: run `which pre-commit`. If not found, run `pip install pre-commit` or `pip3 install pre-commit`. +3. Confirm hooks are installed: check if `.git/hooks/pre-commit` exists. If not, run `pre-commit install`. + +### Step 2 — Run hooks + +Determine the run mode from `$ARGUMENTS`: +- `--all-files` → run `pre-commit run --all-files` +- `` (a word that is not a flag) → run `pre-commit run ` +- empty or default → run `pre-commit run` (staged files only) + +Capture the full stdout and stderr output. + +### Step 3 — Parse and categorise results + +For each hook in the output, classify it as one of: +- **Passed** — hook exited 0, no changes +- **Auto-fixed** — hook exited non-zero but modified files (trailing-whitespace, end-of-file-fixer) +- **Failed** — hook exited non-zero, no auto-fix + +Extract for each failure: +- Hook ID and name +- Affected files and line numbers if present +- The error message +- Whether it is a security hook (gitleaks, rbac-wildcard-check) + +### Step 4 — Handle auto-fixes (idempotency loop, golden rule 9) + +If any hooks auto-fixed files: +1. Stage the modified files: `git add ` +2. Re-run the hooks on staged files +3. Report what was fixed + +### Step 5 — Retry on failure (golden rule 19, max 2 iterations) + +Track `attempt_count` starting at 1. + +For each non-security failure with an identifiable fix: +1. Apply the fix (edit the file, run the suggested command) +2. Stage the changes +3. Re-run `pre-commit run` +4. Increment `attempt_count` + +**Stop retrying when:** +- All hooks pass → report success +- `attempt_count` reaches 3 → stop, escalate to human (see Step 6) +- A security hook fails → stop immediately, escalate to human (see Step 6) + +### Step 6 — Escalate to human when required + +Escalate (do not retry further) when: +- A **security hook** fires (gitleaks, rbac-wildcard-check) — these require human judgment +- Hooks still fail after **2 fix-and-retry attempts** +- A hook **timed out** — this indicates a systemic issue, not a fixable code problem + +When escalating, report: +- Which hook is failing +- The exact error output +- What was already attempted +- The recommended next action for the human + +### Step 7 — Final report + +Always end with a structured summary: + +``` +PRE-COMMIT SUMMARY +================== +Passed: +Auto-fixed: → files staged +Fixed: → changes applied +Failed: → escalated to human +Attempts: of 2 maximum +``` + +## Rules you must never break + +- **Never run `git commit --no-verify`** — bypassing all hooks is not permitted +- **Never modify `.pre-commit-config.yaml`** to suppress a failing hook +- **Never retry more than twice** — escalate on the third failure +- **Never auto-fix a security hook failure** — always escalate to human +- **Always stage auto-fixed files** before re-running — do not leave unstaged modifications +- **Always report what changed** — the human must be able to review every fix you applied diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..763c048 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,136 @@ +# ============================================================================= +# Pre-Commit Hooks — ocm-agent +# SREP-4485 | Golden rules: SREP-4450 +# ============================================================================= +# +# INSTALL +# pip install pre-commit +# pre-commit install +# +# USAGE +# pre-commit run # staged files only (developer / agent workflow) +# pre-commit run --all-files # full repo (CI / first-time setup) +# +# BYPASS (golden rule 16) +# Skip one hook: SKIP=hook-id git commit +# Never use: git commit --no-verify +# Agents: never bypass any hook +# Security hooks: never bypassable under any circumstances +# +# CI RELATIONSHIP (golden rule 17) +# These hooks mirror ci/prow/lint. CI remains the authoritative gate. +# Every check here also runs in CI. Pre-commit is developer convenience. +# +# AGENT USAGE (golden rule 1, 7, 19) +# Agents run: pre-commit run +# Output: PRE_COMMIT=1 is set automatically — hooks emit structured output +# Retry: max 2 fix-and-retry iterations before escalating to human +# +# TIMING TARGETS (golden rule 2, 3) +# Total run: <= 10s target / <= 60s hard limit on a 10-file changeset +# Hooks run fastest-first (golden rule 13). Each hook has a timeout guard. +# +# FIRST RUN NOTE +# Auto-fix hooks (trailing-whitespace, end-of-file-fixer) will correct +# pre-existing violations on the first run. Stage and commit those fixes +# separately before day-to-day use. +# +# ============================================================================= + +repos: + + # --------------------------------------------------------------------------- + # 1. FILE HYGIENE | target < 1s | auto-fix | golden rules 3, 8, 9 + # --------------------------------------------------------------------------- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 # pinned immutable tag (golden rule 15) + hooks: + - id: check-merge-conflict # error: merge markers must never exist + - id: trailing-whitespace # auto-fix: removes trailing spaces + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer # auto-fix: ensures single EOF newline + + # --------------------------------------------------------------------------- + # 2. YAML SYNTAX | target < 2s | error | golden rules 3, 10 + # Mirrors ci/prow/lint: olm-deploy-yaml-validate (yaml.safe_load_all) + # --------------------------------------------------------------------------- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-yaml + name: YAML syntax (deploy/) + files: ^deploy/.*\.ya?ml$ + args: [--allow-multiple-documents] + + # --------------------------------------------------------------------------- + # 3. SECRETS DETECTION | target < 5s | always blocking | golden rules 3, 12 + # Scans all file types (YAML, shell, config) — gosec covers Go only. + # High-confidence findings block; configure .gitleaks.toml for allowlist. + # --------------------------------------------------------------------------- + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.0 # pinned immutable tag (golden rule 15) + hooks: + - id: gitleaks + + # --------------------------------------------------------------------------- + # 4. STATIC ANALYSIS | target < 15s cached | error | golden rules 3, 5, 10 + # Mirrors ci/prow/lint: go-check exactly (same version + config as CI). + # Linter config detail: boilerplate/openshift/golang-osd-operator/golangci.yml + # --------------------------------------------------------------------------- + - repo: https://github.com/golangci/golangci-lint + rev: v2.0.2 # pinned immutable tag — must match CI (golden rule 15) + hooks: + - id: golangci-lint + args: + - --config=boilerplate/openshift/golang-osd-operator/golangci.yml + - --timeout=120s # graceful timeout (golden rule 3) + + # --------------------------------------------------------------------------- + # Local hooks — compile, dependency, security + # + # TIMEOUT NOTE (golden rule 3) + # Uses portable timeout detection: 'timeout' on Linux, 'gtimeout' on macOS. + # macOS: brew install coreutils + # Linux: timeout is available by default (GNU coreutils) + # --------------------------------------------------------------------------- + - repo: local + hooks: + + # ----------------------------------------------------------------------- + # 5. COMPILE CHECK | target < 10s cached | error | golden rules 3, 4 + # Catches import cycles and type errors before golangci-lint runs. + # Fix: resolve compilation errors reported by go build. + # Timeout: 30s (2x per-hook limit for macOS/slow cache warm-up) + # ----------------------------------------------------------------------- + - id: go-build + name: go build + language: system + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 30s} go build ./...' + types: [go] + pass_filenames: false + + # ----------------------------------------------------------------------- + # 6. DEPENDENCY DRIFT | target < 10s | error | golden rules 3, 5, 8 + # Detects uncommitted go.mod/go.sum changes after go mod tidy. + # Fix: run 'go mod tidy' and stage go.mod and go.sum. + # Timeout: 60s (2x per-hook limit; first run may download modules) + # ----------------------------------------------------------------------- + - id: go-mod-tidy + name: go mod tidy + language: system + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 60s} go mod tidy && git diff --exit-code go.mod go.sum' + files: go\.(mod|sum)$ + pass_filenames: false + + # ----------------------------------------------------------------------- + # 7. RBAC WILDCARD CHECK | target < 5s | always blocking | golden rules 3, 12 + # Rejects verbs: ["*"] or resources: ["*"] in deploy/ YAML manifests. + # Critical for AI-generated RBAC — agents over-provision by default. + # Fix: replace wildcard with explicit verbs and resource names. + # ----------------------------------------------------------------------- + - id: rbac-wildcard-check + name: RBAC wildcard permissions + language: system + entry: "python3 -c \"import sys,glob;violations=[(f,n,l.rstrip()) for f in glob.glob('deploy/*.yaml')+glob.glob('deploy/*.yml') for lines in [list(enumerate(open(f),1))] for i,(n,l) in enumerate(lines) if l.strip().lstrip('- ').strip(chr(39)+chr(34))=='*' and any(lines[j][1].strip() in ('verbs:','resources:') for j in range(max(0,i-5),i))];[print(v[0]+'|'+str(v[1])+'|'+v[2]) for v in violations];sys.exit(int(bool(violations)))\"" + files: ^deploy/.*\.ya?ml$ + pass_filenames: false