@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.
- package/dist/ai/realtime-analyzer.js +67 -2
- package/dist/git/change-coupling.d.ts +41 -0
- package/dist/git/change-coupling.js +250 -0
- package/dist/graph/incremental.d.ts +35 -0
- package/dist/graph/incremental.js +319 -0
- package/dist/graph/memo-cache.d.ts +47 -0
- package/dist/graph/memo-cache.js +176 -0
- package/dist/graph/scc.d.ts +57 -0
- package/dist/graph/scc.js +206 -0
- package/dist/graph.d.ts +6 -0
- package/dist/graph.js +17 -1
- package/dist/index.js +151 -11
- package/dist/scoring/pagerank.d.ts +67 -0
- package/dist/scoring/pagerank.js +221 -0
- package/dist/scoring/risk-scorer.d.ts +99 -0
- package/dist/scoring/risk-scorer.js +623 -0
- package/dist/tools/analyze-impact.d.ts +36 -1
- package/dist/tools/analyze-impact.js +278 -2
- package/dist/tools/gate-build.d.ts +7 -2
- package/dist/tools/gate-build.js +179 -13
- package/dist/watcher/file-cache.d.ts +9 -0
- package/dist/watcher/file-cache.js +41 -1
- package/dist/web/server.js +43 -5
- package/package.json +2 -2
|
@@ -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 =
|
|
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) {
|
package/dist/web/server.js
CHANGED
|
@@ -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.
|
|
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",
|