Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit dc34089

Browse files
zircoteclaude
andcommitted
style: apply ruff formatting and fix unused imports
- Format 10 files with ruff format - Remove unused imports (tempfile, Any) from test_hook_integration.py - Sort import blocks in test_hook_integration.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3b884e5 commit dc34089

10 files changed

Lines changed: 85 additions & 62 deletions

src/git_notes_memory/hooks/config_loader.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,7 @@ def load_hook_config(env: dict[str, str] | None = None) -> HookConfig:
226226

227227
# SessionStart settings
228228
if "HOOK_SESSION_START_ENABLED" in env:
229-
kwargs["session_start_enabled"] = _parse_bool(
230-
env["HOOK_SESSION_START_ENABLED"]
231-
)
229+
kwargs["session_start_enabled"] = _parse_bool(env["HOOK_SESSION_START_ENABLED"])
232230
if "HOOK_SESSION_START_BUDGET_MODE" in env:
233231
with contextlib.suppress(ValueError):
234232
kwargs["session_start_budget_mode"] = _parse_budget_mode(
@@ -268,9 +266,7 @@ def load_hook_config(env: dict[str, str] | None = None) -> HookConfig:
268266

269267
# UserPromptSubmit hook settings
270268
if "HOOK_USER_PROMPT_ENABLED" in env:
271-
kwargs["user_prompt_enabled"] = _parse_bool(
272-
env["HOOK_USER_PROMPT_ENABLED"]
273-
)
269+
kwargs["user_prompt_enabled"] = _parse_bool(env["HOOK_USER_PROMPT_ENABLED"])
274270

275271
# Stop hook settings
276272
if "HOOK_STOP_ENABLED" in env:

src/git_notes_memory/hooks/project_detector.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ def _get_git_repo_name(git_root: Path) -> str | None:
133133
# Match patterns like:
134134
# url = git@github.com:user/repo.git
135135
# url = https://github.com/user/repo.git
136-
match = re.search(r'url\s*=\s*.*[/:]([^/]+?)(?:\.git)?\s*$', content, re.MULTILINE)
136+
match = re.search(
137+
r"url\s*=\s*.*[/:]([^/]+?)(?:\.git)?\s*$", content, re.MULTILINE
138+
)
137139
if match:
138140
return match.group(1)
139141
except OSError:
@@ -278,7 +280,7 @@ def _extract_spec_from_claude_md(claude_md_path: Path) -> str | None:
278280
patterns = [
279281
r'(?:spec_id|project_id):\s*["\']?(SPEC-\d{4}-\d{2}-\d{2}-\d+)["\']?',
280282
r'Active\s+Spec:\s*["\']?(SPEC-\d{4}-\d{2}-\d{2}-\d+)["\']?',
281-
r'\b(SPEC-\d{4}-\d{2}-\d{2}-\d+)\b', # Any SPEC-... pattern
283+
r"\b(SPEC-\d{4}-\d{2}-\d{2}-\d+)\b", # Any SPEC-... pattern
282284
]
283285
for pattern in patterns:
284286
match = re.search(pattern, content, re.IGNORECASE)

src/git_notes_memory/hooks/session_analyzer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,7 @@ def analyze_content(
280280
# Filter by novelty
281281
if check_novelty and filtered:
282282
checker = self._get_novelty_checker()
283-
filtered = [
284-
s for s in filtered if checker.check_signal_novelty(s).is_novel
285-
]
283+
filtered = [s for s in filtered if checker.check_signal_novelty(s).is_novel]
286284

287285
# Sort and limit
288286
filtered.sort(key=lambda s: s.confidence, reverse=True)

src/git_notes_memory/hooks/signal_detector.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@
4747
],
4848
SignalType.LEARNING: [
4949
# Strong learning signals
50-
(r"(?i)\b(I|we)\s+(learned|realized|discovered|found out)\s+(that|about)?\b", 0.90),
50+
(
51+
r"(?i)\b(I|we)\s+(learned|realized|discovered|found out)\s+(that|about)?\b",
52+
0.90,
53+
),
5154
(r"(?i)\bTIL\b", 0.95), # Very strong explicit signal
5255
(r"(?i)\bturns out\b", 0.85),
5356
(r"(?i)\bkey (insight|takeaway|learning)[:\s]", 0.92),
@@ -136,7 +139,9 @@ class SignalDetector:
136139
"""
137140

138141
# Class-level compiled patterns cache
139-
_compiled_patterns: ClassVar[dict[SignalType, list[tuple[re.Pattern[str], float]]]] = {}
142+
_compiled_patterns: ClassVar[
143+
dict[SignalType, list[tuple[re.Pattern[str], float]]]
144+
] = {}
140145

141146
def __init__(
142147
self,
@@ -241,7 +246,9 @@ def detect(self, text: str) -> list[CaptureSignal]:
241246
# Sort by position
242247
signals.sort(key=lambda s: s.position)
243248

244-
logger.debug("Detected %d signals in text of length %d", len(signals), len(text))
249+
logger.debug(
250+
"Detected %d signals in text of length %d", len(signals), len(text)
251+
)
245252
return signals
246253

247254
def _extract_context(self, text: str, start: int, end: int) -> str:

src/git_notes_memory/hooks/user_prompt_handler.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ def main() -> None:
296296
try:
297297
# Read and validate input
298298
input_data = _read_input()
299-
logger.debug("Received input with prompt: %s...", input_data.get("prompt", "")[:50])
299+
logger.debug(
300+
"Received input with prompt: %s...", input_data.get("prompt", "")[:50]
301+
)
300302

301303
if not _validate_input(input_data):
302304
logger.warning("Invalid hook input - missing prompt field")
@@ -320,7 +322,9 @@ def main() -> None:
320322
decider = CaptureDecider(config=config)
321323
decision = decider.decide(signals)
322324

323-
logger.debug("Capture decision: %s - %s", decision.action.value, decision.reason)
325+
logger.debug(
326+
"Capture decision: %s - %s", decision.action.value, decision.reason
327+
)
324328

325329
# Handle the decision
326330
captured: list[dict[str, Any]] = []

tests/test_hook_handlers.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ def temp_transcript(tmp_path: Path) -> Path:
7979
"messages": [
8080
{"role": "user", "content": "I decided to use PostgreSQL"},
8181
{"role": "assistant", "content": "Good choice!"},
82-
{"role": "user", "content": "I learned that indexes improve performance"},
82+
{
83+
"role": "user",
84+
"content": "I learned that indexes improve performance",
85+
},
8386
]
8487
}
8588
)
@@ -160,7 +163,10 @@ def test_write_output_format(self) -> None:
160163
output = json.loads(captured.getvalue())
161164
assert "hookSpecificOutput" in output
162165
assert output["hookSpecificOutput"]["hookEventName"] == "SessionStart"
163-
assert output["hookSpecificOutput"]["additionalContext"] == "<context>test</context>"
166+
assert (
167+
output["hookSpecificOutput"]["additionalContext"]
168+
== "<context>test</context>"
169+
)
164170

165171
def test_setup_logging_debug(self) -> None:
166172
"""Test debug logging setup configures correctly."""
@@ -327,7 +333,9 @@ def test_write_output_auto_action(self) -> None:
327333
confidence=0.98,
328334
)
329335
]
330-
captured_memories = [{"success": True, "memory_id": "abc123", "summary": "Test"}]
336+
captured_memories = [
337+
{"success": True, "memory_id": "abc123", "summary": "Test"}
338+
]
331339

332340
out = io.StringIO()
333341
with patch.object(sys, "stdout", out):
@@ -522,9 +530,7 @@ def test_session_start_wrapper_import_error(self) -> None:
522530
import importlib.util
523531
from pathlib import Path as P
524532

525-
wrapper_path = (
526-
P(__file__).parent.parent / "hooks" / "session_start.py"
527-
)
533+
wrapper_path = P(__file__).parent.parent / "hooks" / "session_start.py"
528534
if not wrapper_path.exists():
529535
pytest.skip("Wrapper script not found")
530536

@@ -549,9 +555,7 @@ def test_user_prompt_wrapper_import_error(self) -> None:
549555
import importlib.util
550556
from pathlib import Path as P
551557

552-
wrapper_path = (
553-
P(__file__).parent.parent / "hooks" / "user_prompt.py"
554-
)
558+
wrapper_path = P(__file__).parent.parent / "hooks" / "user_prompt.py"
555559
if not wrapper_path.exists():
556560
pytest.skip("Wrapper script not found")
557561

@@ -580,9 +584,7 @@ def test_stop_wrapper_import_error(self) -> None:
580584
import importlib.util
581585
from pathlib import Path as P
582586

583-
wrapper_path = (
584-
P(__file__).parent.parent / "hooks" / "stop.py"
585-
)
587+
wrapper_path = P(__file__).parent.parent / "hooks" / "stop.py"
586588
if not wrapper_path.exists():
587589
pytest.skip("Wrapper script not found")
588590

@@ -673,9 +675,7 @@ def test_session_start_respects_disabled_hook(
673675
config = load_hook_config()
674676
assert config.enabled is False
675677

676-
def test_user_prompt_respects_disabled_hook(
677-
self, hook_env_disabled: None
678-
) -> None:
678+
def test_user_prompt_respects_disabled_hook(self, hook_env_disabled: None) -> None:
679679
"""Test UserPromptSubmit respects HOOK_ENABLED=false."""
680680
from git_notes_memory.hooks.config_loader import load_hook_config
681681

tests/test_hook_integration.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
import json
1717
import os
1818
import sys
19-
import tempfile
2019
from pathlib import Path
21-
from typing import TYPE_CHECKING, Any
20+
from typing import TYPE_CHECKING
2221
from unittest.mock import MagicMock, patch
2322

2423
import pytest
@@ -40,14 +39,12 @@ def temp_git_repo(tmp_path: Path) -> Generator[Path, None, None]:
4039
(git_dir / "HEAD").write_text("ref: refs/heads/main")
4140
# Include origin URL so git_repo name can be detected
4241
(git_dir / "config").write_text(
43-
'[core]\n\trepositoryformatversion = 0\n'
42+
"[core]\n\trepositoryformatversion = 0\n"
4443
'[remote "origin"]\n\turl = https://github.com/test-user/test-project.git\n'
4544
)
4645

4746
# Create a basic project structure
48-
(tmp_path / "pyproject.toml").write_text(
49-
'[project]\nname = "test-project"\n'
50-
)
47+
(tmp_path / "pyproject.toml").write_text('[project]\nname = "test-project"\n')
5148
(tmp_path / "src").mkdir()
5249

5350
yield tmp_path
@@ -179,7 +176,9 @@ def test_session_start_json_output_format(
179176
"""Test SessionStart hook produces valid JSON output."""
180177
from git_notes_memory.hooks.session_start_handler import _write_output
181178

182-
context = "<memory_context project='test'><summary>Test</summary></memory_context>"
179+
context = (
180+
"<memory_context project='test'><summary>Test</summary></memory_context>"
181+
)
183182

184183
captured = io.StringIO()
185184
with patch.object(sys, "stdout", captured):
@@ -319,7 +318,11 @@ def test_low_confidence_signals_skipped(self, hook_env_enabled: None) -> None:
319318
"""Test that low-confidence signals result in SKIP action."""
320319
from git_notes_memory.hooks.capture_decider import CaptureDecider
321320
from git_notes_memory.hooks.config_loader import load_hook_config
322-
from git_notes_memory.hooks.models import CaptureAction, CaptureSignal, SignalType
321+
from git_notes_memory.hooks.models import (
322+
CaptureAction,
323+
CaptureSignal,
324+
SignalType,
325+
)
323326

324327
# Create a low-confidence signal
325328
signals = [
@@ -448,12 +451,12 @@ def test_session_flow_start_to_stop(
448451
self, temp_git_repo: Path, hook_env_enabled: None
449452
) -> None:
450453
"""Test complete session flow from start to stop."""
454+
from git_notes_memory.hooks.capture_decider import CaptureDecider
451455
from git_notes_memory.hooks.config_loader import load_hook_config
452456
from git_notes_memory.hooks.context_builder import ContextBuilder
453457
from git_notes_memory.hooks.models import CaptureAction
454458
from git_notes_memory.hooks.project_detector import detect_project
455459
from git_notes_memory.hooks.signal_detector import SignalDetector
456-
from git_notes_memory.hooks.capture_decider import CaptureDecider
457460

458461
# 1. Session Start - detect project and build context
459462
project_info = detect_project(str(temp_git_repo))
@@ -556,14 +559,14 @@ def test_stop_handles_invalid_transcript(
556559
result = _analyze_session(str(invalid))
557560
assert result == []
558561

559-
def test_all_hooks_output_continue_on_error(
560-
self, hook_env_enabled: None
561-
) -> None:
562+
def test_all_hooks_output_continue_on_error(self, hook_env_enabled: None) -> None:
562563
"""Test that all hooks output continue:true even on errors."""
563564
# This verifies the non-blocking error handling pattern
564565

565566
# SessionStart - outputs empty on error (no hookSpecificOutput)
566-
from git_notes_memory.hooks.session_start_handler import _write_output as ss_write
567+
from git_notes_memory.hooks.session_start_handler import (
568+
_write_output as ss_write,
569+
)
567570

568571
captured = io.StringIO()
569572
with patch.object(sys, "stdout", captured):
@@ -572,8 +575,8 @@ def test_all_hooks_output_continue_on_error(
572575
# SessionStart doesn't have continue field but hookSpecificOutput
573576

574577
# UserPromptSubmit - outputs continue:true
575-
from git_notes_memory.hooks.user_prompt_handler import _write_output as up_write
576578
from git_notes_memory.hooks.models import CaptureAction
579+
from git_notes_memory.hooks.user_prompt_handler import _write_output as up_write
577580

578581
captured = io.StringIO()
579582
with patch.object(sys, "stdout", captured):

tests/test_hooks.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@ def test_signal_context_extraction(self, signal_detector: SignalDetector) -> Non
357357
assert len(signals) >= 1
358358
# Context should include surrounding text
359359
decision_signal = next(s for s in signals if s.type == SignalType.DECISION)
360-
assert "Redis" in decision_signal.context or "caching" in decision_signal.context
360+
assert (
361+
"Redis" in decision_signal.context or "caching" in decision_signal.context
362+
)
361363

362364
def test_confidence_scoring(self, signal_detector: SignalDetector) -> None:
363365
"""Test that confidence scores are reasonable."""

tests/test_hooks_integration.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,24 @@ def transcript_file(tmp_path: Path) -> Path:
102102
transcript = tmp_path / "transcript.jsonl"
103103
# Write JSONL format (one JSON object per line)
104104
lines = [
105-
json.dumps({"role": "user", "content": "I decided to use PostgreSQL for the database"}),
106-
json.dumps({"role": "assistant", "content": "Good choice for relational data!"}),
107-
json.dumps({"role": "user", "content": "I learned that indexes improve query performance"}),
108-
json.dumps({"role": "assistant", "content": "Yes, especially for large tables."}),
109-
json.dumps({"role": "user", "content": "The API rate limit is blocking progress"}),
105+
json.dumps(
106+
{"role": "user", "content": "I decided to use PostgreSQL for the database"}
107+
),
108+
json.dumps(
109+
{"role": "assistant", "content": "Good choice for relational data!"}
110+
),
111+
json.dumps(
112+
{
113+
"role": "user",
114+
"content": "I learned that indexes improve query performance",
115+
}
116+
),
117+
json.dumps(
118+
{"role": "assistant", "content": "Yes, especially for large tables."}
119+
),
120+
json.dumps(
121+
{"role": "user", "content": "The API rate limit is blocking progress"}
122+
),
110123
]
111124
transcript.write_text("\n".join(lines))
112125
return transcript
@@ -151,9 +164,7 @@ def test_full_flow_with_git_repo(
151164
# requires proper database setup for full end-to-end testing.
152165
# Here we validate the project detection flow works correctly.
153166

154-
def test_full_flow_with_spec_id(
155-
self, spec_project: Path, hook_env: None
156-
) -> None:
167+
def test_full_flow_with_spec_id(self, spec_project: Path, hook_env: None) -> None:
157168
"""Test SessionStart flow detects active spec."""
158169
from git_notes_memory.hooks.project_detector import detect_project
159170

@@ -240,9 +251,7 @@ def test_blocker_signal_detection(self, hook_env: None) -> None:
240251
from git_notes_memory.hooks.signal_detector import SignalDetector
241252

242253
detector = SignalDetector()
243-
signals = detector.detect(
244-
"We are blocked by the external API being down"
245-
)
254+
signals = detector.detect("We are blocked by the external API being down")
246255

247256
assert len(signals) >= 1
248257
assert any(s.type.value == "blocker" for s in signals)
@@ -253,7 +262,9 @@ def test_explicit_remember_signal(self, hook_env: None) -> None:
253262

254263
detector = SignalDetector()
255264
# Use a stronger trigger that matches pattern detection
256-
signals = detector.detect("/remember: always use prepared statements for security")
265+
signals = detector.detect(
266+
"/remember: always use prepared statements for security"
267+
)
257268

258269
# The explicit signal might require specific patterns
259270
# If no explicit signal, at least verify no crash
@@ -416,9 +427,7 @@ def test_config_consistency_across_handlers(self, hook_env: None) -> None:
416427
assert config1.user_prompt_enabled == config2.user_prompt_enabled
417428
assert config1.stop_enabled == config2.stop_enabled
418429

419-
def test_signal_detector_and_analyzer_compatibility(
420-
self, hook_env: None
421-
) -> None:
430+
def test_signal_detector_and_analyzer_compatibility(self, hook_env: None) -> None:
422431
"""Test SignalDetector and SessionAnalyzer produce compatible signals."""
423432
from git_notes_memory.hooks.models import CaptureSignal
424433
from git_notes_memory.hooks.signal_detector import SignalDetector

tests/test_hooks_performance.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,9 @@ def test_transcript_parsing_speed(self, tmp_path: Path, hook_env: None) -> None:
309309
if i % 2 == 0:
310310
lines.append(json.dumps({"role": "user", "content": f"Message {i}"}))
311311
else:
312-
lines.append(json.dumps({"role": "assistant", "content": f"Response {i}"}))
312+
lines.append(
313+
json.dumps({"role": "assistant", "content": f"Response {i}"})
314+
)
313315
transcript.write_text("\n".join(lines))
314316

315317
analyzer = SessionAnalyzer()

0 commit comments

Comments
 (0)