gitmem-mcp 1.3.0 → 1.3.2

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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * contribute_feedback Tool
3
+ *
4
+ * Submit feedback about gitmem — feature requests, bug reports,
5
+ * friction points, or suggestions. Always saved locally to .gitmem/feedback/.
6
+ * If opted in via config, sent anonymously to improve gitmem.
7
+ *
8
+ * Rate limited to 10 submissions per session.
9
+ */
10
+ import { createRequire } from "module";
11
+ const require = createRequire(import.meta.url);
12
+ const pkg = require("../../package.json");
13
+ import { v4 as uuidv4 } from "uuid";
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import { getCurrentSession, getFeedbackCount, incrementFeedbackCount } from "../services/session-state.js";
17
+ import { getGitmemDir, sanitizePathComponent } from "../services/gitmem-dir.js";
18
+ import { isFeedbackEnabled, getInstallId } from "../services/gitmem-dir.js";
19
+ import { getAgentIdentity } from "../services/agent-detection.js";
20
+ import { sanitizeFeedbackText } from "../services/feedback-sanitizer.js";
21
+ import { getEffectTracker } from "../services/effect-tracker.js";
22
+ import { submitFeedbackRemote } from "../services/feedback-remote.js";
23
+ import { wrapDisplay } from "../services/display-protocol.js";
24
+ import { Timer } from "../services/metrics.js";
25
+ const MAX_FEEDBACK_PER_SESSION = 10;
26
+ export async function contributeFeedback(params) {
27
+ const timer = new Timer();
28
+ // 1. Check active session
29
+ const session = getCurrentSession();
30
+ if (!session) {
31
+ const msg = "No active session. Call session_start first.";
32
+ return {
33
+ success: false,
34
+ remote_submitted: false,
35
+ display: wrapDisplay(msg),
36
+ error: msg,
37
+ performance_ms: timer.stop(),
38
+ };
39
+ }
40
+ // 2. Rate limit check
41
+ const count = getFeedbackCount();
42
+ if (count >= MAX_FEEDBACK_PER_SESSION) {
43
+ const msg = `Feedback limit reached (${MAX_FEEDBACK_PER_SESSION}/session). Try again next session.`;
44
+ return {
45
+ success: false,
46
+ remote_submitted: false,
47
+ display: wrapDisplay(msg),
48
+ error: msg,
49
+ performance_ms: timer.stop(),
50
+ };
51
+ }
52
+ incrementFeedbackCount();
53
+ // 3. Sanitize text fields
54
+ const sanitizedDescription = sanitizeFeedbackText(params.description);
55
+ const sanitizedFix = params.suggested_fix ? sanitizeFeedbackText(params.suggested_fix) : undefined;
56
+ const sanitizedContext = params.context ? sanitizeFeedbackText(params.context) : undefined;
57
+ // 4. Build feedback record
58
+ const id = uuidv4();
59
+ const shortId = id.slice(0, 8);
60
+ const now = new Date();
61
+ const dateStr = now.toISOString().slice(0, 10); // YYYY-MM-DD
62
+ const record = {
63
+ id,
64
+ type: params.type,
65
+ tool: params.tool,
66
+ description: sanitizedDescription,
67
+ severity: params.severity,
68
+ suggested_fix: sanitizedFix,
69
+ context: sanitizedContext,
70
+ timestamp: now.toISOString(),
71
+ gitmem_version: pkg.version,
72
+ agent_identity: getAgentIdentity(),
73
+ session_id: session.sessionId,
74
+ };
75
+ // 5. Local write: .gitmem/feedback/{YYYY-MM-DD}-{type}-{short-id}.json
76
+ const feedbackDir = path.join(getGitmemDir(), "feedback");
77
+ if (!fs.existsSync(feedbackDir)) {
78
+ fs.mkdirSync(feedbackDir, { recursive: true });
79
+ }
80
+ const filename = `${dateStr}-${params.type}-${shortId}.json`;
81
+ sanitizePathComponent(filename, "feedback filename");
82
+ const filePath = path.join(feedbackDir, filename);
83
+ fs.writeFileSync(filePath, JSON.stringify(record, null, 2));
84
+ // 6. Remote write (if feedback_enabled in config)
85
+ let remoteSubmitted = false;
86
+ if (isFeedbackEnabled()) {
87
+ const installId = getInstallId();
88
+ const remotePayload = {
89
+ ...record,
90
+ install_id: installId,
91
+ };
92
+ // Fire-and-forget via effect tracker
93
+ const tracker = getEffectTracker();
94
+ tracker.track("feedback", "remote-submit", async () => {
95
+ await submitFeedbackRemote({
96
+ feedback_id: id,
97
+ type: params.type,
98
+ tool: params.tool,
99
+ description: sanitizedDescription,
100
+ severity: params.severity,
101
+ suggested_fix: sanitizedFix,
102
+ context: sanitizedContext,
103
+ gitmem_version: pkg.version,
104
+ agent_identity: getAgentIdentity(),
105
+ install_id: installId,
106
+ client_timestamp: now.toISOString(),
107
+ });
108
+ return remotePayload;
109
+ });
110
+ remoteSubmitted = true;
111
+ }
112
+ const latencyMs = timer.stop();
113
+ const remaining = MAX_FEEDBACK_PER_SESSION - getFeedbackCount();
114
+ const remoteNote = remoteSubmitted ? " (queued for remote)" : "";
115
+ const display = `Feedback recorded: ${id} (${remaining} remaining this session)\nType: ${params.type} | Tool: ${params.tool} | Severity: ${params.severity}\nSaved to .gitmem/feedback/${filename}${remoteNote}\n(${latencyMs}ms)`;
116
+ return {
117
+ success: true,
118
+ id,
119
+ path: filePath,
120
+ remote_submitted: remoteSubmitted,
121
+ display: wrapDisplay(display),
122
+ performance_ms: latencyMs,
123
+ };
124
+ }
125
+ //# sourceMappingURL=contribute-feedback.js.map