Skip to content

Add automated per-plugin versioning (NBGV) with /version-bump + weekly backstop#813

Open
AbhitejJohn wants to merge 3 commits into
mainfrom
abhitejjohn/per-plugin-version-strategy
Open

Add automated per-plugin versioning (NBGV) with /version-bump + weekly backstop#813
AbhitejJohn wants to merge 3 commits into
mainfrom
abhitejjohn/per-plugin-version-strategy

Conversation

@AbhitejJohn

Copy link
Copy Markdown
Collaborator

Draft for review. Opens the versioning strategy we discussed: per‑plugin semantic versions, materialized into the checked‑in manifests, with two low‑touch automations and no auto‑editing of contributor PRs.

Why

Every client that surfaces these skills — Copilot CLI, Claude Code, Codex, Cursor — reads a plugin's version directly from its checked‑in manifest (plugin.json / .codex-plugin/plugin.json). Today those versions are static, so:

  • A plugin's behavior can change while its advertised version stays flat, and consumers never get a signal to re‑pull.
  • There's no human‑readable record of what changed when.
  • Bumping by hand across 14 plugins is error‑prone and easy to forget.

We want correct, current versions in the repo with minimal manual work, without bloating the marketplace clone and without taking on third‑party/external plugins (out of scope for now).

What this adds

  • Per‑plugin SemVer via Nerdbank.GitVersioning (NBGV). Each plugin gets a version.json whose pathFilters exclude the generated manifests and version.json itself, so the computed version height tracks real content changes only (editing a manifest or the version file doesn't inflate the height).
  • Versions are materialized into the checked‑in manifests, so consumers read a current value with no build step on their side.
  • One workhorse scripteng/version/Sync-PluginVersions.ps1 — resolves the changed‑plugin set from a git diff, computes each version with nbgv (predicting the squash‑merge height for PRs), and either reports or stamps.

Automations (two, low‑touch by design)

Workflow Trigger What it does
/version-bump A maintainer comments /version-bump on a PR Stamps the affected plugins on the PR branch. Gated on collaborator permission (admin/write/maintain); forks rejected before any privileged step.
weekly-version-sync Mondays 09:00 UTC + workflow_dispatch Backstop: stamps any drift on main, opens/updates a single bot PR, and explains the per‑plugin reason. Self‑heals anything merged without a bump.

We deliberately did not auto‑edit contributor PRs or add a noisy advisory‑comment bot — maintainers stay in control and the signal stays clean. (No official skills repo we surveyed auto‑mutates contributor PRs for versioning; the opt‑in command + scheduled backstop pattern is the conservative norm.)

Security — multi‑model adversarial review (GPT‑5.5 + Gemini 3.1 Pro)

  • Supply‑chain RCE (High — flagged independently by both models). dotnet tool restore would have honored a nuget.config authored in the PR tree, letting an attacker remap the nbgv package source to a malicious feed and execute code in the privileged contents: write context. This was genuinely reachable — no nuget.config is tracked anywhere in the repo today, so a PR could add one. Fix: a trusted eng/version/nuget.config (<clear/> + nuget.org‑only + packageSourceMapping), overlaid from main and consumed via --configfile so PR‑supplied configs are ignored entirely.
  • TOCTOU (Medium). /version-bump now checks out the authorized head SHA rather than the mutable branch name; if the branch advances after authorization, the push fails non‑fast‑forward — the safe outcome.
  • Injection hardening. Set-ManifestVersion uses a MatchEvaluator (not a replacement string), so a $‑bearing version can't re‑expand, plus a strict ^\d+\.\d+\.\d+$ guard that throws on a malformed base and leaves manifests untouched.
  • Correctness. A base‑only version.json bump (0.1 → 0.2) is now detected and stamped.

Verification

Exercised end‑to‑end against a real NBGV git harness (the repo global.json pins an SDK that isn't installable locally, so testing uses an isolated harness):

  • content‑scoped predict → only the touched plugin bumps;
  • base‑only bump → x.y.0;
  • docs‑only change → [] (no bump);
  • weekly -OnlyChanged → stamps drift authoritatively;
  • malformed base → throws, manifest intact;
  • dotnet tool restore --configfile → exit 0.

actionlint passes on both workflows.

Files

  • eng/version/Sync-PluginVersions.ps1, eng/version/nuget.config, .config/dotnet-tools.json
  • .github/workflows/version-bump-command.yml, .github/workflows/weekly-version-sync.yml
  • plugins/*/version.json (×14), CONTRIBUTING.md

Notes for reviewers

  • The issue_comment/scheduled workflows run the YAML from main, so /version-bump and the weekly job only take effect once this is merged — expected, not a bootstrap bug.
  • Open question for discussion: cadence/branch‑protection interplay for the weekly bot PR.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…y backstop

WHY
Tools that surface skills (Copilot CLI, Claude Code, Codex, Cursor) read a
plugin's version directly from its checked-in manifest. With no versioning
discipline, a plugin's behavior can change while its advertised version stays
flat, so clients never learn to re-pull, and there is no human-readable signal
of what changed. We want correct, current versions in the repo with minimal
manual work and without bloating the marketplace clone.

WHAT
- Per-plugin semantic versioning via Nerdbank.GitVersioning (NBGV). Each plugin
  owns a version.json whose pathFilters exclude the generated manifests and the
  version.json itself, so version height tracks real content changes only.
- The computed version is materialized into the checked-in manifests
  (plugin.json and .codex-plugin/plugin.json) so every consumer reads a current
  value with no build step on their side.
- eng/version/Sync-PluginVersions.ps1 is the single workhorse. It resolves the
  set of changed plugins from a git diff, computes each version with nbgv
  (predicting the squash-merge height for PRs), and either reports or stamps.

AUTOMATIONS (two, low-touch by design)
- /version-bump: an admin/maintainer comments the command on a PR and the
  affected plugins are stamped on the PR branch. Gated on collaborator
  permission (admin/write/maintain); forks are rejected before any privileged
  step. No other PRs are auto-modified.
- weekly-version-sync: a Monday backstop (and workflow_dispatch) that stamps any
  drift on main, opens/updates a single bot PR, and explains the per-plugin
  reason. This self-heals anything that merged without a bump.

We deliberately did NOT auto-edit contributor PRs or add a noisy advisory
comment bot; maintainers stay in control and the signal stays clean.

SECURITY (multi-model adversarial review: GPT-5.5 + Gemini 3.1 Pro)
- Supply chain (High, both models): dotnet tool restore would have honored a
  nuget.config authored in the PR tree, letting an attacker remap the nbgv
  package source to a malicious feed and run code in the privileged
  contents:write context. Mitigated with a trusted eng/version/nuget.config
  (clear + nuget.org-only + packageSourceMapping), overlaid from main and used
  via --configfile so PR-supplied configs are ignored. No nuget.config is
  tracked in the repo today, so this path was genuinely exploitable.
- TOCTOU (Medium): /version-bump now checks out the authorized head SHA rather
  than the mutable branch name; a racing push fails non-fast-forward, which is
  the safe outcome.
- Injection: Set-ManifestVersion uses a MatchEvaluator (not a replacement
  string) so a "$"-bearing version cannot re-expand, plus a strict
  major.minor.patch guard that throws on a malformed base, leaving manifests
  untouched.
- A base-only version.json bump (0.1 -> 0.2) is correctly detected and stamped.

VERIFIED
End-to-end against a real NBGV git harness: content-scoped predict, base-only
bump -> x.y.0, docs-only -> [], weekly drift stamping, malformed-base guard,
and --configfile restore (exit 0). actionlint passes on both workflows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Skill Coverage Report

Plugin Skill Covered Coverage
⚠️ dotnet-aspnetcore configuring-opentelemetry-dotnet 13/21 61.9%
⚠️ dotnet-aspnetcore convert-blazor-server-to-webapp 17/27 63%
dotnet-aspnetcore dotnet-webapi 38/43 88.4%
⚠️ dotnet-aspnetcore minimal-api-file-upload 4/8 50%
dotnet-experimental exp-mock-usage-analysis 12/13 92.3%
⚠️ dotnet-experimental exp-test-maintainability 10/18 55.6%
⚠️ dotnet csharp-scripts 13/22 59.1%
dotnet dotnet-pinvoke 6/28 21.4%
dotnet setup-local-sdk 3/8 37.5%
dotnet-test-migration migrate-mstest-v1v2-to-v3 13/16 81.2%
dotnet-test-migration migrate-mstest-v3-to-v4 38/41 92.7%
dotnet-test-migration migrate-vstest-to-mtp 21/25 84%
⚠️ dotnet-test-migration migrate-xunit-to-mstest 31/46 67.4%
dotnet-test-migration migrate-xunit-to-xunit-v3 14/16 87.5%
⚠️ dotnet-maui maui-collectionview 7/9 77.8%
⚠️ dotnet-maui maui-data-binding 6/10 60%
dotnet-maui maui-dependency-injection 0/1 0%
dotnet-maui maui-safe-area 0/2 0%
dotnet-maui maui-theming 0/1 0%
dotnet-nuget convert-to-cpm 16/16 100%
dotnet-test assertion-quality 20/22 90.9%
dotnet-test code-testing-agent 5/5 100%
dotnet-test crap-score 6/6 100%
dotnet-test detect-static-dependencies 15/15 100%
dotnet-test dotnet-test-frameworks 4/5 80%
dotnet-test filter-syntax 0/1 0%
⚠️ dotnet-test generate-testability-wrappers 14/22 63.6%
dotnet-test grade-tests 23/28 82.1%
⚠️ dotnet-test migrate-static-to-wrapper 15/21 71.4%
dotnet-test mtp-hot-reload 16/16 100%
dotnet-test run-tests 15/16 93.8%
dotnet-test test-anti-patterns 19/21 90.5%
⚠️ dotnet-test test-gap-analysis 16/24 66.7%
dotnet-test test-smell-detection 22/26 84.6%
dotnet-test test-tagging 24/28 85.7%
dotnet-test writing-mstest-tests 39/45 86.7%
⚠️ dotnet-upgrade dotnet-aot-compat 6/10 60%
dotnet-upgrade migrate-dotnet10-to-dotnet11 5/6 83.3%
dotnet-upgrade migrate-dotnet8-to-dotnet9 6/6 100%
dotnet-upgrade migrate-dotnet9-to-dotnet10 6/6 100%
⚠️ dotnet-upgrade migrate-nullable-references 20/27 74.1%
dotnet-upgrade thread-abort-migration 21/22 95.5%
dotnet-data optimizing-ef-core-queries 4/17 23.5%
⚠️ dotnet-msbuild check-bin-obj-clash 9/13 69.2%
dotnet-msbuild directory-build-organization 0/1 0%
dotnet-msbuild extension-points 0/1 0%
dotnet-msbuild msbuild-modernization 6/7 85.7%
⚠️ dotnet-msbuild msbuild-server 7/9 77.8%
dotnet-msbuild property-patterns 0/1 0%
dotnet-msbuild resolve-project-references 5/6 83.3%
⚠️ dotnet-diag analyzing-dotnet-performance 12/21 57.1%
dotnet-diag android-tombstone-symbolication 5/5 100%
dotnet-diag apple-crash-symbolication 4/5 80%
dotnet-diag clr-activation-debugging 16/19 84.2%
dotnet-diag dotnet-trace-collect 15/17 88.2%
dotnet-diag microbenchmarking 3/3 100%
dotnet-blazor author-component 0/1 0%
dotnet-blazor collect-user-input 0/4 0%
dotnet-blazor configure-auth 0/3 0%
dotnet-blazor coordinate-components 0/2 0%
dotnet-blazor fetch-and-send-data 1/5 20%
dotnet-blazor plan-ui-change 0/1 0%
dotnet-blazor support-prerendering 0/3 0%
dotnet-blazor use-js-interop 0/2 0%
⚠️ dotnet-ai mcp-csharp-create 18/25 72%
dotnet-ai mcp-csharp-debug 16/18 88.9%
⚠️ dotnet-ai mcp-csharp-publish 9/15 60%
⚠️ dotnet-ai mcp-csharp-test 11/18 61.1%
⚠️ dotnet-ai technology-selection 18/24 75%
dotnet-template-engine template-authoring 5/16 31.2%
dotnet-template-engine template-comparison 8/10 80%
dotnet-template-engine template-discovery 10/12 83.3%
⚠️ dotnet-template-engine template-instantiation 11/16 68.8%
⚠️ dotnet-template-engine template-smart-defaults 6/8 75%
⚠️ dotnet-template-engine template-validation 5/10 50%
Uncovered: dotnet-aspnetcore/configuring-opentelemetry-dotnet
  • [Validation] Traces appear in the observability backend (Jaeger, Aspire dashboard, etc.) (line 272)
  • [Validation] HTTP requests automatically create spans with correct verb, URL, status code (line 273)
  • [Validation] Health check endpoints are filtered from traces (line 277)
  • [Validation] Exception details appear on error spans (line 278)
  • [Pitfall] Missing HTTP client spans (line 286)
  • [Pitfall] High cardinality tags (line 287)
  • [CodePattern] readonly (line 124)
  • [CodePattern] [key] (line 241)
Uncovered: dotnet-aspnetcore/convert-blazor-server-to-webapp
  • [Validation] No references to AddServerSideBlazor remain (line 261)
  • [Validation] No references to MapBlazorHub remain (line 262)
  • [Validation] No references to MapFallbackToPage("/_Host") remain (line 263)
  • [Validation] No references to blazor.server.js remain (line 264)
  • [Validation] Pages/_Host.cshtml has been deleted (line 265)
  • [Validation] App builds and runs successfully on the target framework (line 272)
  • [Pitfall] Not migrating AddServerSideBlazor circuit options (line 283)
  • [Pitfall] CSS isolation bundle link has wrong assembly name (line 285)
  • [WorkflowStep] Step 1: Update the project file (line 48)
  • [WorkflowStep] Step 6: Recommended improvements (optional) (line 234)
Uncovered: dotnet-aspnetcore/dotnet-webapi
  • [Validation] DELETE endpoints return 204 No Content (line 464)
  • [Validation] A .http file exists with a request for every new endpoint (line 474)
  • [Validation] dotnet build passes with zero errors and zero warnings (line 475)
  • [CodePattern] [Range] (line 147)
  • [CodePattern] [HttpGet] (line 248)
Uncovered: dotnet-aspnetcore/minimal-api-file-upload
  • [WorkflowStep] Step 1: CRITICAL — Understand IFormFile Binding in Minimal APIs (line 29)
  • [WorkflowStep] Step 5: CRITICAL — Streaming Large Files Without Buffering (line 170)
  • [CodePattern] [RequestSizeLimit] (line 54)
  • [CodePattern] [FromForm] (line 31)
Uncovered: dotnet-experimental/exp-mock-usage-analysis
  • [Validation] Production code was read and execution paths were traced (not just test code reviewed) (line 86)
Uncovered: dotnet-experimental/exp-test-maintainability
  • [Validation] Every finding shows the actual duplicated code, not just a description (line 181)
  • [Validation] Every suggestion includes a concrete before/after example (line 182)
  • [Validation] Findings are filtered through the 3+ occurrence threshold (line 183)
  • [Validation] Simple constructors are not flagged (line 184)
  • [Validation] If tests are clean, the report says so upfront (line 186)
  • [Pitfall] Suggesting extraction for 2 occurrences (line 193)
  • [Pitfall] Flagging simple new X() as boilerplate (line 196)
  • [CodePattern] [TestMethod] (line 60)
Uncovered: dotnet/csharp-scripts
  • [Validation] dotnet --version reports 10.0 or later (or fallback path is used) (line 274)
  • [Validation] The app compiles without errors (can be checked explicitly with dotnet build <file>.cs) (line 276)
  • [Validation] App files and cached artifacts are cleaned up after the session (line 279)
  • [Pitfall] #:package without a version (line 286)
  • [Pitfall] #:property with wrong syntax (line 287)
  • [Pitfall] Directives placed after C# code (line 288)
  • [Pitfall] Reflection-based JSON serialization fails (line 293)
  • [WorkflowStep] Step 5: Clean up (line 199)
  • [CodePattern] [MSBuild] (line 93)
Uncovered: dotnet/dotnet-pinvoke
  • [Validation] Calling convention specified if targeting Windows x86; omitted otherwise (line 416)
  • [Validation] String encoding is explicit — no reliance on defaults or CharSet.Auto (line 417)
  • [Validation] Memory ownership is documented and matched (who allocates, who frees, with what) (line 418)
  • [Validation] SafeHandle used for all native handles (no raw IntPtr escaping the interop layer) (line 419)
  • [Validation] Delegates passed as callbacks are rooted to prevent GC collection (line 420)
  • [Validation] SetLastError/SetLastPInvokeError set for APIs that use OS error codes (line 421)
  • [Validation] Struct layout matches native (packing, alignment, field order) (line 422)
  • [Validation] CLong/CULong used for C long/unsigned long in cross-platform code (line 423)
  • [Validation] If using CLong/CULong with LibraryImport, [assembly: DisableRuntimeMarshalling] is applied (line 424)
  • [Validation] No bool without explicit MarshalAs — always specify UnmanagedType.Bool (4-byte) or UnmanagedType.U1 (1-byte) to ensure normalization across the language boundary. (line 425)
  • [WorkflowStep] Step 5: Establish Memory Ownership (line 168)
  • [WorkflowStep] Step 6: Use SafeHandle for Native Handles (line 224)
  • [WorkflowStep] Step 7: Handle Errors (line 261)
  • [CodePattern] [UnmanagedCallersOnly] (line 281)
  • [CodePattern] [256] (line 176)
  • [CodePattern] [DllImport] (line 93)
  • [CodePattern] [LibraryImport] (line 101)
  • [CodePattern] [UnmanagedFunctionPointer] (line 300)
  • [CodePattern] [MarshalAs] (line 144)
  • [CodePattern] [Out] (line 144)
  • [CodePattern] sealed (line 228)
  • [CodePattern] [UnmanagedCallConv] (line 111)
Uncovered: dotnet/setup-local-sdk
  • [Pitfall] paths ignored (line 382)
  • [Pitfall] dotnet app.dll wrong runtime (line 386)
  • [CodePattern] [ordered] (line 301)
  • [CodePattern] [pscustomobject] (line 301)
  • [CodePattern] [guid] (line 104)
Uncovered: dotnet-test-migration/migrate-mstest-v1v2-to-v3
  • [Validation] Project builds with zero errors (line 185)
  • [Validation] All tests pass (dotnet test) -- compare pass/fail counts to pre-migration baseline (line 186)
  • [CodePattern] Assert.AreNotEqual (line 144)
Uncovered: dotnet-test-migration/migrate-mstest-v3-to-v4
  • [CodePattern] [TestMethod] (line 265)
  • [CodePattern] [Timeout] (line 212)
  • [CodePattern] [TestMethodAttribute] (line 172)
Uncovered: dotnet-test-migration/migrate-vstest-to-mtp
  • [Validation] dotnet build completes with zero errors (line 361)
  • [Validation] Test executable runs directly (e.g., ./bin/Debug/net8.0/MyTests.exe) (line 363)
  • [Validation] No vstest.console.exe invocations remain in CI scripts (line 366)
  • [CodePattern] [trait] (line 252)
Uncovered: dotnet-test-migration/migrate-xunit-to-mstest
  • [Validation] using Xunit; and using Xunit.Abstractions; removed (line 521)
  • [Validation] xunit.runner.json removed; equivalent config in .runsettings / [assembly: Parallelize] (line 522)
  • [Validation] Project builds with zero errors (line 524)
  • [CodePattern] [TestClass] (line 277)
  • [CodePattern] [TestMethod] (line 320)
  • [CodePattern] Parallelize (line 405)
  • [CodePattern] sealed (line 277)
  • [CodePattern] [Fact] (line 308)
  • [CodePattern] Assert.IsFalse (line 347)
  • [CodePattern] readonly (line 262)
  • [CodePattern] [DoNotParallelize] (line 434)
  • [CodePattern] DoNotParallelize (line 434)
  • [CodePattern] Assert.False (line 347)
  • [CodePattern] [ClassInitialize] (line 277)
  • [CodePattern] [ClassCleanup] (line 277)
Uncovered: dotnet-test-migration/migrate-xunit-to-xunit-v3
  • [WorkflowStep] Step 13: Build and verify (line 199)
  • [CodePattern] sealed (line 118)
Uncovered: dotnet-maui/maui-collectionview
  • [Pitfall] EmptyView doesn't render correctly (line 329)
  • [Pitfall] Poor scroll performance (line 330)
Uncovered: dotnet-maui/maui-data-binding
  • [Pitfall] Specifying redundant Mode=OneWay / Mode=TwoWay (line 375)
  • [Pitfall] Mutating ObservableCollection off the UI thread (line 377)
  • [Pitfall] Complex converter chains in hot paths (line 378)
  • [Pitfall] Binding to non-public properties (line 380)
Uncovered: dotnet-maui/maui-dependency-injection
  • [CodePattern] readonly (line 102)
Uncovered: dotnet-maui/maui-safe-area
  • [CodePattern] [Flags] (line 48)
  • [CodePattern] readonly (line 65)
Uncovered: dotnet-maui/maui-theming
  • [CodePattern] [Activity] (line 226)
Uncovered: dotnet-test/assertion-quality
  • [Validation] Metrics are computed correctly (counts add up) (line 155)
  • [Validation] If the suite has good diversity, the report acknowledges this (line 160)
Uncovered: dotnet-test/dotnet-test-frameworks
  • [CodePattern] Assert.AreEqual (line 64)
Uncovered: dotnet-test/filter-syntax
  • [CodePattern] [trait] (line 114)
Uncovered: dotnet-test/generate-testability-wrappers
  • [Validation] DI registration uses AddSingleton for stateless wrappers, AddTransient for stateful ones (line 217)
  • [Validation] Ambient context pattern includes AsyncLocal<T>, scoped disposal, and trade-off explanation (line 220)
  • [Pitfall] Ambient context without AsyncLocal (line 231)
  • [WorkflowStep] Step 5: Generate ambient context alternative (when DI is not available) (line 176)
  • [CodePattern] Assert.Equal (line 167)
  • [CodePattern] sealed (line 120)
  • [CodePattern] Assert.True (line 81)
  • [CodePattern] readonly (line 180)
Uncovered: dotnet-test/grade-tests
  • [Validation] Every grade is justified by at least one observable signal in the (line 307)
  • [Pitfall] Inflating deductions to justify the grade (line 331)
  • [Pitfall] Flagging Go/Rust table-driven loops as conditional logic (line 335)
  • [Pitfall] Penalizing tests when production code is unavailable (line 337)
  • [Pitfall] Spilling a 500-row table into a PR comment (line 339)
Uncovered: dotnet-test/migrate-static-to-wrapper
  • [Validation] Required using directives added (line 164)
  • [Validation] Build succeeds after migration (line 166)
  • [Validation] Test files updated with appropriate test doubles (line 167)
  • [Validation] No behavioral changes introduced (wrapper delegates directly to the static) (line 168)
  • [Pitfall] Breaking static classes (line 175)
  • [Pitfall] Migrating too much at once (line 178)
Uncovered: dotnet-test/run-tests
  • [Validation] Correct dotnet test invocation was used for the detected platform and SDK version (line 251)
Uncovered: dotnet-test/test-anti-patterns
  • [Validation] Every finding includes a specific location (not just a general warning) (line 155)
  • [Validation] Recommendations are prioritized by severity (line 159)
Uncovered: dotnet-test/test-gap-analysis
  • [Validation] Trivial code (simple getters, auto-properties) is excluded from analysis (line 202)
  • [Validation] Findings are prioritized by risk, not just listed in source order (line 203)
  • [Validation] Report includes strengths (killed mutations) alongside gaps (line 204)
  • [Validation] Mutation categories are correctly labeled (line 205)
  • [Pitfall] Analyzing trivial code (line 211)
  • [Pitfall] Ignoring call chains (line 213)
  • [Pitfall] Over-counting mutations in generated code (line 214)
  • [Pitfall] Forgetting Rust's ? operator (line 220)
Uncovered: dotnet-test/test-smell-detection
  • [Validation] Every finding includes a concrete fix example (not just "fix this") (line 216)
  • [Validation] Contextually obvious numbers are not flagged as magic numbers (line 219)
  • [Validation] Severity levels are justified, not arbitrary (line 221)
  • [Pitfall] Treating skip annotations with reasons same as bare skips (line 238)
Uncovered: dotnet-test/test-tagging
  • [Validation] For report-only frameworks, no source files were modified (line 243)
  • [Pitfall] Guessing traits without reading the test body (line 250)
  • [WorkflowStep] Step 3: Classify each test method (line 92)
  • [CodePattern] [Category] (line 135)
Uncovered: dotnet-test/writing-mstest-tests
  • [CodePattern] Assert.Contains (line 178)
  • [CodePattern] Assert.AreSame (line 154)
  • [CodePattern] Assert.DoesNotContain (line 178)
  • [CodePattern] Assert.IsEmpty (line 178)
  • [CodePattern] Assert.IsNull (line 154)
  • [CodePattern] Assert.IsNotEmpty (line 178)
Uncovered: dotnet-upgrade/dotnet-aot-compat
  • [CodePattern] [JsonSerializerContext] (line 169)
  • [CodePattern] [MSBuild] (line 67)
  • [CodePattern] [DynamicallyAccessedMembers] (line 119)
  • [CodePattern] [RequiresUnreferencedCode] (line 195)
Uncovered: dotnet-upgrade/migrate-dotnet10-to-dotnet11
  • [WorkflowStep] Step 5: Update infrastructure (line 169)
Uncovered: dotnet-upgrade/migrate-nullable-references
  • [Validation] Project file(s) contain <Nullable>enable</Nullable> (or #nullable enable per-file for file-by-file strategy) (line 235)
  • [Validation] <WarningsAsErrors>nullable</WarningsAsErrors> added to project file to prevent regressions (line 237)
  • [Validation] No #nullable disable directives remain unless justified with a comment (line 239)
  • [Validation] Public API signatures accurately reflect null contracts (line 241)
  • [Validation] For public libraries: breaking changes documented in nullable-breaking-changes.md and reviewed by the user (line 242)
  • [Pitfall] Multi-target projects and older TFMs (line 265)
  • [Pitfall] Warnings reappear after upgrading a dependency (line 266)
Uncovered: dotnet-upgrade/thread-abort-migration
  • [Validation] No SYSLIB0006 pragma suppressions remain (line 117)
Uncovered: dotnet-data/optimizing-ef-core-queries
  • [Validation] SQL logging shows expected number of queries (no N+1) (line 164)
  • [Validation] Read-only queries use AsNoTracking() (line 165)
  • [Validation] Hot-path queries use compiled queries (line 166)
  • [Validation] No client-side evaluation warnings in logs (line 167)
  • [Validation] Include/split strategy matches data shape (line 168)
  • [Pitfall] Global query filters forgotten in perf analysis (line 175)
  • [Pitfall] DbContext kept alive too long (line 176)
  • [Pitfall] String interpolation in FromSqlRaw (line 178)
  • [WorkflowStep] Step 1: Enable query logging to see the actual SQL (line 31)
  • [WorkflowStep] Step 4: Use compiled queries for hot paths (line 120)
  • [WorkflowStep] Step 5: Avoid common query traps (line 134)
  • [WorkflowStep] Step 6: Use raw SQL or FromSql for complex queries (line 144)
  • [CodePattern] readonly (line 122)
Uncovered: dotnet-msbuild/check-bin-obj-clash
  • [WorkflowStep] Step 2: Get an overview and list projects (line 48)
  • [WorkflowStep] Step 5: Check for double writes (line 62)
  • [WorkflowStep] Step 2: Replay the Binary Log to Text (line 78)
  • [WorkflowStep] Step 3: List All Projects (line 84)
Uncovered: dotnet-msbuild/directory-build-organization
  • [CodePattern] [MSBuild] (line 122)
Uncovered: dotnet-msbuild/extension-points
  • [CodePattern] [MSBuild] (line 184)
Uncovered: dotnet-msbuild/msbuild-modernization
  • [WorkflowStep] Step 6: Remove Unnecessary Boilerplate (line 217)
Uncovered: dotnet-msbuild/msbuild-server
  • [Validation] MSBUILDUSESERVER=1 is set in the shell (line 59)
  • [Validation] Second sequential build is faster than the first (line 60)
Uncovered: dotnet-msbuild/property-patterns
  • [CodePattern] [MSBuild] (line 33)
Uncovered: dotnet-msbuild/resolve-project-references
  • [Validation] ResolveProjectReferences was not set as the optimization target (line 64)
Uncovered: dotnet-diag/analyzing-dotnet-performance
  • [Validation] All critical patterns were checked (from reference files or inline recipes) (line 178)
  • [Validation] Topic-specific recipes run only when matching signals detected (line 179)
  • [Validation] Each finding includes a concrete code fix (line 180)
  • [Validation] Scan execution checklist is complete (all recipes run) (line 181)
  • [Validation] Summary table included at end (line 182)
  • [Pitfall] Suggesting ConfigureAwait(false) in app code (line 191)
  • [Pitfall] Recommending ValueTask everywhere (line 192)
  • [Pitfall] Flagging new HttpClient() in DI services (line 193)
  • [Pitfall] Suggesting CollectionsMarshal.AsSpan broadly (line 195)
Uncovered: dotnet-diag/apple-crash-symbolication
  • [WorkflowStep] Step 1: Parse the .ips Crash Log (line 19)
Uncovered: dotnet-diag/clr-activation-debugging
  • [Validation] The entry point for each problematic activation was identified (line 289)
  • [Validation] SEM_FAILCRITICALERRORS state was noted for FOD-related issues (line 292)
  • [Validation] Multiple activations within a single log were individually traced (line 293)
Uncovered: dotnet-diag/dotnet-trace-collect
  • [Validation] The recommended tool is compatible with the developer's runtime, OS, and deployment topology (line 235)
  • [Validation] The output file is generated in the expected location (line 237)
Uncovered: dotnet-blazor/author-component
  • [CodePattern] [Parameter] (line 31)
Uncovered: dotnet-blazor/collect-user-input
  • [CodePattern] [SupplyParameterFromForm] (line 31)
  • [CodePattern] [CascadingParameter] (line 162)
  • [CodePattern] [Range] (line 135)
  • [CodePattern] [Required] (line 135)
Uncovered: dotnet-blazor/configure-auth
  • [CodePattern] [ExcludeFromInteractiveRouting] (line 152)
  • [CodePattern] [CascadingParameter] (line 51)
  • [CodePattern] [Authorize] (line 101)
Uncovered: dotnet-blazor/coordinate-components
  • [CodePattern] [CascadingParameter] (line 63)
  • [CodePattern] readonly (line 137)
Uncovered: dotnet-blazor/fetch-and-send-data
  • [CodePattern] [PersistentState] (line 84)
  • [CodePattern] [StreamRendering] (line 74)
  • [CodePattern] [Parameter] (line 159)
  • [CodePattern] [SupplyParameterFromQuery] (line 159)
Uncovered: dotnet-blazor/plan-ui-change
  • [CodePattern] [Parameter] (line 64)
Uncovered: dotnet-blazor/support-prerendering
  • [CodePattern] [PersistentState] (line 39)
  • [CodePattern] [ExcludeFromInteractiveRouting] (line 148)
  • [CodePattern] [CascadingParameter] (line 159)
Uncovered: dotnet-blazor/use-js-interop
  • [CodePattern] sealed (line 118)
  • [CodePattern] readonly (line 118)
Uncovered: dotnet-ai/mcp-csharp-create
  • [Validation] Project builds with no errors (dotnet build) (line 232)
  • [Validation] HTTP: app.MapMcp() is called in Program.cs (line 237)
  • [Validation] Server starts successfully with dotnet run (line 238)
  • [Pitfall] WithToolsFromAssembly() fails in AOT (line 247)
  • [WorkflowStep] Step 5: Add prompts and resources (optional) (line 151)
  • [CodePattern] CancellationToken (line 90)
  • [CodePattern] [Description] (line 90)
Uncovered: dotnet-ai/mcp-csharp-debug
  • [Validation] Breakpoints hit when debugging in IDE (line 185)
  • [Pitfall] HTTP server returns 404 at MCP endpoint (line 195)
Uncovered: dotnet-ai/mcp-csharp-publish
  • [Validation] NuGet: Package installs and runs via dnx PackageId@version (line 243)
  • [Validation] Azure: Server is reachable and tools respond (line 245)
  • [Validation] MCP Registry: Server appears at registry.modelcontextprotocol.io (line 246)
  • [Validation] MCP client can connect and call tools on the deployed server (line 247)
  • [Pitfall] Docker container exits immediately (line 255)
  • [Pitfall] API keys leaked in Docker image (line 258)
Uncovered: dotnet-ai/mcp-csharp-test
  • [Validation] All tests pass: dotnet test (line 165)
  • [Validation] Tests run in CI without manual setup (line 166)
  • [Pitfall] Full test suite runs are slow (line 176)
  • [WorkflowStep] Step 4: Run tests (line 138)
  • [CodePattern] [InlineData] (line 57)
  • [CodePattern] [Fact] (line 57)
  • [CodePattern] [Theory] (line 57)
Uncovered: dotnet-ai/technology-selection
  • [Validation] API keys are loaded from secure sources — not in source code or committed config files (line 314)
  • [Validation] Non-deterministic outputs have validation and fallback paths (line 319)
  • [Validation] dotnet build -c Release -warnaserror completes cleanly (line 320)
  • [Pitfall] Over-engineering with LLMs (line 348)
  • [Pitfall] Cold start latency on ML.NET models (line 354)
  • [WorkflowStep] Step 3: Implement with guardrails (line 134)
Uncovered: dotnet-template-engine/template-authoring
  • [Validation] Template identity and shortName are unique and meaningful (line 102)
  • [Validation] All parameters have descriptions and appropriate defaults (line 103)
  • [Validation] Template can be installed, dry-run, and instantiated successfully (line 104)
  • [Validation] Created projects build cleanly with dotnet build (line 105)
  • [Validation] Conditional content produces correct output for all parameter combinations (line 106)
  • [Pitfall] Identity format issues (line 112)
  • [Pitfall] Missing parameter descriptions (line 114)
  • [Pitfall] Not testing all parameter combinations (line 115)
  • [Pitfall] Not setting classifications (line 117)
  • [WorkflowStep] Step 3: Refine the template (line 80)
  • [WorkflowStep] Step 4: Test the template locally (line 90)
Uncovered: dotnet-template-engine/template-comparison
  • [Validation] Differences relevant to the user's scenario are called out explicitly (line 90)
  • [WorkflowStep] Step 3: Recommend (line 81)
Uncovered: dotnet-template-engine/template-discovery
  • [Validation] User understands what the template produces before proceeding to creation (line 145)
  • [Pitfall] A dotnet new call fails with a "mutex"/"persistence" error and you return nothing (line 154)
Uncovered: dotnet-template-engine/template-instantiation
  • [Validation] If CPM is active, .csproj has no version attributes and Directory.Packages.props has matching entries (line 128)
  • [Pitfall] Creating projects without specifying the framework (line 137)
  • [WorkflowStep] Step 2: Analyze the workspace (line 50)
  • [WorkflowStep] Step 3: Preview the creation (line 59)
  • [WorkflowStep] Step 6: Template package management (line 109)
Uncovered: dotnet-template-engine/template-smart-defaults
  • [Validation] No parameter the user set explicitly was overridden (line 62)
  • [Validation] Only unset parameters were filled (line 63)
Uncovered: dotnet-template-engine/template-validation
  • [Pitfall] ShortName = "list" or "search" (line 150)
  • [Pitfall] Invalid datatype value (line 153)
  • [Pitfall] Parameter prefix collision (Auth vs AuthMode) (line 155)
  • [Pitfall] Source condition without parentheses (line 156)
  • [WorkflowStep] Step 1: Locate the template.json (line 124)

@AbhitejJohn AbhitejJohn requested a review from Evangelink June 24, 2026 17:12
@AbhitejJohn

Copy link
Copy Markdown
Collaborator Author

Curious to hear thoughts on this versioning strategy. Goal was to keep this with as less ceremony as possible. At the moment Claude code users do not really get updates if they've already pulled down a plugin earlier because we haven't changed our versions.

@AbhitejJohn AbhitejJohn marked this pull request as ready for review June 24, 2026 17:14
Copilot AI review requested due to automatic review settings June 24, 2026 17:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces automated, per-plugin semantic versioning for the plugins/* tree using Nerdbank.GitVersioning (NBGV), and adds two GitHub Actions automations to materialize the computed versions into the checked-in plugin manifests (so downstream consumers read current versions directly from the repo).

Changes:

  • Add per-plugin plugins/<plugin>/version.json files to scope NBGV version height to each plugin subtree.
  • Add eng/version/Sync-PluginVersions.ps1 plus a locked-down eng/version/nuget.config and a local tool manifest for nbgv.
  • Add two workflows: /version-bump (issue_comment-driven, same-repo PRs) and a scheduled weekly backstop PR that stamps any drift on main.
Show a summary per file
File Description
plugins/dotnet11/version.json Adds NBGV per-plugin version configuration for dotnet11.
plugins/dotnet/version.json Adds NBGV per-plugin version configuration for dotnet.
plugins/dotnet-upgrade/version.json Adds NBGV per-plugin version configuration for dotnet-upgrade.
plugins/dotnet-test/version.json Adds NBGV per-plugin version configuration for dotnet-test.
plugins/dotnet-template-engine/version.json Adds NBGV per-plugin version configuration for dotnet-template-engine.
plugins/dotnet-nuget/version.json Adds NBGV per-plugin version configuration for dotnet-nuget.
plugins/dotnet-msbuild/version.json Adds NBGV per-plugin version configuration for dotnet-msbuild.
plugins/dotnet-maui/version.json Adds NBGV per-plugin version configuration for dotnet-maui.
plugins/dotnet-experimental/version.json Adds NBGV per-plugin version configuration for dotnet-experimental.
plugins/dotnet-diag/version.json Adds NBGV per-plugin version configuration for dotnet-diag.
plugins/dotnet-data/version.json Adds NBGV per-plugin version configuration for dotnet-data.
plugins/dotnet-blazor/version.json Adds NBGV per-plugin version configuration for dotnet-blazor.
plugins/dotnet-aspnetcore/version.json Adds NBGV per-plugin version configuration for dotnet-aspnetcore.
plugins/dotnet-ai/version.json Adds NBGV per-plugin version configuration for dotnet-ai.
eng/version/Sync-PluginVersions.ps1 Core PowerShell implementation to compute/stamp versions and emit a JSON report.
eng/version/nuget.config Locked-down NuGet config used by workflows to prevent PR-controlled feed remapping during tool restore.
.config/dotnet-tools.json Adds local nbgv tool dependency for consistent version computation in CI.
.github/workflows/version-bump-command.yml Implements the maintainer-triggered /version-bump workflow.
.github/workflows/weekly-version-sync.yml Implements the scheduled backstop that opens/updates a single “weekly sync” PR on drift.
CONTRIBUTING.md Documents the new per-plugin versioning model and contributor expectations.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 20/20 changed files
  • Comments generated: 4

Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md
Comment thread .github/workflows/weekly-version-sync.yml Outdated
Comment thread eng/version/Sync-PluginVersions.ps1
@github-actions github-actions Bot added the waiting-on-author PR state label label Jun 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 @AbhitejJohn — this PR has 4 unresolved review thread(s). When you're ready, please address the feedback and push an update; the triage bot will pick up the next state automatically. (Add the no-stale label to silence further pings.)

- Add missing plugins/dotnet-test-migration/version.json so it participates
  in versioning (it was the only plugin without one; manifests are at 0.1.0).
- CONTRIBUTING: the two manifests are not byte-identical; say the version is
  duplicated across two manifest files instead.
- weekly-version-sync: include version.json in commit attribution so a
  base-only bump is explained rather than showing 'no attributable commits'.
- Get-NbgvInfo: capture nbgv stderr and include it in the thrown error so CI
  failures are diagnosable, while keeping stdout clean for JSON parsing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink Evangelink enabled auto-merge (squash) June 25, 2026 07:57
@github-actions github-actions Bot added waiting-on-review PR state label and removed waiting-on-author PR state label labels Jun 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Evaluation passed for 766115f. cc @webreidi @AbhitejJohn @ViktorHofer @JanKrivanek @dotnet/aspnet @Redth @jfversluis @dotnet/skills-maui-reviewers @dotnet/skills-msbuild-reviewers @dotnet/area-infrastructure-libraries @kartheekp-ms @YuliiaKovalova @dotnet/dotnet-testing @ManishJayaswal — please review.

@Evangelink Evangelink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review: per-plugin NBGV versioning

Really like the overall design — per-plugin pathFilters, materializing into the checked-in manifests, the opt-in /version-bump + weekly backstop split, and the security hardening on the privileged workflow (SHA-pinned actions, fail-closed permission gate, fork rejection before any privileged step, --configfile source-mapped NuGet, trusted-tooling overlay from main, injection-safe MatchEvaluator). The security posture of version-bump-command.yml looks sound to me.

My concern is that the privileged workflow's git plumbing and the tool runtime pin look like they'll prevent /version-bump (and likely the weekly sync) from actually running. Because both workflows are issue_comment/schedule-triggered they run from main and get no CI signal on this PR, so these can only be caught by review. Two of the inline findings below were reproduced in a scratch repo.

This was a combined pass (two independent reviewers); we converged on the same set with no contradictions. Inline comments have details + suggested fixes. Summary:

Blocking / CI-breaking

  • git fetch --depth=1 origin main makes the whole repo shallow → NBGV refuses to compute height. Reproduced: a depth-limited fetch writes .git/shallow and flips the entire repo to shallow even though the head was checked out with fetch-depth: 0, so every nbgv get-version in the ordinary-content path fails.
  • git merge-base $BASE_SHA $head returns empty when the PR is behind main. Reproduced (PR 3 commits behind → empty merge-base, exit 1 → script throws -PredictSquashMerge requires -BaseCommit). Fixed by the same full-fetch change, plus an empty-merge-base guard.
  • nbgv (net8) under rollForward: false on a net11-preview-only runtime. setup-dotnet installs only the global.json runtime; a net8-targeted tool won't run, and even rollForward: true won't pick a preview runtime without DOTNET_ROLL_FORWARD_TO_PRERELEASE=1. Best fix: explicitly install an 8.0.x runtime in both workflows.

Correctness

  • Squash prediction over-bumps on version.json-only (non-base) edits, then the weekly backstop computes the true (lower) height and corrects downward — a non-monotonic version regression visible to consumers.

Minor

  • Format guard validates the computed value but not the 2-part base shape in version.json (a malformed 3-part base slips through the weekly path).
  • Concurrent /version-bump on the same plugin can stamp colliding patch numbers until the weekly sync reconciles — worth a doc note.
  • PR description says version.json ×14; there are 15 (dotnet-test-migration was added in commit 2).

- name: Pin trusted tooling from main
if: steps.pr.outputs.is_fork == 'false'
run: |
git fetch --no-tags --depth=1 origin main

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking (CI-breaker, reproduced). git fetch --no-tags --depth=1 origin main writes .git/shallow and turns the entire repository shallow — even though the head was checked out with fetch-depth: 0. NBGV checks Repository.Info.IsShallow at the repo level and throws Shallow clone lacks the objects required to calculate version height, so every nbgv get-version in the ordinary-content path (Sync-PluginVersions.ps1 heightAtBase) fails and /version-bump breaks for the common case.

Reproduced in a scratch repo: is-shallow(before)=false → after this fetch → is-shallow(after)=true.

Fix: drop --depth=1 (the repo is already fully fetched, so git fetch --no-tags origin main is cheap and keeps it non-shallow), or fetch the four tooling blobs without affecting shallow state and git checkout origin/main -- <files>. If a shallow fetch is unavoidable, follow with git fetch --unshallow before invoking NBGV.

BASE_SHA: ${{ steps.pr.outputs.base_sha }}
run: |
$head = git rev-parse HEAD
$mergeBase = git merge-base $env:BASE_SHA $head

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking (reproduced). git merge-base $BASE_SHA $head returns empty when main has advanced past the PR's branch point: BASE_SHA (the PR base.sha) isn't an ancestor of head, and the depth-1 fetch above truncates its ancestry at the shallow boundary, so merge-base can't reach the common ancestor (exit 1). $mergeBase becomes empty and the script throws -PredictSquashMerge requires -BaseCommit.

Reproduced (PR 3 commits behind main): merge-base result='' exit=1.

Fix: fetching main at full depth (the fix above) resolves the ancestry; additionally guard against an empty $mergeBase and fail with a clear message rather than passing -BaseCommit ''.

Comment thread .config/dotnet-tools.json
"commands": [
"nbgv"
],
"rollForward": false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking. nbgv 3.10.85 targets net8.0, but both workflows setup-dotnet from global.json (11.0.100-preview), which provides only the 11.0 runtime. With rollForward: false the host won't roll an 8.0-targeted tool across majors and fails with The framework 'Microsoft.NETCore.App', version '8.0.0' was not found unless a net8 runtime happens to be pre-installed on the runner (not pinned, not guaranteed).

Note rollForward: true alone is not sufficient here: the only installed runtime is a preview, and roll-forward won't select a prerelease runtime without DOTNET_ROLL_FORWARD_TO_PRERELEASE=1.

Preferred fix: explicitly install a net8 runtime in both workflows (setup-dotnet with an additional dotnet-version: 8.0.x), so nbgv doesn't depend on rolling a release tool onto a preview runtime.

}
else {
$heightAtBase = (Get-NbgvInfo -PluginDir $pluginDir -Commit $BaseCommit).VersionHeight
$computed = "$newBase.$([int]$heightAtBase + 1)"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Correctness (Medium). Get-ChangedPlugins intentionally counts version.json as a change, but each plugin's version.json excludes itself from NBGV height (:!version.json). So a PR that edits version.json without changing the base (e.g. tweaking pathFilters, $schema, or whitespace) has oldBase == newBase and lands here, predicting base.(heightAtBase + 1) — yet the squashed commit only touches height-excluded files, so the true post-merge height stays heightAtBase. The PR gets stamped one patch too high; the weekly backstop later computes the real (lower) height and corrects downward — a non-monotonic version regression consumers can observe.

Fix: in predict mode, check whether the BaseCommit..HeadCommit diff touches any height-bearing file for the plugin (apply the plugin's own pathFilters exclusions, not just the manifest excludes). Only add +1 when a height-bearing file actually changed; otherwise predict base.heightAtBase.

# Guard against a malformed version.json base (e.g. "0.2$1" or "1.x"): a bad value
# would otherwise be written verbatim into the manifests. NBGV versions are always
# numeric major.minor.patch, so anything else means the source data is wrong.
if ($computed -notmatch '^\d+\.\d+\.\d+$') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor. This guard validates the computed value, so a malformed 3-part base in version.json (e.g. "version": "0.1.0") is caught in predict mode (becomes 0.1.0.N → fails the 3-part check) but not in the weekly path: NBGV normalizes it into a 3-part SimpleVersion that passes this guard while silently producing a wrong/fixed version. Consider also asserting the version.json base matches ^\d+\.\d+$ when it's read in Get-VersionBase.

@github-actions github-actions Bot added waiting-on-author PR state label and removed waiting-on-review PR state label labels Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-author PR state label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants