Skip to content

build: standardize repository tooling and documentation - #29

Merged
rent-a-developer merged 17 commits into
mainfrom
chore/issue-28-harmonize-repository-tooling
Sep 8, 2026
Merged

rent-a-developer merged 17 commits into
mainfrom
chore/issue-28-harmonize-repository-tooling

Conversation

@rent-a-developer

Copy link
Copy Markdown
Owner

Closes #28

Standardize the build configuration, formatting, scripts, CI, AI guidance, and documentation, while preserving the public API and the runtime behavior.

  • Put the produced version in the root build props and every dependency version in central package management. Validate the package contracts against the last published version and enable dependency auditing.
  • Align the style and tool configuration, and use two spaces for all XML configuration.
  • Make script checks preserve the source and the Git index, keep fixes explicit, and scope the formatting hooks to the triggering edit.
  • Keep the database coverage and the executed consumer gates, and validate them against the exact local package artifacts.
  • Use quarterly dependency updates and consistent contribution conventions.
  • Keep the README short, publish the longer material from docs/, and separate the DocFX configuration and output from the authored documentation.
  • Apply the repository-wide formatting and ordering pass in its own commits, last.

Merge instruction

Merge with a merge commit. Do not squash. .git-blame-ignore-revs records the mechanical commit IDs, and a squash merge replaces those commits.

Validation

Implementation draft: every check below is pending. Complete this description from the actual diff before opening the pull request, and tick a box only after that check has actually passed.

  • Release build, unit and integration tests, and coverage
  • Package metadata, compiled API, package validation against the baseline version, symbols, and SourceLink
  • Minimum-SDK consumers and all three native AOT executions
  • Full formatting check, plus the manual source and index and hook-scope checks
  • DocFX build with warnings as errors, and a review of the rendered documentation
  • Mechanical commits recorded in .git-blame-ignore-revs and verified with git blame

No release version change and no publication are included. Record any approved exception here.

rent-a-developer and others added 17 commits September 7, 2026 03:25
.editorconfig is about to require two-space indentation for .csproj, .props, .targets, .slnx, .config, .xml
and .DotSettings. Nothing in the tidy pipeline applies that: .csharpierignore keeps CSharpier away from XML,
`dotnet format` does not touch XML whitespace, and the ReSharper profile only reorders C# members. So the
transformation is done here, on its own, before the content changes land.

Leading tabs only, two spaces each. No other byte moved: no reflow, no tab elsewhere on a line, and in
DbConnectionPlus.slnx.DotSettings the XML-encoded file-layout string is the value of an element and is left
exactly as it was. Every file still parses and every evaluated MSBuild property is unchanged.

This commit is mechanical and belongs in .git-blame-ignore-revs; a later commit records it.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every version in the repository now lives in Directory.Packages.props, grouped by what it is for, so a bump
is one edit in one file instead of a hunt through eleven projects that could disagree with each other. The
package-consumption projects opt out of central management on purpose: they exist to consume the produced
packages the way a stranger would, and pinning their versions centrally would make that test lie.

Packing is opt-in. Every project is IsPackable=false at the root and the six shipping projects say yes
themselves, so a new test, sample or tool project cannot ship by forgetting to opt out. The four packed
files move to the repository-root Directory.Build.targets, because IsPackable is set by the project after
every .props file has been read and a condition on it in a .props file is always false.

Packaging changes that follow from that:

- <Version> moves to the repository-root Directory.Build.props, so one file answers what version the
  repository is at, and scripts read it from there.
- The licence is the SPDX expression MIT rather than a file reference, which nuget.org and licence tooling
  can read without opening the package. LICENSE.md is still packed, unchanged.
- PackageRequireLicenseAcceptance is false: acceptance prompts are for restricted licences, and one in
  front of an MIT package trains people to click through the prompts that matter.
- Package validation runs against the published 4.0.0, so removing or changing a public signature fails
  the pack instead of reaching nuget.org.
- NuGetAudit runs at "all" scope, where transitive advisories are. NU1901-NU1904 stay warnings rather than
  errors, so a newly published advisory in the transitive graph cannot break a build that touched nothing.

