@syke1/mcp-server 1.3.8 → 1.3.10
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/graph.js +2 -0
- package/dist/languages/plugin.js +1 -0
- package/dist/languages/typescript.d.ts +2 -0
- package/dist/languages/typescript.js +74 -6
- package/dist/web/public/app.js +198 -45
- package/dist/web/public/style.css +304 -28
- package/package.json +1 -1
package/dist/graph.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.getGraph = getGraph;
|
|
|
38
38
|
exports.refreshGraph = refreshGraph;
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const plugin_1 = require("./languages/plugin");
|
|
41
|
+
const typescript_1 = require("./languages/typescript");
|
|
41
42
|
let cachedGraph = null;
|
|
42
43
|
function buildGraph(projectRoot, packageName) {
|
|
43
44
|
const detectedPlugins = (0, plugin_1.detectLanguages)(projectRoot);
|
|
@@ -103,5 +104,6 @@ function getGraph(projectRoot, packageName) {
|
|
|
103
104
|
}
|
|
104
105
|
function refreshGraph(projectRoot, packageName) {
|
|
105
106
|
cachedGraph = null;
|
|
107
|
+
(0, typescript_1.clearAliasCache)();
|
|
106
108
|
return buildGraph(projectRoot, packageName);
|
|
107
109
|
}
|
package/dist/languages/plugin.js
CHANGED
|
@@ -101,6 +101,7 @@ const SKIP_DIRS = new Set([
|
|
|
101
101
|
"node_modules", ".git", "dist", "build", ".dart_tool", ".pub-cache",
|
|
102
102
|
"__pycache__", ".mypy_cache", ".pytest_cache", "venv", ".venv",
|
|
103
103
|
"target", "vendor", ".gradle", "bin", "obj",
|
|
104
|
+
".next", "out", ".nuxt", ".output",
|
|
104
105
|
]);
|
|
105
106
|
function discoverAllFiles(rootDir, extensions, extraSkipDirs) {
|
|
106
107
|
const results = [];
|
|
@@ -34,12 +34,53 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.typescriptPlugin = void 0;
|
|
37
|
+
exports.clearAliasCache = clearAliasCache;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const plugin_1 = require("./plugin");
|
|
40
41
|
const TS_IMPORT_RE = /(?:import|export)\s+.*?from\s+['"](.+?)['"]/;
|
|
41
42
|
const TS_SIDE_EFFECT_RE = /^import\s+['"](.+?)['"]/;
|
|
42
43
|
const JS_REQUIRE_RE = /(?:const|let|var)\s+\w+\s*=\s*require\s*\(\s*['"](.+?)['"]\s*\)/;
|
|
44
|
+
const aliasCache = new Map(); // projectRoot → aliases
|
|
45
|
+
function loadPathAliases(projectRoot) {
|
|
46
|
+
if (aliasCache.has(projectRoot))
|
|
47
|
+
return aliasCache.get(projectRoot);
|
|
48
|
+
const aliases = [];
|
|
49
|
+
try {
|
|
50
|
+
const tsconfigPath = path.join(projectRoot, "tsconfig.json");
|
|
51
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
52
|
+
aliasCache.set(projectRoot, aliases);
|
|
53
|
+
return aliases;
|
|
54
|
+
}
|
|
55
|
+
// Strip single-line comments (// ...) and trailing commas for lenient parsing
|
|
56
|
+
const raw = fs.readFileSync(tsconfigPath, "utf-8")
|
|
57
|
+
.replace(/\/\/.*$/gm, "")
|
|
58
|
+
.replace(/,\s*([\]}])/g, "$1");
|
|
59
|
+
const tsconfig = JSON.parse(raw);
|
|
60
|
+
const paths = tsconfig?.compilerOptions?.paths;
|
|
61
|
+
const baseUrl = tsconfig?.compilerOptions?.baseUrl || ".";
|
|
62
|
+
if (!paths) {
|
|
63
|
+
aliasCache.set(projectRoot, aliases);
|
|
64
|
+
return aliases;
|
|
65
|
+
}
|
|
66
|
+
const baseDir = path.resolve(projectRoot, baseUrl);
|
|
67
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
68
|
+
// Pattern like "@/*" → prefix "@/", target "./src/*" → baseDir + "src"
|
|
69
|
+
const prefix = pattern.endsWith("/*") ? pattern.slice(0, -1) : pattern;
|
|
70
|
+
const resolvedTargets = [];
|
|
71
|
+
for (const target of targets) {
|
|
72
|
+
const stripped = target.endsWith("/*") ? target.slice(0, -1) : target;
|
|
73
|
+
resolvedTargets.push(path.resolve(baseDir, stripped));
|
|
74
|
+
}
|
|
75
|
+
aliases.push({ prefix, targets: resolvedTargets });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (_) {
|
|
79
|
+
// Ignore parse errors
|
|
80
|
+
}
|
|
81
|
+
aliasCache.set(projectRoot, aliases);
|
|
82
|
+
return aliases;
|
|
83
|
+
}
|
|
43
84
|
function resolveTsImport(fromDir, importPath) {
|
|
44
85
|
const base = path.resolve(fromDir, importPath);
|
|
45
86
|
const candidates = [
|
|
@@ -49,6 +90,7 @@ function resolveTsImport(fromDir, importPath) {
|
|
|
49
90
|
base + ".js",
|
|
50
91
|
base + ".jsx",
|
|
51
92
|
path.join(base, "index.ts"),
|
|
93
|
+
path.join(base, "index.tsx"),
|
|
52
94
|
path.join(base, "index.js"),
|
|
53
95
|
];
|
|
54
96
|
for (const candidate of candidates) {
|
|
@@ -58,6 +100,24 @@ function resolveTsImport(fromDir, importPath) {
|
|
|
58
100
|
}
|
|
59
101
|
return null;
|
|
60
102
|
}
|
|
103
|
+
/** Clear cached aliases (call on project switch / graph refresh) */
|
|
104
|
+
function clearAliasCache() {
|
|
105
|
+
aliasCache.clear();
|
|
106
|
+
}
|
|
107
|
+
function resolveAliasImport(importPath, projectRoot) {
|
|
108
|
+
const aliases = loadPathAliases(projectRoot);
|
|
109
|
+
for (const alias of aliases) {
|
|
110
|
+
if (importPath.startsWith(alias.prefix)) {
|
|
111
|
+
const rest = importPath.slice(alias.prefix.length);
|
|
112
|
+
for (const targetDir of alias.targets) {
|
|
113
|
+
const resolved = resolveTsImport(targetDir, "./" + rest);
|
|
114
|
+
if (resolved)
|
|
115
|
+
return resolved;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
61
121
|
exports.typescriptPlugin = {
|
|
62
122
|
id: "typescript",
|
|
63
123
|
name: "TypeScript",
|
|
@@ -82,7 +142,7 @@ exports.typescriptPlugin = {
|
|
|
82
142
|
discoverFiles(dir) {
|
|
83
143
|
return (0, plugin_1.discoverAllFiles)(dir, [".ts", ".tsx", ".js", ".jsx"]).filter(f => !f.endsWith(".d.ts"));
|
|
84
144
|
},
|
|
85
|
-
parseImports(filePath,
|
|
145
|
+
parseImports(filePath, projectRoot, _sourceDir) {
|
|
86
146
|
let content;
|
|
87
147
|
try {
|
|
88
148
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -100,11 +160,19 @@ exports.typescriptPlugin = {
|
|
|
100
160
|
importPath = match[1];
|
|
101
161
|
if (!importPath)
|
|
102
162
|
continue;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
163
|
+
// Skip bare package imports (e.g. "react", "next/link", "firebase/app")
|
|
164
|
+
if (importPath.startsWith(".")) {
|
|
165
|
+
// Relative import
|
|
166
|
+
const resolved = resolveTsImport(fileDir, importPath);
|
|
167
|
+
if (resolved)
|
|
168
|
+
imports.push(resolved);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Try path alias resolution (e.g. @/components/Sidebar → projectRoot/components/Sidebar)
|
|
172
|
+
const resolved = resolveAliasImport(importPath, projectRoot);
|
|
173
|
+
if (resolved)
|
|
174
|
+
imports.push(resolved);
|
|
175
|
+
}
|
|
108
176
|
}
|
|
109
177
|
return imports;
|
|
110
178
|
},
|
package/dist/web/public/app.js
CHANGED
|
@@ -27,12 +27,12 @@ const LAYER_HEX = {
|
|
|
27
27
|
const LAYER_KEYS = ["FE", "BE", "DB", "API", "CONFIG", "UTIL"];
|
|
28
28
|
|
|
29
29
|
const LAYER_CENTERS = {
|
|
30
|
-
FE: { x: -
|
|
31
|
-
BE: { x:
|
|
32
|
-
DB: { x:
|
|
33
|
-
API: { x:
|
|
34
|
-
CONFIG: { x: -
|
|
35
|
-
UTIL: { x:
|
|
30
|
+
FE: { x: -2500, y: 1000, z: -1000 },
|
|
31
|
+
BE: { x: 2500, y: 1000, z: 1000 },
|
|
32
|
+
DB: { x: 0, y: -2200, z: 1800 },
|
|
33
|
+
API: { x: 2200, y: -1200, z: -1800 },
|
|
34
|
+
CONFIG: { x: -2200, y: 2500, z: 1500 },
|
|
35
|
+
UTIL: { x: 0, y: 200, z: 0 },
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
// ═══════════════════════════════════════════
|
|
@@ -54,18 +54,23 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
54
54
|
|
|
55
55
|
// Periodic server health check (catches server down even without SSE)
|
|
56
56
|
let healthCheckTimer = null;
|
|
57
|
+
let healthFailCount = 0;
|
|
58
|
+
const HEALTH_FAIL_THRESHOLD = 3; // Show offline only after 3 consecutive failures
|
|
57
59
|
function startHealthCheck() {
|
|
58
60
|
if (healthCheckTimer) return;
|
|
59
61
|
healthCheckTimer = setInterval(async () => {
|
|
60
62
|
try {
|
|
61
|
-
const res = await fetch("/api/project-info", { signal: AbortSignal.timeout(
|
|
63
|
+
const res = await fetch("/api/project-info", { signal: AbortSignal.timeout(10000) });
|
|
62
64
|
if (res.ok) {
|
|
63
|
-
|
|
65
|
+
healthFailCount = 0; // Reset on success
|
|
64
66
|
}
|
|
65
67
|
} catch (e) {
|
|
66
|
-
|
|
68
|
+
healthFailCount++;
|
|
69
|
+
if (healthFailCount >= HEALTH_FAIL_THRESHOLD) {
|
|
70
|
+
showServerOffline();
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
|
-
},
|
|
73
|
+
}, 30000); // Check every 30 seconds (not 10)
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
// ═══════════════════════════════════════════
|
|
@@ -112,9 +117,9 @@ async function loadGraph() {
|
|
|
112
117
|
lineCount: n.data.lineCount || 0, importsCount: n.data.importsCount || 0,
|
|
113
118
|
depth: n.data.depth || 0, group: n.data.group,
|
|
114
119
|
layer, action: n.data.action || "X", env: n.data.env || "PROD",
|
|
115
|
-
x: c.x + (Math.random() - 0.5) *
|
|
116
|
-
y: c.y + (Math.random() - 0.5) *
|
|
117
|
-
z: c.z + (Math.random() - 0.5) *
|
|
120
|
+
x: c.x + (Math.random() - 0.5) * 600,
|
|
121
|
+
y: c.y + (Math.random() - 0.5) * 600,
|
|
122
|
+
z: c.z + (Math.random() - 0.5) * 600,
|
|
118
123
|
};
|
|
119
124
|
});
|
|
120
125
|
const links = raw.edges.map(e => ({ source: e.data.source, target: e.data.target }));
|
|
@@ -140,7 +145,7 @@ async function loadGraph() {
|
|
|
140
145
|
.nodeColor(node => getNodeColor(node))
|
|
141
146
|
.nodeVal(node => {
|
|
142
147
|
if (!isNodeVisible(node)) return 0.001;
|
|
143
|
-
const base = Math.max(
|
|
148
|
+
const base = Math.max(8, Math.sqrt(node.lineCount) * 0.8);
|
|
144
149
|
const hb = heartbeatNodes.get(node.id);
|
|
145
150
|
if (hb) {
|
|
146
151
|
const elapsed = Date.now() - hb.startTime;
|
|
@@ -215,19 +220,19 @@ async function loadGraph() {
|
|
|
215
220
|
node.fz = undefined;
|
|
216
221
|
})
|
|
217
222
|
|
|
218
|
-
.d3AlphaDecay(0.
|
|
219
|
-
.d3VelocityDecay(0.
|
|
220
|
-
.warmupTicks(
|
|
221
|
-
.cooldownTicks(
|
|
223
|
+
.d3AlphaDecay(0.008)
|
|
224
|
+
.d3VelocityDecay(0.3)
|
|
225
|
+
.warmupTicks(300)
|
|
226
|
+
.cooldownTicks(800)
|
|
222
227
|
.enablePointerInteraction(true);
|
|
223
228
|
|
|
224
|
-
Graph.d3Force("cluster", clusterForce(0.
|
|
225
|
-
Graph.d3Force("charge").strength(-
|
|
229
|
+
Graph.d3Force("cluster", clusterForce(0.015));
|
|
230
|
+
Graph.d3Force("charge").strength(-800);
|
|
226
231
|
Graph.d3Force("link")
|
|
227
|
-
.distance(l => srcLayer(l) === tgtLayer(l) ?
|
|
228
|
-
.strength(l => srcLayer(l) === tgtLayer(l) ? 0.
|
|
232
|
+
.distance(l => srcLayer(l) === tgtLayer(l) ? 250 : 900)
|
|
233
|
+
.strength(l => srcLayer(l) === tgtLayer(l) ? 0.2 : 0.05);
|
|
229
234
|
|
|
230
|
-
Graph.cameraPosition({ x: 0, y: 0, z:
|
|
235
|
+
Graph.cameraPosition({ x: 0, y: 0, z: 3500 });
|
|
231
236
|
|
|
232
237
|
setTimeout(() => {
|
|
233
238
|
try {
|
|
@@ -257,6 +262,12 @@ async function loadGraph() {
|
|
|
257
262
|
buildLegend(layerCounts);
|
|
258
263
|
createNodeLabels();
|
|
259
264
|
updateLabelsLoop();
|
|
265
|
+
|
|
266
|
+
// ── Auto-start code crawl with highest-hub file on initial load ──
|
|
267
|
+
setTimeout(() => {
|
|
268
|
+
const topNode = nodes.slice().sort((a, b) => b.dependentCount - a.dependentCount)[0];
|
|
269
|
+
if (topNode) startCodeCrawl(topNode.id);
|
|
270
|
+
}, 2000);
|
|
260
271
|
}
|
|
261
272
|
|
|
262
273
|
// ═══════════════════════════════════════════
|
|
@@ -604,7 +615,7 @@ function handleBackgroundClick() {
|
|
|
604
615
|
document.getElementById("code-content").innerHTML = '<p class="placeholder">Select a node to preview source code</p>';
|
|
605
616
|
document.getElementById("sim-content").innerHTML = '<p class="placeholder">Select a node, then switch to SIM tab</p>';
|
|
606
617
|
document.getElementById("btn-ai-analyze").disabled = true;
|
|
607
|
-
|
|
618
|
+
// Don't stop code crawl — keep it always visible
|
|
608
619
|
refreshGraph();
|
|
609
620
|
}
|
|
610
621
|
|
|
@@ -1578,10 +1589,52 @@ function stopCodeCrawl() {
|
|
|
1578
1589
|
if (crawlUserTimer) { clearTimeout(crawlUserTimer); crawlUserTimer = null; }
|
|
1579
1590
|
const vp = document.getElementById("crawl-viewport");
|
|
1580
1591
|
if (vp) { vp.removeEventListener("wheel", pauseAutoScroll); vp.removeEventListener("mousedown", pauseAutoScroll); vp.removeEventListener("touchstart", pauseAutoScroll); }
|
|
1581
|
-
|
|
1592
|
+
// Code crawl stays always visible — never hide
|
|
1582
1593
|
crawlData = null;
|
|
1583
1594
|
}
|
|
1584
1595
|
|
|
1596
|
+
// ═══════════════════════════════════════════
|
|
1597
|
+
// CODE CRAWL: DRAG SUPPORT
|
|
1598
|
+
// ═══════════════════════════════════════════
|
|
1599
|
+
(function initCrawlDrag() {
|
|
1600
|
+
let isDragging = false, startX, startY, startLeft, startTop;
|
|
1601
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
1602
|
+
const crawlEl = document.getElementById("code-crawl");
|
|
1603
|
+
const header = document.getElementById("crawl-header");
|
|
1604
|
+
if (!crawlEl || !header) return;
|
|
1605
|
+
|
|
1606
|
+
header.addEventListener("mousedown", (e) => {
|
|
1607
|
+
if (e.target.closest("#crawl-viewport")) return;
|
|
1608
|
+
isDragging = true;
|
|
1609
|
+
crawlEl.classList.add("dragging");
|
|
1610
|
+
const rect = crawlEl.getBoundingClientRect();
|
|
1611
|
+
const parent = crawlEl.offsetParent.getBoundingClientRect();
|
|
1612
|
+
startX = e.clientX;
|
|
1613
|
+
startY = e.clientY;
|
|
1614
|
+
startLeft = rect.left - parent.left;
|
|
1615
|
+
startTop = rect.top - parent.top;
|
|
1616
|
+
crawlEl.style.left = startLeft + "px";
|
|
1617
|
+
crawlEl.style.top = startTop + "px";
|
|
1618
|
+
crawlEl.style.bottom = "auto";
|
|
1619
|
+
e.preventDefault();
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
document.addEventListener("mousemove", (e) => {
|
|
1623
|
+
if (!isDragging) return;
|
|
1624
|
+
const dx = e.clientX - startX;
|
|
1625
|
+
const dy = e.clientY - startY;
|
|
1626
|
+
crawlEl.style.left = (startLeft + dx) + "px";
|
|
1627
|
+
crawlEl.style.top = (startTop + dy) + "px";
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
document.addEventListener("mouseup", () => {
|
|
1631
|
+
if (!isDragging) return;
|
|
1632
|
+
isDragging = false;
|
|
1633
|
+
crawlEl.classList.remove("dragging");
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1636
|
+
})();
|
|
1637
|
+
|
|
1585
1638
|
// ═══════════════════════════════════════════
|
|
1586
1639
|
// REAL-TIME DIFF VIEWER (SSE-driven)
|
|
1587
1640
|
// ═══════════════════════════════════════════
|
|
@@ -1683,6 +1736,9 @@ function showRealtimeDiff(data) {
|
|
|
1683
1736
|
|
|
1684
1737
|
contentEl.innerHTML = html;
|
|
1685
1738
|
|
|
1739
|
+
// ── Mirror diff to CODE tab (VS Code style) ──
|
|
1740
|
+
renderCodeTabDiff(data, html);
|
|
1741
|
+
|
|
1686
1742
|
// ── Animated diff scroll: sweep through file, pause on changes ──
|
|
1687
1743
|
if (diffScrollAnim) cancelAnimationFrame(diffScrollAnim);
|
|
1688
1744
|
viewport.scrollTop = 0;
|
|
@@ -1768,6 +1824,97 @@ function updateDiffWithAnalysis(analysis) {
|
|
|
1768
1824
|
}
|
|
1769
1825
|
|
|
1770
1826
|
// ═══════════════════════════════════════════
|
|
1827
|
+
// CODE TAB: VS Code-style real-time diff viewer
|
|
1828
|
+
// ═══════════════════════════════════════════
|
|
1829
|
+
let codeTabScrollAnim = null;
|
|
1830
|
+
|
|
1831
|
+
function renderCodeTabDiff(data, diffHtml) {
|
|
1832
|
+
const codeEl = document.getElementById("code-content");
|
|
1833
|
+
if (!codeEl) return;
|
|
1834
|
+
|
|
1835
|
+
// Build VS Code-style header
|
|
1836
|
+
const fileName = data.file.split("/").pop();
|
|
1837
|
+
const ext = fileName.split(".").pop();
|
|
1838
|
+
const langLabel = { ts: "TypeScript", tsx: "TypeScript React", js: "JavaScript", jsx: "JSX",
|
|
1839
|
+
dart: "Dart", py: "Python", go: "Go", rs: "Rust", java: "Java", cpp: "C++", rb: "Ruby",
|
|
1840
|
+
css: "CSS", html: "HTML", json: "JSON", md: "Markdown", yaml: "YAML", yml: "YAML" }[ext] || ext.toUpperCase();
|
|
1841
|
+
const changeIcon = data.type === "added" ? "A" : data.type === "deleted" ? "D" : "M";
|
|
1842
|
+
const changeCls = data.type === "added" ? "vsc-added" : data.type === "deleted" ? "vsc-deleted" : "vsc-modified";
|
|
1843
|
+
|
|
1844
|
+
let html = `<div class="vsc-editor">`;
|
|
1845
|
+
// Tab bar (like VS Code file tab)
|
|
1846
|
+
html += `<div class="vsc-tab-bar">`;
|
|
1847
|
+
html += `<div class="vsc-tab active">`;
|
|
1848
|
+
html += `<span class="vsc-tab-change ${changeCls}">${changeIcon}</span>`;
|
|
1849
|
+
html += `<span class="vsc-tab-name">${fileName}</span>`;
|
|
1850
|
+
html += `<span class="vsc-tab-lang">${langLabel}</span>`;
|
|
1851
|
+
html += `</div>`;
|
|
1852
|
+
html += `</div>`;
|
|
1853
|
+
// Breadcrumb path
|
|
1854
|
+
html += `<div class="vsc-breadcrumb">${data.file}</div>`;
|
|
1855
|
+
// Diff content area (reuse crawl-line classes)
|
|
1856
|
+
html += `<div class="vsc-diff-body">${diffHtml}</div>`;
|
|
1857
|
+
// Status bar
|
|
1858
|
+
const lineCount = data.newContent ? data.newContent.length : 0;
|
|
1859
|
+
const diffCount = data.diffCount || 0;
|
|
1860
|
+
html += `<div class="vsc-status-bar">`;
|
|
1861
|
+
html += `<span class="vsc-status-item">${langLabel}</span>`;
|
|
1862
|
+
html += `<span class="vsc-status-item">Ln ${lineCount}</span>`;
|
|
1863
|
+
html += `<span class="vsc-status-item vsc-status-diff">+${diffCount} changes</span>`;
|
|
1864
|
+
html += `<span class="vsc-status-item">${new Date().toLocaleTimeString()}</span>`;
|
|
1865
|
+
html += `</div>`;
|
|
1866
|
+
html += `</div>`;
|
|
1867
|
+
|
|
1868
|
+
codeEl.innerHTML = html;
|
|
1869
|
+
|
|
1870
|
+
// Auto-switch to CODE tab
|
|
1871
|
+
switchTab("code");
|
|
1872
|
+
|
|
1873
|
+
// Auto-scroll to first change
|
|
1874
|
+
if (codeTabScrollAnim) cancelAnimationFrame(codeTabScrollAnim);
|
|
1875
|
+
setTimeout(() => {
|
|
1876
|
+
const diffBody = codeEl.querySelector(".vsc-diff-body");
|
|
1877
|
+
const firstChange = diffBody?.querySelector(".crawl-line.added, .crawl-line.changed, .crawl-line.removed");
|
|
1878
|
+
if (firstChange && diffBody) {
|
|
1879
|
+
firstChange.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1880
|
+
firstChange.classList.add("diff-flash-active");
|
|
1881
|
+
setTimeout(() => firstChange.classList.remove("diff-flash-active"), 1200);
|
|
1882
|
+
}
|
|
1883
|
+
}, 300);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
function updateCodeTabAnalysis(analysis) {
|
|
1887
|
+
const statusBar = document.querySelector(".vsc-status-bar");
|
|
1888
|
+
if (!statusBar) return;
|
|
1889
|
+
|
|
1890
|
+
const riskColors = { CRITICAL: "#f85149", HIGH: "#ff6b6b", MEDIUM: "#e3b341", LOW: "#3fb950", SAFE: "#3fb950" };
|
|
1891
|
+
const color = riskColors[analysis.riskLevel] || "#888";
|
|
1892
|
+
statusBar.style.background = analysis.riskLevel === "CRITICAL" || analysis.riskLevel === "HIGH" ? "#6e1b1b" : "#007acc";
|
|
1893
|
+
|
|
1894
|
+
// Add risk badge to status bar
|
|
1895
|
+
const existing = statusBar.querySelector(".vsc-risk-badge");
|
|
1896
|
+
if (existing) existing.remove();
|
|
1897
|
+
const badge = document.createElement("span");
|
|
1898
|
+
badge.className = "vsc-risk-badge";
|
|
1899
|
+
badge.style.cssText = `background:${color};color:#fff;padding:1px 6px;border-radius:2px;font-weight:bold;font-size:10px;`;
|
|
1900
|
+
badge.textContent = analysis.riskLevel;
|
|
1901
|
+
statusBar.prepend(badge);
|
|
1902
|
+
|
|
1903
|
+
// Add AI summary banner to diff body
|
|
1904
|
+
const diffBody = document.querySelector(".vsc-diff-body");
|
|
1905
|
+
if (diffBody && analysis.summary) {
|
|
1906
|
+
const banner = document.createElement("div");
|
|
1907
|
+
banner.className = "vsc-ai-banner";
|
|
1908
|
+
banner.style.cssText = `padding:8px 16px;background:rgba(0,122,204,0.15);border-left:3px solid ${color};color:${color};font-size:11px;margin:0;`;
|
|
1909
|
+
let text = `AI: ${analysis.summary}`;
|
|
1910
|
+
if (analysis.suggestion) text += ` — ${analysis.suggestion}`;
|
|
1911
|
+
banner.textContent = text;
|
|
1912
|
+
const firstBanner = diffBody.querySelector(".diff-banner");
|
|
1913
|
+
if (firstBanner) firstBanner.after(banner);
|
|
1914
|
+
else diffBody.prepend(banner);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1771
1918
|
// ═══════════════════════════════════════════
|
|
1772
1919
|
// AUDIT RESULT (shown after analysis completes)
|
|
1773
1920
|
// ═══════════════════════════════════════════
|
|
@@ -1798,14 +1945,7 @@ function showAuditResult(analysis) {
|
|
|
1798
1945
|
<div class="audit-meta">${analysis.affectedNodes?.length || 0} files checked · ${analysis.analysisMs}ms</div>
|
|
1799
1946
|
</div>`;
|
|
1800
1947
|
|
|
1801
|
-
//
|
|
1802
|
-
setTimeout(() => {
|
|
1803
|
-
crawlEl.classList.remove("active");
|
|
1804
|
-
contentEl.innerHTML = "";
|
|
1805
|
-
headerName.textContent = "";
|
|
1806
|
-
headerStatus.textContent = "";
|
|
1807
|
-
headerStatus.style.color = "";
|
|
1808
|
-
}, 8000);
|
|
1948
|
+
// Keep showing audit result — don't hide code crawl
|
|
1809
1949
|
} else {
|
|
1810
1950
|
// ── WARNING: issues found — keep showing ──
|
|
1811
1951
|
headerName.textContent = analysis.file;
|
|
@@ -1962,6 +2102,7 @@ async function initSSE() {
|
|
|
1962
2102
|
sseSource.addEventListener("connected", (e) => {
|
|
1963
2103
|
const data = JSON.parse(e.data);
|
|
1964
2104
|
console.log("[SYKE:SSE] Connected, cache:", data.cacheSize, "files");
|
|
2105
|
+
healthFailCount = 0; // Reset health failures on SSE connect
|
|
1965
2106
|
updateSSEStatus("LIVE", "connected");
|
|
1966
2107
|
});
|
|
1967
2108
|
|
|
@@ -2055,6 +2196,8 @@ async function initSSE() {
|
|
|
2055
2196
|
|
|
2056
2197
|
// Update diff view with analysis risk badge
|
|
2057
2198
|
updateDiffWithAnalysis(analysis);
|
|
2199
|
+
// Also update CODE tab with analysis result
|
|
2200
|
+
updateCodeTabAnalysis(analysis);
|
|
2058
2201
|
|
|
2059
2202
|
addRealtimeEvent({
|
|
2060
2203
|
type: "result",
|
|
@@ -2133,24 +2276,34 @@ async function initSSE() {
|
|
|
2133
2276
|
updateSSEStatus("PROJECT LOADED", "connected");
|
|
2134
2277
|
});
|
|
2135
2278
|
|
|
2279
|
+
let sseRetryCount = 0;
|
|
2136
2280
|
sseSource.onerror = async () => {
|
|
2137
|
-
console.warn("[SYKE:SSE] Connection error");
|
|
2138
|
-
updateSSEStatus("OFFLINE", "offline");
|
|
2281
|
+
console.warn("[SYKE:SSE] Connection error, retry #" + (sseRetryCount + 1));
|
|
2139
2282
|
sseSource.close();
|
|
2140
2283
|
sseSource = null;
|
|
2141
|
-
if (sseBlocked) return;
|
|
2284
|
+
if (sseBlocked) return;
|
|
2142
2285
|
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2286
|
+
sseRetryCount++;
|
|
2287
|
+
updateSSEStatus("RECONNECTING...", "warning");
|
|
2288
|
+
|
|
2289
|
+
// Only show offline after 5 consecutive SSE failures
|
|
2290
|
+
if (sseRetryCount >= 5) {
|
|
2291
|
+
try {
|
|
2292
|
+
const probe = await fetch("/api/project-info", { signal: AbortSignal.timeout(8000) });
|
|
2293
|
+
if (!probe.ok) throw new Error("not ok");
|
|
2294
|
+
// Server alive but SSE failing — just keep retrying
|
|
2295
|
+
sseRetryCount = 0;
|
|
2296
|
+
} catch (e) {
|
|
2297
|
+
showServerOffline();
|
|
2298
|
+
}
|
|
2150
2299
|
}
|
|
2151
2300
|
|
|
2301
|
+
// Exponential backoff: 2s, 4s, 8s, 16s, max 30s
|
|
2302
|
+
const delay = Math.min(2000 * Math.pow(2, sseRetryCount - 1), 30000);
|
|
2152
2303
|
if (sseReconnectTimer) clearTimeout(sseReconnectTimer);
|
|
2153
|
-
sseReconnectTimer = setTimeout(
|
|
2304
|
+
sseReconnectTimer = setTimeout(() => {
|
|
2305
|
+
initSSE();
|
|
2306
|
+
}, delay);
|
|
2154
2307
|
};
|
|
2155
2308
|
}
|
|
2156
2309
|
|
|
@@ -2423,7 +2576,7 @@ async function loadProjectInfo() {
|
|
|
2423
2576
|
updateLicenseBadge(info.plan, info.expiresAt);
|
|
2424
2577
|
} catch (e) {
|
|
2425
2578
|
console.warn("[SYKE] Failed to load project info:", e);
|
|
2426
|
-
|
|
2579
|
+
// Don't immediately show offline — let health check handle it
|
|
2427
2580
|
}
|
|
2428
2581
|
}
|
|
2429
2582
|
|
|
@@ -660,9 +660,11 @@ main {
|
|
|
660
660
|
|
|
661
661
|
#code-content {
|
|
662
662
|
flex: 1;
|
|
663
|
-
overflow
|
|
663
|
+
overflow: hidden;
|
|
664
664
|
font-size: 11px;
|
|
665
665
|
line-height: 1.6;
|
|
666
|
+
display: flex;
|
|
667
|
+
flex-direction: column;
|
|
666
668
|
}
|
|
667
669
|
|
|
668
670
|
.code-block {
|
|
@@ -689,6 +691,148 @@ main {
|
|
|
689
691
|
opacity: 0.5;
|
|
690
692
|
}
|
|
691
693
|
|
|
694
|
+
/* ── VS Code-style Code Editor (CODE tab) ── */
|
|
695
|
+
.vsc-editor {
|
|
696
|
+
display: flex;
|
|
697
|
+
flex-direction: column;
|
|
698
|
+
height: 100%;
|
|
699
|
+
background: #1e1e1e;
|
|
700
|
+
border: 1px solid #333;
|
|
701
|
+
border-radius: 4px;
|
|
702
|
+
overflow: hidden;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.vsc-tab-bar {
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
background: #252526;
|
|
709
|
+
border-bottom: 1px solid #333;
|
|
710
|
+
min-height: 36px;
|
|
711
|
+
padding: 0 4px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.vsc-tab {
|
|
715
|
+
display: flex;
|
|
716
|
+
align-items: center;
|
|
717
|
+
gap: 6px;
|
|
718
|
+
padding: 6px 12px;
|
|
719
|
+
background: #1e1e1e;
|
|
720
|
+
border-top: 2px solid #007acc;
|
|
721
|
+
border-right: 1px solid #333;
|
|
722
|
+
font-size: 12px;
|
|
723
|
+
color: #ccc;
|
|
724
|
+
white-space: nowrap;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.vsc-tab-change {
|
|
728
|
+
font-size: 10px;
|
|
729
|
+
font-weight: bold;
|
|
730
|
+
padding: 1px 4px;
|
|
731
|
+
border-radius: 2px;
|
|
732
|
+
}
|
|
733
|
+
.vsc-tab-change.vsc-modified { color: #e2c08d; }
|
|
734
|
+
.vsc-tab-change.vsc-added { color: #73c991; }
|
|
735
|
+
.vsc-tab-change.vsc-deleted { color: #f44747; }
|
|
736
|
+
|
|
737
|
+
.vsc-tab-name { color: #fff; }
|
|
738
|
+
.vsc-tab-lang { color: #666; font-size: 10px; }
|
|
739
|
+
|
|
740
|
+
.vsc-breadcrumb {
|
|
741
|
+
padding: 4px 12px;
|
|
742
|
+
font-size: 11px;
|
|
743
|
+
color: #888;
|
|
744
|
+
background: #1e1e1e;
|
|
745
|
+
border-bottom: 1px solid #2a2a2a;
|
|
746
|
+
white-space: nowrap;
|
|
747
|
+
overflow: hidden;
|
|
748
|
+
text-overflow: ellipsis;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.vsc-diff-body {
|
|
752
|
+
flex: 1;
|
|
753
|
+
overflow-y: auto;
|
|
754
|
+
overflow-x: auto;
|
|
755
|
+
background: #1e1e1e;
|
|
756
|
+
padding: 4px 0;
|
|
757
|
+
font-family: 'Consolas', 'SF Mono', 'Fira Code', monospace;
|
|
758
|
+
font-size: 12px;
|
|
759
|
+
line-height: 20px;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/* Scrollbar styling for diff body */
|
|
763
|
+
.vsc-diff-body::-webkit-scrollbar { width: 10px; }
|
|
764
|
+
.vsc-diff-body::-webkit-scrollbar-track { background: #1e1e1e; }
|
|
765
|
+
.vsc-diff-body::-webkit-scrollbar-thumb { background: #424242; border-radius: 4px; }
|
|
766
|
+
.vsc-diff-body::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
767
|
+
|
|
768
|
+
/* Override crawl-line in diff body for VS Code look */
|
|
769
|
+
.vsc-diff-body .crawl-line {
|
|
770
|
+
padding: 0 16px 0 0;
|
|
771
|
+
min-height: 20px;
|
|
772
|
+
display: flex;
|
|
773
|
+
align-items: center;
|
|
774
|
+
border: none;
|
|
775
|
+
margin: 0;
|
|
776
|
+
font-size: 12px;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.vsc-diff-body .crawl-line .cl-num {
|
|
780
|
+
min-width: 48px;
|
|
781
|
+
padding-right: 12px;
|
|
782
|
+
text-align: right;
|
|
783
|
+
color: #5a5a5a;
|
|
784
|
+
user-select: none;
|
|
785
|
+
font-size: 12px;
|
|
786
|
+
background: #1e1e1e;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.vsc-diff-body .crawl-line.added {
|
|
790
|
+
background: rgba(35, 134, 54, 0.2);
|
|
791
|
+
border-left: 3px solid #28a745;
|
|
792
|
+
}
|
|
793
|
+
.vsc-diff-body .crawl-line.added .cl-num { background: rgba(35, 134, 54, 0.15); color: #73c991; }
|
|
794
|
+
|
|
795
|
+
.vsc-diff-body .crawl-line.removed {
|
|
796
|
+
background: rgba(248, 81, 73, 0.15);
|
|
797
|
+
border-left: 3px solid #f85149;
|
|
798
|
+
}
|
|
799
|
+
.vsc-diff-body .crawl-line.removed .cl-num { background: rgba(248, 81, 73, 0.1); color: #f85149; }
|
|
800
|
+
|
|
801
|
+
.vsc-diff-body .crawl-line.changed {
|
|
802
|
+
background: rgba(227, 179, 65, 0.12);
|
|
803
|
+
border-left: 3px solid #e3b341;
|
|
804
|
+
}
|
|
805
|
+
.vsc-diff-body .crawl-line.changed .cl-num { background: rgba(227, 179, 65, 0.08); color: #e3b341; }
|
|
806
|
+
|
|
807
|
+
.vsc-diff-body .diff-banner {
|
|
808
|
+
padding: 6px 16px;
|
|
809
|
+
font-size: 11px;
|
|
810
|
+
margin: 0;
|
|
811
|
+
border-radius: 0;
|
|
812
|
+
border-left: 3px solid;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.vsc-diff-body .diff-marker {
|
|
816
|
+
min-width: 16px;
|
|
817
|
+
text-align: center;
|
|
818
|
+
font-weight: bold;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.vsc-status-bar {
|
|
822
|
+
display: flex;
|
|
823
|
+
align-items: center;
|
|
824
|
+
gap: 16px;
|
|
825
|
+
padding: 2px 12px;
|
|
826
|
+
background: #007acc;
|
|
827
|
+
color: #fff;
|
|
828
|
+
font-size: 11px;
|
|
829
|
+
min-height: 24px;
|
|
830
|
+
flex-shrink: 0;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.vsc-status-item { white-space: nowrap; }
|
|
834
|
+
.vsc-status-diff { font-weight: bold; }
|
|
835
|
+
|
|
692
836
|
/* ── Simulation Panel ── */
|
|
693
837
|
#sim-panel {
|
|
694
838
|
flex: 1;
|
|
@@ -1192,52 +1336,134 @@ main {
|
|
|
1192
1336
|
STAR WARS CODE CRAWL
|
|
1193
1337
|
═══════════════════════════════════════════ */
|
|
1194
1338
|
|
|
1339
|
+
/* ── FBI/CIA Code Crawl Overlay ── */
|
|
1195
1340
|
#code-crawl {
|
|
1196
1341
|
position: absolute;
|
|
1197
|
-
|
|
1198
|
-
|
|
1342
|
+
left: 20px;
|
|
1343
|
+
bottom: 20px;
|
|
1199
1344
|
width: 500px;
|
|
1200
|
-
|
|
1345
|
+
height: 500px;
|
|
1201
1346
|
pointer-events: auto;
|
|
1202
|
-
z-index:
|
|
1347
|
+
z-index: 10;
|
|
1203
1348
|
overflow: hidden;
|
|
1204
|
-
display:
|
|
1205
|
-
border-radius:
|
|
1349
|
+
display: block;
|
|
1350
|
+
border-radius: 6px;
|
|
1351
|
+
background: rgba(2, 8, 20, 0.92);
|
|
1352
|
+
border: 1px solid rgba(0, 212, 255, 0.25);
|
|
1353
|
+
box-shadow:
|
|
1354
|
+
0 0 30px rgba(0, 212, 255, 0.15),
|
|
1355
|
+
0 0 60px rgba(0, 212, 255, 0.05),
|
|
1356
|
+
inset 0 0 80px rgba(0, 0, 0, 0.5);
|
|
1357
|
+
backdrop-filter: blur(8px);
|
|
1358
|
+
cursor: grab;
|
|
1359
|
+
transition: box-shadow 0.3s ease;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
#code-crawl:hover {
|
|
1363
|
+
box-shadow:
|
|
1364
|
+
0 0 40px rgba(0, 212, 255, 0.25),
|
|
1365
|
+
0 0 80px rgba(0, 212, 255, 0.08),
|
|
1366
|
+
inset 0 0 80px rgba(0, 0, 0, 0.5);
|
|
1206
1367
|
}
|
|
1207
1368
|
|
|
1208
1369
|
#code-crawl.active {
|
|
1209
|
-
|
|
1370
|
+
animation: crawl-open 0.4s ease-out;
|
|
1210
1371
|
}
|
|
1211
1372
|
|
|
1212
|
-
|
|
1373
|
+
@keyframes crawl-open {
|
|
1374
|
+
0% { opacity: 0; transform: scale(0.85); }
|
|
1375
|
+
100% { opacity: 1; transform: scale(1); }
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/* Scan line sweep effect */
|
|
1379
|
+
#code-crawl::before {
|
|
1380
|
+
content: '';
|
|
1213
1381
|
position: absolute;
|
|
1214
|
-
top: 0;
|
|
1215
|
-
|
|
1216
|
-
|
|
1382
|
+
top: 0; left: 0; right: 0;
|
|
1383
|
+
height: 2px;
|
|
1384
|
+
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
|
1385
|
+
z-index: 5;
|
|
1386
|
+
animation: crawl-scanline 3s linear infinite;
|
|
1387
|
+
opacity: 0.6;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
@keyframes crawl-scanline {
|
|
1391
|
+
0% { top: 0; opacity: 0.8; }
|
|
1392
|
+
100% { top: 100%; opacity: 0; }
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/* Corner brackets (FBI terminal style) */
|
|
1396
|
+
#code-crawl::after {
|
|
1397
|
+
content: '';
|
|
1398
|
+
position: absolute;
|
|
1399
|
+
inset: 4px;
|
|
1400
|
+
border: 1px solid rgba(0, 212, 255, 0.12);
|
|
1401
|
+
border-radius: 3px;
|
|
1402
|
+
pointer-events: none;
|
|
1403
|
+
z-index: 1;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/* Dragging state */
|
|
1407
|
+
#code-crawl.dragging {
|
|
1408
|
+
cursor: grabbing;
|
|
1409
|
+
opacity: 0.9;
|
|
1410
|
+
box-shadow:
|
|
1411
|
+
0 0 50px rgba(0, 212, 255, 0.3),
|
|
1412
|
+
0 0 100px rgba(0, 212, 255, 0.1);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
#crawl-header {
|
|
1416
|
+
position: relative;
|
|
1417
|
+
padding: 8px 12px;
|
|
1217
1418
|
display: flex;
|
|
1218
1419
|
justify-content: space-between;
|
|
1219
1420
|
align-items: center;
|
|
1220
|
-
z-index:
|
|
1221
|
-
background: linear-gradient(180deg, rgba(
|
|
1421
|
+
z-index: 3;
|
|
1422
|
+
background: linear-gradient(180deg, rgba(0, 15, 35, 0.95) 0%, rgba(0, 10, 25, 0.8) 100%);
|
|
1423
|
+
border-bottom: 1px solid rgba(0, 212, 255, 0.15);
|
|
1424
|
+
cursor: grab;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
#crawl-header::before {
|
|
1428
|
+
content: '◉ LIVE INTERCEPT';
|
|
1429
|
+
font-size: 8px;
|
|
1430
|
+
letter-spacing: 3px;
|
|
1431
|
+
color: var(--risk-high);
|
|
1432
|
+
position: absolute;
|
|
1433
|
+
top: -1px;
|
|
1434
|
+
left: 12px;
|
|
1435
|
+
animation: intercept-blink 1.5s ease-in-out infinite;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
@keyframes intercept-blink {
|
|
1439
|
+
0%, 100% { opacity: 1; }
|
|
1440
|
+
50% { opacity: 0.3; }
|
|
1222
1441
|
}
|
|
1223
1442
|
|
|
1224
1443
|
#crawl-file-name {
|
|
1225
|
-
font-size:
|
|
1444
|
+
font-size: 10px;
|
|
1226
1445
|
color: var(--accent);
|
|
1227
|
-
letter-spacing:
|
|
1446
|
+
letter-spacing: 1.5px;
|
|
1228
1447
|
font-weight: 600;
|
|
1229
1448
|
text-transform: uppercase;
|
|
1230
|
-
opacity: 0.
|
|
1449
|
+
opacity: 0.8;
|
|
1450
|
+
margin-top: 10px;
|
|
1451
|
+
white-space: nowrap;
|
|
1452
|
+
overflow: hidden;
|
|
1453
|
+
text-overflow: ellipsis;
|
|
1454
|
+
max-width: 320px;
|
|
1231
1455
|
}
|
|
1232
1456
|
|
|
1233
1457
|
#crawl-status {
|
|
1234
1458
|
font-size: 9px;
|
|
1235
1459
|
letter-spacing: 2px;
|
|
1236
1460
|
color: var(--risk-low);
|
|
1461
|
+
margin-top: 10px;
|
|
1237
1462
|
}
|
|
1238
1463
|
|
|
1239
1464
|
#crawl-status.modifying {
|
|
1240
1465
|
color: var(--risk-high);
|
|
1466
|
+
text-shadow: 0 0 8px rgba(255, 45, 85, 0.6);
|
|
1241
1467
|
animation: status-blink 0.6s ease-in-out infinite;
|
|
1242
1468
|
}
|
|
1243
1469
|
|
|
@@ -1248,24 +1474,80 @@ main {
|
|
|
1248
1474
|
|
|
1249
1475
|
#crawl-viewport {
|
|
1250
1476
|
position: absolute;
|
|
1251
|
-
top:
|
|
1477
|
+
top: 42px; left: 0; right: 0; bottom: 0;
|
|
1252
1478
|
overflow-y: auto;
|
|
1253
1479
|
overflow-x: hidden;
|
|
1254
1480
|
}
|
|
1255
1481
|
|
|
1482
|
+
/* Scrollbar */
|
|
1483
|
+
#crawl-viewport::-webkit-scrollbar { width: 6px; }
|
|
1484
|
+
#crawl-viewport::-webkit-scrollbar-track { background: transparent; }
|
|
1485
|
+
#crawl-viewport::-webkit-scrollbar-thumb { background: rgba(0, 212, 255, 0.2); border-radius: 3px; }
|
|
1486
|
+
|
|
1487
|
+
/* Top/bottom fade for cinema effect */
|
|
1488
|
+
#crawl-viewport::before,
|
|
1489
|
+
#crawl-viewport::after {
|
|
1490
|
+
content: '';
|
|
1491
|
+
position: sticky;
|
|
1492
|
+
display: block;
|
|
1493
|
+
height: 40px;
|
|
1494
|
+
z-index: 2;
|
|
1495
|
+
pointer-events: none;
|
|
1496
|
+
}
|
|
1497
|
+
#crawl-viewport::before {
|
|
1498
|
+
top: 0;
|
|
1499
|
+
background: linear-gradient(180deg, rgba(2, 8, 20, 0.95) 0%, transparent 100%);
|
|
1500
|
+
}
|
|
1501
|
+
#crawl-viewport::after {
|
|
1502
|
+
bottom: 0;
|
|
1503
|
+
background: linear-gradient(0deg, rgba(2, 8, 20, 0.95) 0%, transparent 100%);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1256
1506
|
#crawl-content {
|
|
1257
|
-
padding:
|
|
1507
|
+
padding: 8px 10px;
|
|
1258
1508
|
}
|
|
1259
1509
|
|
|
1260
|
-
/* Individual crawl line —
|
|
1510
|
+
/* Individual crawl line — FBI terminal style */
|
|
1261
1511
|
.crawl-line {
|
|
1262
1512
|
font-family: 'Consolas', 'SF Mono', 'Fira Code', monospace;
|
|
1263
1513
|
font-size: 11px;
|
|
1264
|
-
line-height: 1.
|
|
1514
|
+
line-height: 1.6;
|
|
1265
1515
|
white-space: pre;
|
|
1266
|
-
color:
|
|
1516
|
+
color: rgba(200, 214, 229, 0.5);
|
|
1267
1517
|
overflow: hidden;
|
|
1268
1518
|
text-overflow: ellipsis;
|
|
1519
|
+
padding: 0 4px;
|
|
1520
|
+
transition: color 0.3s, background 0.3s;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/* Non-changed lines are dim / ghostly */
|
|
1524
|
+
.crawl-line:not(.added):not(.removed):not(.changed) {
|
|
1525
|
+
color: rgba(200, 214, 229, 0.25);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/* Changed lines glow bright */
|
|
1529
|
+
.crawl-line.added, .crawl-line.removed, .crawl-line.changed {
|
|
1530
|
+
color: #d4d4d4;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/* Staggered fade-in for each line */
|
|
1534
|
+
#code-crawl.active .crawl-line {
|
|
1535
|
+
animation: line-materialize 0.5s ease-out both;
|
|
1536
|
+
}
|
|
1537
|
+
#code-crawl.active .crawl-line:nth-child(1) { animation-delay: 0.02s; }
|
|
1538
|
+
#code-crawl.active .crawl-line:nth-child(2) { animation-delay: 0.04s; }
|
|
1539
|
+
#code-crawl.active .crawl-line:nth-child(3) { animation-delay: 0.06s; }
|
|
1540
|
+
#code-crawl.active .crawl-line:nth-child(4) { animation-delay: 0.08s; }
|
|
1541
|
+
#code-crawl.active .crawl-line:nth-child(5) { animation-delay: 0.10s; }
|
|
1542
|
+
#code-crawl.active .crawl-line:nth-child(6) { animation-delay: 0.12s; }
|
|
1543
|
+
#code-crawl.active .crawl-line:nth-child(7) { animation-delay: 0.14s; }
|
|
1544
|
+
#code-crawl.active .crawl-line:nth-child(8) { animation-delay: 0.16s; }
|
|
1545
|
+
#code-crawl.active .crawl-line:nth-child(9) { animation-delay: 0.18s; }
|
|
1546
|
+
#code-crawl.active .crawl-line:nth-child(10) { animation-delay: 0.20s; }
|
|
1547
|
+
|
|
1548
|
+
@keyframes line-materialize {
|
|
1549
|
+
0% { opacity: 0; transform: translateX(8px); filter: blur(2px); }
|
|
1550
|
+
100% { opacity: 1; transform: translateX(0); filter: blur(0); }
|
|
1269
1551
|
}
|
|
1270
1552
|
|
|
1271
1553
|
.crawl-line .cl-num {
|
|
@@ -1577,12 +1859,6 @@ main {
|
|
|
1577
1859
|
100% { background: transparent; }
|
|
1578
1860
|
}
|
|
1579
1861
|
|
|
1580
|
-
/* Crawl status when modifying */
|
|
1581
|
-
#crawl-status.modifying {
|
|
1582
|
-
color: var(--risk-medium);
|
|
1583
|
-
animation: sse-pulse 0.5s infinite;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
1862
|
/* ═══════════════════════════════════════════ */
|
|
1587
1863
|
/* Audit Result (post-analysis) */
|
|
1588
1864
|
/* ═══════════════════════════════════════════ */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syke1/mcp-server",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.10",
|
|
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",
|