Skip to content
Open
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.10.0] - 2026-09-08

This is a minor release that introduces `dotnet-nuget-update`, a deterministic NuGet dependency audit and update workflow for .NET repositories. The skill owns the complete-audit invariant, ensuring every declared package version is accounted for before any update is applied. It handles both central package management (`Directory.Packages.props`) and project-level `PackageReference` versioning, tracks stable and prerelease intent, preserves TFM-band pins (keeping `net9` or `net10` packages within their matching major when that major is the compatibility signal), and supports both normal mode (auto-applies patch/minor/revision, batches majors for approval) and yolo mode (auto-applies safe classes only, reports held majors). All scripts are deterministic and offline-testable via bundled fixtures.

### Added

- `dotnet-nuget-update` skill for auditing and updating NuGet dependencies with complete declaration accounting, supporting both central package management and project-level versioning, two interactive modes (normal with approval batching, yolo for safe updates only), stable/prerelease intent inference, and TFM-band awareness so conditional `net9`/`net10` package declarations stay within their matching major when that major is the compatibility signal rather than jumping to the newest overall release,
- bundled deterministic scripts: `Get-DependencyAudit.ps1` for complete graph enumeration before any edit, `Get-PackageGraph.ps1` for central-package condition resolution, `Get-TargetFrameworks.ps1` for TFM matrix discovery, `Resolve-NuGetVersion.ps1` and `Compare-Version.ps1` for version investigation, `Apply-PackageUpdates.ps1` for minimal structural XML edits preserving comments and line endings, and `Get-NuGetSources.ps1` for feed configuration visibility,
- comprehensive test coverage: regression harnesses for dependency audit, package graph, TFM-band logic, project-level package references, version comparison, and update application, together with offline-testable fixtures covering central-package scenarios, mixed stable/prerelease intent, multi-TFM bands, plain project references, and XML comment pinning,
- per-process memoization for live or offline flat-container NuGet version feeds, with filesystem flat-container fixtures supporting deterministic offline testing,
- complete-audit invariant validation ensuring `current + auto + approval + unresolved == declared` before reporting the repository as updated,
- history-aware update tracking with adjacent XML comments surfacing as `note` fields so pin rationale remains visible during audits and auto-updates marked with `READ THE NOTE before applying` receive explicit attention,
- README catalog entry, installation snippet, and "Why dotnet-nuget-update?" community health section explaining the need for complete dependency graph auditing, TFM-band awareness, and preservation of intentional pins and compatibility markers.

## [0.9.1] - 2026-09-07

This patch release adds harness-agnostic Eval Runner execution boundary infrastructure without changing the paired evaluation methodology or existing report schemas, while optimizing skill descriptions and refactoring repository-level authoring guidance. Prepared packages now carry `execution-profile.json`, package-local runner protocol tools, and normalized `execution-result.json` evidence. The deterministic fake runner is the conformance reference, with Codex, GitHub Copilot CLI, and OpenCode as supported real adapters. Repository automation remains model-free; only a human-directed external Eval Orchestrator may invoke the selected runner, and unsupported isolation fails closed.
Expand Down Expand Up @@ -638,6 +652,7 @@ This is a minor release that introduces two complementary git workflow skills, e

- Improved scaffold fidelity with hidden `.bot` asset preservation, explicit UTF-8 and BOM handling, and checks aimed at preventing mojibake or incomplete generated output.

[0.10.0]: https://github.com/codebeltnet/agentic/compare/v0.9.1...v0.10.0
[0.9.1]: https://github.com/codebeltnet/agentic/compare/v0.9.0...v0.9.1
[0.9.0]: https://github.com/codebeltnet/agentic/compare/v0.8.2...v0.9.0
[0.8.2]: https://github.com/codebeltnet/agentic/compare/v0.8.1...v0.8.2
Expand Down
20 changes: 20 additions & 0 deletions README.md

Large diffs are not rendered by default.

190 changes: 190 additions & 0 deletions skills/dotnet-nuget-update/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
---
name: dotnet-nuget-update
description: >
Use when the user wants to update, audit, or check NuGet package dependencies
in a .NET repository. Handles both central package management
(Directory.Packages.props) and project-level PackageReference. Invoke as
"dotnet-nuget-update" for interactive normal mode (auto-applies patch/minor,
asks about majors) or "dotnet-nuget-update yolo" for silent patch/minor-only
mode. Use any time packages need updating, versions need auditing, or the
user asks about outdated dependencies.
---

