Skip to content

Migrate testing to native Microsoft.Testing.Platform and update test packages - #549

Closed
ptr727 wants to merge 1 commit into
resync/eol-lffrom
resync/dotnet-mtp
Closed

Migrate testing to native Microsoft.Testing.Platform and update test packages#549
ptr727 wants to merge 1 commit into
resync/eol-lffrom
resync/dotnet-mtp

Conversation

@ptr727

@ptr727 ptr727 commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Unblocks the Dependabot nuget PRs (#545, #546) and every later nuget bump.

Stacked on #547, so this PR is based on resync/eol-lf and will retarget to
develop automatically once #547 merges. Its own diff is the five files below.

Reworked after ptr727/ProjectTemplate#1111 merged. The first revision used
coverlet.MTP, which was the open recommendation at the time. The hub has since
settled WORKFLOW.md D1.6 on Microsoft.Testing.Extensions.CodeCoverage, and this
PR now follows that instead.

The problem

xunit.v3 4.0.0 removed the VSTest bridge, so the CI unit-test step's
dotnet test --collect:"XPlat Code Coverage" now fails outright on the .NET 10
SDK:

error : Testing with VSTest target is no longer supported by Microsoft.Testing.Platform
on .NET 10 SDK and later. If you use dotnet test, you should opt-in to the new
dotnet test experience.

That is what #545 and #546 have been red on, and it blocks every later bump until
the runner moves.

The fix, per WORKFLOW.md D1.6

Change Why
global.json with {"test":{"runner":"Microsoft.Testing.Platform"}} opts into the native MTP runner. No sdk section, so SDK resolution and roll-forward are untouched.
drop xunit.runner.visualstudio the VSTest adapter MTP replaces
coverlet.collector -> Microsoft.Testing.Extensions.CodeCoverage 18.9.0 coverlet's VSTest data collector is ignored under MTP without failing
CI step -> dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage, then prefix each report to coverage-<guid>.cobertura.xml matches the hub's validate-task.yml byte for byte

Three details are load-bearing, and none of them reds the job on its own:

  • The 18.9.0 floor. Below 18.1.0 the extension is built against
    Microsoft.Testing.Platform 1.x and throws TypeLoadException against the 2.x
    platform xunit.v3 4.0.0 carries. It then runs zero tests and still writes a
    well-formed Cobertura file reporting full coverage.
  • --coverage-output stays unset. Pinning one filename would give every test
    project in a solution the same path, and the last to finish would overwrite the
    rest.
  • The prefix rename. The default <guid>.cobertura.xml that the unset flag
    produces is a name codecov-cli's finder does not match (its patterns are
    *coverage*.* and an exact cobertura.xml), so an unprefixed report uploads
    nothing while the step still exits green.

Package bumps the runner change unblocks: AwesomeAssertions 9.5.0 -> 9.6.0,
xunit.analyzers 1.27.0 -> 2.0.0, xunit.v3 3.2.2 -> 4.0.0.
Microsoft.NET.Test.Sdk stays at 18.9.0 (already current).

.gitignore gains the hub's dotnet coverage block verbatim. The output was
untracked and unignored, so a blanket git add -A after a local coverage run
would have staged it.

Verification

Against the real invocation, not the documented one:

  • 21 tests ran and passed, which is the check that matters given the
    zero-test failure mode above.
  • Resolved graph (from obj/project.assets.json, not the csproj text):
    Microsoft.Testing.Extensions.CodeCoverage/18.9.0,
    Microsoft.Testing.Platform/2.3.3, xunit.v3/4.0.0 with the mtp-v2 variants.
    No coverlet, no xunit.runner.visualstudio.
  • The run wrote bbfde807-....cobertura.xml and the prefix step renamed it to
    coverage-bbfde807-....cobertura.xml, confirming the rename is genuinely
    needed rather than defensive.
  • git check-ignore covers both filename shapes;
    git ls-files -z | xargs -0 git check-ignore -v is empty.
  • validate-task.yml is the only dotnet test caller; publish-release.yml and
    test-pull-request.yml both reach it via uses:, so the publish gate and the
    PR gate move together. No --collect survives anywhere.
  • Build, CSharpier, dotnet format style --verify-no-changes,
    editorconfig-checker, actionlint, markdownlint and cspell all clean.

One thing worth knowing: the reported coverage number moves, because the
engine does. Line rate goes from 0.59 under coverlet to 0.26 here, with
lines-valid 1350 -> 3143, since this engine instruments more of the graph. It
cannot gate anything: codecov.yml sets informational: true on both project
and patch, and the only ruleset-required check is
Check pull request workflow status job.

