playbook/antigravity-awesome-skills/skills/vercel-optimize/scripts/deep-dive.mjs

320 lines
12 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Runs AFTER gate-investigations.mjs and BEFORE any sub-agent reads source.
// Attaches per-candidate evidence.deepDive to gate.toLaunch + gate.platform.
// Byte-stable apart from totalWallMs; each CLI query is isolated.
import { readFile } from 'node:fs/promises';
import { queryMetric, readProjectJson, resolveCommandScope } from '../lib/vercel.mjs';
import { specsForCandidate, mergeIntoEvidence, SCANNER_KINDS, TIME_WINDOW } from '../lib/deep-dive.mjs';
const SCHEMA_VERSION = '1.0';
const log = (...a) => console.error('[deep-dive]', ...a);
async function main() {
// --cwd is load-bearing: the Vercel CLI resolves project/team from cwd's
// .vercel/project.json. Outside the project, metric queries silently hit the
// wrong team and look like "no traffic". We hard-fail on mismatch below.
const positional = [];
let explicitCwd = null;
for (let i = 2; i < process.argv.length; i++) {
const a = process.argv[i];
if (a === '--cwd' && i + 1 < process.argv.length) {
explicitCwd = process.argv[++i];
} else if (a.startsWith('--cwd=')) {
explicitCwd = a.slice('--cwd='.length);
} else {
positional.push(a);
}
}
const mergedPath = positional[0];
const gatePath = positional[1];
if (!mergedPath || !gatePath) {
console.error('usage: node scripts/deep-dive.mjs <merged.json> <gate.json> [--cwd <project-dir>]');
process.exit(1);
}
const [merged, gate] = await Promise.all([
readFile(mergedPath, 'utf-8').then(JSON.parse),
readFile(gatePath, 'utf-8').then(JSON.parse),
]);
if (explicitCwd) {
process.chdir(explicitCwd);
log(`cwd: ${process.cwd()} (via --cwd)`);
}
const link = await readProjectJson(process.cwd());
if (!link) {
console.error(`[deep-dive] FATAL: cwd ${process.cwd()} has no .vercel/project.json or .vercel/repo.json.`);
console.error(' Re-run with --cwd <project-dir> pointing at the linked project, or cd into it first.');
console.error(' (The Vercel CLI resolves team/project from cwd; without a .vercel/ linkage every query returns empty rows for the wrong team.)');
process.exit(2);
}
if (merged.projectId && link.projectId !== merged.projectId) {
console.error('[deep-dive] FATAL: cwd .vercel/ links a different project than merged.json.');
console.error(' Re-run with --cwd <dir-linked-to-the-collected-project>.');
process.exit(2);
}
if (merged.orgId && link.orgId && link.orgId !== merged.orgId) {
console.error('[deep-dive] FATAL: cwd .vercel/ links the project to a different Vercel scope than signals.json.');
console.error(' Re-run with --cwd <dir-linked-to-the-collected-project>, or rerun collect-signals.mjs from the intended app directory.');
process.exit(2);
}
log(`cwd link OK (source ${link.source})`);
const commandScope = await resolveDeepDiveCommandScope(merged, link);
if (!commandScope.ok) {
console.error(`[deep-dive] FATAL: could not resolve a CLI-safe Vercel scope (${commandScope.detail ?? commandScope.error ?? 'unknown'}).`);
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>`.');
process.exit(2);
}
if (typeof commandScope.cliScope === 'string' && /^(team|usr)_/.test(commandScope.cliScope)) {
console.error('[deep-dive] FATAL: commandScope.cliScope is a raw account ID, not a CLI-safe scope.');
console.error(' Re-run collect-signals.mjs with the current skill so deep-dive queries use the same team as the broad pass.');
process.exit(2);
}
const commandAccountId = commandScope.teamId ?? commandScope.userId ?? null;
if (commandAccountId && link.orgId && link.orgId !== commandAccountId) {
console.error('[deep-dive] FATAL: cwd .vercel/ links the project to a different Vercel scope than commandScope.');
console.error(' Re-run with --cwd <dir-linked-to-the-collected-project>, or rerun collect-signals.mjs from the intended app directory.');
process.exit(2);
}
const scope = commandScope.cliScope || undefined;
log(`command scope resolved (source=${commandScope.source}; scoped=${scope ? 'yes' : 'no'})`);
const toLaunch = Array.isArray(gate.toLaunch) ? gate.toLaunch : [];
const platform = Array.isArray(gate.platform) ? gate.platform : [];
log(`enriching ${toLaunch.length} toLaunch + ${platform.length} platform candidate(s) (window=${TIME_WINDOW})`);
const t0 = Date.now();
const errors = [];
// Flatten {candidate, spec}, fire all CLI calls in one Promise.all, re-group.
// Avoids per-candidate sequentiality.
const allCandidates = [...toLaunch.map((c, i) => ({ c, group: 'toLaunch', i })),
...platform.map((c, i) => ({ c, group: 'platform', i }))];
const flatJobs = [];
const skipNotes = new Map();
for (const entry of allCandidates) {
const specs = specsForCandidate(entry.c);
if (specs.length === 0) {
if (SCANNER_KINDS.has(entry.c.kind)) {
skipNotes.set(`${entry.group}:${entry.i}`, 'scanner-driven (no deep-dive needed)');
} else if (entry.c.kind === 'platform_fluid_compute') {
skipNotes.set(`${entry.group}:${entry.i}`, 'reused from broad pass (fnStartTypeByRoute)');
} else {
skipNotes.set(`${entry.group}:${entry.i}`, `no deep-dive spec for kind=${entry.c.kind}`);
}
continue;
}
for (const spec of specs) {
flatJobs.push({ entry, spec });
}
}
// Cut CLI calls two ways: (1) extract per-route slices already collected in
// the broad pass; (2) dedupe identical queries across candidates (same route
// can fire multiple gates wanting the same metric).
let extractedFromBroadPass = 0;
let dedupedQueryHits = 0;
const broadPassResults = [];
const remainingJobs = [];
for (const job of flatJobs) {
const extracted = tryExtractFromBroadPass(job.spec, merged);
if (extracted) {
broadPassResults.push({ entry: job.entry, spec: job.spec, ok: true, ...extracted });
extractedFromBroadPass++;
} else {
remainingJobs.push(job);
}
}
// One CLI call per unique dedup key; jobs sharing a key share the result.
const queryGroups = new Map();
for (const job of remainingJobs) {
const key = queryKey(job.spec, scope);
if (!queryGroups.has(key)) {
queryGroups.set(key, { spec: job.spec, jobs: [] });
}
queryGroups.get(key).jobs.push(job);
}
dedupedQueryHits = remainingJobs.length - queryGroups.size;
const totalCliQueries = queryGroups.size;
log(`${flatJobs.length} specs total: ${extractedFromBroadPass} extracted from broad-pass, ${dedupedQueryHits} deduped, ${totalCliQueries} CLI queries to run`);
const groupResults = await Promise.all([...queryGroups.values()].map(async ({ spec, jobs }) => {
const r = await queryMetric(spec.metricId, {
aggregation: spec.aggregation,
groupBy: spec.groupBy,
filter: spec.filter,
since: spec.since,
limit: spec.limit,
scope,
});
return { spec, jobs, response: r };
}));
const cliResults = [];
for (const { spec, jobs, response: r } of groupResults) {
if (!r.ok) {
for (const job of jobs) {
errors.push({
candidateGroup: job.entry.group,
candidateIndex: job.entry.i,
kind: job.entry.c.kind,
route: job.entry.c.route ?? job.entry.c.hostname ?? null,
specId: spec.id,
code: r.code,
});
cliResults.push({ entry: job.entry, spec, ok: false, error: r.code });
}
continue;
}
const norm = normalizeResponse(r.data, spec);
for (const job of jobs) {
cliResults.push({ entry: job.entry, spec, ok: true, ...norm });
}
}
const results = [...broadPassResults, ...cliResults];
const wallMs = Date.now() - t0;
log(`done in ${wallMs}ms (${totalCliQueries} CLI queries, ${extractedFromBroadPass} extracted from broad-pass, ${dedupedQueryHits} deduped, ${errors.length} errors)`);
const byCandidate = new Map();
for (const res of results) {
const k = `${res.entry.group}:${res.entry.i}`;
if (!byCandidate.has(k)) byCandidate.set(k, []);
byCandidate.get(k).push(res);
}
function enrich(c, group, i) {
const k = `${group}:${i}`;
const note = skipNotes.get(k);
if (note) {
return {
...c,
evidence: {
...(c.evidence ?? {}),
deepDive: { note },
},
};
}
const list = byCandidate.get(k) ?? [];
const merged = mergeIntoEvidence(list);
return {
...c,
evidence: {
...(c.evidence ?? {}),
deepDive: merged,
},
};
}
const enrichedToLaunch = toLaunch.map((c, i) => enrich(c, 'toLaunch', i));
const enrichedPlatform = platform.map((c, i) => enrich(c, 'platform', i));
const out = {
schemaVersion: SCHEMA_VERSION,
appliedAt: new Date().toISOString(),
candidatesEnriched: toLaunch.length + platform.length,
specsTotal: flatJobs.length,
queriesRun: totalCliQueries,
extractedFromBroadPass,
dedupedQueryHits,
totalWallMs: wallMs,
errors,
toLaunch: enrichedToLaunch,
platform: enrichedPlatform,
};
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
}
async function resolveDeepDiveCommandScope(merged, link) {
const linkedOrgId = merged.orgId ?? link.orgId ?? null;
if (merged.commandScope?.ok && (merged.commandScope.cliScope || !linkedOrgId)) {
return merged.commandScope;
}
if (merged.commandScope && merged.commandScope.ok === false) return merged.commandScope;
return await resolveCommandScope({
projectId: merged.projectId ?? link.projectId ?? null,
orgId: merged.orgId ?? link.orgId ?? null,
});
}
// Reduce CLI response to {value} or {rows:[{value,...dims}]}. The per-metric
// underscore field (e.g. vercel_function_invocation_count_sum) gets renamed
// to `value` for compactness.
function normalizeResponse(data, spec) {
if (!data || !Array.isArray(data.summary)) return { value: null };
const field = `${spec.metricId.replace(/\./g, '_')}_${spec.aggregation}`;
if (spec.groupBy.length === 0) {
const first = data.summary[0];
if (!first) return { value: null };
const v = first[field];
return { value: typeof v === 'number' ? round4(v) : null };
}
const rows = data.summary.map((row) => {
const out = { value: typeof row[field] === 'number' ? round4(row[field]) : null };
for (const dim of spec.groupBy) {
if (row[dim] !== undefined) out[dim] = row[dim];
}
return out;
});
return { rows };
}
function round4(n) {
if (!Number.isFinite(n)) return n;
return Math.round(n * 10000) / 10000;
}
// Skip the CLI call when broad-pass already collected the same metric grouped
// by [route, dim]. Returns {rows} on hit, null on miss. Cuts rate-limit pressure
// for per-route slice specs (startTypeSplit, cacheBreakdown, methodDistribution).
function tryExtractFromBroadPass(spec, merged) {
const eq = spec.broadPassEquivalent;
if (!eq) return null;
const broadRows = merged?.metrics?.[eq.key]?.rows;
if (!Array.isArray(broadRows)) return null;
const rows = [];
for (const row of broadRows) {
if (row.route !== eq.routeFilter) continue;
const out = { value: typeof row.value === 'number' ? row.value : null };
for (const dim of (eq.projectDims ?? [])) {
if (row[dim] !== undefined) out[dim] = row[dim];
}
rows.push(out);
}
// Zero rows ≠ "no data" — broad-pass row limit may have truncated the route.
// Fall through to CLI so the caller gets a definitive answer.
if (rows.length === 0) return null;
return { rows };
}
// Two specs sharing this key answer the same question — one CLI call serves both.
// Must include everything that affects the CLI's arg list.
function queryKey(spec, scope) {
const groupBy = [...(spec.groupBy ?? [])].sort();
return JSON.stringify({
metricId: spec.metricId,
aggregation: spec.aggregation,
groupBy,
filter: spec.filter ?? null,
since: spec.since ?? null,
limit: spec.limit ?? null,
scope: scope ?? null,
});
}
main().catch((err) => {
console.error('[deep-dive] FAILED:', err.message);
console.error(err.stack);
process.exit(1);
});