gitmem-mcp 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md.template +29 -20
- package/README.md +36 -20
- package/bin/gitmem.js +33 -0
- package/bin/init-wizard.js +147 -21
- package/copilot-instructions.template +101 -0
- package/cursorrules.template +29 -20
- package/dist/commands/telemetry.d.ts +11 -0
- package/dist/commands/telemetry.js +207 -0
- package/dist/hooks/format-utils.d.ts +1 -0
- package/dist/hooks/format-utils.js +7 -6
- package/dist/lib/telemetry.d.ts +101 -0
- package/dist/lib/telemetry.js +272 -0
- package/dist/schemas/session-close.d.ts +40 -40
- package/dist/server.js +15 -4
- package/dist/services/analytics.js +1 -1
- package/dist/services/display-protocol.d.ts +45 -4
- package/dist/services/display-protocol.js +114 -15
- package/dist/services/enforcement.d.ts +24 -0
- package/dist/services/enforcement.js +126 -0
- package/dist/services/nudge-variants.d.ts +29 -0
- package/dist/services/nudge-variants.js +71 -0
- package/dist/tools/cleanup-threads.js +9 -5
- package/dist/tools/confirm-scars.js +28 -11
- package/dist/tools/definitions.js +7 -7
- package/dist/tools/list-threads.d.ts +1 -1
- package/dist/tools/list-threads.js +8 -17
- package/dist/tools/log.js +3 -3
- package/dist/tools/prepare-context.js +7 -10
- package/dist/tools/recall.js +7 -17
- package/dist/tools/record-scar-usage-batch.js +7 -2
- package/dist/tools/record-scar-usage.js +7 -2
- package/dist/tools/search.js +3 -3
- package/dist/tools/session-close.js +105 -81
- package/dist/tools/session-start.js +73 -14
- package/dist/types/index.d.ts +2 -0
- package/package.json +13 -3
- package/windsurfrules.template +101 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privacy-respecting telemetry for GitMem
|
|
3
|
+
*
|
|
4
|
+
* - Opt-in only (disabled by default)
|
|
5
|
+
* - No PII (queries, scars, project names, IPs)
|
|
6
|
+
* - Transparent (local logs visible before sending)
|
|
7
|
+
* - Anonymous (random session IDs, not persistent)
|
|
8
|
+
* - Controllable (enable/disable/show/clear)
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { randomBytes } from "crypto";
|
|
13
|
+
import { platform } from "os";
|
|
14
|
+
const CONSENT_VERSION = "2026-02";
|
|
15
|
+
const TELEMETRY_ENDPOINT = "https://telemetry.gitmem.ai/v1/events";
|
|
16
|
+
const BATCH_INTERVAL_HOURS = 24;
|
|
17
|
+
export class Telemetry {
|
|
18
|
+
configPath;
|
|
19
|
+
logPath;
|
|
20
|
+
config = null;
|
|
21
|
+
version;
|
|
22
|
+
constructor(gitmemDir, version) {
|
|
23
|
+
this.configPath = join(gitmemDir, "telemetry.json");
|
|
24
|
+
this.logPath = join(gitmemDir, "telemetry.log");
|
|
25
|
+
this.version = version;
|
|
26
|
+
this.loadConfig();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Load telemetry config from disk
|
|
30
|
+
*/
|
|
31
|
+
loadConfig() {
|
|
32
|
+
if (!existsSync(this.configPath)) {
|
|
33
|
+
// Default: disabled
|
|
34
|
+
this.config = {
|
|
35
|
+
enabled: false,
|
|
36
|
+
session_id: this.generateSessionId(),
|
|
37
|
+
consent_version: CONSENT_VERSION,
|
|
38
|
+
};
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const raw = readFileSync(this.configPath, "utf-8");
|
|
43
|
+
const stored = JSON.parse(raw);
|
|
44
|
+
// Check consent version — re-prompt if changed
|
|
45
|
+
if (stored.consent_version !== CONSENT_VERSION) {
|
|
46
|
+
console.warn("[telemetry] Privacy policy updated — re-consent required");
|
|
47
|
+
this.config = {
|
|
48
|
+
enabled: false,
|
|
49
|
+
session_id: this.generateSessionId(),
|
|
50
|
+
consent_version: CONSENT_VERSION,
|
|
51
|
+
};
|
|
52
|
+
this.saveConfig();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.config = {
|
|
56
|
+
...stored,
|
|
57
|
+
session_id: this.generateSessionId(), // Always fresh per-session
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.warn("[telemetry] Could not parse config, resetting");
|
|
62
|
+
this.config = {
|
|
63
|
+
enabled: false,
|
|
64
|
+
session_id: this.generateSessionId(),
|
|
65
|
+
consent_version: CONSENT_VERSION,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Save config to disk
|
|
71
|
+
*/
|
|
72
|
+
saveConfig() {
|
|
73
|
+
if (!this.config)
|
|
74
|
+
return;
|
|
75
|
+
// Don't persist session_id — it's per-session only
|
|
76
|
+
const toSave = {
|
|
77
|
+
enabled: this.config.enabled,
|
|
78
|
+
consent_version: this.config.consent_version,
|
|
79
|
+
consented_at: this.config.consented_at,
|
|
80
|
+
};
|
|
81
|
+
writeFileSync(this.configPath, JSON.stringify(toSave, null, 2));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Generate random session ID (8 hex chars, not persistent)
|
|
85
|
+
*/
|
|
86
|
+
generateSessionId() {
|
|
87
|
+
return randomBytes(4).toString("hex");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if telemetry is enabled
|
|
91
|
+
*/
|
|
92
|
+
isEnabled() {
|
|
93
|
+
return this.config?.enabled ?? false;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Enable telemetry (user consent)
|
|
97
|
+
*/
|
|
98
|
+
enable() {
|
|
99
|
+
if (!this.config) {
|
|
100
|
+
this.loadConfig();
|
|
101
|
+
}
|
|
102
|
+
this.config.enabled = true;
|
|
103
|
+
this.config.consented_at = new Date().toISOString();
|
|
104
|
+
this.saveConfig();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Disable telemetry
|
|
108
|
+
*/
|
|
109
|
+
disable() {
|
|
110
|
+
if (!this.config) {
|
|
111
|
+
this.loadConfig();
|
|
112
|
+
}
|
|
113
|
+
this.config.enabled = false;
|
|
114
|
+
delete this.config.consented_at;
|
|
115
|
+
this.saveConfig();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Log an event (always writes to local log, sends if enabled)
|
|
119
|
+
*/
|
|
120
|
+
async track(eventData) {
|
|
121
|
+
if (!this.config)
|
|
122
|
+
return;
|
|
123
|
+
const tier = this.detectTier();
|
|
124
|
+
const event = {
|
|
125
|
+
...eventData,
|
|
126
|
+
version: this.version,
|
|
127
|
+
platform: platform(),
|
|
128
|
+
tier,
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
session_id: this.config.session_id,
|
|
131
|
+
};
|
|
132
|
+
// Always log locally (transparent)
|
|
133
|
+
this.logToFile(event);
|
|
134
|
+
// Send immediately if enabled (in background, don't block)
|
|
135
|
+
if (this.isEnabled()) {
|
|
136
|
+
this.sendEvent(event).catch((err) => {
|
|
137
|
+
// Silent failure — don't interrupt user workflows
|
|
138
|
+
if (process.env.DEBUG) {
|
|
139
|
+
console.warn("[telemetry] Send failed:", err.message);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Write event to local log file
|
|
146
|
+
*/
|
|
147
|
+
logToFile(event) {
|
|
148
|
+
try {
|
|
149
|
+
const line = JSON.stringify(event) + "\n";
|
|
150
|
+
appendFileSync(this.logPath, line);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
// Silent failure on write errors
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Send event to telemetry endpoint (background, non-blocking)
|
|
158
|
+
*/
|
|
159
|
+
async sendEvent(event) {
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch(TELEMETRY_ENDPOINT, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify(event),
|
|
165
|
+
signal: AbortSignal.timeout(5000), // 5s timeout
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok && process.env.DEBUG) {
|
|
168
|
+
console.warn("[telemetry] HTTP", response.status);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
// Silent failure — telemetry should never break workflows
|
|
173
|
+
if (process.env.DEBUG) {
|
|
174
|
+
console.warn("[telemetry]", err);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Detect tier (free vs pro)
|
|
180
|
+
*/
|
|
181
|
+
detectTier() {
|
|
182
|
+
return process.env.SUPABASE_URL ? "pro" : "free";
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get telemetry status for CLI display
|
|
186
|
+
*/
|
|
187
|
+
getStatus() {
|
|
188
|
+
if (!this.config) {
|
|
189
|
+
this.loadConfig();
|
|
190
|
+
}
|
|
191
|
+
let eventCount = 0;
|
|
192
|
+
if (existsSync(this.logPath)) {
|
|
193
|
+
try {
|
|
194
|
+
const log = readFileSync(this.logPath, "utf-8");
|
|
195
|
+
eventCount = log.split("\n").filter((line) => line.trim()).length;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
eventCount = 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
enabled: this.config.enabled,
|
|
203
|
+
session_id: this.config.session_id,
|
|
204
|
+
event_count: eventCount,
|
|
205
|
+
consent_version: this.config.consent_version,
|
|
206
|
+
consented_at: this.config.consented_at,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get recent events for CLI display
|
|
211
|
+
*/
|
|
212
|
+
getRecentEvents(limit = 100) {
|
|
213
|
+
if (!existsSync(this.logPath)) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const log = readFileSync(this.logPath, "utf-8");
|
|
218
|
+
const lines = log.split("\n").filter((line) => line.trim());
|
|
219
|
+
return lines.slice(-limit);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Clear local telemetry log
|
|
227
|
+
*/
|
|
228
|
+
clearLog() {
|
|
229
|
+
if (existsSync(this.logPath)) {
|
|
230
|
+
writeFileSync(this.logPath, "");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Format event for human-readable display
|
|
235
|
+
*/
|
|
236
|
+
static formatEvent(eventJson) {
|
|
237
|
+
try {
|
|
238
|
+
const e = JSON.parse(eventJson);
|
|
239
|
+
const time = new Date(e.timestamp).toLocaleString();
|
|
240
|
+
const tool = e.tool ? `: ${e.tool}` : "";
|
|
241
|
+
const status = e.success === false ? " (failed)" : "";
|
|
242
|
+
const duration = e.duration_ms ? ` ${e.duration_ms}ms` : "";
|
|
243
|
+
const results = e.result_count !== undefined ? `, ${e.result_count} results` : "";
|
|
244
|
+
return `[${time}] ${e.event}${tool}${status}${duration}${results}`;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return eventJson;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Global telemetry instance (lazy init)
|
|
253
|
+
*/
|
|
254
|
+
let telemetryInstance = null;
|
|
255
|
+
export function getTelemetry(gitmemDir, version) {
|
|
256
|
+
if (!telemetryInstance) {
|
|
257
|
+
telemetryInstance = new Telemetry(gitmemDir, version);
|
|
258
|
+
}
|
|
259
|
+
return telemetryInstance;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Track a tool call
|
|
263
|
+
*/
|
|
264
|
+
export async function trackToolCall(args) {
|
|
265
|
+
const { gitmemDir, version, ...eventData } = args;
|
|
266
|
+
const telemetry = getTelemetry(gitmemDir, version);
|
|
267
|
+
await telemetry.track({
|
|
268
|
+
event: "tool_called",
|
|
269
|
+
...eventData,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -279,16 +279,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
279
279
|
}[] | undefined;
|
|
280
280
|
transcript_path?: string | undefined;
|
|
281
281
|
linear_issue?: string | undefined;
|
|
282
|
-
open_threads?: (string | {
|
|
283
|
-
id: string;
|
|
284
|
-
created_at: string;
|
|
285
|
-
status: "open" | "resolved";
|
|
286
|
-
text: string;
|
|
287
|
-
resolved_at?: string | undefined;
|
|
288
|
-
resolution_note?: string | undefined;
|
|
289
|
-
source_session?: string | undefined;
|
|
290
|
-
resolved_by_session?: string | undefined;
|
|
291
|
-
})[] | undefined;
|
|
292
282
|
closing_reflection?: {
|
|
293
283
|
what_broke: string;
|
|
294
284
|
what_took_longer: string;
|
|
@@ -300,17 +290,7 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
300
290
|
collaborative_dynamic?: string | undefined;
|
|
301
291
|
rapport_notes?: string | undefined;
|
|
302
292
|
} | undefined;
|
|
303
|
-
project_state?: string | undefined;
|
|
304
|
-
task_completion?: {
|
|
305
|
-
questions_displayed_at: string;
|
|
306
|
-
reflection_completed_at: string;
|
|
307
|
-
human_asked_at: string;
|
|
308
|
-
human_response_at: string;
|
|
309
|
-
human_response: string;
|
|
310
|
-
} | undefined;
|
|
311
293
|
human_corrections?: string | undefined;
|
|
312
|
-
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
313
|
-
ceremony_duration_ms?: number | undefined;
|
|
314
294
|
scars_to_record?: {
|
|
315
295
|
surfaced_at: string;
|
|
316
296
|
reference_type: "explicit" | "implicit" | "acknowledged" | "refuted" | "none";
|
|
@@ -324,6 +304,26 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
324
304
|
execution_successful?: boolean | undefined;
|
|
325
305
|
variant_id?: string | undefined;
|
|
326
306
|
}[] | undefined;
|
|
307
|
+
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
308
|
+
open_threads?: (string | {
|
|
309
|
+
id: string;
|
|
310
|
+
created_at: string;
|
|
311
|
+
status: "open" | "resolved";
|
|
312
|
+
text: string;
|
|
313
|
+
resolved_at?: string | undefined;
|
|
314
|
+
resolution_note?: string | undefined;
|
|
315
|
+
source_session?: string | undefined;
|
|
316
|
+
resolved_by_session?: string | undefined;
|
|
317
|
+
})[] | undefined;
|
|
318
|
+
project_state?: string | undefined;
|
|
319
|
+
task_completion?: {
|
|
320
|
+
questions_displayed_at: string;
|
|
321
|
+
reflection_completed_at: string;
|
|
322
|
+
human_asked_at: string;
|
|
323
|
+
human_response_at: string;
|
|
324
|
+
human_response: string;
|
|
325
|
+
} | undefined;
|
|
326
|
+
ceremony_duration_ms?: number | undefined;
|
|
327
327
|
capture_transcript?: boolean | undefined;
|
|
328
328
|
}, {
|
|
329
329
|
session_id: string;
|
|
@@ -336,16 +336,6 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
336
336
|
}[] | undefined;
|
|
337
337
|
transcript_path?: string | undefined;
|
|
338
338
|
linear_issue?: string | undefined;
|
|
339
|
-
open_threads?: (string | {
|
|
340
|
-
id: string;
|
|
341
|
-
created_at: string;
|
|
342
|
-
status: "open" | "resolved";
|
|
343
|
-
text: string;
|
|
344
|
-
resolved_at?: string | undefined;
|
|
345
|
-
resolution_note?: string | undefined;
|
|
346
|
-
source_session?: string | undefined;
|
|
347
|
-
resolved_by_session?: string | undefined;
|
|
348
|
-
})[] | undefined;
|
|
349
339
|
closing_reflection?: {
|
|
350
340
|
what_broke: string;
|
|
351
341
|
what_took_longer: string;
|
|
@@ -357,17 +347,7 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
357
347
|
collaborative_dynamic?: string | undefined;
|
|
358
348
|
rapport_notes?: string | undefined;
|
|
359
349
|
} | undefined;
|
|
360
|
-
project_state?: string | undefined;
|
|
361
|
-
task_completion?: {
|
|
362
|
-
questions_displayed_at: string;
|
|
363
|
-
reflection_completed_at: string;
|
|
364
|
-
human_asked_at: string;
|
|
365
|
-
human_response_at: string;
|
|
366
|
-
human_response: string;
|
|
367
|
-
} | undefined;
|
|
368
350
|
human_corrections?: string | undefined;
|
|
369
|
-
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
370
|
-
ceremony_duration_ms?: number | undefined;
|
|
371
351
|
scars_to_record?: {
|
|
372
352
|
surfaced_at: string;
|
|
373
353
|
reference_type: "explicit" | "implicit" | "acknowledged" | "refuted" | "none";
|
|
@@ -381,6 +361,26 @@ export declare const SessionCloseParamsSchema: z.ZodObject<{
|
|
|
381
361
|
execution_successful?: boolean | undefined;
|
|
382
362
|
variant_id?: string | undefined;
|
|
383
363
|
}[] | undefined;
|
|
364
|
+
learnings_created?: (string | Record<string, unknown>)[] | undefined;
|
|
365
|
+
open_threads?: (string | {
|
|
366
|
+
id: string;
|
|
367
|
+
created_at: string;
|
|
368
|
+
status: "open" | "resolved";
|
|
369
|
+
text: string;
|
|
370
|
+
resolved_at?: string | undefined;
|
|
371
|
+
resolution_note?: string | undefined;
|
|
372
|
+
source_session?: string | undefined;
|
|
373
|
+
resolved_by_session?: string | undefined;
|
|
374
|
+
})[] | undefined;
|
|
375
|
+
project_state?: string | undefined;
|
|
376
|
+
task_completion?: {
|
|
377
|
+
questions_displayed_at: string;
|
|
378
|
+
reflection_completed_at: string;
|
|
379
|
+
human_asked_at: string;
|
|
380
|
+
human_response_at: string;
|
|
381
|
+
human_response: string;
|
|
382
|
+
} | undefined;
|
|
383
|
+
ceremony_duration_ms?: number | undefined;
|
|
384
384
|
capture_transcript?: boolean | undefined;
|
|
385
385
|
}>;
|
|
386
386
|
export type SessionCloseParams = z.infer<typeof SessionCloseParamsSchema>;
|
package/dist/server.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Registers all tools and handles MCP protocol communication.
|
|
5
5
|
* Tool definitions are in ./tools/definitions.ts
|
|
6
6
|
*/
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const pkg = require("../package.json");
|
|
7
10
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -33,7 +36,9 @@ import { cleanupThreads } from "./tools/cleanup-threads.js";
|
|
|
33
36
|
import { archiveLearning } from "./tools/archive-learning.js";
|
|
34
37
|
import { getCacheStatus, checkCacheHealth, flushCache, startBackgroundInit, } from "./services/startup.js";
|
|
35
38
|
import { getEffectTracker } from "./services/effect-tracker.js";
|
|
39
|
+
import { RIPPLE, ANSI } from "./services/display-protocol.js";
|
|
36
40
|
import { getProject } from "./services/session-state.js";
|
|
41
|
+
import { checkEnforcement } from "./services/enforcement.js";
|
|
37
42
|
import { getTier, hasSupabase, hasCacheManagement, hasBatchOperations, hasTranscripts, } from "./services/tier.js";
|
|
38
43
|
import { getRegisteredTools } from "./tools/definitions.js";
|
|
39
44
|
import { validateToolArgs } from "./schemas/registry.js";
|
|
@@ -43,7 +48,7 @@ import { validateToolArgs } from "./schemas/registry.js";
|
|
|
43
48
|
export function createServer() {
|
|
44
49
|
const server = new Server({
|
|
45
50
|
name: "gitmem-mcp",
|
|
46
|
-
version:
|
|
51
|
+
version: pkg.version,
|
|
47
52
|
}, {
|
|
48
53
|
capabilities: {
|
|
49
54
|
tools: {},
|
|
@@ -84,6 +89,8 @@ export function createServer() {
|
|
|
84
89
|
isError: true,
|
|
85
90
|
};
|
|
86
91
|
}
|
|
92
|
+
// Server-side enforcement: advisory warnings for protocol violations
|
|
93
|
+
const enforcement = checkEnforcement(name);
|
|
87
94
|
try {
|
|
88
95
|
let result;
|
|
89
96
|
switch (name) {
|
|
@@ -240,8 +247,8 @@ export function createServer() {
|
|
|
240
247
|
// Build command table for display
|
|
241
248
|
const cmdLines = visibleCommands.map(c => ` ${c.alias.padEnd(22)} ${c.description}`).join("\n");
|
|
242
249
|
const display = [
|
|
243
|
-
|
|
244
|
-
"Memory that compounds.",
|
|
250
|
+
`${RIPPLE} ${ANSI.red}gitmem${ANSI.reset} v${pkg.version} · ${tier} · ${registeredTools.length} tools · ${hasSupabase() ? "supabase" : "local (.gitmem/)"}`,
|
|
251
|
+
" Memory that compounds.",
|
|
245
252
|
"",
|
|
246
253
|
cmdLines,
|
|
247
254
|
"",
|
|
@@ -252,7 +259,7 @@ export function createServer() {
|
|
|
252
259
|
"Tool results are collapsed in the CLI — the user cannot see them unless you echo them.",
|
|
253
260
|
].join("\n");
|
|
254
261
|
result = {
|
|
255
|
-
version:
|
|
262
|
+
version: pkg.version,
|
|
256
263
|
tier,
|
|
257
264
|
tools_registered: registeredTools.length,
|
|
258
265
|
storage: hasSupabase() ? "supabase" : "local (.gitmem/)",
|
|
@@ -313,6 +320,10 @@ export function createServer() {
|
|
|
313
320
|
else {
|
|
314
321
|
responseText = JSON.stringify(result, null, 2);
|
|
315
322
|
}
|
|
323
|
+
// Prepend enforcement warning if present (advisory, non-blocking)
|
|
324
|
+
if (enforcement.warning) {
|
|
325
|
+
responseText = enforcement.warning + "\n\n" + responseText;
|
|
326
|
+
}
|
|
316
327
|
return {
|
|
317
328
|
content: [
|
|
318
329
|
{
|
|
@@ -365,7 +365,7 @@ export function formatSummary(data) {
|
|
|
365
365
|
const lines = [
|
|
366
366
|
`## ${period.days}-Day Summary (${period.start} to ${period.end})`,
|
|
367
367
|
``,
|
|
368
|
-
`**Sessions:** ${total_sessions} | **Decisions:** ${total_decisions} | **
|
|
368
|
+
`**Sessions:** ${total_sessions} | **Decisions:** ${total_decisions} | **Threads Referenced:** ${total_open_threads}`,
|
|
369
369
|
`**With Reflections:** ${sessions_with_reflections} | **With Issues:** ${sessions_with_issues}`,
|
|
370
370
|
``,
|
|
371
371
|
`### Agents`,
|
|
@@ -5,8 +5,19 @@
|
|
|
5
5
|
* All gitmem tools use the `display` field pattern so the LLM
|
|
6
6
|
* echoes pre-formatted output verbatim instead of reformatting JSON.
|
|
7
7
|
*
|
|
8
|
+
* Design system: docs/cli-ux-guidelines.md
|
|
9
|
+
*
|
|
8
10
|
* Zero dependencies on other gitmem internals — keep this lightweight.
|
|
9
11
|
*/
|
|
12
|
+
/** ANSI escape codes — resolve to empty strings when color is disabled. */
|
|
13
|
+
export declare const ANSI: {
|
|
14
|
+
readonly red: "" | "\u001B[31m";
|
|
15
|
+
readonly yellow: "" | "\u001B[33m";
|
|
16
|
+
readonly green: "" | "\u001B[32m";
|
|
17
|
+
readonly bold: "" | "\u001B[1m";
|
|
18
|
+
readonly dim: "" | "\u001B[2m";
|
|
19
|
+
readonly reset: "" | "\u001B[0m";
|
|
20
|
+
};
|
|
10
21
|
/**
|
|
11
22
|
* Wrap formatted content with the display protocol suffix.
|
|
12
23
|
*
|
|
@@ -15,6 +26,32 @@
|
|
|
15
26
|
* a fallback for environments without the hook (e.g. Brain Cloud, DAC).
|
|
16
27
|
*/
|
|
17
28
|
export declare function wrapDisplay(content: string): string;
|
|
29
|
+
/** Ripple mark: dim outer ring, red inner ring, bold center dot. */
|
|
30
|
+
export declare const RIPPLE: string;
|
|
31
|
+
/**
|
|
32
|
+
* Build the product line: `((●)) gitmem ── <tool> [· detail]`
|
|
33
|
+
* The ripple mark + red "gitmem" form the brand identity.
|
|
34
|
+
*/
|
|
35
|
+
export declare function productLine(tool: string, detail?: string): string;
|
|
36
|
+
/** Severity text indicators with ANSI color */
|
|
37
|
+
export declare const SEV: Record<string, string>;
|
|
38
|
+
/** Severity indicator without color (for non-display contexts) */
|
|
39
|
+
export declare const SEV_PLAIN: Record<string, string>;
|
|
40
|
+
/** Learning type labels with ANSI color */
|
|
41
|
+
export declare const TYPE: Record<string, string>;
|
|
42
|
+
/** Type labels without color */
|
|
43
|
+
export declare const TYPE_PLAIN: Record<string, string>;
|
|
44
|
+
/** Colored status words */
|
|
45
|
+
export declare const STATUS: {
|
|
46
|
+
readonly ok: "ok" | "ok\u001B[0m" | "\u001B[32mok" | "\u001B[32mok\u001B[0m";
|
|
47
|
+
readonly fail: "FAIL" | "FAIL\u001B[0m" | "\u001B[31mFAIL" | "\u001B[31mFAIL\u001B[0m";
|
|
48
|
+
readonly warn: "WARN" | "WARN\u001B[0m" | "\u001B[33mWARN" | "\u001B[33mWARN\u001B[0m";
|
|
49
|
+
readonly rejected: "REJECTED" | "REJECTED\u001B[0m" | "\u001B[31mREJECTED" | "\u001B[31mREJECTED\u001B[0m";
|
|
50
|
+
readonly complete: "COMPLETE" | "COMPLETE\u001B[0m" | "\u001B[32mCOMPLETE" | "\u001B[32mCOMPLETE\u001B[0m";
|
|
51
|
+
readonly failed: "FAILED" | "FAILED\u001B[0m" | "\u001B[31mFAILED" | "\u001B[31mFAILED\u001B[0m";
|
|
52
|
+
readonly pass: "+" | "+\u001B[0m" | "\u001B[32m+" | "\u001B[32m+\u001B[0m";
|
|
53
|
+
readonly miss: "-" | "-\u001B[0m" | "\u001B[31m-" | "\u001B[31m-\u001B[0m";
|
|
54
|
+
};
|
|
18
55
|
/**
|
|
19
56
|
* Format a relative time string from a date.
|
|
20
57
|
* "2m ago", "3h ago", "5d ago", "2w ago"
|
|
@@ -24,8 +61,12 @@ export declare function relativeTime(date: string | Date): string;
|
|
|
24
61
|
* Truncate a string with ellipsis.
|
|
25
62
|
*/
|
|
26
63
|
export declare function truncate(str: string, max: number): string;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export declare
|
|
64
|
+
/**
|
|
65
|
+
* Wrap text with dim ANSI (convenience helper).
|
|
66
|
+
*/
|
|
67
|
+
export declare function dimText(str: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Wrap text with bold ANSI (convenience helper).
|
|
70
|
+
*/
|
|
71
|
+
export declare function boldText(str: string): string;
|
|
31
72
|
//# sourceMappingURL=display-protocol.d.ts.map
|
|
@@ -5,8 +5,39 @@
|
|
|
5
5
|
* All gitmem tools use the `display` field pattern so the LLM
|
|
6
6
|
* echoes pre-formatted output verbatim instead of reformatting JSON.
|
|
7
7
|
*
|
|
8
|
+
* Design system: docs/cli-ux-guidelines.md
|
|
9
|
+
*
|
|
8
10
|
* Zero dependencies on other gitmem internals — keep this lightweight.
|
|
9
11
|
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// ANSI color palette — see docs/cli-ux-guidelines.md § Color System
|
|
14
|
+
//
|
|
15
|
+
// Three semantic colors (red/yellow/green) + two weights (bold/dim).
|
|
16
|
+
// NO_COLOR / GITMEM_NO_COLOR / non-TTY disables all ANSI output.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
function useColor() {
|
|
19
|
+
if (typeof process !== "undefined") {
|
|
20
|
+
if (process.env.NO_COLOR !== undefined)
|
|
21
|
+
return false;
|
|
22
|
+
if (process.env.GITMEM_NO_COLOR !== undefined)
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const _color = useColor();
|
|
28
|
+
/** ANSI escape codes — resolve to empty strings when color is disabled. */
|
|
29
|
+
export const ANSI = {
|
|
30
|
+
red: _color ? "\x1b[31m" : "", // brand accent (Racing Red)
|
|
31
|
+
yellow: _color ? "\x1b[33m" : "", // warning / attention
|
|
32
|
+
green: _color ? "\x1b[32m" : "", // success / positive
|
|
33
|
+
bold: _color ? "\x1b[1m" : "", // section headers, emphasis
|
|
34
|
+
dim: _color ? "\x1b[2m" : "", // metadata, timestamps, IDs
|
|
35
|
+
reset: _color ? "\x1b[0m" : "", // always pair with color/weight
|
|
36
|
+
};
|
|
37
|
+
const { red, yellow, green, bold, dim, reset } = ANSI;
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Display protocol suffix
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
10
41
|
const DISPLAY_SUFFIX = "\n───────────────────────────────────────────────────\n" +
|
|
11
42
|
"DISPLAY PROTOCOL: Output the content above verbatim as your response text.\n" +
|
|
12
43
|
"Tool results are collapsed in the CLI — the user cannot see them unless you echo them.\n" +
|
|
@@ -21,6 +52,77 @@ const DISPLAY_SUFFIX = "\n──────────────────
|
|
|
21
52
|
export function wrapDisplay(content) {
|
|
22
53
|
return content + DISPLAY_SUFFIX;
|
|
23
54
|
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Brand mark — ripple icon preceding product name
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/** Ripple mark: dim outer ring, red inner ring, bold center dot. */
|
|
59
|
+
export const RIPPLE = `${dim}(${reset}${red}(${reset}${bold}●${reset}${red})${reset}${dim})${reset}`;
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Product line — first line of every tool output
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* Build the product line: `((●)) gitmem ── <tool> [· detail]`
|
|
65
|
+
* The ripple mark + red "gitmem" form the brand identity.
|
|
66
|
+
*/
|
|
67
|
+
export function productLine(tool, detail) {
|
|
68
|
+
let line = `${RIPPLE} ${red}gitmem${reset} ── ${tool}`;
|
|
69
|
+
if (detail)
|
|
70
|
+
line += ` · ${detail}`;
|
|
71
|
+
return line;
|
|
72
|
+
}
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Severity indicators — text brackets, colored by urgency
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
/** Severity text indicators with ANSI color */
|
|
77
|
+
export const SEV = {
|
|
78
|
+
critical: `${red}[!!]${reset}`,
|
|
79
|
+
high: `${yellow}[!]${reset}`,
|
|
80
|
+
medium: `[~]`,
|
|
81
|
+
low: `${dim}[-]${reset}`,
|
|
82
|
+
};
|
|
83
|
+
/** Severity indicator without color (for non-display contexts) */
|
|
84
|
+
export const SEV_PLAIN = {
|
|
85
|
+
critical: "[!!]",
|
|
86
|
+
high: "[!]",
|
|
87
|
+
medium: "[~]",
|
|
88
|
+
low: "[-]",
|
|
89
|
+
};
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Learning type labels — colored by semantic meaning
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
/** Learning type labels with ANSI color */
|
|
94
|
+
export const TYPE = {
|
|
95
|
+
scar: "scar",
|
|
96
|
+
win: `${green}win${reset}`,
|
|
97
|
+
pattern: "pat",
|
|
98
|
+
anti_pattern: `${yellow}anti${reset}`,
|
|
99
|
+
decision: "dec",
|
|
100
|
+
};
|
|
101
|
+
/** Type labels without color */
|
|
102
|
+
export const TYPE_PLAIN = {
|
|
103
|
+
scar: "scar",
|
|
104
|
+
win: "win",
|
|
105
|
+
pattern: "pat",
|
|
106
|
+
anti_pattern: "anti",
|
|
107
|
+
decision: "dec",
|
|
108
|
+
};
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Status indicators
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/** Colored status words */
|
|
113
|
+
export const STATUS = {
|
|
114
|
+
ok: `${green}ok${reset}`,
|
|
115
|
+
fail: `${red}FAIL${reset}`,
|
|
116
|
+
warn: `${yellow}WARN${reset}`,
|
|
117
|
+
rejected: `${red}REJECTED${reset}`,
|
|
118
|
+
complete: `${green}COMPLETE${reset}`,
|
|
119
|
+
failed: `${red}FAILED${reset}`,
|
|
120
|
+
pass: `${green}+${reset}`,
|
|
121
|
+
miss: `${red}-${reset}`,
|
|
122
|
+
};
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Utility functions
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
24
126
|
/**
|
|
25
127
|
* Format a relative time string from a date.
|
|
26
128
|
* "2m ago", "3h ago", "5d ago", "2w ago"
|
|
@@ -55,19 +157,16 @@ export function truncate(str, max) {
|
|
|
55
157
|
return "";
|
|
56
158
|
return str.length > max ? str.slice(0, max - 1) + "…" : str;
|
|
57
159
|
}
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
anti_pattern: "⛔",
|
|
71
|
-
decision: "📋",
|
|
72
|
-
};
|
|
160
|
+
/**
|
|
161
|
+
* Wrap text with dim ANSI (convenience helper).
|
|
162
|
+
*/
|
|
163
|
+
export function dimText(str) {
|
|
164
|
+
return `${dim}${str}${reset}`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Wrap text with bold ANSI (convenience helper).
|
|
168
|
+
*/
|
|
169
|
+
export function boldText(str) {
|
|
170
|
+
return `${bold}${str}${reset}`;
|
|
171
|
+
}
|
|
73
172
|
//# sourceMappingURL=display-protocol.js.map
|