Notes

  • Dependabot dual-targets develop and main, so Bump AwesomeAssertions and 3 others #546 (the main copy) stays
    blocked until this reaches main via the promotion PR. main's tree is not
    broken in the meantime, it just cannot take the bump.
  • The fleet audit flags this repo's local validate-task.yml as hub-only.
    Now that Promote develop to main ProjectTemplate#1111 has landed MTP support in the hub's
    reusable workflow, that migration is unblocked, but it is a separate interface
    change covering five workflow files and belongs in its own PR rather than here.

Copilot AI lite review requested due to automatic review settings August 30, 2026 04:14
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • main
  • develop

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5f0370ef-86a9-4b40-8823-fe407d11a443

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Migrate tests and coverage collection to native MTP

🐞 Bug fix ✨ Enhancement ⚙️ Configuration changes 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Opt into native MTP to restore xUnit v4 testing on .NET 10.
• Replace VSTest coverage collection with coverlet.MTP Cobertura output for Codecov.
• Upgrade test dependencies and ignore generated coverage artifacts.
Diagram

graph TD
  G["global.json"] --> D["dotnet test"] --> X["xUnit v4"] --> C["coverlet.MTP"] --> O["Cobertura output"] --> U["Codecov upload"]
  W["Validation workflow"] --> D
Loading
High-Level Assessment

The native MTP migration is the appropriate approach because xUnit v4 no longer supports the VSTest bridge on .NET 10. Pinning older xUnit packages or retaining the collector would only defer dependency upgrades or silently lose coverage; the selected coverlet.MTP integration preserves the existing Codecov workflow.

Files changed (5) +20 / -10

Bug fix (1) +4 / -2
validate-task.ymlRun CI tests and coverage through native MTP +4/-2

Run CI tests and coverage through native MTP

• Replaces the VSTest '--collect' invocation with coverlet.MTP arguments that emit Cobertura reports into the existing Codecov directory. Comments document why the native runner requires the new command.

.github/workflows/validate-task.yml

Documentation (1) +4 / -0
AGENTS.mdDocument the native MTP testing workflow +4/-0

Document the native MTP testing workflow

• Explains the repository-wide MTP runner selection, local test command, CI coverage command, and incompatibility of the former VSTest collector syntax.

AGENTS.md

Other (3) +12 / -8
.gitignoreIgnore generated Cobertura coverage artifacts +3/-0

Ignore generated Cobertura coverage artifacts

• Ignores the coverage output directory and both standard and timestamped Cobertura XML filenames produced by local MTP coverage runs.

.gitignore

CreateMatrixTests.csprojUpgrade tests and replace VSTest coverage packages +4/-8

Upgrade tests and replace VSTest coverage packages

• Upgrades AwesomeAssertions, xUnit, and xUnit analyzers. Removes the Visual Studio runner and VSTest coverlet collector in favor of the self-registering coverlet.MTP package.

CreateMatrixTests/CreateMatrixTests.csproj

global.jsonSelect Microsoft.Testing.Platform for dotnet test +5/-0

Select Microsoft.Testing.Platform for dotnet test

• Adds repository-wide test configuration that opts into the native Microsoft.Testing.Platform runner without constraining SDK selection or roll-forward behavior.

global.json

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@codecov

codecov Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.01%. Comparing base (2efaeb2) to head (dc48c3b).

Additional details and impacted files
@@                Coverage Diff                @@
##           resync/eol-lf     #549      +/-   ##
=================================================
- Coverage          57.28%   57.01%   -0.27%     
=================================================
  Files                 15       15              
  Lines               1386     1375      -11     
  Branches              89      108      +19     
=================================================
- Hits                 794      784      -10     
  Misses               573      573              
+ Partials              19       18       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The changes are small, consistent across docs/tests/CI, and fully remove the incompatible VSTest-based coverage invocation needed for .NET 10 + xunit.v3 4.0.0.

Pull request overview

This PR migrates the repository’s unit test execution from the legacy VSTest-based flow to the native Microsoft.Testing.Platform runner (required for .NET 10 + xunit.v3 4.0.0), unblocking current and future Dependabot NuGet upgrades and restoring CI coverage generation.

Changes:

  • Opt into the Microsoft.Testing.Platform runner via global.json.
  • Update the test project’s package set for xUnit v3 4.0.0 compatibility (drop VSTest adapter/collector; use coverlet.MTP).
  • Update CI to run dotnet test with Coverlet MTP arguments and ignore generated coverage artifacts in git.
