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
1 change: 1 addition & 0 deletions .agents/skills/engineering-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ otherwise favors.

| When you're about to... | Read |
|---|---|
| Decide whether a proposed capability, feature, integration, or new package belongs in Compono at all, before any design work starts | [`docs/architecture/capability-admission.md`](../../../docs/architecture/capability-admission.md) (standalone; read it directly, not via `references/`) |
| Decide where an architecture/feature decision belongs, run a design dive (light or deep) before writing code, write/reference an ADR (`docs/adr/`), or write/track a plan (`docs/plans/`) | `references/design-decisions.md` |
| Write or review any C# (naming, nullable, async, DI, error handling, file layout) | `references/coding-standards.md` |
| Add or change tests | `references/testing.md` |
Expand Down
11 changes: 11 additions & 0 deletions .agents/skills/engineering-workflow/references/design-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ already decided" is more useful than one that re-derives a solved problem
from first principles, and it's a fast check relative to the cost of
designing around a wrong assumption.

If the request is a genuinely new capability, a material expansion of an
existing package's public surface, or a new extension/integration
package — not a bug fix or straightforward implementation against an
already-`Accepted` ADR — run it through
[`docs/architecture/capability-admission.md`](../../../../docs/architecture/capability-admission.md)
**before** deciding light vs. deep dive below. That page is the standalone,
current process for whether something belongs in Compono at all; a
candidate that doesn't clear it doesn't get an ADR of its own, light or
deep. `docs/adr/0029-...` and `docs/adr/0039-...` are that page's
underlying decisions, not a substitute for reading it directly.

## Where decisions live

Four places, each with a different job — don't blur them together:
Expand Down
8 changes: 8 additions & 0 deletions .agents/skills/engineering-workflow/tasks/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ alternatives are even legal:
the docs, here's where" (`design-decisions.md`'s opening rule). A design
session grounded in what's already decided beats one that re-derives a
solved problem.

