@syke1/mcp-server 1.0.0
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 +112 -0
- package/dist/ai/analyzer.d.ts +3 -0
- package/dist/ai/analyzer.js +120 -0
- package/dist/ai/realtime-analyzer.d.ts +20 -0
- package/dist/ai/realtime-analyzer.js +182 -0
- package/dist/graph.d.ts +13 -0
- package/dist/graph.js +105 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +518 -0
- package/dist/languages/cpp.d.ts +2 -0
- package/dist/languages/cpp.js +109 -0
- package/dist/languages/dart.d.ts +2 -0
- package/dist/languages/dart.js +162 -0
- package/dist/languages/go.d.ts +2 -0
- package/dist/languages/go.js +111 -0
- package/dist/languages/java.d.ts +2 -0
- package/dist/languages/java.js +113 -0
- package/dist/languages/plugin.d.ts +20 -0
- package/dist/languages/plugin.js +148 -0
- package/dist/languages/python.d.ts +2 -0
- package/dist/languages/python.js +129 -0
- package/dist/languages/ruby.d.ts +2 -0
- package/dist/languages/ruby.js +97 -0
- package/dist/languages/rust.d.ts +2 -0
- package/dist/languages/rust.js +121 -0
- package/dist/languages/typescript.d.ts +2 -0
- package/dist/languages/typescript.js +138 -0
- package/dist/license/validator.d.ts +23 -0
- package/dist/license/validator.js +297 -0
- package/dist/tools/analyze-impact.d.ts +23 -0
- package/dist/tools/analyze-impact.js +102 -0
- package/dist/tools/gate-build.d.ts +25 -0
- package/dist/tools/gate-build.js +243 -0
- package/dist/watcher/file-cache.d.ts +56 -0
- package/dist/watcher/file-cache.js +241 -0
- package/dist/web/public/app.js +2398 -0
- package/dist/web/public/index.html +258 -0
- package/dist/web/public/style.css +1827 -0
- package/dist/web/server.d.ts +29 -0
- package/dist/web/server.js +744 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# SYKE — Your Codebase Has a Pulse
|
|
2
|
+
|
|
3
|
+
**AI code impact analysis MCP server.** SYKE monitors every file your AI touches, maps dependency graphs, detects cascading breakage, and gates builds before damage spreads.
|
|
4
|
+
|
|
5
|
+
Works with **Claude Code**, **Cursor**, **Windsurf**, and any MCP-compatible AI coding agent.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### 1. Add to your MCP config
|
|
10
|
+
|
|
11
|
+
**Claude Code** (`~/.claude/mcp.json`):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"syke": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["@syke1/mcp-server@latest"],
|
|
19
|
+
"env": {
|
|
20
|
+
"SYKE_LICENSE_KEY": "your-key-here"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"syke": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["@syke1/mcp-server@latest"],
|
|
35
|
+
"env": {
|
|
36
|
+
"SYKE_LICENSE_KEY": "your-key-here"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Restart your AI agent
|
|
44
|
+
|
|
45
|
+
SYKE auto-detects your project language and builds the dependency graph on startup.
|
|
46
|
+
|
|
47
|
+
### 3. Open the dashboard
|
|
48
|
+
|
|
49
|
+
A web dashboard opens automatically at `http://localhost:3333` showing your live dependency graph.
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
### 8 MCP Tools
|
|
54
|
+
|
|
55
|
+
| Tool | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `gate_build` | **Mandatory pre-build check.** Returns PASS/WARN/FAIL verdict before any build or deploy. |
|
|
58
|
+
| `analyze_impact` | Shows direct and transitive dependents when a file is modified. |
|
|
59
|
+
| `check_safe` | Quick one-line safety verdict: HIGH/MEDIUM/LOW/NONE risk. |
|
|
60
|
+
| `get_dependencies` | Lists internal imports (forward dependencies) of a file. |
|
|
61
|
+
| `get_hub_files` | Ranks files by how many other files depend on them. |
|
|
62
|
+
| `refresh_graph` | Re-scans all source files and rebuilds the dependency graph. |
|
|
63
|
+
| `ai_analyze` | **Pro** — Gemini AI semantic analysis of a file and its dependents. |
|
|
64
|
+
| `check_warnings` | Real-time monitoring alerts for file changes that may break dependents. |
|
|
65
|
+
|
|
66
|
+
### Language Support
|
|
67
|
+
|
|
68
|
+
Auto-detected, zero-config: **Dart/Flutter**, **TypeScript/JavaScript**, **Python**, **Go**, **Rust**, **Java**, **C++**, **Ruby**.
|
|
69
|
+
|
|
70
|
+
### Web Dashboard
|
|
71
|
+
|
|
72
|
+
Live dependency graph visualization at `localhost:3333` with:
|
|
73
|
+
- Interactive node graph (click any file to see its connections)
|
|
74
|
+
- Real-time cascade monitoring
|
|
75
|
+
- Risk level indicators
|
|
76
|
+
|
|
77
|
+
## Pricing
|
|
78
|
+
|
|
79
|
+
### Free — $0 forever
|
|
80
|
+
- 1 project, 50 files
|
|
81
|
+
- All 8 core tools
|
|
82
|
+
- Hub & cycle detection
|
|
83
|
+
- 8 language support
|
|
84
|
+
|
|
85
|
+
### Pro — $12/mo (or $99/yr, save 31%)
|
|
86
|
+
- Unlimited projects & files
|
|
87
|
+
- Real-time cascade monitoring
|
|
88
|
+
- AI semantic analysis (BYOK Gemini key)
|
|
89
|
+
- Priority support
|
|
90
|
+
|
|
91
|
+
Get your license key at [syke.cloud/dashboard](https://syke.cloud/dashboard/).
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
| Environment Variable | Description | Required |
|
|
96
|
+
|---------------------|-------------|----------|
|
|
97
|
+
| `SYKE_LICENSE_KEY` | Pro license key from dashboard | No (Free tier works without) |
|
|
98
|
+
| `GEMINI_KEY` | Google Gemini API key for `ai_analyze` | Pro only |
|
|
99
|
+
| `SYKE_WEB_PORT` | Dashboard port (default: 3333) | No |
|
|
100
|
+
| `SYKE_NO_BROWSER` | Set to `1` to disable auto-open browser | No |
|
|
101
|
+
| `SYKE_currentProjectRoot` | Override auto-detected project root | No |
|
|
102
|
+
|
|
103
|
+
## How It Works
|
|
104
|
+
|
|
105
|
+
1. **On startup**, SYKE scans your source directory and builds a complete dependency graph using static import analysis.
|
|
106
|
+
2. **When your AI modifies a file**, call `gate_build` to check if the change is safe before building.
|
|
107
|
+
3. **If dependencies break**, SYKE detects cascading failures and blocks the build with a `FAIL` verdict.
|
|
108
|
+
4. **The dashboard** shows a real-time visualization of your dependency graph with risk indicators.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.analyzeWithAI = analyzeWithAI;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const generative_ai_1 = require("@google/generative-ai");
|
|
40
|
+
let genAI = null;
|
|
41
|
+
function getGenAI() {
|
|
42
|
+
if (!genAI) {
|
|
43
|
+
const key = process.env.GEMINI_KEY;
|
|
44
|
+
if (!key) {
|
|
45
|
+
throw new Error("GEMINI_KEY environment variable is required for AI analysis");
|
|
46
|
+
}
|
|
47
|
+
genAI = new generative_ai_1.GoogleGenerativeAI(key);
|
|
48
|
+
}
|
|
49
|
+
return genAI;
|
|
50
|
+
}
|
|
51
|
+
function readFileContent(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function buildSystemPrompt(languages) {
|
|
60
|
+
const langNames = languages.length > 0 ? languages.join("/") : "source";
|
|
61
|
+
return `당신은 ${langNames} 코드 영향도 분석 전문가입니다.
|
|
62
|
+
주어진 파일의 소스코드와 이 파일에 의존하는 파일들의 코드를 분석하여,
|
|
63
|
+
이 파일을 수정할 때 어떤 부분이 깨질 수 있는지 구체적으로 설명해주세요.
|
|
64
|
+
|
|
65
|
+
분석 포맷:
|
|
66
|
+
## 핵심 역할
|
|
67
|
+
이 파일이 프로젝트에서 하는 역할을 한 문장으로 설명
|
|
68
|
+
|
|
69
|
+
## 수정 시 위험 포인트
|
|
70
|
+
수정 시 깨질 수 있는 구체적인 부분들 (함수명, 클래스명 포함)
|
|
71
|
+
|
|
72
|
+
## 영향받는 파일 분석
|
|
73
|
+
의존 파일들이 이 파일의 어떤 부분을 사용하는지 구체적으로
|
|
74
|
+
|
|
75
|
+
## 안전한 수정 가이드
|
|
76
|
+
이 파일을 수정할 때 주의할 점과 추천 접근법
|
|
77
|
+
|
|
78
|
+
한국어로 답변하세요. 간결하되 구체적으로 작성하세요.`;
|
|
79
|
+
}
|
|
80
|
+
async function analyzeWithAI(filePath, impactResult, graph) {
|
|
81
|
+
const ai = getGenAI();
|
|
82
|
+
const model = ai.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
83
|
+
const targetSource = readFileContent(filePath);
|
|
84
|
+
if (!targetSource) {
|
|
85
|
+
return `파일을 읽을 수 없습니다: ${filePath}`;
|
|
86
|
+
}
|
|
87
|
+
const codeBlockLang = graph.languages[0] || "text";
|
|
88
|
+
const directDeps = (graph.reverse.get(path.normalize(filePath)) || []).slice(0, 5);
|
|
89
|
+
const dependentSources = [];
|
|
90
|
+
for (const dep of directDeps) {
|
|
91
|
+
const source = readFileContent(dep);
|
|
92
|
+
if (source) {
|
|
93
|
+
const rel = path.relative(graph.sourceDir, dep).replace(/\\/g, "/");
|
|
94
|
+
dependentSources.push(`### ${rel}\n\`\`\`${codeBlockLang}\n${source}\n\`\`\``);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const userPrompt = `## 분석 대상 파일: ${impactResult.relativePath}
|
|
98
|
+
- 위험도: ${impactResult.riskLevel}
|
|
99
|
+
- 직접 의존 파일 수: ${impactResult.directDependents.length}
|
|
100
|
+
- 전이적 의존 파일 수: ${impactResult.transitiveDependents.length}
|
|
101
|
+
- 총 영향 파일 수: ${impactResult.totalImpacted}
|
|
102
|
+
|
|
103
|
+
### 대상 파일 소스코드
|
|
104
|
+
\`\`\`${codeBlockLang}
|
|
105
|
+
${targetSource}
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
${dependentSources.length > 0 ? `### 이 파일에 의존하는 파일들 (상위 ${dependentSources.length}개)\n${dependentSources.join("\n\n")}` : "이 파일에 의존하는 내부 파일이 없습니다."}`;
|
|
109
|
+
try {
|
|
110
|
+
const systemPrompt = buildSystemPrompt(graph.languages);
|
|
111
|
+
const result = await model.generateContent({
|
|
112
|
+
contents: [{ role: "user", parts: [{ text: userPrompt }] }],
|
|
113
|
+
systemInstruction: { role: "model", parts: [{ text: systemPrompt }] },
|
|
114
|
+
});
|
|
115
|
+
return result.response.text();
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return `AI 분석 중 오류 발생: ${err.message || err}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FileChange } from "../watcher/file-cache";
|
|
2
|
+
import { DependencyGraph } from "../graph";
|
|
3
|
+
export interface RealtimeAnalysis {
|
|
4
|
+
file: string;
|
|
5
|
+
changeType: FileChange["type"];
|
|
6
|
+
timestamp: number;
|
|
7
|
+
riskLevel: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "SAFE";
|
|
8
|
+
summary: string;
|
|
9
|
+
brokenImports: string[];
|
|
10
|
+
sideEffects: string[];
|
|
11
|
+
warnings: string[];
|
|
12
|
+
suggestion: string;
|
|
13
|
+
affectedNodes: string[];
|
|
14
|
+
analysisMs: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Analyze a file change in real-time using Gemini.
|
|
18
|
+
* Receives the diff + connected files context from memory cache.
|
|
19
|
+
*/
|
|
20
|
+
export declare function analyzeChangeRealtime(change: FileChange, graph: DependencyGraph, getFileContent: (relPath: string) => string | null): Promise<RealtimeAnalysis>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.analyzeChangeRealtime = analyzeChangeRealtime;
|
|
37
|
+
const generative_ai_1 = require("@google/generative-ai");
|
|
38
|
+
const analyze_impact_1 = require("../tools/analyze-impact");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const REALTIME_SYSTEM_PROMPT = `당신은 20년 경력의 풀스택 아키텍트이자, 코드 영향도 감시 AI입니다.
|
|
41
|
+
역할: 파일이 수정/추가/삭제될 때, 빌드 전에 잠재적 오류와 연쇄 영향을 감지합니다.
|
|
42
|
+
|
|
43
|
+
분석 원칙:
|
|
44
|
+
1. import/export 깨짐: 삭제/이름변경된 클래스/함수/변수가 다른 파일에서 참조되는지 확인
|
|
45
|
+
2. 타입 불일치: 매개변수 타입, 반환 타입 변경이 호출부와 맞는지 확인
|
|
46
|
+
3. 상태 관리 연쇄: Provider/Notifier 변경이 UI와 비즈니스 로직에 미치는 영향
|
|
47
|
+
4. 라우팅 영향: GoRouter 경로/매개변수 변경이 네비게이션에 미치는 영향
|
|
48
|
+
5. 누락된 초기화: 새로 추가된 Provider가 적절히 등록되었는지
|
|
49
|
+
|
|
50
|
+
응답 형식 (반드시 JSON):
|
|
51
|
+
{
|
|
52
|
+
"riskLevel": "CRITICAL|HIGH|MEDIUM|LOW|SAFE",
|
|
53
|
+
"summary": "한 줄 요약 (한국어)",
|
|
54
|
+
"brokenImports": ["깨질 수 있는 import 목록"],
|
|
55
|
+
"sideEffects": ["예상되는 부작용 목록"],
|
|
56
|
+
"warnings": ["주의사항 목록"],
|
|
57
|
+
"suggestion": "추천 조치 (한국어)"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
CRITICAL: 빌드 실패 확실
|
|
61
|
+
HIGH: 런타임 오류 가능
|
|
62
|
+
MEDIUM: 기능 동작 변경 가능
|
|
63
|
+
LOW: 사소한 영향
|
|
64
|
+
SAFE: 안전한 변경
|
|
65
|
+
|
|
66
|
+
JSON만 응답하세요. 설명 텍스트 없이 순수 JSON만.`;
|
|
67
|
+
let genAI = null;
|
|
68
|
+
function getGenAI() {
|
|
69
|
+
if (!genAI) {
|
|
70
|
+
const key = process.env.GEMINI_KEY;
|
|
71
|
+
if (!key)
|
|
72
|
+
throw new Error("GEMINI_KEY required");
|
|
73
|
+
genAI = new generative_ai_1.GoogleGenerativeAI(key);
|
|
74
|
+
}
|
|
75
|
+
return genAI;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Analyze a file change in real-time using Gemini.
|
|
79
|
+
* Receives the diff + connected files context from memory cache.
|
|
80
|
+
*/
|
|
81
|
+
async function analyzeChangeRealtime(change, graph, getFileContent) {
|
|
82
|
+
const start = Date.now();
|
|
83
|
+
const relPath = change.relativePath;
|
|
84
|
+
const codeBlockLang = graph.languages[0] || "text";
|
|
85
|
+
// Get impacted files from graph
|
|
86
|
+
const absPath = path.normalize(path.join(graph.sourceDir, relPath));
|
|
87
|
+
let affectedNodes = [];
|
|
88
|
+
if (graph.files.has(absPath)) {
|
|
89
|
+
const impact = (0, analyze_impact_1.analyzeImpact)(absPath, graph);
|
|
90
|
+
affectedNodes = [...impact.directDependents, ...impact.transitiveDependents];
|
|
91
|
+
}
|
|
92
|
+
// Build context: changed file + top 5 connected files' content
|
|
93
|
+
const connectedFiles = [];
|
|
94
|
+
const revDeps = graph.reverse.get(absPath) || [];
|
|
95
|
+
const fwdDeps = graph.forward.get(absPath) || [];
|
|
96
|
+
const connected = [...new Set([...revDeps, ...fwdDeps])].slice(0, 5);
|
|
97
|
+
for (const dep of connected) {
|
|
98
|
+
const depRel = path.relative(graph.sourceDir, dep).replace(/\\/g, "/");
|
|
99
|
+
const content = getFileContent(depRel);
|
|
100
|
+
if (content) {
|
|
101
|
+
const truncated = content.split("\n").slice(0, 60).join("\n");
|
|
102
|
+
connectedFiles.push(`### ${depRel}\n\`\`\`${codeBlockLang}\n${truncated}\n\`\`\``);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Build diff summary
|
|
106
|
+
let diffSummary = "";
|
|
107
|
+
if (change.type === "deleted") {
|
|
108
|
+
diffSummary = `파일이 삭제됨. 이전 내용:\n\`\`\`${codeBlockLang}\n${(change.oldContent || "").split("\n").slice(0, 40).join("\n")}\n\`\`\``;
|
|
109
|
+
}
|
|
110
|
+
else if (change.type === "added") {
|
|
111
|
+
diffSummary = `새 파일 추가됨:\n\`\`\`${codeBlockLang}\n${(change.newContent || "").split("\n").slice(0, 60).join("\n")}\n\`\`\``;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const diffLines = change.diff.slice(0, 30).map(d => {
|
|
115
|
+
if (d.type === "added")
|
|
116
|
+
return `+ L${d.line}: ${d.new}`;
|
|
117
|
+
if (d.type === "removed")
|
|
118
|
+
return `- L${d.line}: ${d.old}`;
|
|
119
|
+
return `~ L${d.line}: ${d.old} → ${d.new}`;
|
|
120
|
+
});
|
|
121
|
+
diffSummary = `변경된 라인 (${change.diff.length}개 중 상위 30개):\n\`\`\`\n${diffLines.join("\n")}\n\`\`\``;
|
|
122
|
+
if (change.newContent) {
|
|
123
|
+
diffSummary += `\n\n전체 수정 후 파일:\n\`\`\`${codeBlockLang}\n${change.newContent.split("\n").slice(0, 80).join("\n")}\n\`\`\``;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const userPrompt = `## 파일 변경 감지: ${relPath}
|
|
127
|
+
변경 유형: ${change.type.toUpperCase()}
|
|
128
|
+
프로젝트 언어: ${graph.languages.join(", ") || "unknown"}
|
|
129
|
+
영향받는 파일 수: ${affectedNodes.length}
|
|
130
|
+
|
|
131
|
+
${diffSummary}
|
|
132
|
+
|
|
133
|
+
${connectedFiles.length > 0 ? `## 연결된 파일들 (${connectedFiles.length}개)\n${connectedFiles.join("\n\n")}` : "연결된 파일 없음"}
|
|
134
|
+
|
|
135
|
+
이 변경이 프로젝트에 미치는 영향을 분석하세요.`;
|
|
136
|
+
try {
|
|
137
|
+
const ai = getGenAI();
|
|
138
|
+
const model = ai.getGenerativeModel({ model: "gemini-2.5-flash" });
|
|
139
|
+
const result = await model.generateContent({
|
|
140
|
+
contents: [{ role: "user", parts: [{ text: userPrompt }] }],
|
|
141
|
+
systemInstruction: { role: "model", parts: [{ text: REALTIME_SYSTEM_PROMPT }] },
|
|
142
|
+
});
|
|
143
|
+
const text = result.response.text();
|
|
144
|
+
let jsonStr = text;
|
|
145
|
+
const jsonMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
146
|
+
if (jsonMatch)
|
|
147
|
+
jsonStr = jsonMatch[1];
|
|
148
|
+
jsonStr = jsonStr.trim();
|
|
149
|
+
const parsed = JSON.parse(jsonStr);
|
|
150
|
+
const analysisMs = Date.now() - start;
|
|
151
|
+
return {
|
|
152
|
+
file: relPath,
|
|
153
|
+
changeType: change.type,
|
|
154
|
+
timestamp: change.timestamp,
|
|
155
|
+
riskLevel: parsed.riskLevel || "LOW",
|
|
156
|
+
summary: parsed.summary || "분석 완료",
|
|
157
|
+
brokenImports: parsed.brokenImports || [],
|
|
158
|
+
sideEffects: parsed.sideEffects || [],
|
|
159
|
+
warnings: parsed.warnings || [],
|
|
160
|
+
suggestion: parsed.suggestion || "",
|
|
161
|
+
affectedNodes,
|
|
162
|
+
analysisMs,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const analysisMs = Date.now() - start;
|
|
167
|
+
console.error(`[syke:ai] Analysis error for ${relPath}: ${err.message}`);
|
|
168
|
+
return {
|
|
169
|
+
file: relPath,
|
|
170
|
+
changeType: change.type,
|
|
171
|
+
timestamp: change.timestamp,
|
|
172
|
+
riskLevel: affectedNodes.length >= 10 ? "HIGH" : affectedNodes.length >= 5 ? "MEDIUM" : "LOW",
|
|
173
|
+
summary: `AI 분석 실패 — 그래프 기반 분석: ${affectedNodes.length}개 파일 영향`,
|
|
174
|
+
brokenImports: [],
|
|
175
|
+
sideEffects: [],
|
|
176
|
+
warnings: [`AI 분석 오류: ${err.message}`],
|
|
177
|
+
suggestion: "수동 확인 필요",
|
|
178
|
+
affectedNodes,
|
|
179
|
+
analysisMs,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
package/dist/graph.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DependencyGraph {
|
|
2
|
+
forward: Map<string, string[]>;
|
|
3
|
+
reverse: Map<string, string[]>;
|
|
4
|
+
files: Set<string>;
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
languages: string[];
|
|
7
|
+
sourceDirs: string[];
|
|
8
|
+
/** backward compat: first source directory */
|
|
9
|
+
sourceDir: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildGraph(projectRoot: string, packageName?: string): DependencyGraph;
|
|
12
|
+
export declare function getGraph(projectRoot: string, packageName?: string): DependencyGraph;
|
|
13
|
+
export declare function refreshGraph(projectRoot: string, packageName?: string): DependencyGraph;
|
package/dist/graph.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildGraph = buildGraph;
|
|
37
|
+
exports.getGraph = getGraph;
|
|
38
|
+
exports.refreshGraph = refreshGraph;
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const plugin_1 = require("./languages/plugin");
|
|
41
|
+
let cachedGraph = null;
|
|
42
|
+
function buildGraph(projectRoot, packageName) {
|
|
43
|
+
const detectedPlugins = (0, plugin_1.detectLanguages)(projectRoot);
|
|
44
|
+
const languages = detectedPlugins.map(p => p.id);
|
|
45
|
+
const forward = new Map();
|
|
46
|
+
const reverse = new Map();
|
|
47
|
+
const files = new Set();
|
|
48
|
+
const allSourceDirs = [];
|
|
49
|
+
for (const plugin of detectedPlugins) {
|
|
50
|
+
const dirs = plugin.getSourceDirs(projectRoot);
|
|
51
|
+
for (const dir of dirs) {
|
|
52
|
+
if (!allSourceDirs.includes(dir))
|
|
53
|
+
allSourceDirs.push(dir);
|
|
54
|
+
const sourceFiles = plugin.discoverFiles(dir);
|
|
55
|
+
for (const f of sourceFiles) {
|
|
56
|
+
files.add(f);
|
|
57
|
+
if (!forward.has(f))
|
|
58
|
+
forward.set(f, []);
|
|
59
|
+
}
|
|
60
|
+
for (const f of sourceFiles) {
|
|
61
|
+
const imports = plugin.parseImports(f, projectRoot, dir);
|
|
62
|
+
const validImports = [];
|
|
63
|
+
for (const imp of imports) {
|
|
64
|
+
if (!files.has(imp))
|
|
65
|
+
continue;
|
|
66
|
+
validImports.push(imp);
|
|
67
|
+
const rev = reverse.get(imp) || [];
|
|
68
|
+
rev.push(f);
|
|
69
|
+
reverse.set(imp, rev);
|
|
70
|
+
}
|
|
71
|
+
forward.set(f, validImports);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const sourceDir = allSourceDirs[0] || path.join(projectRoot, "src");
|
|
76
|
+
const graph = {
|
|
77
|
+
forward,
|
|
78
|
+
reverse,
|
|
79
|
+
files,
|
|
80
|
+
projectRoot,
|
|
81
|
+
languages,
|
|
82
|
+
sourceDirs: allSourceDirs,
|
|
83
|
+
sourceDir,
|
|
84
|
+
};
|
|
85
|
+
cachedGraph = graph;
|
|
86
|
+
console.error(`[syke] Graph built (${languages.join("+")}): ${files.size} files, ${countEdges(forward)} edges`);
|
|
87
|
+
return graph;
|
|
88
|
+
}
|
|
89
|
+
function countEdges(forward) {
|
|
90
|
+
let count = 0;
|
|
91
|
+
for (const deps of forward.values()) {
|
|
92
|
+
count += deps.length;
|
|
93
|
+
}
|
|
94
|
+
return count;
|
|
95
|
+
}
|
|
96
|
+
function getGraph(projectRoot, packageName) {
|
|
97
|
+
if (cachedGraph && cachedGraph.projectRoot === projectRoot) {
|
|
98
|
+
return cachedGraph;
|
|
99
|
+
}
|
|
100
|
+
return buildGraph(projectRoot, packageName);
|
|
101
|
+
}
|
|
102
|
+
function refreshGraph(projectRoot, packageName) {
|
|
103
|
+
cachedGraph = null;
|
|
104
|
+
return buildGraph(projectRoot, packageName);
|
|
105
|
+
}
|
package/dist/index.d.ts
ADDED