← Back to posts

Dependency updates that understand your code

Part 2 of 7 in the Agentic (.NET) developer workflow series
  1. Turning your AI tool into your pair programming companion
  2. Dependency updates that understand your code
  3. Teaching your AI how to write tests with you
  4. Quality gates that actually run: verification and security in the agentic workflow
  5. Documentation as a first-class concern in your agentic workflow
  6. AI-driven usability testing: a think-aloud study with a team of AI testers
  7. Building and evolving your own AI development skills

We’ve all been there. You open your repository on Monday morning and there are a dozen dependency update PRs waiting. Some are patch updates, some are minor, one is a major version bump buried in the middle. CI is green on all of them.

You merge them. What could go wrong?

Note: The skills shown here reflect my stack and conventions at the time of writing. They improve over time as the workflow learns from daily use. Your project will have different tools, different security concerns, different quality bars. These are examples of what’s possible, not prescriptions. Fork them, adjust them, or use them as inspiration for your own.

The maze of dependency updates

The problem with automated dependency tools isn’t that they update packages. It’s that they update packages without understanding what changed. They see version numbers. They don’t see your code.

Here’s what that looks like in practice:

The silent behavioral change. Newtonsoft.Json v12 to v13 looks like a normal major bump. Dependabot creates a PR, your tests pass, you merge. But v13 changed the default MaxDepth from null (unlimited) to 64. If your API handles deeply nested JSON from external systems, requests start failing in production. No compiler error. No test failure. Just a 500 in your logs two weeks later.

The “which one broke it?” problem. You update twenty packages at once because Renovate grouped them by “minor updates.” The build passes. But in staging, one endpoint returns different data. You now need to bisect twenty package changes to find which one caused it. An afternoon gone.

The transitive vulnerability. Microsoft.Bcl.Memory has CVE-2026-26127, a high-severity out-of-bounds read in Base64Url decoding. But you don’t reference it directly. It comes in through another package. Dependabot might catch the direct dependency, but the transitive chain? That depends on how deep it looks.

The deprecated package you’re still using. Microsoft.AspNetCore.Mvc.NewtonsoftJson has been deprecated in favor of System.Text.Json. Renovate will keep bumping its version. It won’t tell you to stop using it.

These aren’t hypothetical scenarios. They’re Tuesday.

A different approach

In part 1 of this series, I introduced the agentic dev workflow: a framework that gives AI coding assistants persistent memory and directed sessions. One of its skills is /dev-dependency, and it handles updates very differently from Renovate or Dependabot.

The core difference: it doesn’t just bump version numbers. It reads release notes, checks for breaking changes, analyzes how your code uses the package, and walks you through the update in risk order. It feels like a helping hand instead of a maze.

Let me show you what that looks like.

Running /dev-dependency on a .NET project

You type /dev-dependency in your AI agent session. The skill detects your .csproj files and starts with an audit.

Step 1: The audit

It runs the equivalent of dotnet list package --outdated, --vulnerable, and --deprecated, then classifies everything by risk. The AI adapts how it presents this: sometimes as a text report, sometimes as a table. Both carry the same information.

Text format:

DEPENDENCY UPDATE REPORT — Backend (.NET)
==========================================

Security (fix immediately):
  🔒 Microsoft.Bcl.Memory     9.0.3  → 9.0.14  (CVE-2026-26127, High)

Patch (safe):
  Aspire.Hosting.AppHost     13.1.0 → 13.1.2
  Aspire.Hosting.Redis       13.1.0 → 13.1.2
  Aspire.Hosting.PostgreSQL  13.1.0 → 13.1.2

Minor (review):
  Polly                       8.2.0 → 8.4.0
  Serilog.Sinks.Console       5.0.0 → 5.1.0

Major (BREAKING — needs confirmation):
  ⚠ Newtonsoft.Json          12.0.3 → 13.0.3
  ⚠ xUnit                    2.7.0  → 3.0.0

Deprecated:
  ⚠ Microsoft.AspNetCore.Mvc.NewtonsoftJson
    → Deprecated: migrate to System.Text.Json

Or as a table:

Risk Package Current Latest Notes
🔒 Security Microsoft.Bcl.Memory 9.0.3 9.0.14 CVE-2026-26127, High
Patch Aspire.Hosting.AppHost 13.1.0 13.1.2
Patch Aspire.Hosting.Redis 13.1.0 13.1.2
Patch Aspire.Hosting.PostgreSQL 13.1.0 13.1.2
Minor Polly 8.2.0 8.4.0
Minor Serilog.Sinks.Console 5.0.0 5.1.0
⚠ Major Newtonsoft.Json 12.0.3 13.0.3 Breaking changes
⚠ Major xUnit 2.7.0 3.0.0 Breaking changes
Deprecated Microsoft.AspNetCore.Mvc.NewtonsoftJson Migrate to System.Text.Json

