Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5ae159a
style: reindent the MSBuild and configuration files with two spaces
rent-a-developer Sep 7, 2026
2e24977
build: manage package versions centrally and make packing opt-in
rent-a-developer Sep 7, 2026
5aea5fd
build: state the style contract once and drop the rules that decided …
rent-a-developer Sep 7, 2026
75911e0
build: make the repository scripts check by default and fix on request
rent-a-developer Sep 7, 2026
0d813f4
docs: add a code of conduct and make CONTRIBUTING.md self-contained
rent-a-developer Sep 7, 2026
e869b2a
docs: split the documentation into guides and reference
rent-a-developer Sep 7, 2026
8d07fe6
build: verify the packed artifacts against their nuspecs in an isolat…
rent-a-developer Sep 7, 2026
a7bb11d
fix: scope the editor hooks to the file the edit named
rent-a-developer Sep 7, 2026
67d80d7
docs: state one policy for agents and contributors, and make the revi…
rent-a-developer Sep 7, 2026
c9ebb45
ci: group the dependency updates and ignore only generated output
rent-a-developer Sep 7, 2026
8fd52d3
chore: point .git-blame-ignore-revs at the reindentation commit
rent-a-developer Sep 7, 2026
6442d2c
docs: keep the counts out of the prose and the comparison in the readmes
rent-a-developer Sep 7, 2026
11e1bbe
refactor: rename preflight.ps1 to pre-commit-gate.ps1
rent-a-developer Sep 7, 2026
75a6a10
docs: drop the hard-coded counts the first sweep missed
rent-a-developer Sep 7, 2026
305f732
build: add a pre-release gate that runs what CI runs and can be run here
rent-a-developer Sep 7, 2026
c0db19e
build: check line endings in both gates, and CSharpier against the tree
rent-a-developer Sep 7, 2026
a3eaca8
fix: the line-endings remedy did not work - deleting the files is wha…
rent-a-developer Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions .agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,59 @@ Codex-only UI metadata and explicit-invocation policy. Claude needs thin skill w
The hook adapters differ because Claude and Codex use different payload and response contracts. Both delegate
all substantive behavior to the same scripts: `scripts/tidy-code.ps1` and `scripts/public-api-guard.ps1`.

The tidy hook runs the **default scope only** — CSharpier, under a second. Code style and member ordering are
not run on every edit: `dotnet format style` needs MSBuild and ReSharper loads the whole solution, and neither
belongs on the critical path of a single edit. All three are build errors, and `scripts/preflight.ps1` runs
`tidy-code.ps1 -Scope all` before a commit, so nothing reaches a pull request untidied.
## What the hooks do, and what they will not do

Both `PostToolUse` hooks are **scoped to the file the triggering edit touched**. Claude reports it as
`tool_input.file_path`; Codex reports an `apply_patch` whose patch text is in `tool_input.command`, so the
adapter reads the `*** Add File:`, `*** Update File:` and `*** Move to:` headers out of it and skips
`*** Delete File:`.

These rules follow, and they are the point of the design:

- **No fallback.** If the payload cannot be parsed, the hook formats nothing and says so in one line. It does
not fall back to "every file git reports as changed" — that would rewrite work in progress that this edit
never touched.
- **No path outside the repository.** Every path is resolved and then checked to be under the repository
root, so `../` and a symbolic link that points out of the tree are both refused. `bin/` and `obj/` are
refused too; generated output is not ours to format.
- **They never fail an edit.** A `PostToolUse` failure cannot undo an edit that already happened, so both
adapters exit 0 whatever went wrong and report it as text. Neither one stages a file, changes an API
snapshot, installs a tool, or runs a style, ordering, build or test pass.

The child scripts run in a **child `pwsh` process**. They end with `exit`, which run in-process would end the
adapter before it could emit its protocol output — and a Codex hook that writes nothing is a hook that
failed. Concurrent edits serialize on a named mutex derived from the repository path, so two formatters
cannot run over one file.

### Codex needs the hooks trusted, once per clone

Codex does not run a repository's hooks until the project is trusted. Until then **nothing fires**. Run
`/hooks` in Codex to see the hook definitions this repository declares, review them, and trust the project.
Review them again whenever `.codex/hooks.json` or anything under `.codex/hooks/` changes in a pull request:
a hook is code that runs on your machine after every edit, and "it was already trusted" is not a review.

Never bypass project trust to make a hook fire.

### When no hook covers the edit

The hooks only see edits made through a tool that reports one. An edit made another way — a shell
redirection, an editor outside the agent, a `git apply`, a Codex session whose hooks are not trusted yet — is
not formatted by anything. So:

```bash
pwsh -File scripts/tidy-code.ps1 # format what git reports as changed
pwsh -File scripts/pre-commit-gate.ps1 # and before committing, check the whole tree
```

`pre-commit-gate.ps1` is the backstop for all of it, and the build is the backstop for it: formatting,
style and member ordering are build errors, so an unformatted file cannot reach a green pull request whether
a hook fired or not.

