Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5157129
test: make Node and repo-root checks hold across patch bumps and work…
BrainerVirus Oct 3, 2026
4f8f56b
test: delete the phase-9 traceability gate
BrainerVirus Oct 3, 2026
e6f0279
test: drop packed-artifact assertions duplicated across three gate files
BrainerVirus Oct 3, 2026
ceb63cc
test: check generated copies against git trees and derive skill lists…
BrainerVirus Oct 3, 2026
b5d4347
test: assert bootstrap and skill contract keywords instead of whole s…
BrainerVirus Oct 3, 2026
3e2c583
test: rename typescript-parity to repo-context and drop its source-gr…
BrainerVirus Oct 3, 2026
43a1be1
test: fold per-host route-denial scope tests into the core parity table
BrainerVirus Oct 3, 2026
6342f97
test: build packed Codex and Pi artifacts from source instead of loca…
BrainerVirus Oct 3, 2026
16a17a0
chore(test): split test and test:packaging tiers and guard the Node v…
BrainerVirus Oct 3, 2026
5f97fd2
chore(test): run every test directory in CI and drop the phase-9 chec…
BrainerVirus Oct 3, 2026
e3a7b8e
test: build the Cursor dist that the dev-checkout install test needs
BrainerVirus Oct 3, 2026
0331169
fix(core): resolve npm-cli.js through a symlinked npm before layout g…
BrainerVirus Oct 3, 2026
b3cbad3
test: pin one phrase per agent-critical delivery rule and the skill c…
BrainerVirus Oct 3, 2026
4d51528
test: build the Cursor package for the dev-install test in a sandbox
BrainerVirus Oct 3, 2026
ab5d158
chore(test): delete the stale windows-inner-suite-probe workflow
BrainerVirus Oct 3, 2026
bdd28f1
test: pin behavioral-tdd RED-first order and escape phrase regex input
BrainerVirus Oct 3, 2026
018e53d
Merge branch 'main' into chore/test-suite-cleanup
BrainerVirus Oct 3, 2026
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
27 changes: 23 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,29 @@ jobs:
- name: Typecheck (whole repo)
run: bun run typecheck

remaining:
# Test directories without a package job above, so no suite escapes CI.
name: check (codex, mcp, opencode-v2, acceptance)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}

- uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_CURRENT }}

- name: Install deps
run: bun install --frozen-lockfile

- name: Test codex, mcp, opencode-v2, acceptance
run: bun test test/workit-codex test/workit-mcp test/opencode-v2 test/acceptance

shared:
# test/shared/helpers has no standalone tests (helpers are exercised via the
# importing packages' jobs); the job guards that the helpers still compile.
Expand Down Expand Up @@ -216,12 +239,8 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [24.20.0]
steps:
# The phase-9 traceability gate asserts the DECLARED branch is active;
# the default PR checkout is detached at the merge commit, so check out
# the head ref (fallback: the default ref, e.g. main pushes).
- uses: actions/checkout@v7
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

- uses: oven-sh/setup-bun@v2
Expand Down
25 changes: 0 additions & 25 deletions .github/workflows/windows-inner-suite-probe.yml

This file was deleted.

4 changes: 4 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ linker = "hoisted"
# fires when a pin is deliberately moved to a fresh release. Machine-global
# `min-release-age` in ~/.npmrc still guards every other project.
minimumReleaseAge = 0