The shared test settings move to tests/Directory.Build.props, which imports the repository root explicitly
because MSBuild stops at the first Directory.Build.props it finds.

No version, framework floor, package id, assembly name or dependency was changed. The six packages, their
contents and their dependency groups are byte-identical apart from the two licence lines and the release
notes URL above.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nothing

The `var` decision had two of its three halves written down; the third was left to the analyzer default, so
what the repository wanted depended on a tool version. It is now stated - explicitly typed where nothing on
the line names the type - and deliberately silent rather than an error, because raising it would rewrite
every such declaration and that is a code change, not a configuration one.

The rest is the same kind of tidying:

- One indentation rule for every structured configuration format, two spaces, matching what Visual Studio
  and Rider do to XML by themselves. The files were re-indented in the previous commit.
- The top-level-statements preference said the opposite of what all three executables here do, each of
  which has an explicit Program class and one of which names it in <StartupObject>.
- The naming rule for non-field members demanded plain PascalCase, which rejects every
  Method_Scenario_ShouldExpectedOutcome test name AGENTS.md requires. It now uses the same style as the
  type rule instead of contradicting a documented convention, and pascal_case, referenced by nothing
  afterwards, is gone.
- usingDirectivesPlacement in stylecop.json was read by no enabled rule - SA1200 is off, because CSharpier
  decides placement - so it stated a policy nothing enforced.
- The ReSharper file layout now pins StaticFieldReorderingPolicy explicitly. It was the implicit default;
  saying it means a ReSharper release cannot change member order without a commit that says so. Measured:
  byte-identical output on a full cleanup run before and after.

No .cs file changes as a result of this - the C# half of the contract already held. The comment about
"zero violations across 283 files" is replaced by the fact that makes it true: the rules are errors and
the build is green.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
preflight.ps1 was a formatter that also verified: it rewrote the working tree and then reported on what it
had written. It now checks by default and writes nothing but build output and caches, and -Fix is what
applies. That is the order a pre-commit gate needs - you look at what is wrong before something changes it
under you - and it is what lets CI run the same script the contributor runs.

tidy-code.ps1 -Check honours that too. Checking the whole solution needs the formatters to run, and they
edit in place, so -Check -Scope all now copies the tracked and untracked tree to a temporary directory,
initializes a throwaway repository there, runs the pipeline against the copy and reports the diff. The
real working tree is never touched, and the temporary directory is deleted only when its path starts with
the prefix the script itself created.

While measuring that, a defect: `dotnet format style --no-restore` on a tree whose projects are not
restored DELETES using directives it believes are unused, reports success, and leaves source that does not
compile. On a fresh clone it removed `using Humanizer;` and `using LinkDotNet.StringBuilder;` from the
shipping library - twelve compiler errors from a formatting step that said it had done nothing wrong.
--no-restore is gone from that step and the reason is written into the function, so it is not put back as a
performance optimization.

The rest:

- clean-build-artifacts.ps1 deleted .xml files next to a .dll by a name heuristic, which reaches documents
  that are not build output; it now deletes only what it can attribute to a build, skips reparse points
  rather than following a junction out of the tree, and refuses any path that resolves outside it.
- extract-release-notes.ps1 writes to artifacts/release/ with the rest of the generated output instead of
  the repository root, and strips the link definitions that a changelog section carries and a release page
  cannot resolve.
- public-api-guard.ps1 no longer asks a contributor for a version bump. The version is the maintainer's, at
  release time.
- benchmarks.ps1 and update-public-api.ps1 resolve the repository root and restore the caller's location
  and PATH in a finally block, so an interrupted run leaves the shell as it found it.
- Every script requires PowerShell 7 explicitly rather than failing later with a syntax error.

No behaviour a package consumer can see changes here.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The code of conduct was referenced but did not exist: CONTRIBUTING.md said "please note we have a code of
conduct" and there was nothing to follow. It is Contributor Covenant 2.1, with a real reporting address.
The enforcement ladder is deliberately left out - a single-maintainer project cannot staff a four-stage
escalation process, and publishing one it will not run is worse than not publishing it.

