@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
@@ -697,6 +697,104 @@ Searched ${result.data.timeRange.from} to ${result.data.timeRange.to}`
697
697
  process.exit(1);
698
698
  }
699
699
  });
700
+ const agentCmd = skillsCmd.command("agent").description("Spawn parallel Claude agents in isolated workspaces");
701
+ agentCmd.command("research <question>").description("Explore codebase and save findings as a frame").option("--timeout <ms>", "Agent timeout in milliseconds", "300000").action(async (question, options) => {
702
+ const spinner = ora("Spawning research agent...").start();
703
+ try {
704
+ const { context } = await initializeSkillContext();
705
+ const { ParallelAgentSkill } = await import("../../skills/parallel-agent-skill.js");
706
+ const agentSkill = new ParallelAgentSkill(context);
707
+ const result = await agentSkill.research(question, {
708
+ timeout: parseInt(options.timeout)
709
+ });
710
+ spinner.stop();
711
+ if (result.success) {
712
+ console.log(chalk.green("\u2713"), result.message);
713
+ if (result.data?.findings) {
714
+ console.log(chalk.cyan("\nFindings:\n"));
715
+ console.log(result.data.findings);
716
+ }
717
+ } else {
718
+ console.log(chalk.red("\u2717"), result.message);
719
+ }
720
+ await context.database.disconnect();
721
+ } catch (error) {
722
+ spinner.stop();
723
+ console.error(chalk.red("Error:"), error.message);
724
+ process.exit(1);
725
+ }
726
+ });
727
+ agentCmd.command("maintain <task>").description("Low-stakes fix, produces a .patch file").option("--timeout <ms>", "Agent timeout in milliseconds", "300000").action(async (task, options) => {
728
+ const spinner = ora("Spawning maintenance agent...").start();
729
+ try {
730
+ const { context } = await initializeSkillContext();
731
+ const { ParallelAgentSkill } = await import("../../skills/parallel-agent-skill.js");
732
+ const agentSkill = new ParallelAgentSkill(context);
733
+ const result = await agentSkill.maintain(task, {
734
+ timeout: parseInt(options.timeout)
735
+ });
736
+ spinner.stop();
737
+ if (result.success) {
738
+ console.log(chalk.green("\u2713"), result.message);
739
+ if (result.data) {
740
+ console.log(chalk.cyan("\nPatch Details:"));
741
+ console.log(` Files changed: ${result.data.filesChanged}`);
742
+ console.log(` Additions: +${result.data.additions}`);
743
+ console.log(` Deletions: -${result.data.deletions}`);
744
+ console.log(chalk.yellow(`
745
+ ${result.action}`));
746
+ }
747
+ } else {
748
+ console.log(chalk.red("\u2717"), result.message);
749
+ }
750
+ await context.database.disconnect();
751
+ } catch (error) {
752
+ spinner.stop();
753
+ console.error(chalk.red("Error:"), error.message);
754
+ process.exit(1);
755
+ }
756
+ });
757
+ agentCmd.command("spec-run <specPath>").description("Implement a spec on a branch, validate with lint+test+build").option("--timeout <ms>", "Agent timeout in milliseconds", "300000").action(async (specPath, options) => {
758
+ const spinner = ora("Spawning spec agent...").start();
759
+ try {
760
+ const { context } = await initializeSkillContext();
761
+ const { ParallelAgentSkill } = await import("../../skills/parallel-agent-skill.js");
762
+ const agentSkill = new ParallelAgentSkill(context);
763
+ const result = await agentSkill.specRun(specPath, {
764
+ timeout: parseInt(options.timeout)
765
+ });
766
+ spinner.stop();
767
+ if (result.success) {
768
+ console.log(chalk.green("\u2713"), result.message);
769
+ } else {
770
+ console.log(chalk.red("\u2717"), result.message);
771
+ }
772
+ if (result.data) {
773
+ console.log(chalk.cyan("\nResults:"));
774
+ console.log(` Branch: ${result.data.branch}`);
775
+ console.log(` Workspace: ${result.data.workDir}`);
776
+ const v = result.data.validation;
777
+ if (v) {
778
+ console.log(
779
+ ` Lint: ${v.lint ? chalk.green("pass") : chalk.red("fail")} Test: ${v.test ? chalk.green("pass") : chalk.red("fail")} Build: ${v.build ? chalk.green("pass") : chalk.red("fail")}`
780
+ );
781
+ }
782
+ if (result.data.diffStat) {
783
+ console.log(chalk.gray(`
784
+ ${result.data.diffStat}`));
785
+ }
786
+ if (result.action) {
787
+ console.log(chalk.yellow(`
788
+ ${result.action}`));
789
+ }
790
+ }
791
+ await context.database.disconnect();
792
+ } catch (error) {
793
+ spinner.stop();
794
+ console.error(chalk.red("Error:"), error.message);
795
+ process.exit(1);
796
+ }
797
+ });
700
798
  skillsCmd.command("help [skill]").description("Show help for a specific skill").action(async (skill) => {
701
799
  if (skill) {
702
800
  switch (skill) {
@@ -725,6 +823,29 @@ Options:
725
823
  --security Focus on security vulnerabilities
726
824
  --performance Focus on performance issues
727
825
  --verbose Show detailed output
826
+ `);
827
+ break;
828
+ case "agent":
829
+ console.log(`
830
+ agent \u2014 Parallel Agent Skill (Willison Patterns)
831
+
832
+ Spawn isolated Claude agents in disposable /tmp workspaces.
833
+
834
+ Subcommands:
835
+ research <question> Explore codebase, save findings as a frame
836
+ maintain <task> Low-stakes fix, produces a .patch file
837
+ spec-run <specPath> Implement spec on branch, validate
838
+
839
+ Options:
840
+ --timeout <ms> Agent timeout (default: 300000)
841
+
842
+ Examples:
843
+ stackmemory skills agent research "How does FTS5 search work?"
844
+ stackmemory skills agent maintain "Fix deprecation warning in webhook.ts"
845
+ stackmemory skills agent spec-run docs/specs/my-feature.md
846
+
847
+ Apply patches: git apply .stackmemory/patches/<file>.patch
848
+ Review specs: cd /tmp/sm-spec-* && git log --oneline
728
849
  `);
