Skip to content

Commit cd1bb58

Browse files
committed
fix(plugin): reduce PreToolUse information overload with priority-based display (#1039)
- Condense quality gate context from 5 lines to 1 line - Collapse test suggestions to count only (e.g. "3 related test(s) found") - Collapse checklist warnings to domain counts (e.g. "[Checklist] security(5)") - Total output max 5 lines to reduce cognitive load - Add 4 new tests for compact output behavior
1 parent 4a41f3d commit cd1bb58

2 files changed

Lines changed: 106 additions & 17 deletions

File tree

packages/claude-code-plugin/hooks/pre-tool-use.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,7 @@
3131
_GIT_COMMIT_RE = re.compile(r"\bgit\s+commit\b")
3232

3333
QUALITY_GATE_CONTEXT = (
34-
"[CodingBuddy Quality Gate] Before committing, ensure:\n"
35-
"- All tests pass\n"
36-
"- Code follows project conventions\n"
37-
"- Changes are reviewed (self-review at minimum)\n"
38-
"- Commit message follows project convention"
34+
"[Quality Gate] Verify: tests pass, conventions followed, changes self-reviewed."
3935
)
4036

4137
FILE_WATCHER_CONTEXT = (
@@ -144,26 +140,43 @@ def _get_staged_files() -> List[str]:
144140

145141

146142
def _get_test_suggestion(staged_files: List[str]) -> Optional[str]:
147-
"""Use SmartTestRunner to build a test-run suggestion for staged files."""
143+
"""Use SmartTestRunner to build a compact test-run suggestion for staged files.
144+
145+
Returns a collapsed count instead of listing individual files (#1039).
146+
"""
148147
try:
149148
from smart_test_runner import SmartTestRunner
150149

151150
runner = SmartTestRunner()
152151
related = runner.find_related_tests(staged_files)
153152
if not related:
154153
return None
155-
return runner.format_suggestion(related)
154+
count = len(related)
155+
return f"{count} related test(s) found — consider running before commit"
156156
except Exception:
157157
return None
158158

159159

160160
def _get_checklist_warning(staged_files: List[str]) -> Optional[str]:
161-
"""Use ChecklistVerifier to build a checklist warning for staged files (#1001)."""
161+
"""Use ChecklistVerifier to build a compact checklist summary for staged files.
162+
163+
Returns collapsed domain counts instead of detailed items (#1039).
164+
"""
162165
try:
163166
from checklist_verifier import ChecklistVerifier
164167

165168
verifier = ChecklistVerifier()
166-
return verifier.verify(staged_files)
169+
domains = verifier.detect_domains(staged_files)
170+
if not domains:
171+
return None
172+
domain_counts = []
173+
for domain in domains:
174+
items = verifier.get_checklist_items(domain)
175+
if items:
176+
domain_counts.append(f"{domain}({len(items)})")
177+
if not domain_counts:
178+
return None
179+
return f"[Checklist] {', '.join(domain_counts)}"
167180
except Exception:
168181
return None
169182

packages/claude-code-plugin/tests/test_pre_tool_use.py

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_git_commit_with_gates_disabled(self, monkeypatch, capsys):
114114
assert result is None
115115

116116
def test_git_commit_with_gates_enabled_returns_context(self, monkeypatch, capsys):
117-
"""When qualityGates.enabled is True, should return additionalContext."""
117+
"""When qualityGates.enabled is True, should return compact additionalContext."""
118118
config = {"qualityGates": {"enabled": True}}
119119
result = _run_hook(
120120
{"tool_name": "Bash", "tool_input": {"command": "git commit -m 'feat: test'"}},
@@ -123,8 +123,12 @@ def test_git_commit_with_gates_enabled_returns_context(self, monkeypatch, capsys
123123
assert result is not None
124124
assert "hookSpecificOutput" in result
125125
hook_output = result["hookSpecificOutput"]
126-
# Should have additionalContext with quality gate reminder
126+
# Should have compact additionalContext with quality gate reminder (#1039)
127127
assert "additionalContext" in hook_output
128+
ctx = hook_output["additionalContext"]
129+
assert "[Quality Gate]" in ctx
130+
# Compact: single line, no bullet points
131+
assert ctx.count("\n") == 0
128132

129133
def test_git_commit_amend_with_gates(self, monkeypatch, capsys):
130134
"""git commit --amend should also trigger quality gates."""
@@ -160,7 +164,7 @@ class TestPreToolUseSmartTestRunner:
160164
"""Tests for SmartTestRunner integration in pre-tool-use hook."""
161165

162166
def test_git_commit_injects_test_suggestion(self, monkeypatch, capsys, tmp_path):
163-
"""git commit should inject related test suggestion into additionalContext."""
167+
"""git commit should inject compact test count into additionalContext (#1039)."""
164168
# Create a fake staged file list
165169
monkeypatch.setattr(
166170
"subprocess.check_output",
@@ -173,8 +177,9 @@ def test_git_commit_injects_test_suggestion(self, monkeypatch, capsys, tmp_path)
173177
)
174178
assert result is not None
175179
ctx = result["hookSpecificOutput"]["additionalContext"]
176-
assert "Consider running" in ctx
177-
assert "foo.spec.ts" in ctx
180+
# Collapsed format: count only, no individual file listing
181+
assert "related test(s) found" in ctx
182+
assert "foo.spec.ts" not in ctx
178183

179184
def test_git_commit_no_staged_files_no_suggestion(self, monkeypatch, capsys):
180185
"""git commit with no staged files should not inject suggestion."""
@@ -203,7 +208,7 @@ def test_git_commit_config_files_only_no_suggestion(self, monkeypatch, capsys):
203208
assert result is None
204209

205210
def test_git_commit_combines_quality_gate_and_test_suggestion(self, monkeypatch, capsys):
206-
"""When both quality gates and test suggestion active, both in context."""
211+
"""When both quality gates and test suggestion active, both in compact context (#1039)."""
207212
monkeypatch.setattr(
208213
"subprocess.check_output",
209214
lambda *a, **kw: b"src/bar.ts\n",
@@ -215,7 +220,7 @@ def test_git_commit_combines_quality_gate_and_test_suggestion(self, monkeypatch,
215220
)
216221
assert result is not None
217222
ctx = result["hookSpecificOutput"]["additionalContext"]
218-
assert "Consider running" in ctx
223+
assert "related test(s) found" in ctx
219224
assert "Quality Gate" in ctx
220225

221226
def test_non_git_commit_no_test_suggestion(self, monkeypatch, capsys):
@@ -241,6 +246,77 @@ def _raise(*a, **kw):
241246
assert result is None
242247

243248

249+
class TestPreToolUseCompactOutput:
250+
"""Tests for compact output format (#1039)."""
251+
252+
def test_checklist_collapsed_to_domain_counts(self, monkeypatch, capsys):
253+
"""Checklist should show domain counts, not individual items (#1039)."""
254+
monkeypatch.setattr(
255+
"subprocess.check_output",
256+
lambda *a, **kw: b"src/auth/login.ts\n",
257+
)
258+
config = {"qualityGates": {"enabled": False}}
259+
result = _run_hook(
260+
{"tool_name": "Bash", "tool_input": {"command": "git commit -m 'feat: auth'"}},
261+
monkeypatch, capsys, config=config,
262+
)
263+
assert result is not None
264+
ctx = result["hookSpecificOutput"]["additionalContext"]
265+
# Collapsed: "[Checklist] security(5)" not individual items
266+
assert "[Checklist]" in ctx
267+
assert "security(" in ctx
268+
# Should NOT contain individual checklist items
269+
assert "Validate and sanitize" not in ctx
270+
271+
def test_quality_gate_compact_single_line(self, monkeypatch, capsys):
272+
"""Quality gate should be a single line (#1039)."""
273+
config = {"qualityGates": {"enabled": True}}
274+
result = _run_hook(
275+
{"tool_name": "Bash", "tool_input": {"command": "git commit -m 'test'"}},
276+
monkeypatch, capsys, config=config,
277+
)
278+
assert result is not None
279+
ctx = result["hookSpecificOutput"]["additionalContext"]
280+
# Quality gate alone: single line, no newlines
281+
assert "[Quality Gate]" in ctx
282+
assert ctx.count("\n") == 0
283+
284+
def test_full_commit_output_max_5_lines(self, monkeypatch, capsys):
285+
"""Combined output (gate + tests + checklist) should be max 5 lines (#1039)."""
286+
monkeypatch.setattr(
287+
"subprocess.check_output",
288+
lambda *a, **kw: b"src/auth/login.ts\nsrc/api/users.ts\n",
289+
)
290+
config = {"qualityGates": {"enabled": True}}
291+
result = _run_hook(
292+
{"tool_name": "Bash", "tool_input": {"command": "git commit -m 'feat: all'"}},
293+
monkeypatch, capsys, config=config,
294+
)
295+
assert result is not None
296+
ctx = result["hookSpecificOutput"]["additionalContext"]
297+
line_count = ctx.count("\n") + 1
298+
assert line_count <= 5, f"Output has {line_count} lines, max is 5:\n{ctx}"
299+
300+
def test_test_suggestion_shows_count_not_files(self, monkeypatch, capsys):
301+
"""Test suggestion should show count, not individual file paths (#1039)."""
302+
monkeypatch.setattr(
303+
"subprocess.check_output",
304+
lambda *a, **kw: b"src/foo.ts\nsrc/bar.ts\nsrc/baz.ts\n",
305+
)
306+
config = {"qualityGates": {"enabled": False}}
307+
result = _run_hook(
308+
{"tool_name": "Bash", "tool_input": {"command": "git commit -m 'feat: multi'"}},
309+
monkeypatch, capsys, config=config,
310+
)
311+
assert result is not None
312+
ctx = result["hookSpecificOutput"]["additionalContext"]
313+
# Should show count (3 files * ~5 candidates each = many, but deduplicated)
314+
assert "related test(s) found" in ctx
315+
# Should NOT list individual test files
316+
assert ".spec.ts" not in ctx
317+
assert ".test.ts" not in ctx
318+
319+
244320
class TestPreToolUseStatusMessage:
245321
"""Tests for agent statusMessage in hook output (#974)."""
246322

@@ -325,4 +401,4 @@ def test_status_combined_with_quality_gate(self, monkeypatch, capsys, tmp_path):
325401
hook_out = result["hookSpecificOutput"]
326402
assert "statusMessage" in hook_out
327403
assert "additionalContext" in hook_out
328-
assert "Quality Gate" in hook_out["additionalContext"]
404+
assert "[Quality Gate]" in hook_out["additionalContext"]

0 commit comments

Comments
 (0)