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

Commit ab8b104

Browse files
committed
feat(hooks): add namespace styling with colors and emojis
- Add namespace_styles.py with ANSI color codes for terminal output - Define NamespaceStyle dataclass with emoji, color, and formatting - Color psychology: red=blockers, blue=decisions, green=learnings, cyan=progress, yellow=research, magenta=patterns - Provide format_marker(), format_namespace(), format_memory_header()
1 parent 9c373e2 commit ab8b104

1 file changed

Lines changed: 338 additions & 0 deletions

File tree

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
"""Namespace styling with ANSI colors and emojis.
2+
3+
This module provides visual styling for memory namespaces using ANSI
4+
color codes and emojis. Colors are chosen based on psychological and
5+
philosophical associations with each namespace's purpose.
6+
7+
Color Psychology:
8+
- RED: Danger, urgency, stop (blockers)
9+
- BLUE: Trust, stability, authority (decisions)
10+
- GREEN: Growth, knowledge, freshness (learnings, inception)
11+
- CYAN: Forward movement, achievement (progress, elicitation)
12+
- YELLOW: Curiosity, discovery, attention (research)
13+
- MAGENTA: Creativity, patterns, wisdom (patterns, retrospective)
14+
- ORANGE: Evaluation, warmth, feedback (reviews)
15+
"""
16+
17+
from __future__ import annotations
18+
19+
from dataclasses import dataclass
20+
from enum import Enum
21+
22+
__all__ = [
23+
"NamespaceStyle",
24+
"get_style",
25+
"format_namespace",
26+
"format_marker",
27+
"STYLES",
28+
]
29+
30+
31+
class Color(Enum):
32+
"""ANSI color codes for terminal output."""
33+
34+
# Standard colors
35+
RED = "\033[31m"
36+
GREEN = "\033[32m"
37+
YELLOW = "\033[33m"
38+
BLUE = "\033[34m"
39+
MAGENTA = "\033[35m"
40+
CYAN = "\033[36m"
41+
42+
# Bright/bold colors
43+
BRIGHT_RED = "\033[91m"
44+
BRIGHT_GREEN = "\033[92m"
45+
BRIGHT_YELLOW = "\033[93m"
46+
BRIGHT_BLUE = "\033[94m"
47+
BRIGHT_MAGENTA = "\033[95m"
48+
BRIGHT_CYAN = "\033[96m"
49+
50+
# Orange (via 256-color mode)
51+
ORANGE = "\033[38;5;208m"
52+
BRIGHT_ORANGE = "\033[38;5;214m"
53+
54+
# Reset
55+
RESET = "\033[0m"
56+
57+
# Bold modifier
58+
BOLD = "\033[1m"
59+
60+
61+
@dataclass(frozen=True)
62+
class NamespaceStyle:
63+
"""Visual style for a namespace.
64+
65+
Attributes:
66+
namespace: The namespace identifier.
67+
emoji: Emoji representing the namespace.
68+
color: ANSI color code for the namespace.
69+
label: Human-readable label for the namespace.
70+
description: Brief description of namespace purpose.
71+
"""
72+
73+
namespace: str
74+
emoji: str
75+
color: str
76+
label: str
77+
description: str
78+
79+
def format_badge(self, text: str | None = None) -> str:
80+
"""Format a colored badge for the namespace.
81+
82+
Args:
83+
text: Optional text to include after the badge.
84+
If None, just returns the emoji and label.
85+
86+
Returns:
87+
ANSI-colored string with emoji, label, and optional text.
88+
"""
89+
badge = f"{self.color}{self.emoji} {self.label}{Color.RESET.value}"
90+
if text:
91+
return f"{badge} {text}"
92+
return badge
93+
94+
def format_marker(self, content: str) -> str:
95+
"""Format a capture marker with styling.
96+
97+
Args:
98+
content: The marker content text.
99+
100+
Returns:
101+
Styled marker string like "[🛑 blocker] content".
102+
"""
103+
marker_name = self.namespace
104+
# Use shorter names for common markers
105+
if self.namespace == "learnings":
106+
marker_name = "learned"
107+
elif self.namespace == "decisions":
108+
marker_name = "decision"
109+
elif self.namespace == "blockers":
110+
marker_name = "blocker"
111+
112+
return (
113+
f"{self.color}[{self.emoji} {marker_name}]{Color.RESET.value} "
114+
f"{content}"
115+
)
116+
117+
def format_inline(self) -> str:
118+
"""Format just the namespace with emoji and color.
119+
120+
Returns:
121+
Colored namespace string like "🛑 blockers".
122+
"""
123+
return f"{self.color}{self.emoji} {self.namespace}{Color.RESET.value}"
124+
125+
126+
# =============================================================================
127+
# Namespace Style Definitions
128+
# =============================================================================
129+
130+
STYLES: dict[str, NamespaceStyle] = {
131+
# 🛑 BLOCKERS - Red for danger/stop/urgent
132+
"blockers": NamespaceStyle(
133+
namespace="blockers",
134+
emoji="🛑",
135+
color=Color.BRIGHT_RED.value,
136+
label="BLOCKER",
137+
description="Obstacles and impediments",
138+
),
139+
# ⚖️ DECISIONS - Blue for trust/authority/stability
140+
"decisions": NamespaceStyle(
141+
namespace="decisions",
142+
emoji="⚖️",
143+
color=Color.BRIGHT_BLUE.value,
144+
label="DECISION",
145+
description="Architecture decisions and choices",
146+
),
147+
# 💡 LEARNINGS - Green for growth/knowledge/insight
148+
"learnings": NamespaceStyle(
149+
namespace="learnings",
150+
emoji="💡",
151+
color=Color.BRIGHT_GREEN.value,
152+
label="LEARNED",
153+
description="Technical insights and discoveries",
154+
),
155+
# 🚀 PROGRESS - Cyan for forward movement/achievement
156+
"progress": NamespaceStyle(
157+
namespace="progress",
158+
emoji="🚀",
159+
color=Color.BRIGHT_CYAN.value,
160+
label="PROGRESS",
161+
description="Task completions and milestones",
162+
),
163+
# 🧩 PATTERNS - Magenta for creativity/abstraction/wisdom
164+
"patterns": NamespaceStyle(
165+
namespace="patterns",
166+
emoji="🧩",
167+
color=Color.BRIGHT_MAGENTA.value,
168+
label="PATTERN",
169+
description="Cross-project generalizations",
170+
),
171+
# 🔍 RESEARCH - Yellow for curiosity/discovery/illumination
172+
"research": NamespaceStyle(
173+
namespace="research",
174+
emoji="🔍",
175+
color=Color.BRIGHT_YELLOW.value,
176+
label="RESEARCH",
177+
description="External findings and evaluations",
178+
),
179+
# 👁️ REVIEWS - Orange for evaluation/scrutiny/feedback
180+
"reviews": NamespaceStyle(
181+
namespace="reviews",
182+
emoji="👁️",
183+
color=Color.ORANGE.value,
184+
label="REVIEW",
185+
description="Code review findings",
186+
),
187+
# 🔄 RETROSPECTIVE - Magenta for reflection/introspection
188+
"retrospective": NamespaceStyle(
189+
namespace="retrospective",
190+
emoji="🔄",
191+
color=Color.MAGENTA.value,
192+
label="RETRO",
193+
description="Post-mortem reflections",
194+
),
195+
# 🌱 INCEPTION - Light green for beginnings/new growth
196+
"inception": NamespaceStyle(
197+
namespace="inception",
198+
emoji="🌱",
199+
color=Color.GREEN.value,
200+
label="INCEPTION",
201+
description="Problem statements and scope",
202+
),
203+
# 💬 ELICITATION - Light cyan for communication/dialogue
204+
"elicitation": NamespaceStyle(
205+
namespace="elicitation",
206+
emoji="💬",
207+
color=Color.CYAN.value,
208+
label="ELICIT",
209+
description="Requirements clarifications",
210+
),
211+
}
212+
213+
# Default style for unknown namespaces
214+
DEFAULT_STYLE = NamespaceStyle(
215+
namespace="memory",
216+
emoji="📝",
217+
color=Color.RESET.value,
218+
label="MEMORY",
219+
description="General memory",
220+
)
221+
222+
223+
# =============================================================================
224+
# Public API
225+
# =============================================================================
226+
227+
228+
def get_style(namespace: str) -> NamespaceStyle:
229+
"""Get the style for a namespace.
230+
231+
Args:
232+
namespace: The namespace identifier.
233+
234+
Returns:
235+
NamespaceStyle for the namespace, or DEFAULT_STYLE if unknown.
236+
"""
237+
return STYLES.get(namespace, DEFAULT_STYLE)
238+
239+
240+
def format_namespace(namespace: str, with_emoji: bool = True) -> str:
241+
"""Format a namespace with color and optional emoji.
242+
243+
Args:
244+
namespace: The namespace identifier.
245+
with_emoji: Whether to include the emoji (default True).
246+
247+
Returns:
248+
Colored namespace string.
249+
"""
250+
style = get_style(namespace)
251+
if with_emoji:
252+
return style.format_inline()
253+
return f"{style.color}{namespace}{Color.RESET.value}"
254+
255+
256+
def format_marker(namespace: str, content: str) -> str:
257+
"""Format a capture marker with full styling.
258+
259+
Args:
260+
namespace: The namespace identifier.
261+
content: The marker content text.
262+
263+
Returns:
264+
Fully styled marker string.
265+
266+
Example:
267+
>>> format_marker("blockers", "Database connection timeout")
268+
'[🛑 blocker] Database connection timeout' # with red coloring
269+
"""
270+
style = get_style(namespace)
271+
return style.format_marker(content)
272+
273+
274+
def format_memory_header(
275+
namespace: str,
276+
memory_id: str,
277+
timestamp: str | None = None,
278+
) -> str:
279+
"""Format a memory header for display.
280+
281+
Args:
282+
namespace: The namespace identifier.
283+
memory_id: The memory ID.
284+
timestamp: Optional ISO timestamp.
285+
286+
Returns:
287+
Formatted header string with color and emoji.
288+
"""
289+
style = get_style(namespace)
290+
header = f"{style.color}{style.emoji} [{style.label}]{Color.RESET.value}"
291+
292+
# Add dimmed ID
293+
dim = "\033[2m"
294+
header += f" {dim}{memory_id}{Color.RESET.value}"
295+
296+
if timestamp:
297+
header += f" {dim}({timestamp}){Color.RESET.value}"
298+
299+
return header
300+
301+
302+
# =============================================================================
303+
# Marker Reference for Guidance Templates
304+
# =============================================================================
305+
306+
307+
def get_marker_reference_styled() -> str:
308+
"""Get a styled marker reference for guidance templates.
309+
310+
Returns:
311+
Multi-line string showing all markers with colors and emojis.
312+
"""
313+
lines = ["**Capture Markers:**", ""]
314+
315+
# Primary markers (most commonly used)
316+
primary = ["decisions", "learnings", "blockers", "progress"]
317+
lines.append("*Primary:*")
318+
for ns in primary:
319+
style = STYLES[ns]
320+
marker_name = ns
321+
if ns == "learnings":
322+
marker_name = "learned"
323+
elif ns == "decisions":
324+
marker_name = "decision"
325+
elif ns == "blockers":
326+
marker_name = "blocker"
327+
lines.append(f" `[{style.emoji} {marker_name}]` - {style.description}")
328+
329+
lines.append("")
330+
lines.append("*Additional:*")
331+
332+
# Additional markers
333+
additional = ["research", "patterns", "reviews", "retrospective"]
334+
for ns in additional:
335+
style = STYLES[ns]
336+
lines.append(f" `[{style.emoji} {ns}]` - {style.description}")
337+
338+
return "\n".join(lines)

0 commit comments

Comments
 (0)