-
-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Eval: Add worktree prep and PR artifacts #34421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
yannbf
merged 53 commits into
project/sb-agentic-setup
from
kasper/eval-worktrees-chromatic-pr
Apr 15, 2026
Merged
Changes from 39 commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
940e76d
Eval: Add worktree prep and PR artifacts
kasperpeulen 20eeecb
Eval: Improve transcript docs rendering
kasperpeulen a45d092
Eval: copy transcript docs from MCP
kasperpeulen 0c5db20
Eval: keep transcript template as TSX
kasperpeulen fbf4ad7
Eval: link transcript template to GitHub source
kasperpeulen 6428925
Fix eval formatting drift
kasperpeulen 68f7fef
Fix CI type errors
kasperpeulen 29f5f8c
Eval: stop injecting Chromatic workflow into trials
kasperpeulen fe23d3f
Eval: use repo-backed Storybook support
kasperpeulen b157f85
Eval: fix script formatting
kasperpeulen c64e42a
Vitest: enable retina screenshot captures
kasperpeulen d9e763a
Vitest: gate retina screenshots on env
kasperpeulen e9a5868
Vitest: use screenshot flag for retina mode
kasperpeulen 6dc4d72
Eval: unify trial data and baseline docs
kasperpeulen dd058e9
Eval: format data pipeline files
kasperpeulen 0f73e3a
Eval: fix screenshot typing and project list
kasperpeulen 88fe49e
Eval: write provisional data before grading
kasperpeulen 1013961
Eval: re-add evergreen project
kasperpeulen 061acb0
Eval: handle empty Claude tool results
kasperpeulen 92d8e3f
Vitest: refresh config update snapshots
kasperpeulen 7c3821b
Eval: unify .storybook baseline syncing
kasperpeulen 8babe7b
Eval: score preview gain on generated stories
kasperpeulen 4248a42
Eval: refine trial prep and preview setup
kasperpeulen 2dda826
Eval: interleave batch runs across projects
kasperpeulen 798eb60
Eval: refine trial collection and batch controls
kasperpeulen e3f41aa
Tests: fix formatting and stabilize CI assertions
kasperpeulen 959740b
Vitest: keep screenshot config out of public templates
kasperpeulen 9ce34d7
Build: fix production check failures
kasperpeulen 25f017e
Eval: fast-forward source baseline before sync
kasperpeulen eeccece
Format eval batch types and baseline test assertions
kasperpeulen 33ae28e
refactor eval harness for play-driven trials
kasperpeulen 0372c7a
Remove screenshot plumbing from addon-vitest
kasperpeulen 14d1e91
Revert extra ghost-story and vitest test changes
kasperpeulen 94d9872
Restore review-pr skill and trim Vitest noise
kasperpeulen 5c90b6c
Merge remote-tracking branch 'origin/project/sb-agentic-setup' into k…
kasperpeulen 3062995
Drop remaining non-eval review noise
kasperpeulen bfb5bb1
Changes from Codex
kasperpeulen 8b926cb
Fix formatting in eval scripts
kasperpeulen 07cb30b
Fix eval scripts lint and type errors
kasperpeulen de01e41
Clarify vitest pass-rate wording
kasperpeulen 5685647
Eval: store sync baselines as string templates
kasperpeulen 3e26b59
Eval: make baseline templates readable
kasperpeulen c4a883e
Eval: make baseline MDX consumers safe
kasperpeulen 4a76b60
Chore: fix eval formatting
kasperpeulen 8929a4c
Fix CI: TypeScript error and stale project list in eval tests
kasperpeulen e495640
Eval: remove syncLocalBuildsToTrial
kasperpeulen d7720a8
Restore trailing newline in scripts/.eslintignore
kasperpeulen 4bf9804
Fix formatting in prepare-trial.test.ts
kasperpeulen f997d25
Fix sync-baselines: auto-clone missing repos and remove premature mai…
kasperpeulen 93cd9dd
Fix formatting in sync-baselines files
kasperpeulen ef3857b
eval: format scores as percentages; default batch to Claude+Codex at …
kasperpeulen c9bba6f
Fix formatting in run-batch.test.ts
kasperpeulen 0045954
Fix CI: remove unused BATCH_AGENT_IDS import
kasperpeulen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,4 +22,3 @@ ember-output | |
| !.eslintrc-markdown.js | ||
| !.jest.config.cjs | ||
| !.storybook | ||
|
|
||
|
kasperpeulen marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,291 @@ | ||
| import { DatabaseSync } from 'node:sqlite'; | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| const execFileSyncMock = vi.fn(); | ||
| const openDatabases: DatabaseSync[] = []; | ||
|
|
||
| vi.mock('node:child_process', () => ({ | ||
| execFileSync: execFileSyncMock, | ||
| })); | ||
|
|
||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| while (openDatabases.length > 0) { | ||
| openDatabases.pop()?.close(); | ||
| } | ||
| }); | ||
|
|
||
| describe('listEvalPullRequests', () => { | ||
| it('parses GitHub CLI JSON output on success', async () => { | ||
| execFileSyncMock.mockReturnValueOnce( | ||
| JSON.stringify([ | ||
| { | ||
| number: 123, | ||
| title: '[eval] mealdrop trial-123', | ||
| }, | ||
| ]) | ||
| ); | ||
|
|
||
| const { listEvalPullRequests } = await import('./collect-pr-data.ts'); | ||
|
|
||
| await expect(listEvalPullRequests('storybook-tmp/mealdrop', 10)).resolves.toMatchObject([ | ||
| { | ||
| number: 123, | ||
| title: '[eval] mealdrop trial-123', | ||
| }, | ||
| ]); | ||
|
|
||
| expect(execFileSyncMock).toHaveBeenCalledWith( | ||
| 'gh', | ||
| expect.arrayContaining(['pr', 'list', '--state', 'all']), | ||
| expect.any(Object) | ||
| ); | ||
| }); | ||
|
|
||
| it('passes through an explicit PR state override', async () => { | ||
| execFileSyncMock.mockReturnValueOnce('[]'); | ||
|
|
||
| const { listEvalPullRequests } = await import('./collect-pr-data.ts'); | ||
|
|
||
| await expect(listEvalPullRequests('storybook-tmp/mealdrop', 10, 'open')).resolves.toEqual([]); | ||
|
|
||
| expect(execFileSyncMock).toHaveBeenCalledWith( | ||
| 'gh', | ||
| expect.arrayContaining(['pr', 'list', '--state', 'open']), | ||
| expect.any(Object) | ||
| ); | ||
| }); | ||
|
|
||
| it('throws a clear error when GitHub CLI cannot list PRs', async () => { | ||
| execFileSyncMock.mockImplementationOnce(() => { | ||
| throw Object.assign(new Error('Command failed: gh'), { | ||
| status: 1, | ||
| stderr: Buffer.from('authentication required\n'), | ||
| }); | ||
| }); | ||
|
|
||
| const { listEvalPullRequests } = await import('./collect-pr-data.ts'); | ||
|
|
||
| await expect(listEvalPullRequests('storybook-tmp/mealdrop', 10)).rejects.toThrow( | ||
| /Failed to list eval PRs for storybook-tmp\/mealdrop: .*stderr: authentication required/ | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('parseCliArgs', () => { | ||
| it('defaults PR state to all', async () => { | ||
| const { parseCliArgs } = await import('./collect-pr-data.ts'); | ||
|
|
||
| expect(parseCliArgs([])).toMatchObject({ | ||
| prState: 'all', | ||
| }); | ||
| }); | ||
|
|
||
| it('parses --state open', async () => { | ||
| const { parseCliArgs } = await import('./collect-pr-data.ts'); | ||
|
|
||
| expect(parseCliArgs(['--state', 'open'])).toMatchObject({ | ||
| prState: 'open', | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('normalizeTrialData', () => { | ||
| it('ingests v3 payloads while ignoring screenshot-era fields', async () => { | ||
| const { normalizeTrialData } = await import('./collect-pr-data.ts'); | ||
|
|
||
| const normalized = normalizeTrialData({ | ||
| trialId: 'trial-123', | ||
| data: createEvalDataPayload({ | ||
| schemaVersion: 3, | ||
| screenshots: [ | ||
| { | ||
| storyFilePath: 'src/Button.stories.tsx', | ||
| exportName: 'Primary', | ||
| imagePath: 'src/Button.stories.Primary.chromium.png', | ||
| }, | ||
| ], | ||
| artifacts: { | ||
| buildOutput: { | ||
| path: '.storybook/eval-results/build-output.txt', | ||
| }, | ||
| typecheckOutput: { | ||
| path: '.storybook/eval-results/typecheck-output.txt', | ||
| }, | ||
| screenshotOutput: { | ||
| path: '.storybook/eval-results/screenshot-output.txt', | ||
| }, | ||
| }, | ||
| }), | ||
| }); | ||
|
|
||
| expect(normalized).toMatchObject({ | ||
| dataSchemaVersion: 3, | ||
| ghostBefore: { | ||
| candidateCount: 4, | ||
| total: 2, | ||
| passed: 1, | ||
| }, | ||
| ghostAfter: { | ||
| candidateCount: 4, | ||
| total: 2, | ||
| passed: 2, | ||
| }, | ||
| buildOutputPath: '.storybook/eval-results/build-output.txt', | ||
| typecheckOutputPath: '.storybook/eval-results/typecheck-output.txt', | ||
| }); | ||
| expect(normalized).not.toHaveProperty('screenshots'); | ||
| expect(normalized).not.toHaveProperty('screenshotOutputPath'); | ||
| }); | ||
|
|
||
| it('rejects v4 payloads that still include screenshots', async () => { | ||
| const { normalizeTrialData } = await import('./collect-pr-data.ts'); | ||
|
|
||
| expect(() => | ||
| normalizeTrialData({ | ||
| trialId: 'trial-123', | ||
| data: createEvalDataPayload({ | ||
| schemaVersion: 4, | ||
| screenshots: [], | ||
| }), | ||
| }) | ||
| ).toThrow( | ||
| /data\.json\.schemaVersion 4 must not include screenshot-era fields: data\.json\.screenshots/ | ||
| ); | ||
| }); | ||
|
|
||
| it('rejects v4 payloads that still include screenshot artifacts', async () => { | ||
| const { normalizeTrialData } = await import('./collect-pr-data.ts'); | ||
|
|
||
| expect(() => | ||
| normalizeTrialData({ | ||
| trialId: 'trial-123', | ||
| data: createEvalDataPayload({ | ||
| schemaVersion: 4, | ||
| artifacts: { | ||
| buildOutput: { | ||
| path: '.storybook/eval-results/build-output.txt', | ||
| }, | ||
| typecheckOutput: { | ||
| path: '.storybook/eval-results/typecheck-output.txt', | ||
| }, | ||
| screenshotOutput: { | ||
| path: '.storybook/eval-results/screenshot-output.txt', | ||
| }, | ||
| }, | ||
| }), | ||
| }) | ||
| ).toThrow( | ||
| /data\.json\.schemaVersion 4 must not include screenshot-era fields: data\.json\.artifacts\.screenshotOutput/ | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('ensureSchema', () => { | ||
| it('creates the rebuilt schema without screenshot storage', async () => { | ||
| const { ensureSchema } = await import('./collect-pr-data.ts'); | ||
| const db = createInMemoryDb(); | ||
|
|
||
| ensureSchema(db, '/tmp/eval-pr-data.sqlite'); | ||
|
|
||
| const trialColumns = db.prepare('PRAGMA table_info(trials)').all() as Array<{ name: string }>; | ||
| const tableNames = db | ||
| .prepare(` | ||
| SELECT name | ||
| FROM sqlite_master | ||
| WHERE type = 'table' | ||
| `) | ||
| .all() as Array<{ name: string }>; | ||
|
|
||
| expect(trialColumns.map((column) => column.name)).not.toContain('screenshot_output_path'); | ||
| expect(tableNames.map((table) => table.name)).not.toContain('trial_screenshots'); | ||
| }); | ||
|
|
||
| it('fails fast on a legacy screenshot-era cache DB', async () => { | ||
| const { ensureSchema } = await import('./collect-pr-data.ts'); | ||
| const db = createInMemoryDb(); | ||
|
|
||
| db.exec(` | ||
| CREATE TABLE trials ( | ||
| trial_id TEXT PRIMARY KEY, | ||
| screenshot_output_path TEXT | ||
| ); | ||
| `); | ||
|
|
||
| expect(() => ensureSchema(db, '/tmp/eval-pr-data.sqlite')).toThrow( | ||
| /Delete \.cache\/eval-pr-data\.sqlite .* rerun scripts\/eval\/collect-pr-data\.ts/ | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| function createInMemoryDb() { | ||
| const db = new DatabaseSync(':memory:'); | ||
| openDatabases.push(db); | ||
| return db; | ||
| } | ||
|
|
||
| function createEvalDataPayload(overrides: Record<string, unknown>) { | ||
| return { | ||
| schemaVersion: 4, | ||
| id: 'trial-123', | ||
| timestamp: '2026-04-14T01:02:03.000Z', | ||
| prompt: { | ||
| name: 'setup', | ||
| content: 'prompt body', | ||
| }, | ||
| baselineCommit: 'deadbeef', | ||
| variant: { | ||
| agent: 'codex', | ||
| model: 'gpt-5.4', | ||
| effort: 'high', | ||
| }, | ||
| environment: { | ||
| nodeVersion: 'v22.22.1', | ||
| evalBranch: 'trial/test-branch', | ||
| evalCommit: 'abc123', | ||
| }, | ||
| execution: { | ||
| cost: 0.12, | ||
| duration: 30, | ||
| durationApi: 20, | ||
| turns: 3, | ||
| terminalResultSubtype: 'success', | ||
| }, | ||
| grade: { | ||
| buildSuccess: true, | ||
| typeCheckErrors: 0, | ||
| baselineGhostStories: { | ||
| candidateCount: 4, | ||
| total: 2, | ||
| passed: 1, | ||
| }, | ||
| ghostStories: { | ||
| candidateCount: 4, | ||
| total: 2, | ||
| passed: 2, | ||
| }, | ||
| baselinePreviewStories: { | ||
| total: 4, | ||
| passed: 1, | ||
| }, | ||
| storyRender: { | ||
| total: 4, | ||
| passed: 3, | ||
| }, | ||
| fileChanges: [] as Array<Record<string, unknown>>, | ||
| }, | ||
| transcript: [] as unknown[], | ||
| artifacts: { | ||
| buildOutput: { | ||
| path: '.storybook/eval-results/build-output.txt', | ||
| }, | ||
| typecheckOutput: { | ||
| path: '.storybook/eval-results/typecheck-output.txt', | ||
| }, | ||
| }, | ||
| ...overrides, | ||
| }; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.