Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .agents/skills/dotnet-codestyle/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ The .NET mechanics, narrowest first:

xUnit v3 (`xunit.v3`, not the legacy `xunit`) + AwesomeAssertions (`.Should()` API, never native
asserts). Arrange-Act-Assert pattern, descriptive underscore names, `[Theory]`/`[InlineData]` for
parameterized tests. See `references/testing.md` for the framework setup template.
parameterized tests. A test project on `xunit.v3` 4.0.0 or later is MTP-based, and also carries a `global.json` runner declaration, a `Microsoft.Testing.Extensions.CodeCoverage` floor, and no `xunit.runner.visualstudio`. See `references/testing.md` for the framework setup template and that configuration.

## Project configuration

Expand Down
16 changes: 16 additions & 0 deletions .agents/skills/dotnet-codestyle/references/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@
2. **Organization**: Arrange-Act-Assert pattern.
3. **Naming**: descriptive names with underscores.
4. **Theory tests**: use `[Theory]` with `[InlineData]`.

## Microsoft.Testing.Platform and coverage

A test project on `xunit.v3` 4.0.0 or later is MTP-based, and the .NET 10 SDK and later refuse to run one through the VSTest target, so such a project also carries:

- a root **`global.json`** declaring `{"test": {"runner": "Microsoft.Testing.Platform"}}`, which is what selects the driver `dotnet test` runs the project through,
- **`Microsoft.Testing.Extensions.CodeCoverage`** at **18.9.0 or later**, in place of `coverlet.collector`, whose VSTest data collector MTP ignores without failing,
- no **`xunit.runner.visualstudio`**, the VSTest adapter MTP replaces.

A project not yet MTP-based keeps the VSTest collector, and that lagging state is a migration owed rather than drift, until its own `xunit.v3` bump forces the move.

**The version floor is load-bearing rather than cautionary.** Below 18.1.0 the extension is built against Microsoft.Testing.Platform 1.x, and an 18.0.x resolution, which is what a `>= 18.0.0` range picks, throws a `TypeLoadException` against the 2.x platform `xunit.v3` 4.0.0 carries, runs zero tests, and **still writes a well-formed Cobertura file reporting full coverage**, so only the non-zero exit says the run reported nothing. 18.9.0 is the first release on Microsoft.Testing.Platform 2.3.x, where every test project writes into the one shared `--results-directory` the invocation names rather than resolving that relative path per project.

The CI invocation `WORKFLOW.md` D1.6 requires is `dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage`. Two further details of it are equally load-bearing, and neither failure reds the job on its own. `--coverage-output` stays unset, because pinning one filename gives every test project in the solution the same path and a solution with more than one then keeps only whichever ran last. Leaving it unset produces the default name `<guid>.cobertura.xml`, which `codecov-cli`'s own file finder does not match, so the report is renamed before the upload reads the directory, per `WORKFLOW.md` D1.6.

**Diagnosing a local run.** `dotnet test` under the CI configuration reports zero tests on some machines where CI reports the full suite on the same SDK, which reads as a broken repository and is a broken driver. The target string the run prints separates the two: `net10.0` with no architecture means the driver resolved none, and `net10.0|<arch>` with no tests means the tests did not register, which is the case that points back at the three requirements above.
9 changes: 7 additions & 2 deletions .agents/skills/operational-vs-release-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,15 @@ rather than guessing from the repo's contents.
`HISTORY.md`, and release notes name the version as `Version 1.0` (the floor), never the concrete
build height, which is both wrong (the real height differs) and a maintenance trap.
"Correcting" `1.0` to `1.0.0` is a defect.
- **A no-op publish (unchanged NBGV `SemVer2`) re-pushes nothing to any target keyed on the
version string, except Docker, which always re-pushes** to pick up upstream base-image
- **A no-op publish on a schedule or push trigger (unchanged NBGV `SemVer2`) re-pushes nothing to
any target keyed on the version string, except Docker, which always re-pushes** (a dispatch
refreshes the release instead of skipping) to pick up upstream base-image
refreshes. Full guarantee and the `version.json` `pathFilters` boundary:
`references/release-publish-mechanics.md`.
- **A package push can fail after the release is already cut**, since it runs after the release
task and no gate covers it. A full re-run is always available inside its bounded
window and is the only route once the branch tip has moved:
`references/release-publish-mechanics.md`.
- **Adding, dropping, or wiring a release target** (which leaf task, which artifact-naming
contract, which seam a given output belongs to: a GitHub Release asset, a package-registry push,
an image-registry push, a filesystem deploy, or a source-only repo with no build layer at all),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

