@syke1/mcp-server 1.3.10 → 1.3.11

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.
@@ -18,6 +18,8 @@ let crawlData = null;
18
18
  let modifyingNodes = new Set(); // nodes currently being modified by AI
19
19
  let heartbeatNodes = new Map(); // nodeId → { riskLevel, startTime, interval }
20
20
  let diffScrollAnim = null; // animation for diff scroll
21
+ let knownNodeIds = new Set(); // track existing nodes for star-birth detection
22
+ let birthAnimations = new Map(); // nodeId → { startTime, spawnPos, targetPos }
21
23
 
22
24
  const LAYER_HEX = {
23
25
  FE: "#00d4ff", BE: "#c084fc", DB: "#ff6b35",
@@ -108,20 +110,62 @@ async function loadGraph() {
108
110
  }
109
111
  hideWelcomeOverlay();
110
112
 
113
+ const isReload = Graph !== null;
114
+
115
+ // Spawn point for new nodes (top-right corner of 3D space)
116
+ const SPAWN = { x: 5000, y: 4000, z: -2000 };
117
+
118
+ // Preserve existing node positions on reload
119
+ const currentPositions = {};
120
+ if (isReload) {
121
+ const cur = Graph.graphData();
122
+ cur.nodes.forEach(n => {
123
+ currentPositions[n.id] = { x: n.x, y: n.y, z: n.z };
124
+ });
125
+ }
126
+
111
127
  const nodes = raw.nodes.map(n => {
112
128
  const layer = n.data.layer || "UTIL";
113
129
  const c = LAYER_CENTERS[layer] || LAYER_CENTERS.UTIL;
130
+ const isNew = knownNodeIds.size > 0 && !knownNodeIds.has(n.data.id);
131
+
132
+ // For existing nodes on reload: keep their current simulated position
133
+ const targetPos = currentPositions[n.data.id] || {
134
+ x: c.x + (Math.random() - 0.5) * 600,
135
+ y: c.y + (Math.random() - 0.5) * 600,
136
+ z: c.z + (Math.random() - 0.5) * 600,
137
+ };
138
+
139
+ // New node: spawn from far corner, will animate to target
140
+ if (isNew) {
141
+ console.log("[SYKE] ★ Star birth:", n.data.id);
142
+ birthAnimations.set(n.data.id, {
143
+ startTime: Date.now(),
144
+ spawnPos: { ...SPAWN },
145
+ targetPos: { ...targetPos },
146
+ duration: 2500, // 2.5s flight time
147
+ });
148
+ }
149
+
114
150
  return {
115
151
  id: n.data.id, label: n.data.label, fullPath: n.data.fullPath,
116
152
  riskLevel: n.data.riskLevel, dependentCount: n.data.dependentCount,
117
153
  lineCount: n.data.lineCount || 0, importsCount: n.data.importsCount || 0,
118
154
  depth: n.data.depth || 0, group: n.data.group,
119
155
  layer, action: n.data.action || "X", env: n.data.env || "PROD",
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,
156
+ x: isNew ? SPAWN.x : targetPos.x,
157
+ y: isNew ? SPAWN.y : targetPos.y,
158
+ z: isNew ? SPAWN.z : targetPos.z,
159
+ // Pin new nodes at SPAWN so simulation doesn't skip their animation
160
+ fx: isNew ? SPAWN.x : undefined,
161
+ fy: isNew ? SPAWN.y : undefined,
162
+ fz: isNew ? SPAWN.z : undefined,
163
+ _isNew: isNew,
123
164
  };
124
165
  });
166
+
167
+ // Update known node IDs
168
+ knownNodeIds = new Set(nodes.map(n => n.id));
125
169
  const links = raw.edges.map(e => ({ source: e.data.source, target: e.data.target }));
126
170
  graphData = { nodes, links };
127
171
 
@@ -133,6 +177,15 @@ async function loadGraph() {
133
177
  const highRisk = nodes.filter(n => n.riskLevel === "HIGH").length;
134
178
  document.getElementById("stat-high").textContent = highRisk;
135
179
 
180
+ // ── RELOAD: just update data, no graph re-creation ──
181
+ if (isReload) {
182
+ Graph.graphData(graphData);
183
+ buildLegend(layerCounts);
184
+ console.log("[SYKE] Graph updated (reload), birth animations:", birthAnimations.size);
185
+ return;
186
+ }
187
+
188
+ // ── FIRST LOAD: create new Graph instance ──
136
189
  const container = document.getElementById("3d-graph");
137
190
 
138
191
  Graph = ForceGraph3D()(container)
@@ -158,6 +211,14 @@ async function loadGraph() {
158
211
  const pulse = 0.5 + 0.5 * Math.sin(Date.now() / 200);
159
212
  return base * (1 + pulse * 0.4);
160
213
  }
214
+ // Star birth: start large, shrink to normal
215
+ const birth = birthAnimations.get(node.id);
216
+ if (birth) {
217
+ const t = Math.min(1, (Date.now() - birth.startTime) / birth.duration);
218
+ if (t >= 1) birthAnimations.delete(node.id);
219
+ const scale = 1 + (3 - 1) * (1 - t) * (1 - t); // ease-out: 3x → 1x
220
+ return base * scale;
221
+ }
161
222
  return base;
162
223
  })
