From 27e5eec28307ff88ad76ecf6d7c9649b392368d1 Mon Sep 17 00:00:00 2001 From: Min Huang <70873102+min0625@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:04:42 +0000 Subject: [PATCH] ci: run the full prek hook suite as the gate .pre-commit-config.yaml becomes the single source of what gets checked: `make check` is now `prek run --all-files`, and check-tidy/lint/test are hooks in there alongside prek's builtin hygiene checks and gitleaks. Hooks that would have run green without checking anything are pinned down: check-added-large-files gets --enforce-all and check-merge-conflict gets --assume-in-merge, since both otherwise consult git state (staged files, mid-merge) that CI's clean checkout never has. gitleaks keeps its --staged entry on purpose and is documented as a local commit-time guard, with GitHub push protection as the server-side net. The three local hooks carry always_run: true so a commit that only deletes files can't skip lint and test on an empty file list. A shared check-rev target makes `make lint` and `make fix` hard-fail on an unresolvable NEW_FROM_REV -- golangci-lint only warns and exits 0 there, which would let CI pass having linted nothing. Co-Authored-By: Claude Opus 5 --- .github/workflows/pr-check.yml | 7 +++ .pre-commit-config.yaml | 68 ++++++++++++++++++++++++-- .serena/memories/memory_maintenance.md | 8 +-- AGENTS.md | 22 +++++++-- Makefile | 19 +++++-- 5 files changed, 109 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 5626fb9..09b5037 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -42,6 +42,13 @@ jobs: key: golangci-lint-${{ runner.os }}-${{ hashFiles('mise.toml', '.golangci.yaml') }} restore-keys: golangci-lint-${{ runner.os }}- + - name: Cache prek hook environments + uses: actions/cache@v4 + with: + path: ~/.cache/prek + key: prek-${{ runner.os }}-${{ hashFiles('mise.toml', '.pre-commit-config.yaml') }} + restore-keys: prek-${{ runner.os }}- + - name: Download Go dependencies run: go mod download diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 524680d..9296981 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,71 @@ +# A green hook is not proof it checked anything: `--all-files` only guarantees prek +# hands the hook a file list, not that the hook consults it. Some check git state +# instead (staged files, mid-merge) and CI's clean checkout has neither -- so they +# run, pass, and weigh nothing. Read a hook's source before adding it. repos: + # prek's built-in Rust implementations of the pre-commit-hooks checks. Naming + # the upstream repo URL here would run the same native code anyway (prek's + # fast path ignores `rev`), but would also clone the repo and build a Python + # environment that is never executed. prek-only: upstream `pre-commit` + # rejects this config with `Missing required key: rev`. + - repo: builtin + hooks: + - id: check-added-large-files + # Without this the hook intersects its file list with `git diff --staged + # --diff-filter=A`, which is empty on CI's clean checkout -- it would pass + # green having weighed nothing. Verified: a 900 KB file passes without it. + args: [--enforce-all] + - id: check-case-conflict + - id: check-illegal-windows-names + - id: check-merge-conflict + # Same shape: the hook returns 0 early unless .git/MERGE_MSG exists, and CI + # is never mid-merge. Verified: a file full of conflict markers passes + # without it. + args: [--assume-in-merge] + - id: check-symlinks + - id: check-yaml + - id: detect-private-key + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace + + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.1 + hooks: + # Its entry hard-codes --staged, so CI (nothing staged) scans an empty diff and + # is always green. Deliberate: this stays a local commit-time guard; GitHub + # secret scanning push protection (enabled on the repo) is the server-side net. + - id: gitleaks + + # These entries must stay leaf targets. `make check` runs prek itself, so + # pointing a hook at it would make prek recurse into itself. + # + # `always_run: true` is required: these are repo-wide checks, and without it + # prek skips a hook whose file list comes back empty -- so a commit that only + # *deletes* files (no path survives the filter) would skip lint and test + # entirely, which is exactly when they matter most. - repo: local hooks: - - id: make-check - name: make-check - entry: make check + - id: check-tidy + name: check-tidy + entry: make check-tidy + language: system + always_run: true + pass_filenames: false + require_serial: true + + - id: lint + name: lint + entry: make lint + language: system + always_run: true + pass_filenames: false + require_serial: true + + - id: test + name: test + entry: make test language: system + always_run: true pass_filenames: false require_serial: true diff --git a/.serena/memories/memory_maintenance.md b/.serena/memories/memory_maintenance.md index 6f84514..64fac79 100644 --- a/.serena/memories/memory_maintenance.md +++ b/.serena/memories/memory_maintenance.md @@ -12,14 +12,14 @@ Folders can mirror project structure (e.g. modules like frontend/backend) or topics like debugging, architecture, etc. - Memory references must use a mem: prefix inside backticks, e.g. `mem:frontend/core`. The surrounding text should clearly indicate when to read the memory/which content to expect. - The text should provide more precise guidance than the memory name alone, + The text should provide more precise guidance than the memory name alone, i.e. avoid a reference like "frontend debugging: `mem:frontend/debugging` and instead make clear which aspects of frontend debugging are covered. - Memories themselves should not contain information about when to read them; this is the responsibility of the referring memory. ## Style -Dense agent notes, not prose docs. Prefer invariants, terse bullets. -Avoid obvious context, rationale, and examples unless they prevent likely mistakes. +Dense agent notes, not prose docs. Prefer invariants, terse bullets. +Avoid obvious context, rationale, and examples unless they prevent likely mistakes. Keep guidance durable and generalizable, not task-local. ## Add/update threshold @@ -30,4 +30,4 @@ Do not add: quick-read facts; generic language/framework knowledge; one-off task ## Maintenance Actions - Renaming memories: References are updated automatically if handled via Serena's memory rename tool. -- Checking for stale memories (e.g. after deletion): Call `serena memories check` for a report. \ No newline at end of file +- Checking for stale memories (e.g. after deletion): Call `serena memories check` for a report. diff --git a/AGENTS.md b/AGENTS.md index 6ae732f..3cf55e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,17 +19,25 @@ go install github.com/min0625/mint/cmd/mint@latest # Go 1.26.4+; binary → $G ```bash make build # compile → bin/mint (CGO_ENABLED=0, trimpath, ldflags injected) -make test # go test -race -failfast ./... +make test # go test -race -failfast ./... (use this to watch output; prek buffers it) make lint # golangci-lint (only new violations since HEAD) make fix # golangci-lint --fix + go mod tidy -make check # check-tidy + lint + test (CI gate) +make check # every prek hook over all tracked files (CI gate); may rewrite + # files -- the formatting fixer hooks run here too make check-tidy # verify go.mod/go.sum are tidy make release-snapshot # goreleaser release --snapshot --clean (test release locally) ``` Tool versions are pinned in [mise.toml](./mise.toml) (Go 1.26.4, golangci-lint 2.12.2, goreleaser 2.16.0, prek 0.4.6). -Run `mise install` to set up the exact toolchain. Pre-commit hooks run `make check` via -`prek` (config: [.pre-commit-config.yaml](./.pre-commit-config.yaml)). +Run `mise install` to set up the exact toolchain. [.pre-commit-config.yaml](./.pre-commit-config.yaml) +is the single source of truth for what gets checked: `prek` runs it on `git commit` (staged files), +and `make check` runs the same hooks over **all** tracked files, which is what CI runs. `check-tidy`, +`lint` and `test` are hooks in there too — `make check` must never be used as a hook entry, since it +runs prek and would recurse. The hygiene checks come from `repo: builtin` — prek's own Rust +implementations, versioned with prek itself rather than a `rev:` pin, and unreadable by upstream +`pre-commit`. The `gitleaks` hook only scans *staged* changes, so it is a no-op in CI by design — +a local commit-time guard, not a CI secret scan — GitHub secret scanning push +protection is the server-side net. ## Distribution @@ -94,7 +102,11 @@ bin/mint # compiled binary (gitignored) - **Pipe-friendly** — translation input via args or stdin; results to stdout; errors to stderr. - **Unix philosophy** — do one thing well; composable with `grep`, `sed`, `xargs`, etc. - **No unnecessary dependencies** — keep `go.mod` minimal. -- Lint is checked only for *new* violations (`--new-from-rev=HEAD`); always run `make lint` before committing. +- Lint is checked only for *new* violations (`--new-from-rev=$(NEW_FROM_REV)`, default `HEAD`; CI passes + the PR base branch). `make lint` and `make fix` hard-fail if that revision does not resolve (shared + `check-rev` guard) — `golangci-lint` itself only warns and exits 0, which would let CI pass having + linted nothing. The pre-commit hook already runs + lint on every commit, so running it by hand is only needed to re-check a different rev. - **Release workflow** — push a tag matching `v*.*.*` to automatically trigger GoReleaser CI; creates GitHub Release with multi-platform binaries. - **Local snapshot testing** — run `goreleaser release --snapshot --clean` to validate build configuration before publishing. diff --git a/Makefile b/Makefile index ef7313c..b9fb6f6 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,21 @@ build: mkdir -p ./bin/ CGO_ENABLED=0 go build -trimpath -ldflags="$(LDFLAGS)" -o ./bin/ ./cmd/mint +# golangci-lint only *warns* and exits 0 when --new-from-rev does not resolve, +# so a typo'd or unfetched rev would silently lint nothing and report success. +# Both --new-from-rev callers below depend on this guard. +.PHONY: check-rev +check-rev: + @git rev-parse --verify --quiet '$(NEW_FROM_REV)^{commit}' >/dev/null || \ + { echo "NEW_FROM_REV=$(NEW_FROM_REV) is not a valid revision" >&2; exit 1; } + .PHONY: fix -fix: +fix: check-rev go mod tidy golangci-lint run --new-from-rev=$(NEW_FROM_REV) --fix ./... .PHONY: lint -lint: +lint: check-rev golangci-lint config verify golangci-lint run --new-from-rev=$(NEW_FROM_REV) ./... @@ -37,8 +45,13 @@ cover-html: cover check-tidy: go mod tidy -diff +# The full gate: every hook in .pre-commit-config.yaml -- check-tidy, lint and +# test included -- over every tracked file, i.e. the same suite `git commit` +# runs, but repo-wide instead of staged. NEW_FROM_REV is set on the command +# line, so make exports it and prek passes it through to the nested make. .PHONY: check -check: check-tidy lint test +check: + prek run --all-files --show-diff-on-failure .PHONY: release release: