1+ import { parseGitHubNoreplyEmail } from '@crowd/common'
12import {
23 findMembersByGithubHandles ,
34 findResolvableEmailsForMembers ,
@@ -13,6 +14,28 @@ function latestTimestamp(provenance: ProvenanceEntry[]): string {
1314 : times . reduce ( ( a , b ) => ( new Date ( b ) . getTime ( ) > new Date ( a ) . getTime ( ) ? b : a ) )
1415}
1516
17+ // A GitHub noreply email already encodes its owner's handle — derive it so the handle can be
18+ // resolved to a real address the same way any other github-handle contact is. The noreply email
19+ // itself is kept as the provenance source, so the resolved contact's origin stays traceable.
20+ export function deriveGithubHandlesFromNoreplyEmails ( contacts : RawContact [ ] ) : RawContact [ ] {
21+ const derived : RawContact [ ] = [ ]
22+ for ( const c of contacts ) {
23+ if ( c . channel !== 'email' ) continue
24+ const handle = parseGitHubNoreplyEmail ( c . value )
25+ if ( ! handle ) continue
26+ derived . push ( {
27+ channel : 'github-handle' ,
28+ value : handle ,
29+ role : c . role ,
30+ tier : c . tier ,
31+ provenance : [
32+ { source : c . value , sourceTier : c . tier , fetchedAt : latestTimestamp ( c . provenance ) } ,
33+ ] ,
34+ } )
35+ }
36+ return derived
37+ }
38+
1639export async function resolveCdpEmails (
1740 cdpQx : QueryExecutor ,
1841 handleContacts : RawContact [ ] ,
@@ -51,8 +74,9 @@ export async function resolveCdpEmails(
5174 channel : 'email' as const ,
5275 value : email ,
5376 role : contact . role ,
77+ handle : contact . value ,
5478 tier : contact . tier ,
55- provenance : [ { source, sourceTier : contact . tier , fetchedAt } ] ,
79+ provenance : [ ... contact . provenance , { source, sourceTier : contact . tier , fetchedAt } ] ,
5680 } ) )
5781 } )
5882 } )
0 commit comments