Surge is a Cargo workspace plus a .NET wrapper:
crates/surge-core/: update engine (storage, releases, diff, pack, update, supervisor).crates/surge-cli/:surgeCLI.crates/surge-ffi/: C ABI used by native/.NET callers.crates/surge-installer/: console installer launcher (extracts payload, delegates tosurge setup).crates/surge-installer-ui/: GUI installer with egui (self-contained graphical installer).crates/surge-supervisor/: supervisor binary.dotnet/: managed wrapper and tests (Surge.NET,Surge.NET.Tests).include/surge/: public C headers.vendor/bsdiff/: required submodule for C bsdiff backend.assets/,demoapp/: examples and fixtures.
Surge supports four installer types configured via installers: in the manifest:
online: Console installer that downloads the package at install time (usessurge-installer).offline: Console installer with the full package embedded (usessurge-installer).online-gui: GUI installer (egui) that downloads at install time (usessurge-installer-ui).offline-gui: GUI installer (egui) with the full package embedded (usessurge-installer-ui).
The legacy web type has been removed; use online instead.
Initialize submodules first:
git submodule update --init --recursiveRust:
RUSTFLAGS="-D warnings" cargo build --release
RUSTFLAGS="-D warnings" cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings.NET:
dotnet build dotnet/Surge.slnx --configuration Release
dotnet test dotnet/Surge.slnx --configuration ReleaseBefore any push, run the same quality gates CI uses. Do not push if any command fails.
./scripts/check-version-sync.sh
cargo fmt --all -- --check
RUSTFLAGS="-D warnings" cargo test --workspace
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --workspace --lib --bins --examples -- -D warnings -D clippy::unwrap_used -D clippy::expect_used
cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
dotnet format dotnet/Surge.slnx --verify-no-changes
dotnet test dotnet/Surge.slnx --configuration ReleaseIf the local environment cannot run a listed command, document the exact gap in the PR and run it in CI before merge.
- Prefer self-documenting code: clear types, names, and small functions over explanatory comments.
- Use comments sparingly; add them only for invariants, non-obvious tradeoffs, or safety contracts.
- Keep modules cohesive and APIs explicit (
Result<T, E>, typed structs/enums instead of ad-hoc tuples). - Prefer typed error enums (
thiserror) overBox<dyn Error>in binaries/crates where error cases are known. - Consolidate repeated crate-local helpers (for example mutex poison recovery and C-string sanitization) into a shared internal module.
- Prefer
unwrap_or_else(std::sync::PoisonError::into_inner)over manualmatchwhen recovering poisoned mutexes. - Avoid unnecessary
crate::path prefixes in module-local code/tests when imports already provide the item. - For multi-app manifests, always scope storage access and emitted installer metadata prefixes by app id; never mix base-prefix release indexes with app-scoped artifact flows.
- For CLI commands that accept both optional
--app-idand--rid, use a shared RID-hint resolver to infer app id only when the RID uniquely identifies one app. - Minimize
unsafe: isolate it to FFI/boundary layers, prefer safe wrappers, and remove unnecessaryunsafe impl. - Every remaining unsafe block must include a short
SAFETY:rationale. - Run periodic panic-path sweeps in non-test targets with:
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -D clippy::expect_used- fix runtime
unwrap/expectin production/build paths instead of suppressing lints. expect_usedis treated the same asunwrap_used: both can hide panic paths in runtime code.
- CI hardening tiers:
- blocking:
cargo clippy --workspace --lib --bins --examples -- -D warnings -D clippy::unwrap_used -D clippy::expect_used - advisory debt visibility:
cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic - keep pedantic advisory until backlog is reduced; then promote selected pedantic lints to blocking.
- blocking:
- Enforce unsafe boundaries by crate/module:
surge-coredenies unsafe by default; onlysrc/diff/*is allowed to use it.surge-cliandsurge-supervisorforbid unsafe.surge-ffiis the primary unsafe boundary and must stay explicit/auditable.
- Do not store raw back-pointers to context handles in long-lived FFI handles; clone shared
Arcstate instead. - Clear out-parameters at function entry (
*out = null) before any fallible work. - Free functions must safely handle zero-length buffers and null pointers.
- Keep shared error propagation consistent across context/manager/pack handles.
- Prefer checked conversions for lengths/indices (
i64/i32 -> usize) and reject invalid values early. - Prefer safe
extern "C" fncallback types when the function pointer itself has no extra unsafe preconditions. - If converting C strings for outbound FFI, sanitize embedded NULs to avoid truncated/empty fallback errors.
- When adding new dependencies, always check crates.io for the latest stable version before specifying a version in
Cargo.toml. Never assume a version number from memory.
- For
surge-clicommand output (including progress/status updates), use the existingloglinefacility incrates/surge-cli/src/logline.rs. - Do not introduce alternative output paths for status/progress (for example ad-hoc
println!-driven progress UIs) whenloglinecan represent the same information.
- Rust edition: 2024; format with
cargo fmt --all. - Clippy/rustc warnings are treated as errors in CI; fix warnings instead of suppressing.
- Use idiomatic Rust naming:
snake_case(functions/modules),CamelCase(types),SCREAMING_SNAKE_CASE(consts). - Prefer
thiserrorfor errors andtracingfor logs. - Keep interfaces cross-platform and avoid embedding credentials in manifests or client configs.
- Keep unit tests close to code (
#[cfg(test)]in modules). - Add integration tests under
crates/*/testswhen behavior spans modules/commands. - For update/diff changes, include regression coverage for full + delta flows.
- For FFI changes, add regression tests for:
- handle lifetime behavior after context destruction,
- out-pointer behavior on failure paths,
- zero-length and null-pointer edge cases.
- Keep
crates/surge-core/tests/unsafe_boundaries.rspassing (unsafe confined to approved modules). - Before push, run Rust workspace tests + clippy and .NET tests.
- Benchmark and pack-policy memory lives under
docs/performance/. - When changing pack defaults, delta strategy,
surge tune pack,surge-bench, or.github/workflows/benchmark.yml, update the relevant files indocs/performance/in the same change. - Keep benchmark payload descriptions anonymized and generic; do not add private product names or file names to docs, workflow labels, or benchmark fixtures.
- Keep
.github/workflows/benchmark.ymlaligned withdocs/performance/benchmark-profiles.mdanddocs/performance/pack-policy.md.
Versioning is managed by GitVersion (GitVersion.yml). The next-version field controls the base version.
- Merge
develop→main(creates the release, e.g.0.3.0). - Immediately bump
next-versioninGitVersion.ymlondevelopto the next minor (e.g.0.4.0). - When changing version baselines, also bump Cargo values in
Cargo.tomlin the same PR/commit series:[workspace.package].version[workspace.dependencies].surge-coreversion
- Commit and push to
develop.
If step 2 is skipped, develop will keep producing preview versions under the old release number (e.g. 0.3.0-preview.N instead of 0.4.0-preview.N).
CI enforces this with ./scripts/check-version-sync.sh, which requires GitVersion.yml next-version to match both Cargo values above.
For a major release (e.g. 1.0.0), manually set next-version to the target version before merging to main.
- Use concise imperative commit messages, optionally scoped (examples:
feat(cli): ...,fix(core): ...,ci: ...). - Keep commits focused (one logical change per commit).
- PRs should include: purpose, behavior impact, test evidence (commands run), and migration notes if applicable.
- Ensure GitHub Actions are green before merge.