# .NET NuGet Update

Use this skill when a .NET repository needs a complete dependency audit or a controlled package update pass.

## Start with the audit, not intuition

Your first deterministic step is:

```powershell
pwsh -NoProfile -File "<skill-root>/scripts/Get-DependencyAudit.ps1" -RepoRoot "<repo-root>"
```

Use `scripts/Get-DependencyAudit.ps1 -RepoRoot <path>` to enumerate every declaration before touching anything. The skill is complete only when every declared package version is accounted for.

The bundled scripts do the mechanical work:

- `scripts/Get-DependencyAudit.ps1` enumerates and classifies each declaration.
- `scripts/Get-PackageGraph.ps1` exposes the central-package condition graph.
- `scripts/Get-TargetFrameworks.ps1` exposes the repository TFM matrix.
- `scripts/Resolve-NuGetVersion.ps1` and `scripts/Compare-Version.ps1` investigate one package or version pair.
- `scripts/Apply-PackageUpdates.ps1` performs the minimal structural edit.
- `scripts/Get-NuGetSources.ps1` shows configured package feeds without exposing secrets.

The agent orchestrates. The scripts own the deterministic enumeration, comparison, and file edits.

## Two modes

### Normal mode

Normal mode is for an attended update run.

1. Audit the whole graph first.
2. Auto-apply only `revision`, `patch`, `minor`, and same-major `prerelease` steps.
3. Do not interrupt the user for each package.
4. Batch all `major` candidates into one approval question after the full audit is complete.
5. Preserve any deliberately held pins and explain why they were held.

### Yolo mode

Yolo mode means no approval prompts, not broader authority.

1. Audit the whole graph first.
2. Auto-apply only `revision`, `patch`, `minor`, and same-major `prerelease` steps.
3. Hold all `major` candidates.
4. Report the held majors explicitly at the end.

Yolo never means “apply majors silently,” and it never means commit or push anything.

## The complete-audit invariant

The audit is not optional scaffolding. It is the work list.

For every run, ensure the summary closes:

- `declared`
- `current`
- `auto`
- `approval`
- `unresolved`

The invariant is:

```text
current + auto + approval + unresolved == declared
```

Do not report the repository as updated unless every declaration is in exactly one bucket. A package that appears under two different conditions is two declarations and must produce two audit rows.

## TFM-band rule

Conditional central package graphs are load-bearing. Never flatten them.

If a declaration lives under a modern .NET TFM condition and its pinned major matches that band, keep resolution inside that band.

Examples:

- `$(TargetFramework.StartsWith('net9'))` + `Microsoft.Extensions.Logging` `9.0.0` → resolve within `9.x`
- `$(TargetFramework.StartsWith('net10'))` + `Microsoft.EntityFrameworkCore` `10.0.0` → resolve within `10.x`
- `$(TargetFramework.StartsWith('net9'))` + `Asp.Versioning.Http` `8.1.0` → no band restriction, because package major `8` does not match band `9`
- `$(TargetFramework.StartsWith('net10')) OR $(TargetFramework.StartsWith('net11'))` + `10.0.0` → resolve within `10.x`, because `10` is one of the declared bands and matches the pinned major

The band rule is a compatibility safeguard, not a guess about package policy.

## Stable versus prerelease intent

Infer package intent from the pin unless the user asks for something else.

- If the pinned version is stable, prefer stable candidates only.
- If the pinned version is prerelease, allow prerelease candidates for that package.
- If the caller explicitly requests prerelease review, use `-IncludePrerelease`.
- Same-major prerelease movement is an `auto` class, not an approval class.

That means `1.0.0-rc.1` → `1.0.0-rc.2` is a `prerelease` bump and may be auto-applied, while `13.0.3` → `14.0.0` remains `approval`.

## History first: comments and past decisions

Before changing a held or surprising pin, inspect its context.

