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

feat(#192): Phase 4 — discoverability nudges at clone/status time

Hooks that are declared but never installed are invisible unless someone thinks to run `muse hooks status`. This phase surfaces that gap at the two moments it's most likely to matter: right after cloning a repo that ships .musehooks.toml, and any time `muse status` runs afterward (covers clones that predate this feature, or where install was skipped).

Added: - muse/core/hooks.py::install_notice() — shared one-line nudge, "None" for every state except "defined_not_installed". Best-effort: swallows a malformed .musehooks.toml rather than erroring here (that's surfaced by `muse hooks status`/`list` directly, not by an incidental notice). - muse/cli/commands/clone.py: prints the notice to stderr right after working-tree restore. A fresh clone can never have hooks installed yet (install state is deliberately local-only, never cloned) -- so if the repo declares any, this is the earliest possible moment to say so. - muse/cli/commands/status.py: prints the same notice to stderr, unconditionally before the JSON/text output split, so it fires identically for both `muse status` and `muse status --json` (stdout stays clean for JSON consumers either way).

Tests (TDD, red confirmed before implementation): - tests/test_hooks_clone_status_notice.py (9 tests): install_notice() unit-tested directly against a real filesystem (not_defined/ defined_not_installed/installed); muse status prints/omits the notice correctly in both text and --json modes; muse clone prints the notice when the cloned manifest includes .musehooks.toml (via a patched _restore_working_tree that writes the file, following this repo's existing clone-test mocking pattern) and stays silent when it doesn't. - Manually smoke-tested against a real disposable repo via muse-dev: `muse status` shows the nudge before install, silent after.

Full regression run: tests/test_cmd_clone_hardening.py + 3 related clone test files (59 tests) and 7 status test files (174 tests) all still green. 99/99 across all hooks test files + tests/test_app.py.

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

sha256:b5a46a9923166b1435c3d7549801a23fbf281f8f78c766142e923b35c23bdc13 sha
+32 ~2 symbols
4 changed · 1183 in snapshot files
sha256:6a52ea4dc024e6eee9466e4466fb50e59055e206ab663c8078925ea5507a2946 snapshot
+32
symbols added
~2
symbols modified
4
files changed
1183
files in snapshot
0
dead code introduced
Semantic Changes 34 symbols
+ TestCloneNotice class class TestCloneNotice L127–164
+ _clone method method _clone L128–154
+ test_clone_prints_notice_when_repo_ships_hooks method method test_clone_prints_notice_when_repo_ships_hooks L156–159
+ test_clone_silent_when_repo_has_no_hooks method method test_clone_silent_when_repo_has_no_hooks L161–164
+ TestInstallNotice class class TestInstallNotice L45–58
+ test_message_when_defined_not_installed method method test_message_when_defined_not_installed L49–53
+ test_none_when_installed method method test_none_when_installed L55–58
+ test_none_when_not_defined method method test_none_when_not_defined L46–47
+ TestStatusNotice class class TestStatusNotice L66–89
+ test_status_json_mode_still_silent_on_stdout method method test_status_json_mode_still_silent_on_stdout L85–89
+ test_status_prints_notice_when_defined_not_installed method method test_status_prints_notice_when_defined_not_installed L67–71
+ test_status_silent_when_already_installed method method test_status_silent_when_already_installed L78–83
+ test_status_silent_when_no_hooks_defined method method test_status_silent_when_no_hooks_defined L73–76
+ _init_repo function function _init_repo L31–33
+ _invoke_status function function _invoke_status L22–28
+ _make_apply_result function function _make_apply_result L114–124
+ _make_transport_mock function function _make_transport_mock L97–111
+ _write_hooks function function _write_hooks L36–37
+ CliRunner import import CliRunner L17–17
+ InvokeResult import import InvokeResult L17–17
+ MagicMock import import MagicMock L12–12
+ annotations import import annotations L8–8
+ install_hooks import import install_hooks L16–16
+ install_notice import import install_notice L16–16
+ os import import os L10–10
+ patch import import patch L12–12
+ pathlib import import pathlib L11–11
+ pytest import import pytest L14–14
+ runner variable variable runner L19–19
~ muse/cli/commands/clone.py .py 1 symbol added, 1 symbol modified
+ install_notice import import install_notice L79–79
~ run
~ muse/cli/commands/status.py .py 1 symbol added, 1 symbol modified
+ install_notice import import install_notice L96–96
~ run
~ muse/core/hooks.py .py 1 symbol added
+ install_notice function function install_notice L292–313
Files Changed
+1 ~3
1183 in snapshot
← Older Oldest on feat/192-musehooks-phase4-discoverability
All commits
Newer → Latest on feat/192-musehooks-phase4-discoverability

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:b5a46a9923166b1435c3d7549801a23fbf281f8f78c766142e923b35c23bdc13 --body "your comment"