From 5e302e707ce2939ecfc0da92c86dea5bade6981b Mon Sep 17 00:00:00 2001 From: Eduardo Marquez <55303379+DocksDocks@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:35:58 -0300 Subject: [PATCH 1/4] test: make the author suites pass on Node and Bun Three author-side tests depended on Node-only behavior, which blocks moving the tooling to Bun (#87): - skills/guard.mjs printed only the runtime's spawn error message, and author-tooling.mjs matched Node's text. The guard now leads with the errno code, which both runtimes report the same way. - agent-score-vacuity loaded its fixture through NODE_OPTIONS=--import, which Bun ignores. The hook now goes on the gate's command line, where both runtimes accept --import. - tag-ci-run-identity changed PATH for the whole process and cleaned up only at exit. A runner that shares one process across files would leak the stub gh into later files, so the file restores PATH in test.after. Refs #87 --- scripts/skills/guard.mjs | 6 ++- scripts/tests/author-tooling.mjs | 2 +- .../tests/unit/agent-score-vacuity.test.mjs | 40 +++++++++++-------- .../tests/unit/tag-ci-run-identity.test.mjs | 11 ++++- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/scripts/skills/guard.mjs b/scripts/skills/guard.mjs index 0a10ae75..1a8662d0 100644 --- a/scripts/skills/guard.mjs +++ b/scripts/skills/guard.mjs @@ -12,7 +12,11 @@ function run(args) { const result = spawnSync('node', args, { stdio: 'inherit' }); const command = `node ${args.join(' ')}`; if (result.error) { - console.error(`FAIL: ${command} could not start: ${result.error.message}`); + // Lead with the errno code: it is the same on every runtime, while the message text is + // not (Node prints `spawnSync node ENOENT`, Bun prints `Executable not found in $PATH`). + console.error( + `FAIL: ${command} could not start: ${result.error.code ?? 'unknown error'} (${result.error.message})`, + ); process.exit(1); } if (result.signal !== null || result.status === null) { diff --git a/scripts/tests/author-tooling.mjs b/scripts/tests/author-tooling.mjs index 57c5d971..969cbd11 100755 --- a/scripts/tests/author-tooling.mjs +++ b/scripts/tests/author-tooling.mjs @@ -243,7 +243,7 @@ function testSkillsGuardSpawnFailure() { assert.match( result.stderr, new RegExp( - `^FAIL: node ${path.join(ROOT, 'scripts/lib/validate-skills.mjs')} --runtime all ${target} could not start: .*ENOENT`, + `^FAIL: node ${path.join(ROOT, 'scripts/lib/validate-skills.mjs')} --runtime all ${target} could not start: ENOENT \\(`, ), ); } diff --git a/scripts/tests/unit/agent-score-vacuity.test.mjs b/scripts/tests/unit/agent-score-vacuity.test.mjs index f0469343..25f19934 100644 --- a/scripts/tests/unit/agent-score-vacuity.test.mjs +++ b/scripts/tests/unit/agent-score-vacuity.test.mjs @@ -72,7 +72,9 @@ const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docks-agent-score-vacuity for (const tool of ['node', 'bun', 'claude', 'shellcheck', 'cargo']) writeShim(shimDir, tool); // Make the parent gate observe either an empty agents directory or a declared root that does -// not exist. Child checks are shimmed; the same hook is harmless in those short-lived processes. +// not exist. The hook loads through a `--import` argument on the gate process only: Node and +// Bun both accept that flag, but Bun ignores `--import` inside NODE_OPTIONS. Child checks are +// shimmed, so they never need it. const fixtureHook = path.join(shimDir, 'payload-root-fixture.mjs'); fs.writeFileSync( fixtureHook, @@ -93,23 +95,27 @@ delete baseEnv.GITHUB_ACTIONS; delete baseEnv.NODE_OPTIONS; const runCi = (mode) => { - const result = spawnSync(process.execPath, ['scripts/ci.mjs', '--plugin', TARGET], { - cwd: REPO, - encoding: 'utf8', - timeout: 180_000, - env: { - ...baseEnv, - PATH: `${shimDir}${path.delimiter}${process.env.PATH ?? ''}`, - DOCKS_AGENT_SCORE_MODE: mode, - ...(mode === 'empty-directory' || mode.startsWith('missing-') - ? { - NODE_OPTIONS: `--import=${fixtureHook}`, - DOCKS_MISSING_ROOT: - mode === 'missing-skills-root' ? SKILLS_ROOT : mode === 'missing-agents-root' ? AGENTS_ROOT : '', - } - : {}), + const needsFixture = mode === 'empty-directory' || mode.startsWith('missing-'); + const result = spawnSync( + process.execPath, + [...(needsFixture ? [`--import=${fixtureHook}`] : []), 'scripts/ci.mjs', '--plugin', TARGET], + { + cwd: REPO, + encoding: 'utf8', + timeout: 180_000, + env: { + ...baseEnv, + PATH: `${shimDir}${path.delimiter}${process.env.PATH ?? ''}`, + DOCKS_AGENT_SCORE_MODE: mode, + ...(needsFixture + ? { + DOCKS_MISSING_ROOT: + mode === 'missing-skills-root' ? SKILLS_ROOT : mode === 'missing-agents-root' ? AGENTS_ROOT : '', + } + : {}), + }, }, - }); + ); return { status: result.status, output: `${result.stdout ?? ''}${result.stderr ?? ''}` }; }; diff --git a/scripts/tests/unit/tag-ci-run-identity.test.mjs b/scripts/tests/unit/tag-ci-run-identity.test.mjs index cee38542..c8331d88 100644 --- a/scripts/tests/unit/tag-ci-run-identity.test.mjs +++ b/scripts/tests/unit/tag-ci-run-identity.test.mjs @@ -48,8 +48,15 @@ exit 64 ); // The poll sleeps between attempts; a no-op keeps the 30-attempt exhaustion case fast. fs.writeFileSync(path.join(binDir, 'sleep'), '#!/bin/sh\nexit 0\n', { mode: 0o755 }); -process.env.PATH = `${binDir}${path.delimiter}${process.env.PATH ?? ''}`; -process.on('exit', () => fs.rmSync(stubRoot, { recursive: true, force: true })); +// Restore PATH when this file's tests end: a runner that shares one process across test +// files (`bun test`) would otherwise hand the stub `gh` and `sleep` to every later file. +const inheritedPath = process.env.PATH; +process.env.PATH = `${binDir}${path.delimiter}${inheritedPath ?? ''}`; +test.after(() => { + if (inheritedPath === undefined) delete process.env.PATH; + else process.env.PATH = inheritedPath; + fs.rmSync(stubRoot, { recursive: true, force: true }); +}); const io = createGenericPluginReleaseIo({ repo: REPO, plugins: PLUGINS }); const run = (overrides) => ({ From b65f78bbfc787a6115202d27dc170d0a887d8a09 Mon Sep 17 00:00:00 2001 From: Eduardo Marquez <55303379+DocksDocks@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:45:28 -0300 Subject: [PATCH 2/4] build: run the author tooling on Bun 1.4.4 Author scripts start 2-4x faster on Bun (an empty script takes 5 ms instead of 21 ms), and every validator, guard, scorer, and author suite gives the same output on both runtimes (#87). The gate now runs them as `bun