File summaries
File Description
global.json Opts dotnet test into the native Microsoft.Testing.Platform runner.
CreateMatrixTests/CreateMatrixTests.csproj Updates test dependencies for xUnit v3 4.0.0 and switches coverage to coverlet.MTP.
AGENTS.md Documents the repo’s testing/coverage invocation under the native MTP runner.
.gitignore Prevents accidental staging of generated coverage output files.
.github/workflows/validate-task.yml Replaces --collect (VSTest) with MTP-compatible Coverlet flags and preserves Codecov ingestion path.
Review details
  • Files reviewed: 4/5 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…packages

xunit.v3 4.0.0 drops the VSTest bridge, so `dotnet test --collect:"XPlat Code
Coverage"` fails outright on the .NET 10 SDK with "Testing with VSTest target is
no longer supported by Microsoft.Testing.Platform". That is what has been
blocking the nuget-deps bumps in #545 and #546, and it blocks every later bump
until the runner moves.

Follow WORKFLOW.md D1.6 as the hub settled it in ptr727/ProjectTemplate#1111,
which chose Microsoft.Testing.Extensions.CodeCoverage over coverlet:

- `global.json` opts into the `Microsoft.Testing.Platform` runner. It carries no
  `sdk` section, so SDK resolution and roll-forward are untouched.
- `xunit.runner.visualstudio` is dropped, the VSTest adapter having no role under
  native MTP.
- `coverlet.collector` becomes `Microsoft.Testing.Extensions.CodeCoverage`
  18.9.0, whose predecessor's VSTest data collector MTP ignores without failing.
  The floor is load-bearing rather than cautionary: below 18.1.0 the extension is
  built against Microsoft.Testing.Platform 1.x and throws a `TypeLoadException`
  against the 2.x platform xunit.v3 4.0.0 carries, running zero tests while still
  writing a well-formed Cobertura file that reports full coverage.
- The CI unit-test step becomes
  `dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage`,
  then prefixes each report to `coverage-<guid>.cobertura.xml`. Both halves are
  load-bearing and neither reds the job on its own: `--coverage-output` stays
  unset so a second test project could not overwrite the first, and the default
  `<guid>.cobertura.xml` that produces is a name codecov-cli's finder does not
  match, its patterns being `*coverage*.*` and an exact `cobertura.xml`.

Bundle the four package bumps the runner change unblocks: AwesomeAssertions
9.5.0 -> 9.6.0, xunit.analyzers 1.27.0 -> 2.0.0, xunit.v3 3.2.2 -> 4.0.0, and
`xunit.runner.visualstudio` removed rather than bumped to 4.0.0.

`.gitignore` gains the hub's coverage block. The output was untracked and
unignored, so a blanket `git add -A` after a local coverage run would have staged
it.

Verified against the real invocation, not the documented one: 21 tests ran and
passed (not the zero the version-floor trap produces), the extension resolved at
18.9.0 on Microsoft.Testing.Platform 2.3.3, the run wrote
`<guid>.cobertura.xml` and the prefix step renamed it as intended, both filename
shapes are ignored while nothing tracked is, and the build, CSharpier,
`dotnet format style --verify-no-changes`, editorconfig-checker, actionlint,
markdownlint and cspell gates are all clean.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The runner/package/workflow changes are internally consistent (docs, CI invocation, and ignores match) and no remaining --collect/coverlet adapter usage is present in the repo.

Review details
  • Files reviewed: 4/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@ptr727

ptr727 commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #553.

GitHub closed this automatically when its base branch resync/eol-lf was deleted as part of merging #547. That was my sequencing error: with a stacked set, the children have to be retargeted before the parent is merged with --delete-branch, and I did it the other way round. Reopening is not possible once the base branch is gone.

#553 is the same branch and the same content, targeting develop directly. The review history here still stands: this PR reached CLEAN with zero findings across two rounds, and nothing about the change has altered since.