CONTRIBUTING.md becomes the page a contributor can work from without opening another: what to install, what
the integration tests need from Docker, what the Native AOT gate needs from a C++ toolchain, how branches
and commits are named, which tool owns formatting, style and ordering, and what to run before pushing.
Previously it deferred the working detail to AGENTS.md, a file written for agents, and left the human
contributor reading around it.

Two policy corrections, applied here and mirrored in the agent guidance and the pull-request template:

- **Contributors never bump a version.** The old text asked for a SemVer bump in the pull request. The
  version, the changelog date, promoting PublicAPI.Unshipped.txt and the tag are the maintainer's, at
  release time. A contributor who bumps it makes every concurrent pull request conflict.
- **An AI agent's branch is named after what it does**, not after who typed it. The `claude/` and `codex/`
  prefixes are gone; there is one branch convention.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README.md was 1323 lines - the entire manual on the landing page, where a reader looking for "what is this
and how do I install it" had to scroll past every feature to find out, and where anything that changed had
to be found among a thousand lines of prose. It is now 181 lines: what the library is, how to install it,
one example, and where to go next.

The material is not lost, it is filed. Six guides carry the how-to - querying, parameters and temporary
tables, entity mapping and CRUD, configuration, custom adapters, Native AOT - and three reference pages
carry the lookups: a one-line index of the public API, the comparison table, and the benchmark numbers.
Each page is one subject, so a change lands in one file.

- PACKAGE_README.md is the NuGet package page and now reads like one: 92 lines, the pitch and the install
  line, then links. It is not a place to maintain a second copy of the manual.
- DESIGN-DECISIONS.md moves under docs/, where the documentation lives. The links that pointed at the root
  copy - CHANGELOG.md and the AOT consumer's README - move with it.
- docfx.json moves to build/docfx/, with the rest of the build configuration, so docs/ holds content and
  nothing else. Its content mappings are rewritten for the new layout; the generated API URLs are
  unchanged, so no published link breaks.
- CHANGELOG.md gains the empty `## [Unreleased]` heading Keep a Changelog expects, so a contributor has a
  section to write into without inventing a version number. No entry is added by this branch.

`docfx build --warningsAsErrors` is clean, and every relative link and anchor in the repository resolves.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed cache

The Native AOT gate is the only check in the repository that can see silent trimming damage, so what it
consumes has to be exactly what would be published, and nothing else.

- The six packages are confirmed from their **nuspecs** rather than from the file names on disk: id,
  version and target frameworks are read out of each package, so a stale artifact from an earlier pack, or
  a missing adapter, is a failure with a name rather than a run against the wrong input.
- The consumer package cache is emptied on every run and NUGET_PACKAGES points at it, so a package
  restored from the machine's global cache cannot stand in for the one just built. Proved by setting an
  inherited NUGET_PACKAGES deliberately and watching the consumer's closure still land in the local
  directory.
- RestoreConfigFile names the consumer's own NuGet.config, so the feeds are the consumer's and not
  whatever the machine has configured, and the publish runs from the consumer directory for the same
  reason.
- The whole body is in try/finally: PATH, NUGET_PACKAGES and the working directory are restored however
  the run ends, so an interrupted gate does not leave the shell pointed at a throwaway cache.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Codex adapter discarded the payload and ran the tidy script with no arguments, which formats every file
git reports as changed. An agent editing one file therefore reformatted the contributor's unrelated work in
progress, in the same edit, without saying so. Both adapters now format only the path the triggering edit
carried: Claude Code's `tool_input.file_path`, and for Codex the patch text parsed for `*** Add File:`,
`*** Update File:` and `*** Move to:`. A delete is skipped - there is nothing left to format - and a rename
formats the new name only, because the old one no longer exists.

There is **no fallback**. An unparseable payload formats nothing and says so in one line; the old behaviour
was the fallback, and it was worse than doing nothing.

- Every path is resolved and then required to sit under the resolved repository root, so `../` traversal is
  refused and so is a junction or symlink pointing out of the tree - the check happens after resolution,
  which follows it. Paths under bin/ and obj/ are refused too.
- The public-API guard now fires only when the edited file is actually a PublicAPI.*.txt, instead of
  running on every edit.
- Concurrent edits serialize on a named mutex derived from the repository path, shared by both agents and
  distinct per clone, with a timeout so a stuck hold cannot wedge later edits.
