---
description: "GitHub API Usage Policy"
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: true
---

# GitHub API Policy

All GitHub operations must use the API — CLI is only a fallback.

---

## Primary Tool: @octokit/rest

**Always use** `@octokit/rest` for GitHub operations:

```typescript
import { Octokit } from '@octokit/rest';

const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN,
});

// Get issue
const issue = await octokit.issues.get({
  owner: '{{GITHUB_ORG}}',
  repo: '{{GITHUB_REPO}}',
  issue_number: 123,
});

// List pull requests
const prs = await octokit.pulls.list({
  owner: '{{GITHUB_ORG}}',
  repo: '{{GITHUB_REPO}}',
  state: 'open',
});
```

## Authentication

- **Always use `GITHUB_TOKEN` environment variable**
- **Never hardcode tokens**
- **Token must have required scopes:**
  - `repo` (full control of repositories)
  - `workflows` (manage GitHub Actions)

```typescript
const token = process.env.GITHUB_TOKEN;
if (!token) {
  throw new Error('GITHUB_TOKEN environment variable is required');
}

const octokit = new Octokit({ auth: token });
```

## Rate Limits

**Know the limits:**
- **Authenticated requests:** 5,000/hour per user
- **Unauthenticated:** 60/hour per IP

**Handle rate limits gracefully:**

```typescript
// ✅ GOOD - Delay between batches
for (const batch of batches) {
  await processBatch(batch);
  await delay(1000); // 1 second between batches
}

// ✅ GOOD - Check remaining rate limit
const rate = await octokit.rateLimit.get();
if (rate.data.core.remaining < 100) {
  console.warn(`Low rate limit: ${rate.data.core.remaining} remaining`);
  await delay(60000); // Wait 1 minute
}

// ❌ BAD - Ignore rate limits
for (const item of items) {
  await processItem(item); // No delay
}
```

## Fallback: GitHub CLI (gh)

Only use `gh` CLI if:
- API doesn't support the operation
- Octokit is broken/unavailable
- Explicitly approved for that use case

```bash
# Fallback example
gh pr create --title "My PR" --body "Description"
```

## Best Practices

### Batch Operations
```typescript
// Process in batches to avoid rate limits
const items = [/* 1000 items */];
const BATCH_SIZE = 50;

for (let i = 0; i < items.length; i += BATCH_SIZE) {
  const batch = items.slice(i, i + BATCH_SIZE);
  await Promise.all(batch.map(item => processItem(item)));
  await delay(1000); // Wait between batches
}
```

### Error Handling
```typescript
try {
  const result = await octokit.repos.get({
    owner: '{{GITHUB_ORG}}',
    repo: '{{GITHUB_REPO}}',
  });
} catch (error) {
  if (error.status === 404) {
    console.error('Repository not found');
  } else if (error.status === 403) {
    console.error('Access denied - check permissions');
  } else if (error.status === 429) {
    console.error('Rate limit exceeded - wait and retry');
  } else {
    console.error('Unexpected error:', error.message);
  }
  throw error;
}
```

### Pagination
```typescript
// ✅ GOOD - Use pagination.paginate() for automatic handling
const allPRs = await octokit.paginate(octokit.pulls.list, {
  owner: '{{GITHUB_ORG}}',
  repo: '{{GITHUB_REPO}}',
  state: 'open',
});

// Manual pagination if needed
let page = 1;
let allIssues = [];
while (true) {
  const issues = await octokit.issues.list({
    owner: '{{GITHUB_ORG}}',
    repo: '{{GITHUB_REPO}}',
    page,
    per_page: 100,
  });
  if (issues.data.length === 0) break;
  allIssues = allIssues.concat(issues.data);
  page++;
}
```

## Operations Used in {{PROJECT_SHORTNAME}}

**Issue Management:**
- `octokit.issues.get()` — fetch issue details
- `octokit.issues.list()` — list issues with filters
- `octokit.issues.update()` — update issue status/labels

**Pull Requests:**
- `octokit.pulls.create()` — create PR from branch
- `octokit.pulls.list()` — list PRs
- `octokit.pulls.update()` — update PR title/body

**Branches:**
- `octokit.git.createRef()` — create new branch
- `octokit.git.deleteRef()` — delete branch
- `octokit.git.getRef()` — check if branch exists

**Files:**
- `octokit.repos.createOrUpdateFileContents()` — create/update file
- `octokit.repos.deleteFile()` — delete file
- `octokit.repos.getContent()` — read file

**Publishing ({{PROJECT_SHORTNAME}}-Specific):**
- Used in `scripts/core/publish-api.ts` (5-phase workflow)
- Rate limit handled with delays between phases
- Batch updates to avoid max file limits

---

## Summary

✅ **Use @octokit/rest for everything**
✅ **Authenticate via GITHUB_TOKEN**
✅ **Handle rate limits (delay + check remaining)**
✅ **Batch operations when processing many items**
✅ **Use gh CLI only as documented fallback**
❌ **Never ignore errors or rate limits**
❌ **Never hardcode tokens**
