@stackmemoryai/stackmemory 1.2.0 → 1.2.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.
- package/dist/src/cli/claude-sm.js +65 -0
- package/dist/src/cli/commands/skills.js +123 -1
- package/dist/src/hooks/graphiti-hooks.js +149 -0
- package/dist/src/hooks/session-summary.js +30 -0
- package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
- package/dist/src/integrations/greptile/client.js +101 -0
- package/dist/src/integrations/greptile/config.js +14 -0
- package/dist/src/integrations/greptile/index.js +11 -0
- package/dist/src/integrations/greptile/types.js +4 -0
- package/dist/src/integrations/linear/webhook.js +16 -0
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
- package/dist/src/integrations/mcp/server.js +136 -0
- package/dist/src/integrations/mcp/tool-definitions.js +53 -1
- package/dist/src/skills/claude-skills.js +46 -1
- package/dist/src/skills/parallel-agent-skill.js +514 -0
- package/dist/src/utils/hook-installer.js +155 -0
- package/package.json +2 -2
- package/scripts/gepa/.before-optimize.md +140 -0
- package/scripts/gepa/config.json +7 -1
- package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
- package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
- package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
- package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
- package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
- package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
- package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
- package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
- package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
- package/scripts/gepa/generations/gen-000/baseline.md +48 -0
- package/scripts/gepa/generations/gen-001/baseline.md +172 -0
- package/scripts/gepa/generations/gen-001/variant-a.md +176 -0
- package/scripts/gepa/generations/gen-001/variant-b.md +266 -0
- package/scripts/gepa/generations/gen-001/variant-c.md +142 -0
- package/scripts/gepa/generations/gen-001/variant-d.md +172 -0
- package/scripts/gepa/hooks/reflect.js +44 -5
- package/scripts/gepa/optimize.js +281 -39
- package/scripts/gepa/results/eval-1-baseline.json +218 -0
- package/scripts/gepa/results/eval-1-variant-a.json +218 -0
- package/scripts/gepa/results/eval-1-variant-b.json +218 -0
- package/scripts/gepa/results/eval-1-variant-c.json +198 -0
- package/scripts/gepa/results/eval-1-variant-d.json +198 -0
- package/scripts/gepa/state.json +44 -5
- package/scripts/install-claude-hooks-auto.js +176 -44
- package/templates/claude-hooks/auto-checkpoint.js +174 -0
- package/templates/claude-hooks/chime-on-stop.sh +22 -0
- package/templates/claude-hooks/session-rescue.sh +15 -0
- package/templates/claude-hooks/stop-checkpoint.js +120 -0
|
@@ -22,6 +22,7 @@ function getOptionalEnv(key) {
|
|
|
22
22
|
class LinearWebhookHandler {
|
|
23
23
|
syncEngine;
|
|
24
24
|
taskStore;
|
|
25
|
+
graphitiBridge;
|
|
25
26
|
webhookSecret;
|
|
26
27
|
constructor(webhookSecret) {
|
|
27
28
|
this.webhookSecret = webhookSecret || process.env["LINEAR_WEBHOOK_SECRET"];
|
|
@@ -38,6 +39,12 @@ class LinearWebhookHandler {
|
|
|
38
39
|
setTaskStore(taskStore) {
|
|
39
40
|
this.taskStore = taskStore;
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Set the Graphiti bridge for knowledge graph sync
|
|
44
|
+
*/
|
|
45
|
+
setGraphitiBridge(bridge) {
|
|
46
|
+
this.graphitiBridge = bridge;
|
|
47
|
+
}
|
|
41
48
|
/**
|
|
42
49
|
* Verify webhook signature
|
|
43
50
|
*/
|
|
@@ -104,6 +111,15 @@ class LinearWebhookHandler {
|
|
|
104
111
|
default:
|
|
105
112
|
logger.warn(`Unknown webhook action: ${payload.action}`);
|
|
106
113
|
}
|
|
114
|
+
if (this.graphitiBridge) {
|
|
115
|
+
this.graphitiBridge.processWebhook(payload).catch((err) => {
|
|
116
|
+
logger.debug("Linear-Graphiti bridge error", {
|
|
117
|
+
action: payload.action,
|
|
118
|
+
identifier: payload.data.identifier,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err)
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
107
123
|
}
|
|
108
124
|
/**
|
|
109
125
|
* Handle issue created in Linear
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { DEFAULT_GREPTILE_CONFIG } from "../../greptile/config.js";
|
|
6
|
+
import { GreptileClient, GreptileClientError } from "../../greptile/client.js";
|
|
7
|
+
import { logger } from "../../../core/monitoring/logger.js";
|
|
8
|
+
class GreptileHandlers {
|
|
9
|
+
config;
|
|
10
|
+
client = null;
|
|
11
|
+
constructor(deps) {
|
|
12
|
+
this.config = { ...DEFAULT_GREPTILE_CONFIG, ...deps?.config };
|
|
13
|
+
if (this.config.enabled && this.config.apiKey) {
|
|
14
|
+
try {
|
|
15
|
+
this.client = new GreptileClient(this.config);
|
|
16
|
+
} catch {
|
|
17
|
+
this.client = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
getToolDefinitions() {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
name: "greptile_pr_comments",
|
|
25
|
+
description: "Get PR review comments from Greptile. Returns unaddressed comments with suggestedCode for auto-fix workflows.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
name: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: 'Repository full name (e.g., "owner/repo")'
|
|
32
|
+
},
|
|
33
|
+
remote: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: ["github", "gitlab", "azure", "bitbucket"],
|
|
36
|
+
description: "Remote provider"
|
|
37
|
+
},
|
|
38
|
+
defaultBranch: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: 'Default branch (e.g., "main")'
|
|
41
|
+
},
|
|
42
|
+
prNumber: {
|
|
43
|
+
type: "number",
|
|
44
|
+
description: "Pull request number"
|
|
45
|
+
},
|
|
46
|
+
greptileGenerated: {
|
|
47
|
+
type: "boolean",
|
|
48
|
+
description: "Filter for only Greptile review comments"
|
|
49
|
+
},
|
|
50
|
+
addressed: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "Filter by comment addressed status"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
required: ["name", "remote", "defaultBranch", "prNumber"]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "greptile_pr_details",
|
|
60
|
+
description: "Get detailed PR information including metadata, statistics, and review analysis from Greptile.",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
name: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: 'Repository full name (e.g., "owner/repo")'
|
|
67
|
+
},
|
|
68
|
+
remote: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: ["github", "gitlab", "azure", "bitbucket"],
|
|
71
|
+
description: "Remote provider"
|
|
72
|
+
},
|
|
73
|
+
defaultBranch: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "Default branch"
|
|
76
|
+
},
|
|
77
|
+
prNumber: {
|
|
78
|
+
type: "number",
|
|
79
|
+
description: "Pull request number"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
required: ["name", "remote", "defaultBranch", "prNumber"]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "greptile_list_prs",
|
|
87
|
+
description: "List pull requests. Filter by repository, branch, author, or state.",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: "object",
|
|
90
|
+
properties: {
|
|
91
|
+
name: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: 'Repository full name (e.g., "owner/repo")'
|
|
94
|
+
},
|
|
95
|
+
remote: {
|
|
96
|
+
type: "string",
|
|
97
|
+
enum: ["github", "gitlab", "azure", "bitbucket"],
|
|
98
|
+
description: "Remote provider"
|
|
99
|
+
},
|
|
100
|
+
defaultBranch: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Default branch"
|
|
103
|
+
},
|
|
104
|
+
sourceBranch: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Filter by source branch name"
|
|
107
|
+
},
|
|
108
|
+
authorLogin: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Filter by PR author username"
|
|
111
|
+
},
|
|
112
|
+
state: {
|
|
113
|
+
type: "string",
|
|
114
|
+
enum: ["open", "closed", "merged"],
|
|
115
|
+
description: "Filter by PR state"
|
|
116
|
+
},
|
|
117
|
+
limit: {
|
|
118
|
+
type: "number",
|
|
119
|
+
description: "Max results (default 20)"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "greptile_trigger_review",
|
|
126
|
+
description: "Trigger a Greptile code review for a pull request.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
name: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: 'Repository full name (e.g., "owner/repo")'
|
|
133
|
+
},
|
|
134
|
+
remote: {
|
|
135
|
+
type: "string",
|
|
136
|
+
enum: ["github", "gitlab", "azure", "bitbucket"],
|
|
137
|
+
description: "Remote provider"
|
|
138
|
+
},
|
|
139
|
+
defaultBranch: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Default branch"
|
|
142
|
+
},
|
|
143
|
+
prNumber: {
|
|
144
|
+
type: "number",
|
|
145
|
+
description: "Pull request number"
|
|
146
|
+
},
|
|
147
|
+
branch: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "Current working branch"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
required: ["name", "remote", "prNumber"]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "greptile_search_patterns",
|
|
157
|
+
description: "Search custom coding patterns and instructions in Greptile.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
query: {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "Search query for pattern content"
|
|
164
|
+
},
|
|
165
|
+
limit: {
|
|
166
|
+
type: "number",
|
|
167
|
+
description: "Max results (default 10)"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
required: ["query"]
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "greptile_create_pattern",
|
|
175
|
+
description: "Create a new custom coding pattern or instruction in Greptile.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
body: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description: "Pattern content"
|
|
182
|
+
},
|
|
183
|
+
type: {
|
|
184
|
+
type: "string",
|
|
185
|
+
enum: ["CUSTOM_INSTRUCTION", "PATTERN"],
|
|
186
|
+
description: "Context type (default: CUSTOM_INSTRUCTION)"
|
|
187
|
+
},
|
|
188
|
+
scopes: {
|
|
189
|
+
type: "object",
|
|
190
|
+
description: "Boolean expression defining where this pattern applies"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
required: ["body"]
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "greptile_status",
|
|
198
|
+
description: "Check Greptile integration connection status.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
async handleListPRComments(args) {
|
|
207
|
+
if (!this.client) return this.disabledResponse();
|
|
208
|
+
try {
|
|
209
|
+
const toolArgs = {
|
|
210
|
+
name: args.name,
|
|
211
|
+
remote: args.remote,
|
|
212
|
+
defaultBranch: args.defaultBranch,
|
|
213
|
+
prNumber: args.prNumber
|
|
214
|
+
};
|
|
215
|
+
if (args.greptileGenerated !== void 0)
|
|
216
|
+
toolArgs.greptileGenerated = args.greptileGenerated;
|
|
217
|
+
if (args.addressed !== void 0) toolArgs.addressed = args.addressed;
|
|
218
|
+
const result = await this.client.callTool(
|
|
219
|
+
"list_merge_request_comments",
|
|
220
|
+
toolArgs
|
|
221
|
+
);
|
|
222
|
+
let actionableCount = 0;
|
|
223
|
+
if (Array.isArray(result)) {
|
|
224
|
+
actionableCount = result.filter(
|
|
225
|
+
(c) => !c.addressed && (c.suggestedCode || typeof c.body === "string" && c.body.includes("```suggestion"))
|
|
226
|
+
).length;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: "text",
|
|
232
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
metadata: { actionableCount, tool: "list_merge_request_comments" }
|
|
236
|
+
};
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return this.handleError("listPRComments", error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async handleGetMergeRequest(args) {
|
|
242
|
+
if (!this.client) return this.disabledResponse();
|
|
243
|
+
try {
|
|
244
|
+
const result = await this.client.callTool("get_merge_request", {
|
|
245
|
+
name: args.name,
|
|
246
|
+
remote: args.remote,
|
|
247
|
+
defaultBranch: args.defaultBranch,
|
|
248
|
+
prNumber: args.prNumber
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
content: [
|
|
252
|
+
{
|
|
253
|
+
type: "text",
|
|
254
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
metadata: { tool: "get_merge_request" }
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return this.handleError("getMergeRequest", error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async handleListPullRequests(args) {
|
|
264
|
+
if (!this.client) return this.disabledResponse();
|
|
265
|
+
try {
|
|
266
|
+
const toolArgs = {};
|
|
267
|
+
if (args.name) toolArgs.name = args.name;
|
|
268
|
+
if (args.remote) toolArgs.remote = args.remote;
|
|
269
|
+
if (args.defaultBranch) toolArgs.defaultBranch = args.defaultBranch;
|
|
270
|
+
if (args.sourceBranch) toolArgs.sourceBranch = args.sourceBranch;
|
|
271
|
+
if (args.authorLogin) toolArgs.authorLogin = args.authorLogin;
|
|
272
|
+
if (args.state) toolArgs.state = args.state;
|
|
273
|
+
if (args.limit) toolArgs.limit = args.limit;
|
|
274
|
+
const result = await this.client.callTool("list_pull_requests", toolArgs);
|
|
275
|
+
return {
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: "text",
|
|
279
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
280
|
+
}
|
|
281
|
+
],
|
|
282
|
+
metadata: { tool: "list_pull_requests" }
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return this.handleError("listPullRequests", error);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async handleTriggerCodeReview(args) {
|
|
289
|
+
if (!this.client) return this.disabledResponse();
|
|
290
|
+
try {
|
|
291
|
+
const toolArgs = {
|
|
292
|
+
name: args.name,
|
|
293
|
+
remote: args.remote,
|
|
294
|
+
prNumber: args.prNumber
|
|
295
|
+
};
|
|
296
|
+
if (args.defaultBranch) toolArgs.defaultBranch = args.defaultBranch;
|
|
297
|
+
if (args.branch) toolArgs.branch = args.branch;
|
|
298
|
+
const result = await this.client.callTool(
|
|
299
|
+
"trigger_code_review",
|
|
300
|
+
toolArgs
|
|
301
|
+
);
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: "text",
|
|
306
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
metadata: { tool: "trigger_code_review" }
|
|
310
|
+
};
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return this.handleError("triggerCodeReview", error);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async handleSearchPatterns(args) {
|
|
316
|
+
if (!this.client) return this.disabledResponse();
|
|
317
|
+
try {
|
|
318
|
+
const toolArgs = { query: args.query };
|
|
319
|
+
if (args.limit) toolArgs.limit = args.limit;
|
|
320
|
+
const result = await this.client.callTool(
|
|
321
|
+
"search_custom_context",
|
|
322
|
+
toolArgs
|
|
323
|
+
);
|
|
324
|
+
return {
|
|
325
|
+
content: [
|
|
326
|
+
{
|
|
327
|
+
type: "text",
|
|
328
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
metadata: { tool: "search_custom_context" }
|
|
332
|
+
};
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return this.handleError("searchPatterns", error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async handleCreatePattern(args) {
|
|
338
|
+
if (!this.client) return this.disabledResponse();
|
|
339
|
+
try {
|
|
340
|
+
const toolArgs = { body: args.body };
|
|
341
|
+
if (args.type) toolArgs.type = args.type;
|
|
342
|
+
if (args.scopes) toolArgs.scopes = args.scopes;
|
|
343
|
+
const result = await this.client.callTool(
|
|
344
|
+
"create_custom_context",
|
|
345
|
+
toolArgs
|
|
346
|
+
);
|
|
347
|
+
return {
|
|
348
|
+
content: [
|
|
349
|
+
{
|
|
350
|
+
type: "text",
|
|
351
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
352
|
+
}
|
|
353
|
+
],
|
|
354
|
+
metadata: { tool: "create_custom_context" }
|
|
355
|
+
};
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return this.handleError("createPattern", error);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async handleStatus() {
|
|
361
|
+
if (!this.client) {
|
|
362
|
+
return {
|
|
363
|
+
content: [
|
|
364
|
+
{
|
|
365
|
+
type: "text",
|
|
366
|
+
text: JSON.stringify(
|
|
367
|
+
{
|
|
368
|
+
connected: false,
|
|
369
|
+
message: "Greptile integration disabled (GREPTILE_API_KEY not set)"
|
|
370
|
+
},
|
|
371
|
+
null,
|
|
372
|
+
2
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
],
|
|
376
|
+
metadata: { connected: false }
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
await this.client.callTool("list_pull_requests", { limit: 1 });
|
|
381
|
+
return {
|
|
382
|
+
content: [
|
|
383
|
+
{
|
|
384
|
+
type: "text",
|
|
385
|
+
text: JSON.stringify(
|
|
386
|
+
{
|
|
387
|
+
connected: true,
|
|
388
|
+
endpoint: this.config.mcpEndpoint
|
|
389
|
+
},
|
|
390
|
+
null,
|
|
391
|
+
2
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
metadata: { connected: true }
|
|
396
|
+
};
|
|
397
|
+
} catch (error) {
|
|
398
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
399
|
+
return {
|
|
400
|
+
content: [
|
|
401
|
+
{
|
|
402
|
+
type: "text",
|
|
403
|
+
text: JSON.stringify(
|
|
404
|
+
{
|
|
405
|
+
connected: false,
|
|
406
|
+
error: msg,
|
|
407
|
+
endpoint: this.config.mcpEndpoint
|
|
408
|
+
},
|
|
409
|
+
null,
|
|
410
|
+
2
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
],
|
|
414
|
+
metadata: { connected: false, error: msg }
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
disabledResponse() {
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: "Greptile integration disabled (GREPTILE_API_KEY not set)"
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
handleError(operation, error) {
|
|
429
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
430
|
+
const isConnectionError = msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("network") || error instanceof GreptileClientError;
|
|
431
|
+
logger.debug(`Greptile ${operation} failed`, { error: msg });
|
|
432
|
+
if (isConnectionError) {
|
|
433
|
+
return {
|
|
434
|
+
content: [
|
|
435
|
+
{
|
|
436
|
+
type: "text",
|
|
437
|
+
text: `Greptile unavailable (${operation}). The service may be temporarily unreachable.`
|
|
438
|
+
}
|
|
439
|
+
],
|
|
440
|
+
metadata: { error: true, unavailable: true, operation }
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: "text",
|
|
447
|
+
text: `Greptile ${operation} error: ${msg}`
|
|
448
|
+
}
|
|
449
|
+
],
|
|
450
|
+
metadata: { error: true, operation, message: msg }
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
export {
|
|
455
|
+
GreptileHandlers
|
|
456
|
+
};
|
|
@@ -30,6 +30,8 @@ import { TraceDetector } from "../../core/trace/trace-detector.js";
|
|
|
30
30
|
import { LLMContextRetrieval } from "../../core/retrieval/index.js";
|
|
31
31
|
import { DiscoveryHandlers } from "./handlers/discovery-handlers.js";
|
|
32
32
|
import { DiffMemHandlers } from "./handlers/diffmem-handlers.js";
|
|
33
|
+
import { GreptileHandlers } from "./handlers/greptile-handlers.js";
|
|
34
|
+
import { GraphitiClient } from "../graphiti/client.js";
|
|
33
35
|
import { fuzzyEdit } from "../../utils/fuzzy-edit.js";
|
|
34
36
|
import { v4 as uuidv4 } from "uuid";
|
|
35
37
|
import {
|
|
@@ -63,7 +65,9 @@ class LocalStackMemoryMCP {
|
|
|
63
65
|
contextRetrieval;
|
|
64
66
|
discoveryHandlers;
|
|
65
67
|
diffMemHandlers;
|
|
68
|
+
greptileHandlers;
|
|
66
69
|
providerHandlers = null;
|
|
70
|
+
graphitiClient = null;
|
|
67
71
|
pendingPlans = /* @__PURE__ */ new Map();
|
|
68
72
|
constructor() {
|
|
69
73
|
this.projectRoot = this.findProjectRoot();
|
|
@@ -136,7 +140,17 @@ class LocalStackMemoryMCP {
|
|
|
136
140
|
projectRoot: this.projectRoot
|
|
137
141
|
});
|
|
138
142
|
this.diffMemHandlers = new DiffMemHandlers();
|
|
143
|
+
this.greptileHandlers = new GreptileHandlers();
|
|
139
144
|
this.initProviderHandlers();
|
|
145
|
+
if (process.env.GRAPHITI_ENDPOINT) {
|
|
146
|
+
this.graphitiClient = new GraphitiClient({
|
|
147
|
+
endpoint: process.env.GRAPHITI_ENDPOINT,
|
|
148
|
+
projectNamespace: process.env.STACKMEMORY_PROJECT_ID || this.projectId
|
|
149
|
+
});
|
|
150
|
+
logger.info("Graphiti client initialized", {
|
|
151
|
+
endpoint: process.env.GRAPHITI_ENDPOINT
|
|
152
|
+
});
|
|
153
|
+
}
|
|
140
154
|
this.setupHandlers();
|
|
141
155
|
this.loadInitialContext();
|
|
142
156
|
this.loadPendingPlans();
|
|
@@ -1073,6 +1087,53 @@ ${summary}...`, 0.8);
|
|
|
1073
1087
|
properties: {}
|
|
1074
1088
|
}
|
|
1075
1089
|
},
|
|
1090
|
+
// Graphiti tools (only active when GRAPHITI_ENDPOINT is set)
|
|
1091
|
+
...this.graphitiClient ? [
|
|
1092
|
+
{
|
|
1093
|
+
name: "graphiti_status",
|
|
1094
|
+
description: "Check Graphiti temporal knowledge graph connection status",
|
|
1095
|
+
inputSchema: {
|
|
1096
|
+
type: "object",
|
|
1097
|
+
properties: {}
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
name: "graphiti_query",
|
|
1102
|
+
description: "Query the Graphiti temporal knowledge graph for entities, relations, and episodes",
|
|
1103
|
+
inputSchema: {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
properties: {
|
|
1106
|
+
query: {
|
|
1107
|
+
type: "string",
|
|
1108
|
+
description: "Semantic text query"
|
|
1109
|
+
},
|
|
1110
|
+
entityTypes: {
|
|
1111
|
+
type: "array",
|
|
1112
|
+
items: { type: "string" },
|
|
1113
|
+
description: 'Entity types to filter (e.g., ["Person", "File", "Issue"])'
|
|
1114
|
+
},
|
|
1115
|
+
validFrom: {
|
|
1116
|
+
type: "number",
|
|
1117
|
+
description: "Start of time window (epoch ms)"
|
|
1118
|
+
},
|
|
1119
|
+
validTo: {
|
|
1120
|
+
type: "number",
|
|
1121
|
+
description: "End of time window (epoch ms)"
|
|
1122
|
+
},
|
|
1123
|
+
maxHops: {
|
|
1124
|
+
type: "number",
|
|
1125
|
+
description: "Graph traversal depth (default 2)"
|
|
1126
|
+
},
|
|
1127
|
+
k: {
|
|
1128
|
+
type: "number",
|
|
1129
|
+
description: "Top-k results (default 20)"
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
] : [],
|
|
1135
|
+
// Greptile tools (only active when GREPTILE_API_KEY is set)
|
|
1136
|
+
...process.env.GREPTILE_API_KEY ? this.greptileHandlers.getToolDefinitions() : [],
|
|
1076
1137
|
// Provider tools (only active when STACKMEMORY_MULTI_PROVIDER=true)
|
|
1077
1138
|
...isFeatureEnabled("multiProvider") ? [
|
|
1078
1139
|
{
|
|
@@ -1321,6 +1382,28 @@ ${summary}...`, 0.8);
|
|
|
1321
1382
|
case "diffmem_status":
|
|
1322
1383
|
result = await this.diffMemHandlers.handleStatus();
|
|
1323
1384
|
break;
|
|
1385
|
+
// Greptile handlers
|
|
1386
|
+
case "greptile_pr_comments":
|
|
1387
|
+
result = await this.greptileHandlers.handleListPRComments(args);
|
|
1388
|
+
break;
|
|
1389
|
+
case "greptile_pr_details":
|
|
1390
|
+
result = await this.greptileHandlers.handleGetMergeRequest(args);
|
|
1391
|
+
break;
|
|
1392
|
+
case "greptile_list_prs":
|
|
1393
|
+
result = await this.greptileHandlers.handleListPullRequests(args);
|
|
1394
|
+
break;
|
|
1395
|
+
case "greptile_trigger_review":
|
|
1396
|
+
result = await this.greptileHandlers.handleTriggerCodeReview(args);
|
|
1397
|
+
break;
|
|
1398
|
+
case "greptile_search_patterns":
|
|
1399
|
+
result = await this.greptileHandlers.handleSearchPatterns(args);
|
|
1400
|
+
break;
|
|
1401
|
+
case "greptile_create_pattern":
|
|
1402
|
+
result = await this.greptileHandlers.handleCreatePattern(args);
|
|
1403
|
+
break;
|
|
1404
|
+
case "greptile_status":
|
|
1405
|
+
result = await this.greptileHandlers.handleStatus();
|
|
1406
|
+
break;
|
|
1324
1407
|
case "sm_edit":
|
|
1325
1408
|
result = await this.handleSmEdit(args);
|
|
1326
1409
|
break;
|
|
@@ -1373,6 +1456,59 @@ ${summary}...`, 0.8);
|
|
|
1373
1456
|
);
|
|
1374
1457
|
}
|
|
1375
1458
|
break;
|
|
1459
|
+
// Graphiti tools
|
|
1460
|
+
case "graphiti_status":
|
|
1461
|
+
if (!this.graphitiClient) {
|
|
1462
|
+
result = {
|
|
1463
|
+
content: [
|
|
1464
|
+
{
|
|
1465
|
+
type: "text",
|
|
1466
|
+
text: JSON.stringify({
|
|
1467
|
+
connected: false,
|
|
1468
|
+
message: "Graphiti integration disabled (GRAPHITI_ENDPOINT not set)"
|
|
1469
|
+
})
|
|
1470
|
+
}
|
|
1471
|
+
]
|
|
1472
|
+
};
|
|
1473
|
+
} else {
|
|
1474
|
+
const status = await this.graphitiClient.getStatus();
|
|
1475
|
+
result = {
|
|
1476
|
+
content: [
|
|
1477
|
+
{ type: "text", text: JSON.stringify(status, null, 2) }
|
|
1478
|
+
]
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
break;
|
|
1482
|
+
case "graphiti_query":
|
|
1483
|
+
if (!this.graphitiClient) {
|
|
1484
|
+
result = {
|
|
1485
|
+
content: [
|
|
1486
|
+
{
|
|
1487
|
+
type: "text",
|
|
1488
|
+
text: "Graphiti integration disabled (GRAPHITI_ENDPOINT not set)"
|
|
1489
|
+
}
|
|
1490
|
+
]
|
|
1491
|
+
};
|
|
1492
|
+
} else {
|
|
1493
|
+
const gCtx = await this.graphitiClient.queryTemporal({
|
|
1494
|
+
query: args.query,
|
|
1495
|
+
entityTypes: args.entityTypes,
|
|
1496
|
+
validFrom: args.validFrom,
|
|
1497
|
+
validTo: args.validTo,
|
|
1498
|
+
maxHops: args.maxHops,
|
|
1499
|
+
k: args.k
|
|
1500
|
+
});
|
|
1501
|
+
const text = gCtx.chunks.map((c) => c.text).join("\n\n");
|
|
1502
|
+
result = {
|
|
1503
|
+
content: [
|
|
1504
|
+
{
|
|
1505
|
+
type: "text",
|
|
1506
|
+
text: text || `No results found (${gCtx.totalTokens} tokens searched)`
|
|
1507
|
+
}
|
|
1508
|
+
]
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
break;
|
|
1376
1512
|
default:
|
|
1377
1513
|
throw new Error(`Unknown tool: ${name}`);
|
|
1378
1514
|
}
|