feat: enforce pre-commit hooks in worker worktrees#194
Conversation
Install pre-commit hooks during forge-loop init and propagate them after worker worktree creation so worker commits run the configured gates instead of silently bypassing them. Add typed telemetry for the worker hook outcome and critic/brief guidance for justified bypasses.\n\nRefs #158
hadamrd
left a comment
There was a problem hiding this comment.
[sev1/correctness] The new worker_precommit_installed audit event is emitted under a silent broad Exception suppressor. If event validation or the event sink fails, the required typed event disappears with no signal, so the acceptance criterion that the worker hook setup is visible in the event stream/audit log is not reliable. Let the emit failure surface, or catch/log the specific expected exception with enough context.
| payload: dict[str, Any] = {"worktree_path": str(worktree), "method": method} | ||
| if reason: | ||
| payload["reason"] = reason | ||
| with contextlib.suppress(Exception): |
There was a problem hiding this comment.
[sev1/correctness] The new worker_precommit_installed audit event is emitted under a silent broad Exception suppressor. If event validation or the event sink fails, the required typed event disappears with no signal, so the acceptance criterion that the worker hook setup is visible in the event stream/audit log is not reliable. Let the emit failure surface, or catch/log the specific expected exception with enough context.
hadamrd
left a comment
There was a problem hiding this comment.
[sev1/correctness] detect_precommit_bypass is only tested directly and is never called from review_pr or either critic execution path. That means the deterministic sev1 rule for unjustified git commit --no-verify is not actually enforced by the runtime critic; wire the detector into the critic flow using the PR body/commit text, or add an integration-level enforcement path that the tests exercise.
| parse_retries: int = 0 | ||
|
|
||
|
|
||
| def detect_precommit_bypass(commit_text: str, *, pr_body: str) -> CriticReport: |
There was a problem hiding this comment.
[sev1/correctness] detect_precommit_bypass is only tested directly and is never called from review_pr or either critic execution path. That means the deterministic sev1 rule for unjustified git commit --no-verify is not actually enforced by the runtime critic; wire the detector into the critic flow using the PR body/commit text, or add an integration-level enforcement path that the tests exercise.
hadamrd
left a comment
There was a problem hiding this comment.
Manifesto violations:
- [sev1] error-handling.md#EH-001 — `+ with contextlib.suppress(Exception):
-
emit("worker_precommit_installed", payload)` → Do not broadly suppress the event emit failure; either let it propagate, or catch the specific expected exception and log the worktree_path, method, and reason so the missing audit event can be debugged.
Surface worker pre-commit event sink failures, install hooks in repair worktrees, and merge deterministic no-verify findings into critic review results.\n\nRefs #158
Keep PR body text out of the deterministic no-verify command scan and cover gh failure plus malformed JSON paths.\n\nRefs #158
Flag PR-body action statements that say a worker ran git commit --no-verify, while allowing static rule text to mention the command.\n\nRefs #158
Replace the sequential hook parametrization with a real same-repo two-worker concurrency test and ignore static no-verify rule mentions in deterministic bypass scans.\n\nRefs #158
Detect git -c commit invocations and simple line-continuation no-verify forms in the deterministic critic gate.\n\nRefs #158
Include local worker and repair command logs in the deterministic bypass gate and cover git -C/-n commit forms.\n\nRefs #158
Validate worker_precommit_installed through the typed event model and include worker command transcripts in the deterministic no-verify critic gate.\n\nRefs #158
Repair pass after parallel=2 dogfoodAddressed the critic findings from the two-worker run:
Verification on the repaired branch:
|
Fixes #158
Acceptance criteria
forge-loop initnow detects.pre-commit-config.yaml, installs the hook whenpre-commitis available, reportsprecommit_installed,precommit_already_present,precommit_skipped_no_config, orprecommit_skipped_no_binary, and provides an install hint for the missing-binary path.pre-commit installinside the worker worktree aftergit worktree add, with copy fallback from the main checkout hook when the binary is unavailable.worker_precommit_installedwith{worktree_path, method, reason}via a typed event model.git commit --no-verifyrequires a## Pre-commit bypass justificationsection.--no-verifywithout a justified reason in the PR body. Pre-commit gates are not optional.--no-verify.forge-loop init --target .against this repo; it reportedprecommit_installedand installed the hook.Tests
PYTHONPATH=src uv run --extra dev pytest tests/test_init_precommit.py tests/test_worker_precommit_install.py tests/test_worker_precommit_e2e.py tests/test_critic_precommit_bypass.py tests/test_init.py tests/test_worker_worktree.py tests/test_events.py tests/test_worker.py::test_run_worker_emits_start_and_done_events tests/test_critic_manifesto_check.py -qPYTHONPATH=src uv run --extra dev ruff check src/forge_loop/precommit.py src/forge_loop/_testing/precommit.py src/forge_loop/init.py src/forge_loop/worker.py src/forge_loop/worker_worktree.py src/forge_loop/events.py src/forge_loop/critic.py src/forge_loop/cli_operator_commands.py tests/test_init_precommit.py tests/test_worker_precommit_install.py tests/test_worker_precommit_e2e.py tests/test_critic_precommit_bypass.pyPYTHONPATH=src uv run --extra dev mypy src/forge_loop/precommit.py src/forge_loop/_testing/precommit.py src/forge_loop/init.py src/forge_loop/worker_worktree.py src/forge_loop/events.py src/forge_loop/critic.py tests/test_init_precommit.py tests/test_worker_precommit_install.py tests/test_worker_precommit_e2e.py tests/test_critic_precommit_bypass.pyPYTHONPATH=src uv run --extra dev pyright src/forge_loop/precommit.py src/forge_loop/_testing/precommit.py src/forge_loop/init.py src/forge_loop/worker_worktree.py src/forge_loop/events.py src/forge_loop/critic.pymcp__lumen__semantic_searchunavailable.Gate note
pre-commit run --files ...passed ruff format, ruff lint, and mypy, then failed the repo-wide pyright hook on 54 unrelated existing baseline errors outside this change.--no-verify;SKIP=pyrightwas used for the commit because targeted pyright on touched source passed while the repo-wide pyright hook baseline is currently red.