@stackmemoryai/stackmemory 1.2.2 → 1.2.6

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 (77) hide show
  1. package/README.md +10 -6
  2. package/dist/src/cli/claude-sm.js +33 -4
  3. package/dist/src/cli/codex-sm-danger.js +4 -1
  4. package/dist/src/cli/codex-sm.js +6 -8
  5. package/dist/src/cli/commands/config.js +0 -81
  6. package/dist/src/cli/commands/context-rehydrate.js +133 -47
  7. package/dist/src/cli/commands/db.js +35 -8
  8. package/dist/src/cli/commands/handoff.js +1 -1
  9. package/dist/src/cli/commands/linear.js +9 -0
  10. package/dist/src/cli/commands/ralph.js +2 -2
  11. package/dist/src/cli/commands/setup.js +2 -2
  12. package/dist/src/cli/commands/signup.js +3 -1
  13. package/dist/src/cli/commands/skills.js +108 -1
  14. package/dist/src/cli/commands/storage-tier.js +26 -8
  15. package/dist/src/cli/index.js +1 -57
  16. package/dist/src/core/config/feature-flags.js +0 -4
  17. package/dist/src/core/context/dual-stack-manager.js +10 -3
  18. package/dist/src/core/context/frame-database.js +32 -0
  19. package/dist/src/core/context/frame-handoff-manager.js +2 -2
  20. package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
  21. package/dist/src/core/context/index.js +2 -2
  22. package/dist/src/core/database/sqlite-adapter.js +161 -1
  23. package/dist/src/core/digest/frame-digest-integration.js +1 -1
  24. package/dist/src/core/digest/index.js +1 -1
  25. package/dist/src/core/execution/parallel-executor.js +5 -1
  26. package/dist/src/core/projects/project-isolation.js +18 -4
  27. package/dist/src/core/security/index.js +2 -0
  28. package/dist/src/core/security/input-sanitizer.js +23 -0
  29. package/dist/src/core/utils/update-checker.js +10 -6
  30. package/dist/src/daemon/daemon-config.js +2 -1
  31. package/dist/src/daemon/services/auto-save-service.js +121 -0
  32. package/dist/src/daemon/services/maintenance-service.js +76 -1
  33. package/dist/src/features/sweep/prompt-builder.js +2 -2
  34. package/dist/src/hooks/daemon.js +0 -8
  35. package/dist/src/integrations/linear/config.js +3 -1
  36. package/dist/src/integrations/linear/sync.js +18 -5
  37. package/dist/src/integrations/linear/webhook.js +0 -16
  38. package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
  39. package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
  40. package/dist/src/integrations/mcp/handlers/index.js +55 -9
  41. package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
  42. package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
  43. package/dist/src/integrations/mcp/handlers/trace-handlers.js +28 -9
  44. package/dist/src/integrations/mcp/index.js +2 -2
  45. package/dist/src/integrations/mcp/refactored-server.js +31 -10
  46. package/dist/src/integrations/mcp/server.js +0 -130
  47. package/dist/src/integrations/mcp/tool-definitions.js +196 -67
  48. package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
  49. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
  50. package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
  51. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
  52. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
  53. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
  54. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
  55. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
  56. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
  57. package/dist/src/skills/claude-skills.js +37 -103
  58. package/dist/src/skills/theory-skill.js +191 -0
  59. package/dist/src/utils/hook-installer.js +16 -0
  60. package/package.json +6 -6
  61. package/scripts/install-claude-hooks-auto.js +8 -0
  62. package/templates/claude-hooks/cord-trace.js +225 -0
  63. package/templates/claude-hooks/theory-capture.js +100 -0
  64. package/dist/src/core/config/storage-config.js +0 -114
  65. package/dist/src/core/storage/chromadb-adapter.js +0 -379
  66. package/dist/src/hooks/graphiti-hooks.js +0 -253
  67. package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
  68. package/dist/src/integrations/graphiti/client.js +0 -115
  69. package/dist/src/integrations/graphiti/config.js +0 -17
  70. package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +0 -115
  71. package/dist/src/integrations/graphiti/types.js +0 -4
  72. package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
  73. package/dist/src/skills/repo-ingestion-skill.js +0 -631
  74. package/templates/claude-hooks/chromadb-wrapper +0 -21
  75. /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
  76. /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
  77. /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
