Skip to content
Merged
Show file tree
Hide file tree
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 Mar 31, 2026
20eeecb
Eval: Improve transcript docs rendering
kasperpeulen Apr 1, 2026
a45d092
Eval: copy transcript docs from MCP
kasperpeulen Apr 1, 2026
0c5db20
Eval: keep transcript template as TSX
kasperpeulen Apr 1, 2026
fbf4ad7
Eval: link transcript template to GitHub source
kasperpeulen Apr 1, 2026
6428925
Fix eval formatting drift
kasperpeulen Apr 1, 2026
68f7fef
Fix CI type errors
kasperpeulen Apr 1, 2026
29f5f8c
Eval: stop injecting Chromatic workflow into trials
kasperpeulen Apr 1, 2026
fe23d3f
Eval: use repo-backed Storybook support
kasperpeulen Apr 1, 2026
b157f85
Eval: fix script formatting
kasperpeulen Apr 1, 2026
c64e42a
Vitest: enable retina screenshot captures
kasperpeulen Apr 1, 2026
d9e763a
Vitest: gate retina screenshots on env
kasperpeulen Apr 1, 2026
e9a5868
Vitest: use screenshot flag for retina mode
kasperpeulen Apr 1, 2026
6dc4d72
Eval: unify trial data and baseline docs
kasperpeulen Apr 2, 2026
dd058e9
Eval: format data pipeline files
kasperpeulen Apr 2, 2026
0f73e3a
Eval: fix screenshot typing and project list
kasperpeulen Apr 2, 2026
88fe49e
Eval: write provisional data before grading
kasperpeulen Apr 2, 2026
1013961
Eval: re-add evergreen project
kasperpeulen Apr 2, 2026
061acb0
Eval: handle empty Claude tool results
kasperpeulen Apr 2, 2026
92d8e3f
Vitest: refresh config update snapshots
kasperpeulen Apr 2, 2026
7c3821b
Eval: unify .storybook baseline syncing
kasperpeulen Apr 2, 2026
8babe7b
Eval: score preview gain on generated stories
kasperpeulen Apr 3, 2026
4248a42
Eval: refine trial prep and preview setup
kasperpeulen Apr 3, 2026
2dda826
Eval: interleave batch runs across projects
kasperpeulen Apr 3, 2026
798eb60
Eval: refine trial collection and batch controls
kasperpeulen Apr 9, 2026
e3f41aa
Tests: fix formatting and stabilize CI assertions
kasperpeulen Apr 9, 2026
959740b
Vitest: keep screenshot config out of public templates
kasperpeulen Apr 9, 2026
9ce34d7
Build: fix production check failures
kasperpeulen Apr 9, 2026
25f017e
Eval: fast-forward source baseline before sync
kasperpeulen Apr 9, 2026
eeccece
Format eval batch types and baseline test assertions
kasperpeulen Apr 9, 2026
33ae28e
refactor eval harness for play-driven trials
kasperpeulen Apr 14, 2026
0372c7a
Remove screenshot plumbing from addon-vitest
kasperpeulen Apr 14, 2026
14d1e91
Revert extra ghost-story and vitest test changes
kasperpeulen Apr 14, 2026
94d9872
Restore review-pr skill and trim Vitest noise
kasperpeulen Apr 14, 2026
5c90b6c
Merge remote-tracking branch 'origin/project/sb-agentic-setup' into k…
kasperpeulen Apr 14, 2026
3062995
Drop remaining non-eval review noise
kasperpeulen Apr 14, 2026
bfb5bb1
Changes from Codex
kasperpeulen Apr 14, 2026
8b926cb
Fix formatting in eval scripts
kasperpeulen Apr 14, 2026
07cb30b
Fix eval scripts lint and type errors
kasperpeulen Apr 14, 2026
de01e41
Clarify vitest pass-rate wording
kasperpeulen Apr 14, 2026
5685647
Eval: store sync baselines as string templates
kasperpeulen Apr 14, 2026
3e26b59
Eval: make baseline templates readable
kasperpeulen Apr 14, 2026
c4a883e
Eval: make baseline MDX consumers safe
kasperpeulen Apr 14, 2026
4a76b60
Chore: fix eval formatting
kasperpeulen Apr 14, 2026
8929a4c
Fix CI: TypeScript error and stale project list in eval tests
kasperpeulen Apr 15, 2026
e495640
Eval: remove syncLocalBuildsToTrial
kasperpeulen Apr 15, 2026
d7720a8
Restore trailing newline in scripts/.eslintignore
kasperpeulen Apr 15, 2026
4bf9804
Fix formatting in prepare-trial.test.ts
kasperpeulen Apr 15, 2026
f997d25
Fix sync-baselines: auto-clone missing repos and remove premature mai…
kasperpeulen Apr 15, 2026
93cd9dd
Fix formatting in sync-baselines files
kasperpeulen Apr 15, 2026
ef3857b
eval: format scores as percentages; default batch to Claude+Codex at …
kasperpeulen Apr 15, 2026
c9bba6f
Fix formatting in run-batch.test.ts
kasperpeulen Apr 15, 2026
0045954
Fix CI: remove unused BATCH_AGENT_IDS import
kasperpeulen Apr 15, 2026
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: 0 additions & 1 deletion scripts/.eslintignore
Comment thread
kasperpeulen marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ ember-output
!.eslintrc-markdown.js
!.jest.config.cjs
!.storybook

291 changes: 291 additions & 0 deletions scripts/eval/collect-pr-data.test.ts
Comment thread
kasperpeulen marked this conversation as resolved.
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,
};
}
Loading
Loading