@syke1/mcp-server 1.4.17 → 1.4.18

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.
@@ -38,6 +38,8 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const events_1 = require("events");
40
40
  const plugin_1 = require("../languages/plugin");
41
+ const incremental_1 = require("../graph/incremental");
42
+ const memo_cache_1 = require("../graph/memo-cache");
41
43
  /**
42
44
  * FileCache: Holds ALL source files in memory.
43
45
  * Emits "change" events when files are modified on disk.
@@ -51,6 +53,7 @@ class FileCache extends events_1.EventEmitter {
51
53
  this.watcher = null;
52
54
  this.debounceTimers = new Map();
53
55
  this.DEBOUNCE_MS = 1500;
56
+ this.graph = null;
54
57
  this.plugins = (0, plugin_1.detectLanguages)(projectRoot);
55
58
  // Collect all extensions from detected plugins
56
59
  const allExts = new Set();
@@ -73,6 +76,15 @@ class FileCache extends events_1.EventEmitter {
73
76
  get sourceDir() {
74
77
  return this.sourceDirs[0] || path.join(this.projectRoot, "src");
75
78
  }
79
+ /**
80
+ * Set the dependency graph reference for incremental updates.
81
+ * When a graph is set, file changes will trigger incremental
82
+ * edge updates and memo cache invalidation instead of requiring
83
+ * a full graph rebuild.
84
+ */
85
+ setGraph(graph) {
86
+ this.graph = graph;
87
+ }
76
88
  /** Load ALL source files into memory on startup */
77
89
  initialize() {
78
90
  let totalLines = 0;
@@ -173,6 +185,34 @@ class FileCache extends events_1.EventEmitter {
173
185
  };
174
186
  console.error(`[syke:cache] ${type.toUpperCase()}: ${relPath} (${diff.length} changes)`);
175
187
  this.emit("change", change);
188
+ // Incremental graph update (if graph is available)
189
+ if (this.graph) {
190
+ try {
191
+ let result;
192
+ if (type === "modified") {
193
+ result = (0, incremental_1.updateGraphForFile)(this.graph, absPath, this.projectRoot);
194
+ }
195
+ else if (type === "added") {
196
+ result = (0, incremental_1.addFileToGraph)(this.graph, absPath, this.projectRoot);
197
+ }
198
+ else {
199
+ // deleted
200
+ result = (0, incremental_1.removeFileFromGraph)(this.graph, absPath);
201
+ }
202
+ // Invalidate memo cache for affected files
203
+ if (result.edgesChanged && result.affectedFiles.length > 0) {
204
+ const invalidated = (0, memo_cache_1.getMemoCache)().invalidate(result.affectedFiles);
205
+ console.error(`[syke:incremental] ${type}: ${relPath} — ` +
206
+ `+${result.addedEdges.length}/-${result.removedEdges.length} edges, ` +
207
+ `${result.affectedFiles.length} affected, ${invalidated} cache entries invalidated`);
208
+ }
209
+ // Emit graph-updated event for downstream consumers (e.g., SSE broadcast)
210
+ this.emit("graph-updated", result);
211
+ }
212
+ catch (err) {
213
+ console.error(`[syke:incremental] Error updating graph for ${relPath}: ${err.message}`);
214
+ }
215
+ }
176
216
  }
177
217
  /** Simple line-by-line diff */
178
218
  computeDiff(oldContent, newContent) {
@@ -292,6 +292,23 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
292
292
  });
293
293
  // Wire FileCache change events → SSE broadcast + AI analysis
294
294
  function wireFileCacheEvents(cache) {
295
+ // Give the FileCache a reference to the current graph for incremental updates
296
+ const graph = getGraphFn();
297
+ cache.setGraph(graph);
298
+ // Listen for incremental graph updates (emitted after file changes update edges)
299
+ cache.on("graph-updated", (result) => {
300
+ if (result.edgesChanged) {
301
+ console.error(`[syke:sse] Graph incrementally updated: ${result.updatedFile} ` +
302
+ `(+${result.addedEdges.length}/-${result.removedEdges.length} edges, ` +
303
+ `${result.affectedFiles.length} affected files)`);
304
+ broadcastSSE("graph-incremental-update", {
305
+ file: path.relative(getGraphFn().sourceDir, result.updatedFile).replace(/\\/g, "/"),
306
+ addedEdges: result.addedEdges.length,
307
+ removedEdges: result.removedEdges.length,
308
+ affectedFiles: result.affectedFiles.length,
309
+ });
310
+ }
311
+ });
295
312
  cache.on("change", async (change) => {
296
313
  const graph = getGraphFn();
297
314
  const absPath = path.normalize(path.join(graph.sourceDir, change.relativePath));
@@ -467,7 +484,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
467
484
  res.json({ nodes, edges, totalFiles: graph.files.size, limited: !isPro && graph.files.size > FREE_GRAPH_LIMIT });
468
485
  });
469
486
  // GET /api/impact/:file — Impact analysis for a specific file
470
- app.get("/api/impact/*splat", (req, res) => {
487
+ app.get("/api/impact/*splat", async (req, res) => {
471
488
  const splat = req.params.splat;
472
489
  const fileParam = Array.isArray(splat) ? splat.join("/") : splat;
473
490
  if (!fileParam) {
@@ -478,7 +495,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
478
495
  if (!graph.files.has(resolved)) {
479
496
  return res.status(404).json({ error: `File not found in graph: ${fileParam}` });
480
497
  }
481
- const result = (0, analyze_impact_1.analyzeImpact)(resolved, graph);
498
+ const result = await (0, analyze_impact_1.analyzeImpact)(resolved, graph);
482
499
  res.json(result);
483
500
  });
484
501
  // POST /api/ai-analyze — AI semantic analysis (Pro or BYOK)
@@ -508,7 +525,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
508
525
  return res.status(403).json({ error: "This file exceeds the Free tier limit (50 files). Upgrade to Pro for unlimited analysis.", upgrade: "https://syke.cloud/dashboard/" });
509
526
  }
510
527
  }
511
- const impactResult = (0, analyze_impact_1.analyzeImpact)(resolved, graph);
528
+ const impactResult = await (0, analyze_impact_1.analyzeImpact)(resolved, graph);
512
529
  try {
513
530
  const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
514
531
  const partial = !isPro && graph.files.size > 50;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syke1/mcp-server",
3
- "version": "1.4.17",
3
+ "version": "1.4.18",
4
4
  "mcpName": "io.github.khalomsky/syke",
5
5
  "description": "AI code impact analysis MCP server — dependency graphs, cascade detection, and a mandatory build gate for AI coding agents",
6
6
  "main": "dist/index.js",