[test]
# Fail fast with one clear message when the PATH node is older than Node 24.
preload = ["./test/shared/node-guard.ts"]
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@
"knip": "knip --no-progress --no-config-hints && knip --no-progress --production --files && bun scripts/check-reachability.ts",
"format": "oxfmt packages/workit-core/src packages/workit-mcp/src packages/workit-opencode/src packages/workit-cli/src packages/workit-cursor/mcp/run-server.ts packages/workit-codex/hooks packages/workit-codex/scripts packages/workit-pi/src packages/workit-pi/extensions test '!test/fixtures/cursor-schemas/**' package.json packages/*/package.json tsconfig.json",
"format:check": "oxfmt --check packages/workit-core/src packages/workit-mcp/src packages/workit-opencode/src packages/workit-cli/src packages/workit-cursor/mcp/run-server.ts packages/workit-codex/hooks packages/workit-codex/scripts packages/workit-pi/src packages/workit-pi/extensions test '!test/fixtures/cursor-schemas/**' package.json packages/*/package.json tsconfig.json",
"test": "bun test",
"test": "bun scripts/test.ts unit",
"test:packaging": "bun scripts/test.ts packaging",
"typecheck": "tsc --noEmit",
"check": "bun run build && bun run lint && bun run format:check && bun test && tsc --noEmit"
"check": "bun run build && bun run lint && bun run format:check && bun run test && bun run test:packaging && tsc --noEmit"
},
"devDependencies": {
"@earendil-works/pi-coding-agent": "0.85.1",
Expand Down
18 changes: 17 additions & 1 deletion packages/workit-core/src/core/host-install.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { spawnSync } from "node:child_process";
import { accessSync, constants, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import {
accessSync,
constants,
existsSync,
readFileSync,
readdirSync,
realpathSync,
statSync,
} from "node:fs";
import os from "node:os";
import path from "node:path";

Expand Down Expand Up @@ -448,6 +456,14 @@ export function runHostCommand(
}

const findNpmCli = (npm: string): string | null => {
// `npm` is usually a symlink to npm-cli.js (Node's bin/npm, a package's
// node_modules/.bin/npm); resolve it before guessing install layouts.
try {
const resolved = realpathSync(npm);
if (path.basename(resolved) === "npm-cli.js") return resolved;
} catch {
// Fall through to the layout candidates.
}
const dir = path.dirname(npm);
const candidates = [
path.join(dir, "node_modules", "npm", "bin", "npm-cli.js"),
Expand Down
35 changes: 35 additions & 0 deletions scripts/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bun
// Test tiers. `bun run test` runs the fast domain/unit tier; `bun run
// test:packaging` runs the suites that pack tarballs, run npm/Pi installs,
// drive doctor against installed artifacts, or need docker. Packaging suites
// build what they pack (packWorkspacePackages builds every adapter into a
// sandbox), so neither tier needs `bun run build` first. Plain `bun test`
// still runs everything. Extra arguments pass through to `bun test`.
import { spawnSync } from "node:child_process";

const PACKAGING = [
"test/artifacts/**",
"test/opencode-v2/contract.test.ts",
"test/opencode-v2/lifecycle.test.ts",
"test/opencode-v2/matrix.test.ts",
"test/workit-cli/doctor.test.ts",
"test/workit-cli/packed-cli.test.ts",
"test/workit-cli/platform-install.test.ts",
"test/workit-codex/packed-launcher.test.ts",
"test/workit-core/cursor-install-mcp.test.ts",
"test/workit-core/doctor.test.ts",
"test/workit-core/install-scripts.test.ts",
"test/workit-pi/stock-pi.test.ts",
];

const [tier, ...rest] = process.argv.slice(2);
if (tier !== "unit" && tier !== "packaging") {
console.error("usage: bun scripts/test.ts <unit|packaging> [bun test args]");
process.exit(2);
}
const selection =
tier === "packaging"
? PACKAGING.map((pattern) => `./${pattern.replace(/\/\*\*$/, "")}`)
: PACKAGING.map((pattern) => `--path-ignore-patterns=${pattern}`);
const run = spawnSync("bun", ["test", ...selection, ...rest], { stdio: "inherit" });
process.exit(run.status ?? 1);
1 change: 0 additions & 1 deletion test/artifacts/cursor-install-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ const byName = (packs: ReturnType<typeof packWorkspacePackages>, name: string) =
test(
"Cursor build, package, and packed CLI doctor enforce exact canonical method skills and Workit identity",
() => {
expect(WORKIT).toHaveLength(14);
const fixture = mkdtempSync(path.join(os.tmpdir(), "wk-cursor-invariants-"));
try {
const missingWorkitRepo = copyBuildFixture(path.join(fixture, "missing-workit"));
Expand Down
22 changes: 5 additions & 17 deletions test/artifacts/manifests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { expect, test } from "bun:test";
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { WORKIT_METHOD_SKILLS } from "@/packages/workit-core/src/core/skill-manifests";
import {
WORKIT_METHOD_SKILLS,
WORKIT_SKILL_ALIASES,
} from "@/packages/workit-core/src/core/skill-manifests";
import { SUPPORT_MATRIX } from "@/packages/workit-core/src/core/support-matrix";
import {
listTarball,
Expand Down Expand Up @@ -248,22 +251,7 @@ test(
for (const skill of WORKIT_METHOD_SKILLS) {
expect(entries, `skills/${skill}/SKILL.md`).toContain(`skills/${skill}/SKILL.md`);
}
for (const alias of [
"wk-babysit",
"wk-blast-radius",
"wk-challenge",
"wk-debug",
"wk-deslop",
"wk-diagram",
"wk-green-run",
"wk-handoff",
"wk-implement",
"wk-mockup",
"wk-plan",
"wk-review",
"wk-steer",
"wk-tdd",
]) {
for (const alias of Object.keys(WORKIT_SKILL_ALIASES)) {
expect(entries, `commands/${alias}.md`).toContain(`commands/${alias}.md`);
}
expect(
Expand Down
1 change: 0 additions & 1 deletion test/artifacts/opencode-skill-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const tarballSkillNames = (tarball: string, prefix: string): string[] =>
.sort();

test("opencode packed tarball ships exactly the canonical method skills", () => {
expect(WORKIT).toHaveLength(14);
const tarball = byName(packWorkspacePackages(), OPENCODE).tarball;
expect(tarballSkillNames(tarball, "assets/skills/")).toEqual(WORKIT);
expect(tarballSkillNames(tarball, "assets/vendor/superpowers/skills/")).toEqual([]);
Expand Down
51 changes: 2 additions & 49 deletions test/artifacts/package-contents.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import { expect, test } from "bun:test";
import { spawnSync } from "node:child_process";
import {
cpSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
symlinkSync,
} from "node:fs";
import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import {
Expand Down Expand Up @@ -72,28 +64,12 @@ test("runtime TS allowlist permits vendor examples and exactly the root loader s
}
});

test("opencode tarball ships one bundled dist entry plus fourteen method skills (RR-02/PT-06/PT-07)", () => {
test("opencode tarball ships one bundled dist entry and no commands, templates or vendor trees", () => {
const packs = packWorkspacePackages();
const tarball = byName(packs, OPENCODE).tarball;
const entries = listTarball(tarball);

expect(entries).toContain("dist/plugin.js");
expect(entries.filter((e) => e.startsWith("assets/skills/") && e.endsWith("/SKILL.md"))).toEqual([
"assets/skills/workit-babysit/SKILL.md",
"assets/skills/workit-behavioral-tdd/SKILL.md",
"assets/skills/workit-blast-radius/SKILL.md",
"assets/skills/workit-challenge/SKILL.md",
"assets/skills/workit-debug/SKILL.md",
"assets/skills/workit-deslop/SKILL.md",
"assets/skills/workit-diagram/SKILL.md",
"assets/skills/workit-green-run/SKILL.md",
"assets/skills/workit-handoff/SKILL.md",
"assets/skills/workit-implement/SKILL.md",
"assets/skills/workit-mockup/SKILL.md",
"assets/skills/workit-plan/SKILL.md",
"assets/skills/workit-review/SKILL.md",
"assets/skills/workit-steer/SKILL.md",
]);
expect(entries.some((e) => e.startsWith("assets/commands/"))).toBe(false);
expect(entries.some((e) => e.startsWith("assets/templates/"))).toBe(false);
expect(entries.some((e) => e.startsWith("assets/vendor/"))).toBe(false);
Expand Down Expand Up @@ -181,16 +157,6 @@ test("core tarball keeps its source package layout without legacy vendor shell (
expect(entries).not.toContain("scripts/verify-project.sh");
});

test("no packed package.json carries a workspace:, file: or git: protocol (CA-03)", () => {
const packs = packWorkspacePackages();
for (const pack of packs) {
const raw = readTarballFile(pack.tarball, "package.json");
expect(raw, pack.packageName).not.toContain("workspace:");
expect(raw, pack.packageName).not.toContain("file:");
expect(raw, pack.packageName).not.toContain("git:");
}
});

test("packed runtime JS imports no core source subpaths and no checkout paths", () => {
const packs = packWorkspacePackages();
const normalized = REPO_ROOT.split(path.sep).join("/");
Expand Down Expand Up @@ -403,16 +369,3 @@ test("adapter tarballs ship no legacy vendor trees", () => {
).toBe(false);
}
});

test("tracked CLI template mirrors stay byte-identical to the core templates", () => {
// The CLI package tracks copies of the execution templates (shipped to
// projects by hygiene scaffolding); a fix in one copy must land in both.
for (const name of ["execution-contract.md", "plan-template.md"]) {
const core = readFileSync(path.join(REPO_ROOT, "packages/workit-core/templates", name), "utf8");
const mirror = readFileSync(
path.join(REPO_ROOT, "packages/workit-cli/assets/templates", name),
"utf8",
);
expect(mirror, name).toBe(core);
}
});
23 changes: 6 additions & 17 deletions test/artifacts/packed-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ import {
installPackedPackage,
isolatedEnv,
npmRegistryReachable,
packReleaseCandidate,
packWorkspacePackages,
tarballSpec,
REPO_ROOT,
} from "@/test/shared/helpers/packages";
import {
WORKIT_METHOD_SKILLS,
WORKIT_SKILL_ALIASES,
} from "@/packages/workit-core/src/core/skill-manifests";

// Task 7 packed-runtime gate: from EXTRACTED tarballs with repository node_modules
// unavailable, the packaged adapters load/boot under plain Node without Bun or a
Expand Down Expand Up @@ -208,8 +211,8 @@ test(
};
const cleanup = await mod.default.setup(fake as never);
if (typeof cleanup === "function") cleanup();
expect(skills).toHaveLength(14);
expect(commands).toHaveLength(14);
expect(skills).toHaveLength(WORKIT_METHOD_SKILLS.length);
expect(commands).toHaveLength(Object.keys(WORKIT_SKILL_ALIASES).length);
for (const skill of skills) {
expect(existsSync(skill.path), skill.id).toBe(true);
expect(realpathSync(skill.path).startsWith(realpathSync(skillsPath)), skill.id).toBe(true);
Expand Down Expand Up @@ -498,20 +501,6 @@ test(
{ timeout: 60_000 },
);

test(
"the runtime gate exercises the same final candidate artifacts (CA-30)",
() => {
// Both calls resolve to the same cached pack, so this asserts the gate uses
// the final candidate, not fresh-vs-cached bytes. Byte-stability across a
// forced repack is proven in release-candidate.test.ts ("a fresh repack
// yields byte-identical sha256") and phase-0-candidate.test.ts (D12).
const candidate = packReleaseCandidate();
const packs = packWorkspacePackages();
expect(candidate.map((p) => p.sha256)).toEqual(packs.map((p) => p.sha256));
},
{ timeout: 60_000 },
);

test(
"declared platform matrix is pinned across CI, engines, and lockfiles (PT-11/PT-12)",
() => {
Expand Down
Loading
Loading