ptr727 added a commit that referenced this pull request Aug 30, 2026
…packages (#553)

Unblocks the Dependabot nuget PRs (#545, #546) and every later nuget
bump.

**Replaces #549**, which GitHub closed automatically when its base
branch
`resync/eol-lf` was deleted on #547's merge. Same branch, same content,
same
review history on the closed PR. This one targets `develop` directly.

The branch carries a `-s ours` merge of `develop` recorded after #547
squashed.
That is lossless here and was verified before recording: `git diff
origin/develop
2efaeb2` is empty, so the squash reproduced this branch's own ancestor
exactly,
and that ancestor is reachable from this branch, so `develop` carries
nothing the
branch lacks. The diff against `develop` is the five files below and
nothing
else.

> Reworked after ptr727/ProjectTemplate#1111 merged. The first revision
used
> `coverlet.MTP`, which was the open recommendation at the time. The hub
has since
> settled WORKFLOW.md D1.6 on
`Microsoft.Testing.Extensions.CodeCoverage`, and this
> PR now follows that instead.

## The problem

`xunit.v3` 4.0.0 removed the VSTest bridge, so the CI unit-test step's
`dotnet test --collect:"XPlat Code Coverage"` now fails outright on the
.NET 10
SDK:

```text
error : Testing with VSTest target is no longer supported by Microsoft.Testing.Platform
on .NET 10 SDK and later. If you use dotnet test, you should opt-in to the new
dotnet test experience.
```

That is what #545 and #546 have been red on, and it blocks every later
bump until
the runner moves.

## The fix, per WORKFLOW.md D1.6

| Change | Why |
| --- | --- |
| `global.json` with `{"test":{"runner":"Microsoft.Testing.Platform"}}`
| opts into the native MTP runner. No `sdk` section, so SDK resolution
and roll-forward are untouched. |
| drop `xunit.runner.visualstudio` | the VSTest adapter MTP replaces |
| `coverlet.collector` -> `Microsoft.Testing.Extensions.CodeCoverage`
18.9.0 | coverlet's VSTest data collector is ignored under MTP without
failing |
| CI step -> `dotnet test --coverage --coverage-output-format cobertura
--results-directory ./coverage`, then prefix each report to
`coverage-<guid>.cobertura.xml` | matches the hub's `validate-task.yml`
byte for byte |

Three details are load-bearing, and none of them reds the job on its
own:

- **The 18.9.0 floor.** Below 18.1.0 the extension is built against
Microsoft.Testing.Platform 1.x and throws `TypeLoadException` against
the 2.x
platform xunit.v3 4.0.0 carries. It then runs **zero tests** and still
writes a
  well-formed Cobertura file reporting full coverage.
- **`--coverage-output` stays unset.** Pinning one filename would give
every test
project in a solution the same path, and the last to finish would
overwrite the
  rest.
- **The prefix rename.** The default `<guid>.cobertura.xml` that the
unset flag
produces is a name codecov-cli's finder does not match (its patterns are
`*coverage*.*` and an exact `cobertura.xml`), so an unprefixed report
uploads
  nothing while the step still exits green.

Package bumps the runner change unblocks: AwesomeAssertions 9.5.0 ->
9.6.0,
xunit.analyzers 1.27.0 -> 2.0.0, xunit.v3 3.2.2 -> 4.0.0.
`Microsoft.NET.Test.Sdk` stays at 18.9.0 (already current).

`.gitignore` gains the hub's dotnet coverage block verbatim. The output
was
untracked and unignored, so a blanket `git add -A` after a local
coverage run
would have staged it.

## Verification

Against the real invocation, not the documented one:

- 21 tests **ran** and passed, which is the check that matters given the
  zero-test failure mode above.
- Resolved graph (from `obj/project.assets.json`, not the csproj text):
  `Microsoft.Testing.Extensions.CodeCoverage/18.9.0`,
`Microsoft.Testing.Platform/2.3.3`, `xunit.v3/4.0.0` with the `mtp-v2`
variants.
  No coverlet, no `xunit.runner.visualstudio`.
- The run wrote `bbfde807-....cobertura.xml` and the prefix step renamed
it to
`coverage-bbfde807-....cobertura.xml`, confirming the rename is
genuinely
  needed rather than defensive.
- `git check-ignore` covers both filename shapes;
  `git ls-files -z | xargs -0 git check-ignore -v` is empty.
- `validate-task.yml` is the only `dotnet test` caller;
`publish-release.yml` and
`test-pull-request.yml` both reach it via `uses:`, so the publish gate
and the
  PR gate move together. No `--collect` survives anywhere.
- Build, CSharpier, `dotnet format style --verify-no-changes`,
  editorconfig-checker, actionlint, markdownlint and cspell all clean.

**One thing worth knowing:** the reported coverage number moves, because
the
engine does. Line rate goes from 0.59 under coverlet to 0.26 here, with
lines-valid 1350 -> 3143, since this engine instruments more of the
graph. It
cannot gate anything: `codecov.yml` sets `informational: true` on both
project
and patch, and the only ruleset-required check is
`Check pull request workflow status job`.

## Notes

- Dependabot dual-targets `develop` and `main`, so #546 (the `main`
copy) stays
blocked until this reaches `main` via the promotion PR. `main`'s tree is
not
  broken in the meantime, it just cannot take the bump.
