@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.
Files changed (41) hide show
  1. package/README.md +112 -0
  2. package/dist/ai/analyzer.d.ts +3 -0
  3. package/dist/ai/analyzer.js +120 -0
  4. package/dist/ai/realtime-analyzer.d.ts +20 -0
  5. package/dist/ai/realtime-analyzer.js +182 -0
  6. package/dist/graph.d.ts +13 -0
  7. package/dist/graph.js +105 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +518 -0
  10. package/dist/languages/cpp.d.ts +2 -0
  11. package/dist/languages/cpp.js +109 -0
  12. package/dist/languages/dart.d.ts +2 -0
  13. package/dist/languages/dart.js +162 -0
  14. package/dist/languages/go.d.ts +2 -0
  15. package/dist/languages/go.js +111 -0
  16. package/dist/languages/java.d.ts +2 -0
  17. package/dist/languages/java.js +113 -0
  18. package/dist/languages/plugin.d.ts +20 -0
  19. package/dist/languages/plugin.js +148 -0
  20. package/dist/languages/python.d.ts +2 -0
  21. package/dist/languages/python.js +129 -0
  22. package/dist/languages/ruby.d.ts +2 -0
  23. package/dist/languages/ruby.js +97 -0
  24. package/dist/languages/rust.d.ts +2 -0
  25. package/dist/languages/rust.js +121 -0
  26. package/dist/languages/typescript.d.ts +2 -0
  27. package/dist/languages/typescript.js +138 -0
  28. package/dist/license/validator.d.ts +23 -0
  29. package/dist/license/validator.js +297 -0
  30. package/dist/tools/analyze-impact.d.ts +23 -0
  31. package/dist/tools/analyze-impact.js +102 -0
  32. package/dist/tools/gate-build.d.ts +25 -0
  33. package/dist/tools/gate-build.js +243 -0
  34. package/dist/watcher/file-cache.d.ts +56 -0
  35. package/dist/watcher/file-cache.js +241 -0
  36. package/dist/web/public/app.js +2398 -0
  37. package/dist/web/public/index.html +258 -0
  38. package/dist/web/public/style.css +1827 -0
  39. package/dist/web/server.d.ts +29 -0
  40. package/dist/web/server.js +744 -0
  41. package/package.json +50 -0
