@@ -98,6 +98,13 @@ export interface MatchQuery {
9898
9999/** Weight of a full structural match relative to a rare matched term. */
100100const STRUCTURE_WEIGHT = 3 ;
101+ /**
102+ * Multiplier when a matched term also names the component itself — its name,
103+ * props, or file (6F.7). "calendar" matching CalendarPanel outranks the same
104+ * text rendered incidentally elsewhere; global character rarity alone can't
105+ * tell them apart.
106+ */
107+ const IDENTIFIER_AFFINITY = 1.5 ;
101108/** A glossary alias hit outweighs any text/structure evidence (Phase 4.6). */
102109const ALIAS_WEIGHT = 10 ;
103110/** A recorded human correction is the strongest signal of all. */
@@ -170,6 +177,28 @@ export function matchComponents(
170177 return base * ( boosts . get ( term ) ?? 1 ) ;
171178 } ;
172179
180+ // A component's own identifiers — name, props, file basename — split on
181+ // camelCase and separators. Terms that also NAME the component score higher
182+ // than the same text rendered incidentally elsewhere (6F.7).
183+ const identifierMemo = new Map < string , Set < string > > ( ) ;
184+ const identifierTokens = ( component : ComponentNode ) : Set < string > => {
185+ const cached = identifierMemo . get ( component . id ) ;
186+ if ( cached ) return cached ;
187+ const camelSplit = ( s : string ) : string => s . replace ( / ( [ a - z 0 - 9 ] ) ( [ A - Z ] ) / g, "$1 $2" ) ;
188+ const basename = component . loc . file . split ( "/" ) . pop ( ) ?. replace ( / \. [ a - z ] + $ / i, "" ) ?? "" ;
189+ const out = new Set (
190+ tokenize (
191+ [ component . name , ...component . props , basename ] . map ( camelSplit ) . join ( " " ) ,
192+ ) ,
193+ ) ;
194+ identifierMemo . set ( component . id , out ) ;
195+ return out ;
196+ } ;
197+ const namesComponent = ( term : string , component : ComponentNode ) : boolean => {
198+ const ids = identifierTokens ( component ) ;
199+ return tokenize ( term ) . some ( ( t ) => ids . has ( t ) || [ ...ids ] . some ( ( id ) => fuzzyTokenMatch ( id , t ) ) ) ;
200+ } ;
201+
173202 // Glossary aliases + recorded corrections (Phase 4.6). Both are authority
174203 // signals — a phrase resolves even when it appears nowhere in the code.
175204 const aliasEntries = Object . entries ( query . aliases ?? { } ) ;
@@ -233,7 +262,8 @@ export function matchComponents(
233262 }
234263 }
235264 if ( hit === null ) continue ;
236- const w = termWeight ( term ) ;
265+ const affine = namesComponent ( term , component ) ;
266+ const w = termWeight ( term ) * ( affine ? IDENTIFIER_AFFINITY : 1 ) ;
237267 covered . add ( term ) ;
238268 if ( where . id === component . id ) matched . push ( term ) ;
239269 weight += w ;
@@ -247,7 +277,9 @@ export function matchComponents(
247277 : "" ;
248278 evidence . push ( {
249279 kind : "text-match" ,
250- detail : `"${ term } " matched rendered text "${ hit . text } "${ provenance } — rarity weight ${ w . toFixed ( 2 ) } ` ,
280+ detail :
281+ `"${ term } " matched rendered text "${ hit . text } "${ provenance } — rarity weight ${ w . toFixed ( 2 ) } ` +
282+ ( affine ? " (also names the component)" : "" ) ,
251283 loc : where . loc ,
252284 } ) ;
253285 }
@@ -347,7 +379,7 @@ export function matchComponents(
347379 s . covered . size === 0 && conf . level === "high"
348380 ? { score : conf . score , level : "medium" as const }
349381 : conf ;
350- return { value : s . match , confidence, evidence : s . evidence } ;
382+ return { value : s . match , confidence, evidence : s . evidence , score : s . score } ;
351383 } ) ;
352384
353385 const top = winners [ 0 ] ;
0 commit comments