- Both adapters exit 0 whatever happens. A PostToolUse failure cannot undo an edit that already happened,
  so failing only costs the agent its next step; they report through their own channel instead.
- Each adapter runs the shared script as a child `pwsh -File` process. The shared scripts end with `exit`,
  which inside the adapter's own process would end the adapter before it could write its response - fatal
  for Codex, where a hook that writes no JSON is a hook that failed.
- A missing formatter is reported with the command that installs it, and nothing is installed on your
  behalf.

The wiring in .claude/settings.json and .codex/hooks.json is unchanged: the triggers are still Edit|Write
and apply_patch|Edit|Write, and neither was broadened to every shell command.

Two defects were found by feeding hand-written payloads to the adapters in throwaway clones: PowerShell
unrolls a one-element list on the way out of a function, so a single-file patch threw and formatted
nothing; both adapters now wrap the result as [string[]] @(...).

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ewers read-only

The agent guidance and CONTRIBUTING.md described the same repository differently, which is how an agent ends
up doing what a contributor is told not to. They now say the same things in the same words: the branch
convention with no agent-specific prefix, the commit convention, the companion edits a change needs, and
that a version bump is the maintainer's at release time and nobody else's.

- The two custom reviewers lose `Bash` and keep Read, Grep and Glob. They review; they report findings and
  cite file and line. A reviewer that can run a shell can also write a file, and "please do not edit" is
  not a sandbox. Where a checklist wants a diff or a build, it now says to ask the caller for it.
- The `model:` and `model_reasoning_effort` pins are removed. They are the operator's choice, not the
  repository's, and a pinned model in a checked-in agent definition goes stale silently.
- .agents/README.md documents the hooks the previous commit scoped: what they format, the three rules that
  keep them from reaching anything else, why each adapter runs a child process, how to review and trust the
  declared hooks in Codex, and what to run by hand when no hook covered the edit - a shell redirection, an
  editor outside the agent, a `git apply`, or an untrusted session.
- The links that pointed at the root DESIGN-DECISIONS.md follow it under docs/.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dependabot ran monthly and split what is reviewed together. It now runs quarterly with grouped pull
requests: one review of "what does the build reject now" instead of one per analyzer, and one driver review
instead of five. Drivers, analyzers and the test stack take minor and patch only, because a major version
of a driver is a breaking change for the adapter that wraps it and deserves its own integration run. The
coupled families - AutoFixture, Testcontainers, xunit, the CodeQL actions - move together at every update
type, including major, because they will not resolve against each other across a major boundary and a split
major produces a branch that cannot restore.

Security updates are untouched and stay immediate. The quarterly cadence is about noise; a published
advisory is not noise, and NuGetAudit reports the same advisories in every build in the meantime. The
NSubstitute ignore was re-verified against AutoFixture.AutoNSubstitute's own nuspec rather than assumed:
the [2.0.3, 6.0.0) constraint is still there.

.gitignore now ignores generated output and nothing else. It ignored `**/AssemblyInfo.cs`, which would
silently hide a hand-written one - an ignore rule that matches an authored file is worse than no rule,
because the file never gets added and the build works for you and for nobody else. The bare `obj` and `bin`
entries are anchored so a source directory that happens to be called `bin` is not swallowed.

The pull-request checklist stops asking every contributor to confirm items that do not apply to their
change - the AOT gate, the integration run, the public-API snapshot are conditional now - and no longer
asks for a version bump.

`docfx.json` moved to build/docfx/ in an earlier commit; the workflow's DOCFX_CONFIG follows it.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The commit that re-indented the MSBuild and configuration files is mechanical: leading tabs to two spaces,
534 lines in 17 files, no non-whitespace byte moved. Recording it here keeps `git blame` on those files
pointing at whoever wrote the line rather than at the transformation.

It is a separate commit because a commit cannot record its own id.

⚠️ Merge this branch with a MERGE COMMIT. "Squash and merge" and "Rebase and merge" rewrite every SHA on
the branch, the entry above stops resolving, and git does not warn - blame quietly goes back to pointing at
the tool. CI's lint job fails on an unresolvable revision, which is what makes that visible.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three unrelated fixes to the same class of problem - text that goes stale without anyone noticing.

