Skip to content

Latest commit

 

History

History
174 lines (134 loc) · 7.49 KB

File metadata and controls

174 lines (134 loc) · 7.49 KB

Development Rules

RSScript development is spec-first and product-contract-driven. The goal is not to accumulate fixtures or backend experiments; it is to keep one small, deterministic compilation and execution path correct and auditable.

Implementation Discipline

  1. Implement the real prerequisite first.

    If a feature needs syntax, HIR facts, type inference, runtime support, or verifier facts that do not exist yet, build that prerequisite before the feature. Do not encode a one-off lowering shortcut, fixture-only bypass, or runtime fallback that preserves a different language model.

  2. Check equals build.

    A program rss check accepts must build, verify, and run. A lowering or verification refusal for checked code is a bug in the lowerer or the checker, never a limitation to document. fixture_build_corpus builds every pass fixture with a main; its allowlist exists only to record a gap while it is being closed, and it is empty.

  3. Facts are proofs, and unknown is honest.

    Typed executable facts, native region facts, and analysis are derived from MIR and the verifier. Where a fact cannot be proved, publish Unknown; never publish a guess. The interpreter is the oracle for every other engine: identical outcome, termination reason, and usage counts, or decline.

  4. Measure generation, do not assume it.

    The language exists to be written by models. A change that claims to help generation is measured with the eval corpus (evals/, the sample collector, and agent-eval) before and after, and the report records the counts. Syntax sugar is admitted only when it desugars to an existing AST node and a measured failure class justifies it.

  5. Prefer fewer, harder tests.

    Tiny fixtures are useful for a specific diagnostic regression. They are not enough to prove the language. Every new semantic rule gets a rejected fixture with its exact code, an accepted fixture, and, where the rule affects execution, an SDK test that builds, verifies, and runs the shape.

  6. No aliases or compatibility shims by default.

    RSScript is still pre-adoption. Prefer one canonical spelling and migrate all tests, examples, and specs to it. Do not keep legacy aliases unless there is a current external compatibility contract. Did-you-mean tables suggest a real name; they never make an invented one resolve.

  7. Keep documents synchronized with the code.

    docs/spec/RSScript_Semantics_v0.7.md states each rule with its file and diagnostic citation and every fenced example is re-run; docs/generated/ is regenerated by xtask, never edited; contract changes carry an ADR. README status sections describe implemented commands only.

Priority Order

  1. Core language invariants: named arguments, read/mut/take, local, manage, fresh, retains, resources, handles, structured async, and the check-equals-build rule that ties them to execution.
  2. The generation oracle and its measured pass rate: diagnostics with machine-applicable fixes, signatures and parameter facts at the cursor, and the eval corpus.
  3. Native tier parity: exact accounting under every armed limit, so the bounded runner can use the JIT.
  4. Semantic-fact and execution-report quality.
  5. Provider conformance and isolated-runner boundary hardening.

Testing Loop

Use the pinned toolchain and locked dependency graphs. The supported local gate mirrors Core CI:

cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked
cargo test --locked -p rsscript-sdk --features execution
cargo test --locked -p rsscript-sdk --test fixture_corpus
cargo test --locked -p rsscript-sdk --features execution --test fixture_build_corpus
cargo test --locked -p rsscript-cli --features execution
cargo run --locked -p rsscript-xtask -- validate-ci
cargo run --locked -p rsscript-xtask -- language-card --check
git diff --check

fixture_corpus checks every tests/fixtures/{pass,fail} file against its // expect: codes; fixture_build_corpus builds and verifies every pass fixture with a main. Changes that touch diagnostics or generation are also scored:

cargo run --locked -p rsscript-xtask -- agent-eval --tasks evals/tasks --candidates evals --output /tmp/agent-eval.json

The SDK has explicit integration-test targets because autotests = false. validate-ci checks workflow package, feature, and test references against Cargo metadata and rejects unregistered top-level SDK test files. A filtered test is useful only after the containing registered target has proved that the filter matches real coverage.

Providers and the runner have focused gates:

cargo test --locked -p rsscript-provider-api
cargo test --locked -p rsscript-provider-conformance
cargo test --locked -p rsscript-provider-fs
cargo test --locked -p rsscript-provider-env
cargo test --locked -p rsscript-provider-process
cargo test --locked -p rsscript-provider-http
cargo test --locked -p rsscript-runner-protocol

Rust AOT, REIR, and self-hosting research are archived on the archive/experiments-2026-09 branch and are not built or tested from this workspace.

Native JIT is an explicit trusted-host SDK mode. It is absent from default closures and requires both correctness and performance evidence:

cargo test --locked -p rsscript-sdk --features native-jit
cargo test --locked --release -p rsscript-sdk --features native-jit --test native_jit_smoke native_hot_loop_release_gate_beats_the_interpreter -- --nocapture

Fuzz targets are maintained in their own manifest:

cargo check --locked --manifest-path fuzz/Cargo.toml --bins

Avoid concurrent workspace Cargo commands because their build lock makes the feedback slower. Use a focused test during diagnosis, then return to the broad gate. Long-running or platform-specific hardening belongs in its dedicated workflow, not in an invented hidden SDK target.

Why Not Always Run One Test First?

A single focused test is useful only after a broad command has identified the failure or while editing a narrow regression. Starting every change with a single test costs extra tokens and time because it still needs a full test pass later. The default flow should be:

broad quiet test -> inspect failure -> focused loop -> broad quiet test -> full gate

This keeps the evidence strong while avoiding repeated command output.

Containerized development

A single Docker toolchain image builds and tests the workspace identically on macOS, Windows, and Linux. It carries the Rust toolchain, cargo-nextest, and the C build dependencies; the checkout is bind-mounted at /work, so host edits take effect immediately. Only Docker with Compose v2 is required locally.

docker compose build                                   # first run downloads the toolchain
docker compose run --rm dev cargo test --workspace     # the broad gate
docker compose run --rm dev cargo run -p rsscript-cli --bin rss --features execution -- check examples/scripts/basic/hello.rss
docker compose run --rm dev bash                       # interactive shell

Every command in the testing loop above works unchanged inside the container. target, the Cargo registry, and Cargo git checkouts live in named volumes so compilation persists between runs; docker compose down -v resets them. The image tracks rust:1-bookworm; pin a concrete tag in Dockerfile for a fully reproducible toolchain. .devcontainer/devcontainer.json reuses the same service for VS Code and Codespaces, and the image builds natively on amd64 and arm64.