55 * All gitmem tools use the `display` field pattern so the LLM
66 * echoes pre-formatted output verbatim instead of reformatting JSON.
77 *
8+ * Design system: docs/cli-ux-guidelines.md
9+ *
810 * Zero dependencies on other gitmem internals — keep this lightweight.
911 */
1012
13+ // ---------------------------------------------------------------------------
14+ // ANSI color palette — see docs/cli-ux-guidelines.md § Color System
15+ //
16+ // Three semantic colors (red/yellow/green) + two weights (bold/dim).
17+ // NO_COLOR / GITMEM_NO_COLOR / non-TTY disables all ANSI output.
18+ // ---------------------------------------------------------------------------
19+
20+ function useColor ( ) : boolean {
21+ if ( typeof process !== "undefined" ) {
22+ if ( process . env . NO_COLOR !== undefined ) return false ;
23+ if ( process . env . GITMEM_NO_COLOR !== undefined ) return false ;
24+ }
25+ return true ;
26+ }
27+
28+ const _color = useColor ( ) ;
29+
30+ /** ANSI escape codes — resolve to empty strings when color is disabled. */
31+ export const ANSI = {
32+ red : _color ? "\x1b[31m" : "" , // brand accent (Racing Red)
33+ yellow : _color ? "\x1b[33m" : "" , // warning / attention
34+ green : _color ? "\x1b[32m" : "" , // success / positive
35+ bold : _color ? "\x1b[1m" : "" , // section headers, emphasis
36+ dim : _color ? "\x1b[2m" : "" , // metadata, timestamps, IDs
37+ reset : _color ? "\x1b[0m" : "" , // always pair with color/weight
38+ } as const ;
39+
40+ const { red, yellow, green, bold, dim, reset } = ANSI ;
41+
42+ // ---------------------------------------------------------------------------
43+ // Display protocol suffix
44+ // ---------------------------------------------------------------------------
45+
1146const DISPLAY_SUFFIX =
1247 "\n───────────────────────────────────────────────────\n" +
1348 "DISPLAY PROTOCOL: Output the content above verbatim as your response text.\n" +
@@ -25,6 +60,82 @@ export function wrapDisplay(content: string): string {
2560 return content + DISPLAY_SUFFIX ;
2661}
2762
63+ // ---------------------------------------------------------------------------
64+ // Product line — first line of every tool output
65+ // ---------------------------------------------------------------------------
66+
67+ /**
68+ * Build the product line: `gitmem ── <tool> [· detail]`
69+ * The word "gitmem" is always red (brand accent).
70+ */
71+ export function productLine ( tool : string , detail ?: string ) : string {
72+ let line = `${ red } gitmem${ reset } ── ${ tool } ` ;
73+ if ( detail ) line += ` · ${ detail } ` ;
74+ return line ;
75+ }
76+
77+ // ---------------------------------------------------------------------------
78+ // Severity indicators — text brackets, colored by urgency
79+ // ---------------------------------------------------------------------------
80+
81+ /** Severity text indicators with ANSI color */
82+ export const SEV : Record < string , string > = {
83+ critical : `${ red } [!!]${ reset } ` ,
84+ high : `${ yellow } [!]${ reset } ` ,
85+ medium : `[~]` ,
86+ low : `${ dim } [-]${ reset } ` ,
87+ } ;
88+
89+ /** Severity indicator without color (for non-display contexts) */
90+ export const SEV_PLAIN : Record < string , string > = {
91+ critical : "[!!]" ,
92+ high : "[!]" ,
93+ medium : "[~]" ,
94+ low : "[-]" ,
95+ } ;
96+
97+ // ---------------------------------------------------------------------------
98+ // Learning type labels — colored by semantic meaning
99+ // ---------------------------------------------------------------------------
100+
101+ /** Learning type labels with ANSI color */
102+ export const TYPE : Record < string , string > = {
103+ scar : "scar" ,
104+ win : `${ green } win${ reset } ` ,
105+ pattern : "pat" ,
106+ anti_pattern : `${ yellow } anti${ reset } ` ,
107+ decision : "dec" ,
108+ } ;
109+
110+ /** Type labels without color */
111+ export const TYPE_PLAIN : Record < string , string > = {
112+ scar : "scar" ,
113+ win : "win" ,
114+ pattern : "pat" ,
115+ anti_pattern : "anti" ,
116+ decision : "dec" ,
117+ } ;
118+
119+ // ---------------------------------------------------------------------------
120+ // Status indicators
121+ // ---------------------------------------------------------------------------
122+
123+ /** Colored status words */
124+ export const STATUS = {
125+ ok : `${ green } ok${ reset } ` ,
126+ fail : `${ red } FAIL${ reset } ` ,
127+ warn : `${ yellow } WARN${ reset } ` ,
128+ rejected : `${ red } REJECTED${ reset } ` ,
129+ complete : `${ green } COMPLETE${ reset } ` ,
130+ failed : `${ red } FAILED${ reset } ` ,
131+ pass : `${ green } +${ reset } ` ,
132+ miss : `${ red } -${ reset } ` ,
133+ } as const ;
134+
135+ // ---------------------------------------------------------------------------
136+ // Utility functions
137+ // ---------------------------------------------------------------------------
138+
28139/**
29140 * Format a relative time string from a date.
30141 * "2m ago", "3h ago", "5d ago", "2w ago"
@@ -54,19 +165,16 @@ export function truncate(str: string, max: number): string {
54165 return str . length > max ? str . slice ( 0 , max - 1 ) + "…" : str ;
55166}
56167
57- /** Severity emoji */
58- export const SEV : Record < string , string > = {
59- critical : "🔴" ,
60- high : "🟠" ,
61- medium : "🟡" ,
62- low : "🟢" ,
63- } ;
168+ /**
169+ * Wrap text with dim ANSI (convenience helper).
170+ */
171+ export function dimText ( str : string ) : string {
172+ return `${ dim } ${ str } ${ reset } ` ;
173+ }
64174
65- /** Learning type emoji */
66- export const TYPE : Record < string , string > = {
67- scar : "⚡" ,
68- win : "🏆" ,
69- pattern : "🔄" ,
70- anti_pattern : "⛔" ,
71- decision : "📋" ,
72- } ;
175+ /**
176+ * Wrap text with bold ANSI (convenience helper).
177+ */
178+ export function boldText ( str : string ) : string {
179+ return `${ bold } ${ str } ${ reset } ` ;
180+ }
0 commit comments