@syke1/mcp-server 1.4.16 → 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.
@@ -50,7 +52,8 @@ class FileCache extends events_1.EventEmitter {
50
52
  this.sourceDirs = [];
51
53
  this.watcher = null;
52
54
  this.debounceTimers = new Map();
53
- this.DEBOUNCE_MS = 300;
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) {
@@ -49,6 +49,8 @@ const analyze_impact_1 = require("../tools/analyze-impact");
49
49
  const analyzer_1 = require("../ai/analyzer");
50
50
  const realtime_analyzer_1 = require("../ai/realtime-analyzer");
51
51
  const config_1 = require("../config");
52
+ // ── Real-time AI analysis toggle ──
53
+ let realtimeAIEnabled = true;
52
54
  function resolveFilePath(fileArg, projectRoot, sourceDir) {
53
55
  const srcDir = sourceDir || path.join(projectRoot, "lib");
54
56
  const srcDirName = path.basename(srcDir); // "lib" or "src"
@@ -290,6 +292,23 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
290
292
  });
291
293
  // Wire FileCache change events → SSE broadcast + AI analysis
292
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
+ });
293
312
  cache.on("change", async (change) => {
294
313
  const graph = getGraphFn();
295
314
  const absPath = path.normalize(path.join(graph.sourceDir, change.relativePath));
@@ -318,8 +337,8 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
318
337
  connectedNodes,
319
338
  timestamp: change.timestamp,
320
339
  });
321
- // Run Gemini real-time analysis (Pro only)
322
- if (isProPlan()) {
340
+ // Run Gemini real-time analysis (Pro only, when toggle is on)
341
+ if (isProPlan() && realtimeAIEnabled) {
323
342
  broadcastSSE("analysis-start", { file: change.relativePath });
324
343
  try {
325
344
  const analysis = await (0, realtime_analyzer_1.analyzeChangeRealtime)(change, graph, (relPath) => currentFileCache?.getFileByRelPath(relPath) ?? null);
@@ -338,6 +357,13 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
338
357
  });
339
358
  }
340
359
  }
360
+ else if (isProPlan() && !realtimeAIEnabled) {
361
+ // Pro but AI toggle is off — log and skip AI, still handle structural changes
362
+ console.error(`[syke:ai] Real-time AI disabled — skipping analysis for ${change.relativePath}`);
363
+ if (change.type === "added" || change.type === "deleted") {
364
+ broadcastSSE("graph-rebuild", { reason: change.type, file: change.relativePath });
365
+ }
366
+ }
341
367
  else {
342
368
  // Free: still rebuild graph on structural changes, but skip AI
343
369
  if (change.type === "added" || change.type === "deleted") {
@@ -366,6 +392,18 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
366
392
  sseClients: sseClients.size,
367
393
  });
368
394
  });
395
+ // POST /api/toggle-realtime-ai — Enable or disable real-time AI analysis
396
+ app.post("/api/toggle-realtime-ai", (req, res) => {
397
+ const { enabled } = req.body;
398
+ if (typeof enabled === "boolean") {
399
+ realtimeAIEnabled = enabled;
400
+ }
401
+ else {
402
+ realtimeAIEnabled = !realtimeAIEnabled;
403
+ }
404
+ console.error(`[syke:ai] Real-time AI analysis ${realtimeAIEnabled ? "ENABLED" : "DISABLED"}`);
405
+ res.json({ realtimeAIEnabled });
406
+ });
369
407
  // GET /api/graph — Cytoscape.js compatible JSON
370
408
  app.get("/api/graph", (_req, res) => {
371
409
  const graph = getGraphFn();
@@ -446,7 +484,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
446
484
  res.json({ nodes, edges, totalFiles: graph.files.size, limited: !isPro && graph.files.size > FREE_GRAPH_LIMIT });
447
485
  });
448
486
  // GET /api/impact/:file — Impact analysis for a specific file
449
- app.get("/api/impact/*splat", (req, res) => {
487
+ app.get("/api/impact/*splat", async (req, res) => {
450
488
  const splat = req.params.splat;
451
489
  const fileParam = Array.isArray(splat) ? splat.join("/") : splat;
452
490
  if (!fileParam) {
@@ -457,7 +495,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
457
495
  if (!graph.files.has(resolved)) {
458
496
  return res.status(404).json({ error: `File not found in graph: ${fileParam}` });
459
497
  }
460
- const result = (0, analyze_impact_1.analyzeImpact)(resolved, graph);
498
+ const result = await (0, analyze_impact_1.analyzeImpact)(resolved, graph);
461
499
  res.json(result);
462
500
  });
463
501
  // POST /api/ai-analyze — AI semantic analysis (Pro or BYOK)
@@ -487,7 +525,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
487
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/" });
488
526
  }
489
527
  }
490
- const impactResult = (0, analyze_impact_1.analyzeImpact)(resolved, graph);
528
+ const impactResult = await (0, analyze_impact_1.analyzeImpact)(resolved, graph);
491
529
  try {
492
530
  const aiResult = await (0, analyzer_1.analyzeWithAI)(resolved, impactResult, graph);
493
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.16",
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",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "https://github.com/khalomsky/syke.git"
21
+ "url": "git+https://github.com/khalomsky/syke.git"
22
22
  },
23
23
  "keywords": [
24
24
  "mcp",