- The fleet audit flags this repo's local `validate-task.yml` as
`hub-only`.
Now that ptr727/ProjectTemplate#1111 has landed MTP support in the hub's
reusable workflow, that migration is unblocked, but it is a separate
interface
change covering five workflow files and belongs in its own PR rather
than here.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Improvements**
* Improved automated test execution and code coverage reporting in
continuous integration.
* Coverage reports are now generated in a standardized format for more
reliable analysis.

* **Documentation**
* Added guidance for running tests locally and understanding coverage
validation.

* **Chores**
* Updated testing tools and configuration to use the modern test
platform.
* Added rules to keep generated test and coverage files out of source
control.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
ptr727 added a commit that referenced this pull request Aug 30, 2026
…nfig (#550)

The mechanical half of the ProjectTemplate resync. Audit run
`2026-08-30T03:26:30Z | hub a378121`, re-measured against hub `f3b4cc9`.

Stacked on #549 (which is stacked on #547), so this PR is based on
`resync/dotnet-mtp` and will retarget as its parents merge. Its own diff
is the
five items below.

## What changed

**`.github/skills/` (37 files), carried whole.** A manifest-owned tree,
applied
with the hub's own `scripts/carry.py apply` rather than by hand.
`carry.py check`
now reports `sourceDigest == targetDigest ==
c5475deb46bfbf1f9253bc6128a40b7686687597ebe38a6cd1c580a39817f03e` with
`extra`,
`missing` and `modified` all empty, so this is the hub's content byte
for byte.

**`host-tools.json`**, the repo's own tighten-only overlay on the fleet
host-tool
declaration. The `tools` list is empty because this repo needs nothing
the fleet
declaration does not already carry, and the file is present anyway so
the
declaration is somewhere a reader can find rather than somewhere they
have to know
to look. The hub's `$schema` pointer is deliberately not carried: it is
a relative
path to a hub-only schema, and the hub's own note instructs a copying
repo to
leave it behind.

**`.markdownlint-cli2.jsonc`** re-vendored whole, it being `verbatim`
fidelity.
The only real change is two comment lines, `markdown` -> `Markdown`.

**`.editorconfig-checker.json`** takes the hub's `Exclude` list. The
entries are
Python cache directories, inert here, carried whole per the fleet's
config-carry
model. `Exclude` is additive to the tool's built-in defaults, so it can
only
narrow the scan, never widen it. The `Disable` block is untouched.

**`cspell.json` becomes the single source of truth.** It is now the
union of three
lists: the hub's 134 words, this repo's existing 164, and the 97-word
`cSpell.words` block that lived in `NxWitness.code-workspace`, 29 of
which existed
nowhere else. That workspace block is deleted, per CODESTYLE.md
"Markdown and
Spelling".

## Verification

- Set-checked the word union against all three sources: **zero words
dropped**,
zero extras not traceable to a source, zero exact duplicates. Every
non-`words`
key (`version`, `language`, `ignorePaths`, `ignoreRegExpList`) preserved
with
  its value and position. The original carried no JSONC comment to lose.
- `NxWitness.code-workspace` still parses as JSONC. Diffing the parsed
objects
before and after, `settings` differs by exactly one removed key and
nothing
  else; `folders` and `extensions` are identical. The
`streetsidesoftware.code-spell-checker` recommendation is retained, so
the
  editor still reads `cspell.json` from the workspace root.
- `.markdownlint-cli2.jsonc` byte-matches the hub, as `verbatim`
requires.
- `host_gate.py --repo <this checkout>` returns `0 issue(s) over 8
declared
  tool(s)`, and the file validates against the hub's
  `spec/host-tools-local.schema.json`, which marks `$schema` optional.
- markdownlint clean across all 45 markdown files (up from 8), cspell
clean at the
CI scope, editorconfig-checker clean, every touched JSON/JSONC file
parses, and
  no tracked file carries a CR.

## Deliberately not in this PR

Three audit findings are real but do not belong to the mechanical class,
and one
is not a defect at all:

- **The `AGENTS.md` split** into `CLAUDE.md`, `GOVERNANCE.md`,
`ARCHITECTURE.md`
and `OPERATIONS.md`. A distinctive-phrase probe against the hub
canonical found
that this repo's `AGENTS.md` predates the router split and mixes stale
fleet law
with substantial local content, including the entire "Template
adaptations"
record of deliberate deviations. Re-vendoring over it would delete that
  silently, which is the exact incident the fleet's
`carried-instruction-file-guard` exists to prevent. That needs its own
PR and
  its own review.
- **The `repo-config/` retirement.** `spec/divergences.json` marks the
whole tree
`retire`, but the deletion owes a tree-wide reference sweep, and the
live
inbound references are in `AGENTS.md` and `WORKFLOW.md`, both of which
the split
  PR rewrites. Doing the deletion here would edit those files twice.
- **README structure** (10 letter-class findings). Content work, grouped
with the
  doc PR.
- **The two `interface` findings are not drift to fix.**
`publish-release.yml`
"missing required job `publish`" and `merge-bot-pull-request.yml`
"missing
required job `merge-bot`" both resolve to *calling a hub-hosted task
workflow*
(`build-release-task.yml`, `merge-bot-task.yml`) that this repo has not
adopted.
`spec/divergences.json` states adoption "is a separate, later change per
repo".
This repo's multi-image, shared-base fan-out is a documented deviation,
so
renaming jobs to satisfy the checker would misreport the state rather
than fix
  it.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Added guidance for repository setup, coding standards, testing,
reviews, releases, worktrees, contributions, host tools, and instruction
preservation.
* Added references for Markdown links, line endings, project
configuration, testing, release publishing, and workflow guarantees.

* **Maintenance**
  * Expanded spelling and Markdown terminology coverage.
* Excluded Python caches and virtual environments from configuration
checks.
  * Added a repository-level host-tools configuration placeholder.
  * Removed the workspace-specific spelling dictionary.

* **Security**
  * Restricted validation workflows to read-only repository permissions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
ptr727 added a commit that referenced this pull request Aug 30, 2026
…551)