1. Read the adjacent XML comment through the audit `note` field.
2. Treat comments as maintainer intent, not decoration.
3. Review file or repository history when the pin looks deliberate or compatibility-sensitive.
4. If an `auto` candidate has a note, pause and read it before applying the update.

A version comparison can tell you what is newer. It cannot tell you why a repository deliberately stayed behind.

## Structural editing only

NuGet props files and project files are structured XML, so edit them structurally and minimally.

Use `scripts/Apply-PackageUpdates.ps1` to update only the targeted declaration. Preserve:

- conditions and item-group boundaries,
- comments and blank lines,
- package ordering,
- indentation,
- encoding,
- existing line endings.

Do not rewrite the file wholesale. Change only the relevant `Version` attribute or project-level `<PackageReference Version="...">` value.

## Central package management and project-level references

Handle both repository styles.

### Central package management

When `Directory.Packages.props` exists:

- inspect it with `scripts/Get-PackageGraph.ps1`,
- audit it with `scripts/Get-DependencyAudit.ps1`,
- apply updates through `scripts/Apply-PackageUpdates.ps1`.

### Project-level package references

When the repository does not use central package management:

- audit explicit project-level `PackageReference` versions,
- update only the affected `.csproj` files,
- preserve unrelated project content.

Do not invent central package management for a repository that does not already use it.

## Dirty working trees and conflicts

Treat in-place dependency work as a surgical edit in a potentially dirty repository.

- Do not overwrite a declaration whose current file value no longer matches the audited `from` version.
- If `scripts/Apply-PackageUpdates.ps1` reports `conflict`, stop and report it instead of guessing.
- Leave unrelated dirty files alone.
- Re-run the audit after applying updates when the repository state changed materially.

A conflict is evidence that the file changed after the audit. Respect that evidence.

## Validation

Prefer the smallest deterministic validation that proves the update is safe.

1. Discover target frameworks with `scripts/Get-TargetFrameworks.ps1 -RepoRoot <path>`.
2. If source selection matters, inspect feeds with `scripts/Get-NuGetSources.ps1`.
3. Run the narrowest restore, build, or test command that covers the affected projects and target frameworks.
4. If the repository already has a targeted test or validation command, use it rather than inventing one.

If no code changed because every declaration was already current, say so and report the audit summary anyway.

## Reporting

The final report must contain:

1. Repository path and whether it used central or project-level package management.
2. Audit summary with `declared/current/auto/approval/unresolved` counts.
3. Every package actually updated, including condition or file context.
4. Every major candidate held for approval or yolo holdback.
5. Every unresolved package.
6. Every note-bearing declaration that was held or required human review.
7. Validation commands run and their outcomes.
8. Any conflicts or manual follow-up required.