Full detail for the "Publishing" rules in `SKILL.md`. Load this when adding or removing a release
target, wiring a new leaf build task, deciding where a build output belongs (a GitHub Release
asset, a package-registry push, an image push, a deploy), or setting up a wrapper repo that tracks
an upstream release, not for reading the release model's shape (the SKILL.md summary covers that).
asset, a package-registry push, an image push, a deploy), recovering a package push that failed
after the release was already cut, or setting up a wrapper repo that tracks an upstream release,
not for reading the release model's shape (the SKILL.md summary covers that).

## Reusable-task parameter contract

Expand Down Expand Up @@ -128,6 +129,26 @@ NBGV git height and therefore `SemVer2`, and the next publish *does* create a fr
even when the shipped binary is byte-identical. This is accepted NBGV behavior, and `pathFilters`
are intentionally not added.

## Recovering a failed registry push

A package publish job is gated like everything else, `needs:` the release-task call, so a failed build skips it. The **push inside it** is what no gate can reach, because it runs after the whole release task and therefore after `github-release`. `WORKFLOW.md` D4.5 names the two recovery routes and leaves their mechanics here. A rejected token exchange, a registry outage, or a trusted-publishing policy naming the wrong workflow file leaves a published release and tag for a version that never reached the registry. The recovery is a re-dispatch or a full re-run rather than a cleanup. **A full re-run is always available inside its window, and a re-dispatch only while the branch tip has not moved**, so the tip decides whether there is a choice at all rather than which route to take. What re-dispatch buys, where it is available, is that it outlives the re-run window.

**Re-dispatch, available only while the tip has not moved.** A `workflow_dispatch` takes a ref rather than a commit, and D2.3 admits only `main` or `develop`, so what it builds is that branch's tip at dispatch time. While the tip is still the commit whose push failed, a re-dispatch rebuilds the same version and runs its push again, refreshing the release the way any dispatch does.

This is a time-of-check-to-time-of-use race rather than a guarded operation: nothing compares the tip against the failed run, so a push landing between the two mints a new version instead of erroring, and the operator sees a green publish that left the failed version unpublished. Confirm the failed run's own head commit still equals the branch tip immediately before dispatching, reading it as `gh run view <id> --json headSha` against `gh api repos/{owner}/{repo}/branches/<branch>` for the branch that run built rather than whichever branch is to hand. Where the two differ, or where the check is not worth making, prefer the re-run route, which is bound to that commit by construction, and fall back to re-dispatch only once the re-run window below has closed.

**Re-run all jobs, available inside the window whatever the tip has done.** `gh run rerun <id>` replays the run under the original event's `GITHUB_SHA` and `GITHUB_REF` and re-executes every job rather than only the failed ones. The publisher pins the release task to that commit with `ref: ${{ github.sha }}`, so `get-version` recomputes the same version from the same commit and history, each build leaf checks out the `GitCommitId` that job emits, the package artifact D5.2 deleted is rebuilt and re-uploaded rather than missing when `publish-<target>` downloads it, and that job retries the push it failed. The release itself needs nothing from the re-run, the failed run having already cut it, though on a dispatch-triggered run the re-run re-enters `github-release`, which refreshes the release per D4.4's dispatch leg and runs the `release-asset-*` delete with it per D5.2. A re-dispatch here would build the new tip instead, and NBGV derives the version from git height, so that is a further version and the one whose push failed never reaches the registry.

Three qualifications come with the re-run route.

- D4.4 and `WORKFLOW.md` 5B's S9 describe a re-run whose predecessor push **succeeded**, where the registry dedupes the second one. This is the case they do not cover, and its retried push is the first the registry ever receives for that version.
- GitHub offers a re-run only within **30 days** of the initial run, and a repository's own **log** retention setting can be shorter, so the usable window is the shorter of the two. This is the run's own retention and is unrelated to D5.4's `retention-days: 1`, which bounds an uploaded artifact rather than the run.
- **Re-run failed jobs** (`--failed`) does not serve here. D5.2's delete runs on the path that reaches this case, its gate being `!cancelled()` and the download having succeeded, so it has already removed the package artifact a `--failed` re-run would download, and only the full re-run rebuilds it.

Past the window, a moved tip leaves that version with no route to the registry. The release and tag already name it, and removing them is not the answer: leave them, and let the next publish carry a later version, recording the gap in `HISTORY.md`, since the release body is regenerated on any later dispatch refresh and cannot hold the record.

What no route settles in advance is whether the registry accepts the retried push.

## Wrapper repos that track an upstream release

A repo wrapping an upstream release uses the hub-hosted `check-upstream-version-task.yml`: a
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/python-codestyle/references/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ are in `references/profiles.md`.
Use `pytest` with configuration in `[tool.pytest.ini_options]`. Default invocation:
`uv run pytest`.

