Setting the file. One moment.
Scanner Driven · Vercel Optimize · vercel-labs/agent-skills · Skills Docs
ContentsBack to the top of the page Next
Bundled file Select Candidates
lib/gates/ scanner-driven.mjs
JavaScript · 122 lines · 5 KB
1
,
9 billingDimension: 'edge-requests' , priority: 40 },
10 { id: 'rendering_candidate' , patterns: [ 'force-dynamic' , 'headers-in-page' ], threshold: 3 ,
11 billingDimension: 'function-duration' , priority: 35 },
12 { id: 'use_cache_date_stamp' , patterns: [ 'use-cache-date-stamp' ], threshold: 1 ,
13 billingDimension: 'isr' , priority: 45 },
14 { id: 'cache_components_suspense_dedupe' , patterns: [ 'cache-components-suspense-dedupe' ], threshold: 1 ,
15 billingDimension: 'function-duration' , priority: 38 },
16 ];
17
18 export const metadata = {
19 id: 'scanner-driven' ,
20 threshold: 'per-kind: scanner matches.length >= threshold' ,
21 billingDimension: 'mixed' ,
22 scope: 'mixed' ,
23 sourceCitation: 'vercel-optimize gate threshold' ,
24 description:
25 'Configured kinds emitted from scanner output. Each requires a minimum match count to avoid noise. Findings on cold-path or unmappable files are dropped unless the underlying scanner is trafficIndependent.' ,
26 };
27
28 export function gate ( signals ) {
29 const findings = signals.codebase?.findings ?? [];
30 if (findings. length === 0 ) return [];
31
32 const candidates = [];
33
34 for ( const cfg of SCANNER_GATES ) {
35 const matched = findings. filter (( f ) => {
36 if ( ! cfg.patterns. includes (f.pattern)) return false ;
37 if ( ! f.trafficIndependent) {
38 if ( ! f.o11ySignal || f.o11ySignal === 'scanner-only' ) return false ;
39 if (f.o11ySignal === 'COLD-PATH' ) return false ;
40 if (f.o11ySignal === 'NO-ROUTE-MAPPING' ) return false ;
41 }
42 if (cfg.id === 'cache_header_gap' && observedCacheHitRate (f.o11ySignal) >= 90 ) return false ;
43 return true ;
44 });
45
46 for ( const group of groupFindings (cfg, matched)) {
47 if (group.findings. length < cfg.threshold) continue ;
48 candidates. push ( candidateForGroup (cfg, group));
49 }
50 }
51
52 return candidates;
53 }
54
55 function groupFindings ( cfg , findings ) {
56 const groups = new Map ();
57 for ( const finding of findings) {
58 const scope = finding.route ? 'route' : 'file' ;
59 const target = scope === 'route' ? finding.route : finding.file;
60 if ( ! target) continue ;
61 const key = `${ cfg . id }:${ scope }:${ target }` ;
62 if ( ! groups. has (key)) groups. set (key, { scope, target, findings: [] });
63 groups. get (key).findings. push (finding);
64 }
65 return [ ... groups. values ()];
66 }
67
68 function candidateForGroup ( cfg , group ) {
69 const matched = group.findings;
70 const route = group.scope === 'route' ? group.target : null ;
71 return {
72 kind: cfg.id,
73 scope: group.scope,
74 route,
75 files: uniqueStrings (matched. map (( m ) => m.file)). slice ( 0 , 6 ),
76 priority: cfg.priority + Math. min (matched. length , 10 ),
77 confidence: 0.88 ,
78 o11ySignal: matched
79 . map (( m ) => m.o11ySignal)
80 . find (( s ) => s && s !== 'COLD-PATH' && s !== 'NO-ROUTE-MAPPING' )
81 ?? 'scanner-only' ,
82 reason: `${ matched . length } ${ cfg . patterns . join ( '+' ) } finding(s)` ,
83 question: questionFor (cfg.id, matched),
84 evidence: {
85 scannerMatches: matched. length ,
86 patterns: cfg.patterns,
87 scope: group.scope,
88 route,
89 sampleFiles: matched. slice ( 0 , 3 ). map (( m ) => ({ file: m.file, line: m.line })),
90 },
91 };
92 }
93
94 function questionFor ( kindId , matched ) {
95 const sample = matched. slice ( 0 , 3 ). map (( m ) => m.file). join ( ', ' );
96 switch (kindId) {
97 case 'image_optimization' :
98 return `Which raw <img> tags in ${ sample } should move to next/image (or the framework's image component)?` ;
99 case 'cache_header_gap' :
100 return `Should the route handlers in ${ sample } set Cache-Control with s-maxage to serve from the CDN?` ;
101 case 'rendering_candidate' :
102 return `Why are the routes in ${ sample } forced to dynamic rendering, and can any of them tolerate ISR or static generation?` ;
103 case 'use_cache_date_stamp' :
104 return `Which 'use cache' boundaries in ${ sample } embed new Date()/Date.now()/Math.random() that destabilizes cache keys, and can the timestamps be hoisted to a build constant or moved into a client useEffect?` ;
105 case 'cache_components_suspense_dedupe' :
106 return `In ${ sample }, which repeated fetch or helper is being re-invoked across separate <Suspense> boundaries, and can the promise be hoisted to the page level or moved to 'use cache: remote' for cross-boundary dedupe?` ;
107 default :
108 return `Investigate ${ matched . length } ${ kindId } finding(s).` ;
109 }
110 }
111
112 function uniqueStrings ( values ) {
113 return [ ...new Set (values. filter (( v ) => typeof v === 'string' && v. length > 0 ))];
114 }
115
116 function observedCacheHitRate ( signal ) {
117 if ( typeof signal !== 'string' ) return null ;
118 const m = / \b cache=( [\d.] + )%/ . exec (signal);
119 if ( ! m) return null ;
120 const n = Number (m[ 1 ]);
121 return Number. isFinite (n) ? n : null ;
122 }