diff --git a/packages/review-kit/CHANGELOG.md b/packages/review-kit/CHANGELOG.md index 72b2195..1ce7d1e 100644 --- a/packages/review-kit/CHANGELOG.md +++ b/packages/review-kit/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to `@agentworkforce/review-kit` will be documented in this f ## [Unreleased] +### Fixed + +- Skip non-pull-request events before requesting a gateway expansion, while + continuing to surface expansion failures for genuine pull-request events. + ## [4.1.30] - 2026-07-17 ### Added @@ -17,4 +22,3 @@ All notable changes to `@agentworkforce/review-kit` will be documented in this f - Add strongly typed review-agent and review-persona factories that encode the seven silent review plumbing traps from workforce#281. - Add built-in PR diff and git-history evidence providers. - diff --git a/packages/review-kit/src/agent.ts b/packages/review-kit/src/agent.ts index 22e0759..036c346 100644 --- a/packages/review-kit/src/agent.ts +++ b/packages/review-kit/src/agent.ts @@ -124,6 +124,15 @@ function createParsedReviewHandler( event: event.type }); + if (!event.type.startsWith('github.pull_request.')) { + ctx.log?.('info', 'review-kit.skipped', { + lens: parsed.lens, + reason: 'non-pull-request-event', + eventType: event.type + }); + return; + } + const payload = (await event.expand('full')).data; const pullRequest = readPullRequest(payload); if (!pullRequest) { diff --git a/packages/review-kit/src/review-kit.test.ts b/packages/review-kit/src/review-kit.test.ts index 63555c8..1f75ab8 100644 --- a/packages/review-kit/src/review-kit.test.ts +++ b/packages/review-kit/src/review-kit.test.ts @@ -5,11 +5,12 @@ import { join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import test from 'node:test'; import { extractAgentSpec } from '@agentworkforce/deploy'; -import type { - HarnessRunArgs, - WorkforceCtx, - WorkforceEvent, - WritebackResult +import { + envelopeToAgentEvent, + type HarnessRunArgs, + type WorkforceCtx, + type WorkforceEvent, + type WritebackResult } from '@agentworkforce/runtime'; import { defineReviewAgent, @@ -191,6 +192,74 @@ test('trap 6: a missing writeback receipt is logged as unconfirmed and never thr ); }); +test('non-PR events skip before requesting an unavailable expansion', async () => { + const fixture = handlerFixture(); + const event = envelopeToAgentEvent({ + id: 'evt-manual', + workspace: 'workspace-1', + type: 'cron.tick', + occurredAt: '2026-07-17T15:18:00Z', + name: 'manual', + cron: 'manual' + }); + assert.ok(event); + + await assert.rejects( + () => event.expand('full'), + /expand\("full"\) is unavailable/u, + 'the real cron event shape has no gateway expansion loader' + ); + await assert.doesNotReject(() => fixture.handler(fixture.ctx, event)); + + assert.equal(fixture.harnessRuns(), 0); + assert.equal(fixture.writes.length, 0); + assert.deepEqual(fixture.logs.at(-1), { + level: 'info', + message: 'review-kit.skipped', + fields: { + lens: 'maintainability', + reason: 'non-pull-request-event', + eventType: 'cron.tick' + } + }); +}); + +test('PR expansion failures still surface instead of being mistaken for non-PR events', async () => { + const fixture = handlerFixture(); + const expansionFailure = new Error('github gateway expansion failed'); + const event = { + type: 'github.pull_request.opened', + expand: async () => { + throw expansionFailure; + } + } as unknown as WorkforceEvent; + + await assert.rejects(() => fixture.handler(fixture.ctx, event), expansionFailure); + assert.equal(fixture.logs.some((entry) => entry.message === 'review-kit.skipped'), false); + assert.equal(fixture.harnessRuns(), 0); + assert.equal(fixture.writes.length, 0); +}); + +test('expanded but malformed PR payloads retain the unreadable-pull-request guard', async () => { + const fixture = handlerFixture(); + + await assert.doesNotReject(() => + fixture.handler(fixture.ctx, fixture.eventForData({ malformed: true })) + ); + + assert.equal(fixture.harnessRuns(), 0); + assert.equal(fixture.writes.length, 0); + assert.deepEqual(fixture.logs.at(-1), { + level: 'info', + message: 'review-kit.skipped', + fields: { + lens: 'maintainability', + reason: 'unreadable-pull-request', + keys: 'malformed' + } + }); +}); + test('plumbing: repository, label, and draft guards skip before requiring a mount', async () => { const handler = createReviewHandler({ repo: REPO,