729
850
  break;
730
851
  default:
@@ -752,8 +873,9 @@ Options:
752
873
  console.log(
753
874
  " spec - Generate iterative spec docs (one-pager, dev-spec, prompt-plan, agents)"
754
875
  );
876
+ console.log(" linear-run - Execute Linear tasks via RLM orchestrator");
755
877
  console.log(
756
- " linear-run - Execute Linear tasks via RLM orchestrator\n"
878
+ " agent - Spawn parallel agents (research, maintain, spec-run)\n"
757
879
  );
758
880
  console.log(
759
881
  chalk.yellow(
@@ -20,6 +20,17 @@ class GraphitiHooks {
20
20
  emitter.registerHandler("session_start", this.onSessionStart.bind(this));
21
21
  emitter.registerHandler("file_change", this.onFileChange.bind(this));
22
22
  emitter.registerHandler("session_end", this.onSessionEnd.bind(this));
23
+ emitter.registerHandler("input_idle", this.onInputIdle.bind(this));
24
+ emitter.registerHandler("context_switch", this.onContextSwitch.bind(this));
25
+ emitter.registerHandler("prompt_submit", this.onPromptSubmit.bind(this));
26
+ emitter.registerHandler("tool_use", this.onToolUse.bind(this));
27
+ emitter.registerHandler(
28
+ "suggestion_ready",
29
+ this.onSuggestionReady.bind(this)
30
+ );
31
+ emitter.registerHandler("agent_start", this.onAgentStart.bind(this));
32
+ emitter.registerHandler("agent_complete", this.onAgentComplete.bind(this));
33
+ emitter.registerHandler("agent_error", this.onAgentError.bind(this));
23
34
  logger.info("Graphiti hooks registered", {
24
35
  endpoint: this.config.endpoint,
25
36
  backend: this.config.backend,
@@ -82,6 +93,144 @@ class GraphitiHooks {
82
93
  });
83
94
  }
84
95
  }
96
+ async onInputIdle(event) {
97
+ const idle = event;
98
+ try {
99
+ const episode = {
100
+ type: "input_idle",
101
+ content: {
102
+ idleDuration: idle.data.idleDuration,
103
+ lastInput: idle.data.lastInput
104
+ },
105
+ timestamp: Date.now(),
106
+ source: "stackmemory"
107
+ };
108
+ await this.client.upsertEpisode(episode);
109
+ } catch (error) {
110
+ logger.debug("Graphiti input_idle episode failed", {
111
+ error: error instanceof Error ? error.message : String(error)
112
+ });
113
+ }
114
+ }
115
+ async onContextSwitch(event) {
116
+ const ctx = event;
117
+ try {
118
+ const episode = {
119
+ type: "context_switch",
120
+ content: {
121
+ fromBranch: ctx.data.fromBranch,
122
+ toBranch: ctx.data.toBranch,
123
+ fromProject: ctx.data.fromProject,
124
+ toProject: ctx.data.toProject
125
+ },
126
+ timestamp: Date.now(),
127
+ source: "stackmemory"
128
+ };
129
+ await this.client.upsertEpisode(episode);
130
+ } catch (error) {
131
+ logger.debug("Graphiti context_switch episode failed", {
132
+ error: error instanceof Error ? error.message : String(error)
133
+ });
134
+ }
135
+ }
136
+ async onPromptSubmit(event) {
137
+ try {
138
+ const episode = {
139
+ type: "prompt_submit",
140
+ content: event.data || {},
141
+ timestamp: Date.now(),
142
+ source: "stackmemory"
143
+ };
144
+ await this.client.upsertEpisode(episode);
145
+ } catch (error) {
146
+ logger.debug("Graphiti prompt_submit episode failed", {
147
+ error: error instanceof Error ? error.message : String(error)
148
+ });
149
+ }
150
+ }
151
+ async onToolUse(event) {
152
+ try {
153
+ const episode = {
154
+ type: "tool_use",
155
+ content: event.data || {},
156
+ timestamp: Date.now(),
157
+ source: "stackmemory"
158
+ };
159
+ await this.client.upsertEpisode(episode);
160
+ } catch (error) {
161
+ logger.debug("Graphiti tool_use episode failed", {
162
+ error: error instanceof Error ? error.message : String(error)
163
+ });
164
+ }
165
+ }
166
+ async onSuggestionReady(event) {
167
+ const suggestion = event;
168
+ try {
169
+ const episode = {
170
+ type: "suggestion_ready",
171
+ content: {
172
+ source: suggestion.data.source,
173
+ confidence: suggestion.data.confidence,
174
+ preview: suggestion.data.preview
175
+ },
176
+ timestamp: Date.now(),
177
+ source: "stackmemory"
178
+ };
179
+ await this.client.upsertEpisode(episode);
180
+ } catch (error) {
181
+ logger.debug("Graphiti suggestion_ready episode failed", {
182
+ error: error instanceof Error ? error.message : String(error)
183
+ });
184
+ }
185
+ }
186
+ async onAgentStart(event) {
187
+ const e = event;
188
+ try {
189
+ const episode = {
190
+ type: "agent_start",
191
+ content: { agentType: e.data.agentType, task: e.data.task },
192
+ timestamp: Date.now(),
193
+ source: "stackmemory"
194
+ };
195
+ await this.client.upsertEpisode(episode);
196
+ } catch (error) {
197
+ logger.debug("Graphiti agent_start episode failed", {
198
+ error: error instanceof Error ? error.message : String(error)
199
+ });
200
+ }
201
+ }
202
+ async onAgentComplete(event) {
203
+ const e = event;
204
+ try {
205
+ const episode = {
206
+ type: "agent_complete",
207
+ content: { ...e.data },
208
+ timestamp: Date.now(),
209
+ source: "stackmemory"
210
+ };
211
+ await this.client.upsertEpisode(episode);
212
+ } catch (error) {
213
+ logger.debug("Graphiti agent_complete episode failed", {
214
+ error: error instanceof Error ? error.message : String(error)
215
+ });
216
+ }
217
+ }
218
+ async onAgentError(event) {
219
+ const e = event;
220
+ try {
221
+ const episode = {
222
+ type: "agent_error",
223
+ content: { agentType: e.data.agentType, error: e.data.error },
224
+ timestamp: Date.now(),
225
+ source: "stackmemory"
226
+ };
227
+ await this.client.upsertEpisode(episode);
228
+ } catch (error) {
229
+ logger.debug("Graphiti agent_error episode failed", {
230
+ error: error instanceof Error ? error.message : String(error)
231
+ });
232
+ }
233
+ }
85
234
  // Expose a simple temporal query helper for future MCP tooling
86
235
  async buildTemporalContext(query = {}) {
87
236
  const now = Date.now();
@@ -3,6 +3,8 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { execSync } from "child_process";
6
+ import * as fs from "fs";
7
+ import * as path from "path";
6
8
  import { pickNextLinearTask } from "./linear-task-picker.js";
7
9
  function formatDuration(ms) {
8
10
  const seconds = Math.floor(ms / 1e3);
@@ -125,6 +127,34 @@ async function generateSuggestions(context) {
125
127
  }
126
128
  } catch {
127
129
  }
130
+ try {
131
+ const patchDir = path.join(process.cwd(), ".stackmemory", "patches");
132
+ if (fs.existsSync(patchDir)) {
133
+ const patches = fs.readdirSync(patchDir).filter((f) => f.endsWith(".patch"));
134
+ if (patches.length > 0) {
135
+ suggestions.push({
136
+ key: String(keyIndex++),
137
+ label: `Review ${patches.length} pending patch${patches.length > 1 ? "es" : ""}`,
138
+ action: `ls -la .stackmemory/patches/`,
139
+ priority: 75
140
+ });
141
+ }
142
+ }
143
+ } catch {
144
+ }
145
+ try {
146
+ const tmpEntries = fs.readdirSync("/tmp").filter((d) => d.startsWith("sm-spec-"));
147
+ if (tmpEntries.length > 0) {
148
+ const latest = path.join("/tmp", tmpEntries[tmpEntries.length - 1]);
149
+ suggestions.push({
150
+ key: String(keyIndex++),
151
+ label: `Inspect spec branch (${tmpEntries.length} workspace${tmpEntries.length > 1 ? "s" : ""})`,
152
+ action: `cd ${latest} && git log --oneline -5`,
153
+ priority: 65
154
+ });
155
+ }
156
+ } catch {
157
+ }
128
158
  const durationMs = Date.now() - context.sessionStartTime;
129
159
  if (durationMs > 30 * 60 * 1e3) {
130
160
  suggestions.push({
@@ -0,0 +1,115 @@
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 { logger } from "../../core/monitoring/logger.js";
6
+ import { GraphitiClient } from "./client.js";
7
+ class LinearGraphitiBridge {
8
+ client;
9
+ constructor(config = {}) {
10
+ this.client = new GraphitiClient(config);
11
+ }
12
+ async processWebhook(payload) {
13
+ const { action, data } = payload;
14
+ const now = Date.now();
15
+ try {
16
+ const episode = {
17
+ type: `linear_issue_${action}`,
18
+ content: {
19
+ identifier: data.identifier,
20
+ title: data.title,
21
+ action,
22
+ state: data.state?.name,
23
+ priority: data.priority,
24
+ assignee: data.assignee?.name
25
+ },
26
+ timestamp: now,
27
+ source: "linear"
28
+ };
29
+ await this.client.upsertEpisode(episode);
30
+ if (action === "remove") return;
31
+ const entities = [
32
+ {
33
+ type: "Issue",
34
+ name: data.identifier,
35
+ summary: data.title,
36
+ properties: {
37
+ linearId: data.id,
38
+ state: data.state?.name,
39
+ priority: data.priority
40
+ }
41
+ }
42
+ ];
43
+ if (data.assignee) {
44
+ entities.push({
45
+ type: "Person",
46
+ name: data.assignee.name,
47
+ properties: {
48
+ linearId: data.assignee.id,
49
+ email: data.assignee.email
50
+ }
51
+ });
52
+ }
53
+ if (data.team) {
54
+ entities.push({
55
+ type: "Team",
56
+ name: data.team.name,
57
+ properties: { linearId: data.team.id, key: data.team.key }
58
+ });
59
+ }
60
+ if (data.labels?.length) {
61
+ for (const label of data.labels) {
62
+ entities.push({
63
+ type: "Label",
64
+ name: label.name,
65
+ properties: { linearId: label.id, color: label.color }
66
+ });
67
+ }
68
+ }
69
+ const entityResult = await this.client.upsertEntities(entities);
70
+ const issueId = entityResult.ids[0];
71
+ const relations = [];
72
+ let idx = 1;
73
+ if (data.assignee) {
74
+ relations.push({
75
+ fromId: issueId,
76
+ toId: entityResult.ids[idx],
77
+ type: "ASSIGNED_TO",
78
+ validFrom: now
79
+ });
80
+ idx++;
81
+ }
82
+ if (data.team) {
83
+ relations.push({
84
+ fromId: issueId,
85
+ toId: entityResult.ids[idx],
86
+ type: "BELONGS_TO",
87
+ validFrom: now
88
+ });
89
+ idx++;
90
+ }
91
+ if (data.labels?.length) {
92
+ for (let i = 0; i < data.labels.length; i++) {
93
+ relations.push({
94
+ fromId: issueId,
95
+ toId: entityResult.ids[idx + i],
96
+ type: "HAS_LABEL",
97
+ validFrom: now
98
+ });
99
+ }
100
+ }
101
+ if (relations.length > 0) {
102
+ await this.client.upsertRelations(relations);
103
+ }
104
+ } catch (error) {
105
+ logger.debug("Linear-Graphiti bridge error", {
106
+ action,
107
+ identifier: data.identifier,
108
+ error: error instanceof Error ? error.message : String(error)
109
+ });
110
+ }
111
+ }
112
+ }
113
+ export {
114
+ LinearGraphitiBridge
115
+ };
@@ -0,0 +1,101 @@
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 { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7
+ import { DEFAULT_GREPTILE_CONFIG } from "./config.js";
8
+ class GreptileClientError extends Error {
9
+ constructor(message, code) {
10
+ super(message);
11
+ this.code = code;
12
+ this.name = "GreptileClientError";
13
+ }
14
+ }
15
+ class GreptileClient {
16
+ config;
17
+ client = null;
18
+ transport = null;
19
+ connecting = null;
20
+ constructor(config = {}) {
21
+ this.config = { ...DEFAULT_GREPTILE_CONFIG, ...config };
22
+ if (!this.config.enabled || !this.config.apiKey) {
23
+ throw new GreptileClientError(
24
+ "Greptile integration disabled (GREPTILE_API_KEY not set)",
25
+ "DISABLED"
26
+ );
27
+ }
28
+ }
29
+ async ensureConnected() {
30
+ if (this.client) return this.client;
31
+ if (this.connecting) {
32
+ await this.connecting;
33
+ return this.client;
34
+ }
35
+ this.connecting = this.connect();
36
+ try {
37
+ await this.connecting;
38
+ return this.client;
39
+ } finally {
40
+ this.connecting = null;
41
+ }
42
+ }
43
+ async connect() {
44
+ const transport = new StreamableHTTPClientTransport(
45
+ new URL(this.config.mcpEndpoint),
46
+ {
47
+ requestInit: {
48
+ headers: {
49
+ Authorization: `Bearer ${this.config.apiKey}`
50
+ }
51
+ },
52
+ reconnectionOptions: {
53
+ maxRetries: this.config.maxRetries,
54
+ initialReconnectionDelay: 1e3,
55
+ reconnectionDelayGrowFactor: 1.5,
56
+ maxReconnectionDelay: 1e4
57
+ }
58
+ }
59
+ );
60
+ const client = new Client(
61
+ { name: "stackmemory-greptile", version: "1.0.0" },
62
+ { capabilities: {} }
63
+ );
64
+ transport.onclose = () => {
65
+ this.client = null;
66
+ this.transport = null;
67
+ };
68
+ await client.connect(transport);
69
+ this.client = client;
70
+ this.transport = transport;
71
+ }
72
+ async callTool(name, args = {}) {
73
+ const client = await this.ensureConnected();
74
+ const result = await client.callTool({ name, arguments: args });
75
+ if (result.content && Array.isArray(result.content)) {
76
+ const textParts = result.content.filter(
77
+ (c) => c.type === "text" && typeof c.text === "string"
78
+ ).map((c) => c.text);
79
+ if (textParts.length === 0) return result;
80
+ const combined = textParts.join("\n");
81
+ try {
82
+ return JSON.parse(combined);
83
+ } catch {
84
+ return combined;
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+ async disconnect() {
90
+ if (this.transport) {
91
+ await this.transport.close();
92
+ }
93
+ this.client = null;
94
+ this.transport = null;
95
+ this.connecting = null;
96
+ }
97
+ }
98
+ export {
99
+ GreptileClient,
100
+ GreptileClientError
101
+ };
@@ -0,0 +1,14 @@
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
+ const DEFAULT_GREPTILE_CONFIG = {
6
+ enabled: !!process.env.GREPTILE_API_KEY,
7
+ mcpEndpoint: process.env.GREPTILE_MCP_ENDPOINT || "https://api.greptile.com/mcp",
8
+ apiKey: process.env.GREPTILE_API_KEY || "",
9
+ timeoutMs: 15e3,
10
+ maxRetries: 2
11
+ };
12
+ export {
13
+ DEFAULT_GREPTILE_CONFIG
14
+ };
@@ -0,0 +1,11 @@
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 "./config.js";
6
+ import { GreptileClient, GreptileClientError } from "./client.js";
7
+ export {
8
+ DEFAULT_GREPTILE_CONFIG,
9
+ GreptileClient,
10
+ GreptileClientError
11
+ };
@@ -0,0 +1,4 @@
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);
@@ -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