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

Commit 2f0b065

Browse files
committed
feat(hooks): integrate UserPromptSubmit hook
- hooks.json: Switch to signal-detecting user_prompt.py handler - config_loader.py: Add user_prompt_enabled config with env var - models.py: Update exports for capture decision types - PROGRESS.md: Document Phase 3 completion (6/6 tasks)
1 parent 01ed2c8 commit 2f0b065

5 files changed

Lines changed: 88 additions & 10 deletions

File tree

docs/spec/active/2025-12-19-hook-based-memory-capture/PROGRESS.md

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ format_version: "1.0.0"
44
project_id: SPEC-2025-12-19-001
55
project_name: "Hook-Based Memory Capture"
66
project_status: in-progress
7-
current_phase: 3
7+
current_phase: 4
88
implementation_started: 2025-12-19T00:00:00Z
99
last_session: 2025-12-19T00:00:00Z
1010
last_updated: 2025-12-19T00:00:00Z
@@ -36,12 +36,12 @@ This document tracks implementation progress against the spec plan.
3636
| 2.3 | Implement Budget Calculator | done | 2025-12-19 | 2025-12-19 | Integrated into ContextBuilder with adaptive tiers |
3737
| 2.4 | Create SessionStart Hook Handler | done | 2025-12-19 | 2025-12-19 | Created `hooks/session_start_handler.py` |
3838
| 2.5 | Register SessionStart Hook | done | 2025-12-19 | 2025-12-19 | Created `hooks/session_start.py` wrapper, updated hooks.json |
39-
| 3.1 | Implement SignalDetector | pending | | | Pattern matching for signals |
40-
| 3.2 | Implement Novelty Checker | pending | | | Duplicate detection |
41-
| 3.3 | Implement CaptureDecider | pending | | | Decision logic |
42-
| 3.4 | Create UserPromptSubmit Hook Handler | pending | | | hooks/user_prompt.py |
43-
| 3.5 | Format Capture Suggestions | pending | | | XML suggestion format |
44-
| 3.6 | Register UserPromptSubmit Hook | pending | | | Update hooks.json |
39+
| 3.1 | Implement SignalDetector | done | 2025-12-19 | 2025-12-19 | Created `hooks/signal_detector.py` with pattern matching |
40+
| 3.2 | Implement Novelty Checker | done | 2025-12-19 | 2025-12-19 | Created `hooks/novelty_checker.py` with semantic similarity |
41+
| 3.3 | Implement CaptureDecider | done | 2025-12-19 | 2025-12-19 | Created `hooks/capture_decider.py` with threshold-based decision logic |
42+
| 3.4 | Create UserPromptSubmit Hook Handler | done | 2025-12-19 | 2025-12-19 | Created `hooks/user_prompt_handler.py` |
43+
| 3.5 | Format Capture Suggestions | done | 2025-12-19 | 2025-12-19 | XML formatter in handler via XMLBuilder |
44+
| 3.6 | Register UserPromptSubmit Hook | done | 2025-12-19 | 2025-12-19 | Updated hooks.json, created wrapper |
4545
| 4.1 | Implement Session Analyzer | pending | | | Transcript analysis |
4646
| 4.2 | Detect Uncaptured Memories | pending | | | Filter already-captured |
4747
| 4.3 | Enhance Stop Hook Handler | pending | | | Update hooks/stop.py |
@@ -63,7 +63,7 @@ This document tracks implementation progress against the spec plan.
6363
|-------|------|----------|--------|
6464
| 1 | Core Hook Infrastructure | 100% | done |
6565
| 2 | SessionStart Context Injection | 100% | done |
66-
| 3 | Capture Signal Detection | 0% | pending |
66+
| 3 | Capture Signal Detection | 100% | done |
6767
| 4 | Stop Hook Enhancement | 0% | pending |
6868
| 5 | Testing & Documentation | 0% | pending |
6969

@@ -128,3 +128,26 @@ This document tracks implementation progress against the spec plan.
128128
- `mypy` - No issues found in 8 source files
129129
- `pytest` - 910 tests passed
130130
- Ready for Phase 3: Capture Signal Detection
131+
132+
### 2025-12-19 - Phase 3 Complete
133+
- **Phase 3 completed**: All 6 tasks done
134+
- Created signal detection and capture decision pipeline:
135+
- `signal_detector.py` - Pattern-based detection for decisions, learnings, blockers
136+
- `novelty_checker.py` - Semantic similarity checking to avoid duplicates
137+
- `capture_decider.py` - Threshold-based decision logic (AUTO/SUGGEST/SKIP)
138+
- Created `src/git_notes_memory/hooks/user_prompt_handler.py`:
139+
- Full UserPromptSubmit handler with signal detection pipeline
140+
- AUTO capture for high-confidence signals (≥0.95)
141+
- SUGGEST for medium-confidence signals (0.7-0.95)
142+
- XML-formatted suggestions for additionalContext injection
143+
- Non-blocking error handling (exit 0 on all paths)
144+
- Created `hooks/user_prompt.py` wrapper script
145+
- Updated `hooks/hooks.json`:
146+
- Changed UserPromptSubmit to use new signal-detecting handler
147+
- Hook disabled by default (opt-in via HOOK_USER_PROMPT_ENABLED)
148+
- Added `user_prompt_enabled` to HookConfig with env var support
149+
- All quality gates passed:
150+
- `ruff check` - All checks passed
151+
- `mypy` - No issues found in 11 source files
152+
- `pytest` - 910 tests passed
153+
- Ready for Phase 4: Stop Hook Enhancement

