(client *http.Client, query string, limit int, authHeader string)
| 469 | ) |
| 470 | |
| 471 | func executeGHSearch(client *http.Client, query string, limit int, authHeader string) ([]ghCodeSearchItem, error) { |
| 472 | // Always request a full page of results from the API, regardless of |
| 473 | // the display limit. More raw results → better filtering/ranking. |
| 474 | perPage := searchPageSize |
| 475 | if limit > perPage { |
| 476 | perPage = limit |
| 477 | } |
| 478 | apiURL := fmt.Sprintf("https://api.github.com/search/code?q=%s&per_page=%d", |
| 479 | url.QueryEscape(query), perPage) |
| 480 | |
| 481 | req, err := http.NewRequest("GET", apiURL, nil) |
| 482 | if err != nil { |
| 483 | return nil, err |
| 484 | } |
| 485 | req.Header.Set("Accept", "application/vnd.github.v3+json") |
| 486 | req.Header.Set("User-Agent", "code-agent-manager") |
| 487 | if authHeader != "" { |
| 488 | req.Header.Set("Authorization", authHeader) |
| 489 | } |
| 490 | |
| 491 | resp, err := client.Do(req) |
| 492 | if err != nil { |
| 493 | return nil, fmt.Errorf("GitHub API request failed: %w", err) |
| 494 | } |
| 495 | defer resp.Body.Close() |
| 496 | |
| 497 | // Distinguish true rate limits from other 403s. |
| 498 | if resp.StatusCode == 429 { |
| 499 | return nil, fmt.Errorf("GitHub API rate limit exceeded, set GITHUB_TOKEN for higher limits") |
| 500 | } |
| 501 | if resp.StatusCode == 403 { |
| 502 | // Check rate-limit headers: x-ratelimit-remaining: 0 or retry-after. |
| 503 | if resp.Header.Get("X-Ratelimit-Remaining") == "0" || resp.Header.Get("Retry-After") != "" { |
| 504 | return nil, fmt.Errorf("GitHub API rate limit exceeded, set GITHUB_TOKEN for higher limits") |
| 505 | } |
| 506 | // Secondary rate limit or other 403 — return empty, not fatal. |
| 507 | return nil, nil |
| 508 | } |
| 509 | if resp.StatusCode == 401 { |
| 510 | return nil, fmt.Errorf("GitHub API auth failed, set GITHUB_TOKEN or GH_TOKEN") |
| 511 | } |
| 512 | if resp.StatusCode != 200 { |
| 513 | return nil, fmt.Errorf("GitHub API returned HTTP %d", resp.StatusCode) |
| 514 | } |
| 515 | |
| 516 | var result struct { |
| 517 | Items []ghCodeSearchItem `json:"items"` |
| 518 | } |
| 519 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { |
| 520 | return nil, fmt.Errorf("failed to parse GitHub response: %w", err) |
| 521 | } |
| 522 | return result.Items, nil |
| 523 | } |
| 524 | |
| 525 | // enrichGHDescriptions fetches SKILL.md blob content to extract the frontmatter |
| 526 | // description field concurrently with bounded parallelism, and also fetches |
no test coverage detected