mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
* feat(ship-receipt): carry across regenerated outputs and onto a reviewed tree after a rebase
A base merge whose only product-side change is a registered output the
pregate regen check proved fresh now carries the receipt. A rebased head
carries when its tree is one the receipt covers and its product patch-id
against the current base matches (RECEIPT_CARRY_TREE_MISMATCH otherwise).
Docs: merge main into a reviewed branch, never rebase it.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: regenerate doctor anchors; type the head_tree assert
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(review): proportional /ship review depth (light, standard, full)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: carry sets aside only outputs the regen check proved it recreates
The regen check deletes each touched output in its scratch worktree before
the command runs and records the ones recreated byte-identically as proven.
gstack-ship-receipt carry exempts only those paths, and only when the check
ran origin/<base>'s registry at the commit the carry reads. A file under an
output glob that the generator never writes stays product (pre-landing
review, #3564 pass 1).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(ship-receipt): record review depth, check it in the queue, structured review-loop exception (#3559)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: prove generated outputs in a second regen run; literal names only
The regen verdict comes from the unmodified run again, so a generator that
reads its own registered output (gen-skill-docs and
qa/templates/functional-report-template.md) passes as before; the proof is a
second run with the touched outputs deleted, and its failure only means not
proven. The carry ignores a proven path with glob syntax, since it uses the
paths as pathspec globs (pre-landing review, #3564 pass 2).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: receipt evidence keys and carry record fields; carry refusal fix line
Documentation audit for #3564 (/document-release, audit ship3564-docs-1).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes (depth scan binding, queue scope, carry checks)
- carry refuses a --head other than the checked-out HEAD, a receipt with
gstack_version but no review_depth, and an owner exception that no longer
stands; a dirty or foreign checkout makes the write-time gate scan
unavailable (full)
- the queue binds a gate scan only to the full bound commit and recomputes
the scope signals with gstack-diff-scope <base> --head <sha>, the same
range the writer uses
- the queue checks the review-loop exception's fix-check ancestry
- a package.json version line counts as stamp-only only when it is a bare
version key
- docs and help name what review_depth is and which registry the queue reads
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: CHANGELOG for the review fixes; defer two plan items to TODOS
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: queue and carry refusal lists name the depth and exception codes
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: approval-classes summary-line regexes allow the receipt's depth field
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-receipt): summary line prints depth before approval
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Revert "test: approval-classes summary-line regexes allow the receipt's depth field"
This reverts commit e9ec264358.
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
43 lines
2.1 KiB
TypeScript
43 lines
2.1 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { runScriptSync } from '../lib/script-invocation';
|
|
|
|
const ROOT = path.join(import.meta.dir, '..');
|
|
const SCRIPT = path.join(ROOT, 'scripts', 'review-dispatch-compare.ts');
|
|
const FIXTURES = ['test/fixtures/review-depth/docs-only.json', 'test/fixtures/review-depth/code.json'].map(f => path.join(ROOT, f));
|
|
const run = (args: string[]) => {
|
|
const r = runScriptSync(SCRIPT, [...FIXTURES.flatMap(f => ['--fixture', f]), ...args], { cwd: ROOT, encoding: 'utf8', timeout: 120_000 });
|
|
expect(r.status).toBe(0);
|
|
return r.stdout as string;
|
|
};
|
|
|
|
describe('scripts/review-dispatch-compare.ts', () => {
|
|
test('the new policy never dispatches more reviewers than the legacy one, and a docs diff costs under a third', () => {
|
|
const out = JSON.parse(run(['--json']));
|
|
expect(out.tokens_source).toBe('DEFAULT');
|
|
expect(out.ship.length).toBe(10);
|
|
for (const row of out.ship) {
|
|
expect(row.new_dispatches).toBeLessThanOrEqual(row.legacy_dispatches);
|
|
expect(row.new_tokens).toBeLessThanOrEqual(row.legacy_tokens);
|
|
}
|
|
for (const row of out.ship.filter((r: any) => r.class === 'docs')) expect(row.ratio).toBeLessThan(0.34);
|
|
for (const f of out.autoplan) for (const r of f.runs) expect(r.next).toBeLessThanOrEqual(r.legacy);
|
|
});
|
|
|
|
test('--tokens replaces the default output estimates and the table says which it used', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dispatch-compare-'));
|
|
try {
|
|
const tok = path.join(dir, 'measured.json');
|
|
fs.writeFileSync(tok, JSON.stringify({ specialist: { full: 6929, light: 5446 } }));
|
|
const table = run(['--tokens', tok]);
|
|
expect(table).toContain(`TOKENS: measured (${tok})`);
|
|
expect(table).toMatch(/^SHIP_TOKEN_RATIO: median=0\.\d+ min=0\.\d+ max=\d\.\d+ \(n=10\)$/m);
|
|
expect(table).toMatch(/^SHIP_TOKEN_RATIO_BY_CLASS: docs median=0\.\d+ \(n=5\); gate median=0\.\d+ \(n=5\)$/m);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|