The judgment half of the ProjectTemplate resync: the `AGENTS.md` split,
and the
`repo-config/` retirement that depends on it.

Stacked on #550 (which is stacked on #549 and #547), so this PR is based
on
`resync/hub-conformance` and will retarget as its parents merge.

## Why this is not a re-vendor

This repo's `AGENTS.md` predated the hub's `AGENTS.md`/`GOVERNANCE.md`
router
split, so it held two different things in one 243-line file: stale
copies of fleet
law, and local rules written for faults the fleet has not seen
elsewhere. Copying
the hub canonical over it would have deleted the second kind silently,
with no
error and nothing in the diff that looks wrong. That is the exact
incident the
fleet's `carried-instruction-file-guard` exists to prevent.

So every unit was probed by distinctive phrase against the hub canonical
first,
and each one that turned out to be local got a destination rather than a
deletion.
A 29-phrase preservation checklist taken from the pre-split file
confirms every
unit has a home.

## What moved where

| | |
| --- | --- |
| `AGENTS.md` | 243 lines -> 115. Only the three declared sections, all
three byte-identical to the hub. |
| `CLAUDE.md` | New, byte-matches the hub. Claude Code reads `CLAUDE.md`
and never `AGENTS.md`, so without it that provider had no rules at all.
|
| `GOVERNANCE.md` | New, 21 sections. The 18 verbatim ones byte-match
the hub; `Devcontainer` and `Repository Layout` are intent-fidelity and
written for this repo. |
| `ARCHITECTURE.md` | New. Product and variant matrix, codegen data
flow, base/derived image relationship, CI pipeline with its
do-not-reintroduce list, and the Template Adaptations record. |
| `OPERATIONS.md` | New. The six mandated headings, `Local Verification`
first. |
| `CODESTYLE.md` | Gains the encoding rule and the
human-authored-comment rule, both stated in the old `AGENTS.md`. |

**`Where the Rules Live` is carried unedited.** The first draft added
two table
rows pointing at the new docs. That section is declared `verbatim`, and
`spec/fidelity-model.md` normalizes only line endings, action pins and
job
`needs:`, so those rows would have made it modified fixed content with
no
disposition on file, rendering UNTRIAGED in the divergence report. The
two docs
are routed from the preamble instead, which is not a declared section.

**The Template Adaptations record is the piece that most needed
preserving.** All
ten bullets survive. Without them, every one of this repo's deliberate
deviations
from the fleet template reads as unexplained drift to the next audit.

**Two rules were nearly lost and are restored.** "Leave human-authored
comments
exactly as written" is now a `CODESTYLE.md` item, and it matters because
the
carried comment rules push the other way: they tell an agent to collapse
a short
two-line comment, with nothing telling it to leave a maintainer's alone.
The
encoding rule survived only as `.editorconfig`'s `charset` and is now
stated.

**One claim the old file carried is false and is corrected, not
copied.** "Linting
is editor-only (no CI lint job)" is contradicted by `validate-task.yml`,
which runs
markdownlint, cspell, actionlint and editorconfig-checker inside the
required
check. `OPERATIONS.md` says what actually runs.

## The `repo-config/` retirement

