ForgeAdapter interface, contract tests, adding methods, rate limit handling
ForgeAdapter abstracts GitHub and Forgejo behind a common interface. All forge operations go through this — never call Octokit or Forgejo API directly.
// src/forge/adapter.ts
export interface ForgeAdapter {
getIssue(owner: string, repo: string, number: number): Promise<Issue>;
listIssues(owner: string, repo: string, opts: ListOpts): Promise<Issue[]>;
createComment(owner: string, repo: string, number: number, body: string): Promise<void>;
setLabels(owner: string, repo: string, number: number, labels: string[]): Promise<void>;
createPullRequest(owner: string, repo: string, pr: PullRequestInput): Promise<PullRequest>;
// ... etc
}
src/forge/adapter.tssrc/forge/github.tssrc/forge/forgejo.tstest/forge/contract.test.ts// test/forge/contract.test.ts
import { describe, it, expect } from 'vitest';
function forgeContractTests(name: string, createAdapter: () => ForgeAdapter) {
describe(`${name} ForgeAdapter contract`, () => {
it('getIssue returns issue with required fields', async () => {
const adapter = createAdapter();
const issue = await adapter.getIssue('owner', 'repo', 1);
expect(issue).toHaveProperty('number');
expect(issue).toHaveProperty('title');
expect(issue).toHaveProperty('body');
expect(issue).toHaveProperty('labels');
});
// ... more contract tests
});
}
// Run for both adapters
forgeContractTests('GitHub', () => new GitHubAdapter(mockOctokit));
forgeContractTests('Forgejo', () => new ForgejoAdapter(mockClient));
x-ratelimit-remaining header on responsesx-ratelimit-reset timestampwarn leveldebug level — never full bodiesdocs/specs-active/phase-11-forgejo.md (Forgejo) or relevant phasepnpm test -- --run test/forge/contract.test.ts