Contributing
Scopwis treats code, machine contracts, tests, and documentation as one change surface. User-visible behavior should not land with stale docs or an unverified protocol example.
Setup
git clone https://github.com/scopwis/scopwis.git
cd scopwis
cp .env.example .env
make setup
make doctor
Run the local stack with:
make dev
Use make dev-web when you only need the browser client.
Isolated backend smoke
make backend-smoke builds and starts the API with a fresh temporary data root, public-data loading disabled, and authentication disabled. Readiness requires the launched child process to remain alive and emit its own listening log before its bootstrap endpoint responds; a server already occupying the smoke port cannot satisfy this check.
Success also requires that child to handle SIGTERM and return a successful wait result. Cleanup gives it a 10-second external drain budget. A forced kill, nonzero child exit, or startup failure makes the smoke fail and retains the temporary root and log paths for investigation. The 10-second budget is a smoke acceptance limit, not a guarantee that every production blocking operation can finish within it.
This checks isolated startup and normal shutdown, not complete Agent cancellation or persistence acceptance. make backend-workspace-smoke is different: it starts against the configured persistent root and can perform startup migration, recovery, and source-state writes. Do not use it as a read-only check of live or irreplaceable workspace data.
Isolated Agent lifecycle acceptance
On a POSIX host, build the current binary and run the hermetic process fixture:
cargo build --locked -p scopwis-server --bin scopwis-server
python3 -B crates/scopwis-server/tests/runtime_process_smoke_tests.py --binary target/debug/scopwis-server
python3 -B crates/scopwis-server/tests/runtime_process_smoke.py --binary target/debug/scopwis-server
python3 -B crates/scopwis-server/tests/runtime_process_smoke.py --binary target/debug/scopwis-server --report
This uses only a fresh temporary root, a controlled empty .env, and a loopback blocking model fixture; it does not inherit HOME, credentials, or proxy settings. It verifies task cancellation, model-transport closure, SIGTERM exit, terminal SSE followed by EOF, exact durable snapshots, and same-root restart without model replay. Forced termination fails acceptance. Logs and fixture data are retained at the printed evidence path even after success.
Run with assertions enabled: -O, -OO, and nonzero PYTHONOPTIMIZE are rejected before temporary files, sockets, or child processes are created. SSE has a 30-second absolute lifetime and bounded bytes, events, and snapshots; model request sockets have a timeout before any header or body is read. Cleanup uses a shared 5-second thread deadline, including registry lock acquisition, explicitly closes owned connections, and checks that the reader, deadline timer, accept loop, and model handlers have all exited before emitting passed. Any cleanup deadline or thread failure fails acceptance and retains evidence; it never relies on an unbounded server_close() join.
The --report mode uses a deterministic loopback model and three temporary CSV rows. The actual server registers the source, queries it through OpenDAL/DataFusion, validates analysis references, and saves/reopens a report. It checks bound-field public projection, a frozen share after a private revision update, stale-revision rejection, same-root restart, and revocation surviving another restart. Completed model calls are frozen and compared after handler cleanup; the helper suite injects a real late HTTP call to verify this guard. Pass --binary to run all three helper tests; omitting it skips that process regression.
This is a server lifecycle check with a fake model, not live-provider, browser, sensitive-data, or six-platform UI acceptance. The fixture's 30-second shutdown deadline is an external test budget, not a production drain guarantee.
Task mutation wait-state regression
cargo test --locked -p scopwis-server --lib serialized_mutation_future_size_is_independent_of_captured_payload -- --nocapture
This compares the inline size of a transaction Future with a 32 KiB captured array against a minimal capture and checks the exact result, single execution, and guard ownership. The transaction entry heap-owns one work unit before async gate waits. This measures retained wait-state layout, not total process RSS, transient stack use, or throughput; run the lifecycle acceptance above to verify cancellation and shutdown behavior.
Before opening a change
At minimum:
cargo fmt --all --check
make lint
cargo test --locked --workspace
git diff --check
Run Flutter analyze/tests for UI changes and the website build for public documentation changes.
Documentation rules
Public documentation lives under:
docs/public/en/
docs/public/zh-Hans/
The two locale trees must contain the same document keys. The website build fails when a public page exists in only one supported locale.
Normative implementation contracts remain under docs/specs/; design/architecture records remain under docs/design/. Do not put internal research notes into the public navigation.
Website build
bash scripts/build-cloudflare-website.sh
This produces site-dist with the repository-owned Rust renderer. No Node package graph is required for the site runtime.
Keep boundaries explicit
When changing Agent, query, mining, report, capability, auth, or release behavior, update the corresponding Rust tests and machine/schema examples with the code. Avoid adding compatibility behavior in UI prose or model prompts when it belongs in deterministic policy.