@@ -70,7 +70,7 @@ export async function GET(request: Request) {
7070
7171 const { data : tools , error : toolsError } = await db
7272 . from ( "tools" )
73- . select ( "id, name, github_url" )
73+ . select ( "id, name, github_url, health_score, is_stale " )
7474 . not ( "github_url" , "is" , null )
7575 . or ( `last_synced_at.is.null,last_synced_at.lt.${ oneHourAgo } ` ) ;
7676
@@ -83,6 +83,7 @@ export async function GET(request: Request) {
8383 let processed = 0 ;
8484 let skipped = 0 ;
8585 let errors = 0 ;
86+ let eventsWritten = 0 ;
8687
8788 for ( const tool of tools ) {
8889 if ( ! tool . github_url ) {
@@ -97,32 +98,94 @@ export async function GET(request: Request) {
9798 continue ;
9899 }
99100
100- // Find snapshot closest to 30 days ago for stars momentum
101+ // Find snapshot closest to 30 days ago for stars momentum + archived baseline
101102 const thirtyDaysAgo = new Date ( Date . now ( ) - 30 * 24 * 60 * 60 * 1000 ) . toISOString ( ) ;
102103 const { data : prevSnapshot } = await db
103104 . from ( "tool_snapshots" )
104- . select ( "stars" )
105+ . select ( "stars, archived " )
105106 . eq ( "tool_id" , tool . id )
106107 . lte ( "recorded_at" , thirtyDaysAgo )
107108 . order ( "recorded_at" , { ascending : false } )
108109 . limit ( 1 )
109110 . single ( ) ;
110111
111112 const prevStars = prevSnapshot ?. stars ?? null ;
113+ const starsDelta = prevStars !== null ? ghData . stars - prevStars : null ;
112114 const healthScore = computeHealthScore ( ghData , prevStars ) ;
113115
114116 const daysSinceCommit =
115117 ( Date . now ( ) - new Date ( ghData . last_commit_at ) . getTime ( ) ) / ( 1000 * 60 * 60 * 24 ) ;
116118 const isStale = ghData . archived || daysSinceCommit > 90 ;
117119 const now = new Date ( ) . toISOString ( ) ;
118120
121+ // ── Transition events ──────────────────────────────────────────────────
122+
123+ // Health score change (≥10 point swing)
124+ const oldScore = tool . health_score ?? null ;
125+ if ( oldScore !== null && Math . abs ( healthScore - oldScore ) >= 10 ) {
126+ const { error : eventError } = await db . from ( "tool_events" ) . insert ( {
127+ tool_id : tool . id ,
128+ type : "health_score_change" ,
129+ metadata : { old_score : oldScore , new_score : healthScore , delta : healthScore - oldScore } ,
130+ } ) ;
131+ if ( eventError ) {
132+ console . error (
133+ `[sync-health] ✗ ${ tool . name } — health_score_change event failed: ${ eventError . message } `
134+ ) ;
135+ } else {
136+ eventsWritten ++ ;
137+ }
138+ }
139+
140+ // Stale transition (false → true)
141+ if ( ! tool . is_stale && isStale ) {
142+ const { error : eventError } = await db . from ( "tool_events" ) . insert ( {
143+ tool_id : tool . id ,
144+ type : "stale_transition" ,
145+ metadata : {
146+ archived : ghData . archived ,
147+ days_since_commit : Math . floor ( daysSinceCommit ) ,
148+ } ,
149+ } ) ;
150+ if ( eventError ) {
151+ console . error (
152+ `[sync-health] ✗ ${ tool . name } — stale_transition event failed: ${ eventError . message } `
153+ ) ;
154+ } else {
155+ eventsWritten ++ ;
156+ console . log ( `[sync-health] ⚠ ${ tool . name } — stale transition detected` ) ;
157+ }
158+ }
159+
160+ // Archived transition (previously not archived → now archived)
161+ const wasArchived = prevSnapshot ?. archived ?? false ;
162+ if ( ! wasArchived && ghData . archived ) {
163+ const { error : eventError } = await db . from ( "tool_events" ) . insert ( {
164+ tool_id : tool . id ,
165+ type : "archived_detected" ,
166+ metadata : { } ,
167+ } ) ;
168+ if ( eventError ) {
169+ console . error (
170+ `[sync-health] ✗ ${ tool . name } — archived_detected event failed: ${ eventError . message } `
171+ ) ;
172+ } else {
173+ eventsWritten ++ ;
174+ console . log ( `[sync-health] 🗄 ${ tool . name } — archived on GitHub` ) ;
175+ }
176+ }
177+
178+ // ── Snapshot insert ────────────────────────────────────────────────────
179+
119180 const { error : snapshotError } = await db . from ( "tool_snapshots" ) . insert ( {
120181 tool_id : tool . id ,
121182 stars : ghData . stars ,
122183 last_commit_at : ghData . last_commit_at ,
123184 open_issues : ghData . open_issues ,
124185 forks : ghData . forks ,
125186 archived : ghData . archived ,
187+ health_score : healthScore ,
188+ stars_delta : starsDelta ,
126189 } ) ;
127190
128191 if ( snapshotError ) {
@@ -146,17 +209,17 @@ export async function GET(request: Request) {
146209
147210 const starsDisplay =
148211 ghData . stars >= 1000 ? `${ ( ghData . stars / 1000 ) . toFixed ( 0 ) } k` : String ( ghData . stars ) ;
212+ const deltaDisplay =
213+ starsDelta !== null ? ` (${ starsDelta >= 0 ? "+" : "" } ${ starsDelta } vs 30d)` : "" ;
149214 const dayLabel =
150215 Math . floor ( daysSinceCommit ) === 1 ? "1d ago" : `${ Math . floor ( daysSinceCommit ) } d ago` ;
151216 console . log (
152- `[sync-health] ✓ ${ tool . name } (score: ${ healthScore } , stars: ${ starsDisplay } , last_commit: ${ dayLabel } )`
217+ `[sync-health] ✓ ${ tool . name } (score: ${ healthScore } , stars: ${ starsDisplay } ${ deltaDisplay } , last_commit: ${ dayLabel } )`
153218 ) ;
154219 processed ++ ;
155220 }
156221
157222 // ── Pricing change detection ──────────────────────────────────────────────
158- // Fetches ALL tools, hashes their pricing JSON, and records a tool_event
159- // when the hash differs from what's stored. First run sets the baseline.
160223
161224 const { data : allTools , error : allToolsError } = await db
162225 . from ( "tools" )
@@ -194,6 +257,7 @@ export async function GET(request: Request) {
194257 await db . from ( "tools" ) . update ( { pricing_hash : newHash } ) . eq ( "id" , tool . id ) ;
195258 console . log ( `[sync-health] pricing change detected: ${ tool . name } ` ) ;
196259 pricingChanged ++ ;
260+ eventsWritten ++ ;
197261 }
198262 }
199263
@@ -204,13 +268,14 @@ export async function GET(request: Request) {
204268
205269 const duration_ms = Date . now ( ) - startTime ;
206270 console . log (
207- `[sync-health] Done — processed: ${ processed } , skipped: ${ skipped } , errors: ${ errors } , duration: ${ duration_ms } ms`
271+ `[sync-health] Done — processed: ${ processed } , skipped: ${ skipped } , errors: ${ errors } , events: ${ eventsWritten } , duration: ${ duration_ms } ms`
208272 ) ;
209273
210274 return Response . json ( {
211275 processed,
212276 skipped,
213277 errors,
278+ events_written : eventsWritten ,
214279 pricing_checked : pricingChecked ,
215280 pricing_changed : pricingChanged ,
216281 duration_ms,
0 commit comments