Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ published version with a date and open a fresh empty `[Unreleased]` above it.
### Added

- `@relayfile/adapter-gitlab` now creates issues, branches, and merge requests from file-native drafts; accepts or closes/reopens merge requests through canonical sidecars; and publishes schemas, examples, catalog paths, and `LAYOUT.md` guidance for every supported GitLab writeback route.
- `@relayfile/adapter-github` now declares and routes pull-request `ready_for_review`, `labeled`, and `unlabeled` webhooks so review flows can wake on draft and policy-label transitions.
- `@relayfile/adapter-github` and the core GitHub mapping now declare `check_run` and `issue_comment` webhook keys, so consumers that read the mapping's `webhooks:` block can subscribe to CI check completions and issue/PR conversation comments.
- `@relayfile/adapter-github` now exports a cursor-resumable pull-index convergence primitive that backfills `headRef` with one GitHub list request per invocation and no per-record metadata, file, or diff fetches.
- `@relayfile/adapter-linear` now materializes `/linear/issues/by-project/<project-id>/<identifier>.json` aliases so project-scoped consumers can avoid mounting the full issue tree. The path mapper and generated `LAYOUT.md` contract expose the new lookup; existing mounts must resync to materialize and discover it.
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/triggers/catalog.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,12 @@
"issues.unlabeled",
"pull_request.closed",
"pull_request.edited",
"pull_request.labeled",
"pull_request.opened",
"pull_request.ready_for_review",
"pull_request.reopened",
"pull_request.synchronize",
"pull_request.unlabeled",
"pull_request_review.dismissed",
"pull_request_review.edited",
"pull_request_review.submitted",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/triggers/catalog.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,12 @@ export const KNOWN_TRIGGER_CATALOG = {
"issues.unlabeled",
"pull_request.closed",
"pull_request.edited",
"pull_request.labeled",
"pull_request.opened",
"pull_request.ready_for_review",
"pull_request.reopened",
"pull_request.synchronize",
"pull_request.unlabeled",
"pull_request_review.dismissed",
"pull_request_review.edited",
"pull_request_review.submitted",
Expand Down
3 changes: 3 additions & 0 deletions packages/github/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const DEFAULT_SUPPORTED_EVENTS = [
'pull_request.synchronize', // new commits were pushed to an open PR (re-review / re-run CI)
'pull_request.edited', // PR title/body/base metadata changed
'pull_request.reopened', // a closed PR was reopened
'pull_request.ready_for_review', // a draft PR became ready for review
'pull_request.labeled', // a label was added to an open PR
'pull_request.unlabeled', // a label was removed from an open PR
'pull_request.closed', // a PR was closed (check `merged` in the payload to tell merge from abandon)
'pull_request_review.submitted', // someone (human or bot) submitted a review — has `review.state` (approved / changes_requested / commented)
'pull_request_review.edited', // review body or metadata changed
Expand Down
3 changes: 3 additions & 0 deletions packages/github/src/scaffold.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ describe('scaffold', () => {
assert.strictEqual(adapter.name, 'github');
assert.strictEqual(adapter.version, '0.1.0');
assert.ok(adapter.supportedEvents().includes('check_run.completed'));
assert.ok(adapter.supportedEvents().includes('pull_request.ready_for_review'));
assert.ok(adapter.supportedEvents().includes('pull_request.labeled'));
assert.ok(adapter.supportedEvents().includes('pull_request.unlabeled'));

const result = await adapter.ingestPullRequest({
number: 42,
Expand Down
3 changes: 3 additions & 0 deletions packages/github/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export const DEFAULT_GITHUB_EVENTS = [
'pull_request.synchronize',
'pull_request.edited',
'pull_request.reopened',
'pull_request.ready_for_review',
'pull_request.labeled',
'pull_request.unlabeled',
'pull_request.closed',
'pull_request_review.submitted',
'pull_request_review.edited',
Expand Down
24 changes: 22 additions & 2 deletions packages/github/src/webhook/__tests__/webhook-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ describe('WebhookRouter', () => {
assert.strictEqual(mocks.ingestCheckRun.mock.calls.length, 0);
});

it('routes PR review-readiness and label changes through pull request updates', async () => {
for (const action of ['ready_for_review', 'labeled', 'unlabeled']) {
const mocks = createAdapterMocks();
const router = new WebhookRouter(mocks.adapter);
const payload = {
action,
repository: { full_name: 'acme/widgets' },
pull_request: { number: 7 },
};

const result = await router.route({ 'x-github-event': 'pull_request' }, payload);
assert.deepStrictEqual(result, createResult('/github/repos/acme/widgets/pulls/7/diff.patch'));
assert.strictEqual(mocks.updatePullRequest.mock.calls.length, 1);
assert.deepStrictEqual(mocks.updatePullRequest.mock.calls[0].arguments, [payload]);
}
});

it('route calls correct handler for issue opened', async () => {
const mocks = createAdapterMocks();
const router = new WebhookRouter(mocks.adapter);
Expand Down Expand Up @@ -274,14 +291,17 @@ describe('WebhookRouter', () => {
assert.strictEqual(router.isSupported('deployment_status.created'), true);
});

it('getSupportedEvents lists all 21 events', () => {
it('getSupportedEvents lists all 24 events', () => {
const router = new WebhookRouter(createAdapterMocks().adapter);

assert.deepStrictEqual(router.getSupportedEvents(), [
'pull_request.opened',
'pull_request.synchronize',
'pull_request.edited',
'pull_request.reopened',
'pull_request.ready_for_review',
'pull_request.labeled',
'pull_request.unlabeled',
'pull_request.closed',
'pull_request_review.submitted',
'pull_request_review.edited',
Expand All @@ -300,6 +320,6 @@ describe('WebhookRouter', () => {
'status',
'deployment_status.created',
]);
assert.strictEqual(router.getSupportedEvents().length, 21);
assert.strictEqual(router.getSupportedEvents().length, 24);
});
});
3 changes: 3 additions & 0 deletions packages/github/src/webhook/event-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const EVENT_MAP: Record<string, WebhookHandler> = {
'pull_request.synchronize': (adapter, payload) => adapter.updatePullRequest(payload),
'pull_request.edited': (adapter, payload) => adapter.updatePullRequest(payload),
'pull_request.reopened': (adapter, payload) => adapter.updatePullRequest(payload),
'pull_request.ready_for_review': (adapter, payload) => adapter.updatePullRequest(payload),
'pull_request.labeled': (adapter, payload) => adapter.updatePullRequest(payload),
'pull_request.unlabeled': (adapter, payload) => adapter.updatePullRequest(payload),
'pull_request.closed': (adapter, payload) => adapter.closePullRequest(payload),
'pull_request_review.submitted': (adapter, payload) => adapter.ingestReview(payload),
'pull_request_review.edited': (adapter, payload) => adapter.ingestReview(payload),
Expand Down
5 changes: 4 additions & 1 deletion packages/github/src/webhook/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,15 @@ test('WebhookRouter.getSupportedEvents returns all mapped events', () => {
const router = new WebhookRouter(new RecordingAdapter());
const events = router.getSupportedEvents();

assert.equal(events.length, 21);
assert.equal(events.length, 24);
assert.deepEqual(events, [
'pull_request.opened',
'pull_request.synchronize',
'pull_request.edited',
'pull_request.reopened',
'pull_request.ready_for_review',
'pull_request.labeled',
'pull_request.unlabeled',
'pull_request.closed',
'pull_request_review.submitted',
'pull_request_review.edited',
Expand Down
Loading