`spec/divergences.json` marks the payloads, the script and the reference
as
hub-hosted, so this repo reaches them rather than carrying a copy that
drifts. The
deletion swept every inbound reference:

- six sites in `WORKFLOW.md`, which now name the behavior and the
hub-hosted
  command instead of a local path,
- a comment in `test-pull-request.yml`,
- and the `Repo Config` solution folder in `NxWitness.slnx`, which would
otherwise
have shown five missing files in Visual Studio while `dotnet sln list`
stayed
  silent about it.

## Pointers that moved with the sections

Splitting a file moves the anchors other files point into, so those move
too: four
anchors in `.github/copilot-instructions.md` and three in `CODESTYLE.md`
now
resolve to `GOVERNANCE.md`, `WORKFLOW.md`'s D3.3 aside names the section
that
holds the rule, and `publish-release.yml`'s comment names
`ARCHITECTURE.md`.
`.github/copilot-instructions.md` also gains its declared `Reviewing
Carried Fleet
Content` section, and its closing paragraph states the behavior rather
than naming
the template repo and an anchor that no longer exists, which closes the
audit's
`carried:` finding against that file.

## Verification

- The 29-phrase preservation checklist: every unit has a home.
- All three `AGENTS.md` sections, all 18 verbatim `GOVERNANCE.md`
sections, and
`CLAUDE.md` byte-match the hub. Only the two declared-`intent` sections
differ.
- `OPERATIONS.md` carries exactly the six mandated headings in the
mandated order,
  each with content.
- `NxWitness.slnx` still parses as XML and the solution still builds
clean.
- Every relative Markdown link in a changed file resolves.
- No `repo-config/` reference survives outside carried hub content that
  legitimately means the hub's own copy.
- markdownlint (48 files), cspell, editorconfig-checker and actionlint
all clean.

## Known, not fixed here

`README.md` has a pre-existing broken relative link
(`./LSIO/etc/s6-overlay/s6-rc.d/init-nx-relocate/run`). It is untouched
by this
commit and belongs with the README-structure work, which is the last
audit class
still open.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Documentation**
- Added architecture and operations guides covering products, build
workflows, verification, CI/CD, troubleshooting, and recovery.
- Added governance guidance for repository standards, releases,
security, reviews, tooling, and supported platforms.
- Added a Claude Code entry point and updated contributor guidance,
workflow documentation, and coding standards.
  - Expanded the spelling dictionary with project-specific terminology.

- **Chores**
- Moved repository-configuration references from local files to
centrally maintained configuration.
- Removed obsolete repository configuration files and solution-folder
entries.
- Updated workflow comments and documentation links to reflect the new
structure.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
ptr727 added a commit that referenced this pull request Aug 30, 2026
The last audit class: eleven `readme-structure` findings. Audit run
`2026-08-30T03:26:30Z | hub a378121`.

Stacked on #551, so this PR is based on `resync/docs-split` and will
retarget as
its parents merge.

## The eleven findings

| Finding | Fix |
| --- | --- |
| H1 is not the repo name | `# NxWitness` |
| Tagline carries Markdown links | Link-free plain text, one sentence,
72 chars. The original sentence survives as the free prose below it,
where links are allowed. |
| No `## Questions or Issues` | Added. Carries the support routing moved
out of `## Troubleshooting`, which keeps its own subsections. |
| No `## 3rd Party Tools` | Added. The fleet's fixed lead line, then 23
entries alphabetized. |
| No `### Releases` | Added under Build and Distribution, with the base
class's GitHub release and pre-release shields plus the 40 Docker
version shields moved up from the old `## Releases`. |
| `[license-link]` points at a repo path | `[license]`, bare, since an
in-repo path takes no suffix. |
| Ten `[hub<name>-link]` references | Renamed to
`<target>-docker-hub-link`, the shape that covers a repo shipping one
image and one shipping ten. |
| 161 definitions ungrouped | Grouped under the five declared headers in
spec order, sorted by label within each. |

The tagline is also mirrored into `HISTORY.md`, which the spec requires
and which
the old pair could never satisfy at once, since the shared sentence
carried a
Markdown link. Its now-orphaned `networkoptix-link` definition goes with
it.

For the tool list, every tool the hub catalogs uses the catalog's link
and
description verbatim; the rest are described as what the tool is rather
than as
what this repo does with it.

## Defects found while restructuring

