@@ -402,9 +402,9 @@ async function runPostNativeAnalysis(
402402 * Note: `this`/`super` dispatch is handled separately by `runPostNativeThisDispatch`,
403403 * which WASM-re-parses JS/TS files to obtain raw call site receiver info.
404404 *
405- * Returns the set of target node IDs for newly inserted CHA edges so the caller
406- * can re-classify roles for the affected implementation files . An empty set
407- * means no edges were added and role re-classification is unnecessary.
405+ * Returns the count of newly inserted CHA edges so the caller can determine
406+ * whether a full role re-classification is needed . Zero means no edges were
407+ * added and role re-classification is unnecessary.
408408 */
409409function runPostNativeCha ( db : BetterSqlite3Database ) : number {
410410 // Fast guard: no hierarchy edges → no CHA work
@@ -1607,37 +1607,9 @@ export async function tryNativeOrchestrator(
16071607 }
16081608
16091609 // Phase 8.5: expand CHA call edges (interface dispatch → concrete implementations).
1610- // The Rust orchestrator ran role classification BEFORE this post-pass, so without
1611- // a re-run the newly-called implementor methods stay classified as `dead-ffi`.
1612- //
1613- // CHA also changes the global fan-out distribution (callee files gain fan_in, and
1614- // new edges shift the median). A full re-classification is required — not just the
1615- // callee files — because the median shift can change roles in unrelated files whose
1616- // fan-out sits near the old median. (Example: a method that called two siblings
1617- // pre-CHA might be near the median, but post-CHA the median is higher, changing
1618- // its role from utility → core.) Using an incremental pass with a stale median
1619- // cache would produce incorrect roles outside the CHA-affected file set.
1620- //
1621- // Performance: classifyNodeRoles is O(all_nodes). For most repos this is sub-100ms;
1622- // on very large codebases (100k+ nodes) it may add a few hundred ms per build.
1623- // If this becomes a bottleneck, consider a two-pass strategy: incremental first
1624- // (fast, slightly inaccurate), then full only when the median shifts by >N%.
1610+ // Returns the count of newly inserted edges; used to determine whether
1611+ // a full role re-classification is needed after all edge-writing post-passes complete.
16251612 const chaEdgeCount = runPostNativeCha ( ctx . db as unknown as BetterSqlite3Database ) ;
1626- if ( chaEdgeCount > 0 ) {
1627- try {
1628- const db = ctx . db as unknown as BetterSqlite3Database ;
1629- const { classifyNodeRoles } = ( await import ( '../../../../features/structure.js' ) ) as {
1630- classifyNodeRoles : (
1631- db : BetterSqlite3Database ,
1632- changedFiles ?: string [ ] | null ,
1633- ) => Record < string , number > ;
1634- } ;
1635- classifyNodeRoles ( db ) ;
1636- debug ( `CHA post-pass: full role re-classification after ${ chaEdgeCount } new CHA edges` ) ;
1637- } catch ( err ) {
1638- debug ( `CHA post-pass role re-classification failed: ${ toErrorMessage ( err ) } ` ) ;
1639- }
1640- }
16411613
16421614 // Function-as-object-property post-pass: the Rust engine does not yet recognise
16431615 // `fn.method = function() {}` patterns. Re-parse only those JS/TS files via
@@ -1659,51 +1631,23 @@ export async function tryNativeOrchestrator(
16591631 ! ! result . isFullBuild ,
16601632 ) ;
16611633
1662- // Re-classify roles for methods that gained incoming this/super dispatch edges.
1663- // The Rust orchestrator classifies roles BEFORE this post-pass, so target methods
1664- // (e.g. Animal.speak, ConcreteWorker.prepare) that had no callers at Rust time
1665- // are classified `dead` or `dead-ffi`. Inserting the new call edges does not
1666- // automatically update those role labels — without a re-run the stale labels
1667- // propagate to dead-code detection and API boundary analysis.
1668- if ( thisDispatchTargetIds . size > 0 ) {
1634+ // Full role re-classification after JS edge-writing post-passes.
1635+ // The Rust orchestrator classifies roles before these post-passes (CHA,
1636+ // this-dispatch) add edges, so the Rust-computed roles and the cached
1637+ // fan-out medians are stale. A full re-classification ensures the final
1638+ // roles reflect the true fan-in/out with all edges in place.
1639+ if ( chaEdgeCount > 0 || thisDispatchTargetIds . size > 0 ) {
16691640 try {
1670- const db = ctx . db as unknown as BetterSqlite3Database ;
1671- const idArray = Array . from ( thisDispatchTargetIds ) ;
1672- const CHUNK_SIZE = 500 ;
1673- const seenFiles = new Set < string > ( ) ;
1674- const affectedFiles : Array < { file : string } > = [ ] ;
1675- for ( let i = 0 ; i < idArray . length ; i += CHUNK_SIZE ) {
1676- const chunk = idArray . slice ( i , i + CHUNK_SIZE ) ;
1677- const placeholders = chunk . map ( ( ) => '?' ) . join ( ',' ) ;
1678- const rows = db
1679- . prepare (
1680- `SELECT DISTINCT file FROM nodes WHERE id IN (${ placeholders } ) AND file IS NOT NULL` ,
1681- )
1682- . all ( ...chunk ) as Array < { file : string } > ;
1683- for ( const row of rows ) {
1684- if ( ! seenFiles . has ( row . file ) ) {
1685- seenFiles . add ( row . file ) ;
1686- affectedFiles . push ( row ) ;
1687- }
1688- }
1689- }
1690- if ( affectedFiles . length > 0 ) {
1691- const { classifyNodeRoles } = ( await import ( '../../../../features/structure.js' ) ) as {
1692- classifyNodeRoles : (
1693- db : BetterSqlite3Database ,
1694- changedFiles ?: string [ ] | null ,
1695- ) => Record < string , number > ;
1696- } ;
1697- classifyNodeRoles (
1698- db ,
1699- affectedFiles . map ( ( r ) => r . file ) ,
1700- ) ;
1701- debug (
1702- `this/super dispatch post-pass: re-classified roles for ${ affectedFiles . length } target file(s)` ,
1703- ) ;
1704- }
1641+ const { classifyNodeRoles } = ( await import ( '../../../../features/structure.js' ) ) as {
1642+ classifyNodeRoles : (
1643+ db : BetterSqlite3Database ,
1644+ changedFiles ?: string [ ] | null ,
1645+ ) => Record < string , number > ;
1646+ } ;
1647+ classifyNodeRoles ( ctx . db as unknown as BetterSqlite3Database , null ) ;
1648+ debug ( `Post-pass full role re-classification complete` ) ;
17051649 } catch ( err ) {
1706- debug ( `this/super dispatch post -pass role re-classification failed: ${ toErrorMessage ( err ) } ` ) ;
1650+ debug ( `Post -pass full role re-classification failed: ${ toErrorMessage ( err ) } ` ) ;
17071651 }
17081652 }
17091653
0 commit comments