Either way, it’s a risk-stratified plan. Security first, patches next, then minor, then major. Each category gets handled differently.

Step 2: Security fixes first

The skill starts with Microsoft.Bcl.Memory. It doesn’t just update the version. It tells you what the vulnerability is:

🔒 Microsoft.Bcl.Memory 9.0.3 → 9.0.14
   CVE-2026-26127: Out-of-bounds read in Base64Url decoding
   Severity: High (CVSS 7.5)
   Disclosed: March 10, 2026

   This is a transitive dependency pulled in by
   Microsoft.Extensions.Caching.StackExchangeRedis.

   Impact on your code: Your API uses Base64Url encoding
   in the JWT token validation middleware (TokenService.cs:42).

   Applying patch...
   Running dotnet build... ✅
   Running dotnet test...  ✅ (127 passed)

Dependabot and Renovate will include release notes and changelogs in their PRs when available. But they won’t search your codebase for affected patterns. The difference is knowing that a CVE exists vs knowing that it affects TokenService.cs:42 in your project.

Step 3: Patch updates

Next, the Aspire packages. These are patch updates within the same minor version, so the risk is minimal. But instead of updating them one by one, the skill groups related packages that should move together:

Updating Aspire 13.1.x packages as a group:
  Aspire.Hosting.AppHost      13.1.0 → 13.1.2
  Aspire.Hosting.Redis        13.1.0 → 13.1.2
  Aspire.Hosting.PostgreSQL   13.1.0 → 13.1.2

These packages share a release cycle and should
stay on the same version.

Applying patches...
Running dotnet build... ✅
Running dotnet test...  ✅ (127 passed)

Renovate and Dependabot can group packages too. Renovate ships with built-in presets for known families and allows you to extend them. Dependabot supports grouping by name patterns in dependabot.yml. The difference here is that the AI agent recognizes package families from its understanding of the .NET ecosystem without upfront configuration. But credit where it’s due: for well-known package families, the existing tools handle this fine.

Step 4: Minor updates with review

For minor updates, the skill slows down. The AI agent draws on its knowledge of popular packages to flag anything noteworthy or you can ask it to looking the information online:

Minor: Polly 8.2.0 → 8.4.0
  Release notes: Added ResiliencePipelineBuilder.AddTimeout
  overload with CancellationToken support. New telemetry
  enrichment options for OpenTelemetry integration.
  No deprecations. No behavioral changes.

  Applying...
  Running dotnet build... ✅
  Running dotnet test...  ✅ (127 passed)

Each minor update is applied and verified separately. If Polly breaks something, you know it was Polly. Not Polly plus Serilog plus three other packages in the same PR.

Step 5: Major updates — full stop

This is where the real difference shows. The skill hits Newtonsoft.Json v12 to v13 and stops:

⚠ MAJOR VERSION UPDATE: Newtonsoft.Json 12.0.3 → 13.0.3

  Breaking changes detected:
  1. Default MaxDepth changed from null (unlimited) to 64
     → Your code: ApiController.cs:28 deserializes external
       webhook payloads that can nest 100+ levels deep.
       This WILL break.

  2. JsonExtensionData behavior changed for properties
     without setters.
     → Your code: AuditEvent.cs:15 uses [JsonExtensionData]
       on a read-only dictionary. Verify deserialization
       still works.

  Migration guide:
  https://www.newtonsoft.com/json/help/html/upgradeGuide.htm

  Affected files: 4
    src/Api/Controllers/ApiController.cs
    src/Api/Models/AuditEvent.cs
    src/Api/Middleware/JsonSettings.cs
    tests/Api.Tests/Serialization/JsonTests.cs

  Do you want to proceed? [y/n/skip]

Dependabot would have created a PR that said “Bump Newtonsoft.Json from 12.0.3 to 13.0.3.” Your tests would pass because they don’t test with 100-level-deep JSON. You’d merge and overlooked one crucial unexpected point. Production would break.

The skill caught it because it read the breaking changes documentation and then scanned your code for affected patterns. It found the deep nesting in ApiController.cs and the JsonExtensionData usage in AuditEvent.cs. It told you before you committed.

Step 6: Deprecated packages

Finally, it flags the deprecated package:

