v1.91.5.0 feat: balanced free-suite shards, test:ubicloud, and faster PR eval lane (#2989)

* v1.91.5.0 feat: balanced free-suite shards, 16-way Linux runs, and bun run test:ubicloud

* chore: regenerate agents digest for v1.91.5.0

* ci: serial flaky retry and flake ledger for the Windows free lane

* ci: cancel superseded eval runs; relax LLM-judge clarity bar to 3
This commit is contained in:
Garry Tan authored and GitHub committed 2026-09-28 16:00:12 -07:00
1 parent d2a0bbcf4c
commit 080a655791
30 files changed
+2474 -1784

No files matched your search

+9 -2
View File
@@ -81,12 +81,19 @@ describe('paid CI coordination stays off the eval image', () => {
expect(planner.steps.find(step => step.run?.includes('--emit-plan'))?.run).toContain('bun --no-install run');
});
test(`${name}: cancel-in-progress can stop every job (no job-level always())`, () => {
for (const [id, job] of Object.entries(jobs)) {
const condition = String((job as { if?: unknown }).if ?? '');
expect(`${id}: ${condition}`).not.toMatch(/(^|[^!])always\(\)/);
}
});
test(`${name}: executors still require both prerequisites and consume the image`, () => {
const executor = jobs['eval-slices'];
expect(executor.needs).toEqual(['build-image', 'plan-slices']);
expect(JSON.stringify(executor.container)).toContain('needs.build-image.outputs.image-tag');
if (name === 'evals.yml') {
expect(executor.if).toBe("always() && needs.build-image.result == 'success' && needs.plan-slices.result == 'success'");
expect(executor.if).toBe("${{ !cancelled() && needs.build-image.result == 'success' && needs.plan-slices.result == 'success' }}");
} else {
expect(executor.if).toBeUndefined();
}
@@ -99,7 +106,7 @@ describe('paid CI coordination stays off the eval image', () => {
expect(report.container).toBeUndefined();
expect(report.needs).toContain('plan-slices');
expect(report.needs).toContain('eval-slices');
expect(report.if).toBe("always() && needs.plan-slices.result == 'success'");
expect(report.if).toBe("${{ !cancelled() && needs.plan-slices.result == 'success' }}");
expect(JSON.stringify(report.steps)).not.toMatch(/restore-deps|bun install/);
expect(report.steps.find(step => step.run?.includes('--report'))?.run).toContain('bun --no-install run');
if (name === 'evals.yml') expect(report.permissions).toEqual({ contents: 'read' });
+2
View File
@@ -149,6 +149,8 @@ const SCANNER_EXEMPT: Record<string, string> = {
'served-page JS talking to its own loopback server (same-origin relative fetch)',
'design/src/compare.ts':
'served-page JS talking to its own loopback server (relative ./api fetch)',
'scripts/ubicloud':
'developer-invoked test infrastructure: Ubicloud API calls (VM create/show/destroy) with the developer\'s own token, the checkout streamed over SSH to that developer\'s own ephemeral VM, and toolchain downloads run on the VM; no installed-gstack user state is sent',
// Skill prose templates: these render agent-executed instructions (the
// agent runs git in the USER\'S repo at the user\'s direction), they are
// not gstack binaries. Includes the preamble-generated brain-sync block —
+132
View File
@@ -0,0 +1,132 @@
import { afterEach, expect } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, utimesSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
export const ROOT = resolve(import.meta.dir, '../..');
export const files = spawnSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf8', timeout: 10_000 });
if (files.status !== 0) throw new Error(files.stderr);
export const owned: string[] = [];
export const quote = (s: string) => `'${s.replace(/'/g, `'\\''`)}'`;
export function assertFixtureWrite(file: string) {
const root = owned.find(dir => file === dir || file.startsWith(dir + '/'));
if (!root) throw new Error(`Fixture write outside owned roots: ${file}`);
let existing = file;
while (!existsSync(existing)) {
if (lstatSync(existing, { throwIfNoEntry: false })?.isSymbolicLink()) throw new Error(`Unresolvable fixture link: ${existing}`);
existing = dirname(existing);
}
const target = realpathSync(existing);
const physicalRoot = realpathSync(root);
if (target !== physicalRoot && !target.startsWith(physicalRoot + '/')) throw new Error(`Fixture write escapes physical root: ${file} -> ${target}`);
}
export function fixtureWriteFileSync(...args: Parameters<typeof writeFileSync>) {
assertFixtureWrite(String(args[0]));
return writeFileSync(...args);
}
export function fixtureCopyFileSync(...args: Parameters<typeof copyFileSync>) {
assertFixtureWrite(String(args[1]));
return copyFileSync(...args);
}
export function fixtureMkdirSync(...args: Parameters<typeof mkdirSync>) {
assertFixtureWrite(String(args[0]));
return mkdirSync(...args);
}
export function fixtureUtimesSync(...args: Parameters<typeof utimesSync>) {
assertFixtureWrite(String(args[0]));
return utimesSync(...args);
}
export function tree(dir: string): unknown {
const stat = lstatSync(dir);
if (stat.isSymbolicLink()) return { link: readlinkSync(dir) };
if (stat.isDirectory()) return Object.fromEntries(readdirSync(dir).sort().map(name => [name, tree(join(dir, name))]));
return createHash('sha256').update(readFileSync(dir)).digest('hex');
}
export function fixture(layout: string) {
const dir = mkdtempSync(join(tmpdir(), 'gstack-codex-scope-'));
owned.push(dir);
const home = join(dir, 'home');
const project = join(dir, 'project-a');
const other = join(dir, 'project-b');
const source = layout === 'machine' ? join(home, '.claude/skills/gstack')
: layout === 'ordinary' ? join(project, 'custom-checkout') : join(project, layout, 'skills/gstack');
const commands = join(dir, 'commands');
for (const d of [home, other, commands, source]) fixtureMkdirSync(d, { recursive: true });
for (const rel of [...files.stdout.split('\0').filter(Boolean), 'scripts/external-skill-names.ts', 'scripts/preflight-codex-overlap.ts']) {
if (/^(?:test|docs|browse\/test|\.github)\//.test(rel)) continue;
const dest = join(source, rel);
fixtureMkdirSync(dirname(dest), { recursive: true });
if (lstatSync(join(ROOT, rel)).isSymbolicLink()) {
const target = readlinkSync(join(ROOT, rel));
expect(resolve(dirname(dest), target).startsWith(source + '/')).toBe(true);
symlinkSync(target, dest);
} else fixtureCopyFileSync(join(ROOT, rel), dest);
}
const write = (file: string, content: string) => {
fixtureMkdirSync(dirname(file), { recursive: true });
fixtureWriteFileSync(file, content, { mode: 0o755 });
};
for (const rel of ['browse/dist/browse', 'design/dist/design', 'make-pdf/dist/pdf', 'browse/dist/.build-complete']) {
const file = join(source, rel);
write(file, '#!/bin/sh\nexit 0\n');
fixtureUtimesSync(file, new Date('2040-01-01'), new Date('2040-01-01'));
}
write(join(commands, 'bun'), `#!/usr/bin/env bash
case "$*" in
'install --frozen-lockfile') exit 0 ;;
'build --help') echo 'Fixture Bun has no CSO compile flags'; exit 0 ;;
'run build') echo 'Unexpected build in registration fixture' >&2; exit 90 ;;
*) exec ${quote(process.execPath)} "$@" ;;
esac
`);
const realRm = Bun.which('rm');
if (!realRm) throw new Error('rm is required');
write(join(commands, 'rm'), `#!/usr/bin/env bash
if [ "$#" -eq 2 ] && [ "$1" = -f ] && [ "$2" = /tmp/gstack-latest-version ]; then exit 0; fi
exec ${quote(realRm)} "$@"
`);
for (const name of ['codex', 'claude']) write(join(commands, name), '#!/bin/sh\nexit 0\n');
const global = join(home, '.codex/skills');
const previous = join(dir, 'previous/gstack');
write(join(previous, 'bin/gstack-autoplan-snapshot.ts'), readFileSync(join(ROOT, 'bin/gstack-autoplan-snapshot.ts'), 'utf8'));
write(join(previous, 'lib/claude-bin.ts'), 'export {};\n');
for (const name of ['gstack-review', 'gstack-claude', 'gstack-retired']) {
write(join(previous, name, 'SKILL.md'), `---\nname: ${name}\n---\n<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior global skill.\n`);
fixtureMkdirSync(global, { recursive: true });
if (name === 'gstack-claude') {
write(join(global, name, 'SKILL.md'), readFileSync(join(previous, name, 'SKILL.md'), 'utf8'));
} else symlinkSync(join(previous, name), join(global, name), 'dir');
}
fixtureMkdirSync(join(global, 'gstack'), { recursive: true });
symlinkSync(join(previous, 'bin'), join(global, 'gstack/bin'), 'dir');
symlinkSync(join(previous, 'lib'), join(global, 'gstack/lib'), 'dir');
write(join(global, 'gstack/SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nGlobal router.\n');
write(join(global, 'custom/SKILL.md'), 'User-owned skill.\n');
const env = {
PATH: `${commands}:${process.env.PATH}`, HOME: home, USERPROFILE: home,
CODEX_HOME: join(home, '.codex'), CLAUDE_CONFIG_DIR: join(home, '.claude'),
GSTACK_HOME: join(home, '.gstack'), GSTACK_STATE_ROOT: join(home, '.gstack'),
TMPDIR: dir, TMP: dir, TEMP: dir,
GSTACK_SKIP_PLAYWRIGHT: '1', GSTACK_SKIP_FONTS: '1', GSTACK_SKIP_COREUTILS: '1', GSTACK_SKIP_ASIDE: '1', GSTACK_SKIP_GBRAIN_REGEN: '1',
BUN_RUNTIME_TRANSPILER_CACHE_PATH: join(tmpdir(), 'gstack-preflight-bun-cache'),
};
write(join(home, '.gstack/config.yaml'), 'telemetry: off\nartifacts_sync: off\n');
return { dir, source, home, project, other, global, previous, env };
}
export function install(f: ReturnType<typeof fixture>, args = '--host codex') {
const result = spawnSync('bash', [join(f.source, 'setup'), ...args.split(' '), '--no-plan-tune-hooks', '--no-timeline-stop-hook', '--no-team'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(0);
return result;
}
export function cleanupOwnedFixtures() { for (const dir of owned.splice(0)) rmSync(dir, { recursive: true, force: true }); }
afterEach(cleanupOwnedFixtures);
+264
View File
@@ -0,0 +1,264 @@
import { describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { cpSync, existsSync, lstatSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { owned, fixtureWriteFileSync, fixtureCopyFileSync, fixtureMkdirSync, fixtureUtimesSync, tree, fixture, install } from './helpers/setup-codex-scope-fixture';
describe.skipIf(process.platform === 'win32')('setup Codex destination follows recognized source scope', () => {
for (const localLegacy of [false, true]) for (const marker of ['current', '1.85.0.0']) test(`excluded global legacy render survives local migration=${localLegacy}, marker=${marker} and generation`, () => {
const f = fixture('.claude');
const oldRender = join(f.source, '.agents/skills/gstack-claude');
fixtureMkdirSync(oldRender, { recursive: true });
const oldBytes = '---\nname: gstack-claude\n---\n<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nExisting legacy global workflow.\n';
fixtureWriteFileSync(join(oldRender, 'SKILL.md'), oldBytes);
rmSync(join(f.global, 'gstack-claude'), { recursive: true });
symlinkSync(oldRender, join(f.global, 'gstack-claude'), 'dir');
for (const rel of ['bin', 'lib']) {
const target = join(f.global, 'gstack', rel);
expect(lstatSync(target).isSymbolicLink()).toBe(true);
rmSync(target);
fixtureMkdirSync(target);
fixtureWriteFileSync(join(target, 'prior'), 'Existing copied global runtime.\n');
}
const local = join(f.project, '.agents/skills');
if (localLegacy) {
fixtureMkdirSync(local, { recursive: true });
symlinkSync(oldRender, join(local, 'gstack-claude'), 'dir');
}
fixtureWriteFileSync(join(f.home, '.gstack/.last-setup-version'), marker === 'current' ? readFileSync(join(f.source, 'VERSION')) : marker);
const before = tree(f.global);
install(f);
expect(tree(f.global)).toEqual(before);
expect(readFileSync(join(f.global, 'gstack-claude/SKILL.md'), 'utf8')).toBe(oldBytes);
expect(realpathSync(join(local, 'gstack-claude-code/SKILL.md'))).toBe(join(f.source, '.agents/skills/gstack-claude-code/SKILL.md'));
expect(lstatSync(join(local, 'gstack-claude'), { throwIfNoEntry: false })).toBeUndefined();
}, 90_000);
for (const nested of [false, true]) for (const global of [false, true]) for (const windows of [false, true]) {
test(`named ${nested ? 'ancestor' : 'source'} overlap: logical local=${!global}, Windows=${windows}`, () => {
const f = fixture('.claude');
const physical = join(f.project, '.agents/skills/gstack-review', ...(nested ? ['checkout'] : []));
fixtureMkdirSync(physical, { recursive: true });
cpSync(f.source, physical, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
rmSync(f.source, { recursive: true });
symlinkSync(physical, f.source, 'dir');
if (nested) fixtureWriteFileSync(join(dirname(physical), 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior render.\n');
fixtureWriteFileSync(join(physical, 'uncommitted-proof'), 'Do not erase this checkout.\n');
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureCopyFileSync(join(physical, rel.replace('.exe', '')), join(physical, rel));
fixtureUtimesSync(join(physical, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
const env = global ? { ...f.env, CODEX_HOME: join(f.project, '.agents') } : f.env;
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', ...(global ? ['--global'] : []), '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env, encoding: 'utf8', timeout: 60_000,
});
if (windows) {
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('Codex skill copy replacement overlaps source');
expect(tree(f.dir)).toEqual(before);
} else {
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(readFileSync(join(physical, 'uncommitted-proof'), 'utf8')).toBe('Do not erase this checkout.\n');
}
}, 90_000);
}
for (const target of ['source', 'project', 'root-sidecar']) for (const host of target === 'root-sidecar' ? ['codex'] : ['codex', 'claude']) test(`generated namespace alias to ${target} refuses before writes, host=${host}`, () => {
const f = fixture('ordinary');
const render = join(f.source, '.agents/skills');
fixtureMkdirSync(render, { recursive: true });
const alias = join(render, target === 'root-sidecar' ? 'gstack' : 'gstack-review');
const destination = target === 'project' ? join(f.project, 'ordinary-review') : f.source;
if (target === 'project') {
fixtureMkdirSync(destination);
fixtureWriteFileSync(join(destination, 'SKILL.md'), 'Project-owned content.\n');
}
symlinkSync(destination, alias, 'dir');
if (target === 'root-sidecar') fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', host, '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(target === 'root-sidecar' ? 'Codex sidecar' : 'Codex generated skill write');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const target of ['absolute-generation-alias', 'occupied-relocation']) for (const windows of [false, true]) {
test(`direct global checkout preflights ${target} before moving, Windows=${windows}`, () => {
const f = fixture('ordinary');
const direct = join(f.global, 'gstack');
rmSync(direct, { recursive: true });
cpSync(f.source, direct, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
fixtureWriteFileSync(join(direct, 'uncommitted-proof'), 'Keep source checkout.\n');
if (target === 'absolute-generation-alias') {
const generated = join(direct, 'generated-codex');
fixtureMkdirSync(generated);
symlinkSync(generated, join(direct, '.agents'), 'dir');
} else {
fixtureMkdirSync(join(f.home, '.gstack/repos'), { recursive: true });
symlinkSync(direct, join(f.home, '.gstack/repos/gstack'), 'dir');
}
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(direct, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(target === 'absolute-generation-alias' ? 'post-relocation generation namespace' : 'checkout relocation');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
}
test('Claude-only setup cannot prune a bannered stale skill inside its source checkout', () => {
const f = fixture('ordinary');
const skill = join(f.source, 'skills/gstack-obsolete/SKILL.md');
fixtureMkdirSync(dirname(skill), { recursive: true });
fixtureWriteFileSync(skill, '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPreserve source content.\n');
const env = { ...f.env, CODEX_HOME: f.source };
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'claude', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('Codex stale host cleanup');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
test('a dangling owned legacy Codex link reaches the rename migration', () => {
const f = fixture('ordinary');
const old = join(f.global, 'gstack-claude');
rmSync(old, { recursive: true });
symlinkSync(join(f.source, '.agents/skills/gstack-claude'), old, 'dir');
for (const rel of ['bin', 'lib']) {
const target = join(f.global, 'gstack', rel);
rmSync(target);
symlinkSync(join(f.source, rel), target, 'dir');
}
install(f);
expect(lstatSync(old, { throwIfNoEntry: false })).toBeUndefined();
expect(readFileSync(join(f.global, 'gstack-claude-code/SKILL.md'), 'utf8')).toContain('name: claude-code');
expect(readFileSync(join(f.global, 'custom/SKILL.md'), 'utf8')).toBe('User-owned skill.\n');
}, 90_000);
for (const explicit of [false, true]) for (const windows of [false, true]) {
test(`global handwritten runtime is refused before writes, explicit=${explicit}, Windows=${windows}`, () => {
const f = fixture(explicit ? '.claude' : 'ordinary');
const runtime = join(f.global, 'gstack');
rmSync(runtime, { recursive: true });
fixtureMkdirSync(runtime);
fixtureWriteFileSync(join(runtime, 'SKILL.md'), 'A handwritten root skill.\n');
fixtureWriteFileSync(join(runtime, 'user-notes'), 'Keep these notes.\n');
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', ...(explicit ? ['--global'] : []), '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(`global Codex runtime ${runtime} is a real user-owned skill`);
expect(result.stderr).toContain('choose another CODEX_HOME');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
}
for (const prior of ['managed', 'partial']) test(`global ${prior} runtime remains refreshable`, () => {
const f = fixture('ordinary');
const runtime = join(f.global, 'gstack');
rmSync(runtime, { recursive: true });
fixtureMkdirSync(runtime);
if (prior === 'managed') fixtureWriteFileSync(join(runtime, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior runtime.\n');
fixtureWriteFileSync(join(runtime, 'prior-asset'), 'A previous runtime asset.\n');
install(f);
install(f);
expect(existsSync(join(runtime, 'prior-asset'))).toBe(false);
expect(readFileSync(join(runtime, 'SKILL.md'), 'utf8')).toContain('<!-- AUTO-GENERATED from');
expect(realpathSync(join(runtime, 'bin'))).toBe(join(f.source, 'bin'));
}, 90_000);
test('Claude-only setup leaves a global handwritten runtime untouched', () => {
const f = fixture('ordinary');
const runtime = join(f.global, 'gstack');
rmSync(runtime, { recursive: true });
fixtureMkdirSync(runtime);
fixtureWriteFileSync(join(runtime, 'SKILL.md'), 'A handwritten root skill.\n');
fixtureWriteFileSync(join(runtime, 'user-notes'), 'Keep these notes.\n');
const before = tree(runtime);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'claude', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(tree(runtime)).toEqual(before);
}, 90_000);
test('Unix rename does not overwrite a source checkout registered as another skill', () => {
const f = fixture('ordinary');
const source = join(f.global, 'gstack-review');
rmSync(source);
cpSync(f.source, source, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true });
fixtureWriteFileSync(join(source, 'uncommitted-proof'), 'Keep this source.\n');
for (const rel of ['bin', 'lib']) {
const asset = join(f.global, 'gstack', rel);
rmSync(asset);
fixtureMkdirSync(asset);
}
const before = tree(f.dir);
const result = spawnSync('bash', [join(source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('Codex rename workflow write');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const alias of ['SKILL leaf', 'metadata parent']) test(`direct relocation refuses a generated absolute ${alias} before moving source`, () => {
const f = fixture('ordinary');
const direct = join(f.global, 'gstack');
rmSync(direct, { recursive: true });
cpSync(f.source, direct, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true });
fixtureWriteFileSync(join(direct, 'uncommitted-proof'), 'Keep this source.\n');
const generated = join(direct, '.agents/skills/gstack-review');
fixtureMkdirSync(generated, { recursive: true });
if (alias === 'SKILL leaf') {
fixtureWriteFileSync(join(generated, 'saved.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl -->\nPrior render.\n');
symlinkSync(join(generated, 'saved.md'), join(generated, 'SKILL.md'));
} else {
const prior = join(direct, '.agents/skills/gstack-office-hours/agents');
fixtureMkdirSync(prior, { recursive: true });
symlinkSync(prior, join(generated, 'agents'), 'dir');
}
const before = tree(f.dir);
const result = spawnSync('bash', [join(direct, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('post-relocation generated alias');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
test('managed rename detaches a metadata parent link without touching its source', () => {
const f = fixture('ordinary');
const installed = join(f.global, 'gstack-review');
rmSync(installed);
fixtureMkdirSync(installed);
fixtureWriteFileSync(join(installed, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior installed review.\n');
const sourceAgents = join(f.source, '.agents/skills/gstack-review/agents');
fixtureMkdirSync(sourceAgents, { recursive: true });
fixtureWriteFileSync(join(dirname(sourceAgents), 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior canonical review.\n');
fixtureWriteFileSync(join(sourceAgents, 'user-notes'), 'Preserve source metadata.\n');
symlinkSync(sourceAgents, join(installed, 'agents'), 'dir');
for (const rel of ['bin', 'lib']) {
const asset = join(f.global, 'gstack', rel);
rmSync(asset);
symlinkSync(join(f.source, rel), asset, 'dir');
}
const result = install(f);
expect(result.stderr).toContain('migrated 1 installed skill');
expect(lstatSync(join(installed, 'agents')).isDirectory()).toBe(true);
expect(readFileSync(join(sourceAgents, 'user-notes'), 'utf8')).toBe('Preserve source metadata.\n');
expect(existsSync(join(installed, 'agents/openai.yaml'))).toBe(true);
}, 90_000);
});
+273
View File
@@ -0,0 +1,273 @@
import { describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { cpSync, existsSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fixtureWriteFileSync, fixtureCopyFileSync, fixtureMkdirSync, fixtureUtimesSync, tree, fixture, install } from './helpers/setup-codex-scope-fixture';
describe.skipIf(process.platform === 'win32')('setup Codex destination follows recognized source scope', () => {
for (const global of [false, true]) for (const windows of [false, true]) test(`a real runtime containing the source is refused before writes, global=${global}, Windows=${windows}`, () => {
const f = fixture('.claude');
const runtime = join(f.project, '.agents/skills/gstack');
const nested = join(runtime, 'checkout');
cpSync(f.source, nested, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
fixtureWriteFileSync(join(nested, 'uncommitted-work'), 'Preserve the nested checkout.\n');
rmSync(f.source, { recursive: true });
symlinkSync(nested, f.source, 'dir');
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const env = global ? { ...f.env, CODEX_HOME: join(f.project, '.agents') } : f.env;
const before = tree(f.dir);
for (const host of ['codex', 'auto']) for (let run = 0; run < 2; run++) {
const args = [join(f.source, 'setup'), '--host', host, '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'];
if (global) args.push('--global');
const result = spawnSync('bash', args, { cwd: f.other, env, encoding: 'utf8', timeout: 60_000 });
expect(tree(f.dir)).toEqual(before);
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('runtime directory contains the source checkout');
expect(result.stderr).toContain(runtime);
}
}, 90_000);
test('a distinct sibling source checkout is refused before any mutation', () => {
const f = fixture('.claude');
const sibling = join(f.project, '.agents/skills/gstack');
cpSync(f.source, sibling, { recursive: true, verbatimSymlinks: true });
fixtureWriteFileSync(join(sibling, 'uncommitted-work'), 'Keep sibling checkout edits.\n');
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status).toBe(1);
expect(result.stderr).toContain(`existing source checkout at ${sibling}`);
}, 90_000);
test('a project destination aliased to global skills is refused before any mutation', () => {
const f = fixture('.claude');
fixtureMkdirSync(join(f.project, '.agents'));
symlinkSync(f.global, join(f.project, '.agents/skills'), 'dir');
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status).toBe(1);
expect(result.stderr).toContain('project-local Codex destination resolves outside the project');
}, 90_000);
for (const target of ['root', 'rendered', 'missing-tail', 'project-root']) for (const marker of ['current', '1.85.0.0']) test(`another payload cannot own the local namespace: ${target}, marker=${marker}`, () => {
const f = fixture('.claude');
const sibling = target === 'project-root' ? f.project : join(f.project, 'other-checkout');
if (target === 'project-root') {
for (const rel of ['setup', 'VERSION', 'bin/gstack-relink']) {
fixtureMkdirSync(dirname(join(sibling, rel)), { recursive: true });
fixtureCopyFileSync(join(f.source, rel), join(sibling, rel));
}
} else cpSync(f.source, sibling, { recursive: true, verbatimSymlinks: true });
const rendered = join(sibling, '.agents/skills');
const root = join(rendered, 'gstack');
const review = join(rendered, 'gstack-review');
for (const dir of [root, review]) {
fixtureMkdirSync(dir, { recursive: true });
fixtureWriteFileSync(join(dir, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nKeep the sibling workflow.\n');
}
symlinkSync(join(sibling, 'bin'), join(root, 'bin'), 'dir');
for (const [name, source] of [['gstack', root], ['gstack-review', review]]) {
rmSync(join(f.global, name!), { recursive: true });
symlinkSync(source!, join(f.global, name!), 'dir');
}
if (target === 'missing-tail') {
expect(existsSync(join(sibling, 'skills'))).toBe(false);
symlinkSync(sibling, join(f.project, '.agents'), 'dir');
} else if (target !== 'project-root') {
fixtureMkdirSync(join(f.project, '.agents'));
symlinkSync(target === 'root' ? sibling : rendered, join(f.project, '.agents/skills'), 'dir');
}
fixtureWriteFileSync(join(f.home, '.gstack/.last-setup-version'), marker === 'current' ? readFileSync(join(f.source, 'VERSION')) : marker);
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('overlaps the source tree');
expect(result.stderr).toContain(sibling);
expect(tree(f.dir)).toEqual(before);
}
}, 90_000);
for (const preexisting of [false, true]) test(`generated runtime leaf alias survives setup, preexisting=${preexisting}`, () => {
const f = fixture('.claude');
const renderedSkills = join(f.source, '.agents/skills');
const renderedRoot = join(renderedSkills, 'gstack');
const local = join(f.project, '.agents/skills');
if (preexisting) {
fixtureMkdirSync(renderedRoot, { recursive: true });
fixtureWriteFileSync(join(renderedRoot, 'SKILL.md'), readFileSync(join(f.source, 'SKILL.md')));
}
fixtureMkdirSync(local, { recursive: true });
symlinkSync(renderedRoot, join(local, 'gstack'), 'dir');
const globalRoot = join(f.global, 'gstack/SKILL.md');
rmSync(globalRoot);
symlinkSync(join(renderedRoot, 'SKILL.md'), globalRoot);
const before = tree(f.global);
for (let run = 0; run < 2; run++) {
install(f);
expect(tree(f.global)).toEqual(before);
expect(existsSync(join(renderedRoot, 'SKILL.md'))).toBe(true);
expect(realpathSync(join(local, 'gstack/SKILL.md'))).toBe(join(renderedRoot, 'SKILL.md'));
expect(readFileSync(globalRoot)).toEqual(readFileSync(join(renderedRoot, 'SKILL.md')));
expect(realpathSync(join(local, 'gstack/bin'))).toBe(join(f.source, 'bin'));
}
}, 90_000);
for (const target of ['source', 'rendered', 'missing-rendered']) for (const windows of [false, true]) test(`source-backed namespace is refused without mutation: ${target}, Windows=${windows}`, () => {
const f = fixture('.claude');
const renderedSkills = join(f.source, '.agents/skills');
const oldRender = join(renderedSkills, 'gstack-claude');
if (target !== 'missing-rendered') {
fixtureMkdirSync(oldRender, { recursive: true });
fixtureWriteFileSync(join(oldRender, 'SKILL.md'), '---\nname: gstack-claude\n---\n<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nExcluded global workflow.\n');
rmSync(join(f.global, 'gstack-claude'), { recursive: true });
symlinkSync(oldRender, join(f.global, 'gstack-claude'), 'dir');
}
fixtureMkdirSync(join(f.project, '.agents'));
symlinkSync(target === 'source' ? f.source : renderedSkills, join(f.project, '.agents/skills'), 'dir');
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(target === 'missing-rendered' ? 'unresolvable directory' : 'overlaps the source tree');
expect(result.stderr).toContain('Use a separate project-local skills directory');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const level of ['agents', 'skills']) for (const preexisting of [false, true]) for (const windows of [false, true]) test(`outward generation namespace is refused before writes: ${level}, preexisting=${preexisting}, Windows=${windows}`, () => {
const f = fixture('.claude');
const agents = join(f.project, '.agents');
const local = join(agents, 'skills');
fixtureMkdirSync(agents);
if (preexisting || level === 'skills') fixtureMkdirSync(local);
if (level === 'agents') symlinkSync(agents, join(f.source, '.agents'), 'dir');
else {
fixtureMkdirSync(join(f.source, '.agents'));
symlinkSync(local, join(f.source, '.agents/skills'), 'dir');
}
if (preexisting) for (const name of ['gstack', 'gstack-review']) {
fixtureMkdirSync(join(local, name));
fixtureWriteFileSync(join(local, name, 'SKILL.md'), `Handwritten ${name} workflow.\n`);
fixtureWriteFileSync(join(local, name, 'user-notes'), 'Preserve unrelated user notes.\n');
}
if (!preexisting && level === 'skills') rmSync(local, { recursive: true });
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
fixtureWriteFileSync(join(f.home, '.gstack/.last-setup-version'), '1.85.0.0');
const before = tree(f.dir);
for (const host of ['codex', 'claude', 'auto']) for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', host, '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('generation namespace');
expect(result.stderr).toContain('Use a separate project-local skills directory');
}
}, 90_000);
for (const target of ['dangling-agents', 'dangling-skills', 'cyclic-agents', 'cyclic-skills', 'file-agents', 'file-skills', 'source-root']) test(`unresolvable generation namespace is refused without mutation: ${target}`, () => {
const f = fixture('.claude');
const agents = join(f.source, '.agents');
const component = target.endsWith('skills') || target === 'source-root' ? join(agents, 'skills') : agents;
if (component !== agents) fixtureMkdirSync(agents);
if (target.startsWith('file')) fixtureWriteFileSync(component, 'Preserve namespace file.\n');
else symlinkSync(target === 'source-root' ? f.source : target.startsWith('cyclic') ? component : join(f.source, 'missing-generation'), component, 'dir');
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('generation namespace');
}
}, 90_000);
for (const level of ['agents', 'skills']) for (const preexisting of [false, true]) for (const windows of [false, true]) test(`internal generation namespace alias remains supported: ${level}, preexisting=${preexisting}, Windows=${windows}`, () => {
const f = fixture('.claude');
const internal = join(f.source, 'generated-codex');
fixtureMkdirSync(internal);
if (level === 'agents') symlinkSync(internal, join(f.source, '.agents'), 'dir');
else {
fixtureMkdirSync(join(f.source, '.agents'));
symlinkSync(internal, join(f.source, '.agents/skills'), 'dir');
}
const generated = join(f.source, '.agents/skills');
if (preexisting) {
fixtureMkdirSync(join(generated, 'gstack-review'), { recursive: true });
fixtureWriteFileSync(join(generated, 'gstack-review/SKILL.md'), 'Prior internal render.\n');
}
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureWriteFileSync(join(f.source, rel), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
fixtureUtimesSync(join(f.source, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
fixtureWriteFileSync(join(f.source, 'uncommitted-work'), 'Preserve source edits.\n');
const excluded = tree(f.global), sibling = tree(f.other);
const sourceBefore = Object.fromEntries(['SKILL.md', 'bin', 'lib', 'uncommitted-work'].map(rel => [rel, tree(join(f.source, rel))]));
for (let run = 0; run < 2; run++) {
install(f);
expect(tree(f.global)).toEqual(excluded);
expect(tree(f.other)).toEqual(sibling);
for (const [rel, before] of Object.entries(sourceBefore)) expect(tree(join(f.source, rel))).toEqual(before);
expect(readFileSync(join(f.project, '.agents/skills/gstack-review/SKILL.md'))).toEqual(readFileSync(join(generated, 'gstack-review/SKILL.md')));
expect(existsSync(join(f.source, 'bin/bin'))).toBe(false);
}
}, 90_000);
for (const windows of [false, true]) test(`individual generated skill leaf alias remains supported, Windows=${windows}`, () => {
const f = fixture('.claude');
const rendered = join(f.source, '.agents/skills/gstack-review');
const local = join(f.project, '.agents/skills');
fixtureMkdirSync(rendered, { recursive: true });
fixtureMkdirSync(local, { recursive: true });
fixtureWriteFileSync(join(rendered, 'user-notes'), 'Keep notes beside the source render.\n');
symlinkSync(rendered, join(local, 'gstack-review'), 'dir');
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureWriteFileSync(join(f.source, rel), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
fixtureUtimesSync(join(f.source, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
const excluded = tree(f.global);
for (let run = 0; run < 2; run++) {
install(f);
expect(tree(f.global)).toEqual(excluded);
expect(readFileSync(join(rendered, 'user-notes'), 'utf8')).toBe('Keep notes beside the source render.\n');
expect(readFileSync(join(local, 'gstack-review/SKILL.md'))).toEqual(readFileSync(join(rendered, 'SKILL.md')));
}
}, 90_000);
for (const target of ['dangling-skills', 'dangling-agents', 'cyclic-skills', 'file-skills']) test(`unresolvable namespace is refused without mutation: ${target}`, () => {
const f = fixture('.claude');
const agents = join(f.project, '.agents');
if (target === 'dangling-agents') symlinkSync(join(f.dir, 'missing-agents'), agents, 'dir');
else {
fixtureMkdirSync(agents);
const local = join(agents, 'skills');
if (target === 'file-skills') fixtureWriteFileSync(local, 'Preserve this file.\n');
else symlinkSync(target === 'cyclic-skills' ? 'skills' : join(f.dir, 'missing-skills'), local, 'dir');
}
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('unresolvable directory');
expect(result.stderr).toContain('Use a separate project-local skills directory');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
});
@@ -0,0 +1,87 @@
import { describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { existsSync, lstatSync, rmSync, symlinkSync } from 'node:fs';
import { join } from 'node:path';
import { fixtureWriteFileSync, fixtureCopyFileSync, fixtureMkdirSync, fixtureUtimesSync, tree, fixture, install } from './helpers/setup-codex-scope-fixture';
describe.skipIf(process.platform === 'win32')('F13 independent review boundaries', () => {
for (const target of ['legacy', 'replacement', 'legacy-skill', 'runtime', 'runtime-bin', 'runtime-lib']) {
test(`Claude-only setup preserves cyclic foreign ownership link: ${target}`, () => {
const f = fixture('ordinary');
rmSync(join(f.global, 'gstack-retired'));
for (const rel of ['bin', 'lib']) {
const asset = join(f.global, 'gstack', rel);
rmSync(asset);
symlinkSync(join(f.source, rel), asset, 'dir');
}
const old = join(f.global, 'gstack-claude');
rmSync(old, { recursive: true });
const oldRender = join(f.source, '.agents/skills/gstack-claude');
fixtureMkdirSync(oldRender, { recursive: true });
fixtureWriteFileSync(join(oldRender, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior legacy render.\n');
symlinkSync(oldRender, old, 'dir');
const link = target === 'legacy' ? old
: target === 'replacement' ? join(f.global, 'gstack-claude-code')
: target === 'legacy-skill' ? join(old, 'SKILL.md')
: target === 'runtime' ? join(f.global, 'gstack')
: join(f.global, 'gstack', target === 'runtime-bin' ? 'bin' : 'lib');
if (target === 'legacy-skill') {
rmSync(old);
fixtureMkdirSync(old);
}
if (existsSync(link) || lstatSync(link, { throwIfNoEntry: false })) rmSync(link, { recursive: true });
symlinkSync(link, link);
const before = tree(f.global);
for (let run = 0; run < 2; run++) {
install(f, '--host claude');
expect(tree(f.global)).toEqual(before);
}
}, 90_000);
}
for (const alias of [false, true]) for (const windows of [false, true]) {
test(`global generation namespace is refused before mutation: alias=${alias}, Windows=${windows}`, () => {
const f = fixture('ordinary');
const agents = join(f.source, '.agents');
fixtureMkdirSync(agents, { recursive: true });
const codexHome = alias ? join(f.home, 'generation-alias') : agents;
if (alias) symlinkSync(agents, codexHome, 'dir');
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureCopyFileSync(join(f.source, rel.replace('.exe', '')), join(f.source, rel));
fixtureUtimesSync(join(f.source, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: { ...f.env, CODEX_HOME: codexHome }, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('host namespace');
expect(tree(f.dir)).toEqual(before);
}
}, 90_000);
}
for (const leaf of ['SKILL.md', 'agents']) {
test(`selected generated cyclic write remains fail-closed: ${leaf}`, () => {
const f = fixture('ordinary');
const generated = join(f.source, '.agents/skills/gstack-review');
fixtureMkdirSync(generated, { recursive: true });
const link = join(generated, leaf);
symlinkSync(link, link);
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('ELOOP');
expect(tree(f.dir)).toEqual(before);
}
}, 90_000);
}
});
+4 -732
View File
@@ -1,136 +1,12 @@
import { afterEach, describe, expect, test } from 'bun:test';
import { describe, expect, test } from 'bun:test';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, utimesSync, writeFileSync } from 'node:fs';
import { cpSync, existsSync, lstatSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { dirname, join } from 'node:path';
import { generateAutoplanSnapshotTool } from '../scripts/resolvers/composition';
import { HOST_PATHS, type TemplateContext } from '../scripts/resolvers/types';
import { runBashScript } from './helpers/bash-script';
const ROOT = resolve(import.meta.dir, '..');
const files = spawnSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf8', timeout: 10_000 });
if (files.status !== 0) throw new Error(files.stderr);
const owned: string[] = [];
afterEach(() => { for (const dir of owned.splice(0)) rmSync(dir, { recursive: true, force: true }); });
const quote = (s: string) => `'${s.replace(/'/g, `'\\''`)}'`;
function assertFixtureWrite(file: string) {
const root = owned.find(dir => file === dir || file.startsWith(dir + '/'));
if (!root) throw new Error(`Fixture write outside owned roots: ${file}`);
let existing = file;
while (!existsSync(existing)) {
if (lstatSync(existing, { throwIfNoEntry: false })?.isSymbolicLink()) throw new Error(`Unresolvable fixture link: ${existing}`);
existing = dirname(existing);
}
const target = realpathSync(existing);
const physicalRoot = realpathSync(root);
if (target !== physicalRoot && !target.startsWith(physicalRoot + '/')) throw new Error(`Fixture write escapes physical root: ${file} -> ${target}`);
}
function fixtureWriteFileSync(...args: Parameters<typeof writeFileSync>) {
assertFixtureWrite(String(args[0]));
return writeFileSync(...args);
}
function fixtureCopyFileSync(...args: Parameters<typeof copyFileSync>) {
assertFixtureWrite(String(args[1]));
return copyFileSync(...args);
}
function fixtureMkdirSync(...args: Parameters<typeof mkdirSync>) {
assertFixtureWrite(String(args[0]));
return mkdirSync(...args);
}
function fixtureUtimesSync(...args: Parameters<typeof utimesSync>) {
assertFixtureWrite(String(args[0]));
return utimesSync(...args);
}
function tree(dir: string): unknown {
const stat = lstatSync(dir);
if (stat.isSymbolicLink()) return { link: readlinkSync(dir) };
if (stat.isDirectory()) return Object.fromEntries(readdirSync(dir).sort().map(name => [name, tree(join(dir, name))]));
return createHash('sha256').update(readFileSync(dir)).digest('hex');
}
function fixture(layout: string) {
const dir = mkdtempSync(join(tmpdir(), 'gstack-codex-scope-'));
owned.push(dir);
const home = join(dir, 'home');
const project = join(dir, 'project-a');
const other = join(dir, 'project-b');
const source = layout === 'machine' ? join(home, '.claude/skills/gstack')
: layout === 'ordinary' ? join(project, 'custom-checkout') : join(project, layout, 'skills/gstack');
const commands = join(dir, 'commands');
for (const d of [home, other, commands, source]) fixtureMkdirSync(d, { recursive: true });
for (const rel of [...files.stdout.split('\0').filter(Boolean), 'scripts/external-skill-names.ts', 'scripts/preflight-codex-overlap.ts']) {
if (/^(?:test|docs|browse\/test|\.github)\//.test(rel)) continue;
const dest = join(source, rel);
fixtureMkdirSync(dirname(dest), { recursive: true });
if (lstatSync(join(ROOT, rel)).isSymbolicLink()) {
const target = readlinkSync(join(ROOT, rel));
expect(resolve(dirname(dest), target).startsWith(source + '/')).toBe(true);
symlinkSync(target, dest);
} else fixtureCopyFileSync(join(ROOT, rel), dest);
}
const write = (file: string, content: string) => {
fixtureMkdirSync(dirname(file), { recursive: true });
fixtureWriteFileSync(file, content, { mode: 0o755 });
};
for (const rel of ['browse/dist/browse', 'design/dist/design', 'make-pdf/dist/pdf', 'browse/dist/.build-complete']) {
const file = join(source, rel);
write(file, '#!/bin/sh\nexit 0\n');
fixtureUtimesSync(file, new Date('2040-01-01'), new Date('2040-01-01'));
}
write(join(commands, 'bun'), `#!/usr/bin/env bash
case "$*" in
'install --frozen-lockfile') exit 0 ;;
'build --help') echo 'Fixture Bun has no CSO compile flags'; exit 0 ;;
'run build') echo 'Unexpected build in registration fixture' >&2; exit 90 ;;
*) exec ${quote(process.execPath)} "$@" ;;
esac
`);
const realRm = Bun.which('rm');
if (!realRm) throw new Error('rm is required');
write(join(commands, 'rm'), `#!/usr/bin/env bash
if [ "$#" -eq 2 ] && [ "$1" = -f ] && [ "$2" = /tmp/gstack-latest-version ]; then exit 0; fi
exec ${quote(realRm)} "$@"
`);
for (const name of ['codex', 'claude']) write(join(commands, name), '#!/bin/sh\nexit 0\n');
const global = join(home, '.codex/skills');
const previous = join(dir, 'previous/gstack');
write(join(previous, 'bin/gstack-autoplan-snapshot.ts'), readFileSync(join(ROOT, 'bin/gstack-autoplan-snapshot.ts'), 'utf8'));
write(join(previous, 'lib/claude-bin.ts'), 'export {};\n');
for (const name of ['gstack-review', 'gstack-claude', 'gstack-retired']) {
write(join(previous, name, 'SKILL.md'), `---\nname: ${name}\n---\n<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior global skill.\n`);
fixtureMkdirSync(global, { recursive: true });
if (name === 'gstack-claude') {
write(join(global, name, 'SKILL.md'), readFileSync(join(previous, name, 'SKILL.md'), 'utf8'));
} else symlinkSync(join(previous, name), join(global, name), 'dir');
}
fixtureMkdirSync(join(global, 'gstack'), { recursive: true });
symlinkSync(join(previous, 'bin'), join(global, 'gstack/bin'), 'dir');
symlinkSync(join(previous, 'lib'), join(global, 'gstack/lib'), 'dir');
write(join(global, 'gstack/SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nGlobal router.\n');
write(join(global, 'custom/SKILL.md'), 'User-owned skill.\n');
const env = {
PATH: `${commands}:${process.env.PATH}`, HOME: home, USERPROFILE: home,
CODEX_HOME: join(home, '.codex'), CLAUDE_CONFIG_DIR: join(home, '.claude'),
GSTACK_HOME: join(home, '.gstack'), GSTACK_STATE_ROOT: join(home, '.gstack'),
TMPDIR: dir, TMP: dir, TEMP: dir,
GSTACK_SKIP_PLAYWRIGHT: '1', GSTACK_SKIP_FONTS: '1', GSTACK_SKIP_COREUTILS: '1', GSTACK_SKIP_ASIDE: '1', GSTACK_SKIP_GBRAIN_REGEN: '1',
BUN_RUNTIME_TRANSPILER_CACHE_PATH: join(tmpdir(), 'gstack-preflight-bun-cache'),
};
write(join(home, '.gstack/config.yaml'), 'telemetry: off\nartifacts_sync: off\n');
return { dir, source, home, project, other, global, previous, env };
}
function install(f: ReturnType<typeof fixture>, args = '--host codex') {
const result = spawnSync('bash', [join(f.source, 'setup'), ...args.split(' '), '--no-plan-tune-hooks', '--no-timeline-stop-hook', '--no-team'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(0);
return result;
}
import { ROOT, owned, fixtureWriteFileSync, fixtureCopyFileSync, fixtureMkdirSync, fixtureUtimesSync, tree, fixture, install } from './helpers/setup-codex-scope-fixture';
test.skipIf(process.platform === 'win32')('fixture writes reject physical escapes and allow aliased temporary roots', () => {
const dir = mkdtempSync(join(tmpdir(), 'gstack-fixture-guard-'));
@@ -335,608 +211,4 @@ describe.skipIf(process.platform === 'win32')('setup Codex destination follows r
expect(realpathSync(join(f.project, '.agents/skills/gstack/bin'))).toBe(join(f.source, 'bin'));
}, 90_000);
for (const global of [false, true]) for (const windows of [false, true]) test(`a real runtime containing the source is refused before writes, global=${global}, Windows=${windows}`, () => {
const f = fixture('.claude');
const runtime = join(f.project, '.agents/skills/gstack');
const nested = join(runtime, 'checkout');
cpSync(f.source, nested, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
fixtureWriteFileSync(join(nested, 'uncommitted-work'), 'Preserve the nested checkout.\n');
rmSync(f.source, { recursive: true });
symlinkSync(nested, f.source, 'dir');
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const env = global ? { ...f.env, CODEX_HOME: join(f.project, '.agents') } : f.env;
const before = tree(f.dir);
for (const host of ['codex', 'auto']) for (let run = 0; run < 2; run++) {
const args = [join(f.source, 'setup'), '--host', host, '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'];
if (global) args.push('--global');
const result = spawnSync('bash', args, { cwd: f.other, env, encoding: 'utf8', timeout: 60_000 });
expect(tree(f.dir)).toEqual(before);
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('runtime directory contains the source checkout');
expect(result.stderr).toContain(runtime);
}
}, 90_000);
test('a distinct sibling source checkout is refused before any mutation', () => {
const f = fixture('.claude');
const sibling = join(f.project, '.agents/skills/gstack');
cpSync(f.source, sibling, { recursive: true, verbatimSymlinks: true });
fixtureWriteFileSync(join(sibling, 'uncommitted-work'), 'Keep sibling checkout edits.\n');
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status).toBe(1);
expect(result.stderr).toContain(`existing source checkout at ${sibling}`);
}, 90_000);
test('a project destination aliased to global skills is refused before any mutation', () => {
const f = fixture('.claude');
fixtureMkdirSync(join(f.project, '.agents'));
symlinkSync(f.global, join(f.project, '.agents/skills'), 'dir');
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status).toBe(1);
expect(result.stderr).toContain('project-local Codex destination resolves outside the project');
}, 90_000);
for (const target of ['root', 'rendered', 'missing-tail', 'project-root']) for (const marker of ['current', '1.85.0.0']) test(`another payload cannot own the local namespace: ${target}, marker=${marker}`, () => {
const f = fixture('.claude');
const sibling = target === 'project-root' ? f.project : join(f.project, 'other-checkout');
if (target === 'project-root') {
for (const rel of ['setup', 'VERSION', 'bin/gstack-relink']) {
fixtureMkdirSync(dirname(join(sibling, rel)), { recursive: true });
fixtureCopyFileSync(join(f.source, rel), join(sibling, rel));
}
} else cpSync(f.source, sibling, { recursive: true, verbatimSymlinks: true });
const rendered = join(sibling, '.agents/skills');
const root = join(rendered, 'gstack');
const review = join(rendered, 'gstack-review');
for (const dir of [root, review]) {
fixtureMkdirSync(dir, { recursive: true });
fixtureWriteFileSync(join(dir, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nKeep the sibling workflow.\n');
}
symlinkSync(join(sibling, 'bin'), join(root, 'bin'), 'dir');
for (const [name, source] of [['gstack', root], ['gstack-review', review]]) {
rmSync(join(f.global, name!), { recursive: true });
symlinkSync(source!, join(f.global, name!), 'dir');
}
if (target === 'missing-tail') {
expect(existsSync(join(sibling, 'skills'))).toBe(false);
symlinkSync(sibling, join(f.project, '.agents'), 'dir');
} else if (target !== 'project-root') {
fixtureMkdirSync(join(f.project, '.agents'));
symlinkSync(target === 'root' ? sibling : rendered, join(f.project, '.agents/skills'), 'dir');
}
fixtureWriteFileSync(join(f.home, '.gstack/.last-setup-version'), marker === 'current' ? readFileSync(join(f.source, 'VERSION')) : marker);
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('overlaps the source tree');
expect(result.stderr).toContain(sibling);
expect(tree(f.dir)).toEqual(before);
}
}, 90_000);
for (const preexisting of [false, true]) test(`generated runtime leaf alias survives setup, preexisting=${preexisting}`, () => {
const f = fixture('.claude');
const renderedSkills = join(f.source, '.agents/skills');
const renderedRoot = join(renderedSkills, 'gstack');
const local = join(f.project, '.agents/skills');
if (preexisting) {
fixtureMkdirSync(renderedRoot, { recursive: true });
fixtureWriteFileSync(join(renderedRoot, 'SKILL.md'), readFileSync(join(f.source, 'SKILL.md')));
}
fixtureMkdirSync(local, { recursive: true });
symlinkSync(renderedRoot, join(local, 'gstack'), 'dir');
const globalRoot = join(f.global, 'gstack/SKILL.md');
rmSync(globalRoot);
symlinkSync(join(renderedRoot, 'SKILL.md'), globalRoot);
const before = tree(f.global);
for (let run = 0; run < 2; run++) {
install(f);
expect(tree(f.global)).toEqual(before);
expect(existsSync(join(renderedRoot, 'SKILL.md'))).toBe(true);
expect(realpathSync(join(local, 'gstack/SKILL.md'))).toBe(join(renderedRoot, 'SKILL.md'));
expect(readFileSync(globalRoot)).toEqual(readFileSync(join(renderedRoot, 'SKILL.md')));
expect(realpathSync(join(local, 'gstack/bin'))).toBe(join(f.source, 'bin'));
}
}, 90_000);
for (const target of ['source', 'rendered', 'missing-rendered']) for (const windows of [false, true]) test(`source-backed namespace is refused without mutation: ${target}, Windows=${windows}`, () => {
const f = fixture('.claude');
const renderedSkills = join(f.source, '.agents/skills');
const oldRender = join(renderedSkills, 'gstack-claude');
if (target !== 'missing-rendered') {
fixtureMkdirSync(oldRender, { recursive: true });
fixtureWriteFileSync(join(oldRender, 'SKILL.md'), '---\nname: gstack-claude\n---\n<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nExcluded global workflow.\n');
rmSync(join(f.global, 'gstack-claude'), { recursive: true });
symlinkSync(oldRender, join(f.global, 'gstack-claude'), 'dir');
}
fixtureMkdirSync(join(f.project, '.agents'));
symlinkSync(target === 'source' ? f.source : renderedSkills, join(f.project, '.agents/skills'), 'dir');
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(target === 'missing-rendered' ? 'unresolvable directory' : 'overlaps the source tree');
expect(result.stderr).toContain('Use a separate project-local skills directory');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const level of ['agents', 'skills']) for (const preexisting of [false, true]) for (const windows of [false, true]) test(`outward generation namespace is refused before writes: ${level}, preexisting=${preexisting}, Windows=${windows}`, () => {
const f = fixture('.claude');
const agents = join(f.project, '.agents');
const local = join(agents, 'skills');
fixtureMkdirSync(agents);
if (preexisting || level === 'skills') fixtureMkdirSync(local);
if (level === 'agents') symlinkSync(agents, join(f.source, '.agents'), 'dir');
else {
fixtureMkdirSync(join(f.source, '.agents'));
symlinkSync(local, join(f.source, '.agents/skills'), 'dir');
}
if (preexisting) for (const name of ['gstack', 'gstack-review']) {
fixtureMkdirSync(join(local, name));
fixtureWriteFileSync(join(local, name, 'SKILL.md'), `Handwritten ${name} workflow.\n`);
fixtureWriteFileSync(join(local, name, 'user-notes'), 'Preserve unrelated user notes.\n');
}
if (!preexisting && level === 'skills') rmSync(local, { recursive: true });
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
fixtureWriteFileSync(join(f.home, '.gstack/.last-setup-version'), '1.85.0.0');
const before = tree(f.dir);
for (const host of ['codex', 'claude', 'auto']) for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', host, '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('generation namespace');
expect(result.stderr).toContain('Use a separate project-local skills directory');
}
}, 90_000);
for (const target of ['dangling-agents', 'dangling-skills', 'cyclic-agents', 'cyclic-skills', 'file-agents', 'file-skills', 'source-root']) test(`unresolvable generation namespace is refused without mutation: ${target}`, () => {
const f = fixture('.claude');
const agents = join(f.source, '.agents');
const component = target.endsWith('skills') || target === 'source-root' ? join(agents, 'skills') : agents;
if (component !== agents) fixtureMkdirSync(agents);
if (target.startsWith('file')) fixtureWriteFileSync(component, 'Preserve namespace file.\n');
else symlinkSync(target === 'source-root' ? f.source : target.startsWith('cyclic') ? component : join(f.source, 'missing-generation'), component, 'dir');
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(tree(f.dir)).toEqual(before);
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('generation namespace');
}
}, 90_000);
for (const level of ['agents', 'skills']) for (const preexisting of [false, true]) for (const windows of [false, true]) test(`internal generation namespace alias remains supported: ${level}, preexisting=${preexisting}, Windows=${windows}`, () => {
const f = fixture('.claude');
const internal = join(f.source, 'generated-codex');
fixtureMkdirSync(internal);
if (level === 'agents') symlinkSync(internal, join(f.source, '.agents'), 'dir');
else {
fixtureMkdirSync(join(f.source, '.agents'));
symlinkSync(internal, join(f.source, '.agents/skills'), 'dir');
}
const generated = join(f.source, '.agents/skills');
if (preexisting) {
fixtureMkdirSync(join(generated, 'gstack-review'), { recursive: true });
fixtureWriteFileSync(join(generated, 'gstack-review/SKILL.md'), 'Prior internal render.\n');
}
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureWriteFileSync(join(f.source, rel), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
fixtureUtimesSync(join(f.source, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
fixtureWriteFileSync(join(f.source, 'uncommitted-work'), 'Preserve source edits.\n');
const excluded = tree(f.global), sibling = tree(f.other);
const sourceBefore = Object.fromEntries(['SKILL.md', 'bin', 'lib', 'uncommitted-work'].map(rel => [rel, tree(join(f.source, rel))]));
for (let run = 0; run < 2; run++) {
install(f);
expect(tree(f.global)).toEqual(excluded);
expect(tree(f.other)).toEqual(sibling);
for (const [rel, before] of Object.entries(sourceBefore)) expect(tree(join(f.source, rel))).toEqual(before);
expect(readFileSync(join(f.project, '.agents/skills/gstack-review/SKILL.md'))).toEqual(readFileSync(join(generated, 'gstack-review/SKILL.md')));
expect(existsSync(join(f.source, 'bin/bin'))).toBe(false);
}
}, 90_000);
for (const windows of [false, true]) test(`individual generated skill leaf alias remains supported, Windows=${windows}`, () => {
const f = fixture('.claude');
const rendered = join(f.source, '.agents/skills/gstack-review');
const local = join(f.project, '.agents/skills');
fixtureMkdirSync(rendered, { recursive: true });
fixtureMkdirSync(local, { recursive: true });
fixtureWriteFileSync(join(rendered, 'user-notes'), 'Keep notes beside the source render.\n');
symlinkSync(rendered, join(local, 'gstack-review'), 'dir');
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureWriteFileSync(join(f.source, rel), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
fixtureUtimesSync(join(f.source, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
const excluded = tree(f.global);
for (let run = 0; run < 2; run++) {
install(f);
expect(tree(f.global)).toEqual(excluded);
expect(readFileSync(join(rendered, 'user-notes'), 'utf8')).toBe('Keep notes beside the source render.\n');
expect(readFileSync(join(local, 'gstack-review/SKILL.md'))).toEqual(readFileSync(join(rendered, 'SKILL.md')));
}
}, 90_000);
for (const target of ['dangling-skills', 'dangling-agents', 'cyclic-skills', 'file-skills']) test(`unresolvable namespace is refused without mutation: ${target}`, () => {
const f = fixture('.claude');
const agents = join(f.project, '.agents');
if (target === 'dangling-agents') symlinkSync(join(f.dir, 'missing-agents'), agents, 'dir');
else {
fixtureMkdirSync(agents);
const local = join(agents, 'skills');
if (target === 'file-skills') fixtureWriteFileSync(local, 'Preserve this file.\n');
else symlinkSync(target === 'cyclic-skills' ? 'skills' : join(f.dir, 'missing-skills'), local, 'dir');
}
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('unresolvable directory');
expect(result.stderr).toContain('Use a separate project-local skills directory');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const localLegacy of [false, true]) for (const marker of ['current', '1.85.0.0']) test(`excluded global legacy render survives local migration=${localLegacy}, marker=${marker} and generation`, () => {
const f = fixture('.claude');
const oldRender = join(f.source, '.agents/skills/gstack-claude');
fixtureMkdirSync(oldRender, { recursive: true });
const oldBytes = '---\nname: gstack-claude\n---\n<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nExisting legacy global workflow.\n';
fixtureWriteFileSync(join(oldRender, 'SKILL.md'), oldBytes);
rmSync(join(f.global, 'gstack-claude'), { recursive: true });
symlinkSync(oldRender, join(f.global, 'gstack-claude'), 'dir');
for (const rel of ['bin', 'lib']) {
const target = join(f.global, 'gstack', rel);
expect(lstatSync(target).isSymbolicLink()).toBe(true);
rmSync(target);
fixtureMkdirSync(target);
fixtureWriteFileSync(join(target, 'prior'), 'Existing copied global runtime.\n');
}
const local = join(f.project, '.agents/skills');
if (localLegacy) {
fixtureMkdirSync(local, { recursive: true });
symlinkSync(oldRender, join(local, 'gstack-claude'), 'dir');
}
fixtureWriteFileSync(join(f.home, '.gstack/.last-setup-version'), marker === 'current' ? readFileSync(join(f.source, 'VERSION')) : marker);
const before = tree(f.global);
install(f);
expect(tree(f.global)).toEqual(before);
expect(readFileSync(join(f.global, 'gstack-claude/SKILL.md'), 'utf8')).toBe(oldBytes);
expect(realpathSync(join(local, 'gstack-claude-code/SKILL.md'))).toBe(join(f.source, '.agents/skills/gstack-claude-code/SKILL.md'));
expect(lstatSync(join(local, 'gstack-claude'), { throwIfNoEntry: false })).toBeUndefined();
}, 90_000);
for (const nested of [false, true]) for (const global of [false, true]) for (const windows of [false, true]) {
test(`named ${nested ? 'ancestor' : 'source'} overlap: logical local=${!global}, Windows=${windows}`, () => {
const f = fixture('.claude');
const physical = join(f.project, '.agents/skills/gstack-review', ...(nested ? ['checkout'] : []));
fixtureMkdirSync(physical, { recursive: true });
cpSync(f.source, physical, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
rmSync(f.source, { recursive: true });
symlinkSync(physical, f.source, 'dir');
if (nested) fixtureWriteFileSync(join(dirname(physical), 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior render.\n');
fixtureWriteFileSync(join(physical, 'uncommitted-proof'), 'Do not erase this checkout.\n');
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureCopyFileSync(join(physical, rel.replace('.exe', '')), join(physical, rel));
fixtureUtimesSync(join(physical, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
const env = global ? { ...f.env, CODEX_HOME: join(f.project, '.agents') } : f.env;
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', ...(global ? ['--global'] : []), '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env, encoding: 'utf8', timeout: 60_000,
});
if (windows) {
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('Codex skill copy replacement overlaps source');
expect(tree(f.dir)).toEqual(before);
} else {
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(readFileSync(join(physical, 'uncommitted-proof'), 'utf8')).toBe('Do not erase this checkout.\n');
}
}, 90_000);
}
for (const target of ['source', 'project', 'root-sidecar']) for (const host of target === 'root-sidecar' ? ['codex'] : ['codex', 'claude']) test(`generated namespace alias to ${target} refuses before writes, host=${host}`, () => {
const f = fixture('ordinary');
const render = join(f.source, '.agents/skills');
fixtureMkdirSync(render, { recursive: true });
const alias = join(render, target === 'root-sidecar' ? 'gstack' : 'gstack-review');
const destination = target === 'project' ? join(f.project, 'ordinary-review') : f.source;
if (target === 'project') {
fixtureMkdirSync(destination);
fixtureWriteFileSync(join(destination, 'SKILL.md'), 'Project-owned content.\n');
}
symlinkSync(destination, alias, 'dir');
if (target === 'root-sidecar') fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', host, '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(target === 'root-sidecar' ? 'Codex sidecar' : 'Codex generated skill write');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const target of ['absolute-generation-alias', 'occupied-relocation']) for (const windows of [false, true]) {
test(`direct global checkout preflights ${target} before moving, Windows=${windows}`, () => {
const f = fixture('ordinary');
const direct = join(f.global, 'gstack');
rmSync(direct, { recursive: true });
cpSync(f.source, direct, { recursive: true, verbatimSymlinks: true, preserveTimestamps: true });
fixtureWriteFileSync(join(direct, 'uncommitted-proof'), 'Keep source checkout.\n');
if (target === 'absolute-generation-alias') {
const generated = join(direct, 'generated-codex');
fixtureMkdirSync(generated);
symlinkSync(generated, join(direct, '.agents'), 'dir');
} else {
fixtureMkdirSync(join(f.home, '.gstack/repos'), { recursive: true });
symlinkSync(direct, join(f.home, '.gstack/repos/gstack'), 'dir');
}
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(direct, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(target === 'absolute-generation-alias' ? 'post-relocation generation namespace' : 'checkout relocation');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
}
test('Claude-only setup cannot prune a bannered stale skill inside its source checkout', () => {
const f = fixture('ordinary');
const skill = join(f.source, 'skills/gstack-obsolete/SKILL.md');
fixtureMkdirSync(dirname(skill), { recursive: true });
fixtureWriteFileSync(skill, '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPreserve source content.\n');
const env = { ...f.env, CODEX_HOME: f.source };
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'claude', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('Codex stale host cleanup');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
test('a dangling owned legacy Codex link reaches the rename migration', () => {
const f = fixture('ordinary');
const old = join(f.global, 'gstack-claude');
rmSync(old, { recursive: true });
symlinkSync(join(f.source, '.agents/skills/gstack-claude'), old, 'dir');
for (const rel of ['bin', 'lib']) {
const target = join(f.global, 'gstack', rel);
rmSync(target);
symlinkSync(join(f.source, rel), target, 'dir');
}
install(f);
expect(lstatSync(old, { throwIfNoEntry: false })).toBeUndefined();
expect(readFileSync(join(f.global, 'gstack-claude-code/SKILL.md'), 'utf8')).toContain('name: claude-code');
expect(readFileSync(join(f.global, 'custom/SKILL.md'), 'utf8')).toBe('User-owned skill.\n');
}, 90_000);
for (const explicit of [false, true]) for (const windows of [false, true]) {
test(`global handwritten runtime is refused before writes, explicit=${explicit}, Windows=${windows}`, () => {
const f = fixture(explicit ? '.claude' : 'ordinary');
const runtime = join(f.global, 'gstack');
rmSync(runtime, { recursive: true });
fixtureMkdirSync(runtime);
fixtureWriteFileSync(join(runtime, 'SKILL.md'), 'A handwritten root skill.\n');
fixtureWriteFileSync(join(runtime, 'user-notes'), 'Keep these notes.\n');
if (windows) fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\\n"\n', { mode: 0o755 });
const before = tree(f.dir);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', ...(explicit ? ['--global'] : []), '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain(`global Codex runtime ${runtime} is a real user-owned skill`);
expect(result.stderr).toContain('choose another CODEX_HOME');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
}
for (const prior of ['managed', 'partial']) test(`global ${prior} runtime remains refreshable`, () => {
const f = fixture('ordinary');
const runtime = join(f.global, 'gstack');
rmSync(runtime, { recursive: true });
fixtureMkdirSync(runtime);
if (prior === 'managed') fixtureWriteFileSync(join(runtime, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior runtime.\n');
fixtureWriteFileSync(join(runtime, 'prior-asset'), 'A previous runtime asset.\n');
install(f);
install(f);
expect(existsSync(join(runtime, 'prior-asset'))).toBe(false);
expect(readFileSync(join(runtime, 'SKILL.md'), 'utf8')).toContain('<!-- AUTO-GENERATED from');
expect(realpathSync(join(runtime, 'bin'))).toBe(join(f.source, 'bin'));
}, 90_000);
test('Claude-only setup leaves a global handwritten runtime untouched', () => {
const f = fixture('ordinary');
const runtime = join(f.global, 'gstack');
rmSync(runtime, { recursive: true });
fixtureMkdirSync(runtime);
fixtureWriteFileSync(join(runtime, 'SKILL.md'), 'A handwritten root skill.\n');
fixtureWriteFileSync(join(runtime, 'user-notes'), 'Keep these notes.\n');
const before = tree(runtime);
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'claude', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(0);
expect(tree(runtime)).toEqual(before);
}, 90_000);
test('Unix rename does not overwrite a source checkout registered as another skill', () => {
const f = fixture('ordinary');
const source = join(f.global, 'gstack-review');
rmSync(source);
cpSync(f.source, source, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true });
fixtureWriteFileSync(join(source, 'uncommitted-proof'), 'Keep this source.\n');
for (const rel of ['bin', 'lib']) {
const asset = join(f.global, 'gstack', rel);
rmSync(asset);
fixtureMkdirSync(asset);
}
const before = tree(f.dir);
const result = spawnSync('bash', [join(source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('Codex rename workflow write');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
for (const alias of ['SKILL leaf', 'metadata parent']) test(`direct relocation refuses a generated absolute ${alias} before moving source`, () => {
const f = fixture('ordinary');
const direct = join(f.global, 'gstack');
rmSync(direct, { recursive: true });
cpSync(f.source, direct, { recursive: true, preserveTimestamps: true, verbatimSymlinks: true });
fixtureWriteFileSync(join(direct, 'uncommitted-proof'), 'Keep this source.\n');
const generated = join(direct, '.agents/skills/gstack-review');
fixtureMkdirSync(generated, { recursive: true });
if (alias === 'SKILL leaf') {
fixtureWriteFileSync(join(generated, 'saved.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl -->\nPrior render.\n');
symlinkSync(join(generated, 'saved.md'), join(generated, 'SKILL.md'));
} else {
const prior = join(direct, '.agents/skills/gstack-office-hours/agents');
fixtureMkdirSync(prior, { recursive: true });
symlinkSync(prior, join(generated, 'agents'), 'dir');
}
const before = tree(f.dir);
const result = spawnSync('bash', [join(direct, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('post-relocation generated alias');
expect(tree(f.dir)).toEqual(before);
}, 90_000);
test('managed rename detaches a metadata parent link without touching its source', () => {
const f = fixture('ordinary');
const installed = join(f.global, 'gstack-review');
rmSync(installed);
fixtureMkdirSync(installed);
fixtureWriteFileSync(join(installed, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior installed review.\n');
const sourceAgents = join(f.source, '.agents/skills/gstack-review/agents');
fixtureMkdirSync(sourceAgents, { recursive: true });
fixtureWriteFileSync(join(dirname(sourceAgents), 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior canonical review.\n');
fixtureWriteFileSync(join(sourceAgents, 'user-notes'), 'Preserve source metadata.\n');
symlinkSync(sourceAgents, join(installed, 'agents'), 'dir');
for (const rel of ['bin', 'lib']) {
const asset = join(f.global, 'gstack', rel);
rmSync(asset);
symlinkSync(join(f.source, rel), asset, 'dir');
}
const result = install(f);
expect(result.stderr).toContain('migrated 1 installed skill');
expect(lstatSync(join(installed, 'agents')).isDirectory()).toBe(true);
expect(readFileSync(join(sourceAgents, 'user-notes'), 'utf8')).toBe('Preserve source metadata.\n');
expect(existsSync(join(installed, 'agents/openai.yaml'))).toBe(true);
}, 90_000);
});
describe.skipIf(process.platform === 'win32')('F13 independent review boundaries', () => {
for (const target of ['legacy', 'replacement', 'legacy-skill', 'runtime', 'runtime-bin', 'runtime-lib']) {
test(`Claude-only setup preserves cyclic foreign ownership link: ${target}`, () => {
const f = fixture('ordinary');
rmSync(join(f.global, 'gstack-retired'));
for (const rel of ['bin', 'lib']) {
const asset = join(f.global, 'gstack', rel);
rmSync(asset);
symlinkSync(join(f.source, rel), asset, 'dir');
}
const old = join(f.global, 'gstack-claude');
rmSync(old, { recursive: true });
const oldRender = join(f.source, '.agents/skills/gstack-claude');
fixtureMkdirSync(oldRender, { recursive: true });
fixtureWriteFileSync(join(oldRender, 'SKILL.md'), '<!-- AUTO-GENERATED from SKILL.md.tmpl -->\n<!-- Regenerate: bun run gen:skill-docs -->\nPrior legacy render.\n');
symlinkSync(oldRender, old, 'dir');
const link = target === 'legacy' ? old
: target === 'replacement' ? join(f.global, 'gstack-claude-code')
: target === 'legacy-skill' ? join(old, 'SKILL.md')
: target === 'runtime' ? join(f.global, 'gstack')
: join(f.global, 'gstack', target === 'runtime-bin' ? 'bin' : 'lib');
if (target === 'legacy-skill') {
rmSync(old);
fixtureMkdirSync(old);
}
if (existsSync(link) || lstatSync(link, { throwIfNoEntry: false })) rmSync(link, { recursive: true });
symlinkSync(link, link);
const before = tree(f.global);
for (let run = 0; run < 2; run++) {
install(f, '--host claude');
expect(tree(f.global)).toEqual(before);
}
}, 90_000);
}
for (const alias of [false, true]) for (const windows of [false, true]) {
test(`global generation namespace is refused before mutation: alias=${alias}, Windows=${windows}`, () => {
const f = fixture('ordinary');
const agents = join(f.source, '.agents');
fixtureMkdirSync(agents, { recursive: true });
const codexHome = alias ? join(f.home, 'generation-alias') : agents;
if (alias) symlinkSync(agents, codexHome, 'dir');
if (windows) {
fixtureWriteFileSync(join(f.dir, 'commands/uname'), '#!/bin/sh\nprintf "MINGW64_NT-10.0\n"\n', { mode: 0o755 });
for (const rel of ['browse/dist/browse.exe', 'design/dist/design.exe', 'make-pdf/dist/pdf.exe']) {
fixtureCopyFileSync(join(f.source, rel.replace('.exe', '')), join(f.source, rel));
fixtureUtimesSync(join(f.source, rel), new Date('2040-01-01'), new Date('2040-01-01'));
}
}
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: { ...f.env, CODEX_HOME: codexHome }, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('host namespace');
expect(tree(f.dir)).toEqual(before);
}
}, 90_000);
}
for (const leaf of ['SKILL.md', 'agents']) {
test(`selected generated cyclic write remains fail-closed: ${leaf}`, () => {
const f = fixture('ordinary');
const generated = join(f.source, '.agents/skills/gstack-review');
fixtureMkdirSync(generated, { recursive: true });
const link = join(generated, leaf);
symlinkSync(link, link);
const before = tree(f.dir);
for (let run = 0; run < 2; run++) {
const result = spawnSync('bash', [join(f.source, 'setup'), '--host', 'codex', '--no-team', '--no-plan-tune-hooks', '--no-timeline-stop-hook'], {
cwd: f.other, env: f.env, encoding: 'utf8', timeout: 60_000,
});
expect(result.status, result.stdout + result.stderr).toBe(1);
expect(result.stderr).toContain('ELOOP');
expect(tree(f.dir)).toEqual(before);
}
}, 90_000);
}
});
+11 -11
View File
@@ -110,14 +110,14 @@ describeIfSelected('LLM-as-judge quality evals', [
name: 'command reference table',
suite: 'LLM-as-judge quality evals',
tier: 'llm-judge',
passed: scores.clarity >= 4 && scores.completeness >= 3 && scores.actionability >= 4,
passed: scores.clarity >= 3 && scores.completeness >= 3 && scores.actionability >= 4,
duration_ms: Date.now() - t0,
cost_usd: 0.02,
judge_scores: { clarity: scores.clarity, completeness: scores.completeness, actionability: scores.actionability },
judge_reasoning: scores.reasoning,
});
expect(scores.clarity).toBeGreaterThanOrEqual(4);
expect(scores.clarity).toBeGreaterThanOrEqual(3);
expect(scores.completeness).toBeGreaterThanOrEqual(3);
expect(scores.actionability).toBeGreaterThanOrEqual(4);
}, JUDGE_MS);
@@ -136,14 +136,14 @@ describeIfSelected('LLM-as-judge quality evals', [
name: 'snapshot flags reference',
suite: 'LLM-as-judge quality evals',
tier: 'llm-judge',
passed: scores.clarity >= 4 && scores.completeness >= 4 && scores.actionability >= 4,
passed: scores.clarity >= 3 && scores.completeness >= 4 && scores.actionability >= 4,
duration_ms: Date.now() - t0,
cost_usd: 0.02,
judge_scores: { clarity: scores.clarity, completeness: scores.completeness, actionability: scores.actionability },
judge_reasoning: scores.reasoning,
});
expect(scores.clarity).toBeGreaterThanOrEqual(4);
expect(scores.clarity).toBeGreaterThanOrEqual(3);
expect(scores.completeness).toBeGreaterThanOrEqual(4);
expect(scores.actionability).toBeGreaterThanOrEqual(4);
}, JUDGE_MS);
@@ -160,14 +160,14 @@ describeIfSelected('LLM-as-judge quality evals', [
name: 'browse/SKILL.md reference',
suite: 'LLM-as-judge quality evals',
tier: 'llm-judge',
passed: scores.clarity >= 4 && scores.completeness >= 4 && scores.actionability >= 4,
passed: scores.clarity >= 3 && scores.completeness >= 4 && scores.actionability >= 4,
duration_ms: Date.now() - t0,
cost_usd: 0.02,
judge_scores: { clarity: scores.clarity, completeness: scores.completeness, actionability: scores.actionability },
judge_reasoning: scores.reasoning,
});
expect(scores.clarity).toBeGreaterThanOrEqual(4);
expect(scores.clarity).toBeGreaterThanOrEqual(3);
expect(scores.completeness).toBeGreaterThanOrEqual(4);
expect(scores.actionability).toBeGreaterThanOrEqual(4);
}, JUDGE_MS);
@@ -349,14 +349,14 @@ ${section}`);
name: 'qa/SKILL.md workflow',
suite: 'QA skill quality evals',
tier: 'llm-judge',
passed: scores.clarity >= 4 && scores.completeness >= 3 && scores.actionability >= 4,
passed: scores.clarity >= 3 && scores.completeness >= 3 && scores.actionability >= 4,
duration_ms: Date.now() - t0,
cost_usd: 0.02,
judge_scores: { clarity: scores.clarity, completeness: scores.completeness, actionability: scores.actionability },
judge_reasoning: scores.reasoning,
});
expect(scores.clarity).toBeGreaterThanOrEqual(4);
expect(scores.clarity).toBeGreaterThanOrEqual(3);
// Completeness scores 3 when judge notes the health rubric is in a separate
// section (the eval only passes the Workflow section, not the full document).
expect(scores.completeness).toBeGreaterThanOrEqual(3);
@@ -391,14 +391,14 @@ ${section}`);
name: 'qa/SKILL.md health rubric',
suite: 'QA skill quality evals',
tier: 'llm-judge',
passed: scores.clarity >= 4 && scores.completeness >= 3 && scores.actionability >= 4,
passed: scores.clarity >= 3 && scores.completeness >= 3 && scores.actionability >= 4,
duration_ms: Date.now() - t0,
cost_usd: 0.02,
judge_scores: { clarity: scores.clarity, completeness: scores.completeness, actionability: scores.actionability },
judge_reasoning: scores.reasoning,
});
expect(scores.clarity).toBeGreaterThanOrEqual(4);
expect(scores.clarity).toBeGreaterThanOrEqual(3);
expect(scores.completeness).toBeGreaterThanOrEqual(3);
expect(scores.actionability).toBeGreaterThanOrEqual(4);
}, JUDGE_MS);
@@ -664,7 +664,7 @@ async function runWorkflowJudge(opts: {
// Timeout/retry finalizes once; late provider continuations cannot publish evidence.
const work = async () => {
checkActive();
const thresholds = { clarity: 4, completeness: 3, actionability: 4, ...opts.thresholds };
const thresholds = { clarity: 3, completeness: 3, actionability: 4, ...opts.thresholds };
const input = opts.readInput ? opts.readInput() : readWorkflowJudgeInput({ root: ROOT, skillPath: opts.skillPath,
startMarker: opts.startMarker, endMarker: opts.endMarker });
checkActive();
+40 -13
View File
@@ -6,7 +6,7 @@
* main() — these tests pin its two load-bearing inputs:
*
* 1. fullSuiteJobs(): env override wins, is deliberately UNclamped by
* MAX_FULL_SUITE_JOBS, and rejects garbage loudly (a silent fallback to
* the per-platform cap, and rejects garbage loudly (a silent fallback to
* the default would saturate the sandbox's seccomp supervisor — the
* exact failure the knob exists to prevent).
* 2. FreeShardOutcome.failingFiles: empty on pass, attributed files on
@@ -20,6 +20,8 @@ import { readFileSync } from 'node:fs';
import {
fullSuiteJobs,
MAX_FULL_SUITE_JOBS,
MAX_LINUX_FULL_SUITE_JOBS,
maxFullSuiteJobs,
runFreeShard,
} from '../scripts/test-free-shards';
@@ -38,20 +40,31 @@ function withJobsEnv<T>(value: string | undefined, fn: () => T): T {
describe('test-free-shards: fullSuiteJobs (GSTACK_FREE_JOBS override)', () => {
test('unset and empty string both take the computed default — available CPUs, capped, floor 1', () => {
const expected = Math.max(1, Math.min(MAX_FULL_SUITE_JOBS, os.availableParallelism?.() ?? os.cpus().length));
const expected = Math.max(1, Math.min(maxFullSuiteJobs(), os.availableParallelism?.() ?? os.cpus().length));
expect(withJobsEnv(undefined, fullSuiteJobs)).toBe(expected);
// A stray `export GSTACK_FREE_JOBS=` must not throw.
expect(withJobsEnv('', fullSuiteJobs)).toBe(expected);
});
test.each([[0, 1], [1, 1], [2, 2], [4, 4], [8, 6]])(
'%i available CPUs default to %i serial shard processes regardless of host CPU count',
(availableCpus, expected) => {
test('Linux caps at 16 shard processes; macOS and Windows keep the cap of 6', () => {
expect(maxFullSuiteJobs('linux')).toBe(MAX_LINUX_FULL_SUITE_JOBS);
expect(MAX_LINUX_FULL_SUITE_JOBS).toBe(16);
expect(maxFullSuiteJobs('darwin')).toBe(MAX_FULL_SUITE_JOBS);
expect(maxFullSuiteJobs('win32')).toBe(MAX_FULL_SUITE_JOBS);
expect(MAX_FULL_SUITE_JOBS).toBe(6);
});
test.each([
['darwin', 0, 1], ['darwin', 1, 1], ['darwin', 2, 2], ['darwin', 4, 4], ['darwin', 8, 6],
['linux', 0, 1], ['linux', 4, 4], ['linux', 8, 8], ['linux', 16, 16], ['linux', 64, 16],
] as const)(
'%s: %i available CPUs default to %i serial shard processes regardless of host CPU count',
(platform, availableCpus, expected) => {
const available = spyOn(os, 'availableParallelism').mockReturnValue(availableCpus);
const cpus = spyOn(os, 'cpus').mockReturnValue(Array<os.CpuInfo>(16));
const cpus = spyOn(os, 'cpus').mockReturnValue(Array<os.CpuInfo>(64));
try {
expect(withJobsEnv(undefined, fullSuiteJobs)).toBe(expected);
expect(withJobsEnv('', fullSuiteJobs)).toBe(expected);
expect(withJobsEnv(undefined, () => fullSuiteJobs(platform))).toBe(expected);
expect(withJobsEnv('', () => fullSuiteJobs(platform))).toBe(expected);
expect(cpus).not.toHaveBeenCalled();
} finally {
available.mockRestore();
@@ -73,14 +86,14 @@ describe('test-free-shards: fullSuiteJobs (GSTACK_FREE_JOBS override)', () => {
}));
const { fullSuiteJobs } = await import(${JSON.stringify(import.meta.resolve('../scripts/test-free-shards'))});
delete process.env.GSTACK_FREE_JOBS;
console.log(JSON.stringify([0, 1, 2, 4, 8].map(count => {
console.log(JSON.stringify([0, 1, 2, 4, 8, 32].map(count => {
cpuCount = count;
return fullSuiteJobs();
return [fullSuiteJobs('darwin'), fullSuiteJobs('linux')];
})));
`], { timeout: 10_000 });
expect(result.exitCode).toBe(0);
expect(result.stderr.toString()).toBe('');
expect(JSON.parse(result.stdout.toString())).toEqual([1, 1, 2, 4, 6]);
expect(JSON.parse(result.stdout.toString())).toEqual([[1, 1], [1, 1], [2, 2], [4, 4], [6, 8], [6, 16]]);
});
test('a positive integer override is honored exactly (the sandbox recipe sets 2)', () => {
@@ -100,6 +113,20 @@ describe('test-free-shards: fullSuiteJobs (GSTACK_FREE_JOBS override)', () => {
expect(withJobsEnv(step?.env?.GSTACK_FREE_JOBS, fullSuiteJobs)).toBe(2);
});
test('Windows CI retries attributed flakes serially and uploads every flaky pass', () => {
const source = readFileSync(new URL('../.github/workflows/windows-free-tests.yml', import.meta.url), 'utf8');
const workflow = Bun.YAML.parse(source) as {
jobs: Record<string, { steps: Array<{ name?: string; if?: string; run?: string; env?: Record<string, string>; with?: Record<string, unknown> }> }>;
};
const steps = workflow.jobs['windows-free-tests'].steps;
const suite = steps.find(step => step.run === 'bun run test:windows');
expect(suite?.env?.GSTACK_FREE_RETRY_FLAKY).toBe('1');
expect(suite?.env?.GSTACK_FLAKE_LEDGER).toBe('${{ runner.temp }}/flake-ledger.jsonl');
const upload = steps.find(step => step.name === 'Upload flake ledger');
expect(upload?.if).toBe('always()');
expect(upload?.with?.path).toBe('${{ runner.temp }}/flake-ledger.jsonl');
});
test('an explicit override does not probe the available CPUs', () => {
const available = spyOn(os, 'availableParallelism').mockImplementation(() => {
throw new Error('CPU detection must not run for an explicit override');
@@ -118,8 +145,8 @@ describe('test-free-shards: fullSuiteJobs (GSTACK_FREE_JOBS override)', () => {
}
});
test('override is deliberately NOT clamped by MAX_FULL_SUITE_JOBS (beefy boxes may raise it)', () => {
const above = MAX_FULL_SUITE_JOBS + 6;
test('override is deliberately NOT clamped by the platform cap (beefy boxes may raise it)', () => {
const above = MAX_LINUX_FULL_SUITE_JOBS + 6;
expect(withJobsEnv(String(above), fullSuiteJobs)).toBe(above);
});
+17
View File
@@ -32,6 +32,7 @@ import {
verifyFreeCiResults,
eligibleFreeRetryFiles,
selectQuickFreeFiles,
unseededFreeFiles,
QUICK_CORE,
type FreeCiResult,
DEFAULT_WALL_TIMEOUT_MS as WALL_BASE_MS,
@@ -846,6 +847,22 @@ describe('test-free-shards: duration-aware packing (full-suite LPT)', () => {
expect(() => packShardsByDuration(files, 0, {})).toThrow();
});
test('files missing from the seed are named, and --ci-plan warns on stderr without touching the matrix', () => {
expect(unseededFreeFiles(files, { 'test/a.test.ts': 1, 'test/c.test.ts': 1 })).toEqual(['test/b.test.ts', 'test/d.test.ts']);
expect(unseededFreeFiles(files, Object.fromEntries(files.map(f => [f, 1])))).toEqual([]);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'free-seed-drift-'));
try {
const seedPath = path.join(dir, 'seed.json');
fs.writeFileSync(seedPath, JSON.stringify({ version: 1, durations: { 'test/strict-output.test.ts': 1_000 } }));
const planned = Bun.spawnSync([process.execPath, path.join(ROOT, 'scripts/test-free-shards.ts'), '--ci-plan', path.join(dir, 'plan.json'), '--shards', '2'], {
env: { ...process.env, GSTACK_FREE_TEST_DURATIONS: seedPath }, timeout: 10_000,
});
expect(planned.exitCode, planned.stderr.toString()).toBe(0);
expect(JSON.parse(planned.stdout.toString())).toEqual({ shard: [1, 2] });
expect(planned.stderr.toString()).toMatch(/\d+ file\(s\) have no recorded duration .*bun run test:ubicloud --record-durations/);
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
});
test('corrupt seed falls back to null (hash sharding), never throws', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'durations-seed-'));
const seedPath = path.join(dir, 'seed.json');
+63
View File
@@ -0,0 +1,63 @@
/**
* Static and offline pins for `bun run test:ubicloud` (scripts/ubicloud/).
* The VM path needs a Ubicloud token and costs money, so it is exercised by
* hand; these checks keep its environment from drifting away from the
* required CI free lane it mirrors, and prove it fails before any network
* call when the token is absent.
*/
import { describe, expect, test } from 'bun:test';
import { readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
const ROOT = resolve(import.meta.dir, '..');
const DIR = join(ROOT, 'scripts/ubicloud');
const read = (rel: string) => readFileSync(join(ROOT, rel), 'utf8');
type Step = { uses?: string; run?: string; with?: Record<string, unknown>; env?: Record<string, string> };
const workflow = Bun.YAML.parse(read('.github/workflows/free-tests.yml')) as { jobs: Record<string, { steps: Step[] }> };
const freeSuite = workflow.jobs['free-suite'].steps;
describe('ubicloud free-suite runner', () => {
test('setup pins the same Bun version as the CI free-suite job', () => {
const ciBun = freeSuite.find(step => step.uses?.startsWith('oven-sh/setup-bun'))?.with?.['bun-version'];
expect(ciBun).toBeDefined();
expect(read('scripts/ubicloud/setup-free-suite.sh')).toContain(`BUN_VERSION=${ciBun}\n`);
});
test('setup performs the CI job’s build steps', () => {
const setup = read('scripts/ubicloud/setup-free-suite.sh');
for (const command of ['bun install --frozen-lockfile', 'bun run gen:skill-docs --host all', 'bun run vendor:xterm',
'bash browse/scripts/build-node-server.sh', 'bun run build:gates', 'bun run build:cso']) {
expect(freeSuite.some(step => step.run?.includes(command))).toBe(true);
expect(setup).toContain(command);
}
expect(setup).toContain('chrome-sandbox');
expect(setup).toContain('kernel.apparmor_restrict_unprivileged_userns=0');
});
test('the wrapper runs the suite under Xvfb with the CI lane’s strictness knobs', () => {
const ciEnv = freeSuite.find(step => step.run?.includes('bun run test:free'))?.env ?? {};
const wrapper = read('scripts/ubicloud/test-free.sh');
expect(ciEnv.GSTACK_EXPECT_BINARIES).toBe('1');
expect(ciEnv.GSTACK_FREE_RETRY_FLAKY).toBe('1');
expect(wrapper).toContain('--env GSTACK_EXPECT_BINARIES=1');
expect(wrapper).toContain('--env GSTACK_FREE_RETRY_FLAKY=1');
expect(wrapper).toContain('xvfb-run -a bun run test:free');
expect(JSON.parse(read('package.json')).scripts['test:ubicloud']).toBe('bash scripts/ubicloud/test-free.sh');
});
test('remote commands force umask 022 and teardown is trapped on exit', () => {
const runner = read('scripts/ubicloud/ubi-runner.sh');
expect(runner).toContain('"umask 022; $*"');
expect(runner).toMatch(/trap 'cmd_down "\$RUN_VM"[^']*' EXIT/);
});
test('refuses to start without UBICLOUD_API_KEY, before any network call', () => {
const env = { ...process.env, UBICLOUD_API_URL: 'http://127.0.0.1:9' } as Record<string, string | undefined>;
delete env.UBICLOUD_API_KEY;
const result = Bun.spawnSync(['bash', join(DIR, 'ubi-runner.sh'), 'list'], { env, timeout: 10_000 });
expect(result.exitCode).toBe(1);
expect(result.stderr.toString()).toContain('UBICLOUD_API_KEY is not set');
expect(result.stdout.toString()).toBe('');
});
});