@@ -1,458 +0,0 @@
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 { EventEmitter } from "events";
6
- import * as fs from "fs/promises";
7
- import * as path from "path";
8
- import { execSync } from "child_process";
9
- class EnhancedPreClearHooks extends EventEmitter {
10
- frameManager;
11
- dbManager;
12
- clearSurvival;
13
- handoffGenerator;
14
- projectRoot;
15
- constructor(frameManager, dbManager, clearSurvival, handoffGenerator, projectRoot) {
16
- super();
17
- this.frameManager = frameManager;
18
- this.dbManager = dbManager;
19
- this.clearSurvival = clearSurvival;
20
- this.handoffGenerator = handoffGenerator;
21
- this.projectRoot = projectRoot;
22
- }
23
- /**
24
- * Comprehensive pre-clear context capture
25
- */
26
- async capturePreClearContext(trigger) {
27
- console.log("\u{1F50D} Capturing comprehensive session context...");
28
- const context = {
29
- sessionId: await this.dbManager.getCurrentSessionId(),
30
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
31
- trigger,
32
- contextUsage: await this.analyzeContextUsage(),
33
- workingState: await this.captureWorkingState(),
34
- conversationState: await this.captureConversationState(),
35
- codeContext: await this.captureCodeContext(),
36
- cognitiveState: await this.captureCognitiveState(),
37
- environment: await this.captureEnvironment()
38
- };
39
- await this.saveEnhancedContext(context);
40
- this.emit("context:captured", context);
41
- console.log("\u2705 Comprehensive context captured");
42
- return context;
43
- }
44
- /**
45
- * Analyze current context usage with detailed breakdown
46
- */
47
- async analyzeContextUsage() {
48
- const sessionId = await this.dbManager.getCurrentSessionId();
49
- const frames = await this.dbManager.getRecentFrames(sessionId, 1e3);
50
- const traces = await this.dbManager.getRecentTraces(sessionId, 1e3);
51
- const frameTokens = frames.length * 200;
52
- const traceTokens = traces.length * 100;
53
- const conversationTokens = await this.estimateConversationTokens();
54
- const codeBlockTokens = await this.estimateCodeBlockTokens();
55
- const estimatedTokens = frameTokens + traceTokens + conversationTokens + codeBlockTokens;
56
- const maxTokens = 1e5;
57
- return {
58
- estimatedTokens,
59
- maxTokens,
60
- percentage: estimatedTokens / maxTokens,
61
- components: {
62
- frames: frameTokens,
63
- traces: traceTokens,
64
- conversations: conversationTokens,
65
- codeBlocks: codeBlockTokens
66
- }
67
- };
68
- }
69
- /**
70
- * Capture current working state
71
- */
72
- async captureWorkingState() {
73
- const activeFrame = await this.getCurrentActiveFrame();
74
- const recentTraces = await this.dbManager.getRecentTraces(
75
- await this.dbManager.getCurrentSessionId(),
76
- 50
77
- );
78
- const activeFiles = this.extractActiveFiles(recentTraces);
79
- const recentCommands = recentTraces.filter((t) => t.type === "bash" || t.type === "command").map((t) => t.content.command).slice(0, 10);
80
- const pendingActions = this.extractPendingActions(activeFrame);
81
- const blockers = this.extractBlockers(recentTraces);
82
- return {
83
- currentTask: activeFrame?.description || "No active task",
84
- activeFiles,
85
- recentCommands,
86
- pendingActions,
87
- blockers
88
- };
89
- }
90
- /**
91
- * Capture conversation state and recent context
92
- */
93
- async captureConversationState() {
94
- const sessionId = await this.dbManager.getCurrentSessionId();
95
- const recentTraces = await this.dbManager.getRecentTraces(sessionId, 100);
96
- const userMessages = recentTraces.filter((t) => t.type === "user_message" || t.type === "input").slice(0, 5);
97
- const assistantMessages = recentTraces.filter((t) => t.type === "assistant_message" || t.type === "response").slice(0, 5);
98
- const conversationTopic = this.inferConversationTopic(recentTraces);
99
- const recentContext = this.buildRecentContextSummary(recentTraces);
100
- return {
101
- lastUserMessage: userMessages[0]?.content.message || "No recent user message",
102
- lastAssistantMessage: assistantMessages[0]?.content.message || "No recent assistant message",
103
- conversationTopic,
104
- messageCount: userMessages.length + assistantMessages.length,
105
- recentContext
106
- };
107
- }
108
- /**
109
- * Capture comprehensive code context
110
- */
111
- async captureCodeContext() {
112
- const gitStatus = await this.captureGitStatus();
113
- const modifiedFiles = await this.captureModifiedFiles();
114
- const testResults = await this.captureTestResults();
115
- const buildStatus = await this.captureBuildStatus();
116
- const dependencies = await this.captureDependencies();
117
- return {
118
- modifiedFiles,
119
- gitStatus,
120
- testResults,
121
- buildStatus,
122
- dependencies
123
- };
124
- }
125
- /**
126
- * Capture cognitive state and mental model
127
- */
128
- async captureCognitiveState() {
129
- const sessionId = await this.dbManager.getCurrentSessionId();
130
- const recentTraces = await this.dbManager.getRecentTraces(sessionId, 100);
131
- const currentFocus = await this.extractCurrentFocus();
132
- const mentalModel = this.extractMentalModel(recentTraces);
133
- const assumptions = this.extractAssumptions(recentTraces);
134
- const hypotheses = this.extractHypotheses(recentTraces);
135
- const explorationPaths = this.extractExplorationPaths(recentTraces);
136
- return {
137
- currentFocus,
138
- mentalModel,
139
- assumptions,
140
- hypotheses,
141
- explorationPaths
142
- };
143
- }
144
- /**
145
- * Capture environment snapshot
146
- */
147
- async captureEnvironment() {
148
- const gitBranch = await this.getCurrentGitBranch();
149
- const packageJson = await this.getPackageJson();
150
- const environmentVars = this.getRelevantEnvVars();
151
- return {
152
- workingDirectory: this.projectRoot,
153
- gitBranch,
154
- nodeVersion: process.version,
155
- packageJson,
156
- environmentVars
157
- };
158
- }
159
- /**
160
- * Save enhanced context to multiple locations for reliability
161
- */
162
- async saveEnhancedContext(context) {
163
- const timestamp = context.timestamp.replace(/[:.]/g, "-");
164
- const primaryPath = path.join(
165
- this.projectRoot,
166
- ".stackmemory",
167
- "pre-clear",
168
- `context-${timestamp}.json`
169
- );
170
- const backupPath = path.join(
171
- this.projectRoot,
172
- ".stackmemory",
173
- "pre-clear",
174
- "latest-context.json"
175
- );
176
- const markdownPath = path.join(
177
- this.projectRoot,
178
- ".stackmemory",
179
- "pre-clear",
180
- `context-${timestamp}.md`
181
- );
182
- await fs.mkdir(path.dirname(primaryPath), { recursive: true });
183
- await fs.writeFile(primaryPath, JSON.stringify(context, null, 2), "utf-8");
184
- await fs.writeFile(backupPath, JSON.stringify(context, null, 2), "utf-8");
185
- const markdown = this.generateMarkdownSummary(context);
186
- await fs.writeFile(markdownPath, markdown, "utf-8");
187
- console.log(
188
- `\u{1F4C1} Context saved to ${path.relative(this.projectRoot, primaryPath)}`
189
- );
190
- }
191
- /**
192
- * Generate human-readable markdown summary
193
- */
194
- generateMarkdownSummary(context) {
195
- const lines = [
196
- `# Pre-Clear Context Snapshot`,
197
- `**Timestamp**: ${new Date(context.timestamp).toLocaleString()}`,
198
- `**Trigger**: ${context.trigger}`,
199
- `**Session ID**: ${context.sessionId}`,
200
- "",
201
- `## \u{1F4CA} Context Usage`,
202
- `- **Total Tokens**: ${context.contextUsage.estimatedTokens.toLocaleString()} / ${context.contextUsage.maxTokens.toLocaleString()} (${Math.round(context.contextUsage.percentage * 100)}%)`,
203
- `- **Frames**: ${context.contextUsage.components.frames} tokens`,
204
- `- **Traces**: ${context.contextUsage.components.traces} tokens`,
205
- `- **Conversations**: ${context.contextUsage.components.conversations} tokens`,
206
- `- **Code Blocks**: ${context.contextUsage.components.codeBlocks} tokens`,
207
- "",
208
- `## \u{1F3AF} Current Work State`,
209
- `**Task**: ${context.workingState.currentTask}`,
210
- `**Active Files** (${context.workingState.activeFiles.length}):`,
211
- ...context.workingState.activeFiles.slice(0, 10).map((f) => `- ${f}`),
212
- "",
213
- `**Recent Commands**:`,
214
- ...context.workingState.recentCommands.slice(0, 5).map((c) => `- \`${c}\``),
215
- "",
216
- `## \u{1F4AC} Conversation State`,
217
- `**Topic**: ${context.conversationState.conversationTopic}`,
218
- `**Messages**: ${context.conversationState.messageCount}`,
219
- `**Last User**: ${context.conversationState.lastUserMessage.substring(0, 100)}...`,
220
- "",
221
- `## \u{1F4DD} Code Context`,
222
- `**Git Branch**: ${context.codeContext.gitStatus.branch}`,
223
- `**Modified Files**: ${context.codeContext.modifiedFiles.length}`,
224
- `**Staged**: ${context.codeContext.gitStatus.staged.length}`,
225
- `**Unstaged**: ${context.codeContext.gitStatus.unstaged.length}`,
226
- "",
227
- `## \u{1F9E0} Cognitive State`,
228
- `**Current Focus**: ${context.cognitiveState.currentFocus}`,
229
- `**Mental Model**:`,
230
- ...context.cognitiveState.mentalModel.slice(0, 5).map((m) => `- ${m}`),
231
- "",
232
- `## \u{1F30D} Environment`,
233
- `**Directory**: ${context.environment.workingDirectory}`,
234
- `**Node Version**: ${context.environment.nodeVersion}`,
235
- `**Git Branch**: ${context.environment.gitBranch}`,
236
- ""
237
- ];
238
- return lines.filter((l) => l !== void 0).join("\n");
239
- }
240
- // Helper methods (simplified implementations)
241
- async estimateConversationTokens() {
242
- return 15e3;
243
- }
244
- async estimateCodeBlockTokens() {
245
- return 8e3;
246
- }
247
- async getCurrentActiveFrame() {
248
- const stack = await this.frameManager.getStack();
249
- return stack.frames.find((f) => f.status === "open");
250
- }
251
- extractActiveFiles(traces) {
252
- const files = /* @__PURE__ */ new Set();
253
- traces.forEach((trace) => {
254
- if (trace.content?.file_path) files.add(trace.content.file_path);
255
- if (trace.content?.path) files.add(trace.content.path);
256
- });
257
- return Array.from(files).slice(0, 20);
258
- }
259
- extractPendingActions(frame) {
260
- if (!frame?.metadata?.pendingActions) return [];
261
- return frame.metadata.pendingActions;
262
- }
263
- extractBlockers(traces) {
264
- return traces.filter((t) => t.type === "error" && !t.metadata?.resolved).map((t) => t.content.error || "Unknown error").slice(0, 5);
265
- }
266
- inferConversationTopic(traces) {
267
- return "Code implementation and debugging";
268
- }
269
- buildRecentContextSummary(traces) {
270
- return traces.slice(0, 10).map(
271
- (t) => `${t.type}: ${t.content.summary || t.content.description || "No description"}`
272
- ).filter((s) => s.length > 10);
273
- }
274
- async captureGitStatus() {
275
- try {
276
- const branch = execSync("git branch --show-current", {
277
- encoding: "utf-8",
278
- cwd: this.projectRoot
279
- }).trim();
280
- const staged = execSync("git diff --cached --name-only", {
281
- encoding: "utf-8",
282
- cwd: this.projectRoot
283
- }).trim().split("\n").filter(Boolean);
284
- const unstaged = execSync("git diff --name-only", {
285
- encoding: "utf-8",
286
- cwd: this.projectRoot
287
- }).trim().split("\n").filter(Boolean);
288
- const untracked = execSync("git ls-files --others --exclude-standard", {
289
- encoding: "utf-8",
290
- cwd: this.projectRoot
291
- }).trim().split("\n").filter(Boolean);
292
- return {
293
- branch,
294
- ahead: 0,
295
- // Would implement git status parsing
296
- behind: 0,
297
- staged,
298
- unstaged,
299
- untracked,
300
- lastCommit: {
301
- hash: "abc123",
302
- // Would get from git log
303
- message: "Recent commit",
304
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
305
- }
306
- };
307
- } catch (error) {
308
- return {
309
- branch: "unknown",
310
- ahead: 0,
311
- behind: 0,
312
- staged: [],
313
- unstaged: [],
314
- untracked: [],
315
- lastCommit: { hash: "", message: "", timestamp: "" }
316
- };
317
- }
318
- }
319
- async captureModifiedFiles() {
320
- try {
321
- const output = execSync("git diff --name-status", {
322
- encoding: "utf-8",
323
- cwd: this.projectRoot
324
- });
325
- return output.trim().split("\n").filter(Boolean).map((line) => {
326
- const [status, path2] = line.split(" ");
327
- return {
328
- path: path2,
329
- lastModified: (/* @__PURE__ */ new Date()).toISOString(),
330
- changeType: status === "A" ? "created" : status === "D" ? "deleted" : "modified",
331
- lineChanges: { added: 0, removed: 0 },
332
- // Would get from git diff --stat
333
- purpose: "Code changes",
334
- relatedFiles: []
335
- };
336
- });
337
- } catch (error) {
338
- return [];
339
- }
340
- }
341
- async captureTestResults() {
342
- return void 0;
343
- }
344
- async captureBuildStatus() {
345
- return void 0;
346
- }
347
- async captureDependencies() {
348
- try {
349
- const packageJsonPath = path.join(this.projectRoot, "package.json");
350
- const content = await fs.readFile(packageJsonPath, "utf-8");
351
- const packageJson = JSON.parse(content);
352
- const deps = [];
353
- Object.entries(packageJson.dependencies || {}).forEach(
354
- ([name, version]) => {
355
- deps.push({
356
- name,
357
- version,
358
- type: "dependency",
359
- critical: ["react", "express", "next"].includes(name)
360
- });
361
- }
362
- );
363
- return deps;
364
- } catch (error) {
365
- return [];
366
- }
367
- }
368
- async extractCurrentFocus() {
369
- const activeFrame = await this.getCurrentActiveFrame();
370
- return activeFrame?.description || "No current focus";
371
- }
372
- extractMentalModel(traces) {
373
- return [
374
- "Component architecture",
375
- "Data flow patterns",
376
- "Error handling strategy"
377
- ];
378
- }
379
- extractAssumptions(traces) {
380
- return [
381
- "User input is validated",
382
- "Database is available",
383
- "Network is stable"
384
- ];
385
- }
386
- extractHypotheses(traces) {
387
- return [
388
- "Bug is in validation logic",
389
- "Performance issue is database-related"
390
- ];
391
- }
392
- extractExplorationPaths(traces) {
393
- return [
394
- "Try different algorithm",
395
- "Refactor data structure",
396
- "Add caching layer"
397
- ];
398
- }
399
- async getCurrentGitBranch() {
400
- try {
401
- return execSync("git branch --show-current", {
402
- encoding: "utf-8",
403
- cwd: this.projectRoot
404
- }).trim();
405
- } catch (error) {
406
- return "unknown";
407
- }
408
- }
409
- async getPackageJson() {
410
- try {
411
- const content = await fs.readFile(
412
- path.join(this.projectRoot, "package.json"),
413
- "utf-8"
414
- );
415
- return JSON.parse(content);
416
- } catch (error) {
417
- return null;
418
- }
419
- }
420
- getRelevantEnvVars() {
421
- const relevantVars = ["NODE_ENV", "DEBUG", "PORT", "DATABASE_URL"];
422
- const result = {};
423
- relevantVars.forEach((varName) => {
424
- if (process.env[varName]) {
425
- result[varName] = process.env[varName];
426
- }
427
- });
428
- return result;
429
- }
430
- /**
431
- * Restore context after /clear
432
- */
433
- async restoreFromEnhancedContext() {
434
- const latestPath = path.join(
435
- this.projectRoot,
436
- ".stackmemory",
437
- "pre-clear",
438
- "latest-context.json"
439
- );
440
- try {
441
- const content = await fs.readFile(latestPath, "utf-8");
442
- const context = JSON.parse(content);
443
- console.log("\u{1F4DA} Restoring enhanced context...");
444
- console.log(` Session: ${context.sessionId}`);
445
- console.log(` Task: ${context.workingState.currentTask}`);
446
- console.log(` Files: ${context.workingState.activeFiles.length}`);
447
- console.log(` Focus: ${context.cognitiveState.currentFocus}`);
448
- await this.clearSurvival.restoreFromLedger();
449
- return true;
450
- } catch (error) {
451
- console.error("Failed to restore enhanced context:", error);
452
- return false;
453
- }
454
- }
455
- }
456
- export {
457
- EnhancedPreClearHooks
458
- };
@@ -1,115 +0,0 @@
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_GRAPHITI_CONFIG } from "./config.js";
6
- class GraphitiClientError extends Error {
7
- constructor(message, code, statusCode) {
8
- super(message);
9
- this.code = code;
10
- this.statusCode = statusCode;
11
- this.name = "GraphitiClientError";
12
- }
13
- }
14
- class GraphitiClient {
15
- endpoint;
16
- timeout;
17
- maxRetries;
18
- namespace;
19
- constructor(config = {}) {
20
- const merged = { ...DEFAULT_GRAPHITI_CONFIG, ...config };
21
- this.endpoint = merged.endpoint.replace(/\/$/, "");
22
- this.timeout = merged.timeoutMs;
23
- this.maxRetries = merged.maxRetries;
24
- this.namespace = merged.projectNamespace || "default";
25
- }
26
- async request(path, options = {}) {
27
- const controller = new AbortController();
28
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
29
- let lastError;
30
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
31
- try {
32
- const res = await fetch(`${this.endpoint}${path}`, {
33
- ...options,
34
- signal: controller.signal,
35
- headers: {
36
- "Content-Type": "application/json",
37
- ...options.headers || {}
38
- }
39
- });
40
- clearTimeout(timeoutId);
41
- if (!res.ok) {
42
- const msg = await res.text().catch(() => res.statusText);
43
- throw new GraphitiClientError(
44
- `Request failed: ${msg}`,
45
- "HTTP_ERROR",
46
- res.status
47
- );
48
- }
49
- return await res.json();
50
- } catch (err) {
51
- lastError = err;
52
- if (err instanceof GraphitiClientError) throw err;
53
- if (err.name === "AbortError") {
54
- throw new GraphitiClientError("Request timeout", "TIMEOUT");
55
- }
56
- if (attempt < this.maxRetries) {
57
- await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 100));
58
- continue;
59
- }
60
- }
61
- }
62
- clearTimeout(timeoutId);
63
- throw new GraphitiClientError(
64
- lastError?.message || "Network error",
65
- "NETWORK_ERROR"
66
- );
67
- }
68
- // Episodes
69
- async upsertEpisode(episode) {
70
- const payload = { ...episode, namespace: this.namespace };
71
- const res = await this.request(`/episodes`, {
72
- method: "POST",
73
- body: JSON.stringify(payload)
74
- });
75
- return res;
76
- }
77
- // Entities
78
- async upsertEntities(entities) {
79
- const payload = { entities, namespace: this.namespace };
80
- return this.request(`/entities:batchUpsert`, {
81
- method: "POST",
82
- body: JSON.stringify(payload)
83
- });
84
- }
85
- // Relations
86
- async upsertRelations(edges) {
87
- const payload = { edges, namespace: this.namespace };
88
- return this.request(`/relations:batchUpsert`, {
89
- method: "POST",
90
- body: JSON.stringify(payload)
91
- });
92
- }
93
- // Temporal query + hybrid retrieval
94
- async queryTemporal(query) {
95
- const payload = { ...query, namespace: this.namespace };
96
- return this.request(`/query/temporal`, {
97
- method: "POST",
98
- body: JSON.stringify(payload)
99
- });
100
- }
101
- // Health/status
102
- async getStatus() {
103
- try {
104
- return await this.request(
105
- `/status?namespace=${encodeURIComponent(this.namespace)}`
106
- );
107
- } catch {
108
- return { connected: false };
109
- }
110
- }
111
- }
112
- export {
113
- GraphitiClient,
114
- GraphitiClientError
115
- };
@@ -1,17 +0,0 @@
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_GRAPHITI_CONFIG = {
6
- enabled: !!process.env.GRAPHITI_ENDPOINT,
7
- endpoint: process.env.GRAPHITI_ENDPOINT?.replace(/\/$/, "") || "http://localhost:8080",
8
- backend: process.env.GRAPHITI_BACKEND || "neo4j",
9
- projectNamespace: process.env.STACKMEMORY_PROJECT_ID || "default",
10
- timeoutMs: 5e3,
11
- maxRetries: 2,
12
- maxTokens: 1600,
13
- maxHops: 2
14
- };
15
- export {
16
- DEFAULT_GRAPHITI_CONFIG
17
- };
@@ -1,115 +0,0 @@
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
- };