Imported from xray-lang/xray (
AGENTS.md). Install upstream withnpx skills add xray-lang/xray. Copyright stays with the author.
Repository agent gates
Build and test rules
Ninja is the only build generator, on every platform. There is no Makefiles
or Visual Studio path, and no fallback to one — a missing ninja is an error
with an install hint, never a silent switch to another generator.
- Configure locally with
cmake --preset default(Ninja + Release inbuild/). Release is deliberate: this is a compute-heavy compiler, so optimized test runs dominate the edit/build/test loop far more than the slightly longer compile. Use the sanitizer presets (Debug + assertions) for correctness passes. - Ninja is single-config. A build tree has exactly one binary location —
build/xray(build/xray.exeon Windows). Never add aRelease/orDebug/subdirectory probe, and never pass--configtocmake --build/cmake --install. - On Windows, Ninja needs
cl.exeon PATH; CI does this withilammy/msvc-dev-cmd. Everycmake -Bin.github/workflows/passes-G Ninja.
Debug info follows the build type. Release/MinSizeRel compile without
-g; RelWithDebInfo is the "optimized with symbols" build. Do not add -g
back to Release — it slows compile and link, inflates libxray_core.a roughly
4x (which is what makes the per-test static link expensive), and lowers the
ccache hit rate. Debug a release-only crash by rebuilding with RelWithDebInfo.
Diagnostics colour themselves only for a terminal. xr_diag_use_color() in
src/frontend/xdiag_fmt.h gates on isatty(stderr) plus the NO_COLOR
convention. Piped or redirected output is therefore plain text, and test
harnesses must not strip ANSI escapes — if you find yourself adding a sed
to remove colour, the gate is what needs fixing.
Test runners are parallel and order-stable. A corpus runner fans work out
with xargs -P and writes one self-contained result file per case, then reads
them back in sorted order, so the report and the tallies are identical to a
serial run. Keep them free of per-case forks on the hot path (sed, grep,
head, basename per case cost more than the work itself); bash parameter
expansion does the same job. Portable bash only — no mapfile (macOS ships
bash 3.2) — so the same script runs under Windows Git-Bash.
Heavy ctest lanes declare their cost. Any lane over ~100s carries a COST
matching its measured wall time so a cold run (no CTestCostData.txt) schedules
it first instead of stranding it at the tail. Keep PROCESSORS honest about the
cores a lane actually consumes.
The sanitizer lanes are most of a full run. tsan_focused and
asan_focused are RUN_SERIAL and each builds its own instrumented tree, so
together they are roughly two thirds of the wall time of ctest -j4; the other
~344 tests total a few minutes. ctest -LE sanitizer is the iteration loop.
Both lanes decide for themselves whether to build, by comparing the binary
against src, include, stdlib, tests, and CMakeLists.txt, so an
unchanged tree reuses the build and a changed one rebuilds without being asked.
Neither lane is optional before handing off work in the directories above.
Changes under src/ir/, src/aot/, or src/analysis/ must preserve the Task 218 compiler-memory-safety defenses:
- Run
ctest --test-dir build -R meta_ownership_inventoryafter compiler metadata changes. - Run
ctest --test-dir build -R asan_focusedbefore handing off a completed change in those directories. - Strings and metadata crossing AST, analyzer, IR, plan, evidence, or CGen stage boundaries must be copied into the receiving arena/pool or transferred explicitly. Do not retain dynamic-array element pointers across operations that can grow the array.
- The generated-C W1-W4 verifier is always on. Do not add a bypass, downgrade its ICE behavior, or hand malformed output to the host C compiler.
Generated C is portable C11 first. The AOT backend and its runtime headers must compile with supported MSVC, Clang, GCC, and Zig C providers for the selected target.
- Never emit GNU statement expressions (
({ ... })) or another provider-specific language extension on an unconditional generated-C path. - Express multi-statement value production with ordinary scoped statements or a typed runtime helper. Provider intrinsics are allowed only behind an owned, capability-gated runtime boundary with a portable implementation where the feature is part of the supported language surface.
- On Windows, automatic native-toolchain discovery prefers a ready MSVC, then Clang, then Zig. A fallback is evidence of a failed capability probe, not the normal path when MSVC is installed and ready.
- A generated-C portability change needs a regression gate that compiles real generated output with MSVC and rejects newly introduced GNU statement expressions; a synthetic SDK probe alone is insufficient evidence.
Document any platform-only LeakSanitizer suppression in scripts/lsan.supp; the suppression budget may shrink but may not grow without an explicit contract review.
Coding and commit rules
The full coding standard lives in the sibling xray-docs repository, checked
out next to this repo in the umbrella directory (../xray-docs from the
primary checkout; from a worktree under .claude/worktrees/, resolve it via
the primary tree). Read the file that matches the work:
xray-docs/rules/c-coding-standards.md— memory, visibility, assertion density, naming, size caps, the file-header template, and the comment rules.xray-docs/rules/architecture.md— the L0→L8 include DAG,XR_OS_*platform macros, native types via the prelude table, and the task-218/219 ownership and RC contracts.xray-docs/rules/dev-workflow.md— build/test/debug entry points, Windowsscripts/win_pd_test.shdiscipline, and the shared-worktree git rules (nogit stash,reset --hard, orcheckout -- <file>in a shared tree; commit with pathspecs).xray-docs/rules/design-principles.mdandxray-docs/rules/concurrency-ownership-surface.md— the language's type and sharing model. These win over any older doc that still showsshareddeclarations (removed) orany(removed).
(.devin/rules/ in this repo is the legacy Windsurf-era digest of the same
rules — its process content is mostly still right, but its xray-lang.md
predates the shared removal; on any conflict the xray-docs/rules/ files
above are authoritative.)
The rules agents break most often:
- All C comments and all commit messages are in English and self-contained.
Never reference a
.mddocument path from a source comment or a commit message, and never use phase talk (Phase A/B/C,Step 2,P0/P1,Round 2, 本次重构) — docs move and phases expire, but code history must stand alone. State the fact and the reason; long-lived design intent belongs in the doc comment of the owning function or type. - Commits carry no AI attribution or process-specific trailers. No
Co-Authored-Bytrailer naming Claude or any other tool, no tool as author or committer, and noCONTRACT-CHANGE:trailer. Keep contract rationale in the ordinary self-contained subject/body and in the governed evidence. - Allocate only through
xr_malloc/xr_free, check for NULL, and passxr_reallocresults through a temporary pointer. Non-staticfunctions carryXRAY_API/XR_FUNC; preprocessor OS checks useXR_OS_*, never_WIN32/__APPLE__/__linux__. - Bugs are zero-tolerance. Fix the root cause with a regression test now,
or report immediately and record it in
xray-docs/known_bugs.md. Never skip-and-continue, and never mask a bug with catch-and-ignore or a skipped test.
Semantic contract freeze
The machine-checked semantic contracts live in contracts/. Before changing a
listed anchor, read the owning contract and decide whether existing diff cases,
KATs, shape gates, or ports evidence must be migrated.
- Refresh every affected
anchor-sha256record in the same commit. - Do not add a dedicated contract-change trailer to the commit message.
- Record how affected evidence was rerun, regenerated, or retired. Retirement must use the governed tombstone inventory; never silently delete evidence.
- Run
ctest --test-dir build --output-on-failure -R contract_freezeafter the commit so anchors and governed evidence are checked against the final commit.