**Counts are gone.** "The six shipping libraries", "the two test projects", "all five adapters", "the four
database servers", "both target frameworks": every one of those is a number that a new project, adapter or
target framework silently falsifies, in about thirty places across the build files, the workflows, the agent
guidance and the documentation. They now name what they mean - the shipping libraries, the adapters, net8.0
and net10.0 - and a number survives only where the items are listed in the same breath.

**History is gone.** A reader wants to know how the repository works, not how it used to: what .gitignore
once ignored, what clean-build-artifacts.ps1 once deleted, what readiness gate the CI job once carried by
hand, and which sections once lived in the README. Each of those keeps the rule it was justifying and drops
the story.

**"Why not just use Dapper?" is back in README.md and PACKAGE_README.md**, as the first section after the
introduction in both, which is where the question is actually asked. Moving it to a reference page put the
one thing a new reader wants first behind a link. It is the same text as before, and docs/reference/
comparison.md is deleted rather than kept as a second copy that would drift.

Also in .editorconfig: `csharp_style_var_elsewhere` becomes `true:silent`. It said the repository preferred
an explicit type where the right-hand side does not name one, and the repository does not - `var` is used
that way 455 times in src/ and the explicit form essentially never. It stays silent, because that case is a
readability judgement rather than something a build should reject. The top-level-statements comment is
removed; the setting says what it does.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Preflight" says an aviation metaphor; it does not say when to run the script or what it decides. This is
the gate you run before a commit - hygiene, style, formatting, member ordering, a Release build and the unit
suite - and the name now says so, next to the pre-release gate that runs the heavier jobs.

Every reference moves with it: the contribution and agent guidance, the pull-request template, both editor
hook adapters, the sibling scripts and the comment in Directory.Build.props. Nothing about the script's
behaviour changes; CI never referenced it by name.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6442d2c left them in the scripts' comment-based help and console output, in the feature-request template
and in the package-consumption readme and project file: "the six shipping projects", "all six packages",
"all five adapters", "obliges all five". Same rule as before - name the thing, not how many there are - and
the one count that stays is the one whose items are listed in the same sentence.

Two occurrences are deliberately left alone, because changing them would be the first C# edit on this
branch: the `all five providers` remark in src/DbConnectionPlus/Readers/EnumerableReader.cs and the
`all five adapters` comments in tests/package-consumption/AllAdaptersConsumer/Program.cs.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pushing to a public repository and finding out from a red X is a slow way to learn something this machine
could have told you in twenty minutes. scripts/pre-release-gate.ps1 runs everything CI checks that can
honestly be checked locally, in CI's order: the ignored-revision check, the tidiness check, a Release build,
the unit suite and the full integration matrix, the documentation build with warnings as errors, the pack
with package validation, the Native AOT gate on net8.0 and net10.0 against the packages just packed, and
the all-adapters package consumer against the same ones.

It ends in one line - `PASSED: All checks passed.` or `FAILED: Check X failed. See output.` - and it stops
at the first failure, because the later steps consume what the earlier ones produce and a second failure
caused by the first only buries the one that matters.

What it deliberately does not do is reimplement CI. Each omission is named in the script's own help with
the reason: CodeQL and the dependency review need GitHub's services, the Codecov upload needs CI's token,
the Pages deployment needs the Pages environment, publication needs Trusted Publishing and a tag, and the
Linux legs and the .NET-8-SDK-only leg need machines this one is not. A dirty working tree is reported
rather than rejected - CI tests the commit you push, so anything uncommitted is untested by definition, but
you may still be iterating.

`-Version` adds the two checks CI runs immediately before it publishes: the version declared in
Directory.Build.props matches, and CHANGELOG.md holds exactly one dated, non-empty section for it. They are
opt-in because a branch push publishes nothing. `-SkipIntegrationTests` drops the Docker requirement and
`-SkipNativeAot` the C++ toolchain, and both say so in the output rather than passing quietly.

One thing the design has to get right, and it is written into the function so it survives editing: a step
reports through a script-scoped flag, not by returning a value. A script block's output IS its return value
in PowerShell, so capturing it would swallow every line the build and the test run print and - worse - make
a failing step that printed anything at all evaluate as a pass.

