chore(#506): wire pre-push-gate to the framework CI checks + add git pre-push hook#507
Conversation
…pre-push hook - Populate .claude/project-config.json with pre_push.commands mirroring the locally-runnable CI jobs: markdownlint (via npx markdownlint-cli2), shellcheck over .claude/hooks/*.sh at warning severity, site-counts drift detection, and the subpack extraction smoke test. Link-check (lychee) excluded — network- flaky and unsuitable for pre-push latency. - Add .githooks/pre-push (terminal-push companion): delegates to bin/run-pre-push-checks.sh so the same check set protects both Claude-driven and terminal git pushes. Consistent skip-marker escape hatch. - Add bin/run-pre-push-checks.sh as the shared implementation: runs all four checks, surfaces INFO: lines for missing-tool graceful degrade, and prints a BLOCKED message with the skip-marker instructions on failure. - Disable MD049 and MD060 in .markdownlint.json — both rules were added in markdownlint v0.40.0 (shipped by markdownlint-cli2 v0.22.x) and the existing repo files violate them pervasively. Consistent with the existing permissive stance (MD013, MD033, MD034, etc. already disabled). - Document the one-line core.hooksPath opt-in and the full check table in docs/getting-started.md § "Terminal push hook". Closes #506
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #507
Commit: ed38a63abc20e856b6fc2096d4d4a0adc1e8abcf
Independent review by Rex. NOTE: the build agent's own "Rex" self-review was disregarded (the #494 self-review bug — its guardrail isn't in this fork yet). This verdict was reached by re-running every check from scratch in the worktree. No forged
507-rex.approvedmarker was found anywhere on disk.
Summary
Wires the framework repo's pre-push-gate.sh to a real check set (4 commands in pre_push.commands) and adds a committed .githooks/pre-push for terminal pushes, both delegating to a shared bin/run-pre-push-checks.sh. Checks: markdownlint, shellcheck, site-counts drift, subpack smoke test. lychee correctly excluded (network-flaky). Also disables MD049/MD060 in .markdownlint.json and documents the core.hooksPath opt-in.
Checklist Results
- Architecture & Design: Pass
- Code Quality: Pass
- Testing: Pass (ran every check end-to-end)
- Security: Pass (no secrets; tool-missing path fails open by design, acceptable for a local dev convenience gate — CI is the hard backstop)
- Performance: Pass (lychee excluded for latency; checks are fast)
- PR Description & Glossary: Pass (Glossary present,
Closes #506, narrative Summary bullets) - Summary Bullet Narrative: Pass
- Technical Decisions (AgDR): N/A (no new dependency/framework/pattern — wires existing tools)
- Adopter Handbooks: N/A (no migration/domain/TS code in diff; migration-safety handbook not triggered)
Verification performed (ran it, not just read it)
- Config consumption —
jq .valid;pre_push.commandsis a[{name, run}]array, exactly the shapepre-push-gate.shreads viaconfig_get '.pre_push.commands'(cross-checked the hook source). Field names match. - Graceful degrade — simulated
PATH=/usr/bin:/binfor both the npx and shellcheck commands: each prints an actionableINFO:install message and exits 0 (skip-with-note), no crash. Full runner under stripped PATH also exits 0 and still runs the tool-free checks. AC's graceful-degrade holds. - Single source of truth —
.githooks/pre-pushexecsbin/run-pre-push-checks.sh(no duplicated logic; githook can't drift from runner). Exec bit committed (100755). Skip marker<!-- pre-push: skip -->present and identical in both runner and gate; bypass branch verified. - End-to-end BLOCK — healthy HEAD: all 4 checks exit 0 (markdownlint 0 errors / 299 files — no false positive). Deliberately broken markdown (
SCRATCH_BREAK.mdwith MD024 dup-heading):.githooks/pre-pushBLOCKED (exit 1); the gate's command loop, run against THIS PR's config, BLOCKED (exit 2) catchingSCRATCH_BREAK.md:7 MD024. Scratch file reverted; tree clean.- One nuance worth recording: when I first fed mock
git pushJSON topre-push-gate.shfrom inside the worktree, it exited 0. Root cause is NOT a PR defect —_lib-read-config.shresolves the ops-fork root viaresolve_ops_root, which in this multi-fork test layout walks up to the main fork (apexyard/) whoseproject-config.jsonhas nopre_push.commands. In a normal single-clone deployment the repo root IS the ops fork and the gate reads the 4 commands. Proven by running the gate's exact command loop against the worktree's own config → exit 2 on the broken file.
- One nuance worth recording: when I first fed mock
- shellcheck —
shellcheck --severity=warning .githooks/pre-push bin/run-pre-push-checks.sh→ clean (exit 0). - Referenced tests —
test_site_counts.shandtest_subpack_extraction.shboth exist and pass.
Build agent's 3 self-flagged findings — my disposition
- (a) Runner names only the LAST failing check in the final
BLOCKED:summary — confirmed. BUT each failing check still prints its fullFAILED: <name>+ last-20-lines block to stderr as it runs (lines 78-84), so no failure detail is hidden — only the one-line bottom summary names the last. NIT, non-blocking. Gate still correctly exits 1. - (b) Command-string duplication between
bin/run-pre-push-checks.shandproject-config.jsonwith only a prose "keep in sync" comment, no drift guard — confirmed; the strings are byte-identical today (diffed both). Real maintainability concern but both are correct now and a drift would surface in review/CI. NIT / follow-up, non-blocking. Suggestion: a tiny test asserting the config'smarkdownlint/shellcheck.runstrings equal the runner's*_CMDvars would close the drift window cheaply. - (c)
core.hooksPathdocs phrasing — docs are accurate and include the--globalcaveat (shadows other repos'.git/hooks/). NIT.
Suggestions (non-blocking)
- The
.markdownlint.jsonMD049/MD060 disable is harmless and forward-compatible, but on the locally-pinned engine (markdownlint v0.40.0 via cli2 v0.22.1) neither rule produced any errors — the PR body's "3732 pre-existing errors / pervasive violations" rationale didn't reproduce here. The disable is a reasonable version-defensive guard (CI pinsmarkdownlint-cli2-action@v16); just flagging that the stated error count is engine-version-dependent. - Consider having
pre-push-gate.shshell out tobin/run-pre-push-checks.shtoo (instead of re-reading config and re-implementing the run loop), collapsing the three representations to one. Out of scope for this PR; noting for a follow-up.
Verdict
APPROVED
All acceptance criteria verified by execution: gate does not false-positive on a healthy push, missing tools degrade gracefully without crashing, config is well-formed and consumed correctly, and both push paths block on a real failure. The three self-flagged items are nits, not blockers.
Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: ed38a63abc20e856b6fc2096d4d4a0adc1e8abcf
…pre-push hook (#507) - Populate .claude/project-config.json with pre_push.commands mirroring the locally-runnable CI jobs: markdownlint (via npx markdownlint-cli2), shellcheck over .claude/hooks/*.sh at warning severity, site-counts drift detection, and the subpack extraction smoke test. Link-check (lychee) excluded — network- flaky and unsuitable for pre-push latency. - Add .githooks/pre-push (terminal-push companion): delegates to bin/run-pre-push-checks.sh so the same check set protects both Claude-driven and terminal git pushes. Consistent skip-marker escape hatch. - Add bin/run-pre-push-checks.sh as the shared implementation: runs all four checks, surfaces INFO: lines for missing-tool graceful degrade, and prints a BLOCKED message with the skip-marker instructions on failure. - Disable MD049 and MD060 in .markdownlint.json — both rules were added in markdownlint v0.40.0 (shipped by markdownlint-cli2 v0.22.x) and the existing repo files violate them pervasively. Consistent with the existing permissive stance (MD013, MD033, MD034, etc. already disabled). - Document the one-line core.hooksPath opt-in and the full check table in docs/getting-started.md § "Terminal push hook". Closes #506 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Summary
pre_push.commandsin.claude/project-config.json— the framework repo now dog-foods its ownpre-push-gate.shhook with four locally-runnable checks that mirror CI: markdownlint (vianpx markdownlint-cli2), shellcheck over.claude/hooks/*.sh, site-counts drift detection, and the subpack extraction smoke test. Link-check (lychee) is excluded because it is network-dependent and too slow for pre-push latency..githooks/pre-push— a committed git hook that covers terminalgit pushcommands (not just Claude-driven pushes). It delegates tobin/run-pre-push-checks.shso the command set stays in one place. The same skip-marker escape hatch (<!-- pre-push: skip -->in the HEAD commit message) works on both paths.bin/run-pre-push-checks.sh— the shared check runner. Each check guards for its tool: a missingshellcheckornpxprints an actionableINFO:install message and exits 0 (skip-with-note), so a contributor without the tool is never hard-blocked. Shellcheck-clean..markdownlint.json— disabled MD049 (emphasis style) and MD060 (table column style), both new in markdownlint v0.40.0. The existing repo files violate these rules pervasively (3732 pre-existing errors onupstream/devHEAD before this PR). Consistent with the existing permissive stance: MD013, MD033, MD034, and others are already disabled for the same "real problems only" rationale in the file header.core.hooksPathopt-in indocs/getting-started.md§ "Terminal push hook" — one-timegit config core.hooksPath .githooks, per-clone, with the check table, graceful-degrade note, and skip-marker instructions.Testing
Healthy HEAD — all 4 checks pass:
Broken case — temporary MD022/MD024 error file blocks with clear output:
Missing-tool case —
PATH=/usr/bin:/binstripsnpxandshellcheck; both print actionable INFO lines and skip (exit 0), site-counts and subpacks still run:shellcheck clean —
shellcheck --severity=warning .githooks/pre-push bin/run-pre-push-checks.shexits 0.Existing tests —
test_site_counts.shandtest_subpack_extraction.shboth pass on this branch.Glossary
pre-push-gate.shPreToolUsehook (#111) that reads.pre_push.commandsand blocksgit pushon any non-zero check.core.hooksPath.githooks/), enabling the terminal-push git hook to ship in-repo rather than relying on each contributor to install it manually.markdownlint-cli2markdownlint-cli2-action@v16); invoked locally vianpx --yes markdownlint-cli2..markdownlint.jsonbecause the existing repo files violate them pervasively — consistent with the file's stated "real problems only" approach.Closes #506