@@ -0,0 +1,297 @@
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.startHeartbeat = startHeartbeat;
37
+ exports.stopAndDeactivate = stopAndDeactivate;
38
+ exports.getDeviceId = getDeviceId;
39
+ exports.checkLicense = checkLicense;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ const https = __importStar(require("https"));
44
+ const crypto = __importStar(require("crypto"));
45
+ const CACHE_DIR = path.join(os.homedir(), ".syke");
46
+ const CACHE_FILE = path.join(CACHE_DIR, ".license-cache.json");
47
+ const CONFIG_FILE = path.join(CACHE_DIR, "config.json");
48
+ // Cache durations
49
+ const CACHE_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours
50
+ const CACHE_GRACE_PERIOD = 7 * 24 * 60 * 60 * 1000; // 7 days offline grace
51
+ // Heartbeat interval
52
+ const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
53
+ // Cloud Function URLs
54
+ const BASE_URL = "https://us-central1-syke-cloud.cloudfunctions.net";
55
+ const VALIDATE_URL = `${BASE_URL}/validateLicenseKey`;
56
+ const HEARTBEAT_URL = `${BASE_URL}/licenseHeartbeat`;
57
+ const DEACTIVATE_URL = `${BASE_URL}/licenseDeactivate`;
58
+ // Module state for heartbeat
59
+ let heartbeatTimer = null;
60
+ let currentLicenseKey = null;
61
+ let currentDeviceId = null;
62
+ /**
63
+ * Generate a stable device fingerprint from machine info
64
+ */
65
+ function getDeviceFingerprint() {
66
+ const raw = `${os.hostname()}:${os.userInfo().username}:${os.platform()}:${os.arch()}`;
67
+ return crypto.createHash("sha256").update(raw).digest("hex").substring(0, 16);
68
+ }
69
+ /**
70
+ * Human-readable device name for dashboard display
71
+ */
72
+ function getDeviceName() {
73
+ const platformNames = {
74
+ win32: "Windows",
75
+ darwin: "macOS",
76
+ linux: "Linux",
77
+ };
78
+ const platformName = platformNames[os.platform()] || os.platform();
79
+ return `${os.hostname()} (${platformName})`;
80
+ }
81
+ /**
82
+ * Read license key from env var or config file
83
+ */
84
+ function getLicenseKey() {
85
+ // 1. Environment variable
86
+ const envKey = process.env.SYKE_LICENSE_KEY;
87
+ if (envKey && envKey.startsWith("SYKE-"))
88
+ return envKey;
89
+ // 2. Config file
90
+ try {
91
+ if (fs.existsSync(CONFIG_FILE)) {
92
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
93
+ if (config.licenseKey && config.licenseKey.startsWith("SYKE-"))
94
+ return config.licenseKey;
95
+ }
96
+ }
97
+ catch {
98
+ // ignore
99
+ }
100
+ return null;
101
+ }
102
+ /**
103
+ * Read cached license validation result
104
+ */
105
+ function readCache() {
106
+ try {
107
+ if (!fs.existsSync(CACHE_FILE))
108
+ return null;
109
+ const data = JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8"));
110
+ if (!data || typeof data.cachedAt !== "number")
111
+ return null;
112
+ return data;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ /**
119
+ * Write license validation result to cache
120
+ */
121
+ function writeCache(data) {
122
+ try {
123
+ if (!fs.existsSync(CACHE_DIR)) {
124
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
125
+ }
126
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
127
+ }
128
+ catch {
129
+ // silently fail — cache is optional
130
+ }
131
+ }
132
+ /**
133
+ * POST JSON to a URL, return parsed response
134
+ */
135
+ function postJSON(url, body) {
136
+ return new Promise((resolve) => {
137
+ const payload = JSON.stringify(body);
138
+ const parsedUrl = new URL(url);
139
+ const req = https.request({
140
+ hostname: parsedUrl.hostname,
141
+ path: parsedUrl.pathname,
142
+ method: "POST",
143
+ timeout: 10000,
144
+ headers: {
145
+ "Content-Type": "application/json",
146
+ "Content-Length": Buffer.byteLength(payload),
147
+ },
148
+ }, (res) => {
149
+ let data = "";
150
+ res.on("data", (chunk) => { data += chunk.toString(); });
151
+ res.on("end", () => {
152
+ try {
153
+ resolve(JSON.parse(data));
154
+ }
155
+ catch {
156
+ resolve({ valid: false });
157
+ }
158
+ });
159
+ });
160
+ req.on("error", () => resolve({ valid: false }));
161
+ req.on("timeout", () => { req.destroy(); resolve({ valid: false }); });
162
+ req.write(payload);
163
+ req.end();
164
+ });
165
+ }
166
+ /**
167
+ * Validate license online with device binding
168
+ */
169
+ async function validateOnline(key) {
170
+ const deviceId = getDeviceFingerprint();
171
+ const deviceName = getDeviceName();
172
+ return postJSON(VALIDATE_URL, { key, deviceId, deviceName });
173
+ }
174
+ /**
175
+ * Send heartbeat to keep session alive
176
+ */
177
+ async function sendHeartbeat() {
178
+ if (!currentLicenseKey || !currentDeviceId)
179
+ return;
180
+ try {
181
+ await postJSON(HEARTBEAT_URL, {
182
+ key: currentLicenseKey,
183
+ deviceId: currentDeviceId,
184
+ });
185
+ }
186
+ catch {
187
+ // silently fail — next heartbeat will retry
188
+ }
189
+ }
190
+ /**
191
+ * Deactivate session — called on graceful shutdown
192
+ */
193
+ async function sendDeactivate() {
194
+ if (!currentLicenseKey || !currentDeviceId)
195
+ return;
196
+ try {
197
+ await postJSON(DEACTIVATE_URL, {
198
+ key: currentLicenseKey,
199
+ deviceId: currentDeviceId,
200
+ });
201
+ }
202
+ catch {
203
+ // best-effort — server will timeout the session anyway
204
+ }
205
+ }
206
+ /**
207
+ * Start heartbeat interval (called after successful Pro validation)
208
+ */
209
+ function startHeartbeat(key, deviceId) {
210
+ currentLicenseKey = key;
211
+ currentDeviceId = deviceId;
212
+ if (heartbeatTimer)
213
+ clearInterval(heartbeatTimer);
214
+ heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL_MS);
215
+ }
216
+ /**
217
+ * Stop heartbeat and deactivate session (called on shutdown)
218
+ */
219
+ async function stopAndDeactivate() {
220
+ if (heartbeatTimer) {
221
+ clearInterval(heartbeatTimer);
222
+ heartbeatTimer = null;
223
+ }
224
+ await sendDeactivate();
225
+ currentLicenseKey = null;
226
+ currentDeviceId = null;
227
+ }
228
+ /**
229
+ * Get current device fingerprint (exported for use by index.ts)
230
+ */
231
+ function getDeviceId() {
232
+ return getDeviceFingerprint();
233
+ }
234
+ /**
235
+ * Main license validation — called on MCP server startup
236
+ */
237
+ async function checkLicense() {
238
+ const key = getLicenseKey();
239
+ // No key → Free mode
240
+ if (!key) {
241
+ return { plan: "free", source: "default" };
242
+ }
243
+ // Check cache first
244
+ const cache = readCache();
245
+ const now = Date.now();
246
+ if (cache && cache.valid && (now - cache.cachedAt) < CACHE_MAX_AGE) {
247
+ // Cache is fresh — still start heartbeat with cached session
248
+ startHeartbeat(key, getDeviceFingerprint());
249
+ return {
250
+ plan: "pro",
251
+ email: cache.email,
252
+ expiresAt: cache.expiresAt,
253
+ source: "cache",
254
+ };
255
+ }
256
+ // Try online validation (with device binding)
257
+ const result = await validateOnline(key);
258
+ if (result.valid) {
259
+ // Update cache
260
+ writeCache({
261
+ valid: true,
262
+ plan: result.plan,
263
+ email: result.email,
264
+ expiresAt: result.expiresAt,
265
+ cachedAt: now,
266
+ });
267
+ // Start heartbeat
268
+ startHeartbeat(key, getDeviceFingerprint());
269
+ return {
270
+ plan: "pro",
271
+ email: result.email,
272
+ expiresAt: result.expiresAt,
273
+ source: "online",
274
+ };
275
+ }
276
+ // Device/session error — pass through error message
277
+ if (result.error) {
278
+ return {
279
+ plan: "free",
280
+ source: "online",
281
+ error: result.error,
282
+ };
283
+ }
284
+ // Online validation failed — check if we have a grace-period cache
285
+ if (cache && cache.valid && (now - cache.cachedAt) < CACHE_GRACE_PERIOD) {
286
+ startHeartbeat(key, getDeviceFingerprint());
287
+ return {
288
+ plan: "pro",
289
+ email: cache.email,
290
+ expiresAt: cache.expiresAt,
291
+ source: "cache",
292
+ };
293
+ }
294
+ // No valid cache within grace period → Free mode
295
+ writeCache({ valid: false, cachedAt: now });
296
+ return { plan: "free", source: "default" };
297
+ }
@@ -0,0 +1,23 @@
1
+ import { DependencyGraph } from "../graph";
2
+ export type RiskLevel = "HIGH" | "MEDIUM" | "LOW" | "NONE";
3
+ export interface ImpactResult {
4
+ filePath: string;
5
+ relativePath: string;
6
+ riskLevel: RiskLevel;
7
+ directDependents: string[];
8
+ transitiveDependents: string[];
9
+ totalImpacted: number;
10
+ }
11
+ /**
12
+ * BFS reverse traversal to find all files impacted by modifying `filePath`.
13
+ */
14
+ export declare function analyzeImpact(filePath: string, graph: DependencyGraph): ImpactResult;
15
+ export declare function classifyRisk(count: number): RiskLevel;
16
+ /**
17
+ * Rank files by number of reverse dependents (hub score).
18
+ */
19
+ export declare function getHubFiles(graph: DependencyGraph, topN?: number): Array<{
20
+ relativePath: string;
21
+ dependentCount: number;
22
+ riskLevel: RiskLevel;
23
+ }>;
@@ -0,0 +1,102 @@
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.analyzeImpact = analyzeImpact;
37
+ exports.classifyRisk = classifyRisk;
38
+ exports.getHubFiles = getHubFiles;
39
+ const path = __importStar(require("path"));
40
+ /**
41
+ * BFS reverse traversal to find all files impacted by modifying `filePath`.
42
+ */
43
+ function analyzeImpact(filePath, graph) {
44
+ const normalized = path.normalize(filePath);
45
+ // Direct dependents (depth 1)
46
+ const directDependents = graph.reverse.get(normalized) || [];
47
+ // BFS for transitive dependents (all depths)
48
+ const visited = new Set();
49
+ const queue = [...directDependents];
50
+ for (const d of directDependents) {
51
+ visited.add(d);
52
+ }
53
+ while (queue.length > 0) {
54
+ const current = queue.shift();
55
+ const dependents = graph.reverse.get(current) || [];
56
+ for (const dep of dependents) {
57
+ if (!visited.has(dep) && dep !== normalized) {
58
+ visited.add(dep);
59
+ queue.push(dep);
60
+ }
61
+ }
62
+ }
63
+ const totalImpacted = visited.size;
64
+ // Transitive-only = all visited minus direct
65
+ const directSet = new Set(directDependents);
66
+ const transitiveDependents = [...visited].filter((f) => !directSet.has(f));
67
+ const riskLevel = classifyRisk(totalImpacted);
68
+ const toRelative = (f) => path.relative(graph.sourceDir, f).replace(/\\/g, "/");
69
+ return {
70
+ filePath: normalized,
71
+ relativePath: toRelative(normalized),
72
+ riskLevel,
73
+ directDependents: directDependents.map(toRelative),
74
+ transitiveDependents: transitiveDependents.map(toRelative),
75
+ totalImpacted,
76
+ };
77
+ }
78
+ function classifyRisk(count) {
79
+ if (count >= 10)
80
+ return "HIGH";
81
+ if (count >= 5)
82
+ return "MEDIUM";
83
+ if (count >= 1)
84
+ return "LOW";
85
+ return "NONE";
86
+ }
87
+ /**
88
+ * Rank files by number of reverse dependents (hub score).
89
+ */
90
+ function getHubFiles(graph, topN = 10) {
91
+ const entries = [];
92
+ for (const file of graph.files) {
93
+ const revDeps = graph.reverse.get(file) || [];
94
+ entries.push({ file, count: revDeps.length });
95
+ }
96
+ entries.sort((a, b) => b.count - a.count);
97
+ return entries.slice(0, topN).map((e) => ({
98
+ relativePath: path.relative(graph.sourceDir, e.file).replace(/\\/g, "/"),
99
+ dependentCount: e.count,
100
+ riskLevel: classifyRisk(e.count),
101
+ }));
102
+ }
@@ -0,0 +1,25 @@
1
+ import { DependencyGraph } from "../graph";
2
+ export type GateVerdict = "PASS" | "WARN" | "FAIL";
3
+ export interface GateIssue {
4
+ file: string;
5
+ severity: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW";
6
+ description: string;
7
+ }
8
+ export interface GateResult {
9
+ verdict: GateVerdict;
10
+ statusLine: string;
11
+ issues: GateIssue[];
12
+ recommendation: string;
13
+ stats: {
14
+ filesInGraph: number;
15
+ unresolvedWarnings: number;
16
+ };
17
+ autoAcknowledged: number;
18
+ }
19
+ /**
20
+ * Run the build gate check.
21
+ * If `specifiedFiles` is provided, only warnings for those files are considered.
22
+ * Cycle detection also starts from those files.
23
+ */
24
+ export declare function gateCheck(graph: DependencyGraph, specifiedFiles?: string[]): GateResult;
25
+ export declare function formatGateResult(result: GateResult): string;
@@ -0,0 +1,243 @@
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.gateCheck = gateCheck;
37
+ exports.formatGateResult = formatGateResult;
38
+ const path = __importStar(require("path"));
39
+ const server_1 = require("../web/server");
40
+ const analyze_impact_1 = require("./analyze-impact");
41
+ /**
42
+ * DFS forward traversal from specified files to detect circular dependencies.
43
+ * Returns at most `maxCycles` cycles for performance.
44
+ */
45
+ function detectCyclesForFiles(files, graph, maxCycles = 10) {
46
+ const cycles = [];
47
+ const globalVisited = new Set();
48
+ for (const startFile of files) {
49
+ if (!graph.files.has(startFile))
50
+ continue;
51
+ const stack = new Set();
52
+ const pathStack = [];
53
+ function dfs(file) {
54
+ if (cycles.length >= maxCycles)
55
+ return;
56
+ if (globalVisited.has(file))
57
+ return;
58
+ stack.add(file);
59
+ pathStack.push(file);
60
+ const deps = graph.forward.get(file) || [];
61
+ for (const dep of deps) {
62
+ if (cycles.length >= maxCycles)
63
+ break;
64
+ if (stack.has(dep)) {
65
+ // Back-edge found → cycle
66
+ const idx = pathStack.indexOf(dep);
67
+ if (idx >= 0) {
68
+ cycles.push({
69
+ cycle: [...pathStack.slice(idx), dep],
70
+ file: startFile,
71
+ });
72
+ }
73
+ }
74
+ else if (!globalVisited.has(dep)) {
75
+ dfs(dep);
76
+ }
77
+ }
78
+ stack.delete(file);
79
+ pathStack.pop();
80
+ }
81
+ dfs(startFile);
82
+ // Mark all visited from this start as globally visited
83
+ for (const f of stack)
84
+ globalVisited.add(f);
85
+ }
86
+ return cycles.slice(0, maxCycles);
87
+ }
88
+ // ── Gate Check ──
89
+ /**
90
+ * Run the build gate check.
91
+ * If `specifiedFiles` is provided, only warnings for those files are considered.
92
+ * Cycle detection also starts from those files.
93
+ */
94
+ function gateCheck(graph, specifiedFiles) {
95
+ const allWarnings = (0, server_1.getUnacknowledgedWarnings)();
96
+ const stats = {
97
+ filesInGraph: graph.files.size,
98
+ unresolvedWarnings: allWarnings.length,
99
+ };
100
+ // Filter warnings to specified files if provided
101
+ const warnings = specifiedFiles
102
+ ? allWarnings.filter((w) => specifiedFiles.some((f) => w.file === f ||
103
+ f.endsWith(w.file) ||
104
+ w.file.endsWith(f.replace(/\\/g, "/"))))
105
+ : allWarnings;
106
+ const issues = [];
107
+ // 1. Check warnings by severity
108
+ for (const w of warnings) {
109
+ const severity = mapRiskToSeverity(w.riskLevel);
110
+ if (severity) {
111
+ issues.push({
112
+ file: w.file,
113
+ severity,
114
+ description: w.summary +
115
+ (w.brokenImports.length > 0
116
+ ? ` (broken imports: ${w.brokenImports.join(", ")})`
117
+ : ""),
118
+ });
119
+ }
120
+ }
121
+ // 2. Detect cycles for specified files (or skip full-graph scan for perf)
122
+ const filesToCheck = specifiedFiles || [];
123
+ const cycles = detectCyclesForFiles(filesToCheck, graph, 10);
124
+ for (const c of cycles) {
125
+ const cyclePath = c.cycle
126
+ .map((f) => path.relative(graph.sourceDir, f).replace(/\\/g, "/"))
127
+ .join(" → ");
128
+ issues.push({
129
+ file: path.relative(graph.sourceDir, c.file).replace(/\\/g, "/"),
130
+ severity: "CRITICAL",
131
+ description: `Circular dependency: ${cyclePath}`,
132
+ });
133
+ }
134
+ // 3. Check if hub files were modified
135
+ if (specifiedFiles) {
136
+ const hubs = (0, analyze_impact_1.getHubFiles)(graph, 5);
137
+ const hubPaths = new Set(hubs.map((h) => h.relativePath));
138
+ for (const f of specifiedFiles) {
139
+ const rel = path.relative(graph.sourceDir, f).replace(/\\/g, "/");
140
+ if (hubPaths.has(rel)) {
141
+ const hub = hubs.find((h) => h.relativePath === rel);
142
+ issues.push({
143
+ file: rel,
144
+ severity: "MEDIUM",
145
+ description: `Hub file modified (${hub.dependentCount} dependents, ${hub.riskLevel} risk)`,
146
+ });
147
+ }
148
+ }
149
+ }
150
+ // ── Determine verdict ──
151
+ const hasCritical = issues.some((i) => i.severity === "CRITICAL");
152
+ const highIssues = issues.filter((i) => i.severity === "HIGH");
153
+ const hasHighWithBroken = highIssues.length > 0 &&
154
+ warnings.some((w) => w.riskLevel === "HIGH" && w.brokenImports.length > 0);
155
+ const hasCycles = cycles.length > 0;
156
+ let verdict;
157
+ let statusLine;
158
+ let recommendation;
159
+ let autoAcknowledged = 0;
160
+ if (hasCritical || hasHighWithBroken || hasCycles) {
161
+ verdict = "FAIL";
162
+ const reasons = [];
163
+ if (hasCritical)
164
+ reasons.push("CRITICAL warnings");
165
+ if (hasHighWithBroken)
166
+ reasons.push("HIGH warnings with broken imports");
167
+ if (hasCycles)
168
+ reasons.push(`${cycles.length} circular dependency(ies)`);
169
+ statusLine = `BUILD BLOCKED — ${reasons.join(", ")}`;
170
+ recommendation =
171
+ "Fix the issues above before building. Use `check_warnings` for details, then `analyze_impact` on affected files.";
172
+ }
173
+ else if (highIssues.length > 0 ||
174
+ issues.some((i) => i.severity === "MEDIUM")) {
175
+ verdict = "WARN";
176
+ statusLine = `PROCEED WITH CAUTION — ${issues.length} issue(s) detected`;
177
+ recommendation =
178
+ "Review the issues. If you've verified they're safe, proceed with the build. Use `check_warnings acknowledge=true` to clear warnings.";
179
+ }
180
+ else {
181
+ verdict = "PASS";
182
+ statusLine = "All clear — safe to build";
183
+ recommendation = "No issues detected. You may proceed with build/deploy.";
184
+ // Auto-acknowledge warnings on PASS
185
+ autoAcknowledged = (0, server_1.acknowledgeWarnings)();
186
+ }
187
+ return {
188
+ verdict,
189
+ statusLine,
190
+ issues,
191
+ recommendation,
192
+ stats,
193
+ autoAcknowledged,
194
+ };
195
+ }
196
+ // ── Formatting ──
197
+ function formatGateResult(result) {
198
+ const icon = result.verdict === "PASS"
199
+ ? "✅"
200
+ : result.verdict === "WARN"
201
+ ? "⚠️"
202
+ : "🚫";
203
+ const lines = [
204
+ `## SYKE Build Gate — ${icon} ${result.verdict}`,
205
+ "",
206
+ "### Status",
207
+ result.statusLine,
208
+ ];
209
+ if (result.issues.length > 0) {
210
+ lines.push("", "### Issues");
211
+ for (const issue of result.issues) {
212
+ const issueIcon = issue.severity === "CRITICAL"
213
+ ? "🚫"
214
+ : issue.severity === "HIGH"
215
+ ? "🔴"
216
+ : issue.severity === "MEDIUM"
217
+ ? "🟡"
218
+ : "🔵";
219
+ lines.push(`- ${issueIcon} **[${issue.severity}]** ${issue.file}: ${issue.description}`);
220
+ }
221
+ }
222
+ lines.push("", "### Recommendation", result.recommendation);
223
+ lines.push("", "### Stats", `- Files in graph: ${result.stats.filesInGraph}`, `- Unresolved warnings: ${result.stats.unresolvedWarnings}`);
224
+ if (result.autoAcknowledged > 0) {
225
+ lines.push(`- Auto-acknowledged: ${result.autoAcknowledged} warning(s)`);
226
+ }
227
+ return lines.join("\n");
228
+ }
229
+ // ── Helpers ──
230
+ function mapRiskToSeverity(riskLevel) {
231
+ switch (riskLevel) {
232
+ case "CRITICAL":
233
+ return "CRITICAL";
234
+ case "HIGH":
235
+ return "HIGH";
236
+ case "MEDIUM":
237
+ return "MEDIUM";
238
+ case "LOW":
239
+ return "LOW";
240
+ default:
241
+ return null; // SAFE → no issue
242
+ }
243
+ }