163
224
  .nodeOpacity(1.0)
@@ -220,6 +281,31 @@ async function loadGraph() {
220
281
  node.fz = undefined;
221
282
  })
222
283
 
284
+ // Star birth: animate pinned position from SPAWN → target on each tick
285
+ .onEngineTick(() => {
286
+ if (birthAnimations.size === 0) return;
287
+ const now = Date.now();
288
+ for (const [nodeId, anim] of birthAnimations) {
289
+ const node = graphData.nodes.find(n => n.id === nodeId);
290
+ if (!node) continue;
291
+ const t = Math.min(1, (now - anim.startTime) / anim.duration);
292
+ if (t < 1) {
293
+ // Ease-out cubic: fast start, gentle landing
294
+ const ease = 1 - Math.pow(1 - t, 3);
295
+ node.fx = anim.spawnPos.x + (anim.targetPos.x - anim.spawnPos.x) * ease;
296
+ node.fy = anim.spawnPos.y + (anim.targetPos.y - anim.spawnPos.y) * ease;
297
+ node.fz = anim.spawnPos.z + (anim.targetPos.z - anim.spawnPos.z) * ease;
298
+ } else {
299
+ // Animation done: unpin, let simulation fine-tune position
300
+ node.fx = undefined;
301
+ node.fy = undefined;
302
+ node.fz = undefined;
303
+ birthAnimations.delete(nodeId);
304
+ console.log("[SYKE] ★ Star birth complete:", nodeId);
305
+ }
306
+ }
307
+ })
308
+
223
309
  .d3AlphaDecay(0.008)
224
310
  .d3VelocityDecay(0.3)
225
311
  .warmupTicks(300)
@@ -377,6 +463,26 @@ function getNodeColor(node) {
377
463
  return `rgb(${r},${g},${b})`;
378
464
  }
379
465
 
466
+ // Star birth: bright white → cyan → normal layer color
467
+ const birth = birthAnimations.get(node.id);
468
+ if (birth) {
469
+ const t = Math.min(1, (Date.now() - birth.startTime) / birth.duration);
470
+ const layerHex = LAYER_HEX[node.layer] || "#ff69b4";
471
+ const lr = parseInt(layerHex.slice(1,3),16);
472
+ const lg = parseInt(layerHex.slice(3,5),16);
473
+ const lb = parseInt(layerHex.slice(5,7),16);
474
+ if (t < 0.3) {
475
+ // Phase 1: white → cyan flash
476
+ const p = t / 0.3;
477
+ return `rgb(${Math.round(255*(1-p))},${255},${255})`;
478
+ } else {
479
+ // Phase 2: cyan → normal layer color (smooth blend)
480
+ const p = (t - 0.3) / 0.7;
481
+ const ease = p * p; // ease-in for gentle arrival
482
+ return `rgb(${Math.round(lr*ease)},${Math.round(255+(lg-255)*ease)},${Math.round(255+(lb-255)*ease)})`;
483
+ }
484
+ }
485
+
380
486
  // AI is modifying this node → bright pulsing white/orange
381
487
  if (modifyingNodes.has(node.id)) {
382
488
  const t = Date.now() / 200;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syke1/mcp-server",
3
- "version": "1.3.10",
3
+ "version": "1.3.11",
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",