Verified end to end on this machine: a full run passes (11809 tests, 0 failed; both AOT frameworks; the
consumer's every assertion), a deliberately wrong -Version fails at the first step with a named reason and
exit 1, and the skip paths run exactly what they say.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A fresh clone whose files are CRLF passes every check both gates make and then fails the Release build
with one "Was not formatted." error per file in the solution. Every one of those errors names CSharpier,
which is not the problem, and the two steps that could have said so had just reported a clean working tree
and a tidy one.

Neither was lying. Git normalizes on read, so nothing that asks git about content can see a line ending:
`git status` reports no change, and tidy-code.ps1 -Check -Scope all commits its disposable copy before it
runs the tools, which stores every file as LF whatever was on disk - CSharpier rewriting CRLF to LF is then
a change `git diff` reports as nothing at all. The build is the first step that reads the bytes, and all it
can say about them is that the formatter is unhappy.

scripts/verify-line-endings.ps1 asks the one question that is not laundered. `git ls-files --eol` reports,
per file, what git stored, what is on disk and the attributes that decide both, and it writes nothing. Both
columns are checked and only one of them is CI's: the stored column is what CI renormalizes to catch a file
committed past .gitattributes, and the on-disk column is the one only a working copy can answer, because a
CI checkout is written from the index seconds earlier and cannot disagree with it. The expected ending is
read from each file's own eol attribute rather than assumed, and a file that declares none is left alone.
The failure names the files and the fix for each column: `git checkout-index --force --all` for a working
tree that drifted, `git add --renormalize .` for a repository that stored the wrong thing.

Both gates run it, before their tidiness step rather than after, and the pre-release gate now has it where
CI has it - the lint job's second check, which was simply missing here. Both gates also ask CSharpier about
the tree itself once the copy-based check passes: that costs about a second and turns "275 files are
unformatted" into a named file and a reason, whether the cause is the line endings or a .csharpierrc or
.editorconfig in a directory ABOVE the repository, which a copy under the temp directory never sees.

Verified by reproducing the original failure - a clone with CRLF sources builds into one error per file -
and then running both gates over it: each stops with the two offending files named, and
`git checkout-index --force --all` returns the tree to a state where both pass and the build succeeds.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t does

`git checkout-index --force --all` is what the check told you to run against a working tree whose endings
had drifted, and on the tree that prompted it, it did nothing at all: exit 0, not a byte written, every
file still CRLF. The advice was tested against a tree whose files had been rewritten after checkout, which
is not the case it exists for.

Git skips a file whose stat information matches the index before it looks at --force. A tree that was
written wrong by whatever produced it - a clone through a tool that ignored the eol attribute, an unpacked
archive, a sync client - matches the index perfectly, which is precisely why `git status` calls it clean
and why nothing noticed. `git checkout -- .` is a no-op for the same reason. The files have to be gone
before git will write them again, which is the recipe gitattributes(5) gives for exactly this:

  git ls-files | ForEach-Object { Remove-Item -LiteralPath $_ -Force }
  git checkout -- .

The failure message now says that, says what it costs - it deletes tracked files before restoring them, so
uncommitted work has to be committed or stashed first - and says what it means if the endings come back:
something other than git is writing them. The script's help records why checking the tree out again cannot
work, so the next person to reach for it finds the answer before running it.

Verified on a clone of this repository put into the state that produced the report: the checkout wrote CRLF
so the index stat matched and `git status` was clean, `git checkout-index --force --all` left all 387 files
CRLF and exited 0, and the delete-then-checkout pair brought every one of them back to LF with a clean
working tree.

Refs #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rent-a-developer rent-a-developer self-assigned this Sep 8, 2026
@rent-a-developer rent-a-developer linked an issue Sep 8, 2026 that may be closed by this pull request
4 tasks
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@rent-a-developer
rent-a-developer merged commit 2299ba2 into main Sep 8, 2026
15 checks passed
@rent-a-developer
rent-a-developer deleted the chore/issue-28-harmonize-repository-tooling branch September 8, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardize repository tooling, build configuration, and documentation

1 participant