@syke1/mcp-server 1.8.3 → 1.8.5
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/README.md +10 -3
- package/dist/graph.d.ts +8 -0
- package/dist/graph.js +100 -0
- package/dist/index.js +8 -8
- package/dist/languages/cpp.js +8 -7
- package/dist/languages/dart.js +21 -15
- package/dist/languages/go.js +21 -15
- package/dist/languages/java.js +8 -7
- package/dist/languages/plugin.d.ts +2 -1
- package/dist/languages/plugin.js +36 -4
- package/dist/languages/python.js +8 -7
- package/dist/languages/ruby.js +8 -7
- package/dist/languages/rust.js +8 -7
- package/dist/languages/typescript.js +15 -7
- package/dist/watcher/file-cache.d.ts +8 -0
- package/dist/watcher/file-cache.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -256,13 +256,14 @@ You (developer) AI Agent SYKE
|
|
|
256
256
|
|
|
257
257
|
| | Free | Pro | Cortex |
|
|
258
258
|
|---|------|-----|--------|
|
|
259
|
-
| **Price** | $0 forever | $9/mo | $29/mo |
|
|
260
259
|
| **Files** | 200 | Unlimited | Unlimited |
|
|
261
|
-
| **Tools** | 3
|
|
260
|
+
| **Tools** | 3 core | 7 (+ advanced algorithms) | 9 (+ AI analysis) |
|
|
262
261
|
| **Projects** | 1 | Unlimited | Unlimited |
|
|
263
262
|
| **AI Analysis** | — | — | Gemini / OpenAI / Claude (BYOK) |
|
|
264
263
|
|
|
265
|
-
|
|
264
|
+
**[See current pricing →](https://syke.cloud/#pricing)**
|
|
265
|
+
|
|
266
|
+
Annual plans available. 14-day money-back guarantee.
|
|
266
267
|
|
|
267
268
|
## Founding 20 — Free Pro for Early Adopters
|
|
268
269
|
|
|
@@ -280,6 +281,12 @@ We're giving the **first 20 developers** full Pro access for **30 days** — no
|
|
|
280
281
|
|
|
281
282
|
Spots are limited. Once they're gone, they're gone.
|
|
282
283
|
|
|
284
|
+
## Source Code
|
|
285
|
+
|
|
286
|
+
This repository contains the **Free tier source code** — the core dependency graph engine, language plugins, and 3 free MCP tools (`gate_build`, `check_safe`, `get_dependencies`).
|
|
287
|
+
|
|
288
|
+
Pro and Cortex features (advanced algorithms, AI analysis, real-time monitoring, web dashboard) are included in the [npm package](https://www.npmjs.com/package/@syke1/mcp-server) as compiled code.
|
|
289
|
+
|
|
283
290
|
## License
|
|
284
291
|
|
|
285
292
|
[Elastic License 2.0 (ELv2)](LICENSE)
|
package/dist/graph.d.ts
CHANGED
|
@@ -12,5 +12,13 @@ export interface DependencyGraph {
|
|
|
12
12
|
scc?: SCCResult;
|
|
13
13
|
}
|
|
14
14
|
export declare function buildGraph(projectRoot: string, packageName?: string, maxFiles?: number): DependencyGraph;
|
|
15
|
+
/**
|
|
16
|
+
* Async graph builder — reads all files in parallel batches for faster startup.
|
|
17
|
+
* Returns the graph + contentMap (reusable by FileCache to avoid re-reading).
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildGraphAsync(projectRoot: string, packageName?: string, maxFiles?: number): Promise<{
|
|
20
|
+
graph: DependencyGraph;
|
|
21
|
+
contentMap: Map<string, string>;
|
|
22
|
+
}>;
|
|
15
23
|
export declare function getGraph(projectRoot: string, packageName?: string, maxFiles?: number): DependencyGraph;
|
|
16
24
|
export declare function rebuildGraph(projectRoot: string, packageName?: string, maxFiles?: number): DependencyGraph;
|
package/dist/graph.js
CHANGED
|
@@ -34,9 +34,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.buildGraph = buildGraph;
|
|
37
|
+
exports.buildGraphAsync = buildGraphAsync;
|
|
37
38
|
exports.getGraph = getGraph;
|
|
38
39
|
exports.rebuildGraph = rebuildGraph;
|
|
39
40
|
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs/promises"));
|
|
40
42
|
const plugin_1 = require("./languages/plugin");
|
|
41
43
|
const typescript_1 = require("./languages/typescript");
|
|
42
44
|
const scc_1 = require("./graph/scc");
|
|
@@ -109,6 +111,104 @@ function buildGraph(projectRoot, packageName, maxFiles) {
|
|
|
109
111
|
console.error(`[syke] Graph built (${languages.join("+")}): ${files.size} files, ${countEdges(forward)} edges, ${scc.components.length} SCCs (${cyclicCount} cyclic)`);
|
|
110
112
|
return graph;
|
|
111
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Async graph builder — reads all files in parallel batches for faster startup.
|
|
116
|
+
* Returns the graph + contentMap (reusable by FileCache to avoid re-reading).
|
|
117
|
+
*/
|
|
118
|
+
async function buildGraphAsync(projectRoot, packageName, maxFiles) {
|
|
119
|
+
const detectedPlugins = (0, plugin_1.detectLanguages)(projectRoot);
|
|
120
|
+
const languages = detectedPlugins.map(p => p.id);
|
|
121
|
+
const forward = new Map();
|
|
122
|
+
const reverse = new Map();
|
|
123
|
+
const files = new Set();
|
|
124
|
+
const allSourceDirs = [];
|
|
125
|
+
let totalDiscovered = 0;
|
|
126
|
+
let fileLimitHit = false;
|
|
127
|
+
// Phase 1: Discover files (sync — directory walking is I/O-light)
|
|
128
|
+
const pluginDirFiles = new Map();
|
|
129
|
+
for (const plugin of detectedPlugins) {
|
|
130
|
+
const dirs = plugin.getSourceDirs(projectRoot);
|
|
131
|
+
const dirMap = new Map();
|
|
132
|
+
for (const dir of dirs) {
|
|
133
|
+
if (!allSourceDirs.includes(dir))
|
|
134
|
+
allSourceDirs.push(dir);
|
|
135
|
+
const sourceFiles = plugin.discoverFiles(dir);
|
|
136
|
+
totalDiscovered += sourceFiles.length;
|
|
137
|
+
dirMap.set(dir, sourceFiles);
|
|
138
|
+
for (const f of sourceFiles) {
|
|
139
|
+
if (maxFiles && files.size >= maxFiles) {
|
|
140
|
+
fileLimitHit = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
files.add(f);
|
|
144
|
+
if (!forward.has(f))
|
|
145
|
+
forward.set(f, []);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
pluginDirFiles.set(plugin, dirMap);
|
|
149
|
+
}
|
|
150
|
+
if (fileLimitHit) {
|
|
151
|
+
console.error(`[syke] Free tier: loaded ${files.size}/${totalDiscovered} files (limit: ${maxFiles}). Upgrade to Pro for unlimited.`);
|
|
152
|
+
}
|
|
153
|
+
// Phase 2: Parallel file reading (biggest performance win)
|
|
154
|
+
const contentMap = new Map();
|
|
155
|
+
const filesToRead = [...files];
|
|
156
|
+
const BATCH_SIZE = 100;
|
|
157
|
+
for (let i = 0; i < filesToRead.length; i += BATCH_SIZE) {
|
|
158
|
+
const batch = filesToRead.slice(i, i + BATCH_SIZE);
|
|
159
|
+
const results = await Promise.all(batch.map(async (f) => {
|
|
160
|
+
try {
|
|
161
|
+
const content = await fs.readFile(f, "utf-8");
|
|
162
|
+
return [f, content];
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
for (const r of results) {
|
|
169
|
+
if (r)
|
|
170
|
+
contentMap.set(r[0], r[1]);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Phase 3: Parse imports using pre-read content (no I/O)
|
|
174
|
+
for (const [plugin, dirMap] of pluginDirFiles) {
|
|
175
|
+
for (const [dir, sourceFiles] of dirMap) {
|
|
176
|
+
for (const f of sourceFiles) {
|
|
177
|
+
if (!files.has(f))
|
|
178
|
+
continue;
|
|
179
|
+
const content = contentMap.get(f);
|
|
180
|
+
const imports = plugin.parseImports(f, projectRoot, dir, content);
|
|
181
|
+
const validImports = [];
|
|
182
|
+
for (const imp of imports) {
|
|
183
|
+
if (!files.has(imp))
|
|
184
|
+
continue;
|
|
185
|
+
validImports.push(imp);
|
|
186
|
+
const rev = reverse.get(imp) || [];
|
|
187
|
+
rev.push(f);
|
|
188
|
+
reverse.set(imp, rev);
|
|
189
|
+
}
|
|
190
|
+
forward.set(f, validImports);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const sourceDir = allSourceDirs[0] || path.join(projectRoot, "src");
|
|
195
|
+
const graph = {
|
|
196
|
+
forward,
|
|
197
|
+
reverse,
|
|
198
|
+
files,
|
|
199
|
+
projectRoot,
|
|
200
|
+
languages,
|
|
201
|
+
sourceDirs: allSourceDirs,
|
|
202
|
+
sourceDir,
|
|
203
|
+
};
|
|
204
|
+
(0, memo_cache_1.resetMemoCache)();
|
|
205
|
+
const scc = (0, scc_1.computeSCC)(graph);
|
|
206
|
+
graph.scc = scc;
|
|
207
|
+
const cyclicCount = scc.condensed.nodes.filter(n => n.isCyclic).length;
|
|
208
|
+
cachedGraph = graph;
|
|
209
|
+
console.error(`[syke] Graph built (${languages.join("+")}): ${files.size} files, ${countEdges(forward)} edges, ${scc.components.length} SCCs (${cyclicCount} cyclic)`);
|
|
210
|
+
return { graph, contentMap };
|
|
211
|
+
}
|
|
112
212
|
function countEdges(forward) {
|
|
113
213
|
let count = 0;
|
|
114
214
|
for (const deps of forward.values()) {
|
package/dist/index.js
CHANGED
|
@@ -147,7 +147,7 @@ async function main() {
|
|
|
147
147
|
};
|
|
148
148
|
process.on("SIGINT", shutdown);
|
|
149
149
|
process.on("SIGTERM", shutdown);
|
|
150
|
-
const server = new index_js_1.Server({ name: "syke", version: "1.8.
|
|
150
|
+
const server = new index_js_1.Server({ name: "syke", version: "1.8.5" }, { capabilities: { tools: {} } });
|
|
151
151
|
// List tools
|
|
152
152
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
153
153
|
tools: [
|
|
@@ -628,7 +628,7 @@ async function main() {
|
|
|
628
628
|
}
|
|
629
629
|
});
|
|
630
630
|
// Pre-warm the graph (skip if no project root — e.g. Smithery scan)
|
|
631
|
-
console.error(`[syke] Starting SYKE MCP Server v1.8.
|
|
631
|
+
console.error(`[syke] Starting SYKE MCP Server v1.8.5`);
|
|
632
632
|
console.error(`[syke] License: ${licenseStatus.plan.toUpperCase()} (${licenseStatus.source})`);
|
|
633
633
|
if (licenseStatus.expiresAt) {
|
|
634
634
|
console.error(`[syke] Expires: ${licenseStatus.expiresAt}`);
|
|
@@ -656,9 +656,9 @@ async function main() {
|
|
|
656
656
|
});
|
|
657
657
|
const data = await res.json();
|
|
658
658
|
const latest = data["dist-tags"]?.latest;
|
|
659
|
-
if (latest && latest !== "1.8.
|
|
659
|
+
if (latest && latest !== "1.8.5") {
|
|
660
660
|
const [lM, lm, lp] = latest.split(".").map(Number);
|
|
661
|
-
const [cM, cm, cp] = [1, 8,
|
|
661
|
+
const [cM, cm, cp] = [1, 8, 5];
|
|
662
662
|
if (lM > cM || (lM === cM && lm > cm) || (lM === cM && lm === cm && lp > cp)) {
|
|
663
663
|
console.error(`[syke] Update available: v${latest}. Run: npx @syke1/mcp-server@latest`);
|
|
664
664
|
}
|
|
@@ -672,10 +672,10 @@ async function main() {
|
|
|
672
672
|
console.error(`[syke] Project root: ${currentProjectRoot}`);
|
|
673
673
|
console.error(`[syke] Detected languages: ${detectedLangs}`);
|
|
674
674
|
console.error(`[syke] Package name: ${currentPackageName}`);
|
|
675
|
-
const graph = (0, graph_1.
|
|
676
|
-
// Initialize file cache
|
|
675
|
+
const { graph, contentMap } = await (0, graph_1.buildGraphAsync)(currentProjectRoot, currentPackageName, getMaxFiles());
|
|
676
|
+
// Initialize file cache from pre-read content (no re-reading files)
|
|
677
677
|
fileCache = new file_cache_1.FileCache(currentProjectRoot);
|
|
678
|
-
fileCache.
|
|
678
|
+
fileCache.initializeFromContentMap(contentMap);
|
|
679
679
|
fileCache.setGraph(graph); // Enable incremental graph updates on file changes
|
|
680
680
|
fileCache.startWatching();
|
|
681
681
|
}
|
|
@@ -816,7 +816,7 @@ main().catch((err) => {
|
|
|
816
816
|
* See: https://smithery.ai/docs/deploy#sandbox-server
|
|
817
817
|
*/
|
|
818
818
|
function createSandboxServer() {
|
|
819
|
-
const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.8.
|
|
819
|
+
const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.8.5" }, { capabilities: { tools: {} } });
|
|
820
820
|
sandboxServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
821
821
|
tools: [
|
|
822
822
|
{
|
package/dist/languages/cpp.js
CHANGED
|
@@ -73,13 +73,14 @@ exports.cppPlugin = {
|
|
|
73
73
|
discoverFiles(dir) {
|
|
74
74
|
return (0, plugin_1.discoverAllFiles)(dir, [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx"]);
|
|
75
75
|
},
|
|
76
|
-
parseImports(filePath, projectRoot, sourceDir) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
parseImports(filePath, projectRoot, sourceDir, content) {
|
|
77
|
+
if (!content) {
|
|
78
|
+
try {
|
|
79
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
83
84
|
}
|
|
84
85
|
const fileDir = path.dirname(filePath);
|
|
85
86
|
const imports = [];
|
package/dist/languages/dart.js
CHANGED
|
@@ -38,6 +38,7 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const plugin_1 = require("./plugin");
|
|
40
40
|
const IMPORT_RE = /^import\s+['"](.+?)['"]/;
|
|
41
|
+
const dartPackageNameCache = new Map();
|
|
41
42
|
exports.dartPlugin = {
|
|
42
43
|
id: "dart",
|
|
43
44
|
name: "Dart",
|
|
@@ -63,25 +64,30 @@ exports.dartPlugin = {
|
|
|
63
64
|
discoverFiles(dir) {
|
|
64
65
|
return (0, plugin_1.discoverAllFiles)(dir, [".dart"]);
|
|
65
66
|
},
|
|
66
|
-
parseImports(filePath, projectRoot, sourceDir) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
parseImports(filePath, projectRoot, sourceDir, content) {
|
|
68
|
+
if (!content) {
|
|
69
|
+
try {
|
|
70
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
73
75
|
}
|
|
74
76
|
const libDir = sourceDir;
|
|
75
77
|
const imports = [];
|
|
76
|
-
// Read package name
|
|
77
|
-
let packageName =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
// Read package name (cached per projectRoot)
|
|
79
|
+
let packageName = dartPackageNameCache.get(projectRoot);
|
|
80
|
+
if (!packageName) {
|
|
81
|
+
packageName = path.basename(projectRoot);
|
|
82
|
+
try {
|
|
83
|
+
const pubspec = fs.readFileSync(path.join(projectRoot, "pubspec.yaml"), "utf-8");
|
|
84
|
+
const match = pubspec.match(/^name:\s*(\S+)/m);
|
|
85
|
+
if (match)
|
|
86
|
+
packageName = match[1];
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
dartPackageNameCache.set(projectRoot, packageName);
|
|
83
90
|
}
|
|
84
|
-
catch { }
|
|
85
91
|
for (const line of content.split("\n")) {
|
|
86
92
|
const trimmed = line.trim();
|
|
87
93
|
if (trimmed.length > 0 &&
|
package/dist/languages/go.js
CHANGED
|
@@ -38,6 +38,7 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const plugin_1 = require("./plugin");
|
|
40
40
|
const IMPORT_LINE_RE = /^\s*"([^"]+)"/;
|
|
41
|
+
const goModuleCache = new Map();
|
|
41
42
|
exports.goPlugin = {
|
|
42
43
|
id: "go",
|
|
43
44
|
name: "Go",
|
|
@@ -62,23 +63,28 @@ exports.goPlugin = {
|
|
|
62
63
|
discoverFiles(dir) {
|
|
63
64
|
return (0, plugin_1.discoverAllFiles)(dir, [".go"]).filter(f => !f.endsWith("_test.go"));
|
|
64
65
|
},
|
|
65
|
-
parseImports(filePath, projectRoot, _sourceDir) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
parseImports(filePath, projectRoot, _sourceDir, content) {
|
|
67
|
+
if (!content) {
|
|
68
|
+
try {
|
|
69
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
72
74
|
}
|
|
73
|
-
// Get module prefix from go.mod
|
|
74
|
-
let modulePrefix =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
// Get module prefix from go.mod (cached per projectRoot)
|
|
76
|
+
let modulePrefix = goModuleCache.get(projectRoot);
|
|
77
|
+
if (modulePrefix === undefined) {
|
|
78
|
+
modulePrefix = "";
|
|
79
|
+
try {
|
|
80
|
+
const goMod = fs.readFileSync(path.join(projectRoot, "go.mod"), "utf-8");
|
|
81
|
+
const match = goMod.match(/^module\s+(\S+)/m);
|
|
82
|
+
if (match)
|
|
83
|
+
modulePrefix = match[1];
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
goModuleCache.set(projectRoot, modulePrefix);
|
|
80
87
|
}
|
|
81
|
-
catch { }
|
|
82
88
|
const imports = [];
|
|
83
89
|
// Parse import block or single imports
|
|
84
90
|
const importBlockMatch = content.match(/import\s*\(([\s\S]*?)\)/);
|
package/dist/languages/java.js
CHANGED
|
@@ -73,13 +73,14 @@ exports.javaPlugin = {
|
|
|
73
73
|
discoverFiles(dir) {
|
|
74
74
|
return (0, plugin_1.discoverAllFiles)(dir, [".java"]);
|
|
75
75
|
},
|
|
76
|
-
parseImports(filePath, _projectRoot, sourceDir) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
parseImports(filePath, _projectRoot, sourceDir, content) {
|
|
77
|
+
if (!content) {
|
|
78
|
+
try {
|
|
79
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
83
84
|
}
|
|
84
85
|
const imports = [];
|
|
85
86
|
for (const line of content.split("\n")) {
|
|
@@ -7,7 +7,7 @@ export interface LanguagePlugin {
|
|
|
7
7
|
getSourceDirs(root: string): string[];
|
|
8
8
|
getPackageName(root: string): string;
|
|
9
9
|
discoverFiles(dir: string): string[];
|
|
10
|
-
parseImports(filePath: string, projectRoot: string, sourceDir: string): string[];
|
|
10
|
+
parseImports(filePath: string, projectRoot: string, sourceDir: string, content?: string): string[];
|
|
11
11
|
classifyLayer?(relPath: string): string | null;
|
|
12
12
|
}
|
|
13
13
|
export declare function registerPlugin(plugin: LanguagePlugin): void;
|
|
@@ -15,6 +15,7 @@ export declare function getPlugins(): LanguagePlugin[];
|
|
|
15
15
|
export declare function getPluginById(id: string): LanguagePlugin | undefined;
|
|
16
16
|
export declare function getPluginForFile(filePath: string): LanguagePlugin | undefined;
|
|
17
17
|
export declare function detectLanguages(root: string): LanguagePlugin[];
|
|
18
|
+
export declare function clearDetectCache(): void;
|
|
18
19
|
export declare function detectProjectRoot(startDir?: string): string;
|
|
19
20
|
export declare function detectPackageName(root: string, detectedPlugins: LanguagePlugin[]): string;
|
|
20
21
|
export declare function discoverAllFiles(rootDir: string, extensions: string[], extraSkipDirs?: string[]): string[];
|
package/dist/languages/plugin.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.getPlugins = getPlugins;
|
|
|
38
38
|
exports.getPluginById = getPluginById;
|
|
39
39
|
exports.getPluginForFile = getPluginForFile;
|
|
40
40
|
exports.detectLanguages = detectLanguages;
|
|
41
|
+
exports.clearDetectCache = clearDetectCache;
|
|
41
42
|
exports.detectProjectRoot = detectProjectRoot;
|
|
42
43
|
exports.detectPackageName = detectPackageName;
|
|
43
44
|
exports.discoverAllFiles = discoverAllFiles;
|
|
@@ -60,9 +61,17 @@ function getPluginForFile(filePath) {
|
|
|
60
61
|
const ext = path.extname(filePath).toLowerCase();
|
|
61
62
|
return plugins.find(p => p.extensions.includes(ext));
|
|
62
63
|
}
|
|
63
|
-
// ── Auto-detect ──
|
|
64
|
+
// ── Auto-detect (cached) ──
|
|
65
|
+
let detectCache = null;
|
|
64
66
|
function detectLanguages(root) {
|
|
65
|
-
|
|
67
|
+
if (detectCache && detectCache.root === root)
|
|
68
|
+
return detectCache.result;
|
|
69
|
+
const result = plugins.filter(p => p.detectProject(root));
|
|
70
|
+
detectCache = { root, result };
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
function clearDetectCache() {
|
|
74
|
+
detectCache = null;
|
|
66
75
|
}
|
|
67
76
|
function detectProjectRoot(startDir) {
|
|
68
77
|
let dir = startDir || process.cwd();
|
|
@@ -156,6 +165,29 @@ function hasManifestFile(root, manifests) {
|
|
|
156
165
|
catch { }
|
|
157
166
|
return false;
|
|
158
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Quick check: does a directory (recursively) contain any file with matching extensions?
|
|
170
|
+
* Short-circuits on the first match — much faster than collecting all files.
|
|
171
|
+
*/
|
|
172
|
+
function hasAnySourceFile(dir, extensions, skipSet) {
|
|
173
|
+
try {
|
|
174
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Check subdirectories only if no file found at this level
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
if (entry.isDirectory() && !skipSet.has(entry.name) && !entry.name.startsWith(".")) {
|
|
183
|
+
if (hasAnySourceFile(path.join(dir, entry.name), extensions, skipSet))
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
159
191
|
/**
|
|
160
192
|
* Find first-level subdirectories (and optionally root) that contain files with given extensions.
|
|
161
193
|
* Skips hidden dirs and common non-source dirs (.venv, node_modules, etc.).
|
|
@@ -168,14 +200,14 @@ function findSourceDirsWithFiles(root, extensions) {
|
|
|
168
200
|
if (entries.some(e => e.isFile() && extensions.some(ext => e.name.endsWith(ext)))) {
|
|
169
201
|
dirs.push(root);
|
|
170
202
|
}
|
|
171
|
-
// Check first-level subdirectories
|
|
203
|
+
// Check first-level subdirectories (quick existence check, not full walk)
|
|
172
204
|
for (const entry of entries) {
|
|
173
205
|
if (!entry.isDirectory())
|
|
174
206
|
continue;
|
|
175
207
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith("."))
|
|
176
208
|
continue;
|
|
177
209
|
const subdir = path.join(root, entry.name);
|
|
178
|
-
if (
|
|
210
|
+
if (hasAnySourceFile(subdir, extensions, SKIP_DIRS)) {
|
|
179
211
|
dirs.push(subdir);
|
|
180
212
|
}
|
|
181
213
|
}
|
package/dist/languages/python.js
CHANGED
|
@@ -64,13 +64,14 @@ exports.pythonPlugin = {
|
|
|
64
64
|
discoverFiles(dir) {
|
|
65
65
|
return (0, plugin_1.discoverAllFiles)(dir, [".py"]);
|
|
66
66
|
},
|
|
67
|
-
parseImports(filePath, projectRoot, sourceDir) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
parseImports(filePath, projectRoot, sourceDir, content) {
|
|
68
|
+
if (!content) {
|
|
69
|
+
try {
|
|
70
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
74
75
|
}
|
|
75
76
|
const imports = [];
|
|
76
77
|
for (const line of content.split("\n")) {
|
package/dist/languages/ruby.js
CHANGED
|
@@ -69,13 +69,14 @@ exports.rubyPlugin = {
|
|
|
69
69
|
discoverFiles(dir) {
|
|
70
70
|
return (0, plugin_1.discoverAllFiles)(dir, [".rb"]);
|
|
71
71
|
},
|
|
72
|
-
parseImports(filePath, _projectRoot, _sourceDir) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
parseImports(filePath, _projectRoot, _sourceDir, content) {
|
|
73
|
+
if (!content) {
|
|
74
|
+
try {
|
|
75
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
79
80
|
}
|
|
80
81
|
const fileDir = path.dirname(filePath);
|
|
81
82
|
const imports = [];
|
package/dist/languages/rust.js
CHANGED
|
@@ -64,13 +64,14 @@ exports.rustPlugin = {
|
|
|
64
64
|
discoverFiles(dir) {
|
|
65
65
|
return (0, plugin_1.discoverAllFiles)(dir, [".rs"]);
|
|
66
66
|
},
|
|
67
|
-
parseImports(filePath, _projectRoot, sourceDir) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
parseImports(filePath, _projectRoot, sourceDir, content) {
|
|
68
|
+
if (!content) {
|
|
69
|
+
try {
|
|
70
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
74
75
|
}
|
|
75
76
|
const imports = [];
|
|
76
77
|
for (const line of content.split("\n")) {
|
|
@@ -142,18 +142,26 @@ exports.typescriptPlugin = {
|
|
|
142
142
|
discoverFiles(dir) {
|
|
143
143
|
return (0, plugin_1.discoverAllFiles)(dir, [".ts", ".tsx", ".js", ".jsx"]).filter(f => !f.endsWith(".d.ts"));
|
|
144
144
|
},
|
|
145
|
-
parseImports(filePath, projectRoot, _sourceDir) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
parseImports(filePath, projectRoot, _sourceDir, content) {
|
|
146
|
+
if (!content) {
|
|
147
|
+
try {
|
|
148
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
152
153
|
}
|
|
153
154
|
const fileDir = path.dirname(filePath);
|
|
154
155
|
const imports = [];
|
|
155
156
|
for (const line of content.split("\n")) {
|
|
156
157
|
const trimmed = line.trim();
|
|
158
|
+
// Quick prefix guard — skip lines that can't be imports
|
|
159
|
+
if (trimmed.length === 0 || !(trimmed.charCodeAt(0) === 105 /* i */ ||
|
|
160
|
+
trimmed.charCodeAt(0) === 101 /* e */ ||
|
|
161
|
+
trimmed.charCodeAt(0) === 99 /* c */ ||
|
|
162
|
+
trimmed.charCodeAt(0) === 108 /* l */ ||
|
|
163
|
+
trimmed.charCodeAt(0) === 118 /* v */))
|
|
164
|
+
continue;
|
|
157
165
|
let importPath = null;
|
|
158
166
|
const match = trimmed.match(TS_IMPORT_RE) || trimmed.match(TS_SIDE_EFFECT_RE) || trimmed.match(JS_REQUIRE_RE);
|
|
159
167
|
if (match)
|
|
@@ -44,6 +44,14 @@ export declare class FileCache extends EventEmitter {
|
|
|
44
44
|
fileCount: number;
|
|
45
45
|
totalLines: number;
|
|
46
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Initialize from a pre-read content map (avoids re-reading files after graph build).
|
|
49
|
+
* Used when buildGraphAsync already read all files into memory.
|
|
50
|
+
*/
|
|
51
|
+
initializeFromContentMap(contentMap: Map<string, string>): {
|
|
52
|
+
fileCount: number;
|
|
53
|
+
totalLines: number;
|
|
54
|
+
};
|
|
47
55
|
/** Start watching source directories for changes */
|
|
48
56
|
startWatching(): void;
|
|
49
57
|
private isWatchedFile;
|
|
@@ -106,6 +106,19 @@ class FileCache extends events_1.EventEmitter {
|
|
|
106
106
|
console.error(`[syke:cache] Loaded ${this.cache.size} files (${totalLines.toLocaleString()} lines) into memory`);
|
|
107
107
|
return { fileCount: this.cache.size, totalLines };
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Initialize from a pre-read content map (avoids re-reading files after graph build).
|
|
111
|
+
* Used when buildGraphAsync already read all files into memory.
|
|
112
|
+
*/
|
|
113
|
+
initializeFromContentMap(contentMap) {
|
|
114
|
+
let totalLines = 0;
|
|
115
|
+
for (const [filePath, content] of contentMap) {
|
|
116
|
+
this.cache.set(path.normalize(filePath), content);
|
|
117
|
+
totalLines += content.split("\n").length;
|
|
118
|
+
}
|
|
119
|
+
console.error(`[syke:cache] Loaded ${this.cache.size} files (${totalLines.toLocaleString()} lines) from graph build (no re-read)`);
|
|
120
|
+
return { fileCount: this.cache.size, totalLines };
|
|
121
|
+
}
|
|
109
122
|
/** Start watching source directories for changes */
|
|
110
123
|
startWatching() {
|
|
111
124
|
if (this.watcher)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syke1/mcp-server",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.5",
|
|
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",
|