evals: kill the 3,372-line zombie monolith; revive 4 never-run tests

test/skill-e2e.test.ts survived the v1.56 split as a zombie: the paid
glob needs the skill-e2e-* hyphen, so with EVALS=1 NOTHING has executed
it for ~8 releases — and it held the ONLY implementations of four
map-registered tests: review-coverage-audit (gate), plan-eng-coverage-
audit (gate), ship-triage (gate), ship-idempotency (periodic). Three
gate tests silently never ran — the exact 0%-execution class this
branch exists to kill.

Rehomed into test/skill-e2e-coverage-audit.test.ts, -triage.test.ts, and
-ship-idempotency-sdk.test.ts with bodies byte-identical modulo collector
wiring and fixture extraction (drift observed in the skills since v1.56
is DOCUMENTED in each header, not fixed — their first paid run in 8
releases must attribute failures to drift, not to this move). All 24
other monolith names were true duplicates of the split files — dropped
with the monolith. Matrix rows added to both eval workflows; the paid
glob's zombie-exclusion is now a commented regression pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-15 09:03:16 -07:00
parent 9b8f8d7767
commit 1e0ff1e1e2
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
11 changed files with 599 additions and 3381 deletions

View File

@ -86,11 +86,16 @@ jobs:
file: test/skill-e2e-review.test.ts
- name: e2e-retro
file: test/skill-e2e-retro.test.ts
# e2e-review-attribution is gate-only (all three of its tests are
# gate-tier) — deliberately absent here; an all-skip shard would just
# burn a container boot weekly.
# e2e-review-attribution, e2e-coverage-audit, and e2e-triage are
# gate-only (every test they hold is gate-tier) — deliberately absent
# here; an all-skip shard would just burn a container boot weekly.
- name: e2e-workflow
file: test/skill-e2e-workflow.test.ts
# Rehomed from the deleted pre-split monolith: the SDK-harness
# ship-idempotency check (periodic; the real-PTY variant runs via
# the periodic sharded runner).
- name: e2e-ship-idempotency-sdk
file: test/skill-e2e-ship-idempotency-sdk.test.ts
- name: e2e-routing
file: test/skill-routing-e2e.test.ts
- name: e2e-codex

View File

@ -112,6 +112,14 @@ jobs:
file: test/skill-e2e-review-attribution.test.ts
- name: e2e-workflow
file: test/skill-e2e-workflow.test.ts
# Rehomed from the deleted pre-split monolith (its filename never
# matched the skill-e2e-* glob, so these gate tests silently never
# ran). Both files hold gate-tier tests: review/plan-eng coverage
# audits and the /ship failure-ownership triage.
- name: e2e-coverage-audit
file: test/skill-e2e-coverage-audit.test.ts
- name: e2e-triage
file: test/skill-e2e-triage.test.ts
- name: e2e-routing
file: test/skill-routing-e2e.test.ts
- name: e2e-codex

View File

@ -3,7 +3,7 @@
*
* Spawns `codex exec` with skills installed in a temp HOME, parses JSONL
* output, and validates structured results. Follows the same pattern as
* skill-e2e.test.ts but adapted for Codex CLI.
* the skill-e2e-*.test.ts suites but adapted for Codex CLI.
*
* Prerequisites:
* - `codex` binary installed (npm install -g @openai/codex)

View File

@ -1,8 +1,8 @@
/**
* Shared helpers for E2E test files.
*
* Extracted from the monolithic skill-e2e.test.ts to support splitting
* tests across multiple files by category.
* Extracted from the (since-deleted) pre-split monolith to support
* splitting tests across multiple skill-e2e-*.test.ts files by category.
*/
import '../../lib/conductor-env-shim';

View File

