Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,12 @@ jobs:
- name: Build
run: bun run build

# Corbits Code codebase only — ./src and ./tests. The vendored interchange
# inference package (vendor/) is out of scope for this repo's CI.
# Same script the local `bun run check` gate runs: the projects-dir
# guard wraps `bun run test`, which is the seeded, randomized suite
# (bun test ./src ./tests ./evals --randomize --seed 424242) defined
# once in package.json. Randomized order catches tests that only pass
# in the default file order (shared module-level state, an unrestored
# global mock, a leaked env var); the seed is fixed so a failure here
# reproduces locally with `bun run test`.
- name: Test
run: bun run test

# Catches tests that only pass because of the default file order (shared
# module-level state, an unrestored global mock, a leaked env var). The
# seed is fixed so a failure here reproduces locally with the same flag.
- name: Test (randomized order)
run: bun test ./src ./tests ./evals --randomize --seed 424242
run: bun run check:projects-dir-guard
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ When refactoring replaces an old path, delete the old one. No back-compat shims,
bun run check
```

`bun run check` is the single pre-PR gate: it runs `lint`, `typecheck`, `build`, and `test`, in that order, matching CI.
`bun run check` is the single pre-PR gate: it runs `lint`, `typecheck`,
`build`, and `check:projects-dir-guard` — which runs the `test` suite under
the projects-dir sandbox guard — in that order, matching CI.

Run the full suite before declaring any task complete. Do not substitute individual targets. If a failure is pre-existing and unrelated to your change, say so explicitly.

`bun run test` runs `bun test ./src ./tests ./evals`. A bare `bun test` also
`bun run test` runs `bun test ./src ./tests ./evals --randomize --seed 424242` —
the same suite CI runs. A bare `bun test` also
scans `vendor/`, adding hundreds of unrelated results and making pass/fail
counts meaningless to compare across branches — always use `bun run test`.

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
### Changed

- Interactive TUI pins OpenTUI 0.5.10 (`@opentui/core`, keymap, solid, and native platform packages in lockstep).
- `bun run check` now runs the full test suite with the same seed CI uses
(`--randomize --seed 424242`), so a local green can no longer mask a CI test
failure. CI's test job invokes the same `check:projects-dir-guard` script
instead of duplicating the test command, and the projects-dir guard no longer
squats on the `test` script name.

## [0.3.14] - 2026-09-03

Expand Down
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ bun run build
bun run test
```

These match the CI workflow in `.github/workflows/ci.yml`. Run the full suite
before opening a PR. Do not substitute a bare `bun test` (it also scans
These match the CI workflow in `.github/workflows/ci.yml`. Run `bun run check`
(lint, typecheck, build, and the guarded test suite) before opening a PR —
`bun run test` alone skips the projects-dir sandbox guard, which only runs
under `bun run check` and CI. Do
not substitute a bare `bun test` (it also scans
`vendor/` and pollutes pass/fail counts).

## Commits
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@
"build": "bun build ./src/index.ts --outdir ./dist --target bun --external '@opentui/core-*' && bun scripts/copy-repo-plugins.ts",
"build:bin": "bun build ./src/index.ts --compile --minify --define process.env.NODE_ENV='\"production\"' --outfile ./dist/corbits && bun scripts/copy-repo-plugins.ts",
"typecheck": "tsc --noEmit",
"test": "bun scripts/guard-real-projects-dir.ts ./src ./tests ./evals",
"test": "bun test ./src ./tests ./evals --randomize --seed 424242",
"lint": "prettier --check --cache . && eslint --cache .",
"check": "bun run lint && bun run typecheck && bun run build && bun run test",
"check:projects-dir-guard": "bun scripts/guard-real-projects-dir.ts",
"check": "bun run lint && bun run typecheck && bun run build && bun run check:projects-dir-guard",
"start": "bun run build && bun ./dist/index.js",
"eval:capability": "bun scripts/eval-capability.ts",
"eval:public-swe-one": "bun scripts/eval-public-swe-one.ts",
Expand Down
6 changes: 3 additions & 3 deletions scripts/guard-real-projects-dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { join } from "node:path";
import { mkdir, readdir, rm } from "node:fs/promises";
import { spawn } from "node:child_process";

// Runs `bun test` and fails the run if any test wrote into the real
// Runs the test suite (`bun run test` — the same seeded, randomized command
// CI runs) and fails the run if any test wrote into the real
// ~/.corbits/projects directory. Tests must sandbox state under a temp
// `home` (see src/session/index.ts's `home` overrides); nothing running
// under this wrapper is allowed to fall back to the developer's own
Expand Down Expand Up @@ -44,8 +45,7 @@ async function main(): Promise<void> {
const runTmpDir = join(tmpdir(), `corbits-test-guard-${runId}`);
await mkdir(runTmpDir, { recursive: true });

const args = process.argv.slice(2);
const child = spawn("bun", ["test", ...args], {
const child = spawn("bun", ["run", "test"], {
stdio: "inherit",
env: { ...process.env, TMPDIR: runTmpDir, TMP: runTmpDir, TEMP: runTmpDir },
});
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/check-gate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "bun:test";

// Guard against the gate drifting apart again (CL-7300): `bun run check` and
// CI's test job must resolve to the same seeded suite, and the projects-dir
// guard must delegate to the `test` script rather than duplicate its command.

const repoRoot = join(import.meta.dir, "..", "..");
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as {
scripts: Record<string, string>;
};
const ci = readFileSync(join(repoRoot, ".github", "workflows", "ci.yml"), "utf8");
const guardSource = readFileSync(join(repoRoot, "scripts", "guard-real-projects-dir.ts"), "utf8");

const GUARD_SCRIPT = "check:projects-dir-guard";
const TEST_SUITE = "bun test ./src ./tests ./evals --randomize --seed 424242";

describe("check gate", () => {
test("`test` is the seeded, randomized suite CI runs", () => {
expect(pkg.scripts.test).toBe(TEST_SUITE);
});

test("`check` runs the suite through the projects-dir guard", () => {
expect(pkg.scripts[GUARD_SCRIPT]).toContain("scripts/guard-real-projects-dir.ts");
expect(pkg.scripts.check).toContain(`bun run ${GUARD_SCRIPT}`);
// The guard delegates to `bun run test` so the suite command has one home.
expect(guardSource).toContain('"run", "test"');
});

test("CI's test job invokes the same script, not a raw test command", () => {
expect(ci).toContain(`run: bun run ${GUARD_SCRIPT}`);
// An unguarded suite step here would reintroduce the local-green/CI
// mismatch (and skip the projects-dir sandbox) this gate exists to prevent.
expect(ci).not.toMatch(/^\s*run: bun test(\s|$)/m);
expect(ci).not.toMatch(/^\s*run: bun run test(\s|$)/m);
});
});
Loading