mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-26 22:51:47 +02:00
* feat: bind shared-code review advice to source and branch * feat: add shared-code extraction audit and scoped review checks * test: recognize complete source reads and explicit coverage legends * chore: bump version and changelog (v1.88.0.0) Co-Authored-By: OpenAI Codex <noreply@openai.com> * test: capture native review questions and retain public evidence Capture the actual first public native question with strict ownership and display matching. Preserve terminal failures and raw evidence, and retain SDK completion checks. * test: recognize verified review evidence and complete fixtures Recognize complete source and diagram evidence, concrete design and developer-experience decisions, and the complete planted scenario contracts. Preserve negative controls and grading thresholds. * fix: preserve decision brief structure in native questions Keep the required pros-and-cons heading and final Net field in native question text. Regenerate host outputs and document the release and evaluation repairs. Co-Authored-By: OpenAI Codex <noreply@openai.com> * docs: update project documentation for v1.88.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix: correct eval retry accounting and ship workflow gates * fix: capture native eval evidence and stabilize CI fixtures * fix: keep shared-code eval skips read-only Choose explicit no-change answers instead of mixed fix/preservation options. Reuse the bounded revalidation prompt for path fixtures so required review metadata is available without repeated discovery. Preserve source checks, retry limits, and failed native terminal outcomes. Add captured-question and callback regressions, plus evaluation selection coverage for the affected fixtures. --------- Co-authored-by: OpenAI Codex <noreply@openai.com>
223 lines
11 KiB
TypeScript
223 lines
11 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { findFilesBySuffix, gitArgvIn } from './helpers/scratch-repo';
|
|
import { canReuseSharedLibsAdvisory, sharedLibsFingerprint } from '../lib/review-evidence';
|
|
|
|
const ROOT = resolve(import.meta.dir, '..');
|
|
let repo: string;
|
|
let home: string;
|
|
|
|
function cli(name: string, args: string[] = [], cwd = repo) {
|
|
return execFileSync(join(ROOT, 'bin', name), args, {
|
|
cwd, env: { ...process.env, GSTACK_HOME: home }, encoding: 'utf8', timeout: 10_000,
|
|
}).trim();
|
|
}
|
|
|
|
function git(...args: string[]) {
|
|
const result = gitArgvIn(repo, args, 10_000);
|
|
if (result.error || result.status !== 0)
|
|
throw new Error(`Fixture git failed: ${result.error?.message ?? result.stderr.toString()}`);
|
|
return result.stdout.toString().trim();
|
|
}
|
|
|
|
function log(token?: string, overrides: Record<string, any> = {}) {
|
|
const record = {
|
|
skill: 'review', status: 'clean', timestamp: new Date().toISOString(),
|
|
commit: git('rev-parse', '--short', 'HEAD'), completed: true, converged: true, cycles: 0,
|
|
...overrides,
|
|
};
|
|
cli('gstack-review-log', [JSON.stringify(record), ...(token ? ['--finish', token] : [])]);
|
|
return rows().at(-1)!;
|
|
}
|
|
|
|
function rows() {
|
|
return cli('gstack-review-read').split('---CONFIG---')[0].trim().split('\n').map(line => JSON.parse(line));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
repo = mkdtempSync(join(tmpdir(), 'review-start-repo-'));
|
|
home = mkdtempSync(join(tmpdir(), 'review-start-state-'));
|
|
git('init', '-q', '-b', 'main');
|
|
writeFileSync(join(repo, 'source.ts'), 'export const value = 1;\n');
|
|
git('add', 'source.ts');
|
|
git('commit', '-qm', 'initial');
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(repo, { recursive: true, force: true });
|
|
rmSync(home, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('review start/end binding (#2803)', () => {
|
|
test('unchanged completed review is current, including an identical-content commit', () => {
|
|
writeFileSync(join(repo, 'source.ts'), 'export const value = 2;\n');
|
|
writeFileSync(join(repo, 'new.ts'), 'export {};\n');
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
git('add', 'source.ts', 'new.ts');
|
|
git('commit', '-qm', 'reviewed content');
|
|
const row = log(token);
|
|
expect(row.review_binding.state).toBe('verified');
|
|
expect(row.review_binding.start_wtree).toBe(row.wtree);
|
|
expect(row.review_binding.end_wtree).toBe(row.wtree);
|
|
expect(row.review_binding.started_at).toMatch(/^\d{4}-/);
|
|
expect(row.review_binding.branch_id).toBe('0d6e4079e36703ebd37c00722f5891d28b0e2811dc114b129215123adcce3605');
|
|
expect(row.review_freshness.status).toBe('CURRENT');
|
|
git('commit', '--amend', '--no-edit');
|
|
expect(rows()[0].review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
for (const file of ['source.ts', 'untracked.ts']) {
|
|
test(`mid-review ${file} edit is stale even at zero commit distance`, () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
writeFileSync(join(repo, file), 'export const unreviewed = true;\n');
|
|
const row = log(token);
|
|
expect(git('rev-list', '--count', `${row.commit}..HEAD`)).toBe('0');
|
|
expect(row.wtree).toBeUndefined();
|
|
expect(row.review_binding.state).toBe('changed');
|
|
expect(row.review_binding.start_wtree).not.toBe(row.review_binding.end_wtree);
|
|
expect(row.review_freshness.status).toBe('STALE');
|
|
});
|
|
}
|
|
|
|
test('fix commits do not certify the final tree until a new unchanged pass', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
writeFileSync(join(repo, 'source.ts'), 'export const fixed = true;\n');
|
|
git('commit', '-qam', 'fix: review finding');
|
|
expect(log(token, { cycles: 3, converged: false }).review_freshness.status).toBe('STALE');
|
|
const rerun = cli('gstack-review-log', ['--start', 'review']);
|
|
expect(log(rerun, { cycles: 3 }).review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
test('log-only forged binding cannot certify current content', () => {
|
|
const wtree = cli('gstack-wtree');
|
|
const row = log(undefined, {
|
|
wtree, review_binding: { state: 'verified', start_wtree: wtree, end_wtree: wtree, branch_id: 'forged' },
|
|
review_freshness: { status: 'CURRENT' },
|
|
});
|
|
expect(row.wtree).toBeUndefined();
|
|
expect(row.review_binding.state).toBe('uncaptured');
|
|
expect(row.review_binding.branch_id).toBeUndefined();
|
|
expect(row.review_freshness.status).toBe('UNVERIFIED');
|
|
expect(log(wtree).review_freshness.status).toBe('UNVERIFIED');
|
|
expect(log('../forged').review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
test('a valid start supplies the branch digest and discards a caller-forged digest', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
const row = log(token, { review_binding: { branch_id: 'forged' } });
|
|
expect(row.review_binding.branch_id).toBe('0d6e4079e36703ebd37c00722f5891d28b0e2811dc114b129215123adcce3605');
|
|
expect(row.review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
test('colliding log filenames preserve advisory metadata without reusing another raw branch decision', () => {
|
|
writeFileSync(join(repo, 'second.ts'), 'export const other = 2;\n');
|
|
const finding = {
|
|
advisory: true, severity: 'INFORMATIONAL', action: 'skipped',
|
|
evidence_paths: ['source.ts', 'second.ts'], helper_target: { path: 'lib/shared.ts', symbol: 'readValue' },
|
|
};
|
|
const priorFinding = {
|
|
...finding, fingerprint: sharedLibsFingerprint(finding),
|
|
snapshot_covered_paths: [...finding.evidence_paths],
|
|
};
|
|
git('checkout', '-qb', 'feature/a');
|
|
const first = log(cli('gstack-review-log', ['--start', 'review']), { findings: [priorFinding] });
|
|
git('checkout', '-qb', 'feature-a');
|
|
const second = log(cli('gstack-review-log', ['--start', 'review']));
|
|
expect(findFilesBySuffix(home, '-reviews.jsonl')).toHaveLength(1);
|
|
expect(rows()).toHaveLength(2);
|
|
expect(first.findings[0]).toEqual(priorFinding);
|
|
expect(first.findings[0].snapshot_covered_paths).toEqual(['source.ts', 'second.ts']);
|
|
expect(first.wtree).toBe(second.wtree);
|
|
expect(first.review_binding.branch_id).toMatch(/^[0-9a-f]{64}$/);
|
|
expect(first.review_binding.branch_id).not.toBe(second.review_binding.branch_id);
|
|
const currentSnapshot = {
|
|
wtree: second.wtree, branch_id: second.review_binding.branch_id, covered_paths: finding.evidence_paths,
|
|
};
|
|
expect(canReuseSharedLibsAdvisory(first.findings[0], finding, first, currentSnapshot)).toBe(false);
|
|
expect(canReuseSharedLibsAdvisory(first.findings[0], finding, first, {
|
|
...currentSnapshot, branch_id: first.review_binding.branch_id,
|
|
})).toBe(true);
|
|
});
|
|
|
|
test('start receipt is single-use and scoped to the reviewer and branch', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
expect(log(token).review_freshness.status).toBe('CURRENT');
|
|
expect(log(token).review_freshness.status).toBe('UNVERIFIED');
|
|
const wrongSkill = cli('gstack-review-log', ['--start', 'adversarial-review']);
|
|
expect(log(wrongSkill).review_freshness.status).toBe('UNVERIFIED');
|
|
const wrongBranch = cli('gstack-review-log', ['--start', 'review']);
|
|
git('checkout', '-qb', 'other');
|
|
expect(log(wrongBranch).review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
for (const flags of [
|
|
{ completed: false }, { completed: undefined }, { converged: false }, { converged: undefined },
|
|
{ status: 'unavailable' }, { status: 'issues_found', critical: 7, issues_found: 51 },
|
|
{ critical: 7, issues_found: 51 },
|
|
]) {
|
|
test(`incomplete, nonconverged or unresolved result is not current: ${JSON.stringify(flags)}`, () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
expect(log(token, flags).review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
}
|
|
|
|
test('post-log untracked edits invalidate a previously current review', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'review']);
|
|
log(token);
|
|
writeFileSync(join(repo, 'later.ts'), 'export {};\n');
|
|
expect(rows()[0].review_freshness.status).toBe('STALE');
|
|
});
|
|
|
|
test('a Codex pass needs a genuine unchanged rerun after fixes', () => {
|
|
const original = cli('gstack-review-log', ['--start', 'codex-review']);
|
|
writeFileSync(join(repo, 'source.ts'), 'export const fixed = true;\n');
|
|
expect(log(original, { skill: 'codex-review' }).review_freshness.status).toBe('STALE');
|
|
const rerun = cli('gstack-review-log', ['--start', 'codex-review']);
|
|
expect(log(rerun, { skill: 'codex-review' }).review_freshness.status).toBe('CURRENT');
|
|
});
|
|
|
|
for (const [findings, findings_fixed, freshness] of [
|
|
[2, 0, 'UNVERIFIED'],
|
|
[2, 1, 'UNVERIFIED'],
|
|
[2, 2, 'CURRENT'],
|
|
[0, 0, 'CURRENT'],
|
|
] as const) {
|
|
test(`Codex gate pass with ${findings_fixed}/${findings} findings resolved grades ${freshness}`, () => {
|
|
const token = cli('gstack-review-log', ['--start', 'codex-review']);
|
|
const row = log(token, { skill: 'codex-review', status: 'clean', gate: 'pass', findings, findings_fixed });
|
|
expect(row.review_binding.state).toBe('verified');
|
|
expect(row.gate).toBe('pass');
|
|
expect(row.review_freshness.status).toBe(freshness);
|
|
});
|
|
}
|
|
|
|
test('legacy diff rows cannot use log-time wtree or HEAD; plan evidence is unchanged', () => {
|
|
log(undefined, { skill: 'plan-eng-review', completed: undefined, converged: undefined });
|
|
const file = findFilesBySuffix(home, '-reviews.jsonl')[0];
|
|
const plan = JSON.parse(readFileSync(file, 'utf8').trim());
|
|
const legacy = { ...plan, skill: 'review', review_freshness: { status: 'CURRENT' } };
|
|
writeFileSync(file, [plan, legacy, { ...legacy, wtree: undefined }].map(r => JSON.stringify(r)).join('\n') + '\n');
|
|
const read = rows();
|
|
expect(read[0]).toEqual(plan);
|
|
expect(read[0].wtree).toBe(cli('gstack-wtree'));
|
|
expect(read[1].review_freshness.status).toBe('UNVERIFIED');
|
|
expect(read[2].review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
|
|
for (const skill of ['adversarial-review', 'codex-review', 'design-review-lite', 'ship']) {
|
|
test(`${skill} cannot fall through to legacy plan handling`, () => {
|
|
expect(log(undefined, { skill }).review_freshness.status).toBe('UNVERIFIED');
|
|
});
|
|
}
|
|
|
|
test('ship metrics cannot impersonate a completed review pass', () => {
|
|
const token = cli('gstack-review-log', ['--start', 'ship']);
|
|
const row = log(token, { skill: 'ship' });
|
|
expect(row.review_freshness.status).toBe('UNVERIFIED');
|
|
expect(row.review_freshness.reason).toContain('telemetry');
|
|
});
|
|
});
|