Conventions
Three groups: conventions to respect, things never to do, and the tool-wrapper recipe.
Conventions worth respecting
- Centralized package versions — add new packages to
Directory.Packages.props, never inline. Adding a meaningful library (not a tiny transitive helper)? Add a row to docs/dependencies.md in the same PR — reviewers will ask. - Tool wrappers: copy/paste from neighbours; cover full commands; use
<c>,<a>,<ul>/<ol>,<em>,<para/>inhelp; don't writesecret: falseordefault: xxx. See Tool wrapper recipe below. - Tests next to code, separate folder: every
Fooproject undersrc/has a siblingFoo.Testsproject undertests/. Mirror the namespace. - No IDE-specific style files committed.
.editorconfigand*.DotSettingswere removed during the takeover — relying ondotnet formatdefaults and review. - Telemetry opt-out is set in test runs (
FALLOUT_TELEMETRY_OPTOUT=true). Keep it that way. - No per-file license headers. The MIT notice lives in
LICENSEat the repo root, and NuGet packages declare MIT viaPackageLicenseExpression. Per-file headers were stripped in v11 (one source of truth + the header URL would have rotted on the repo-org transfer). Vendored third-party code keeps its own copyright headers — don't touch those (e.g. files undersrc/Persistence/Fallout.Persistence.Solution/retain Microsoft's MIT notice). [Experimental]for opt-in unstable public APIs. Not-yet-stable public surface is marked with[Experimental("FALLOUT0xx")]rather than held back or shipped silently. See the[Experimental]convention below and the diagnostic-ID registry.
Writing style for issues, PRs, and commits
Applies to AI tools and humans. Many readers are non-native English speakers — keep it readable.
- Be short and precise. Lead with the point.
- Prefer bullet points over paragraphs.
- Use plain, simple English. Short sentences, common words.
- Cut filler: no preamble, no hedging, no AI-flavored padding.
- Say what changed and why. Drop the rest.
Covers GitHub issues, PR titles/bodies/comments, and commit messages.
[Experimental] for opt-in unstable APIs
Per ADR-0004 §5, public APIs that aren't ready to commit to a stability guarantee are marked with System.Diagnostics.CodeAnalysis.ExperimentalAttribute instead of being held back or shipped silently. The attribute ships in the .NET 8+ BCL — no package reference needed (the repo targets .NET 10).
using System.Diagnostics.CodeAnalysis;
[Experimental("FALLOUT001")]
public sealed class NewPluginHost
{
// ...
}
Rules:
- Diagnostic-ID scheme:
FALLOUT0xx. Each experimental surface gets its own ID (e.g.FALLOUT001), allocated sequentially and never reused — a retired ID stays retired. Register every allocation in the diagnostic-ID registry in the same PR that introduces it. - Consumers must explicitly opt in.
ExperimentalAttributeis an error-by-default diagnostic: code that touches the API fails to compile until the consumer suppresses the exact ID —#pragma warning disable FALLOUT001around the call site, or<NoWarn>$(NoWarn);FALLOUT001</NoWarn>in their project. Opting into instability is therefore a conscious, per-API choice — which is right for a framework (a product devs build on), not an app. - Promoting to stable = removing the attribute. Because the feature already rode the test lanes (
experimental/main) and was promoted forward, deleting the[Experimental]line is the whole promotion — no special cross-branch dance. This is what lets stabilised work feed into the production line without a divergent fork. Adding or removing[Experimental]is not a breaking change. - Channel discipline differs. On the
experimental(alpha) /main(preview) test lanes, churn is expected and the attribute is a courtesy. On arelease/YYYYproduction line, any risky-but-shipped public surface must wear[Experimental]— that contract is what keeps the stable line trustworthy while still carrying new work. - Don't apply it speculatively. Because the diagnostic is error-by-default, marking an API that's already used internally breaks the build everywhere it's referenced. Only add
[Experimental]to a genuinely not-yet-stable API, and suppress every internal usage in the same change so the build stays green.
CI pipeline & triggers
Shaped by milestone #18 and the ADR-0004 ladder. Invariants:
- Feature branches run zero CI until a PR is opened. Push triggers list only long-lived branches; nothing fires on
feature/*,bugfix/*, etc. until they're PR'd againstexperimental/main/release/*/support/*. Do not add a working-branch pattern to anyOnPush*/branches:trigger. - The Linux PR gate (
ubuntu-latest) is the only required check — runs on PRs to the four long-lived branches. experimental(push) →-alpha,main(push) →-previewto GitHub Packages (experimental.yml/preview.yml).- Cross-platform
windows/macosare gated to release intent — PR-to-release/*/support/*or av*tag push only. They do not run onmain/experimentalpushes. ("Onmainwe've got our edge.") concurrency: cancel-in-progresson every build workflow exceptrelease.yml— never cancel a publish mid-flight.- Canonical CI-ignore paths:
docs/**,.assets/**,**/*.md— applied to every PR/push trigger. - The
ubuntu-latest/windows-latest/macos-latestworkflows are generated frombuild/Build.CI.GitHubActions.cs— edit the attributes + constants there and regenerate (./build.sh), never hand-edit the.yml.experimental.yml/preview.yml/release.ymlare hand-written. - Every publishing lane runs
Testbefore it publishes (#324).experimental.yml,preview.yml, andrelease.ymlall run a singledotnet fallout Test Packinvocation — NUKE executes it as discrete internal stages (Restore → Compile → Test → Pack) and fails at the breaking stage, so a test failure stops the job before the push step. Don't split a lane into separatedotnet fallout Compile/Test/Packsteps — each invocation re-runs the dependency graph (double-compile); the single invocation is the staged build. - Caching (#328): every workflow caches
~/.nuget/packages+.fallout/temp, keyed onglobal.json+**/*.csproj+Directory.Packages.props(the dependency-affecting set), with arestore-keys:prefix fallback for partial restores. There is nopackages.lock.jsonto add to the key, and build outputs (bin/obj) are deliberately not cached (stale-artifact correctness risk).
What not to do
- Don't reintroduce
source/— production code lives undersrc/, tests undertests/. Same forimages/(now.assets/). - Don't add
main/experimental(or any working-branch pattern) to the push triggers of the cross-platform workflows — they're release-intent-gated on purpose (milestone #18 / #318 / #326). - Don't add
submodules: recursiveto checkouts — there are no submodules (no.gitmodules); it's a dead init step. - Don't add
secret/defaultdefaults to tool JSON files (see CONTRIBUTING.md). - Don't introduce a new test framework or assertion library — stay on xUnit + FluentAssertions + Verify.
- Don't commit
output/or anybin//obj/directory. - Don't commit
nuke-global.slnor othernuke-global.*files — they're generated byGenerateGlobalSolution. - Don't bypass
Directory.Packages.propsorDirectory.Build.targets. - Don't reintroduce
.editorconfigor*.DotSettingswithout a maintainer-level decision — they were intentionally removed.
Tool wrapper recipe
When asked to add or extend a tool wrapper:
- Find the closest existing tool under
src/Fallout.Common/Tools/<Tool>/<Tool>.json. - Copy its shape; cover a full command with all arguments.
- Run
./build.ps1 GenerateToolsto verify it generates cleanly. - Do not commit the generated
.csoutput.