.omo/rules/test-discipline.md
Every test in this repo MUST pass bun test in one process, in one go - no isolation flags, no retries, no special ordering. That is the gate. A test that needs --only, its own process, or a specific run order to pass is BROKEN. Fix the test; do not pamper it.
A test that passes 9 of 10 times is failing 10% of the time. Not "occasional." BROKEN.
FORBIDDEN in test bodies unless time itself is the system under test (Date.now, real timers, debounce/throttle windows):
setTimeout(resolve, N) / await new Promise(r => setTimeout(r, N)) / await sleep(N)The replacement: subscribe BEFORE the trigger, await the signal with an explicit timeout.
When code under test emits an event, fires a callback, or resolves a promise:
"waited 5s for event 'X', never fired"). NEVER silently retry, NEVER fall through.Tests must work under arbitrary parallel ordering in a single bun test run, no matter how many mocks are involved.
FORBIDDEN:
.only / .skip to mask a flaky testscript/run-ci-tests.ts already auto-isolates files that use mock.module() - DO NOT add to that list to cover up a real cross-test bugdescribe / it blocks to mask cross-test contaminationCross-test contamination = state leak. Find the leak. Reset in beforeEach, add the reset to test-setup.ts if it is shared, or mock at the module boundary (mock.module) instead of mutating globals other tests will read.
A prompt, skill (SKILL.md), rule, or any markdown/instruction file is PROSE. Its wording is not a contract; the model reads it, a human edits it, it changes every sprint. DO NOT write a test that asserts what the prose SAYS.
BANNED - every one of these guards a diff, not behavior:
expect(prompt).toContain("You are Sisyphus") // phrase-present pin
expect(skill).not.toContain("old wording") // phrase-absent / past-wording guard
expect(prompt).toMatchSnapshot() // snapshot of prose
expect(prompt).toBe(EXPECTED_PROMPT) // full-text pin
expect(wordCount(workflow)).toBeLessThanOrEqual(3930) // word / char / LOC ceiling
expect(md.match(/some phrase/g)?.length).toBe(1) // phrase-occurrence count
The wording changes, the test fails, and the next engineer edits the assertion to match the new text without understanding what it guarded. The test guarded nothing — worse, it now BLOCKS every legitimate prompt edit until someone bumps the pinned string or number.
Decide by what CONSUMES the file:
expect(packaged).toBe(source)), never a list of phrase greps.REQUIRED when a behavioral seam exists - assert the conditional the code enforces:
teamMode.enabled === true, the builder MUST include the team_send_message tool" -> test the branch, keying on a stable token the runtime also uses, not a sentenceverbose === false, the debug directive MUST be absent" -> test the negative branchTest what would break the behavior. Never test what would only break a diff.