@@ -46,6 +46,7 @@ import type {
4646 GhStatusOutput ,
4747 GitCommitInfo ,
4848 GitFileStatus ,
49+ GitHubIssue ,
4950 GitRepoInfo ,
5051 GitStateSnapshot ,
5152 GitSyncStatus ,
@@ -995,4 +996,106 @@ ${filesSummary || "(no file changes detected)"}`;
995996 body : bodyMatch ?. [ 1 ] ?. trim ( ) ?? "" ,
996997 } ;
997998 }
999+
1000+ private async resolveCanonicalRepo ( repo : string ) : Promise < string > {
1001+ const result = await execGh ( [
1002+ "repo" ,
1003+ "view" ,
1004+ repo ,
1005+ "--json" ,
1006+ "name,owner" ,
1007+ "--jq" ,
1008+ '.owner.login + "/" + .name' ,
1009+ ] ) ;
1010+ if ( result . exitCode !== 0 ) return repo ;
1011+ return result . stdout . trim ( ) || repo ;
1012+ }
1013+
1014+ private parseGhIssues ( stdout : string , repo : string ) : GitHubIssue [ ] {
1015+ const raw = JSON . parse ( stdout ) as Array < {
1016+ number : number ;
1017+ title : string ;
1018+ state : string ;
1019+ labels : Array < { name : string } > ;
1020+ url : string ;
1021+ } > ;
1022+ const items = Array . isArray ( raw ) ? raw : [ raw ] ;
1023+ return items . map ( ( issue ) => ( {
1024+ number : issue . number ,
1025+ title : issue . title ,
1026+ state : issue . state . toUpperCase ( ) ,
1027+ labels : issue . labels . map ( ( l ) => l . name ) ,
1028+ url : issue . url ,
1029+ repo,
1030+ } ) ) ;
1031+ }
1032+
1033+ public async searchGithubIssues (
1034+ directoryPath : string ,
1035+ query ?: string ,
1036+ limit = 5 ,
1037+ ) : Promise < GitHubIssue [ ] > {
1038+ const repoInfo = await this . getGitRepoInfo ( directoryPath ) ;
1039+ if ( ! repoInfo ) return [ ] ;
1040+
1041+ const repo = await this . resolveCanonicalRepo (
1042+ `${ repoInfo . organization } /${ repoInfo . repository } ` ,
1043+ ) ;
1044+ const trimmed = query ?. trim ( ) . replace ( / ^ # / , "" ) ;
1045+ const issueNumber = trimmed ? Number ( trimmed ) : Number . NaN ;
1046+
1047+ if ( ! Number . isNaN ( issueNumber ) && Number . isInteger ( issueNumber ) ) {
1048+ return this . fetchGhIssues (
1049+ [ "issue" , "view" , String ( issueNumber ) , "--repo" , repo ] ,
1050+ repo ,
1051+ ) ;
1052+ }
1053+
1054+ if ( trimmed ) {
1055+ return this . fetchGhIssues (
1056+ [
1057+ "search" ,
1058+ "issues" ,
1059+ trimmed ,
1060+ "--repo" ,
1061+ repo ,
1062+ "--limit" ,
1063+ String ( limit ) ,
1064+ "--match" ,
1065+ "title" ,
1066+ ] ,
1067+ repo ,
1068+ ) ;
1069+ }
1070+
1071+ return this . fetchGhIssues (
1072+ [
1073+ "issue" ,
1074+ "list" ,
1075+ "--repo" ,
1076+ repo ,
1077+ "--limit" ,
1078+ String ( limit ) ,
1079+ "--state" ,
1080+ "all" ,
1081+ ] ,
1082+ repo ,
1083+ ) ;
1084+ }
1085+
1086+ private async fetchGhIssues (
1087+ args : string [ ] ,
1088+ repo : string ,
1089+ ) : Promise < GitHubIssue [ ] > {
1090+ const jsonFields = "number,title,state,labels,url" ;
1091+ const result = await execGh ( [ ...args , "--json" , jsonFields ] ) ;
1092+ if ( result . exitCode !== 0 ) return [ ] ;
1093+
1094+ try {
1095+ return this . parseGhIssues ( result . stdout , repo ) ;
1096+ } catch {
1097+ log . warn ( "Failed to parse GitHub issues response" , { repo, args } ) ;
1098+ return [ ] ;
1099+ }
1100+ }
9981101}
0 commit comments