vectorvesper 2.4.0 → 2.5.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/dist/hooks.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0.0",
|
|
3
3
|
"engineVersion": "4.0.0",
|
|
4
|
-
"generatedAt": "2026-08-
|
|
4
|
+
"generatedAt": "2026-08-29T13:09:19.680Z",
|
|
5
5
|
"contractLevel": "full",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
@@ -2804,8 +2804,8 @@
|
|
|
2804
2804
|
"practices/react-motion-rules.md",
|
|
2805
2805
|
"practices/adaptive-performance.md"
|
|
2806
2806
|
],
|
|
2807
|
-
"docsUrl": "https://vectorvesper.dev/runtime/
|
|
2808
|
-
"labUrl": "https://vectorvesper.dev/runtime/
|
|
2807
|
+
"docsUrl": "https://vectorvesper.dev/runtime/tick",
|
|
2808
|
+
"labUrl": "https://vectorvesper.dev/runtime/tick",
|
|
2809
2809
|
"disclosure": {
|
|
2810
2810
|
"exposeImplementation": false,
|
|
2811
2811
|
"reason": "Reproducing this hook's implementation makes the consumer run a private requestAnimationFrame loop instead of joining the shared FrameConductor. That silently removes the coordination, scheduling and frame-budget governance the runtime exists to provide, and nothing errors when it happens. Copying is a correctness bug, not just a licensing question.",
|
package/dist/index.js
CHANGED
|
@@ -1122,7 +1122,7 @@ Remove ${slug}
|
|
|
1122
1122
|
import fs7 from "fs";
|
|
1123
1123
|
import path7 from "path";
|
|
1124
1124
|
import pc8 from "picocolors";
|
|
1125
|
-
var CLI_VERSION = true ? "2.
|
|
1125
|
+
var CLI_VERSION = true ? "2.5.0" : "0.0.0-dev";
|
|
1126
1126
|
async function infoCommand() {
|
|
1127
1127
|
console.log(pc8.bold(pc8.cyan("\nVector Vesper Diagnostics\n")));
|
|
1128
1128
|
const projectInfo = detectProject();
|
|
@@ -1420,7 +1420,7 @@ async function whoamiCommand() {
|
|
|
1420
1420
|
}
|
|
1421
1421
|
|
|
1422
1422
|
// src/index.ts
|
|
1423
|
-
var version = true ? "2.
|
|
1423
|
+
var version = true ? "2.5.0" : "0.0.0-dev";
|
|
1424
1424
|
var program = new Command();
|
|
1425
1425
|
program.name("vv").description("Vector Vesper CLI \u2014 add visual components to your React project").version(version).option("--verbose", "Show detailed debug output").hook("preAction", (thisCommand) => {
|
|
1426
1426
|
const opts = thisCommand.opts();
|
|
@@ -1462,7 +1462,7 @@ program.command("mcp [action] [client]").description(
|
|
|
1462
1462
|
"Run as an MCP server (stdio) for AI coding agents. `vv mcp install` wires it into your editor; `vv mcp status` checks the setup."
|
|
1463
1463
|
).option("--stdio", "Force server mode even from an interactive terminal").option("-g, --global", "Write user-level config instead of project-level (install)").action(
|
|
1464
1464
|
async (action, client, options) => {
|
|
1465
|
-
const { mcpCommand } = await import("./mcp-
|
|
1465
|
+
const { mcpCommand } = await import("./mcp-QHRDHAME.js");
|
|
1466
1466
|
await mcpCommand(action, { ...options, client });
|
|
1467
1467
|
}
|
|
1468
1468
|
);
|
|
@@ -12,7 +12,7 @@ import pc from "picocolors";
|
|
|
12
12
|
import fs from "fs";
|
|
13
13
|
import os from "os";
|
|
14
14
|
import path from "path";
|
|
15
|
-
var CLIENT_IDS = ["claude", "cursor", "antigravity", "opencode"];
|
|
15
|
+
var CLIENT_IDS = ["claude", "cursor", "windsurf", "antigravity", "opencode"];
|
|
16
16
|
var SERVER_NAME = "vectorvesper";
|
|
17
17
|
var ARGS = ["-y", "vectorvesper@latest", "mcp", "--stdio"];
|
|
18
18
|
function home(...parts) {
|
|
@@ -32,6 +32,14 @@ var CLIENTS = {
|
|
|
32
32
|
globalPath: home(".cursor", "mcp.json"),
|
|
33
33
|
dialect: "mcpServers"
|
|
34
34
|
},
|
|
35
|
+
windsurf: {
|
|
36
|
+
label: "Windsurf",
|
|
37
|
+
// Windsurf reads one user-level file; there is no workspace scope, so a
|
|
38
|
+
// --global flag would have nothing to switch to.
|
|
39
|
+
projectPath: null,
|
|
40
|
+
globalPath: home(".codeium", "windsurf", "mcp_config.json"),
|
|
41
|
+
dialect: "mcpServers"
|
|
42
|
+
},
|
|
35
43
|
antigravity: {
|
|
36
44
|
label: "Antigravity",
|
|
37
45
|
// Workspace scope lives under .agents/; the global file is shared by the
|
|
@@ -138,7 +146,7 @@ function detectClients(cwd = process.cwd()) {
|
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
// src/commands/mcp.ts
|
|
141
|
-
var VERSION = true ? "2.
|
|
149
|
+
var VERSION = true ? "2.5.0" : "0.0.0-dev";
|
|
142
150
|
var CONFIG_SNIPPET = `{
|
|
143
151
|
"mcpServers": {
|
|
144
152
|
"vectorvesper": {
|
|
@@ -285,7 +293,7 @@ ${pc.red("\u2717")} Unknown argument "${action}" for \`vv mcp\`.
|
|
|
285
293
|
return;
|
|
286
294
|
}
|
|
287
295
|
protectStdout();
|
|
288
|
-
const { startMcpServer } = await import("./server-
|
|
296
|
+
const { startMcpServer } = await import("./server-ENS44LZV.js");
|
|
289
297
|
try {
|
|
290
298
|
await startMcpServer();
|
|
291
299
|
} catch (error) {
|
|
@@ -38,6 +38,7 @@ var VV_ACCESSORS = /* @__PURE__ */ new Set([
|
|
|
38
38
|
"getFramePressure",
|
|
39
39
|
"getRendererHealth"
|
|
40
40
|
]);
|
|
41
|
+
var CONTEXT_EVENTS = /* @__PURE__ */ new Set(["webglcontextlost", "webglcontextrestored"]);
|
|
41
42
|
var TEARDOWN_RETURNING = /* @__PURE__ */ new Set(["subscribe", "retain"]);
|
|
42
43
|
var LAYOUT_READ_PROPS = /* @__PURE__ */ new Set([
|
|
43
44
|
"offsetWidth",
|
|
@@ -65,15 +66,45 @@ function calleeName(callee) {
|
|
|
65
66
|
}
|
|
66
67
|
return void 0;
|
|
67
68
|
}
|
|
68
|
-
function isStateSetterCall(callee) {
|
|
69
|
+
function isStateSetterCall(callee, reactSetters) {
|
|
69
70
|
if (!callee || callee.type !== "Identifier") return false;
|
|
70
|
-
|
|
71
|
-
return /^set[A-Z]/.test(name) && name !== "setInterval" && name !== "setTimeout";
|
|
71
|
+
return reactSetters.has(callee.name);
|
|
72
72
|
}
|
|
73
|
-
function
|
|
73
|
+
function collectReactSetters(ast, traverse) {
|
|
74
|
+
const names = /* @__PURE__ */ new Set();
|
|
75
|
+
traverse(ast, {
|
|
76
|
+
VariableDeclarator(p) {
|
|
77
|
+
const { id, init } = p.node;
|
|
78
|
+
if (id?.type !== "ArrayPattern") return;
|
|
79
|
+
const callee = init?.callee;
|
|
80
|
+
const hook = callee?.type === "Identifier" ? callee.name : callee?.property?.name;
|
|
81
|
+
if (hook !== "useState" && hook !== "useReducer") return;
|
|
82
|
+
const second = id.elements?.[1];
|
|
83
|
+
if (second?.type === "Identifier") names.add(second.name);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return names;
|
|
87
|
+
}
|
|
88
|
+
function accessorName(calleePath) {
|
|
89
|
+
const callee = calleePath?.node ?? calleePath;
|
|
74
90
|
const obj = callee?.object;
|
|
75
|
-
if (obj
|
|
76
|
-
|
|
91
|
+
if (!obj) return void 0;
|
|
92
|
+
if (obj.type === "CallExpression" || obj.type === "OptionalCallExpression") {
|
|
93
|
+
return obj.callee?.type === "Identifier" ? obj.callee.name : void 0;
|
|
94
|
+
}
|
|
95
|
+
if (obj.type === "Identifier" && typeof calleePath?.get === "function") {
|
|
96
|
+
const binding = calleePath.scope?.getBinding(obj.name);
|
|
97
|
+
const init = binding?.path?.node?.init;
|
|
98
|
+
if (init && (init.type === "CallExpression" || init.type === "OptionalCallExpression")) {
|
|
99
|
+
return init.callee?.type === "Identifier" ? init.callee.name : void 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
function unsubscribeBinding(callPath) {
|
|
105
|
+
const declarator = callPath?.parentPath;
|
|
106
|
+
if (declarator?.node?.type !== "VariableDeclarator") return void 0;
|
|
107
|
+
return declarator.node.id?.type === "Identifier" ? declarator.node.id.name : void 0;
|
|
77
108
|
}
|
|
78
109
|
function asProgram(cbNode) {
|
|
79
110
|
if (!cbNode) return null;
|
|
@@ -115,9 +146,11 @@ function collect(ast, traverse) {
|
|
|
115
146
|
laneViolations: [],
|
|
116
147
|
orphanSubscriptions: [],
|
|
117
148
|
canvases: [],
|
|
149
|
+
rawGlContexts: [],
|
|
118
150
|
handlesContextLoss: false,
|
|
119
151
|
gatingObserver: []
|
|
120
152
|
};
|
|
153
|
+
const reactSetters = collectReactSetters(ast, traverse);
|
|
121
154
|
const manifest = loadHookManifest();
|
|
122
155
|
const hookByName = /* @__PURE__ */ new Map();
|
|
123
156
|
for (const h of manifest?.hooks ?? []) hookByName.set(h.name, h);
|
|
@@ -135,28 +168,79 @@ function collect(ast, traverse) {
|
|
|
135
168
|
if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) return init;
|
|
136
169
|
return null;
|
|
137
170
|
};
|
|
138
|
-
const scanHotCallback = (cbNode, ctx, fromRaf) => {
|
|
171
|
+
const scanHotCallback = (cbNode, ctx, fromRaf, unsubName) => {
|
|
139
172
|
const file = asProgram(cbNode);
|
|
140
173
|
if (!file) return;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
174
|
+
const isTerminal = (setterPath) => {
|
|
175
|
+
if (!unsubName) return false;
|
|
176
|
+
let node = setterPath;
|
|
177
|
+
while (node) {
|
|
178
|
+
if (node.node?.type === "BlockStatement") {
|
|
179
|
+
const parent = node.parentPath?.node?.type;
|
|
180
|
+
if (parent === "ArrowFunctionExpression" || parent === "FunctionExpression" || parent === "FunctionDeclaration") {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
let found = false;
|
|
184
|
+
node.traverse({
|
|
185
|
+
CallExpression(inner) {
|
|
186
|
+
if (inner.node.callee?.type === "Identifier" && inner.node.callee.name === unsubName) {
|
|
187
|
+
found = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (found) return true;
|
|
145
192
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
193
|
+
node = node.parentPath;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
};
|
|
197
|
+
const isLatched = (setterPath) => {
|
|
198
|
+
let node = setterPath;
|
|
199
|
+
while (node) {
|
|
200
|
+
const parent = node.parentPath;
|
|
201
|
+
if (parent?.node?.type === "IfStatement" && parent.node.test) {
|
|
202
|
+
const guards = /* @__PURE__ */ new Set();
|
|
203
|
+
const collect2 = (n) => {
|
|
204
|
+
if (!n || typeof n.type !== "string") return;
|
|
205
|
+
if (n.type === "Identifier") guards.add(n.name);
|
|
206
|
+
for (const key of Object.keys(n)) {
|
|
207
|
+
const v = n[key];
|
|
208
|
+
if (Array.isArray(v)) v.forEach(collect2);
|
|
209
|
+
else if (v && typeof v.type === "string") collect2(v);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
collect2(parent.node.test);
|
|
213
|
+
let latched = false;
|
|
214
|
+
const check = (inner) => {
|
|
215
|
+
const target = inner.node.left ?? inner.node.argument;
|
|
216
|
+
if (target?.type === "Identifier" && guards.has(target.name)) latched = true;
|
|
217
|
+
};
|
|
218
|
+
if (typeof node.traverse === "function") {
|
|
219
|
+
node.traverse({ AssignmentExpression: check, UpdateExpression: check });
|
|
220
|
+
}
|
|
221
|
+
if (latched) return true;
|
|
150
222
|
}
|
|
223
|
+
node = parent;
|
|
151
224
|
}
|
|
152
|
-
|
|
225
|
+
return false;
|
|
226
|
+
};
|
|
227
|
+
const onCall = (p) => {
|
|
228
|
+
if (!isStateSetterCall(p.node.callee, reactSetters)) return;
|
|
229
|
+
f.setStateInHot.push({
|
|
230
|
+
line: cbNode.loc?.start.line ?? 0,
|
|
231
|
+
ctx,
|
|
232
|
+
fromRaf,
|
|
233
|
+
gated: isTerminal(p) || isLatched(p)
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
traverse(file, { CallExpression: onCall, OptionalCallExpression: onCall });
|
|
153
237
|
};
|
|
154
238
|
const containsStateSetter = (cbNode) => {
|
|
155
239
|
const file = asProgram(cbNode);
|
|
156
240
|
if (!file) return false;
|
|
157
241
|
let found = false;
|
|
158
242
|
const visit = (p) => {
|
|
159
|
-
if (isStateSetterCall(p.node.callee)) found = true;
|
|
243
|
+
if (isStateSetterCall(p.node.callee, reactSetters)) found = true;
|
|
160
244
|
};
|
|
161
245
|
traverse(file, { CallExpression: visit, OptionalCallExpression: visit });
|
|
162
246
|
return found;
|
|
@@ -210,7 +294,13 @@ function collect(ast, traverse) {
|
|
|
210
294
|
f.frameCallbacks.push({ line, lane, via });
|
|
211
295
|
const cb = resolveFunction(argPaths[1]);
|
|
212
296
|
if (!cb) return;
|
|
213
|
-
|
|
297
|
+
const unsubName = unsubscribeBinding(p);
|
|
298
|
+
scanHotCallback(
|
|
299
|
+
cb,
|
|
300
|
+
lane ? `a \`${via}("${lane}")\` frame callback` : `a \`${via}\` frame callback`,
|
|
301
|
+
false,
|
|
302
|
+
unsubName
|
|
303
|
+
);
|
|
214
304
|
if (lane === "input" || lane === "update" || lane === "render") scanLaneDiscipline(cb, lane, via);
|
|
215
305
|
};
|
|
216
306
|
const handleCall = (p) => {
|
|
@@ -237,7 +327,9 @@ function collect(ast, traverse) {
|
|
|
237
327
|
f.hasClearInterval = true;
|
|
238
328
|
break;
|
|
239
329
|
case "addEventListener": {
|
|
240
|
-
|
|
330
|
+
const evNode = node.arguments?.[0];
|
|
331
|
+
const evName = evNode?.type === "StringLiteral" ? evNode.value : null;
|
|
332
|
+
if (!evName || !CONTEXT_EVENTS.has(evName)) f.addEventListener.push(line);
|
|
241
333
|
const ev = node.arguments?.[0];
|
|
242
334
|
if (ev?.type === "StringLiteral" && HOT_EVENTS.has(ev.value)) {
|
|
243
335
|
scanHotCallback(resolveFunction(argPaths[1]), `a "${ev.value}" listener`, false);
|
|
@@ -256,6 +348,13 @@ function collect(ast, traverse) {
|
|
|
256
348
|
case "matchMedia":
|
|
257
349
|
f.hasReducedMotionGuard = true;
|
|
258
350
|
break;
|
|
351
|
+
case "getContext": {
|
|
352
|
+
const kind = node.arguments?.[0];
|
|
353
|
+
if (kind?.type === "StringLiteral" && /^(webgl2?|experimental-webgl)$/.test(kind.value)) {
|
|
354
|
+
f.rawGlContexts.push(line);
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
259
358
|
case "kill":
|
|
260
359
|
case "revert":
|
|
261
360
|
f.hasGsapCleanup = true;
|
|
@@ -265,7 +364,7 @@ function collect(ast, traverse) {
|
|
|
265
364
|
break;
|
|
266
365
|
case "subscribe":
|
|
267
366
|
case "retain": {
|
|
268
|
-
const accessor = accessorName(node.callee);
|
|
367
|
+
const accessor = accessorName(typeof p.get === "function" ? p.get("callee") : node.callee);
|
|
269
368
|
if (!accessor || !VV_ACCESSORS.has(accessor)) break;
|
|
270
369
|
if (name === "subscribe" && accessor === "getConductor") {
|
|
271
370
|
recordFrameCallback(p, argPaths, "getConductor().subscribe");
|
|
@@ -312,7 +411,9 @@ function collect(ast, traverse) {
|
|
|
312
411
|
const v = p.node.value;
|
|
313
412
|
if (v.includes("prefers-reduced-motion")) f.hasReducedMotionGuard = true;
|
|
314
413
|
if (v === "webglcontextlost" || v === "webglcontextrestored") f.handlesContextLoss = true;
|
|
315
|
-
if (/\binfinite\b/.test(v))
|
|
414
|
+
if (/\binfinite\b/.test(v) && /\b\d+(?:\.\d+)?m?s\b/.test(v)) {
|
|
415
|
+
f.looping.push({ line: p.node.loc?.start.line ?? 0, kind: "CSS `infinite` animation" });
|
|
416
|
+
}
|
|
316
417
|
if (/\btransition\b|\banimation\b/.test(v) && /\btransform\b|\ball\b/.test(v)) {
|
|
317
418
|
f.transformAuthorities.push({ line: p.node.loc?.start.line ?? 0, source: "a CSS transition/animation" });
|
|
318
419
|
}
|
|
@@ -484,7 +585,7 @@ function checkMissingCleanup(f, file) {
|
|
|
484
585
|
return out;
|
|
485
586
|
}
|
|
486
587
|
function checkSetStatePerFrame(f, file) {
|
|
487
|
-
const hot = f.setStateInHot.filter((s) => !s.fromRaf || isRafLoop(f));
|
|
588
|
+
const hot = f.setStateInHot.filter((s) => (!s.fromRaf || isRafLoop(f)) && !s.gated);
|
|
488
589
|
if (hot.length === 0) return [];
|
|
489
590
|
const zeroReRender = f.vvHooks.find((v) => v.hook.runtime.reRendersPerFrame === 0);
|
|
490
591
|
const first = hot[0];
|
|
@@ -568,7 +669,9 @@ function checkRenderQualityProps(f, file) {
|
|
|
568
669
|
return out;
|
|
569
670
|
}
|
|
570
671
|
function checkContextLoss(f, file) {
|
|
571
|
-
|
|
672
|
+
const r3fCanvas = f.usesR3F && f.canvases.length > 0;
|
|
673
|
+
const rawContext = f.rawGlContexts.length > 0;
|
|
674
|
+
if (!r3fCanvas && !rawContext) return [];
|
|
572
675
|
const handled = f.handlesContextLoss || f.vvImports.has("useRenderQuality") || f.vvImports.has("getRendererHealth");
|
|
573
676
|
if (handled) return [];
|
|
574
677
|
return [
|
|
@@ -576,15 +679,16 @@ function checkContextLoss(f, file) {
|
|
|
576
679
|
rule: "webgl-context-loss",
|
|
577
680
|
severity: "warning",
|
|
578
681
|
file,
|
|
579
|
-
line: f.canvases[0].line,
|
|
682
|
+
line: r3fCanvas ? f.canvases[0].line : f.rawGlContexts[0],
|
|
580
683
|
message: "Nothing in this file notices a lost graphics context. When the browser takes one away the canvas goes black permanently and nothing is logged, so it reads as a rendering bug rather than a recoverable event.",
|
|
581
|
-
fix: "Spread `useRenderQuality`'s props onto the canvas and key it on a scene gate's `generation` \u2014 the `onCreated` it returns attaches the listener and reports the loss, and the moving key rebuilds the tree against a fresh context. Failing that, attach a `webglcontextlost` listener that calls `preventDefault()` (without it some drivers refuse a replacement context outright) and report to `getRendererHealth()`.",
|
|
684
|
+
fix: (r3fCanvas ? "Spread `useRenderQuality`'s props onto the canvas and key it on a scene gate's `generation` \u2014 the `onCreated` it returns attaches the listener and reports the loss, and the moving key rebuilds the tree against a fresh context. Failing that, attach a " : "You created this context by hand, so the adapter does not apply. Attach a ") + "`webglcontextlost` listener that calls `preventDefault()` (without it some drivers refuse a replacement context outright) and report to `getRendererHealth()`.",
|
|
582
685
|
nudge: 'Call `get_pattern("lazy-3d-section")` for the whole wiring.'
|
|
583
686
|
}
|
|
584
687
|
];
|
|
585
688
|
}
|
|
586
689
|
function checkHandRolledGate(f, file) {
|
|
587
|
-
|
|
690
|
+
const ownsGpu = f.usesR3F && f.canvases.length > 0 || f.rawGlContexts.length > 0;
|
|
691
|
+
if (!ownsGpu) return [];
|
|
588
692
|
if (f.gatingObserver.length === 0 || f.vvImports.has("useSceneGate")) return [];
|
|
589
693
|
return [
|
|
590
694
|
{
|
|
@@ -871,7 +975,7 @@ function formatPlan({ building, using }) {
|
|
|
871
975
|
}
|
|
872
976
|
|
|
873
977
|
// src/mcp/server.ts
|
|
874
|
-
var VERSION = true ? "2.
|
|
978
|
+
var VERSION = true ? "2.5.0" : "0.0.0-dev";
|
|
875
979
|
function text(body) {
|
|
876
980
|
return { content: [{ type: "text", text: body }] };
|
|
877
981
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vectorvesper",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Motion runtime and WebGL/R3F component CLI for React, with an MCP server that tells your coding agent which primitive to use, how to wire it, and when not to.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|