If the request is a genuinely new capability, a material expansion of
an existing package's public surface, or a new extension/integration
package, run it through
[`docs/architecture/capability-admission.md`](../../../../docs/architecture/capability-admission.md)
first — a candidate that doesn't clear that process doesn't get an ADR
at all, and this task shouldn't reconstruct that admission reasoning
from `docs/adr/0029-...`/`docs/adr/0039-...` from scratch each time.
2. **Decide light vs. deep**, per `design-decisions.md`:
- **Light** — problem and solution shape are already clear (adding a
provider following a pattern another provider already uses, adopting
Expand Down
10 changes: 9 additions & 1 deletion .github/scripts/inspect-packed-nupkgs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ main() {
}

local pkg nupkg extract_dir extra_paths nuspec
for pkg in Compono Compono.XunitV3 Compono.NSubstitute Compono.Bogus Compono.TUnit Compono.TestDoubles Compono.DependencyInjection Compono.Http Compono.Logging Compono.MSTest Compono.NUnit; do
for pkg in Compono Compono.XunitV3 Compono.NSubstitute Compono.Bogus Compono.TUnit Compono.TestDoubles Compono.DependencyInjection Compono.Http Compono.Logging Compono.MSTest Compono.NUnit Compono.Options; do
nupkg=$(find "$pack_output" -maxdepth 1 -iname "${pkg}.[0-9]*.nupkg" | head -1)
if [ -z "$nupkg" ]; then
echo "FAIL: no .nupkg found for $pkg in $pack_output" >&2
Expand Down Expand Up @@ -339,6 +339,14 @@ main() {
assert_exact_pin_dependency "$nuspec" "$pkg" "Compono"
assert_dependency_range "$nuspec" "$pkg" "NUnit" "$authoritative_json"
;;
Compono.Options)
assert_manifest_field "$nuspec" "$pkg" "title" "Compono — Configuration/Options Testing Support"
assert_exact_pin_dependency "$nuspec" "$pkg" "Compono"
# Per-TFM range, same shape as Compono.Logging's Microsoft.Extensions.Logging.Abstractions
# dependency above (net11.0 carries no explicit dependency entry - satisfied by that TFM's
# own shared framework, confirmed against a real local pack).
assert_dependency_range_per_tfm "$nuspec" "$pkg" "Microsoft.Extensions.Options" "$packages_props"
;;
esac
done

Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/aot-validation.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: AOT Validation

# ADR-0041 Amendment 7: permanent, CI-blocking Native AOT smoke gate for the eight existing
# ADR-0041 Amendment 7: permanent, CI-blocking Native AOT smoke gate for the nine existing
# test/*.AotSmokeTest projects, replacing the previous manual-only "run it by hand before release"
# verification.
#
# Deliberately NO `paths:` filter on this workflow's own `pull_request` trigger. A workflow entirely
# skipped by trigger-level path filtering leaves its required status check `Pending` rather than
# reporting success, under GitHub's required-check semantics - that would block a PR indefinitely
# instead of passing it on an AOT-irrelevant change. Selectivity happens *inside* the workflow
# instead: the `changes` job below computes which of the eight legs are actually applicable from the
# instead: the `changes` job below computes which of the nine legs are actually applicable from the
# PR's changed files (a small repository-owned `git diff` script, not a third-party changed-files
# action), each leg's own publish-and-run job runs behind an `if:` reading that output (an
# inapplicable leg reports an ordinary skipped conclusion, never a missing status), and `aot-gate` -
Expand Down Expand Up @@ -56,14 +56,14 @@ jobs:
echo "Changed files:"
echo "$changed"

all_legs='["Compono","Compono.Http","Compono.Logging","Compono.MSTest","Compono.NUnit","Compono.TestDoubles","Compono.TUnit","Compono.XunitV3"]'
all_legs='["Compono","Compono.Http","Compono.Logging","Compono.MSTest","Compono.NUnit","Compono.Options","Compono.TestDoubles","Compono.TUnit","Compono.XunitV3"]'

# A change to shared/core/generator infrastructure (or to this workflow itself) can affect
# every packaged leg at once - run all eight rather than reflexively narrowing to only the
# every packaged leg at once - run all nine rather than reflexively narrowing to only the
# paths that happened to change (ADR-0041 Amendment 7's own "do not run it reflexively, but
# do not under-run it either" balance).
if echo "$changed" | grep -qE '^(src/Compono/|src/Compono\.Generators/|Directory\.Packages\.props|Directory\.Build\.(props|targets)|test/Directory\.Build\.(props|targets)|\.github/workflows/aot-validation\.yaml)'; then
echo "Core/generator/shared-config change detected - running all eight legs."
echo "Core/generator/shared-config change detected - running all nine legs."
echo "legs=$all_legs" >> "$GITHUB_OUTPUT"
exit 0
fi
Expand All @@ -77,12 +77,13 @@ jobs:
echo "$changed" | grep -q '^src/Compono\.Logging/' && add_leg "Compono.Logging"
echo "$changed" | grep -q '^src/Compono\.MSTest/' && add_leg "Compono.MSTest"
echo "$changed" | grep -q '^src/Compono\.NUnit/' && add_leg "Compono.NUnit"
echo "$changed" | grep -q '^src/Compono\.Options/' && add_leg "Compono.Options"
echo "$changed" | grep -q '^src/Compono\.TestDoubles/' && add_leg "Compono.TestDoubles"
echo "$changed" | grep -q '^src/Compono\.TUnit/' && add_leg "Compono.TUnit"
echo "$changed" | grep -q '^src/Compono\.XunitV3/' && add_leg "Compono.XunitV3"

# A change scoped to one leg's own AotSmokeTest project only needs that leg re-run, not all
# eight - extract which leg(s) directly from the changed paths.
# nine - extract which leg(s) directly from the changed paths.
for proj in $(echo "$changed" | grep -oE '^test/[^/]+\.AotSmokeTest/' | sed -E 's#^test/(.+)\.AotSmokeTest/#\1#' | sort -u); do
add_leg "$proj"
done
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/package-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
# process environment by GitHub Actions itself, so this survives across the separate `run:`
# steps below (each its own shell process) with no extra plumbing - deliberately not a Bash
# array, which would only live for the one step that declared it.
PACKAGES: "Compono Compono.XunitV3 Compono.NSubstitute Compono.Bogus Compono.TUnit Compono.TestDoubles Compono.DependencyInjection Compono.Http Compono.Logging Compono.MSTest Compono.NUnit"
PACKAGES: "Compono Compono.XunitV3 Compono.NSubstitute Compono.Bogus Compono.TUnit Compono.TestDoubles Compono.DependencyInjection Compono.Http Compono.Logging Compono.MSTest Compono.NUnit Compono.Options"
steps:
- uses: actions/checkout@v7

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
2 changes: 2 additions & 0 deletions Compono.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Project Path="src/Compono.Logging/Compono.Logging.csproj" />
<Project Path="src/Compono.MSTest/Compono.MSTest.csproj" />
<Project Path="src/Compono.NUnit/Compono.NUnit.csproj" />
<Project Path="src/Compono.Options/Compono.Options.csproj" />
</Folder>
<Folder Name="/test/">
<Project Path="test/Compono.Tests/Compono.Tests.csproj" />
Expand All @@ -26,6 +27,7 @@
<Project Path="test/Compono.Logging.Tests/Compono.Logging.Tests.csproj" />
<Project Path="test/Compono.MSTest.Tests/Compono.MSTest.Tests.csproj" />
<Project Path="test/Compono.NUnit.Tests/Compono.NUnit.Tests.csproj" />
<Project Path="test/Compono.Options.Tests/Compono.Options.Tests.csproj" />
<!-- Compono.Http.AotSmokeTest (and its AnalyzerContract/ sub-projects) is deliberately NOT
listed here - matching the existing, unlisted Compono.AotSmokeTest/Compono.TUnit.AotSmokeTest/
Compono.TestDoubles.AotSmokeTest convention: a manual, one-shot PublishAot proof driven by
Expand Down
20 changes: 20 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,26 @@
<ItemGroup Label="Microsoft.Extensions.Logging.Abstractions (net11.0)" Condition="'$(TargetFramework)' == 'net11.0'">
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[11.0.0-preview.6.26359.118, 12.0.0-a)" />
</ItemGroup>
<!-- Compono.Options's own dependency, per docs/adr/0061-compono-options-testing-support.md:
the IOptions<T>/IOptionsSnapshot<T>/IOptionsMonitor<T> interface package only - never
Microsoft.Extensions.Options.ConfigurationExtensions or DataAnnotations, and never a
transitive Microsoft.Extensions.DependencyInjection dependency beyond what this package
itself requires. Same per-TargetFramework conditional-range shape as
Microsoft.Extensions.Logging.Abstractions above. -->
<ItemGroup Label="Microsoft.Extensions.Options (net8.0)" Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.Extensions.Options" Version="[8.0.2, 9.0.0)" />
</ItemGroup>
<ItemGroup Label="Microsoft.Extensions.Options (net9.0)" Condition="'$(TargetFramework)' == 'net9.0'">
<PackageVersion Include="Microsoft.Extensions.Options" Version="[9.0.6, 10.0.0)" />
</ItemGroup>
<ItemGroup Label="Microsoft.Extensions.Options (net10.0)" Condition="'$(TargetFramework)' == 'net10.0'">
<PackageVersion Include="Microsoft.Extensions.Options" Version="[10.0.11, 11.0.0)" />
</ItemGroup>
<!-- Lower bound is a prerelease, which causes NuGet to consider prerelease candidates for this
package id - same reasoning as Microsoft.Extensions.Logging.Abstractions (net11.0) above. -->
<ItemGroup Label="Microsoft.Extensions.Options (net11.0)" Condition="'$(TargetFramework)' == 'net11.0'">
<PackageVersion Include="Microsoft.Extensions.Options" Version="[11.0.0-preview.7.26381.103, 12.0.0-a)" />
</ItemGroup>
<ItemGroup>
<!-- Assertions -->
<PackageVersion Include="AwesomeAssertions" Version="9.6.0" />
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Compono determines **how** those requirements are satisfied.
| `Compono.Logging` | `Microsoft.Extensions.Logging` testing support (`UseLogging()`, `CapturingLogger<T>`) | [![NuGet](https://img.shields.io/nuget/v/Compono.Logging.svg)](https://www.nuget.org/packages/Compono.Logging) | [![NuGet Downloads](https://img.shields.io/nuget/dt/Compono.Logging.svg)](https://www.nuget.org/packages/Compono.Logging) |
| `Compono.MSTest` | MSTest integration | [![NuGet](https://img.shields.io/nuget/v/Compono.MSTest.svg)](https://www.nuget.org/packages/Compono.MSTest) | [![NuGet Downloads](https://img.shields.io/nuget/dt/Compono.MSTest.svg)](https://www.nuget.org/packages/Compono.MSTest) |
| `Compono.NUnit` | NUnit integration (no `[TestFixture]` required) | [![NuGet](https://img.shields.io/nuget/v/Compono.NUnit.svg)](https://www.nuget.org/packages/Compono.NUnit) | [![NuGet Downloads](https://img.shields.io/nuget/dt/Compono.NUnit.svg)](https://www.nuget.org/packages/Compono.NUnit) |
| `Compono.Options` | `Microsoft.Extensions.Options` testing support (`TestOptionsSource<T>`, `UseOptions<T>()`) | [![NuGet](https://img.shields.io/nuget/v/Compono.Options.svg)](https://www.nuget.org/packages/Compono.Options) | [![NuGet Downloads](https://img.shields.io/nuget/dt/Compono.Options.svg)](https://www.nuget.org/packages/Compono.Options) |

## Example

Expand Down
Loading
Loading