⚠ Microsoft.AspNetCore.Mvc.NewtonsoftJson is deprecated.
  Recommended: migrate to System.Text.Json

  This package is still functional but won't receive
  new features or non-critical fixes.

  Your code uses it in:
    Program.cs:14  (.AddNewtonsoftJson())
    Startup extensions

  This is a migration, not a version bump.
  Consider creating a /dev-blueprint for this.

Renovate would warn you about the deprecation and may even suggest a replacement for known migrations. But it won’t look at your codebase to assess the migration scope or suggest a structured plan. The skill connects the dots: deprecated package, here’s where you use it, here’s how to plan the migration with /dev-blueprint.

Running /dev-dependency on an Angular project

The same principles apply to the frontend, but the tooling and risks are different. When /dev-dependency detects a package.json, it routes to the npm workflow.

The audit

DEPENDENCY UPDATE REPORT — Frontend (Angular)
===============================================

Security:
  🔒 express                4.18.2 → 4.19.2  (CVE-2024-29041)

Patch (safe):
  rxjs                      7.8.0  → 7.8.1
  zone.js                   0.14.3 → 0.14.4
  @angular/material         17.3.0 → 17.3.4

Minor (review):
  @ngrx/store               17.1.0 → 17.2.0
  @angular-eslint/builder   17.2.0 → 17.5.0

Major (BREAKING — needs confirmation):
  ⚠ @angular/core           17.3.0 → 18.0.0
  ⚠ @angular/cli            17.3.0 → 18.0.0
  ⚠ typescript               5.3.3 → 5.5.0
  ⚠ rxjs                     7.8.1 → 8.0.0

Deprecated:
  ⚠ @types/jasmine
    → Deprecated: Vitest or Jest recommended over Jasmine/Karma

Security fixes

Same as the backend: security comes first. But here the skill adds a critical warning:

🔒 express 4.18.2 → 4.19.2
   CVE-2024-29041: Open redirect vulnerability in express
   Severity: Medium

   This is a dev dependency (used by @angular-devkit/build-angular
   for the dev server). Not exposed in production builds, but
   should still be patched.

   ⚠ Note: npm audit fix would fix this safely.
     npm audit fix --force would NOT be safe — it can silently
     install breaking major versions of unrelated packages.

   Applying safe fix...
   Running ng build... ✅
   Running ng test --watch=false... ✅ (84 passed)

That warning about npm audit fix --force matters. It’s a common mistake. Developers run --force when npm audit fix doesn’t resolve everything, and suddenly Angular jumps two major versions.

Patch and minor updates

Patches are grouped and applied safely. For minor updates, the skill checks for anything noteworthy:

Minor: @ngrx/store 17.1.0 → 17.2.0
  Release notes: Added createFeature() overload for
  signal-based selectors. New withDevtools() function
  for standalone stores.
  No deprecations. No behavioral changes.

  Applying...
  Running ng build... ✅
  Running ng test --watch=false... ✅ (84 passed)

Major updates: where npm gets painful

Angular itself has ng update schematics that handle a lot of migration automatically. But most npm packages don’t have that luxury. The major version bumps that really hurt are the ones where you’re on your own.

RxJS 7 → 8:

⚠ MAJOR VERSION UPDATE: rxjs 7.8.1 → 8.0.0

  Breaking changes detected:
  1. Import paths changed: 'rxjs/operators' removed
     → Your code: 14 files import from 'rxjs/operators'.
       All pipe operators must be imported from 'rxjs' directly.

       Before: import { map, filter } from 'rxjs/operators';
       After:  import { map, filter } from 'rxjs';

  2. Operator renames:
     → pluck() removed. Use map() instead.
       Found in: user.service.ts:23, settings.store.ts:41
     → toPromise() removed. Use firstValueFrom() or lastValueFrom().
       Found in: auth.service.ts:67, api.client.ts:15, api.client.ts:89

  3. Subscription handling changed:
     → Your code: 3 files use .add() for teardown logic.
       Use takeUntilDestroyed() or DestroyRef instead.
       Found in: dashboard.component.ts, chart.component.ts,
       notifications.component.ts

  No migration schematics available.
  Affected files: 22

  Do you want to proceed? [y/n/skip]

There’s no rxjs update command that fixes these for you. Renovate would bump the version, CI would fail with twenty import errors across fourteen files, and you’d spend an hour fixing them manually. The skill tells you exactly which files need which changes before you commit.

ESLint 8 → 9:

