Contributing
Concord has two repositories. Core contains the runtime, packages, analyzers, generators, and tests. Docs contains this site and its theme.
Use the Core repository for code changes. Use the Docs repository for guides, API notes, and site work. A change to public behavior often needs updates in both.
Core development
Set up the Core repository
Core requires the .NET 10 SDK. Its global.json accepts the latest installed .NET 10 feature band.
git clone https://github.com/ConcordLib/Core.git
cd Core
dotnet restore Concord.ci.slnx
dotnet build Concord.ci.slnx -c Release --no-restore
dotnet test Concord.ci.slnx -c Release --no-restore -f net10.0
The build treats warnings as errors. Run the affected test project while you work, then run the CI solution before you open a pull request.
CI tests the runtime libraries on .NET Framework 4.7.2. Windows runs those tests through .NET Framework, while Linux uses Mono. Changes to Emit, Detour, AttachedData, or Orchestration must keep that target working.
Choose the right project
Core keeps each runtime job in a separate project. The Concord project merges the four runtime libraries into one Concord.dll during packaging.
| Project | Change it for |
|---|---|
Concord.Emit |
IL copying, injection positions, control handles, operation handles, or wrapper composition |
Concord.Detour |
Installing wrappers, ordering live injections, recomposing a target, or removing detours |
Concord.Orchestration |
Patcher, the fluent builder, patch discovery, declaration scanning, or apply and undo behavior |
Concord.AttachedData |
Weak-reference storage used by attached fields |
Concord.Analyzers |
Compiler diagnostics for patch declarations |
Concord.Generators |
Patch registries, generated shadow members, or IDE refactorings |
Concord.Ref |
Reference-package construction and public API verification |
Concord |
Merging and packaging the runtime assembly |
Tests follow the same layout under tests/. For example, changes in Concord.Emit belong with tests in Concord.Emit.Tests.
Runtime architecture
How a patch reaches the runtime
Patcher.Applygets patch declarations from the generated registry. It scans the assembly when no registry exists.PatchDeclarationScannerreads[Patch],[Inject], and related attributes. It creates anInjectionfor each resolved injection method.CollectingPatchAppliersends each target and injection toIDetourBackend.ApplyComposed.TargetDetourRegistrykeeps the live injections for that target.InjectionOrdererresolves priority and before or after rules.WrapperComposerbuilds a wrapper from the target body and the ordered injections. MonoMod installs that wrapper as the target's runtime entry point.- Disposing the returned handle removes its injections. Concord rebuilds the wrapper for any injections that remain, or removes the detour when the target has none.
For a runtime bug, find the last correct value in that sequence and follow the next call. Keep the existing boundaries between layers.
Composition code
WrapperComposer coordinates composition. BodyCopier copies method bodies, arguments, locals, branches, and exception handlers. InjectedMemberMap resolves declaration members, while ControlHandleLowering rewrites control calls.
The composer returns a ComposeResult. Its Wrapper becomes the detour target. Its OriginalBody is an unpatched copy of the target body.
WrapperComposer throws ConcordEmitException with a stable CONCxxx code when composition fails. PatchDeclarationScanner throws ConcordDeclarationException when it cannot resolve a patch. Add or update an analyzer rule as part of the change if the compiler can detect the same mistake.
Public API changes
Concord.Ref compiles the source files from the four runtime libraries into a metadata-only assembly. Compiling the same source keeps its public surface tied to the runtime implementation.
Build the reference package after changing a public type or member:
dotnet build src/Concord.Ref/Concord.Ref.csproj -c Release --no-restore
Keep MonoMod types out of public signatures. Add XML documentation for public APIs, and update the user docs when behavior changes.
Design rules
- Keep the author API close to ordinary C#.
- Put IL work in Emit and live runtime state in Detour.
- Keep runtime validation and analyzer diagnostics consistent when the compiler has enough information.
- Check invalid input before touching an active detour.
Verification
Tests
Run the smallest relevant suite while you work:
dotnet test tests/Concord.Emit.Tests/Concord.Emit.Tests.csproj -f net10.0
dotnet test tests/Concord.Detour.Tests/Concord.Detour.Tests.csproj -f net10.0
dotnet test tests/Concord.Orchestration.Tests/Concord.Orchestration.Tests.csproj -f net10.0
Add a regression test for a bug. For ordering, use operations whose result changes when the order changes. Exercise apply and dispose when a registry change affects recomposition. Analyzer tests compile short source samples and assert the diagnostic ID and location.
Benchmarks live under bench/Concord.Benchmarks. Run them when you change code on the patched method's hot path or the cost of applying many patches.
Documentation changes
The Docs repository builds with DocFX:
docfx build docfx.json
DocFX writes the generated site to .site/. It may print existing link warnings from the custom home page. Read each warning and make sure your change did not add one.
Theme changes need the Tailwind build first:
cd templates/concord
npm ci
npm run build:css
Write docs for the current API. Leave upgrade notes and old-versus-new comparisons to pages that cover a migration.
Writing documentation
The site follows the Google developer documentation style guide for vocabulary and structure. On top of that it applies the mechanical rules of ASD-STE100:
- Keep sentences to 25 words or fewer.
- Use active voice. Name who does the action, usually Concord or the reader.
- Give one instruction per sentence.
- Use no em dashes or en dashes.
Vale checks all of this. Run it before you open a pull request:
vale sync
vale docs/*.md
The first command downloads the Google package. You only need it once.
.vale.ini turns off four rules that misfire on this content, such as the one that reads
Concord.Ref as two sentences. Each entry says why. Turn off a rule only when you can show it
produces a false positive.
Put domain terms in styles/config/vocabularies/Concord/accept.txt. ASD-STE100 allows technical
names outside its approved word list, and that file records the ones this site uses.
The lint-prose job gates the deploy, so prose that fails Vale does not reach the site.
Pull requests
Open Core pull requests against main and use the Core pull request template. Explain why the change is needed, list the tests you ran, and name any public API breaks.
Before opening the pull request:
- Build without warnings.
- Run the affected tests and the full CI solution.
- Add tests for changed behavior.
- Update docs for user-facing changes.
- Check that disposing a patch restores the expected state.