@ -34,6 +34,9 @@ describe('paid test enumeration', () => {
expect(isPaidTestFile('test/codex-e2e.test.ts')).toBe(true);
expect(isPaidTestFile('test/skill-e2e-triage-audit.test.ts')).toBe(true);
// Outside the globs: no dash, extra suffix, or a free test.
// 'test/skill-e2e.test.ts' is the DELETED pre-split monolith's name,
// kept here as a regression pin: its glob-invisibility is exactly how
// two gate tests went unexecuted for ~8 releases before the rehoming.
expect(isPaidTestFile('test/skill-e2e.test.ts')).toBe(false);
expect(isPaidTestFile('test/codex-e2e-recommendation-substance.test.ts')).toBe(false);
expect(isPaidTestFile('test/paid-shards.test.ts')).toBe(false);

View File

@ -0,0 +1,191 @@
/**
* Coverage-audit E2E /review and /plan-eng-review coverage-diagram flows.
*
* Rehomed VERBATIM from the pre-split monolith (test/skill-e2e.test.ts,
* deleted on this branch): the monolith's filename never matched the paid
* glob (`test/skill-e2e-*.test.ts` note the hyphen), so these two GATE-tier
* tests (`review-coverage-audit`, `plan-eng-coverage-audit` in E2E_TIERS)
* silently never executed after the v1.56 split.
*
* DRIFT WARNING (attribution for the first paid run after rehoming): the
* prompts reference "Step 4.75 (Test Coverage Diagram)" in review/SKILL.md
* and a "Test Coverage Audit" section in plan-eng-review/SKILL.md. NEITHER
* section exists in the current generated skills the skills drifted while
* these tests were zombies. Test bodies are copied faithfully (no behavioral
* edits), so a failure here indicts the ~8 releases of drift, not the move.
* The only change vs the monolith bodies: the staged SKILL.md fixtures are
* extracted via test/helpers/skill-fixture.ts (extractSkillBody full
* skill-specific body, shared preamble dropped) per CLAUDE.md
* "E2E test fixtures: extract, don't copy".
*/
import { test, expect, beforeAll, afterAll } from 'bun:test';
import { runSkillTest } from './helpers/session-runner';
import {
ROOT, runId,
describeIfSelected,
copyDirSync, logCost, recordE2E,
createEvalCollector, finalizeEvalCollector,
} from './helpers/e2e-helpers';
import { extractSkillBody } from './helpers/skill-fixture';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const evalCollector = createEvalCollector('e2e-coverage-audit');
// --- Review Coverage Audit E2E ---
describeIfSelected('Review Coverage Audit E2E', ['review-coverage-audit'], () => {
let reviewCoverageDir: string;
beforeAll(() => {
reviewCoverageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-review-coverage-'));
// Copy review skill files, then replace the SKILL.md with the extracted
// skill body (extract, don't copy — the checklists/specialists in the
// dir are small hand-written files and stay whole).
copyDirSync(path.join(ROOT, 'review'), path.join(reviewCoverageDir, 'review'));
fs.writeFileSync(
path.join(reviewCoverageDir, 'review', 'SKILL.md'),
extractSkillBody(path.join(ROOT, 'review')),
);
// Use shared fixture for billing project with coverage gaps
const { createCoverageAuditFixture } = require('./fixtures/coverage-audit-fixture');
createCoverageAuditFixture(reviewCoverageDir);
});
afterAll(() => {
try { fs.rmSync(reviewCoverageDir, { recursive: true, force: true }); } catch {}
});
test('/review Step 4.75 produces coverage diagram', async () => {
const result = await runSkillTest({
prompt: `Read the file review/SKILL.md for the review workflow instructions.
You are on the feature/billing branch. The base branch is main.
This is a test project there is no remote, no PR to create.
ONLY run Step 4.75 (Test Coverage Diagram) from the review workflow.
Skip all other steps (scope drift, checklist, design review, fix-first, etc.).
The source code is in ${reviewCoverageDir}/src/billing.ts.
Existing tests are in ${reviewCoverageDir}/test/billing.test.ts.
Produce the ASCII coverage diagram showing which code paths are tested and which have gaps.
Output the diagram directly.`,
workingDirectory: reviewCoverageDir,
maxTurns: 15,
allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep'],
timeout: 120_000,
testName: 'review-coverage-audit',
runId,
});
logCost('/review coverage audit', result);
recordE2E(evalCollector, '/review Step 4.75 coverage audit', 'Review Coverage Audit E2E', result, {
passed: result.exitReason === 'success',
});
expect(result.exitReason).toBe('success');
// Check output contains coverage diagram elements
const output = result.output || '';
const outputLower = output.toLowerCase();
const hasGap = outputLower.includes('gap') || outputLower.includes('no test');
const hasTested = outputLower.includes('tested') || output.includes('✓') || output.includes('★');
const hasCoverage = outputLower.includes('coverage') || outputLower.includes('paths tested');
console.log(`Output has GAP markers: ${hasGap}`);
console.log(`Output has TESTED markers: ${hasTested}`);
console.log(`Output has coverage summary: ${hasCoverage}`);
// The agent MUST produce a coverage diagram with gap and tested markers
expect(hasGap || hasTested).toBe(true);
// At minimum, the agent should have read the source and test files
const readCalls = result.toolCalls.filter(tc => tc.tool === 'Read');
expect(readCalls.length).toBeGreaterThan(0);
}, 180_000);
});
// --- Plan Eng Review Coverage Audit E2E ---
describeIfSelected('Plan Eng Review Coverage Audit E2E', ['plan-eng-coverage-audit'], () => {
let planCoverageDir: string;
beforeAll(() => {
planCoverageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-plan-coverage-'));
// Copy plan-eng-review skill files, then replace the SKILL.md with the
// extracted skill body (extract, don't copy).
copyDirSync(path.join(ROOT, 'plan-eng-review'), path.join(planCoverageDir, 'plan-eng-review'));
fs.writeFileSync(
path.join(planCoverageDir, 'plan-eng-review', 'SKILL.md'),
extractSkillBody(path.join(ROOT, 'plan-eng-review')),
);
// Use shared fixture for billing project with coverage gaps
const { createCoverageAuditFixture } = require('./fixtures/coverage-audit-fixture');
createCoverageAuditFixture(planCoverageDir);
});
afterAll(() => {
try { fs.rmSync(planCoverageDir, { recursive: true, force: true }); } catch {}
});
test('/plan-eng-review coverage audit traces plan codepaths', async () => {
const result = await runSkillTest({
prompt: `Read the file plan-eng-review/SKILL.md for the plan review workflow instructions.
You are on the feature/billing branch. The base branch is main.
This is a test project there is no remote, no PR to create.
ONLY run the Test Coverage Audit section from the plan review workflow.
Skip all other steps (architecture, code quality, performance, etc.).
The source code is in ${planCoverageDir}/src/billing.ts.
Existing tests are in ${planCoverageDir}/test/billing.test.ts.
Produce the ASCII coverage diagram showing which code paths are tested and which have gaps.
Output the diagram directly.`,
workingDirectory: planCoverageDir,
maxTurns: 15,
allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep'],
timeout: 120_000,
testName: 'plan-eng-coverage-audit',
runId,
});
logCost('/plan-eng-review coverage audit', result);
recordE2E(evalCollector, '/plan-eng-review coverage audit', 'Plan Eng Review Coverage Audit E2E', result, {
passed: result.exitReason === 'success',
});
expect(result.exitReason).toBe('success');
// Check output contains coverage diagram elements
const output = result.output || '';
const outputLower = output.toLowerCase();
const hasGap = outputLower.includes('gap') || outputLower.includes('no test');
const hasTested = outputLower.includes('tested') || output.includes('✓') || output.includes('★');
const hasCoverage = outputLower.includes('coverage') || outputLower.includes('paths tested');
console.log(`Output has GAP markers: ${hasGap}`);
console.log(`Output has TESTED markers: ${hasTested}`);
console.log(`Output has coverage summary: ${hasCoverage}`);
// The agent MUST produce a coverage diagram with gap and tested markers
expect(hasGap || hasTested).toBe(true);
// At minimum, the agent should have read the source and test files
const readCalls = result.toolCalls.filter(tc => tc.tool === 'Read');
expect(readCalls.length).toBeGreaterThan(0);
}, 180_000);
});
// Module-level afterAll — finalize eval collector after all tests complete
afterAll(async () => {
await finalizeEvalCollector(evalCollector);
});

View File

@ -0,0 +1,139 @@
/**
* /ship idempotency guard E2E SDK-harness variant (#649).
*
* Rehomed VERBATIM from the pre-split monolith (test/skill-e2e.test.ts,
* deleted on this branch): the monolith's filename never matched the paid
* glob (`test/skill-e2e-*.test.ts` note the hyphen), so this periodic
* test (`ship-idempotency` in E2E_TIERS) silently never executed after
* the v1.56 split. The real-PTY variant lives in
* test/skill-e2e-ship-idempotency.test.ts (`ship-idempotency-pty`) and
* exercises the actual /ship skill end-to-end; this one is the synthetic
* SDK-harness check the PTY variant's header contrasts itself against.
*
* DRIFT WARNING (attribution for the first paid run after rehoming): the
* fixture slices ship/SKILL.md on the markers '## Step 4: Version bump',
* '## Step 7: Push', and '## Step 8.5'. The current generated skill numbers
* these Step 12 (Version bump) and Step 17 (Push) every indexOf returns
* -1 and ship-steps.md ends up essentially empty. The body is copied
* faithfully (no behavioral edits, per the rehoming integrity rule), so a
* failure here indicts the ~8 releases of drift, not the move. The fix
* (repoint the markers or use test/helpers/skill-fixture.ts with the
* current section names) is a deliberate follow-up, not smuggled into the
* rehoming commit.
*/
import { expect, beforeAll, afterAll } from 'bun:test';
import { runSkillTest } from './helpers/session-runner';
import {
ROOT, runId,
describeIfSelected, testConcurrentIfSelected,
logCost, recordE2E,
createEvalCollector, finalizeEvalCollector,
} from './helpers/e2e-helpers';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const evalCollector = createEvalCollector('e2e-ship-idempotency-sdk');
// --- Ship idempotency (#649) ---
describeIfSelected('Ship idempotency', ['ship-idempotency'], () => {
let idempDir: string;
const gitRun = (args: string[], cwd: string) =>
spawnSync('git', args, { cwd, stdio: 'pipe', timeout: 5000 });
beforeAll(() => {
idempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-ship-idemp-'));
// Create git repo with initial commit on main
gitRun(['init', '-b', 'main'], idempDir);
gitRun(['config', 'user.email', 'test@test.com'], idempDir);
gitRun(['config', 'user.name', 'Test'], idempDir);
fs.writeFileSync(path.join(idempDir, 'app.ts'), 'console.log("v1");\n');
fs.writeFileSync(path.join(idempDir, 'VERSION'), '0.1.0.0\n');
fs.writeFileSync(path.join(idempDir, 'CHANGELOG.md'), '# Changelog\n');
gitRun(['add', '.'], idempDir);
gitRun(['commit', '-m', 'initial'], idempDir);
// Create feature branch with changes
gitRun(['checkout', '-b', 'feat/my-feature'], idempDir);
fs.writeFileSync(path.join(idempDir, 'app.ts'), 'console.log("v2");\n');
gitRun(['add', 'app.ts'], idempDir);
gitRun(['commit', '-m', 'feat: update to v2'], idempDir);
// Simulate prior /ship run: bump VERSION and write CHANGELOG entry
fs.writeFileSync(path.join(idempDir, 'VERSION'), '0.2.0.0\n');
fs.writeFileSync(path.join(idempDir, 'CHANGELOG.md'),
'# Changelog\n\n## [0.2.0.0] — 2026-03-30\n\n- Updated app to v2\n');
gitRun(['add', 'VERSION', 'CHANGELOG.md'], idempDir);
gitRun(['commit', '-m', 'chore: bump version to 0.2.0.0'], idempDir);
// Extract just the idempotency-relevant sections from ship/SKILL.md
const full = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
const step4Start = full.indexOf('## Step 4: Version bump');
const step4End = full.indexOf('\n---\n', step4Start);
const step7Start = full.indexOf('## Step 7: Push');
const step8End = full.indexOf('## Step 8.5');
const extracted = [
full.slice(step4Start, step4End > step4Start ? step4End : step4Start + 500),
full.slice(step7Start, step8End > step7Start ? step8End : step7Start + 500),
].join('\n\n---\n\n');
fs.writeFileSync(path.join(idempDir, 'ship-steps.md'), extracted);
});
afterAll(() => {
try { fs.rmSync(idempDir, { recursive: true, force: true }); } catch {}
});
testConcurrentIfSelected('ship-idempotency', async () => {
const result = await runSkillTest({
prompt: `You are in a git repo on branch feat/my-feature. A prior /ship run already:
- Bumped VERSION from 0.1.0.0 to 0.2.0.0
- Wrote a CHANGELOG entry for 0.2.0.0
- But the push/PR step failed
Read ship-steps.md for the idempotency check instructions from the ship workflow.
Run ONLY the idempotency checks described in Steps 4 and 7. Do NOT actually push or create PRs (there is no remote).
After running the checks, write a report to ${idempDir}/idemp-result.md containing:
- Whether VERSION was detected as ALREADY_BUMPED or not
- Whether the push was detected as ALREADY_PUSHED or PUSH_NEEDED
- The current VERSION value (should still be 0.2.0.0)
Do NOT modify VERSION or CHANGELOG. Only run the detection checks and report.`,
workingDirectory: idempDir,
maxTurns: 10,
timeout: 60_000,
testName: 'ship-idempotency',
runId,
});
logCost('/ship idempotency', result);
recordE2E(evalCollector, '/ship idempotency guard', 'Ship idempotency', result);
expect(result.exitReason).toBe('success');
// Verify VERSION was NOT modified
const version = fs.readFileSync(path.join(idempDir, 'VERSION'), 'utf-8').trim();
expect(version).toBe('0.2.0.0');
// Verify CHANGELOG was NOT duplicated
const changelog = fs.readFileSync(path.join(idempDir, 'CHANGELOG.md'), 'utf-8');
const versionEntries = (changelog.match(/## \[0\.2\.0\.0\]/g) || []).length;
expect(versionEntries).toBe(1);
// Check the result report if it was written
const reportPath = path.join(idempDir, 'idemp-result.md');
if (fs.existsSync(reportPath)) {
const report = fs.readFileSync(reportPath, 'utf-8');
expect(report.toLowerCase()).toContain('already_bumped');
}
}, 120_000);
});
// Module-level afterAll — finalize eval collector after all tests complete
afterAll(async () => {
await finalizeEvalCollector(evalCollector);
});

View File

@ -11,8 +11,9 @@
* 4. Does NOT append a duplicate CHANGELOG [0.0.2] entry
* 5. Does NOT create a new "chore: bump version" commit
*
* Why real-PTY: the existing ship-idempotency test in skill-e2e.test.ts
* uses the SDK harness with a synthetic prompt asking the agent to "run
* Why real-PTY: the ship-idempotency test in
* test/skill-e2e-ship-idempotency-sdk.test.ts uses the SDK harness with a
* synthetic prompt asking the agent to "run
* ONLY the idempotency checks." This test exercises the actual /ship
* skill end-to-end against a real git fixture so a regression that
* silently re-bumps despite the check passing would be caught.

View File

@ -0,0 +1,238 @@
/**
* /ship test-failure ownership triage E2E.
*
* Rehomed VERBATIM from the pre-split monolith (test/skill-e2e.test.ts,
* deleted on this branch): the monolith's filename never matched the paid
* glob (`test/skill-e2e-*.test.ts` note the hyphen), so this GATE-tier
* test (`ship-triage` in E2E_TIERS) silently never executed after the
* v1.56 split.
*
* DRIFT WARNING (attribution for the first paid run after rehoming): the
* prompt references "Test Failure Ownership Triage (Steps T1-T4)" no
* such section exists in the current generated ship/SKILL.md (the skill
* drifted while this test was a zombie). The body is copied faithfully
* (no behavioral edits), so a failure here indicts the drift, not the
* move. The only change vs the monolith body: the staged ship/SKILL.md is
* extracted via test/helpers/skill-fixture.ts (extractSkillBody full
* skill-specific body, shared preamble dropped) per CLAUDE.md
* "E2E test fixtures: extract, don't copy".
*/
import { test, expect, beforeAll, afterAll } from 'bun:test';
import { runSkillTest } from './helpers/session-runner';
import {
ROOT, runId,
describeIfSelected,
copyDirSync, logCost, recordE2E,
createEvalCollector, finalizeEvalCollector,
} from './helpers/e2e-helpers';
import { extractSkillBody } from './helpers/skill-fixture';
import { spawnSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const evalCollector = createEvalCollector('e2e-triage');
// --- Triage E2E ---
describeIfSelected('Test Failure Triage E2E', ['ship-triage'], () => {
let triageDir: string;
beforeAll(() => {
triageDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-e2e-triage-'));
// Copy ship skill files, then replace the SKILL.md with the extracted
// skill body (extract, don't copy).
copyDirSync(path.join(ROOT, 'ship'), path.join(triageDir, 'ship'));
fs.writeFileSync(
path.join(triageDir, 'ship', 'SKILL.md'),
extractSkillBody(path.join(ROOT, 'ship')),
);
const run = (cmd: string, args: string[]) =>
spawnSync(cmd, args, { cwd: triageDir, stdio: 'pipe', timeout: 5000 });
// Init git repo
run('git', ['init', '-b', 'main']);
run('git', ['config', 'user.email', 'test@test.com']);
run('git', ['config', 'user.name', 'Test']);
// Create a project with a pre-existing test failure on main
fs.writeFileSync(path.join(triageDir, 'package.json'), JSON.stringify({
name: 'triage-test-app',
version: '1.0.0',
scripts: { test: 'node test/run.js' },
}, null, 2));
fs.mkdirSync(path.join(triageDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(triageDir, 'test'), { recursive: true });
// Source with a bug that exists on main (pre-existing)
fs.writeFileSync(path.join(triageDir, 'src', 'math.js'), `
module.exports = {
add: (a, b) => a + b,
divide: (a, b) => a / b, // BUG: no zero-division check (pre-existing)
};
`);
// Test file that catches the pre-existing bug
fs.writeFileSync(path.join(triageDir, 'test', 'math.test.js'), `
const { add, divide } = require('../src/math');
// This test passes
if (add(2, 3) !== 5) { console.error('FAIL: add(2,3) should be 5'); process.exit(1); }
console.log('PASS: add');
// This test FAILS — pre-existing bug (divide by zero returns Infinity, not an error)
try {
const result = divide(10, 0);
if (result === Infinity) { console.error('FAIL: divide(10,0) should throw, got Infinity'); process.exit(1); }
} catch(e) {
console.log('PASS: divide zero check');
}
`);
// Test runner — each test in a subprocess so one failure doesn't kill the other
fs.writeFileSync(path.join(triageDir, 'test', 'run.js'), `
const { execSync } = require('child_process');
const path = require('path');
let failures = 0;
for (const f of ['math.test.js', 'string.test.js']) {
try {
execSync('node ' + path.join(__dirname, f), { stdio: 'inherit' });
} catch (e) {
failures++;
}
}
if (failures > 0) process.exit(1);
`);
// Commit on main with the pre-existing bug
run('git', ['add', '.']);
run('git', ['commit', '-m', 'initial: math utils with tests']);
// Create feature branch
run('git', ['checkout', '-b', 'feature/string-utils']);
// Add new code with a new bug (in-branch)
fs.writeFileSync(path.join(triageDir, 'src', 'string.js'), `
module.exports = {
capitalize: (s) => s.charAt(0).toUpperCase() + s.slice(1),
reverse: (s) => s.split('').reverse().join(''),
truncate: (s, len) => s.substring(0, len), // BUG: no null check (in-branch)
};
`);
// Add test that catches the in-branch bug
fs.writeFileSync(path.join(triageDir, 'test', 'string.test.js'), `
const { capitalize, reverse, truncate } = require('../src/string');
if (capitalize('hello') !== 'Hello') { console.error('FAIL: capitalize'); process.exit(1); }
console.log('PASS: capitalize');
if (reverse('abc') !== 'cba') { console.error('FAIL: reverse'); process.exit(1); }
console.log('PASS: reverse');
// This test FAILS — in-branch bug (null input causes TypeError)
try {
truncate(null, 5);
console.log('PASS: truncate null');
} catch(e) {
console.error('FAIL: truncate(null, 5) threw: ' + e.message);
process.exit(1);
}
`);
run('git', ['add', '.']);
run('git', ['commit', '-m', 'feat: add string utilities']);
});
afterAll(() => {
try { fs.rmSync(triageDir, { recursive: true, force: true }); } catch {}
});
test('/ship triage correctly classifies in-branch vs pre-existing failures', async () => {
const result = await runSkillTest({
prompt: `Read the file ship/SKILL.md for the ship workflow instructions.
You are on the feature/string-utils branch. The base branch is main.
This is a test project there is no remote, no PR to create.
Run the tests first:
\`\`\`bash
cd ${triageDir} && node test/run.js
\`\`\`
The tests will fail. Now run ONLY the Test Failure Ownership Triage (Steps T1-T4) from the ship workflow.
For each failing test, classify it as:
- **In-branch**: caused by changes on this branch (feature/string-utils)
- **Pre-existing**: existed before this branch (present on main)
Use git diff origin/main...HEAD (or git diff main...HEAD since there's no remote) to determine which files changed on this branch.
Output your classification for each failure clearly, labeling each as "IN-BRANCH" or "PRE-EXISTING" with your reasoning.
This is a solo repo (REPO_MODE=solo). For pre-existing failures, recommend fixing now.`,
workingDirectory: triageDir,
maxTurns: 20,
allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep'],
timeout: 180_000,
testName: 'ship-triage',
runId,
});
logCost('/ship triage', result);
const output = result.output || '';
const outputLower = output.toLowerCase();
// The triage should identify the string/truncate failure as in-branch
const hasInBranch = outputLower.includes('in-branch') || outputLower.includes('in branch') || outputLower.includes('introduced');
// The triage should identify the math/divide failure as pre-existing
const hasPreExisting = outputLower.includes('pre-existing') || outputLower.includes('pre existing') || outputLower.includes('existed before');
console.log(`Output identifies IN-BRANCH failures: ${hasInBranch}`);
console.log(`Output identifies PRE-EXISTING failures: ${hasPreExisting}`);
// Check that the string/truncate bug is classified as in-branch
const mentionsTruncate = outputLower.includes('truncate') || outputLower.includes('string');
const mentionsDivide = outputLower.includes('divide') || outputLower.includes('math');
console.log(`Mentions truncate/string (in-branch bug): ${mentionsTruncate}`);
console.log(`Mentions divide/math (pre-existing bug): ${mentionsDivide}`);
// Verify BOTH failure classes are exercised (not just detected):
// The test runner must have actually run both test files
const ranMathTest = output.includes('math.test') || output.includes('FAIL: divide');
const ranStringTest = output.includes('string.test') || output.includes('FAIL: truncate');
console.log(`Ran math test file (pre-existing failure): ${ranMathTest}`);
console.log(`Ran string test file (in-branch failure): ${ranStringTest}`);
recordE2E(evalCollector, '/ship triage', 'Test Failure Triage E2E', result, {
passed: result.exitReason === 'success' && hasInBranch && hasPreExisting,
has_in_branch_classification: hasInBranch,
has_pre_existing_classification: hasPreExisting,
mentions_truncate: mentionsTruncate,
mentions_divide: mentionsDivide,
ran_both_test_files: ranMathTest && ranStringTest,
});
expect(result.exitReason).toBe('success');
// Must classify at least one failure as in-branch AND one as pre-existing
expect(hasInBranch).toBe(true);
expect(hasPreExisting).toBe(true);
// Must mention the specific bugs
expect(mentionsTruncate).toBe(true);
expect(mentionsDivide).toBe(true);
// Must have actually run both test files (exercises both failure classes)
expect(ranMathTest).toBe(true);
expect(ranStringTest).toBe(true);
}, 240_000);
});
// Module-level afterAll — finalize eval collector after all tests complete
afterAll(async () => {
await finalizeEvalCollector(evalCollector);
});

File diff suppressed because it is too large Load Diff

View File

@ -200,7 +200,12 @@ describe('real-skill pins: section lists used by E2E fixtures', () => {
});
describe('real-skill pins: body/head extraction used by E2E fixtures', () => {
const BODY_EXTRACTED_SKILLS = ['scrape', 'skillify', 'context-save', 'context-restore'];
// scrape/skillify/context-*: skill-e2e-skillify + skill-e2e-context-skills.
// review/plan-eng-review/ship: skill-e2e-coverage-audit + skill-e2e-triage.
const BODY_EXTRACTED_SKILLS = [
'scrape', 'skillify', 'context-save', 'context-restore',
'review', 'plan-eng-review', 'ship',
];
for (const skill of BODY_EXTRACTED_SKILLS) {
test(`extractSkillBody(${skill}) drops the shared preamble, keeps the flow`, () => {