Skip to content

Commit 8e366d3

Browse files
committed
feat: correctly describe which hooks are enabled
1 parent c7612fa commit 8e366d3

2 files changed

Lines changed: 83 additions & 21 deletions

File tree

src/python_claude/hooks/session_start_hook.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Session start hook for Claude Code."""
22

33
import json
4+
from typing import cast
45

56
from python_claude.hooks.base import Hook, HookInput
6-
from python_claude.hooks.state import QualityCheckState
7+
from python_claude.hooks.state import QualityCheck, QualityCheckState
78

89

910
class SessionStartHook(Hook):
@@ -18,25 +19,44 @@ def run(self) -> int:
1819
"""Print the introductory message."""
1920
state = QualityCheckState(self.project_dir)
2021

21-
# Check which quality checks are disabled
22+
# Check which quality checks are enabled and disabled
23+
enabled_checks = []
2224
disabled_checks = []
23-
if not state.is_enabled("pytest"):
24-
disabled_checks.append("pytest")
25-
if not state.is_enabled("mypy"):
26-
disabled_checks.append("mypy")
27-
if not state.is_enabled("ruff"):
28-
disabled_checks.append("ruff")
29-
30-
additional_context = (
31-
"As you edit files, Claude Code Hooks will automatically run "
32-
"ruff, mypy, and pytest after you edit a file. You don't need "
33-
"to run these commands manually. You can run these before making "
34-
"an edit using:\n"
35-
"- poetry run ruff format .\n"
36-
"- poetry run ruff check .\n"
37-
"- poetry run mypy .\n"
38-
"- poetry run pytest"
39-
)
25+
26+
for check_name in ["ruff", "mypy", "pytest"]:
27+
check = cast(QualityCheck, check_name)
28+
if state.is_enabled(check):
29+
enabled_checks.append(check_name)
30+
else:
31+
disabled_checks.append(check_name)
32+
33+
# Build the message based on enabled checks
34+
if enabled_checks:
35+
# Format list with proper grammar: "a, b, and c"
36+
if len(enabled_checks) == 1:
37+
checks_list = enabled_checks[0]
38+
elif len(enabled_checks) == 2:
39+
checks_list = f"{enabled_checks[0]} and {enabled_checks[1]}"
40+
else:
41+
checks_list = (
42+
", ".join(enabled_checks[:-1]) + f", and {enabled_checks[-1]}"
43+
)
44+
45+
additional_context = (
46+
f"As you edit files, Claude Code Hooks will automatically run "
47+
f"{checks_list} after you edit a file. You don't need "
48+
f"to run these commands manually. You can run these before making "
49+
f"an edit using:\n"
50+
f"- poetry run ruff format .\n"
51+
f"- poetry run ruff check .\n"
52+
f"- poetry run mypy .\n"
53+
f"- poetry run pytest"
54+
)
55+
else:
56+
additional_context = (
57+
"No quality checks are currently enabled. "
58+
"Use /pytest, /mypy, or /ruff to enable checks."
59+
)
4060

4161
output = {"additionalContext": additional_context}
4262

tests/test_session_start_hook.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,18 @@ def test_shows_disabled_checks(self, tmp_path: Path, capsys: Any) -> None:
4646

4747
# Parse JSON output
4848
output = json.loads(captured.out)
49+
# Should only mention ruff in the enabled checks
50+
assert "will automatically run ruff after" in output["additionalContext"]
51+
# Should not mention pytest or mypy as enabled
52+
assert "mypy" not in output[
53+
"additionalContext"
54+
] or "disabled" in output.get("systemMessage", "")
55+
assert "pytest" not in output[
56+
"additionalContext"
57+
] or "disabled" in output.get("systemMessage", "")
58+
4959
assert "systemMessage" in output
50-
assert "currently disabled: pytest, mypy" in output["systemMessage"]
60+
assert "currently disabled: mypy, pytest" in output["systemMessage"]
5161
assert (
5262
"Use /pytest, /mypy, or /ruff to toggle them back on"
5363
in output["systemMessage"]
@@ -70,8 +80,12 @@ def test_shows_all_disabled_checks(self, tmp_path: Path, capsys: Any) -> None:
7080

7181
# Parse JSON output
7282
output = json.loads(captured.out)
83+
# When no checks are enabled, should show special message
84+
assert (
85+
"No quality checks are currently enabled" in output["additionalContext"]
86+
)
7387
assert "systemMessage" in output
74-
assert "currently disabled: pytest, mypy, ruff" in output["systemMessage"]
88+
assert "currently disabled: ruff, mypy, pytest" in output["systemMessage"]
7589

7690
def test_shows_single_disabled_check(self, tmp_path: Path, capsys: Any) -> None:
7791
"""Test message when only one check is disabled."""
@@ -88,5 +102,33 @@ def test_shows_single_disabled_check(self, tmp_path: Path, capsys: Any) -> None:
88102

89103
# Parse JSON output
90104
output = json.loads(captured.out)
105+
# Should only mention mypy and pytest as enabled (with "and")
106+
assert (
107+
"will automatically run mypy and pytest after"
108+
in output["additionalContext"]
109+
)
91110
assert "systemMessage" in output
92111
assert "currently disabled: ruff" in output["systemMessage"]
112+
113+
def test_shows_single_enabled_check(self, tmp_path: Path, capsys: Any) -> None:
114+
"""Test message when only one check is enabled."""
115+
hook_input = HookInput(session_id=None, tool_input={}, raw={})
116+
with patch.dict(os.environ, {"CLAUDE_PROJECT_DIR": str(tmp_path)}):
117+
# Disable mypy and pytest, leaving only ruff enabled
118+
state = QualityCheckState(tmp_path)
119+
state.disable("mypy")
120+
state.disable("pytest")
121+
122+
hook = SessionStartHook(hook_input)
123+
exit_code = hook.run()
124+
assert exit_code == 0
125+
captured = capsys.readouterr()
126+
127+
# Parse JSON output
128+
output = json.loads(captured.out)
129+
# Should only mention ruff (no "and")
130+
assert "will automatically run ruff after" in output["additionalContext"]
131+
# Should not have "and" when there's only one
132+
assert " and " not in output["additionalContext"]
133+
assert "systemMessage" in output
134+
assert "currently disabled: mypy, pytest" in output["systemMessage"]

0 commit comments

Comments
 (0)