@@ -83,6 +83,7 @@ async function readLlmsFullIndex() {
8383 const pages = [ ] ;
8484 const byTitle = new Map ( ) ;
8585 const byUrl = new Map ( ) ;
86+ const bySlug = new Map ( ) ;
8687
8788 // Heuristic parser:
8889 // - Treat lines starting with "# " or "## " as page/section titles
@@ -106,7 +107,7 @@ async function readLlmsFullIndex() {
106107 if ( / ^ # \s + / . test ( line ) ) {
107108 // Page title
108109 const title = line . replace ( / ^ # \s + / , '' ) . trim ( ) ;
109- current = { title, url : null , anchors : new Set ( ) } ;
110+ current = { title, url : null , anchors : new Set ( ) , text : '' } ;
110111 pages . push ( current ) ;
111112 byTitle . set ( title . toLowerCase ( ) , current ) ;
112113 continue ;
@@ -115,7 +116,7 @@ async function readLlmsFullIndex() {
115116 // Treat as page title too if no outer # title exists yet
116117 const title = line . replace ( / ^ # # \s + / , '' ) . trim ( ) ;
117118 if ( ! current ) {
118- current = { title, url : null , anchors : new Set ( ) } ;
119+ current = { title, url : null , anchors : new Set ( ) , text : '' } ;
119120 pages . push ( current ) ;
120121 byTitle . set ( title . toLowerCase ( ) , current ) ;
121122 } else {
@@ -134,6 +135,17 @@ async function readLlmsFullIndex() {
134135 if ( m && ! current . url ) {
135136 current . url = m [ 1 ] ;
136137 byUrl . set ( current . url , current ) ;
138+ try {
139+ const u = new URL ( current . url ) ;
140+ // take last non-empty segment as slug
141+ const segs = u . pathname . split ( '/' ) . filter ( Boolean ) ;
142+ const last = segs [ segs . length - 1 ] || '' ;
143+ if ( last ) bySlug . set ( last . toLowerCase ( ) , current ) ;
144+ } catch { }
145+ }
146+ // Accumulate raw text for coverage checks
147+ if ( current ) {
148+ current . text += line + "\n" ;
137149 }
138150 }
139151
@@ -142,9 +154,17 @@ async function readLlmsFullIndex() {
142154 title : p . title ,
143155 url : p . url || null ,
144156 anchors : Array . from ( p . anchors ) ,
157+ text : p . text || ''
145158 } ) ) ;
146159
147- return { source, pages : normalized , byTitle, byUrl } ;
160+ // Re-link bySlug to normalized objects
161+ const bySlugNormalized = new Map ( ) ;
162+ for ( const [ k , v ] of bySlug . entries ( ) ) {
163+ const norm = normalized . find ( p => p . title === v . title && p . url === v . url ) ;
164+ if ( norm ) bySlugNormalized . set ( k , norm ) ;
165+ }
166+
167+ return { source, pages : normalized , byTitle, byUrl, bySlug : bySlugNormalized } ;
148168}
149169
150170function parseArgs ( argv ) {
@@ -923,6 +943,37 @@ async function main() {
923943 }
924944 }
925945
946+ // llms-full cross-check: if targets are present and the page text likely already
947+ // describes the expected behavior (and PR looks like a fix), downgrade to NO.
948+ if ( claudeSuggestions && claudeSuggestions . needsDocs === 'yes' && Array . isArray ( claudeSuggestions . targets ) && claudeSuggestions . targets . length > 0 ) {
949+ const looksLikeFix = / f i x | b u g | r e g r e s s i o n | r e s t o r e | c o r r e c t | m i s l e a d i n g | m i s s i n g / i. test ( prAnalysis . title || '' ) || / f i x | b u g | r e g r e s s i o n | r e s t o r e | c o r r e c t | m i s l e a d i n g | m i s s i n g / i. test ( prAnalysis . body || '' ) ;
950+ if ( looksLikeFix ) {
951+ const coverageHit = claudeSuggestions . targets . some ( t => {
952+ const wanted = ( t . path || '' ) . toLowerCase ( ) ;
953+ let page = null ;
954+ // prefer exact slug match from llmsIndex
955+ const slug = path . basename ( wanted ) . replace ( / \. m d x ? $ / i, '' ) . toLowerCase ( ) ;
956+ if ( slug && llmsIndex . bySlug && llmsIndex . bySlug . has ( slug ) ) {
957+ page = llmsIndex . bySlug . get ( slug ) ;
958+ }
959+ // fallback: any candidate containing the slug
960+ if ( ! page ) {
961+ page = candidates . find ( p => p . url && p . url . toLowerCase ( ) . includes ( slug ) ) ;
962+ }
963+ // last resort: first candidate
964+ if ( ! page ) page = candidates [ 0 ] ;
965+ if ( ! page || ! page . text ) return false ;
966+ const words = ( summary || '' ) . toLowerCase ( ) . split ( / [ ^ a - z 0 - 9 ] + / ) . filter ( w => w . length > 3 ) . slice ( 0 , 8 ) ;
967+ const body = page . text . toLowerCase ( ) ;
968+ const hits = words . filter ( w => body . includes ( w ) ) ;
969+ return hits . length >= Math . max ( 2 , Math . floor ( words . length / 2 ) ) ;
970+ } ) ;
971+ if ( coverageHit ) {
972+ claudeSuggestions = { ...claudeSuggestions , needsDocs : 'no' } ;
973+ }
974+ }
975+ }
976+
926977 analyses . push ( {
927978 ...prAnalysis ,
928979 summary,
0 commit comments