11"""Response guidance builder for SessionStart hook.
22
3- This module provides the GuidanceBuilder class which generates XML templates
3+ This module provides the GuidanceBuilder class which loads XML templates
44teaching Claude how to structure responses for reliable memory signal detection.
55
6+ The guidance templates are stored as external XML files in the templates/
7+ directory for easy editing without code changes.
8+
69The guidance includes:
710- Capture patterns for decisions, learnings, blockers, progress
811- Trigger phrases that improve signal detection accuracy
912- Inline marker syntax reference with namespace support
13+ - Memory recall instructions for surfacing past memories
1014- Best practices for memorable content formatting
1115
1216Detail Levels:
1317- minimal: Basic syntax reference only (~200 tokens)
14- - standard: Syntax + key patterns (~500 tokens)
15- - detailed: Full templates with examples (~1000 tokens)
18+ - standard: Syntax + key patterns + recall instructions (~900 tokens)
19+ - detailed: Full templates with examples (~1200 tokens)
1620"""
1721
1822from __future__ import annotations
1923
20- from dataclasses import dataclass
24+ import logging
2125from enum import Enum
26+ from pathlib import Path
2227
2328__all__ = ["GuidanceBuilder" , "GuidanceLevel" ]
2429
30+ logger = logging .getLogger (__name__ )
31+
32+ # Directory containing XML templates
33+ TEMPLATES_DIR = Path (__file__ ).parent / "templates"
34+
2535
2636class GuidanceLevel (Enum ):
2737 """Detail level for response guidance.
2838
2939 - MINIMAL: Inline marker syntax only, lowest token cost
30- - STANDARD: Syntax + capture patterns, balanced
40+ - STANDARD: Syntax + capture patterns + recall instructions , balanced
3141 - DETAILED: Full templates with examples, highest value
3242 """
3343
@@ -36,106 +46,14 @@ class GuidanceLevel(Enum):
3646 DETAILED = "detailed"
3747
3848
39- @dataclass (frozen = True )
40- class CapturePattern :
41- """Represents a capture pattern definition.
42-
43- Attributes:
44- type_name: The signal type (decision, learning, blocker, progress)
45- description: When to use this pattern
46- template: Markdown template for structuring content
47- trigger_phrases: Phrases that trigger signal detection
48- """
49-
50- type_name : str
51- description : str
52- template : str
53- trigger_phrases : tuple [str , ...]
54-
55-
56- # Pattern definitions for detailed guidance
57- CAPTURE_PATTERNS : tuple [CapturePattern , ...] = (
58- CapturePattern (
59- type_name = "decision" ,
60- description = "When making architectural or design decisions" ,
61- template = """**Decision**: [One-line summary]
62- **Context**: [Why this decision was needed]
63- **Choice**: [What was chosen]
64- **Rationale**: [Why this choice over alternatives]
65- **Alternatives considered**: [Other options evaluated]""" ,
66- trigger_phrases = (
67- "We decided to..." ,
68- "The decision is to..." ,
69- "Going with X because..." ,
70- "After evaluating, we chose..." ,
71- ),
72- ),
73- CapturePattern (
74- type_name = "learning" ,
75- description = "When discovering insights or TIL moments" ,
76- template = """**Learning**: [One-line insight]
77- **Context**: [How this was discovered]
78- **Application**: [When/how to apply this]""" ,
79- trigger_phrases = (
80- "TIL..." ,
81- "Discovered that..." ,
82- "Learned that..." ,
83- "Turns out..." ,
84- "Interesting finding..." ,
85- ),
86- ),
87- CapturePattern (
88- type_name = "blocker" ,
89- description = "When encountering obstacles or issues" ,
90- template = """**Blocker**: [One-line issue]
91- **Impact**: [What this blocks]
92- **Status**: [investigating/blocked/resolved]
93- **Resolution**: [If resolved, how]""" ,
94- trigger_phrases = (
95- "Blocked by..." ,
96- "Cannot proceed because..." ,
97- "Stuck on..." ,
98- "Issue discovered..." ,
99- "Problem found..." ,
100- ),
101- ),
102- CapturePattern (
103- type_name = "progress" ,
104- description = "When completing milestones or deliverables" ,
105- template = """**Completed**: [What was finished]
106- **Deliverables**: [Concrete outputs]
107- **Next**: [What comes next]""" ,
108- trigger_phrases = (
109- "Completed..." ,
110- "Finished implementing..." ,
111- "Milestone reached..." ,
112- "Done with..." ,
113- "Successfully implemented..." ,
114- ),
115- ),
116- )
117-
118- # Namespace definitions for inline markers
119- VALID_NAMESPACES : tuple [str , ...] = (
120- "inception" ,
121- "elicitation" ,
122- "research" ,
123- "decisions" ,
124- "progress" ,
125- "blockers" ,
126- "reviews" ,
127- "learnings" ,
128- "retrospective" ,
129- "patterns" ,
130- )
131-
132-
13349class GuidanceBuilder :
13450 """Builds XML response guidance for session injection.
13551
136- This class generates XML templates that teach Claude how to structure
137- responses for reliable memory signal detection. The guidance helps
138- improve signal detection accuracy from ~70% to ~85%+.
52+ This class loads XML templates from the templates/ directory that teach
53+ Claude how to structure responses for reliable memory signal detection.
54+ The guidance helps improve signal detection accuracy from ~70% to ~85%+.
55+
56+ Templates can be edited directly without code changes.
13957
14058 The guidance is included in SessionStart additionalContext when
14159 `HOOK_SESSION_START_INCLUDE_GUIDANCE` is enabled.
@@ -147,20 +65,31 @@ class GuidanceBuilder:
14765 # Returns XML string for additionalContext prepending
14866 """
14967
68+ def __init__ (self , templates_dir : Path | None = None ) -> None :
69+ """Initialize the guidance builder.
70+
71+ Args:
72+ templates_dir: Optional custom templates directory.
73+ Defaults to the bundled templates/ directory.
74+ """
75+ self ._templates_dir = templates_dir or TEMPLATES_DIR
76+ self ._cache : dict [str , str ] = {}
77+
15078 def build_guidance (self , detail_level : str = "standard" ) -> str :
151- """Build response guidance XML.
79+ """Build response guidance XML by loading from template file .
15280
15381 Args:
15482 detail_level: One of "minimal", "standard", or "detailed".
15583 - minimal: Inline marker syntax only (~200 tokens)
156- - standard: Syntax + capture patterns (~500 tokens)
157- - detailed: Full templates with examples (~1000 tokens)
84+ - standard: Syntax + capture patterns + recall (~900 tokens)
85+ - detailed: Full templates with examples (~1200 tokens)
15886
15987 Returns:
16088 XML string for inclusion in additionalContext.
16189
16290 Raises:
16391 ValueError: If detail_level is not recognized.
92+ FileNotFoundError: If template file doesn't exist.
16493
16594 Example::
16695
@@ -171,128 +100,43 @@ def build_guidance(self, detail_level: str = "standard") -> str:
171100 try :
172101 level = GuidanceLevel (detail_level .lower ())
173102 except ValueError :
174- valid = [level .value for level in GuidanceLevel ]
103+ valid = [lv .value for lv in GuidanceLevel ]
175104 msg = f"Invalid detail_level '{ detail_level } '. Valid: { valid } "
176105 raise ValueError (msg ) from None
177106
178- if level == GuidanceLevel .MINIMAL :
179- return self ._build_minimal ()
180- if level == GuidanceLevel .STANDARD :
181- return self ._build_standard ()
182- return self ._build_detailed ()
183-
184- def _build_minimal (self ) -> str :
185- """Build minimal guidance - inline markers only."""
186- return """<response_guidance level="minimal">
187- <inline_markers title="Quick Capture Markers">
188- <marker syntax="[remember] text" description="Capture to learnings namespace"/>
189- <marker syntax="[remember:namespace] text" description="Capture to specified namespace"/>
190- <marker syntax="[capture] text" description="Auto-detect namespace from content"/>
191- <marker syntax="@memory text" description="Alternative capture syntax"/>
192- <marker syntax="@memory:namespace text" description="Alternative with namespace"/>
193- </inline_markers>
194- <namespaces>decisions, learnings, blockers, progress, patterns, research, reviews, retrospective</namespaces>
195- </response_guidance>"""
196-
197- def _build_standard (self ) -> str :
198- """Build standard guidance - syntax + patterns."""
199- patterns_xml = self ._build_patterns_section (include_templates = False )
200- markers_xml = self ._build_markers_section ()
201- practices_xml = self ._build_practices_section ()
202-
203- return f"""<response_guidance level="standard">
204- { patterns_xml }
205- { markers_xml }
206- { practices_xml }
207- </response_guidance>"""
208-
209- def _build_detailed (self ) -> str :
210- """Build detailed guidance - full templates with examples."""
211- patterns_xml = self ._build_patterns_section (include_templates = True )
212- markers_xml = self ._build_markers_section ()
213- practices_xml = self ._build_practices_section ()
214- examples_xml = self ._build_examples_section ()
215-
216- return f"""<response_guidance level="detailed">
217- { patterns_xml }
218- { markers_xml }
219- { practices_xml }
220- { examples_xml }
221- </response_guidance>"""
222-
223- def _build_patterns_section (self , include_templates : bool = False ) -> str :
224- """Build the capture patterns section."""
225- lines = [
226- ' <capture_patterns title="How to Structure Responses for Memory Capture">'
227- ]
228-
229- for pattern in CAPTURE_PATTERNS :
230- lines .append (f' <pattern type="{ pattern .type_name } ">' )
231- lines .append (f" <description>{ pattern .description } </description>" )
232-
233- if include_templates :
234- # Escape template content for XML
235- escaped_template = (
236- pattern .template .replace ("&" , "&" )
237- .replace ("<" , "<" )
238- .replace (">" , ">" )
239- )
240- lines .append (f" <template>{ escaped_template } </template>" )
241-
242- # Always include trigger phrases
243- lines .append (" <trigger_phrases>" )
244- for phrase in pattern .trigger_phrases :
245- lines .append (f" <phrase>{ phrase } </phrase>" )
246- lines .append (" </trigger_phrases>" )
247- lines .append (" </pattern>" )
248-
249- lines .append (" </capture_patterns>" )
250- return "\n " .join (lines )
251-
252- def _build_markers_section (self ) -> str :
253- """Build the inline markers section."""
254- return """ <inline_markers title="Quick Capture Markers">
255- <marker syntax="[remember] text" namespace="learnings" description="Quick capture to learnings"/>
256- <marker syntax="[remember:namespace] text" namespace="specified" description="Capture to specific namespace"/>
257- <marker syntax="[capture] text" namespace="auto-detect" description="Auto-detect namespace from content"/>
258- <marker syntax="[capture:decisions] text" namespace="decisions" description="Capture decision explicitly"/>
259- <marker syntax="@memory text" namespace="auto-detect" description="Alternative inline syntax"/>
260- <marker syntax="@memory:patterns text" namespace="patterns" description="Alternative with namespace"/>
261- </inline_markers>
262- <valid_namespaces>decisions, learnings, blockers, progress, patterns, research, reviews, retrospective, inception, elicitation</valid_namespaces>"""
263-
264- def _build_practices_section (self ) -> str :
265- """Build the best practices section."""
266- return """ <best_practices title="Memory Capture Best Practices">
267- <practice>Use clear trigger phrases at the start of memorable content</practice>
268- <practice>Include rationale with decisions, not just the choice</practice>
269- <practice>Tag blockers with resolution status when solved</practice>
270- <practice>Keep summaries under 100 characters for better signal detection</practice>
271- <practice>Use namespace hints when content clearly belongs to a specific category</practice>
272- </best_practices>"""
273-
274- def _build_examples_section (self ) -> str :
275- """Build the examples section for detailed level."""
276- return """ <examples title="Effective Capture Examples">
277- <example type="decision">
278- <input>We need to pick a database</input>
279- <output>**Decision**: Use PostgreSQL for persistence
280- **Context**: Need ACID compliance and JSON support
281- **Choice**: PostgreSQL 15 with JSONB columns
282- **Rationale**: Team expertise, proven reliability, excellent JSON support
283- **Alternatives considered**: MongoDB (no ACID), SQLite (scaling concerns)</output>
284- </example>
285- <example type="learning">
286- <input>Found a pytest quirk</input>
287- <output>**Learning**: pytest fixtures with scope="module" share state across tests
288- **Context**: Discovered when tests failed intermittently due to shared mock state
289- **Application**: Use scope="function" for mutable fixtures, module for readonly</output>
290- </example>
291- <example type="inline_marker">
292- <input>Quick insight during coding</input>
293- <output>[remember:patterns] Always use frozen dataclasses for immutable models to prevent accidental mutation</output>
294- </example>
295- </examples>"""
107+ return self ._load_template (level .value )
108+
109+ def _load_template (self , level : str ) -> str :
110+ """Load a template file by level name.
111+
112+ Args:
113+ level: The guidance level (minimal, standard, detailed).
114+
115+ Returns:
116+ Template content as string.
117+
118+ Raises:
119+ FileNotFoundError: If template file doesn't exist.
120+ """
121+ # Check cache first
122+ if level in self ._cache :
123+ return self ._cache [level ]
124+
125+ template_path = self ._templates_dir / f"guidance_{ level } .xml"
126+
127+ if not template_path .exists ():
128+ msg = f"Template file not found: { template_path } "
129+ raise FileNotFoundError (msg )
130+
131+ content = template_path .read_text (encoding = "utf-8" ).strip ()
132+ self ._cache [level ] = content
133+
134+ logger .debug ("Loaded guidance template: %s (%d chars)" , level , len (content ))
135+ return content
136+
137+ def clear_cache (self ) -> None :
138+ """Clear the template cache to reload from disk."""
139+ self ._cache .clear ()
296140
297141
298142def get_guidance_builder () -> GuidanceBuilder :
0 commit comments