**Coverage.** Before creating or modifying `pyproject.toml`, read `WORKFLOW.md` D1.6 for the coverage obligations a build-profile repo with tests owes.
**Coverage.** A build-profile repository with tests declares **`pytest-cov`** among its test dependencies, a dev dependency group where the repository is a uv project and a `requirements*.txt` entry where it is on pip, and selects the coverage source in its own `pyproject.toml`, an `addopts` entry of `--cov=<package>` in practice. CI adds `--cov-report=xml` to the invocation, so the repository owes the dependency and the selector rather than that flag. Both halves are load-bearing and they fail differently: without the dependency the CI run exits non-zero on an unrecognized argument, and with the dependency but no selector it measures nothing, writes no file, and exits zero. Leave the report at the repository root as `coverage.xml`, the one path CI names. `WORKFLOW.md` D1.6 owns the pipeline half, the upload and the check that fails when no report was written.

- One test file per module under test, named `test_<module>.py`.
- Test functions named `test_<scenario>_<expected_behavior>`, descriptive and not numbered.
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/workflow-ci-contract/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ description: >-

## The Contract Text

`references/architecture.md`, `references/d-guarantees.md`, and `references/test-methodology.md` carry `WORKFLOW.md` sections 3, 4, and 5 whole, each as a generated include, so the pipeline's architecture, a guarantee's exact wording, and the audit-trace-probe procedure are each one read away rather than restated in full here. A defect in an include region is fixed in `WORKFLOW.md` and regenerated, never edited in this skill, per the `skill-lifecycle` Skill. `WORKFLOW.md` keeps sections 1, 2, and 6 itself, the applicability rule, the style-rule pointer, and the per-project-type walkthroughs that say which items go N/A per type, so read those there.
`references/architecture.md`, `references/d-guarantees.md`, and `references/test-methodology.md` carry `WORKFLOW.md` sections 3, 4, and 5 whole, each as a generated include, so the pipeline's architecture, a guarantee's exact wording, and the audit-trace-probe procedure are each one read away rather than restated in full here. A defect in an include region is fixed in `WORKFLOW.md` and regenerated, never edited in this skill, per the `skill-lifecycle` Skill. `WORKFLOW.md` keeps sections 1, 2, and 6 itself, the applicability rule, the style-rule pointer, and the per-project-type walkthroughs, which say which constructs each type adds, map each construct to the scenarios it reaches, and carry three rules for reading a row, one of which is about a repository declaring more than one type, so read those there.

## After Any Workflow Edit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Their CI is lint/validation only (editorconfig/EOL plus domain linters such as H

- **Orchestration** is generic and forms the standardization baseline **at the job level**: the single-branch publisher, the `get-version`, `validate-release`, and `github-release` jobs, and the `changes -> smoke-build -> aggregator` shape of the PR workflow. These job *bodies* should not need per-repo edits.
- **Build** is repo-owned in shape: the `build-<target>` leaf tasks, whether this repo hosts them itself or reaches hub-hosted ones by pin.
- **What the repo curates** (by design, not a leak): the *list* of targets. This is **not** a byte-for-byte file carry. Adding or dropping a target edits the orchestrator's surface: the `enable_<target>` inputs and the `build-<target>` job + its `github-release` **and** `build-docker` `needs:` entries in the release task, **and** the `changes` paths-filter entry + output + the `smoke-build` enable-forward in the PR workflow, plus the separate `publish-<target>` job for a package target. "Verbatim" applies to the `github-release` job and the version/publish-plan logic, not to the release task's job list or the paths-filter. Subsetting is symmetric: the same surface you trim to drop a target you extend to add a new one (e.g. a `release-asset-<branch>-library` producer needs a new `enable_library` input, a `build-library` job, its two `needs:` entries, and a `library` paths-filter entry, output, and `smoke-build` enable-forward).
- **What the repo curates** (by design, not a leak): the *list* of targets. This is **not** a byte-for-byte file carry. Adding or dropping a target edits the orchestrator's surface: the `enable_<target>` inputs and the `build-<target>` job + its `github-release` **and** `build-docker` `needs:` entries in the release task, **and** the `changes` paths-filter entry + output + the `smoke-build` enable-forward in the PR workflow, plus the separate `publish-<target>` job for a package target. "Verbatim" applies to the `github-release` job and the version/publish-plan logic, except that job's own `needs:` list, and never to the release task's job list or the paths-filter. Subsetting is symmetric: the same surface you trim to drop a target you extend to add a new one (e.g. a `release-asset-<branch>-library` producer needs a new `enable_library` input, a `build-library` job, its two `needs:` entries, and a `library` paths-filter entry, output, and `smoke-build` enable-forward).

### The Seam Contract

Expand Down
Loading