@stackmemoryai/stackmemory 1.2.1 → 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.
Files changed (39) hide show
  1. package/dist/src/cli/commands/skills.js +123 -1
  2. package/dist/src/hooks/graphiti-hooks.js +149 -0
  3. package/dist/src/hooks/session-summary.js +30 -0
  4. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
  5. package/dist/src/integrations/greptile/client.js +101 -0
  6. package/dist/src/integrations/greptile/config.js +14 -0
  7. package/dist/src/integrations/greptile/index.js +11 -0
  8. package/dist/src/integrations/greptile/types.js +4 -0
  9. package/dist/src/integrations/linear/webhook.js +16 -0
  10. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
  11. package/dist/src/integrations/mcp/server.js +27 -0
  12. package/dist/src/skills/claude-skills.js +46 -1
  13. package/dist/src/skills/parallel-agent-skill.js +514 -0
  14. package/package.json +1 -1
  15. package/scripts/gepa/.before-optimize.md +140 -159
  16. package/scripts/gepa/config.json +7 -1
  17. package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
  18. package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
  19. package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
  20. package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
  21. package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
  22. package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
  23. package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
  24. package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
  25. package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
  26. package/scripts/gepa/generations/gen-000/baseline.md +172 -159
  27. package/scripts/gepa/generations/gen-001/baseline.md +172 -159
  28. package/scripts/gepa/generations/gen-001/variant-a.md +156 -146
  29. package/scripts/gepa/generations/gen-001/variant-b.md +199 -170
  30. package/scripts/gepa/generations/gen-001/variant-c.md +127 -46
  31. package/scripts/gepa/generations/gen-001/variant-d.md +160 -107
  32. package/scripts/gepa/hooks/reflect.js +44 -5
  33. package/scripts/gepa/optimize.js +281 -39
  34. package/scripts/gepa/results/eval-1-baseline.json +187 -10
  35. package/scripts/gepa/results/eval-1-variant-a.json +188 -11
  36. package/scripts/gepa/results/eval-1-variant-b.json +188 -11
  37. package/scripts/gepa/results/eval-1-variant-c.json +168 -11
  38. package/scripts/gepa/results/eval-1-variant-d.json +169 -12
  39. package/scripts/gepa/state.json +18 -18
@@ -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,7 @@ 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";
33
34
  import { GraphitiClient } from "../graphiti/client.js";
34
35
  import { fuzzyEdit } from "../../utils/fuzzy-edit.js";
35
36
  import { v4 as uuidv4 } from "uuid";
@@ -64,6 +65,7 @@ class LocalStackMemoryMCP {
64
65
  contextRetrieval;
65
66
  discoveryHandlers;
66
67
  diffMemHandlers;
68
+ greptileHandlers;
67
69
  providerHandlers = null;
68
70
  graphitiClient = null;
69
71
  pendingPlans = /* @__PURE__ */ new Map();
@@ -138,6 +140,7 @@ class LocalStackMemoryMCP {
138
140
  projectRoot: this.projectRoot
139
141
  });
140
142
  this.diffMemHandlers = new DiffMemHandlers();
143
+ this.greptileHandlers = new GreptileHandlers();
141
144
  this.initProviderHandlers();
142
145
  if (process.env.GRAPHITI_ENDPOINT) {
143
146
  this.graphitiClient = new GraphitiClient({
@@ -1129,6 +1132,8 @@ ${summary}...`, 0.8);
1129
1132
  }
1130
1133
  }
1131
1134
  ] : [],
1135
+ // Greptile tools (only active when GREPTILE_API_KEY is set)
1136
+ ...process.env.GREPTILE_API_KEY ? this.greptileHandlers.getToolDefinitions() : [],
1132
1137
  // Provider tools (only active when STACKMEMORY_MULTI_PROVIDER=true)
1133
1138
  ...isFeatureEnabled("multiProvider") ? [
1134
1139
  {
@@ -1377,6 +1382,28 @@ ${summary}...`, 0.8);
1377
1382
  case "diffmem_status":
1378
1383
  result = await this.diffMemHandlers.handleStatus();
1379
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;
1380
1407
  case "sm_edit":
1381
1408
  result = await this.handleSmEdit(args);
1382
1409
  break;
@@ -948,6 +948,25 @@ class ClaudeSkillsManager {
948
948
  };
949
949
  }
950
950
  }
951
+ case "agent": {
952
+ const { ParallelAgentSkill } = await import("./parallel-agent-skill.js");
953
+ const agentSkill = new ParallelAgentSkill(this.context);
954
+ const sub = args[0];
955
+ const agentOpts = { timeout: options?.timeout };
956
+ switch (sub) {
957
+ case "research":
958
+ return agentSkill.research(args.slice(1).join(" "), agentOpts);
959
+ case "maintain":
960
+ return agentSkill.maintain(args.slice(1).join(" "), agentOpts);
961
+ case "spec-run":
962
+ return agentSkill.specRun(args[1], agentOpts);
963
+ default:
964
+ return {
965
+ success: false,
966
+ message: `Unknown: agent ${sub}. Use: research|maintain|spec-run`
967
+ };
968
+ }
969
+ }
951
970
  case "linear-run": {
952
971
  if (!this.linearTaskRunner) {
953
972
  return {
@@ -983,7 +1002,15 @@ class ClaudeSkillsManager {
983
1002
  }
984
1003
  }
985
1004
  getAvailableSkills() {
986
- const skills = ["handoff", "checkpoint", "dig", "dashboard", "api", "spec"];
1005
+ const skills = [
1006
+ "handoff",
1007
+ "checkpoint",
1008
+ "dig",
1009
+ "dashboard",
1010
+ "api",
1011
+ "spec",
1012
+ "agent"
1013
+ ];
987
1014
  if (this.repoIngestionSkill) {
988
1015
  skills.push("repo");
989
1016
  }
@@ -1145,6 +1172,24 @@ Examples:
1145
1172
  /spec dev-spec
1146
1173
  /spec update prompt-plan "Initialize repository and tooling"
1147
1174
  /spec validate prompt-plan
1175
+ `;
1176
+ case "agent":
1177
+ return `
1178
+ /agent research "How does the FTS5 search work?"
1179
+ /agent maintain "Fix the deprecation warning in webhook.ts"
1180
+ /agent spec-run docs/specs/my-feature.md
1181
+
1182
+ Parallel agent skill (Willison patterns) \u2014 spawn isolated Claude agents:
1183
+ research \u2014 Explore codebase, save findings as a frame (read-only)
1184
+ maintain \u2014 Low-stakes fix, produces a .patch file
1185
+ spec-run \u2014 Implement a spec on a branch, validate with lint+test+build
1186
+
1187
+ Options:
1188
+ --timeout <ms> Agent timeout in milliseconds (default: 300000)
1189
+
1190
+ Each agent runs in a disposable /tmp workspace (git clone --depth=1).
1191
+ Patches: git apply .stackmemory/patches/<file>.patch
1192
+ Spec branches: cd /tmp/sm-spec-* && git log --oneline
1148
1193
  `;
1149
1194
  case "linear-run":
1150
1195
  return `