mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
v1.94.43.0 fix(pregate): gstack-evidence passed skips the tree hash with no ledger row; cheaper setup for two 5 s test cases (#3952)
* fix: gstack-evidence passed skips the tree hash with no ledger row; two local 5 s test cases get cheaper setup (GSTA-262) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: changelog and TODOS 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>
This commit is contained in:
1 parent
e9b40bbd82
commit
913aca6ac0
7 files changed
+27
-21
No files matched your search
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## [1.94.43.0] - 2026-11-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`gstack-evidence passed` skips the tree hash when the ledger has nothing to match.** Before pregate runs a guard, it asks `gstack-evidence passed` whether that exact command already passed at this tree (#3929). Each check hashed the working tree (`gstack-wtree`) before it looked at the ledger. Usually the ledger has no row for the command at all, so that hash was wasted. Each check took 0.19 s idle and 0.74 to 0.94 s on a busy 4-core machine, once per guard. Now the ledger is read first, and the tree is hashed only when a local row names the command. The answer is the same. Only the no-row message drops its "at tree …" suffix.
|
||||
- **Two local test cases no longer run out of time at Bun's default 5 s.** `test/pregate-guards.test.ts`'s guard-timeout case ran two full pregates, each waiting out a real 1 s guard timeout. It is now two cases, one with several guards together and one with a lone timed-out guard. Its fixture also writes the manifest into the base commit instead of making a second commit. On a 4-core machine it went from 4.1 s to 2.0 s and 1.75 s idle. With every core busy (busy loops at nice 10), it went from 0 of 3 runs passing to 10 of 10. `test/spec-quality-gate-secret-sink.test.ts` rendered every host's skill docs in `beforeAll` but reads only the claude and codex output. It now renders just those two, which are byte-identical to the full render: 1.38 s down to 0.37 s. With 16 busy loops, that hook went from timing out in 3 of 3 runs to passing 3 of 3. That is the hook timeout the macOS named-regressions job hit on #3926.
|
||||
|
||||
## [1.94.42.0] - 2026-11-03
|
||||
|
||||
### Tests
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.94.42.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.94.43.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
+5
-3
@@ -653,12 +653,14 @@ function cmdPassed(argv: string[]): number {
|
||||
const sep = argv.indexOf("--");
|
||||
const command = sep < 0 ? "" : argv.slice(sep + 1).join(" ");
|
||||
if (!command || sep !== 0) { console.error("usage: gstack-evidence passed -- <command>"); return 2; }
|
||||
const wtree = currentWtree();
|
||||
if (!wtree) { console.log("NOT_PASSED: no working-tree fingerprint"); return 1; }
|
||||
let records: EvidenceRecord[] = [];
|
||||
try { records = readJsonl<EvidenceRecord>(ledgerPath().file); } catch { records = []; }
|
||||
const cmdSha = sha256(command);
|
||||
const newest = records.findLast((r) => r.cmd_sha256 === cmdSha && r.wtree === wtree && r.source === undefined);
|
||||
const local = records.filter((r) => r.cmd_sha256 === cmdSha && r.source === undefined);
|
||||
if (!local.length) { console.log("NOT_PASSED: no local ledger row for this command"); return 1; }
|
||||
const wtree = currentWtree();
|
||||
if (!wtree) { console.log("NOT_PASSED: no working-tree fingerprint"); return 1; }
|
||||
const newest = local.findLast((r) => r.wtree === wtree);
|
||||
if (!newest) { console.log(`NOT_PASSED: no local ledger row for this command at tree ${wtree.slice(0, 12)}`); return 1; }
|
||||
if (!countedPass(newest, 24)) { console.log(`NOT_PASSED: the newest row (label=${newest.label} ts=${newest.ts}) is not a counted pass within 24 h under this runtime`); return 1; }
|
||||
console.log(`PASSED: label=${newest.label} source=local ts=${newest.reused_from ?? newest.ts}`);
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.94.42",
|
||||
"version": "1.94.43",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -30,7 +30,7 @@ import * as path from 'node:path';
|
||||
import { afterEach } from 'bun:test';
|
||||
import { EXIT } from '../lib/headless-artifacts';
|
||||
import { capyHost, type CapyHost } from './helpers/capy-host';
|
||||
import { BASE_PREGATE, cleanup, commit, makeFixtureRepo, runBin, write, type FixtureRepo } from './helpers/pregate-fixture';
|
||||
import { BASE_PREGATE, cleanup, commit, makeFixtureRepo, runBin, type FixtureRepo } from './helpers/pregate-fixture';
|
||||
|
||||
const repos: FixtureRepo[] = [];
|
||||
const hosts: CapyHost[] = [];
|
||||
@@ -50,16 +50,8 @@ const SERIAL_TEST = "const test = require('node:test');\ntest('serial', () => {}
|
||||
|
||||
/** The base branch gains the manifest, its check script and the given pregate config. */
|
||||
function guardedFixture(pregate: Record<string, unknown>): FixtureRepo {
|
||||
const repo = makeFixtureRepo({ pregate });
|
||||
const repo = makeFixtureRepo({ pregate, baseFiles: { 'scripts/check-serial.js': CHECK_SERIAL, 'scripts/serial-files.tsv': 'path\treason\n' } });
|
||||
repos.push(repo);
|
||||
repo.git('checkout', '-q', 'main');
|
||||
write(repo.work, 'scripts/check-serial.js', CHECK_SERIAL);
|
||||
write(repo.work, 'scripts/serial-files.tsv', 'path\treason\n');
|
||||
repo.git('add', '-A');
|
||||
repo.git('commit', '-q', '-m', 'serial manifest');
|
||||
repo.git('push', '-q', 'origin', 'main');
|
||||
repo.git('checkout', '-q', 'feature');
|
||||
repo.git('merge', '-q', '--ff-only', 'main');
|
||||
return repo;
|
||||
}
|
||||
const pregate = (repo: FixtureRepo, args: string[], env: Record<string, string> = {}) => runBin(repo, 'gstack-pregate', ['--base', 'main', ...args], env);
|
||||
@@ -92,7 +84,7 @@ describe('guards (CD1)', () => {
|
||||
expect(r.out).toContain('GSTACK_RESULT: skill=pregate status=incomplete');
|
||||
});
|
||||
|
||||
test('each guard runs in its own process with its timeout: a failing guard does not stop the next, a timed-out guard is incomplete, never pass', () => {
|
||||
test('each guard runs in its own process with its timeout: a failing guard does not stop the next, a timed-out guard never passes', () => {
|
||||
const repo = guardedFixture({ ...BASE_PREGATE, guards: [
|
||||
{ id: 'first', when: ['lib/**'], command: 'echo first-ran; exit 3' },
|
||||
{ id: 'second', when: ['lib/**'], command: 'touch ../second-ran' },
|
||||
@@ -108,6 +100,9 @@ describe('guards (CD1)', () => {
|
||||
expect(row.status).toBe('fail');
|
||||
expect(row.detail).toContain('first');
|
||||
expect(row.detail).not.toContain('second');
|
||||
});
|
||||
|
||||
test('a timed-out guard with no failing guard beside it is incomplete, never pass', () => {
|
||||
const slowOnly = guardedFixture({ ...BASE_PREGATE, guards: [{ id: 'slow', when: ['lib/**'], command: 'sleep 20', timeout_s: 1 }] });
|
||||
commit(slowOnly, { 'lib/util.js': "exports.add = (a, b) => a + b + 0;\n" });
|
||||
expect(pregate(slowOnly, ['--allow-repo-commands', '--stage', 'preflight', '--only', 'guards']).status).toBe(EXIT.fail);
|
||||
|
||||
@@ -9,10 +9,12 @@ let output: string;
|
||||
|
||||
beforeAll(() => {
|
||||
output = mkdtempSync(join(tmpdir(), 'gstack-spec-gate-render-'));
|
||||
const rendered = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--out-dir', output], {
|
||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
|
||||
});
|
||||
if (rendered.exitCode !== 0) throw new Error(rendered.stderr.toString());
|
||||
for (const host of ['claude', 'codex']) {
|
||||
const rendered = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', host, '--out-dir', output], {
|
||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe', timeout: 120_000,
|
||||
});
|
||||
if (rendered.exitCode !== 0) throw new Error(rendered.stderr.toString());
|
||||
}
|
||||
});
|
||||
afterAll(() => { if (output) rmSync(output, { recursive: true, force: true }); });
|
||||
|
||||
|
||||
Reference in new issue
Block a user