hooks/hooks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
},
99
{
1010
"event": "UserPromptSubmit",
11-
"script": "userpromptsubmit.py",
12-
"description": "Optional: Captures significant prompts as memories when marker is present",
11+
"script": "user_prompt.py",
12+
"description": "Detects memorable content (decisions, learnings, blockers) and suggests capture",
1313
"enabled": false
1414
},
1515
{

src/git_notes_memory/hooks/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@
4343
"TokenBudget",
4444
# Signal Detection
4545
"SignalDetector",
46+
"SIGNAL_PATTERNS",
4647
"CaptureSignal",
4748
"SignalType",
49+
# Novelty Checking
50+
"NoveltyChecker",
51+
"NoveltyResult",
4852
# Capture Decision
4953
"CaptureDecider",
5054
"CaptureDecision",
@@ -90,6 +94,10 @@ def __getattr__(name: str) -> object:
9094
from git_notes_memory.hooks.signal_detector import SignalDetector
9195

9296
return SignalDetector
97+
if name == "SIGNAL_PATTERNS":
98+
from git_notes_memory.hooks.signal_detector import SIGNAL_PATTERNS
99+
100+
return SIGNAL_PATTERNS
93101
if name == "CaptureSignal":
94102
from git_notes_memory.hooks.models import CaptureSignal
95103

@@ -99,6 +107,16 @@ def __getattr__(name: str) -> object:
99107

100108
return SignalType
101109

110+
# Novelty Checking
111+
if name == "NoveltyChecker":
112+
from git_notes_memory.hooks.novelty_checker import NoveltyChecker
113+
114+
return NoveltyChecker
115+
if name == "NoveltyResult":
116+
from git_notes_memory.hooks.models import NoveltyResult
117+
118+
return NoveltyResult
119+
102120
# Capture Decision
103121
if name == "CaptureDecider":
104122
from git_notes_memory.hooks.capture_decider import CaptureDecider

src/git_notes_memory/hooks/config_loader.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
HOOK_CAPTURE_DETECTION_MIN_CONFIDENCE: Minimum confidence for suggestions
1616
HOOK_CAPTURE_DETECTION_AUTO_THRESHOLD: Confidence for auto-capture
1717
HOOK_CAPTURE_DETECTION_NOVELTY_THRESHOLD: Novelty score threshold
18+
HOOK_USER_PROMPT_ENABLED: Enable UserPromptSubmit hook
1819
HOOK_STOP_ENABLED: Enable Stop hook
1920
HOOK_STOP_PROMPT_UNCAPTURED: Prompt for uncaptured content
2021
HOOK_TIMEOUT: Default hook timeout in seconds
@@ -90,6 +91,9 @@ class HookConfig:
9091
stop_prompt_uncaptured: bool = True
9192
stop_sync_index: bool = True
9293

94+
# UserPromptSubmit hook settings
95+
user_prompt_enabled: bool = False # Uses capture_detection_enabled by default
96+
9397
# Performance settings
9498
timeout: int = 30
9599
debug: bool = False
@@ -261,6 +265,12 @@ def load_hook_config(env: dict[str, str] | None = None) -> HookConfig:
261265
defaults.capture_detection_novelty_threshold,
262266
)
263267

268+
# UserPromptSubmit hook settings
269+
if "HOOK_USER_PROMPT_ENABLED" in env:
270+
kwargs["user_prompt_enabled"] = _parse_bool(
271+
env["HOOK_USER_PROMPT_ENABLED"]
272+
)
273+
264274
# Stop hook settings
265275
if "HOOK_STOP_ENABLED" in env:
266276
kwargs["stop_enabled"] = _parse_bool(env["HOOK_STOP_ENABLED"])

src/git_notes_memory/hooks/models.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
__all__ = [
2323
"SignalType",
2424
"CaptureSignal",
25+
"NoveltyResult",
2526
"CaptureAction",
2627
"CaptureDecision",
2728
"SuggestedCapture",
@@ -101,6 +102,32 @@ def __post_init__(self) -> None:
101102
raise ValueError(msg)
102103

103104

105+
@dataclass(frozen=True)
106+
class NoveltyResult:
107+
"""Result of novelty checking for a capture signal.
108+
109+
Indicates whether detected content is novel (should be captured)
110+
or a duplicate of existing memories.
111+
112+
Attributes:
113+
novelty_score: Score from 0.0 (duplicate) to 1.0 (completely new).
114+
is_novel: Whether the content passes the novelty threshold.
115+
similar_memory_ids: IDs of similar existing memories.
116+
highest_similarity: Highest similarity score found.
117+
"""
118+
119+
novelty_score: float
120+
is_novel: bool
121+
similar_memory_ids: list[str] = field(default_factory=list)
122+
highest_similarity: float = 0.0
123+
124+
def __post_init__(self) -> None:
125+
"""Validate novelty score is in valid range."""
126+
if not 0.0 <= self.novelty_score <= 1.0:
127+
msg = f"Novelty score must be between 0.0 and 1.0, got {self.novelty_score}"
128+
raise ValueError(msg)
129+
130+
104131
class CaptureAction(Enum):
105132
"""Actions the capture decider can take.
106133

0 commit comments

Comments
 (0)