Files
gstack/test/review-dispatch-origin-base.test.ts
Garry Tanandcapy-ai[bot] 3f1741ddb4 v1.92.17.0 fix(review): pass takes DIFF_BASE from the origin merge base (GST-716) (#3780)
* test(review): pass names the origin merge base when local main lags (GST-716, failing first)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review): pass takes DIFF_BASE from the origin merge base, as the plan and targeted check do (GST-716)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(review): the origin-base test names the dispatch bin it runs (test-of-test ratchet)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-01 07:28:08 +00:00

50 lines
3.6 KiB
TypeScript

/**
* gstack-review-dispatch pass takes its readers' DIFF_BASE from origin/<base> (GST-716, GSTA-226): with a local <base>
* that lags origin/<base>, the plan scored the origin diff while every brief named the stale local merge base, so the
* readers reviewed upstream commits the branch only inherited.
*/
import { afterEach, describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
const DISPATCH = path.join(import.meta.dir, '..', 'bin', 'gstack-review-dispatch');
const BIN = path.dirname(DISPATCH);
const dirs: string[] = [];
afterEach(() => { while (dirs.length) fs.rmSync(dirs.pop()!, { recursive: true, force: true }); });
function fixture() {
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'rd-origin-')));
dirs.push(root);
const work = path.join(root, 'work');
const env = { ...process.env, PATH: BIN + path.delimiter + process.env.PATH, GSTACK_STATE_ROOT: path.join(root, 'state'), GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@t', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@t', GIT_CONFIG_GLOBAL: path.join(root, 'gitconfig') };
fs.writeFileSync(path.join(root, 'gitconfig'), '[commit]\n\tgpgsign = false\n');
const sh = (cwd: string, c: string) => { const r = spawnSync('bash', ['-c', c], { cwd, env, encoding: 'utf8', timeout: 60_000 }); if (r.status !== 0) throw new Error(`${c}: ${r.stderr}`); return r.stdout.trim(); };
sh(root, 'git init -q --bare -b main origin.git && git clone -q origin.git work && git clone -q origin.git other 2>/dev/null || true');
sh(work, "echo '{}' > package.json && mkdir src && echo 'export const a = 1;' > src/a.ts && git add -A && git commit -qm base && git push -q origin main");
sh(root, 'rm -rf other && git clone -q origin.git other');
sh(path.join(root, 'other'), 'mkdir -p up && for i in $(seq 1 12); do echo "export const u$i = $i;" > up/u$i.ts; done && git add -A && git commit -qm upstream && git push -q origin main');
sh(work, 'git fetch -q origin && git checkout -qb feat origin/main && for i in $(seq 1 60); do echo "export const v$i = $i;" >> src/a.ts; done && git commit -qam feat');
return { work, env, sh };
}
describe('pass reads DIFF_BASE from origin/<base> (GST-716)', () => {
// Value: protects=a stale local <base> never widens what the readers review past what the plan scored;
// fails_when=pass sets the manifest diff_base from `git merge-base <base> HEAD` on the local ref while origin/<base> is ahead;
// why_new=GSTA-226 paid ~12,400 credits for readers reviewing 78 upstream files; seam=gstack-review-dispatch pass; no_claim=--diff-base overrides
test('local main behind origin/main: the manifest and every brief name the origin merge base', () => {
const { work, env, sh } = fixture();
const originBase = sh(work, 'git merge-base origin/main HEAD');
expect(originBase).not.toBe(sh(work, 'git merge-base main HEAD'));
const out = path.join(work, '.state-run');
const r = spawnSync(DISPATCH, ['pass', '--base', 'main', '--out', out, '--pass', 'origin-p1'], { cwd: work, env, encoding: 'utf8', timeout: 180_000 });
expect(r.status, r.stdout + r.stderr).toBe(0);
const m = JSON.parse(fs.readFileSync(path.join(out, 'pass-manifest.json'), 'utf8')) as { diff_base: string };
expect(m.diff_base).toBe(originBase);
const briefs = fs.readdirSync(path.join(out, 'briefs')).filter(f => f.endsWith('.prompt.md')).map(f => fs.readFileSync(path.join(out, 'briefs', f), 'utf8'));
expect(briefs.length).toBeGreaterThan(0);
for (const b of briefs) expect(b).toContain(originBase);
}, 240_000);
});