- The NxGo-LSIO install bullet linked the **Nx Witness** LSIO image.
- The Products list linked **Wisenet WAVE** at `dwspectrum.com`.
- A display filename read `Test.yaml` where the file is `Test.yml`.
- `./LSIO/etc/s6-overlay/s6-rc.d/init-nx-relocate/run` resolved to
nothing.
Fixing the README alone would have been a symptom fix:
`CreateMatrix/Dockerfile.cs`
  emits that comment into every LSIO Dockerfile, so the next codegen run
  reproduces it. The generator and the five generated files now name
`Docker/s6-overlay/...`. Those files carry only that comment change; the
upstream version bump a full regeneration also produces belongs to the
codegen
  bot.
- `Docker/README.md`, the Docker Hub overview, still opened with the
retired
title and tagline, so it disagreed with every other surface about the
project's
  name.
- A spaced hyphen joining two clauses, which the character-set rule bans
in the
  same terms as an em dash.

## A correction to #549's stated verification

`OPERATIONS.md` asserted that the local test command is plain `dotnet
test`. That
is unverified and false on at least one machine: a pristine checkout of
the
migration commit reports `Zero tests ran` and exit 5 there, while CI
runs the same
command on the same SDK (10.0.400, runtime 10.0.11) and reports 21
passed. My
earlier local "21/21" came from a build tree still carrying state from
the
coverlet configuration it replaced.

The configuration is correct, and CI is the evidence. The runbook now
states the
invocation CI actually runs as the one to reproduce locally, says that
an MTP run
discovering nothing exits 5 rather than passing silently so the count is
what to
read, and names the direct `dotnet
CreateMatrixTests/bin/Debug/net10.0/CreateMatrixTests.dll`
run as the way to separate a driver problem from a test-project problem.
The
`net10.0` versus `net10.0|x64` target string is the tell. Filed upstream
as
ptr727/ProjectTemplate#1122, since D1.6 governs this for the whole fleet
and the
remaining dotnet repos will hit it.

## Verification

- Every label has a definition and every definition is used, 161 of 161,
no
duplicates. Every relative target exists, every in-page anchor resolves,
the
five group headers appear in spec order with labels sorted within each.
- The Table of Contents matches the actual headings one for one, in
order.
- The ten image names match `Make/Matrix.json`; the base images match
the two base
  Dockerfiles.
- Build clean, markdownlint (48 files), cspell, editorconfig-checker and
  actionlint clean, no new cspell word needed.

## The 2.15 release is now documented

This started as "reported, not fixed", and changed after the maintainer
ruled on
it. Recording the sequence, because the PR title does not suggest a
release-notes
change.

`version.json` has declared 2.15 since 2026-06-29 and releases 2.15.43
through
2.15.59 have published since 2026-07-27, but neither `HISTORY.md` nor
the README's
Release Notes ever gained an entry, so both still described 2.14 as
current. The
bump came from #461, a large CI/CD migration that listed
`version floor 2.14 -> 2.15` as one line item and added no changelog
entry.

The entry is derived rather than invented. Reading every merge between
the 2.14
entry and now, 2.15 is the branch-scoped triggered-Docker CI/CD
migration, the
Codecov upload, multi-arch on `main` only, the lint-architecture
standardization,
and workflow hygiene. Nothing in it changes a published image, and the
entry says
exactly that rather than dressing infrastructure work up as a product
release:

```text
- Version 2.15:
  - Build, CI, and repository tooling changes only. No functional change to the published images.
```

**The floor is not rolled back**, per the maintainer: forward only once
a release
is cut. That is also the only mechanically safe answer, since NBGV
derives the
patch from git height, so a lower floor would generate versions sorting
below what
is already published. #437 could revert a bump cleanly
because
nothing had shipped at it; that window is closed here.

The wider question, that agents have moved this repo's floor five times
under a
develop-leads-main cadence the fleet has since retired, is filed as
ptr727/ProjectTemplate#1124 rather than addressed here.

## Reported, not fixed

- The GitHub About description still carries the retired sentence. It
feeds the
Docker Hub short description through the docker-readme task, so that
surface
will disagree with the README until it is set by hand.
`registry/repos.json`
declares no `description` for this repo, so `configure.sh apply` cannot
write
  it.
- `HISTORY.md`'s 2.11 entry carries a lowercase `docker` in prose. It is
a shipped
changelog record rather than current prose, so it was left rather than
edited
  for casing.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Expanded setup, installation, image variants, release channels,
publishing, build workflows, troubleshooting, and support guidance.
  * Clarified supported NxWitness and OEM-branded VMS products.
* Added testing, diagnostics, architecture checks, and coverage
instructions.
* Updated release history for version 2.15, corrected links, and removed
an obsolete reference.
* **Chores**
* Updated documentation references for relocated runtime initialization
paths.
  * Improved spell-check dictionary consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

2 participants