If nothing changed, say that explicitly and still include the complete audit summary.
108 changes: 108 additions & 0 deletions skills/dotnet-nuget-update/evals/evals.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"skill_name": "dotnet-nuget-update",
"evals": [
{
"id": 1,
"prompt": "Audit the attached repository and update NuGet packages without breaking its TFM-specific package strategy. The repo targets net9.0 and net10.0, and the same Microsoft package is pinned separately for each band. Resolve every declaration first, then update each band to the newest version that still fits that band instead of proposing the overall newest major.",
"expected_output": "The agent performs a complete dependency audit before editing, updates Microsoft.Extensions.Logging separately within 9.x and 10.x, leaves Newtonsoft.Json as already current, and does not propose an 11.x jump for the band-tracked package.",
"expectations": [
"Runs the complete dependency audit before changing any package",
"Treats the net9 declaration and the net10 declaration as separate rows",
"Updates the net9 package only within 9.x and the net10 package only within 10.x",
"Does not flatten the conditional graph into one shared package version",
"Reports the full audit summary with declared/current/auto/approval/unresolved counts"
],
"files": [
"evals/files/multi-tfm-bands/Directory.Packages.props",
"evals/files/multi-tfm-bands/Directory.Build.props"
]
},
{
"id": 2,
"prompt": "Update the attached central package graph. The latest overall Microsoft.Extensions.Logging release is a newer major, but the repository intentionally pins separate versions under net9 and net10 conditions. Finish the full audit and apply only the in-band updates that remain patch or minor steps.",
"expected_output": "The agent completes the audit without interruption, applies only in-band patch or minor updates, and reports any out-of-band latest major as held by the TFM-band rule instead of proposing it as the target.",
"expectations": [
"Completes the audit before asking anything",
"Uses the TFM-band rule rather than the overall latest package version",
"Finishes without interruption when all in-band updates are auto classes",
"Explains when a newer overall version exists outside the allowed band"
],
"files": [
"evals/files/multi-tfm-bands/Directory.Packages.props",
"evals/files/multi-tfm-bands/Directory.Build.props"
]
},
{
"id": 3,
"prompt": "Review the attached mixed stable and prerelease package graph. Keep stable dependencies on stable releases, allow prerelease movement for the dependency that is already pinned to a prerelease, and avoid asking for approval when that prerelease update stays inside the same major.",
"expected_output": "Stable pins stay on stable candidates, the prerelease pin advances only within the intended prerelease line, and the same-major prerelease step is treated as an auto update instead of an approval candidate.",
"expectations": [
"Infers stable intent from stable pins",
"Infers prerelease intent from the prerelease pin",
"Treats same-major prerelease movement as auto rather than approval",
"Still reports the complete audit summary"
],
"files": [
"evals/files/mixed-stable-prerelease/Directory.Packages.props",
"evals/files/mixed-stable-prerelease/Directory.Build.props"
]
},
{
"id": 4,
"prompt": "Use dotnet-nuget-update on the attached repository and apply all patch and minor NuGet updates. There are no majors that need approval, so the normal-mode run should complete without interrupting me.",
"expected_output": "The agent audits the repository, applies the safe updates, preserves the XML structure, and finishes without asking a question because only auto classes are present.",
"expectations": [
"Normal mode does not interrupt the user when only auto updates are present",
"Applies patch and minor updates structurally rather than rewriting the file",
"Reports what changed and what remained current"
],
"files": [
"evals/files/flat-stable-only/Directory.Packages.props",
"evals/files/flat-stable-only/Directory.Build.props"
]
},
{
"id": 5,
"prompt": "Run dotnet-nuget-update in normal mode on the attached repository. Some dependencies have major upgrades available. Complete the full audit first, then ask only one batched approval question for the held majors instead of stopping package by package.",
"expected_output": "The agent finishes the full dependency audit before any approval prompt, batches the major candidates into one decision point, and separates those approval items from the auto-updatable packages.",
"expectations": [
"Performs the complete audit before any approval question",
"Batches major candidates into one approval decision",
"Keeps auto candidates separate from approval candidates",
"Reports unresolved or note-bearing rows alongside the rest of the audit"
],
"files": [
"evals/files/with-xml-comment-pin/Directory.Packages.props",
"evals/files/with-xml-comment-pin/Directory.Build.props"
]
},
{
"id": 6,
"prompt": "Run dotnet-nuget-update yolo on the attached repository. Apply patch and minor updates silently, hold major upgrades without asking, and show the held majors in the final report.",
"expected_output": "The agent applies only the auto classes, leaves majors untouched, asks no follow-up question, and reports the held majors explicitly at the end.",
"expectations": [
"Yolo mode never applies majors silently",
"Yolo mode asks no approval question",
"The final report lists held major candidates separately from applied updates"
],
"files": [
"evals/files/with-xml-comment-pin/Directory.Packages.props",
"evals/files/with-xml-comment-pin/Directory.Build.props"
]
},
{
"id": 7,
"prompt": "Update the attached repository, but pay attention to any inline notes explaining why a package is pinned. Preserve the comment, surface its warning in the audit, and avoid bulldozing through a deliberate hold just because the version step looks safe.",
"expected_output": "The agent preserves the XML comment, surfaces the note in the audit, and treats the note-bearing declaration as a deliberate human decision that must be read before applying any update.",
"expectations": [
"Carries the adjacent XML comment into the audit note field",
"Preserves the comment and surrounding formatting during any edit",
"Marks a note-bearing auto candidate as needing the note to be read first"
],
"files": [
"evals/files/with-xml-comment-pin/Directory.Packages.props",
"evals/files/with-xml-comment-pin/Directory.Build.props"
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Asp.Versioning.Http" Version="8.1.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
</Project>
Loading
Loading