@@ -101,18 +101,30 @@ const DOCS_LABELS = new Set([
101101 'typo' ,
102102] )
103103
104+ /**
105+ * Check if a label contains any keyword from a set.
106+ * Handles emoji-prefixed labels like ":sparkles: feature request" or ":lady_beetle: bug".
107+ */
108+ function labelMatchesAny ( label : string , keywords : Set < string > ) : boolean {
109+ for ( const keyword of keywords ) {
110+ if ( label === keyword || label . includes ( keyword ) )
111+ return true
112+ }
113+ return false
114+ }
115+
104116/**
105117 * Classify an issue by its labels into a type useful for skill generation
106118 */
107119export function classifyIssue ( labels : string [ ] ) : IssueType {
108120 const lower = labels . map ( l => l . toLowerCase ( ) )
109- if ( lower . some ( l => BUG_LABELS . has ( l ) ) )
121+ if ( lower . some ( l => labelMatchesAny ( l , BUG_LABELS ) ) )
110122 return 'bug'
111- if ( lower . some ( l => QUESTION_LABELS . has ( l ) ) )
123+ if ( lower . some ( l => labelMatchesAny ( l , QUESTION_LABELS ) ) )
112124 return 'question'
113- if ( lower . some ( l => DOCS_LABELS . has ( l ) ) )
125+ if ( lower . some ( l => labelMatchesAny ( l , DOCS_LABELS ) ) )
114126 return 'docs'
115- if ( lower . some ( l => FEATURE_LABELS . has ( l ) ) )
127+ if ( lower . some ( l => labelMatchesAny ( l , FEATURE_LABELS ) ) )
116128 return 'feature'
117129 return 'other'
118130}
@@ -122,7 +134,7 @@ export function classifyIssue(labels: string[]): IssueType {
122134 */
123135function isNoiseIssue ( issue : { labels : string [ ] , title : string , body : string } ) : boolean {
124136 const lower = issue . labels . map ( l => l . toLowerCase ( ) )
125- if ( lower . some ( l => NOISE_LABELS . has ( l ) ) )
137+ if ( lower . some ( l => labelMatchesAny ( l , NOISE_LABELS ) ) )
126138 return true
127139 // Tracking/umbrella issues — low signal for skill generation
128140 if ( issue . title . startsWith ( '☂️' ) || issue . title . startsWith ( '[META]' ) || issue . title . startsWith ( '[Tracking]' ) )
@@ -152,12 +164,13 @@ export function isNonTechnical(issue: { body: string, title: string, reactions:
152164
153165/**
154166 * Freshness-weighted score: reactions * decay(age_in_years)
155- * A 2024 issue with 50 reactions outranks a 2014 issue with 500.
167+ * Steep decay so recent issues dominate over old high-reaction ones.
168+ * At 0.6: 1yr=0.63x, 2yr=0.45x, 4yr=0.29x, 6yr=0.22x
156169 */
157170export function freshnessScore ( reactions : number , createdAt : string ) : number {
158171 const ageMs = Date . now ( ) - new Date ( createdAt ) . getTime ( )
159172 const ageYears = ageMs / ( 365.25 * 24 * 60 * 60 * 1000 )
160- return reactions * ( 1 / ( 1 + ageYears * 0.3 ) )
173+ return reactions * ( 1 / ( 1 + ageYears * 0.6 ) )
161174}
162175
163176/**
0 commit comments