The tidy hook runs the **default scope only**, on the file the edit touched — CSharpier, under a second. Code
style and member ordering are not run on every edit: `dotnet format style` needs MSBuild and ReSharper loads
the whole solution, and neither belongs on the critical path of a single edit. All of them are build errors, and
`scripts/pre-commit-gate.ps1` checks `-Scope all` before a commit (`-Fix` applies it), so nothing reaches a pull
request untidied.

When changing behavior, edit the canonical file. Keep only required names, descriptions, policies, tool/model
settings, and reference instructions in tool-specific files.
34 changes: 28 additions & 6 deletions .agents/references/code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ Background for the rules in [AGENTS.md](../../AGENTS.md#code-style-formatting-an
tool does something you did not expect, or when you are about to write a type name in a place the build does
not check.

## Three concerns, three tools
## The concerns, and the tool that owns each

| Concern | Tool | Configured in |
|---|---|---|
| Formatting — whitespace, line breaks, wrapping | **CSharpier** | `.editorconfig` (`max_line_length`, `indent_size`) |
| Style — `var`, `=>`, `this.`, null checks, usings | **Roslyn analyzers** | `.editorconfig` |
| Ordering — types and their members | **ReSharper** applies it, **NewStyleCop** checks it | `DbConnectionPlus.slnx.DotSettings` and `stylecop.json` |
| Ordering — types and their members | **ReSharper** applies it, **NewStyleCop** checks *part* of it | `DbConnectionPlus.slnx.DotSettings` and `stylecop.json` |

Each tool owns its concern completely, and all three are build errors rather than warnings, in `tests/` and
The word *part* is load-bearing. StyleCop checks kind, access, constant, static and readonly — `SA1201`,
`SA1202`, `SA1203`, `SA1204`, `SA1214`. It has no notion of alphabetical order **within** one of those
groups, which the ReSharper file layout applies and nothing checks. A member that is in the right group but
the wrong place inside it compiles, passes the analyzers, and is only visible by running the pipeline and
looking at what it moves.

Each tool owns its concern completely, and every one of them is a build error rather than a warning, in `tests/` and
`benchmarks/` as much as in `src/`. Two different mechanisms, both in the root `Directory.Build.props`:
`EnforceCodeStyleInBuild=true` with `TreatWarningsAsErrors=true` covers style and ordering, and the
`CSharpier.MsBuild` package covers formatting. It runs in check mode, so a build never rewrites your files —
Expand Down Expand Up @@ -70,6 +76,16 @@ explicit field and the compiler's capture field are one field, not two.
ReSharper puts it by default, at the end; giving events an entry breaks the build. The entries match
`ImplementsInterface` **and** `Access Is="Private"` — without the access test they would also catch implicit
implementations and pull `Equals(T)` away from `Equals(object)`.
- **Constructors sort static-first; everything else sorts by access first.** The file layout gives the
constructors entry `<Static />` ahead of `<Access />`, and only that entry. It is the one place where the
two order definitions had to be reconciled by hand: `stylecop.json` lists `accessibility` before `static`,
which for constructors would put a `public` instance constructor ahead of the static one. `Benchmarks.cs`
has both, in that order, and the build is green — so this is load-bearing, not an oversight. Do not
"regularise" it.
- **Static fields are not reordered past each other.** The file layout sets
`StaticFieldReorderingPolicy="Strict"`, so ReSharper leaves a static field where it found it relative to the
other static fields of its type. Moving one can change the order its initializer runs in, and that is a
behaviour change no formatter is allowed to make. Order them by hand if you need them ordered.
- **Overloads with the same name have no defined order between them.** Two methods called `Equals` tie on
every key the file layout sorts by — kind, access, static, readonly and name — and ReSharper's sort is
stable, so it leaves them in whatever order it found them. Reordering the same file starting from two
Expand All @@ -90,14 +106,16 @@ The benchmarks are the exception: nothing consumes them as an API, so they use p

## Member order

StyleCop's order, applied by ReSharper and checked by NewStyleCop. Write a new member straight into the right
place rather than relying on the fixer:
StyleCop's order, applied by ReSharper. Write a new member straight into the right place rather than relying
on the fixer — and note that only the leading keys below are checked by an analyzer:

constants → fields → constructors → finalizers → delegates → events → enums → interfaces
→ properties → indexers → conversion operators → operators → methods → nested structs → nested classes

Within each of those groups: public before internal before protected before private, static before instance,
readonly before mutable, and **alphabetical** after that.
readonly before mutable, and **alphabetical** after that. The kind, access, constant, static and readonly keys
are `SA1201`/`SA1202`/`SA1203`/`SA1204`/`SA1214` and are build errors. Alphabetical order is applied by
ReSharper and checked by nothing.

Note that fields go at the **top** of a type, and that explicit interface implementations sort ahead of the
ordinary methods — with the exception for events described above.
Expand All @@ -108,6 +126,10 @@ These need no attention beyond letting the tools run — they are listed here so
stays short, not because they are optional:

- **Primary constructors** wherever `IDE0290` asks for one.
- **`var` where the type is obvious** — a built-in type, or a right-hand side that names the type. Both are
errors. Everywhere else the preference is the explicit type, stated in `.editorconfig` as
`csharp_style_var_elsewhere = false`, but it is *not* enforced: it is a preference the build does not fail
on, so an existing `var` in that position is not a defect.
- **Expression-bodied members** are `error`-severity for methods, constructors, operators, properties, indexers,
accessors, lambdas and local functions. Use `=>` wherever a member is a single expression.
- **File-scoped namespaces**, with usings outside the namespace.
Expand Down
14 changes: 7 additions & 7 deletions .agents/references/reviews/adapter-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ src/DbConnectionPlus.DatabaseAdapters.SqlServer
Each contains `{Db}DatabaseAdapter.cs`, `{Db}EntityManipulator.cs`, `{Db}TemporaryTableBuilder.cs` and
`{Db}ConfigurationExtensions.cs`.

A change to one adapter almost always has to be mirrored into the other four. The only thing that catches a
A change to one adapter almost always has to be mirrored into the others. The only thing that catches a
miss is the integration suite, which needs Docker and about ten minutes for the full matrix — so it usually is
not run. The job here is to catch it statically.

Expand All @@ -29,21 +29,21 @@ This is a **review**: report findings, do not edit files.
whether any core seam (`IDatabaseAdapter`, `IEntityManipulator`, `ITemporaryTableBuilder`,
`DatabaseAdapters/Constants.cs`) changed.

2. **For each changed adapter member**, read the corresponding member in all four other adapters and classify:
2. **For each changed adapter member**, read the corresponding member in every other adapter and classify:

- **Missing** — the other adapters were not updated at all, and they should have been.
- **Diverged** — they were updated, but the logic differs in a way that is not explained by dialect
differences.
- **Correct** — either mirrored properly, or deliberately different for a real dialect reason.

3. **For each core-seam change**, enumerate all five implementations of the changed interface member and
confirm each compiles against the new contract. Search the whole repository for the member name — the five
3. **For each core-seam change**, enumerate every implementation of the changed interface member and
confirm each compiles against the new contract. Search the whole repository for the member name — the
implementations are named `{Db}DatabaseAdapter`, `{Db}EntityManipulator` and `{Db}TemporaryTableBuilder`, so
a hit count below five is a missed mirror — and trust the compiler over the search.
a hit count below the number of adapter projects is a missed mirror — and trust the compiler over the search.

4. **Check test parity.** Adapter behaviour is covered in
`tests/DbConnectionPlus.IntegrationTests/DatabaseAdapters/{MySql,Oracle,PostgreSql,Sqlite,SqlServer}/`.
If a behaviour change gained a test in one adapter's file, the other four normally need the same test.
If a behaviour change gained a test in one adapter's file, the others normally need the same test.

## Dialect differences that are legitimately asymmetric

Expand All @@ -60,7 +60,7 @@ Do not report these as divergence unless the change actually gets them wrong:
- MySQL's separate enum-handling behaviour in the temp-table reader path. `EnumerableReader` preserves this
asymmetry deliberately — do not "fix" it as a side effect.
- Oracle's entity manipulator genuinely has **two** `PropertyGetter`/`PropertySetter` call sites where the
other four have three. That is not a missing mirror.
others have three. That is not a missing mirror.

## Reporting

Expand Down
6 changes: 3 additions & 3 deletions .agents/references/reviews/aot-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Use this whenever a change touches reflection, dynamic dispatch, expression tree
`src/`. This is a **standing** checklist — AOT support shipped in 4.0.0, and everything here exists to keep it
from regressing.

Read the [Native AOT and Trimming](../../../DESIGN-DECISIONS.md#native-aot-and-trimming) section of
Read the [Native AOT and Trimming](../../../docs/DESIGN-DECISIONS.md#native-aot-and-trimming) section of
DESIGN-DECISIONS.md before reviewing: it records what is deliberate, and therefore what counts as a regression,
and it carries the measurements behind each decision.

Expand Down Expand Up @@ -78,7 +78,7 @@ surfaces IL diagnostics — and, as warnings-as-errors, fails on them:
dotnet build DbConnectionPlus.slnx -c Release
```

**The expected count is zero, on both target frameworks**, with no suppressions beyond the sanctioned ones
**The expected count is zero, on `net8.0` and `net10.0`**, with no suppressions beyond the sanctioned ones
listed above (`IL2060`, `IL2065`, and the `net8.0`-only `IL3050` on the two `CreateMaterializer` dispatchers).
Any other IL diagnostic in `src/` is a regression; re-measure rather than assuming.

Expand All @@ -89,7 +89,7 @@ the just-in-time compiler, so a change to any reflection path also needs:
pwsh -File scripts/verify-package-aot.ps1 -Pack
```

It packs the six shipping projects, publishes `tests/package-consumption/AotConsumer` natively **from those
It packs the shipping projects, publishes `tests/package-consumption/AotConsumer` natively **from those
packages**, gates its IL diagnostics and runs the binary. `-Framework net8.0` checks the documented floor,
which behaves differently from the `net10.0` default. Needs a C++ toolchain: MSVC on Windows, `clang` +
`zlib1g-dev` on Linux. Drop `-Pack` to reuse the packages already in `artifacts/packages`.
Expand Down
Loading
Loading