CodeTrial conventions
The gate settles formatting and correctness. cargo fmt, ESLint, ruff and
shellcheck run in scripts/test.sh, so none of that is here. What is here is
what a reviewer would otherwise have to say out loud, plus the two rules that
are enforced by the git hooks rather than by CI: the commit message and the
staged-content checks. Install them with make hooks.
README.md is the tracked half and this file does not restate it. Where the
two speak to the same thing, README.md wins.
Commit messages
The house style is Chris Beams' seven rules, and scripts/git-commit-msg.sh
enforces the mechanical ones: subject within 50 characters, capitalized,
imperative, no trailing period, no backticks, no conventional-commit prefix,
body wrapped at 72, printable ASCII throughout. Run
git log --no-merges --format=%s if you want the calibration set rather than
the claim; the recent log sits at 46 characters in the median.
The rule the hook cannot check is what the body says. This tree keeps its detailed reasoning in the comment next to the code, often several paragraphs per decision. A body that retells the mechanics duplicates that comment and then goes stale on its own. Write the premise and the trade, once, usually a single paragraph:
Bound a video frame by the memory it will take
Refuse a permission listing that may be truncated
Prove an empty test run is not a pass
Prose register
Source comments and commit messages are ASCII: no em dash, no typographic
quote, no arrow character, no CJK. Markdown files are exempt and use ordinary
GitHub Markdown, so a backtick belongs in docs/ and not in a commit subject.
Chinese, and CJK generally, stays out of src/, web/ and tests/ even when
the conversation that produced the change was in Chinese. The product's
surfaces are English. A test that genuinely needs multi-byte input to pin a
character-boundary rule should use characters that read as a fixture rather
than as prose in any language: Greek letters, accented Latin, an emoji. Say in
the comment that the byte width is the point.
Comments
Brevity is part of correctness, but the bar here is rationale rather than length: this tree carries long comments where the reasoning is long, and the rule is that every line of one says something the code cannot. Delete anything restating the statement below it.
- Bad:
let deadline = start + duration; // add the duration - Good:
let deadline = start + duration; // truncated to whole minutes above, so the page and the interviewer end at the same number
Never point a comment, a docstring or a docs/ page at TODO.md. It is
untracked and per-developer, so the reference dangles for everyone else. Code
may name the path where it genuinely reads the file, and a runtime diagnostic
may print it so the reader can navigate.
Working docs at the repo root
TODO.md, DONE.md and CLAUDE.md are excluded through .git/info/exclude,
not through .gitignore, because the exclusion is one person's habit and
.gitignore would push it onto everybody. Never git add, stage, commit or
delete them, and never assume a clone has them. Reports and analyses stay out
of the tree the same way: a scratch directory, not a new tracked file.
Deleting a surface
A subcommand, flag or helper that turns out to be redundant gets deleted,
along with its call sites in the Makefile, the scripts, the README and the
tests. It does not become an alias and it does not get a deprecation warning.
"Never break userspace" here means the compatibility contract the golden
fixtures in tests/golden/ and the versions recorded in
docs/interview-contract-versions.md describe, not the convenience surface of
the CLI.
Pull requests and review replies
The commit body carries what and why. A review thread carries a correction, a measurement or nothing: no pasted agent walkthroughs, no severity tables, no re-summarizing a diff git already shows. Close an addressed thread with "Resolve conversation".
Layout
src/ Rust server, interviewer agent, recording, token minting
web/ Static browser application, served embedded or from disk
problem-bank/ Source of truth for web/problems and web/judges
config/ Env file templates and per-provider credentials
docs/ Contracts and operational notes
scripts/ Build, gate, hook and integration scripts
tests/ Rust, Python and browser tests, plus golden fixtures
tests/unit/ Unit test bodies, compiled into src/ by `#[path]`
tests/common/ What the integration tests share
No test code under src/
src/ holds implementation. A file there may declare a test module but must not
contain one:
#[cfg(test)]
#[path = "../tests/unit/gemini.rs"]
mod tests;
Three lines, and the body lives in tests/unit/, mirroring the path under
src/. tests/unit/web/pool.rs holds what src/web/pool.rs declares.
The indirection is what keeps both halves of the promise. A test under
tests/*.rs is an integration test: it links the plain rlib and can reach only
the public API, so moving these outright would have meant publishing internals
for the tests' benefit. Declared this way they are still unit tests -- same
module path, super:: still the containing module, private and pub(crate)
items still in reach -- and none of it is visible to a dependent.
Four rules follow, and each of them cost something to learn:
- Never name a file
tests/<dir>/main.rs. Cargo reads that as an integration-test target and compiles it a second time as a crate root, where everysuper::in it fails.src/main.rsdeclarestests/unit/bin.rsfor this reason. Any other name undertests/unit/is inert to autodiscovery. include_str!andinclude_bytes!resolve against the file that writes them, so a test reading its own production source needs the path rewritten when it moves. Two such tests failed loudly on the move and two more kept passing while reading the test file instead of the source, which is worse.- A second test module in one file keeps its name:
src/accounts/schema.rsdeclarestests/unit/accounts/schema.migration_tests.rs. tests/unit/is not scanned by the gate's browser or Python lanes and has nomain.rs, so nothing under it becomes a target on its own. It compiles only because something insrc/names it.
Generated files live under web/ but are owned by problem-bank/ and by the
generators in scripts/. Editing one by hand is a drift the gate catches.
See codetrial-verify for what regenerates them and codetrial-web for how
web/ reaches a browser.