@@ -5,7 +5,7 @@ import { cssValue, extractCss } from '#design-diff/extract/css'
55import { extractDocument } from '#design-diff/extract/documents'
66import { extractTsx } from '#design-diff/extract/tsx'
77import { GitReader } from '#design-diff/git'
8- import { causalSources , groupFindings } from '#design-diff/group'
8+ import { groupFindings } from '#design-diff/group'
99import { renderingLock } from '#design-diff/infrastructure'
1010import { fileLoadedInputs } from '#design-diff/inputs'
1111import { reclaimMemory } from '#design-diff/memory'
@@ -192,25 +192,48 @@ export async function analyze(
192192 }
193193 let extracted = 0
194194 let omittedConsumers = 0
195- const covered = new Set < string > ( )
196- const currentPath = ( file : string ) => [ ...renames ] . find ( ( [ , old ] ) => old === file ) ?. [ 0 ] ?? file
195+ /** Prefer direct consumers before distant opaque application plumbing when retaining one example. */
196+ const downstream = new Map < string , Set < string > > ( )
197+ for ( const graph of [ before . graph , after . graph ] )
198+ for ( const [ file , dependencies ] of graph . dependencies )
199+ for ( const dependency of dependencies ) {
200+ const consumers = downstream . get ( dependency ) ?? new Set < string > ( )
201+ consumers . add ( file )
202+ downstream . set ( dependency , consumers )
203+ }
204+ const distances = new Map ( [ ...changed ] . map ( ( file ) => [ file , 0 ] ) )
205+ const queue = [ ...changed ]
206+ for ( let index = 0 ; index < queue . length ; index ++ ) {
207+ const file = queue [ index ]
208+ for ( const consumer of downstream . get ( file ) ?? [ ] )
209+ if ( ! distances . has ( consumer ) ) {
210+ distances . set ( consumer , distances . get ( file ) ! + 1 )
211+ queue . push ( consumer )
212+ }
213+ }
197214 const ordered = [ ...affected ] . sort (
198- ( a , b ) => Number ( changed . has ( b ) ) - Number ( changed . has ( a ) ) || a . localeCompare ( b , 'en' )
215+ ( a , b ) =>
216+ ( distances . get ( a ) ?? Number . POSITIVE_INFINITY ) -
217+ ( distances . get ( b ) ?? Number . POSITIVE_INFINITY ) || a . localeCompare ( b , 'en' )
199218 )
219+ let indirectExamples = 0
200220 for ( const file of ordered ) {
201221 if ( ++ extracted % 32 === 0 ) reclaimMemory ( )
202222 if ( ! scoped ( file , config ) && ! infrastructure ( file , config ) ) continue
203223 if ( [ ...renames . values ( ) ] . includes ( file ) && ! after . entries . has ( file ) ) continue
204- const roots = [ ...( causes . get ( file ) ?? [ ] ) ] . map ( currentPath )
205- if ( ! changed . has ( file ) && roots . length && roots . every ( ( root ) => covered . has ( root ) ) ) {
224+ if (
225+ ! changed . has ( file ) &&
226+ indirectExamples > 0 &&
227+ findings . some ( ( finding ) => finding . decision === 'flag' )
228+ ) {
206229 omittedConsumers ++
207230 continue
208231 }
209- const firstFinding = findings . length
210232 const oldFile = renames . get ( file ) ?? file
211233 const a = await extract ( before , previousTailwind , oldFile )
212234 const b = await extract ( after , nextTailwind , file )
213235 findings . push ( ...compareDefinitions ( a , b , affected ) )
236+ if ( ! changed . has ( file ) && ( a . length || b . length ) ) indirectExamples ++
214237 if (
215238 changed . has ( file ) &&
216239 config . infrastructure . some ( ( pattern ) => new RegExp ( pattern ) . test ( file ) )
@@ -247,15 +270,11 @@ export async function analyze(
247270 review ( file , after . entries . get ( file ) ?. oid ?? '' , 'Unsupported rendering mechanism' )
248271 )
249272 )
250- for ( const change of findings . slice ( firstFinding ) )
251- if ( change . decision === 'flag' )
252- for ( const source of causalSources ( change , causes , renames ) )
253- if ( source !== currentPath ( file ) || / \. [ j t ] s x $ / . test ( source ) ) covered . add ( source )
254273 }
255274 if ( omittedConsumers )
256275 report . limitations = [
257276 ...report . limitations ,
258- `Repeated downstream expansion omitted for ${ omittedConsumers } unchanged files after all contributing changed sources already had flagged evidence . Categories describe retained evidence; usage counts remain partial resolved references.` ,
277+ `Indirect analysis omitted for ${ omittedConsumers } unchanged files after the PR qualified and a nearby rendering consumer was examined. All in-scope changed files were analyzed; additional indirect effects are not exhaustively catalogued . Categories describe retained evidence; usage counts remain partial resolved references.` ,
259278 ]
260279 for ( const file of changed ) {
261280 if ( file !== 'bun.lock' && ! file . endsWith ( '/package.json' ) && file !== 'package.json' )
0 commit comments