@syke1/mcp-server 1.4.15 → 1.4.17
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/analyzer.js +4 -46
- package/dist/ai/realtime-analyzer.js +79 -69
- package/dist/config.d.ts +0 -5
- package/dist/config.js +0 -17
- package/dist/index.js +4 -4
- package/dist/watcher/file-cache.js +1 -1
- package/dist/web/server.js +23 -2
- package/package.json +2 -2
package/dist/ai/analyzer.js
CHANGED
|
@@ -38,7 +38,6 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const provider_1 = require("./provider");
|
|
40
40
|
const context_extractor_1 = require("./context-extractor");
|
|
41
|
-
const config_1 = require("../config");
|
|
42
41
|
function readFileContent(filePath) {
|
|
43
42
|
try {
|
|
44
43
|
return fs.readFileSync(filePath, "utf-8");
|
|
@@ -49,27 +48,6 @@ function readFileContent(filePath) {
|
|
|
49
48
|
}
|
|
50
49
|
function buildSystemPrompt(languages) {
|
|
51
50
|
const langNames = languages.length > 0 ? languages.join("/") : "source";
|
|
52
|
-
const ko = (0, config_1.getLanguage)() === "ko";
|
|
53
|
-
if (ko) {
|
|
54
|
-
return `당신은 ${langNames} 코드 영향도 분석 전문가입니다.
|
|
55
|
-
주어진 파일의 소스코드와 이 파일에 의존하는 파일들의 코드를 분석하여,
|
|
56
|
-
이 파일을 수정할 때 어떤 부분이 깨질 수 있는지 구체적으로 설명해주세요.
|
|
57
|
-
|
|
58
|
-
분석 포맷:
|
|
59
|
-
## 핵심 역할
|
|
60
|
-
이 파일이 프로젝트에서 하는 역할을 한 문장으로 설명
|
|
61
|
-
|
|
62
|
-
## 수정 시 위험 포인트
|
|
63
|
-
수정 시 깨질 수 있는 구체적인 부분들 (함수명, 클래스명 포함)
|
|
64
|
-
|
|
65
|
-
## 영향받는 파일 분석
|
|
66
|
-
의존 파일들이 이 파일의 어떤 부분을 사용하는지 구체적으로
|
|
67
|
-
|
|
68
|
-
## 안전한 수정 가이드
|
|
69
|
-
이 파일을 수정할 때 주의할 점과 추천 접근법
|
|
70
|
-
|
|
71
|
-
한국어로 답변하세요. 간결하되 구체적으로 작성하세요.`;
|
|
72
|
-
}
|
|
73
51
|
return `You are an expert in ${langNames} code impact analysis.
|
|
74
52
|
Analyze the source code of the given file and its dependents to identify
|
|
75
53
|
what could break when this file is modified.
|
|
@@ -91,17 +69,12 @@ Be concise but specific.`;
|
|
|
91
69
|
}
|
|
92
70
|
async function analyzeWithAI(filePath, impactResult, graph) {
|
|
93
71
|
const provider = (0, provider_1.getAIProvider)();
|
|
94
|
-
const ko = (0, config_1.getLanguage)() === "ko";
|
|
95
72
|
if (!provider) {
|
|
96
|
-
return
|
|
97
|
-
? "AI 분석 비활성화 — GEMINI_KEY, OPENAI_KEY, 또는 ANTHROPIC_KEY를 설정하세요."
|
|
98
|
-
: "AI analysis disabled — set GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY.";
|
|
73
|
+
return "AI analysis disabled — set GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY.";
|
|
99
74
|
}
|
|
100
75
|
const targetSource = readFileContent(filePath);
|
|
101
76
|
if (!targetSource) {
|
|
102
|
-
return
|
|
103
|
-
? `파일을 읽을 수 없습니다: ${filePath}`
|
|
104
|
-
: `Cannot read file: ${filePath}`;
|
|
77
|
+
return `Cannot read file: ${filePath}`;
|
|
105
78
|
}
|
|
106
79
|
const codeBlockLang = graph.languages[0] || "text";
|
|
107
80
|
// Build smart context for the target file
|
|
@@ -117,20 +90,7 @@ async function analyzeWithAI(filePath, impactResult, graph) {
|
|
|
117
90
|
dependentSources.push(`### ${rel}\n\`\`\`${codeBlockLang}\n${smartDep}\n\`\`\``);
|
|
118
91
|
}
|
|
119
92
|
}
|
|
120
|
-
const userPrompt =
|
|
121
|
-
? `## 분석 대상 파일: ${impactResult.relativePath}
|
|
122
|
-
- 위험도: ${impactResult.riskLevel}
|
|
123
|
-
- 직접 의존 파일 수: ${impactResult.directDependents.length}
|
|
124
|
-
- 전이적 의존 파일 수: ${impactResult.transitiveDependents.length}
|
|
125
|
-
- 총 영향 파일 수: ${impactResult.totalImpacted}
|
|
126
|
-
|
|
127
|
-
### 대상 파일 소스코드
|
|
128
|
-
\`\`\`${codeBlockLang}
|
|
129
|
-
${smartTarget}
|
|
130
|
-
\`\`\`
|
|
131
|
-
|
|
132
|
-
${dependentSources.length > 0 ? `### 이 파일에 의존하는 파일들 (상위 ${dependentSources.length}개)\n${dependentSources.join("\n\n")}` : "이 파일에 의존하는 내부 파일이 없습니다."}`
|
|
133
|
-
: `## Target file: ${impactResult.relativePath}
|
|
93
|
+
const userPrompt = `## Target file: ${impactResult.relativePath}
|
|
134
94
|
- Risk level: ${impactResult.riskLevel}
|
|
135
95
|
- Direct dependents: ${impactResult.directDependents.length}
|
|
136
96
|
- Transitive dependents: ${impactResult.transitiveDependents.length}
|
|
@@ -147,8 +107,6 @@ ${dependentSources.length > 0 ? `### Files depending on this file (top ${depende
|
|
|
147
107
|
return await provider.analyze(systemPrompt, userPrompt);
|
|
148
108
|
}
|
|
149
109
|
catch (err) {
|
|
150
|
-
return
|
|
151
|
-
? `AI 분석 중 오류 발생: ${err.message || err}`
|
|
152
|
-
: `AI analysis error: ${err.message || err}`;
|
|
110
|
+
return `AI analysis error: ${err.message || err}`;
|
|
153
111
|
}
|
|
154
112
|
}
|
|
@@ -35,41 +35,43 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.analyzeChangeRealtime = analyzeChangeRealtime;
|
|
37
37
|
const analyze_impact_1 = require("../tools/analyze-impact");
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const provider_1 = require("./provider");
|
|
40
41
|
const context_extractor_1 = require("./context-extractor");
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
MEDIUM: 기능 동작 변경 가능
|
|
68
|
-
LOW: 사소한 영향
|
|
69
|
-
SAFE: 안전한 변경
|
|
70
|
-
|
|
71
|
-
JSON만 응답하세요. 설명 텍스트 없이 순수 JSON만.`;
|
|
42
|
+
const analysisCache = new Map();
|
|
43
|
+
const MAX_CACHE_SIZE = 100;
|
|
44
|
+
function computeContentHash(content, diff) {
|
|
45
|
+
return crypto.createHash("md5").update((content || "") + "\n---\n" + diff).digest("hex");
|
|
46
|
+
}
|
|
47
|
+
function evictOldestCacheEntry() {
|
|
48
|
+
let oldestKey = null;
|
|
49
|
+
let oldestTime = Infinity;
|
|
50
|
+
for (const [key, entry] of analysisCache) {
|
|
51
|
+
if (entry.insertedAt < oldestTime) {
|
|
52
|
+
oldestTime = entry.insertedAt;
|
|
53
|
+
oldestKey = key;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (oldestKey)
|
|
57
|
+
analysisCache.delete(oldestKey);
|
|
58
|
+
}
|
|
59
|
+
// ── Rate limiter: max 10 AI calls per minute (sliding window) ──
|
|
60
|
+
const RATE_LIMIT_MAX = 10;
|
|
61
|
+
const RATE_LIMIT_WINDOW_MS = 60000;
|
|
62
|
+
const callTimestamps = [];
|
|
63
|
+
function isRateLimited() {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
// Remove timestamps outside the window
|
|
66
|
+
while (callTimestamps.length > 0 && callTimestamps[0] <= now - RATE_LIMIT_WINDOW_MS) {
|
|
67
|
+
callTimestamps.shift();
|
|
72
68
|
}
|
|
69
|
+
return callTimestamps.length >= RATE_LIMIT_MAX;
|
|
70
|
+
}
|
|
71
|
+
function recordCall() {
|
|
72
|
+
callTimestamps.push(Date.now());
|
|
73
|
+
}
|
|
74
|
+
function getSystemPrompt() {
|
|
73
75
|
return `You are a senior full-stack architect and code impact monitoring AI with 20 years of experience.
|
|
74
76
|
Role: Detect potential errors and cascading impacts before build when files are modified/added/deleted.
|
|
75
77
|
|
|
@@ -106,7 +108,6 @@ async function analyzeChangeRealtime(change, graph, getFileContent) {
|
|
|
106
108
|
const start = Date.now();
|
|
107
109
|
const relPath = change.relativePath;
|
|
108
110
|
const codeBlockLang = graph.languages[0] || "text";
|
|
109
|
-
const ko = (0, config_1.getLanguage)() === "ko";
|
|
110
111
|
// Get impacted files from graph
|
|
111
112
|
const absPath = path.normalize(path.join(graph.sourceDir, relPath));
|
|
112
113
|
let affectedNodes = [];
|
|
@@ -130,13 +131,11 @@ async function analyzeChangeRealtime(change, graph, getFileContent) {
|
|
|
130
131
|
// Build diff summary with signature changes
|
|
131
132
|
let diffSummary = "";
|
|
132
133
|
if (change.type === "deleted") {
|
|
133
|
-
|
|
134
|
-
diffSummary = `${label}\n\`\`\`${codeBlockLang}\n${(change.oldContent || "").split("\n").slice(0, 40).join("\n")}\n\`\`\``;
|
|
134
|
+
diffSummary = `File deleted. Previous content:\n\`\`\`${codeBlockLang}\n${(change.oldContent || "").split("\n").slice(0, 40).join("\n")}\n\`\`\``;
|
|
135
135
|
}
|
|
136
136
|
else if (change.type === "added") {
|
|
137
137
|
const smartNew = (0, context_extractor_1.buildSmartContext)(change.newContent || "", codeBlockLang);
|
|
138
|
-
|
|
139
|
-
diffSummary = `${label}\n\`\`\`${codeBlockLang}\n${smartNew}\n\`\`\``;
|
|
138
|
+
diffSummary = `New file added:\n\`\`\`${codeBlockLang}\n${smartNew}\n\`\`\``;
|
|
140
139
|
}
|
|
141
140
|
else {
|
|
142
141
|
// Modified — include signature diff
|
|
@@ -147,50 +146,31 @@ async function analyzeChangeRealtime(change, graph, getFileContent) {
|
|
|
147
146
|
return `- L${d.line}: ${d.old}`;
|
|
148
147
|
return `~ L${d.line}: ${d.old} → ${d.new}`;
|
|
149
148
|
});
|
|
150
|
-
|
|
151
|
-
? `변경된 라인 (${change.diff.length}개 중 상위 30개):`
|
|
152
|
-
: `Changed lines (top 30 of ${change.diff.length}):`;
|
|
153
|
-
diffSummary = `${label}\n\`\`\`\n${diffLines.join("\n")}\n\`\`\``;
|
|
149
|
+
diffSummary = `Changed lines (top 30 of ${change.diff.length}):\n\`\`\`\n${diffLines.join("\n")}\n\`\`\``;
|
|
154
150
|
// Add structural signature changes
|
|
155
151
|
if (change.oldContent && change.newContent) {
|
|
156
152
|
const sigChanges = (0, context_extractor_1.diffSignatures)(change.oldContent, change.newContent, codeBlockLang);
|
|
157
153
|
if (sigChanges.length > 0) {
|
|
158
|
-
diffSummary +=
|
|
159
|
-
? "\n\n### 구조적 변경 (시그니처 비교)"
|
|
160
|
-
: "\n\n### Structural changes (signature diff)";
|
|
154
|
+
diffSummary += "\n\n### Structural changes (signature diff)";
|
|
161
155
|
for (const sc of sigChanges) {
|
|
162
156
|
if (sc.type === "added") {
|
|
163
|
-
diffSummary +=
|
|
157
|
+
diffSummary += `\n+ Added: ${sc.newSignature}`;
|
|
164
158
|
}
|
|
165
159
|
else if (sc.type === "removed") {
|
|
166
|
-
diffSummary +=
|
|
160
|
+
diffSummary += `\n- Removed: ${sc.oldSignature}`;
|
|
167
161
|
}
|
|
168
162
|
else {
|
|
169
|
-
diffSummary +=
|
|
170
|
-
? `\n~ 변경: ${sc.oldSignature}\n → ${sc.newSignature}`
|
|
171
|
-
: `\n~ Changed: ${sc.oldSignature}\n → ${sc.newSignature}`;
|
|
163
|
+
diffSummary += `\n~ Changed: ${sc.oldSignature}\n → ${sc.newSignature}`;
|
|
172
164
|
}
|
|
173
165
|
}
|
|
174
166
|
}
|
|
175
167
|
}
|
|
176
168
|
if (change.newContent) {
|
|
177
169
|
const smartNew = (0, context_extractor_1.buildSmartContext)(change.newContent, codeBlockLang);
|
|
178
|
-
|
|
179
|
-
diffSummary += `\n\n${label2}\n\`\`\`${codeBlockLang}\n${smartNew}\n\`\`\``;
|
|
170
|
+
diffSummary += `\n\nFull file after modification:\n\`\`\`${codeBlockLang}\n${smartNew}\n\`\`\``;
|
|
180
171
|
}
|
|
181
172
|
}
|
|
182
|
-
const userPrompt =
|
|
183
|
-
? `## 파일 변경 감지: ${relPath}
|
|
184
|
-
변경 유형: ${change.type.toUpperCase()}
|
|
185
|
-
프로젝트 언어: ${graph.languages.join(", ") || "unknown"}
|
|
186
|
-
영향받는 파일 수: ${affectedNodes.length}
|
|
187
|
-
|
|
188
|
-
${diffSummary}
|
|
189
|
-
|
|
190
|
-
${connectedFiles.length > 0 ? `## 연결된 파일들 (${connectedFiles.length}개)\n${connectedFiles.join("\n\n")}` : "연결된 파일 없음"}
|
|
191
|
-
|
|
192
|
-
이 변경이 프로젝트에 미치는 영향을 분석하세요.`
|
|
193
|
-
: `## File change detected: ${relPath}
|
|
173
|
+
const userPrompt = `## File change detected: ${relPath}
|
|
194
174
|
Change type: ${change.type.toUpperCase()}
|
|
195
175
|
Project languages: ${graph.languages.join(", ") || "unknown"}
|
|
196
176
|
Affected files: ${affectedNodes.length}
|
|
@@ -200,19 +180,46 @@ ${diffSummary}
|
|
|
200
180
|
${connectedFiles.length > 0 ? `## Connected files (${connectedFiles.length})\n${connectedFiles.join("\n\n")}` : "No connected files"}
|
|
201
181
|
|
|
202
182
|
Analyze the impact of this change on the project.`;
|
|
183
|
+
// ── Hash cache check: skip AI if content+diff unchanged ──
|
|
184
|
+
const diffStr = change.diff.map(d => `${d.type}:${d.line}:${d.old || ""}:${d.new || ""}`).join("|");
|
|
185
|
+
const contentHash = computeContentHash(change.newContent, diffStr);
|
|
186
|
+
const cached = analysisCache.get(relPath);
|
|
187
|
+
if (cached && cached.hash === contentHash) {
|
|
188
|
+
console.error(`[syke:ai] Cache hit for ${relPath} — skipping AI call`);
|
|
189
|
+
return { ...cached.result, timestamp: change.timestamp, analysisMs: 0 };
|
|
190
|
+
}
|
|
191
|
+
// ── Rate limit check ──
|
|
192
|
+
if (isRateLimited()) {
|
|
193
|
+
const analysisMs = Date.now() - start;
|
|
194
|
+
console.error(`[syke:ai] Rate limit reached (${RATE_LIMIT_MAX}/min) — skipping AI for ${relPath}`);
|
|
195
|
+
return {
|
|
196
|
+
file: relPath,
|
|
197
|
+
changeType: change.type,
|
|
198
|
+
timestamp: change.timestamp,
|
|
199
|
+
riskLevel: affectedNodes.length >= 10 ? "HIGH" : affectedNodes.length >= 5 ? "MEDIUM" : "LOW",
|
|
200
|
+
summary: `Rate limited — graph-based analysis: ${affectedNodes.length} files impacted`,
|
|
201
|
+
brokenImports: [],
|
|
202
|
+
sideEffects: [],
|
|
203
|
+
warnings: ["AI analysis skipped: rate limit (10 calls/min)"],
|
|
204
|
+
suggestion: "Wait a moment for AI analysis to resume",
|
|
205
|
+
affectedNodes,
|
|
206
|
+
analysisMs,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
203
209
|
try {
|
|
204
210
|
const provider = (0, provider_1.getAIProvider)();
|
|
205
211
|
if (!provider) {
|
|
206
212
|
throw new Error("No AI provider available (set GEMINI_KEY, OPENAI_KEY, or ANTHROPIC_KEY)");
|
|
207
213
|
}
|
|
214
|
+
recordCall();
|
|
208
215
|
const parsed = await provider.analyzeJSON(getSystemPrompt(), userPrompt);
|
|
209
216
|
const analysisMs = Date.now() - start;
|
|
210
|
-
|
|
217
|
+
const result = {
|
|
211
218
|
file: relPath,
|
|
212
219
|
changeType: change.type,
|
|
213
220
|
timestamp: change.timestamp,
|
|
214
221
|
riskLevel: parsed.riskLevel || "LOW",
|
|
215
|
-
summary: parsed.summary ||
|
|
222
|
+
summary: parsed.summary || "Analysis complete",
|
|
216
223
|
brokenImports: parsed.brokenImports || [],
|
|
217
224
|
sideEffects: parsed.sideEffects || [],
|
|
218
225
|
warnings: parsed.warnings || [],
|
|
@@ -220,6 +227,11 @@ Analyze the impact of this change on the project.`;
|
|
|
220
227
|
affectedNodes,
|
|
221
228
|
analysisMs,
|
|
222
229
|
};
|
|
230
|
+
// Store in cache
|
|
231
|
+
if (analysisCache.size >= MAX_CACHE_SIZE)
|
|
232
|
+
evictOldestCacheEntry();
|
|
233
|
+
analysisCache.set(relPath, { hash: contentHash, result, insertedAt: Date.now() });
|
|
234
|
+
return result;
|
|
223
235
|
}
|
|
224
236
|
catch (err) {
|
|
225
237
|
const analysisMs = Date.now() - start;
|
|
@@ -229,13 +241,11 @@ Analyze the impact of this change on the project.`;
|
|
|
229
241
|
changeType: change.type,
|
|
230
242
|
timestamp: change.timestamp,
|
|
231
243
|
riskLevel: affectedNodes.length >= 10 ? "HIGH" : affectedNodes.length >= 5 ? "MEDIUM" : "LOW",
|
|
232
|
-
summary:
|
|
233
|
-
? `AI 분석 실패 — 그래프 기반 분석: ${affectedNodes.length}개 파일 영향`
|
|
234
|
-
: `AI analysis failed — graph-based analysis: ${affectedNodes.length} files impacted`,
|
|
244
|
+
summary: `AI analysis failed — graph-based analysis: ${affectedNodes.length} files impacted`,
|
|
235
245
|
brokenImports: [],
|
|
236
246
|
sideEffects: [],
|
|
237
|
-
warnings: [
|
|
238
|
-
suggestion:
|
|
247
|
+
warnings: [`AI analysis error: ${err.message}`],
|
|
248
|
+
suggestion: "Manual review required",
|
|
239
249
|
affectedNodes,
|
|
240
250
|
analysisMs,
|
|
241
251
|
};
|
package/dist/config.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ interface SykeConfig {
|
|
|
4
4
|
openaiKey?: string;
|
|
5
5
|
anthropicKey?: string;
|
|
6
6
|
aiProvider?: string;
|
|
7
|
-
language?: string;
|
|
8
7
|
port?: number;
|
|
9
8
|
}
|
|
10
9
|
/**
|
|
@@ -19,10 +18,6 @@ export declare function getAllConfig(): Record<string, string | undefined>;
|
|
|
19
18
|
* Set a config value in ~/.syke/config.json
|
|
20
19
|
*/
|
|
21
20
|
export declare function setConfig(key: keyof SykeConfig, value: string | null): void;
|
|
22
|
-
/**
|
|
23
|
-
* Detect language: config > env > system locale > default "en"
|
|
24
|
-
*/
|
|
25
|
-
export declare function getLanguage(): "ko" | "en";
|
|
26
21
|
export declare const CONFIG_DIR_PATH: string;
|
|
27
22
|
export declare const CONFIG_FILE_PATH: string;
|
|
28
23
|
export {};
|
package/dist/config.js
CHANGED
|
@@ -37,7 +37,6 @@ exports.CONFIG_FILE_PATH = exports.CONFIG_DIR_PATH = void 0;
|
|
|
37
37
|
exports.getConfig = getConfig;
|
|
38
38
|
exports.getAllConfig = getAllConfig;
|
|
39
39
|
exports.setConfig = setConfig;
|
|
40
|
-
exports.getLanguage = getLanguage;
|
|
41
40
|
/**
|
|
42
41
|
* Central config reader for SYKE MCP Server.
|
|
43
42
|
*
|
|
@@ -118,21 +117,5 @@ function setConfig(key, value) {
|
|
|
118
117
|
// ignore write errors
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Detect language: config > env > system locale > default "en"
|
|
123
|
-
*/
|
|
124
|
-
function getLanguage() {
|
|
125
|
-
const configured = getConfig("language", "SYKE_LANGUAGE");
|
|
126
|
-
if (configured) {
|
|
127
|
-
return configured.startsWith("ko") ? "ko" : "en";
|
|
128
|
-
}
|
|
129
|
-
// System locale detection
|
|
130
|
-
const locale = (process.env.LANG ||
|
|
131
|
-
process.env.LC_ALL ||
|
|
132
|
-
process.env.LANGUAGE ||
|
|
133
|
-
Intl.DateTimeFormat().resolvedOptions().locale ||
|
|
134
|
-
"").toLowerCase();
|
|
135
|
-
return locale.startsWith("ko") ? "ko" : "en";
|
|
136
|
-
}
|
|
137
120
|
exports.CONFIG_DIR_PATH = CONFIG_DIR;
|
|
138
121
|
exports.CONFIG_FILE_PATH = CONFIG_FILE;
|
package/dist/index.js
CHANGED
|
@@ -124,7 +124,7 @@ async function main() {
|
|
|
124
124
|
};
|
|
125
125
|
process.on("SIGINT", shutdown);
|
|
126
126
|
process.on("SIGTERM", shutdown);
|
|
127
|
-
const server = new index_js_1.Server({ name: "syke", version: "1.4.
|
|
127
|
+
const server = new index_js_1.Server({ name: "syke", version: "1.4.16" }, { capabilities: { tools: {} } });
|
|
128
128
|
// List tools
|
|
129
129
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
130
130
|
tools: [
|
|
@@ -204,7 +204,7 @@ async function main() {
|
|
|
204
204
|
},
|
|
205
205
|
{
|
|
206
206
|
name: "ai_analyze",
|
|
207
|
-
description: "Use AI (Gemini/OpenAI/Claude) to perform semantic analysis on a file. Reads the file's source code and its dependents to explain what might break when modified and how to safely make changes.
|
|
207
|
+
description: "Use AI (Gemini/OpenAI/Claude) to perform semantic analysis on a file. Reads the file's source code and its dependents to explain what might break when modified and how to safely make changes.",
|
|
208
208
|
inputSchema: {
|
|
209
209
|
type: "object",
|
|
210
210
|
properties: {
|
|
@@ -488,7 +488,7 @@ async function main() {
|
|
|
488
488
|
}
|
|
489
489
|
});
|
|
490
490
|
// Pre-warm the graph (skip if no project root — e.g. Smithery scan)
|
|
491
|
-
console.error(`[syke] Starting SYKE MCP Server v1.4.
|
|
491
|
+
console.error(`[syke] Starting SYKE MCP Server v1.4.16`);
|
|
492
492
|
console.error(`[syke] License: ${licenseStatus.plan.toUpperCase()} (${licenseStatus.source})`);
|
|
493
493
|
if (licenseStatus.expiresAt) {
|
|
494
494
|
console.error(`[syke] Expires: ${licenseStatus.expiresAt}`);
|
|
@@ -660,7 +660,7 @@ main().catch((err) => {
|
|
|
660
660
|
* See: https://smithery.ai/docs/deploy#sandbox-server
|
|
661
661
|
*/
|
|
662
662
|
function createSandboxServer() {
|
|
663
|
-
const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.4.
|
|
663
|
+
const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.4.16" }, { capabilities: { tools: {} } });
|
|
664
664
|
sandboxServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
665
665
|
tools: [
|
|
666
666
|
{
|
|
@@ -50,7 +50,7 @@ class FileCache extends events_1.EventEmitter {
|
|
|
50
50
|
this.sourceDirs = [];
|
|
51
51
|
this.watcher = null;
|
|
52
52
|
this.debounceTimers = new Map();
|
|
53
|
-
this.DEBOUNCE_MS =
|
|
53
|
+
this.DEBOUNCE_MS = 1500;
|
|
54
54
|
this.plugins = (0, plugin_1.detectLanguages)(projectRoot);
|
|
55
55
|
// Collect all extensions from detected plugins
|
|
56
56
|
const allExts = new Set();
|
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"
|
|
@@ -318,8 +320,8 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
|
|
|
318
320
|
connectedNodes,
|
|
319
321
|
timestamp: change.timestamp,
|
|
320
322
|
});
|
|
321
|
-
// Run Gemini real-time analysis (Pro only)
|
|
322
|
-
if (isProPlan()) {
|
|
323
|
+
// Run Gemini real-time analysis (Pro only, when toggle is on)
|
|
324
|
+
if (isProPlan() && realtimeAIEnabled) {
|
|
323
325
|
broadcastSSE("analysis-start", { file: change.relativePath });
|
|
324
326
|
try {
|
|
325
327
|
const analysis = await (0, realtime_analyzer_1.analyzeChangeRealtime)(change, graph, (relPath) => currentFileCache?.getFileByRelPath(relPath) ?? null);
|
|
@@ -338,6 +340,13 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
|
|
|
338
340
|
});
|
|
339
341
|
}
|
|
340
342
|
}
|
|
343
|
+
else if (isProPlan() && !realtimeAIEnabled) {
|
|
344
|
+
// Pro but AI toggle is off — log and skip AI, still handle structural changes
|
|
345
|
+
console.error(`[syke:ai] Real-time AI disabled — skipping analysis for ${change.relativePath}`);
|
|
346
|
+
if (change.type === "added" || change.type === "deleted") {
|
|
347
|
+
broadcastSSE("graph-rebuild", { reason: change.type, file: change.relativePath });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
341
350
|
else {
|
|
342
351
|
// Free: still rebuild graph on structural changes, but skip AI
|
|
343
352
|
if (change.type === "added" || change.type === "deleted") {
|
|
@@ -366,6 +375,18 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
|
|
|
366
375
|
sseClients: sseClients.size,
|
|
367
376
|
});
|
|
368
377
|
});
|
|
378
|
+
// POST /api/toggle-realtime-ai — Enable or disable real-time AI analysis
|
|
379
|
+
app.post("/api/toggle-realtime-ai", (req, res) => {
|
|
380
|
+
const { enabled } = req.body;
|
|
381
|
+
if (typeof enabled === "boolean") {
|
|
382
|
+
realtimeAIEnabled = enabled;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
realtimeAIEnabled = !realtimeAIEnabled;
|
|
386
|
+
}
|
|
387
|
+
console.error(`[syke:ai] Real-time AI analysis ${realtimeAIEnabled ? "ENABLED" : "DISABLED"}`);
|
|
388
|
+
res.json({ realtimeAIEnabled });
|
|
389
|
+
});
|
|
369
390
|
// GET /api/graph — Cytoscape.js compatible JSON
|
|
370
391
|
app.get("/api/graph", (_req, res) => {
|
|
371
392
|
const graph = getGraphFn();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syke1/mcp-server",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.17",
|
|
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",
|