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

Commit 427c936

Browse files
zircoteclaude
andcommitted
refactor(guidance): externalize templates to XML files
- Move guidance templates to external XML files in templates/ directory - Add memory_recall section teaching Claude to surface retrieved memories - Simplify GuidanceBuilder to load templates with file-based caching - Remove hardcoded CAPTURE_PATTERNS constant in favor of XML content - Update tests for file-based template approach Templates can now be edited without code changes: - guidance_minimal.xml: Basic inline marker syntax (~200 tokens) - guidance_standard.xml: Patterns + recall instructions (~900 tokens) - guidance_detailed.xml: Full templates with examples (~1200 tokens) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1ae92c0 commit 427c936

5 files changed

Lines changed: 280 additions & 288 deletions

File tree

src/git_notes_memory/hooks/guidance_builder.py

Lines changed: 68 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,43 @@
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
44
teaching 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+
69
The 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
1216
Detail 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

1822
from __future__ import annotations
1923

20-
from dataclasses import dataclass
24+
import logging
2125
from 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

2636
class 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-
13349
class 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("&", "&amp;")
237-
.replace("<", "&lt;")
238-
.replace(">", "&gt;")
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

298142
def get_guidance_builder() -> GuidanceBuilder:

0 commit comments

Comments
 (0)