gabriel / muse public
feat patch #192 feat/192-musehooks-phase3-commit-integration #1 / 1
AI Agent gabriel · 3 days ago · Sep 11, 2026 · Diff

feat(#192): Phase 3 — muse commit runs installed pre-commit hooks

Wires Phases 1-2's declared-hooks + local-activation machinery into the real commit path. This is the phase that actually closes the gap: nothing runs automatically until now.

Added: - muse/core/hooks.py: HookRunResult + run_hook_point(). No-op (executed=False, passed=True) when this clone doesn't have hooks installed or nothing is declared for the given hook point -- callers never need to check is_installed() separately. Runs commands via the shell (cwd=root), stopping at the first non-zero exit; subsequent commands never run once one fails (fail-fast, matching a pre-commit gate's purpose). - muse/cli/commands/commit.py: - New --no-verify flag: skips hook execution entirely. Never silent -- always adds a "Skipped pre-commit hooks (--no-verify)." warning (JSON `warnings` array, or a printed stderr line in text mode). - Hook execution inserted right before the dry-run/write branch: dry-run never executes hook commands (a pure preview shouldn't have side effects from arbitrary shell commands); real commits do. - "defined_not_installed" state adds a warning (not a hard block) -- hooks that were never activated for this clone don't silently gate anything, but the gap is visible in every commit's output. - A failing "installed" hook aborts the commit with exit 1 before any object is written, naming the exact failed command and its captured output (new _CommitHookFailedJson schema).

Tests (TDD, red confirmed before implementation): - tests/test_core_hooks_run.py (8 tests): not-installed no-op, nothing- declared no-op, passing/failing commands, first-failure short-circuits later commands, stderr capture, cwd=root. - tests/test_cmd_commit_hooks.py (12 tests, HK_20-23): passing hook allows commit; defined-not-installed doesn't block but does warn; failing hook blocks with exit 1 and names the command; failing hook writes zero objects (HEAD unchanged); --no-verify bypasses a failing hook and is never silent (including when nothing was installed at all, still harmless); no warning when hooks are properly installed or when nothing is defined; dry-run never executes hook commands even when one would fail. - Manually smoke-tested against a real disposable repo via muse-dev: failing hook blocks, --no-verify gets it through visibly.

90/90 across all hooks test files + tests/test_app.py (27). Full regression run of tests/test_cmd_commit.py + 5 related commit test files (151 tests) still green -- no regressions to existing commit behavior.

Note: HK_ACCEPT_01 (the full .museagent.md-drift reproduction using `muse agent-config status --fail-if-out-of-sync`) is deferred to Phase 5, once that flag exists.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

sha256:5f7b38765462d4b092afecab903684171b41c9e7aabc0f6515088d43c5e2cb4f sha
+48 ~2 symbols
4 changed · 1182 in snapshot files
sha256:a9a02f85a46239b2fdd5e1cb85c9a132187b415b7c034dc91d91b6f8aca82569 snapshot
+48
symbols added
~2
symbols modified
4
files changed
1182
files in snapshot
0
dead code introduced
Semantic Changes 50 symbols
~ tests/test_cmd_commit_hooks.py .py 28 symbols added
+ TestCommitBlockedByFailingHook class class TestCommitBlockedByFailingHook L64–98
+ test_failing_hook_blocks_commit_exit_1 method method test_failing_hook_blocks_commit_exit_1 L65–72
+ test_failing_hook_names_the_command_in_output method method test_failing_hook_names_the_command_in_output L74–82
+ test_failing_hook_prevents_any_object_being_written method method test_failing_hook_prevents_any_object_being_written L84–98
+ TestCommitRunsInstalledHooks class class TestCommitRunsInstalledHooks L44–61
+ test_defined_not_installed_does_not_block_commit method method test_defined_not_installed_does_not_block_commit L54–61
+ test_passing_hook_allows_commit method method test_passing_hook_allows_commit L45–52
+ TestDefinedNotInstalledWarning class class TestDefinedNotInstalledWarning L128–154
+ test_json_warns_when_hooks_defined_but_not_installed method method test_json_warns_when_hooks_defined_but_not_installed L129–137
+ test_no_warning_when_hooks_installed method method test_no_warning_when_hooks_installed L139–147
+ test_no_warning_when_no_hooks_defined_at_all method method test_no_warning_when_no_hooks_defined_at_all L149–154
+ TestDryRunNeverExecutesHooks class class TestDryRunNeverExecutesHooks L157–165
+ test_dry_run_does_not_execute_hook_commands method method test_dry_run_does_not_execute_hook_commands L158–165
+ TestNoVerifyBypass class class TestNoVerifyBypass L101–125
+ test_no_verify_allows_commit_despite_failing_hook method method test_no_verify_allows_commit_despite_failing_hook L102–109
+ test_no_verify_is_never_silent_json_notes_skip method method test_no_verify_is_never_silent_json_notes_skip L111–119
+ test_no_verify_with_no_hooks_installed_is_harmless method method test_no_verify_with_no_hooks_installed_is_harmless L121–125
+ _init_repo function function _init_repo L28–31
+ _invoke function function _invoke L19–25
+ _stage_a_file function function _stage_a_file L38–41
+ _write_hooks function function _write_hooks L34–35
+ CliRunner import import CliRunner L14–14
+ InvokeResult import import InvokeResult L14–14
+ annotations import import annotations L8–8
+ json import import json L10–10
+ os import import os L11–11
+ pathlib import import pathlib L12–12
+ runner variable variable runner L16–16
~ tests/test_core_hooks_run.py .py 14 symbols added
+ TestRunHookPointInstalled class class TestRunHookPointInstalled L34–98
+ test_failing_command_stops_and_reports_it method method test_failing_command_stops_and_reports_it L46–55
+ test_first_failure_stops_subsequent_commands method method test_first_failure_stops_subsequent_commands L57–65
+ test_multiple_passing_commands_all_run_in_order method method test_multiple_passing_commands_all_run_in_order L67–76
+ test_passing_command_executes_and_passes method method test_passing_command_executes_and_passes L35–44
+ test_runs_relative_to_repo_root method method test_runs_relative_to_repo_root L88–98
+ test_stderr_captured_on_failure method method test_stderr_captured_on_failure L78–86
+ TestRunHookPointNotInstalled class class TestRunHookPointNotInstalled L16–31
+ test_no_hooks_defined_at_all_is_a_pass method method test_no_hooks_defined_at_all_is_a_pass L27–31
+ test_not_installed_does_not_execute_anything method method test_not_installed_does_not_execute_anything L17–25
+ _write_hooks function function _write_hooks L12–13
+ install_hooks import import install_hooks L9–9
+ pathlib import import pathlib L7–7
+ run_hook_point import import run_hook_point L9–9
~ muse/cli/commands/commit.py .py 3 symbols added, 2 symbols modified
+ _CommitHookFailedJson class class _CommitHookFailedJson L106–112
+ get_status import import get_status L83–83
+ run_hook_point import import run_hook_point L83–83
~ run
~ muse/core/hooks.py .py 3 symbols added
+ HookRunResult class class HookRunResult L223–241
+ subprocess import import subprocess L58–58
+ run_hook_point function function run_hook_point L244–289
Files Changed
+2 ~2
1182 in snapshot
← Older Oldest on feat/192-musehooks-phase3-commit-integration
All commits
Newer → Latest on feat/192-musehooks-phase3-commit-integration

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:5f7b38765462d4b092afecab903684171b41c9e7aabc0f6515088d43c5e2cb4f --body "your comment"