mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 22:54:01 +01:00
A completely absent `prettier` binary went unnoticed long enough for unformatted markdown to reach main. Two independent causes, both fixed here. No pre-push hook existed. The three hooks declaring `stages: [pre-push]` in .pre-commit-config.yaml — prettier, ci-mirror, pytest-coverage — therefore never ran locally at all. Adds one. The pre-commit hook failed open. shfmt and shellcheck were wrapped in `if command -v`, and the entire pre-commit framework was skipped with a warning when its binary was missing. A silently skipped gate is indistinguishable from a passing one, so a missing tool now aborts. - lib/common.sh: require_tool tries `sudo -n pacman -S` and aborts with the exact command when a tool cannot be installed non-interactively; verify_toolchain applies it to all eight binaries the hooks invoke. - The pre-push hook resolves git's stdin refs into --from-ref/--to-ref. Without a range, `pre-commit run --hook-stage pre-push` checks *staged* files, and nothing is staged at push time, so prettier would have received zero files and passed vacuously. - ensure_hooks_path repairs core.hooksPath drift on every hook run. - meta/scripts/install_hooks.sh covers the one case the hooks cannot self-heal — a fresh clone where the config was never set, so nothing runs. `--check` reports state and exits non-zero when incomplete. Note `pre-commit install` must never be used here: it writes into .git/hooks/, which git ignores entirely while core.hooksPath is set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
7.6 KiB
JSON
70 lines
7.6 KiB
JSON
{
|
|
"intent": "Make the git hooks and the tools they depend on mandatory rather than best-effort: add the missing pre-push gate, and convert every fail-open path into a fail-closed one so a missing binary aborts the commit or push instead of silently skipping the check.",
|
|
"scope": [
|
|
"linux_configuration/.githooks/lib/common.sh — new shared library (require_tool, verify_toolchain, ensure_hooks_path)",
|
|
"linux_configuration/.githooks/pre-push — new hook; none existed before",
|
|
"linux_configuration/.githooks/pre-commit — fail-open paths for shfmt, shellcheck and the pre-commit framework removed",
|
|
"meta/scripts/install_hooks.sh — new bootstrap with a --check mode",
|
|
"CLAUDE.md — documents the requirement and the install command",
|
|
"Non-goal: porting this to other repos, or changing which hooks .pre-commit-config.yaml defines"
|
|
],
|
|
"changes": [
|
|
"Added a pre-push hook. There was none, so prettier, ci-mirror and pytest-coverage — the three hooks with stages: [pre-push] — had never run locally. That is the direct cause of prettier being absent from this machine without anyone noticing.",
|
|
"The pre-push hook parses git's stdin ref lines into --from-ref/--to-ref. This is load-bearing: `pre-commit run --hook-stage pre-push` with no range operates on staged files, and nothing is staged at push time, so file-scoped hooks like prettier would receive zero files and pass vacuously. Branch deletions exit early; a new remote branch falls back to --all-files rather than checking nothing.",
|
|
"Added require_tool: looks for a command, attempts `sudo -n pacman -S --noconfirm --needed <pkg>` when absent, and aborts with the exact `! sudo pacman -S <pkg>` command if that cannot work non-interactively. verify_toolchain applies it to all eight binaries the hooks invoke (pre-commit, prettier, shellcheck, shfmt, zsh, node, npm, python3).",
|
|
"Removed the three fail-open branches in the pre-commit hook: shfmt and shellcheck were wrapped in `if command -v`, and the entire pre-commit framework was skipped with a warning when the binary was missing. All three now rely on verify_toolchain having already guaranteed the tool.",
|
|
"Added ensure_hooks_path, called by both hooks, which repairs core.hooksPath whenever it has drifted — so the wiring self-heals after a single hook run.",
|
|
"Added meta/scripts/install_hooks.sh for the one case the hooks cannot self-heal: a fresh clone where core.hooksPath was never set, so no hook runs at all. It wires the path, makes the hooks executable, verifies the toolchain, and pre-builds pre-commit environments. --check reports state without changing anything.",
|
|
"Documented in CLAUDE.md why `pre-commit install` must never be used here: it writes into .git/hooks/, which git ignores entirely while core.hooksPath is set."
|
|
],
|
|
"verification": [
|
|
{
|
|
"command": "shellcheck -x -S warning linux_configuration/.githooks/{pre-commit,pre-push,lib/common.sh} meta/scripts/install_hooks.sh",
|
|
"result": "pass",
|
|
"evidence": "Clean, with no suppression comments. An earlier draft of pre-push used `# shellcheck disable=SC2086` for word-splitting a range string; that was replaced with a RANGE_ARGS array so no suppression is needed. One SC2155 in install_hooks.sh (declare-and-assign masking a return value) was fixed by splitting the readonly declaration."
|
|
},
|
|
{
|
|
"command": "./meta/scripts/install_hooks.sh --check",
|
|
"result": "pass",
|
|
"evidence": "Reported core.hooksPath = linux_configuration/.githooks, both hooks present and executable, and all 8 tools found, ending 'Hooks are wired and the toolchain is complete.'"
|
|
},
|
|
{
|
|
"command": "git config --unset core.hooksPath && ./meta/scripts/install_hooks.sh --check",
|
|
"result": "pass",
|
|
"evidence": "Exit code 1 with '✗ core.hooksPath is <unset>, expected linux_configuration/.githooks' and '1 problem(s). Run ./meta/scripts/install_hooks.sh to fix.' Confirms --check detects drift instead of reporting healthy."
|
|
},
|
|
{
|
|
"command": "source lib/common.sh && ensure_hooks_path (with core.hooksPath unset)",
|
|
"result": "pass",
|
|
"evidence": "Printed '↻ repaired core.hooksPath: <unset> → linux_configuration/.githooks' and the config read back as linux_configuration/.githooks. Original value restored afterwards."
|
|
},
|
|
{
|
|
"command": "source lib/common.sh && require_tool definitely-not-a-real-tool definitely-not-a-real-pkg",
|
|
"result": "pass",
|
|
"evidence": "Attempted the install, then exited 1 with \"Aborted: required tool 'definitely-not-a-real-tool' is missing and could not be installed automatically\" and the exact retry command. Execution did not continue past the call, confirming fail-closed rather than warn-and-skip. A fictitious package name was used so nothing real was installed or removed."
|
|
},
|
|
{
|
|
"command": "bash linux_configuration/.githooks/pre-commit (run exactly as git invokes it)",
|
|
"result": "pass",
|
|
"evidence": "Ran the full chain — toolchain verification, shfmt, shellcheck (Passed), jscpd, then the pre-commit framework — and aborted non-zero with 'Commit aborted: pre-commit hooks failed.' The failures it caught (codespell 'dokument' and check-python-location on billsplit/tool/coverage_check.py) came from another session's concurrently staged work, not from this change; the hook correctly refused to let them through."
|
|
},
|
|
{
|
|
"command": "printf '(delete) 000...0 refs/heads/scratch <sha>' | bash linux_configuration/.githooks/pre-push origin <url>",
|
|
"result": "pass",
|
|
"evidence": "Printed 'Verifying hook wiring and toolchain before pushing...', '✓ toolchain complete', then 'Nothing to check (branch deletion or empty push).' and exited 0 — stdin parsing and the branch-deletion early exit both behave correctly."
|
|
}
|
|
],
|
|
"risks": [
|
|
"Every push now runs ci-mirror, which builds a clean venv and runs `pre-commit run --all-files` plus changed-package pytest. Pushes are substantially slower than before. That is the intended trade — the alternative is the silent gap this change exists to close.",
|
|
"require_tool runs pacman from inside a git hook. A held pacman database lock, or a package that has been renamed or dropped from the repos, surfaces as an aborted commit rather than a package-manager error. The abort message names the exact command so the cause is visible.",
|
|
"The REQUIRED_TOOLS list is maintained by hand. Adding a `language: system` hook to .pre-commit-config.yaml without adding its binary here reintroduces exactly the class of gap this change closes; CLAUDE.md says so at the point of use.",
|
|
"ensure_hooks_path cannot help on a fresh clone where core.hooksPath was never set, because no hook runs at all. install_hooks.sh is the only remedy for that case and must be run manually once per clone.",
|
|
"python3 currently resolves through a pyenv shim and node through nvm, neither of which pacman manages. If either shim disappears, require_tool would try to install the pacman package and could end up with a different interpreter on PATH than the one previously in use."
|
|
],
|
|
"rollback": [
|
|
"git revert the commit. That restores the previous pre-commit hook and removes the pre-push hook, returning the repo to commit-time-only gating.",
|
|
"core.hooksPath is a local git config value and is not changed by a revert; it already pointed at linux_configuration/.githooks before this change, so nothing needs undoing there.",
|
|
"After rollback, confirm `git commit` still runs the pre-commit hook and be aware that the pre-push stage silently stops running again — re-check that prettier is installed before trusting any push."
|
|
]
|
|
}
|