⚠ MAJOR VERSION UPDATE: eslint 8.57.0 → 9.0.0

  Breaking changes detected:
  1. Configuration format completely changed
     → .eslintrc.json found in your project root.
       ESLint 9 uses flat config (eslint.config.js).
       Your existing config will be IGNORED.

  2. Multiple plugins need compatible versions:
     → @angular-eslint/builder 17.5.0 — check compatibility
     → @typescript-eslint/parser 7.1.0 → needs v8+ for ESLint 9
     → eslint-plugin-import 2.29.0 → not yet compatible with
       flat config (open issue: eslint-plugin-import#2948)

  3. Formatter removed from core:
     → Your CI script uses eslint --format stylish.
       Formatters are now separate packages.

  This is a complex migration with ecosystem dependencies.
  Some plugins may not support ESLint 9 yet.

  Recommendation: defer this update until
  eslint-plugin-import releases flat config support.
  Run /dev-dependency --security to handle urgent
  fixes without touching ESLint.

  Do you want to proceed anyway? [y/n/skip]

This is the kind of update where Renovate creates a PR, CI fails, and you close it because you don’t have time to deal with it today. Then it creates the same PR next week. And the week after. The skill actually checks whether the ecosystem is ready for the migration and recommends deferring when key plugins aren’t compatible yet. That’s not a version check. That’s judgment.

Deprecated packages

⚠ @types/jasmine is deprecated.
  Recommended: migrate to Vitest or Jest.

  You already have Vitest partially configured:
    vitest.config.ts exists with 12 test files.
    karma.conf.js still present with 84 Jasmine tests.

  This is a migration, not a version bump.
  Consider running /dev-blueprint to plan the
  Jasmine → Vitest migration.

The skill notices you’re already partway through a migration. It doesn’t just flag the deprecated package, it tells you where you are in the transition.

What the skill does vs what the AI agent does

An important distinction: the /dev-dependency skill is a structured procedure. It defines the audit commands, the risk classification, the update order, the verification steps. But the AI executing that procedure adds intelligence on top. The code-level impact analysis, the package family recognition, the breaking change reasoning: that’s your AI agent reading your code and applying its training knowledge.

This means the quality of analysis varies. For well-known packages like Newtonsoft.Json, EF Core, or RxJS, the AI agent has deep knowledge of breaking changes between versions. For niche packages, it might have less to say. The skill provides the structure. The AI agent provides the depth.

Getting started

The /dev-dependency skill is part of the agentic-dev-workflow. If you’ve already set up the workflow from part 1, the skill is ready to use.

Run it with flags to control scope:

/dev-dependency              # Full audit and update
/dev-dependency --audit      # Report only, no changes
/dev-dependency --security   # Security fixes only
/dev-dependency --patch      # Patch updates only
/dev-dependency nuget        # .NET packages only
/dev-dependency npm          # Frontend packages only
/dev-dependency --interactive # Step through each package

For .NET projects with multiple solutions, consider running /dev-dependency --cpm first to convert to Central Package Management. This consolidates all version declarations into a single Directory.Packages.props file, making updates cleaner across your entire solution.

Learning from your upgrades

As covered in part 1, the agentic dev workflow has a self-learning loop. This applies to dependency management too.

Every time you run /dev-dependency and hit a breaking change, the decisions you make get logged in your daily memory. Maybe you discovered that Polly v8.4.0 changed how retry policies interact with HttpClientFactory in your setup. That goes into your session notes. Over time, /meta-continuous-learning picks up these patterns and can improve the skill itself.

Next time a colleague runs the same upgrade on a different project, the skill already knows to watch for that Polly interaction. The knowledge compounds. Your team’s upgrade history becomes part of the workflow, not just a memory in someone’s head.

This is something no external tool can do. Renovate and Dependabot have no concept of your team’s upgrade history. They treat every project as if it’s the first time.

In summary

Dependency management isn’t a version number problem. It’s a “what does this change mean for my code” problem.

Renovate and Dependabot are good tools. They keep your versions current, show release notes, warn about deprecations, and provide risk signals. They deserve credit for what they do well.

What they can’t do is read your code. They can’t tell you that a MaxDepth default change will break your specific webhook handler, or trace a transitive CVE to your JWT middleware, or recommend deferring an ESLint upgrade because the plugin ecosystem isn’t ready.

The /dev-dependency skill bridges that gap. Not by replacing your existing tools, but by adding the code-level context and interactive judgment that turns a list of version bumps into an informed upgrade session.

Next time you see a dozen dependency PRs on a Monday morning, try running /dev-dependency --audit first. The report might change which ones you merge.

← Back to posts