Setting the file. One moment.
Deep Dive · Vercel Optimize · vercel-labs/agent-skills · Skills Docs
ContentsBack to the top of the page scripts/ deep-dive.mjs
JavaScript · 319 lines · 12 KB
11 const log = ( ... a ) => console. error ( '[deep-dive]' , ... a);
12
13 async function main () {
14 // --cwd is load-bearing: the Vercel CLI resolves project/team from cwd's
15 // .vercel/project.json. Outside the project, metric queries silently hit the
16 // wrong team and look like "no traffic". We hard-fail on mismatch below.
17 const positional = [];
18 let explicitCwd = null ;
19 for ( let i = 2 ; i < process.argv. length ; i ++ ) {
20 const a = process.argv[i];
21 if (a === '--cwd' && i + 1 < process.argv. length ) {
22 explicitCwd = process.argv[ ++ i];
23 } else if (a. startsWith ( '--cwd=' )) {
24 explicitCwd = a. slice ( '--cwd=' . length );
25 } else {
26 positional. push (a);
27 }
28 }
29 const mergedPath = positional[ 0 ];
30 const gatePath = positional[ 1 ];
31 if ( ! mergedPath || ! gatePath) {
32 console. error ( 'usage: node scripts/deep-dive.mjs <merged.json> <gate.json> [--cwd <project-dir>]' );
33 process. exit ( 1 );
34 }
35
36 const [ merged , gate ] = await Promise . all ([
37 readFile (mergedPath, 'utf-8' ). then ( JSON .parse),
38 readFile (gatePath, 'utf-8' ). then ( JSON .parse),
39 ]);
40
41 if (explicitCwd) {
42 process. chdir (explicitCwd);
43 log ( `cwd: ${ process . cwd () } (via --cwd)` );
44 }
45
46 const link = await readProjectJson (process. cwd ());
47 if ( ! link) {
48 console. error ( `[deep-dive] FATAL: cwd ${ process . cwd () } has no .vercel/project.json or .vercel/repo.json.` );
49 console. error ( ' Re-run with --cwd <project-dir> pointing at the linked project, or cd into it first.' );
50 console. error ( ' (The Vercel CLI resolves team/project from cwd; without a .vercel/ linkage every query returns empty rows for the wrong team.)' );
51 process. exit ( 2 );
52 }
53 if (merged.projectId && link.projectId !== merged.projectId) {
54 console. error ( '[deep-dive] FATAL: cwd .vercel/ links a different project than merged.json.' );
55 console. error ( ' Re-run with --cwd <dir-linked-to-the-collected-project>.' );
56 process. exit ( 2 );
57 }
58 if (merged.orgId && link.orgId && link.orgId !== merged.orgId) {
59 console. error ( '[deep-dive] FATAL: cwd .vercel/ links the project to a different Vercel scope than signals.json.' );
60 console. error ( ' Re-run with --cwd <dir-linked-to-the-collected-project>, or rerun collect-signals.mjs from the intended app directory.' );
61 process. exit ( 2 );
62 }
63 log ( `cwd link OK (source ${ link . source })` );
64
65 const commandScope = await resolveDeepDiveCommandScope (merged, link);
66 if ( ! commandScope.ok) {
67 console. error ( `[deep-dive] FATAL: could not resolve a CLI-safe Vercel scope (${ commandScope . detail ?? commandScope . error ?? 'unknown'}).` );
68 console. error ( ' Re-run collect-signals.mjs with the current skill, run `vercel switch <team>`, or re-link with `vercel link --yes --project <project> --team <team-slug>`.' );
69 process. exit ( 2 );
70 }
71 if ( typeof commandScope.cliScope === 'string' && / ^ (team | usr)_/ . test (commandScope.cliScope)) {
72 console. error ( '[deep-dive] FATAL: commandScope.cliScope is a raw account ID, not a CLI-safe scope.' );
73 console. error ( ' Re-run collect-signals.mjs with the current skill so deep-dive queries use the same team as the broad pass.' );
74 process. exit ( 2 );
75 }
76 const commandAccountId = commandScope.teamId ?? commandScope.userId ?? null ;
77 if (commandAccountId && link.orgId && link.orgId !== commandAccountId) {
78 console. error ( '[deep-dive] FATAL: cwd .vercel/ links the project to a different Vercel scope than commandScope.' );
79 console. error ( ' Re-run with --cwd <dir-linked-to-the-collected-project>, or rerun collect-signals.mjs from the intended app directory.' );
80 process. exit ( 2 );
81 }
82 const scope = commandScope.cliScope || undefined ;
83 log ( `command scope resolved (source=${ commandScope . source }; scoped=${ scope ? 'yes' : 'no'})` );
84
85 const toLaunch = Array. isArray (gate.toLaunch) ? gate.toLaunch : [];
86 const platform = Array. isArray (gate.platform) ? gate.platform : [];
87
88 log ( `enriching ${ toLaunch . length } toLaunch + ${ platform . length } platform candidate(s) (window=${ TIME_WINDOW })` );
89
90 const t0 = Date. now ();
91 const errors = [];
92
93 // Flatten {candidate, spec}, fire all CLI calls in one Promise.all, re-group.
94 // Avoids per-candidate sequentiality.
95 const allCandidates = [ ... toLaunch. map (( c , i ) => ({ c, group: 'toLaunch' , i })),
96 ... platform. map (( c , i ) => ({ c, group: 'platform' , i }))];
97
98 const flatJobs = [];
99 const skipNotes = new Map ();
100
101 for ( const entry of allCandidates) {
102 const specs = specsForCandidate (entry.c);
103 if (specs. length === 0 ) {
104 if ( SCANNER_KINDS . has (entry.c.kind)) {
105 skipNotes. set ( `${ entry . group }:${ entry . i }` , 'scanner-driven (no deep-dive needed)' );
106 } else if (entry.c.kind === 'platform_fluid_compute' ) {
107 skipNotes. set ( `${ entry . group }:${ entry . i }` , 'reused from broad pass (fnStartTypeByRoute)' );
108 } else {
109 skipNotes. set ( `${ entry . group }:${ entry . i }` , `no deep-dive spec for kind=${ entry . c . kind }` );
110 }
111 continue ;
112 }
113 for ( const spec of specs) {
114 flatJobs. push ({ entry, spec });
115 }
116 }
117
118 // Cut CLI calls two ways: (1) extract per-route slices already collected in
119 // the broad pass; (2) dedupe identical queries across candidates (same route
120 // can fire multiple gates wanting the same metric).
121 let extractedFromBroadPass = 0 ;
122 let dedupedQueryHits = 0 ;
123 const broadPassResults = [];
124 const remainingJobs = [];
125 for ( const job of flatJobs) {
126 const extracted = tryExtractFromBroadPass (job.spec, merged);
127 if (extracted) {
128 broadPassResults. push ({ entry: job.entry, spec: job.spec, ok: true , ... extracted });
129 extractedFromBroadPass ++ ;
130 } else {
131 remainingJobs. push (job);
132 }
133 }
134
135 // One CLI call per unique dedup key; jobs sharing a key share the result.
136 const queryGroups = new Map ();
137 for ( const job of remainingJobs) {
138 const key = queryKey (job.spec, scope);
139 if ( ! queryGroups. has (key)) {
140 queryGroups. set (key, { spec: job.spec, jobs: [] });
141 }
142 queryGroups. get (key).jobs. push (job);
143 }
144 dedupedQueryHits = remainingJobs. length - queryGroups.size;
145
146 const totalCliQueries = queryGroups.size;
147 log ( `${ flatJobs . length } specs total: ${ extractedFromBroadPass } extracted from broad-pass, ${ dedupedQueryHits } deduped, ${ totalCliQueries } CLI queries to run` );
148
149 const groupResults = await Promise . all ([ ... queryGroups. values ()]. map ( async ({ spec , jobs }) => {
150 const r = await queryMetric (spec.metricId, {
151 aggregation: spec.aggregation,
152 groupBy: spec.groupBy,
153 filter: spec.filter,
154 since: spec.since,
155 limit: spec.limit,
156 scope,
157 });
158 return { spec, jobs, response: r };
159 }));
160
161 const cliResults = [];
162 for ( const { spec , jobs , response : r } of groupResults) {
163 if ( ! r.ok) {
164 for ( const job of jobs) {
165 errors. push ({
166 candidateGroup: job.entry.group,
167 candidateIndex: job.entry.i,
168 kind: job.entry.c.kind,
169 route: job.entry.c.route ?? job.entry.c.hostname ?? null ,
170 specId: spec.id,
171 code: r.code,
172 });
173 cliResults. push ({ entry: job.entry, spec, ok: false , error: r.code });
174 }
175 continue ;
176 }
177 const norm = normalizeResponse (r.data, spec);
178 for ( const job of jobs) {
179 cliResults. push ({ entry: job.entry, spec, ok: true , ... norm });
180 }
181 }
182 const results = [ ... broadPassResults, ... cliResults];
183
184 const wallMs = Date. now () - t0;
185 log ( `done in ${ wallMs }ms (${ totalCliQueries } CLI queries, ${ extractedFromBroadPass } extracted from broad-pass, ${ dedupedQueryHits } deduped, ${ errors . length } errors)` );
186
187 const byCandidate = new Map ();
188 for ( const res of results) {
189 const k = `${ res . entry . group }:${ res . entry . i }` ;
190 if ( ! byCandidate. has (k)) byCandidate. set (k, []);
191 byCandidate. get (k). push (res);
192 }
193
194 function enrich ( c , group , i ) {
195 const k = `${ group }:${ i }` ;
196 const note = skipNotes. get (k);
197 if (note) {
198 return {
199 ... c,
200 evidence: {
201 ... (c.evidence ?? {}),
202 deepDive: { note },
203 },
204 };
205 }
206 const list = byCandidate. get (k) ?? [];
207 const merged = mergeIntoEvidence (list);
208 return {
209 ... c,
210 evidence: {
211 ... (c.evidence ?? {}),
212 deepDive: merged,
213 },
214 };
215 }
216
217 const enrichedToLaunch = toLaunch. map (( c , i ) => enrich (c, 'toLaunch' , i));
218 const enrichedPlatform = platform. map (( c , i ) => enrich (c, 'platform' , i));
219
220 const out = {
221 schemaVersion: SCHEMA_VERSION ,
222 appliedAt: new Date (). toISOString (),
223 candidatesEnriched: toLaunch. length + platform. length ,
224 specsTotal: flatJobs. length ,
225 queriesRun: totalCliQueries,
226 extractedFromBroadPass,
227 dedupedQueryHits,
228 totalWallMs: wallMs,
229 errors,
230 toLaunch: enrichedToLaunch,
231 platform: enrichedPlatform,
232 };
233
234 process.stdout. write ( JSON . stringify (out, null , 2 ) + ' \n ' );
235 }
236
237 async function resolveDeepDiveCommandScope ( merged , link ) {
238 const linkedOrgId = merged.orgId ?? link.orgId ?? null ;
239 if (merged.commandScope?.ok && (merged.commandScope.cliScope || ! linkedOrgId)) {
240 return merged.commandScope;
241 }
242 if (merged.commandScope && merged.commandScope.ok === false ) return merged.commandScope;
243
244 return await resolveCommandScope ({
245 projectId: merged.projectId ?? link.projectId ?? null ,
246 orgId: merged.orgId ?? link.orgId ?? null ,
247 });
248 }
249
250 // Reduce CLI response to {value} or {rows:[{value,...dims}]}. The per-metric
251 // underscore field (e.g. vercel_function_invocation_count_sum) gets renamed
252 // to `value` for compactness.
253 function normalizeResponse ( data , spec ) {
254 if ( ! data || ! Array. isArray (data.summary)) return { value: null };
255 const field = `${ spec . metricId . replace ( / \. / g , '_' ) }_${ spec . aggregation }` ;
256 if (spec.groupBy. length === 0 ) {
257 const first = data.summary[ 0 ];
258 if ( ! first) return { value: null };
259 const v = first[field];
260 return { value: typeof v === 'number' ? round4 (v) : null };
261 }
262 const rows = data.summary. map (( row ) => {
263 const out = { value: typeof row[field] === 'number' ? round4 (row[field]) : null };
264 for ( const dim of spec.groupBy) {
265 if (row[dim] !== undefined ) out[dim] = row[dim];
266 }
267 return out;
268 });
269 return { rows };
270 }
271
272 function round4 ( n ) {
273 if ( ! Number. isFinite (n)) return n;
274 return Math. round (n * 10000 ) / 10000 ;
275 }
276
277 // Skip the CLI call when broad-pass already collected the same metric grouped
278 // by [route, dim]. Returns {rows} on hit, null on miss. Cuts rate-limit pressure
279 // for per-route slice specs (startTypeSplit, cacheBreakdown, methodDistribution).
280 function tryExtractFromBroadPass ( spec , merged ) {
281 const eq = spec.broadPassEquivalent;
282 if ( ! eq) return null ;
283 const broadRows = merged?.metrics?.[eq.key]?.rows;
284 if ( ! Array. isArray (broadRows)) return null ;
285 const rows = [];
286 for ( const row of broadRows) {
287 if (row.route !== eq.routeFilter) continue ;
288 const out = { value: typeof row.value === 'number' ? row.value : null };
289 for ( const dim of (eq.projectDims ?? [])) {
290 if (row[dim] !== undefined ) out[dim] = row[dim];
291 }
292 rows. push (out);
293 }
294 // Zero rows ≠ "no data" — broad-pass row limit may have truncated the route.
295 // Fall through to CLI so the caller gets a definitive answer.
296 if (rows. length === 0 ) return null ;
297 return { rows };
298 }
299
300 // Two specs sharing this key answer the same question — one CLI call serves both.
301 // Must include everything that affects the CLI's arg list.
302 function queryKey ( spec , scope ) {
303 const groupBy = [ ... (spec.groupBy ?? [])]. sort ();
304 return JSON . stringify ({
305 metricId: spec.metricId,
306 aggregation: spec.aggregation,
307 groupBy,
308 filter: spec.filter ?? null ,
309 since: spec.since ?? null ,
310 limit: spec.limit ?? null ,
311 scope: scope ?? null ,
312 });
313 }
314
315 main (). catch (( err ) => {
316 console. error ( '[deep-dive] FAILED:' , err.message);
317 console. error (err.stack);
318 process. exit ( 1 );
319 });