@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 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 (gate_build, check_safe, get_dependencies) | 7 (+ analyze_impact, hub_files, refresh, warnings) | 9 (+ ai_analyze, scan_project) |
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
- Annual plans: Pro $89/year, Cortex $249/year.
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.3" }, { capabilities: { tools: {} } });
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.3`);
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.3") {
659
+ if (latest && latest !== "1.8.5") {
660
660
  const [lM, lm, lp] = latest.split(".").map(Number);
661
- const [cM, cm, cp] = [1, 8, 1];
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.getGraph)(currentProjectRoot, currentPackageName, getMaxFiles());
676
- // Initialize file cache (load ALL source files into memory)
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.initialize();
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.3" }, { capabilities: { tools: {} } });
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
  {
@@ -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
- let content;
78
- try {
79
- content = fs.readFileSync(filePath, "utf-8");
80
- }
81
- catch {
82
- return [];
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 = [];
@@ -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
- let content;
68
- try {
69
- content = fs.readFileSync(filePath, "utf-8");
70
- }
71
- catch {
72
- return [];
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 for resolving package: imports
77
- let packageName = path.basename(projectRoot);
78
- try {
79
- const pubspec = fs.readFileSync(path.join(projectRoot, "pubspec.yaml"), "utf-8");
80
- const match = pubspec.match(/^name:\s*(\S+)/m);
81
- if (match)
82
- packageName = match[1];
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 &&
@@ -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
- let content;
67
- try {
68
- content = fs.readFileSync(filePath, "utf-8");
69
- }
70
- catch {
71
- return [];
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
- try {
76
- const goMod = fs.readFileSync(path.join(projectRoot, "go.mod"), "utf-8");
77
- const match = goMod.match(/^module\s+(\S+)/m);
78
- if (match)
79
- modulePrefix = match[1];
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]*?)\)/);
@@ -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
- let content;
78
- try {
79
- content = fs.readFileSync(filePath, "utf-8");
80
- }
81
- catch {
82
- return [];
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[];
@@ -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
- return plugins.filter(p => p.detectProject(root));
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 (discoverAllFiles(subdir, extensions).length > 0) {
210
+ if (hasAnySourceFile(subdir, extensions, SKIP_DIRS)) {
179
211
  dirs.push(subdir);
180
212
  }
181
213
  }
@@ -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
- let content;
69
- try {
70
- content = fs.readFileSync(filePath, "utf-8");
71
- }
72
- catch {
73
- return [];
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")) {
@@ -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
- let content;
74
- try {
75
- content = fs.readFileSync(filePath, "utf-8");
76
- }
77
- catch {
78
- return [];
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 = [];
@@ -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
- let content;
69
- try {
70
- content = fs.readFileSync(filePath, "utf-8");
71
- }
72
- catch {
73
- return [];
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
- let content;
147
- try {
148
- content = fs.readFileSync(filePath, "utf-8");
149
- }
150
- catch {
151
- return [];
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",
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",