Managing Dependencies When AI Keeps Upgrading Things

Hướng dẫn chi tiết về Managing Dependencies When AI Keeps Upgrading Things trong Vibe Coding dành cho None.

Managing Dependencies When AI Keeps Upgrading Things

In the era of Vibe Coding, we have moved from the “Copy-Paste from StackOverflow” phase into the “Agentic Transformation” phase. You give an AI agent a prompt like “Fix the responsive navigation,” and five seconds later, it hands you a perfectly functioning UI. But then you look at your git diff. Along with the CSS fix, the AI—in its infinite “wisdom”—decided to upgrade your UI library from v4 to v5, swapped out your icon set, and introduced a new peer dependency that your CI/CD pipeline doesn’t recognize.

This is the Instant Legacy Problem. AI models, particularly those trained on vast swathes of the internet, often gravitate toward the “latest and greatest” syntax or the most popular current version of a library. While this keeps your code modern, it introduces a non-deterministic chaos into your dependency graph. If you aren’t careful, the “vibe” that feels so good during development becomes a nightmare of broken builds and hydration errors in production.

For the advanced developer, managing dependencies in an AI-driven workflow isn’t just about running npm install. It’s about building a Dependency Firewall that allows the AI to innovate within the code without compromising the stability of the foundation.


The Core Conflict: AI Logic vs. Semantic Versioning

The fundamental problem lies in the gap between an AI’s training data and your project’s specific lockfile. When an AI agent suggests code, it isn’t “thinking” about your package-lock.json. It is predicting tokens based on the most statistically probable implementation of a feature.

If the model was trained heavily on React 18 patterns, but your project is still on React 17 for legacy reasons, the AI will inevitably suggest a useId hook or a startTransition call that doesn’t exist in your environment. Worse yet, if your package.json uses floating versions (e.g., ^1.0.0), the AI might trigger a npm update during a “fix” that pulls in a breaking change you weren’t ready for.

The “Shadow Dependency” Problem

AI agents often introduce “Shadow Dependencies”—packages that aren’t explicitly listed in your package.json but are required for the code they just generated to run. Example: You ask for a date formatter. The AI generates code using date-fns. You don’t have date-fns installed. The code fails, and you (or the agent) spend the next three turns debugging why import { format } from 'date-fns' is throwing a module-not-found error.


How it Works: Building the Dependency Firewall

To survive Vibe Coding at scale, you need to implement a multi-layered strategy that constrains the AI while giving it enough room to move.

1. Strict Version Pinning (The Zero-Vibe Zone)

In a traditional project, ^ (caret) and ~ (tilde) are fine. In an AI project, they are dangerous. By pinning versions exactly (e.g., "react": "18.2.0"), you ensure that the AI cannot accidentally trigger a minor or patch upgrade that introduces a behavior change the model doesn’t understand.

2. The engines and .npmrc Enforcement

Use the engines field in your package.json to strictly define which version of Node and package managers are allowed. This prevents an AI agent from running a build on a version of Node that might have different ESM/CommonJS resolution logic.

"engines": {
  "node": ">=20.0.0",
  "npm": ">=10.0.0"
}

3. Automated Dependency Auditing via Pre-Commit

Advanced Vibe Coding setups use a “Dependency Auditor” script. This is a local script that runs every time the AI modifies the package.json. It checks for:

  • Peer Dependency Mismatches: Ensuring that if the AI adds a plugin, the core library version is compatible.
  • Bundle Size Regressions: If the AI adds lodash to do a simple cloneDeep, the auditor flags it.
  • License Compliance: Ensuring the agent didn’t pull in a GPL library into a MIT project.

Practical Example: Navigating the React 19 Jump

Imagine you are working on a Vite-based project. You ask your AI agent to “Optimize the form submission logic.” The agent sees that React 19 (which is all over its training data) has new useActionState and useFormStatus hooks. It refactors your code to use them, but your project is on React 18.2.

Instead of just letting it break, here is the advanced workflow to handle this:

Step 1: The Context Injection

Before the agent starts, you must inject the “Dependency Reality” into its context. This is often done via a .geminiignore or a CONVENTIONS.md file that the agent is forced to read.

Example CONVENTIONS.md entry:

“Current Stack: React 18.2, Tailwind 3.4. DO NOT use React 19 features or Tailwind 4 features. If a new dependency is needed, ask for permission before adding it to package.json.”

Step 2: The Verification Loop

When the agent provides the code, you run a verification script that checks for the existence of the imported symbols in your node_modules.

// scripts/verify-deps.ts
import packageJson from '../package.json';

const allowedDeps = Object.keys(packageJson.dependencies);
const checkImports = (code: string) => {
  const imports = code.match(/from ['"](.*)['"]/g);
  imports?.forEach(imp => {
    const pkg = imp.replace(/from ['"]|['"]/g, '');
    if (!allowedDeps.includes(pkg) && !pkg.startsWith('.')) {
      throw new Error(`AI tried to use uninstalled dependency: ${pkg}`);
    }
  });
};

Step 3: The “Surgical” Update

If you do decide to let the AI upgrade something, don’t let it do it “on the fly.” Use a dedicated “Upgrade Track.”

  1. Create a new branch: feature/upgrade-to-react-19.
  2. Explicitly prompt the AI: “Upgrade this project to React 19. Start by updating the package.json, then run npm install, then fix the breaking changes in the following order…”
  3. Validate with a test suite that has 100% coverage on critical paths.

Best Practices & Tips for the Advanced Vibe Coder

1. Leverage pnpm Over npm

pnpm is the superior choice for AI-driven development because of its strictness. Unlike npm, pnpm does not allow code to access dependencies that aren’t explicitly listed in the package.json (no “hoisting” ghosts). This forces the AI to be honest about what it’s using. If the agent forgets to add a package, the build fails immediately, preventing hidden runtime errors.

2. The “Three-Branch” Strategy

When managing an AI that likes to upgrade things, maintain three types of branches:

  • Main: The stable, pinned-version production branch.
  • Feature: Where the AI adds business logic using existing dependencies.
  • Experiment: Where the AI is allowed to “upgrade the world” to see if performance improves.

3. Use Custom types for AI Guidance

If you are using TypeScript, define custom types that act as “guardrails.” For instance, if you want the AI to avoid a specific deprecated utility from a library, you can use the @deprecated JSDoc tag. Modern AI models are surprisingly good at respecting these annotations.

/** 
 * @deprecated Use the internal 'UtilityService' instead of the 'old-lib' version.
 * AI NOTE: Never import from 'old-lib/deprecated-path'.
 */
export const LegacyUtil = ...

4. Shadow-Installing (The “Try Before You Buy” Pattern)

If an agent suggests a new library, tell it: “Install it in a temporary directory first and show me a benchmark.” This prevents your main package.json from becoming a graveyard of abandoned AI experiments.


Dealing with the “Dependency Spiral”

Sometimes, an AI will get stuck in a “Dependency Spiral.” It upgrades Package A, which requires Package B to be v2.0, which breaks Package C. The agent then tries to upgrade Package C, which isn’t compatible with your Node.js version.

How to break the spiral:

  1. Hard Reset: Revert the package.json and lockfile to the last known good state.
  2. Isolation: Ask the AI to solve the problem without upgrading the library. Usually, the “upgrade” was just a shortcut for a logic fix the AI could have written manually.
  3. Manual Intervention: Some things are still too complex for current agents. If a peer dependency conflict involves more than three packages, it’s time for you, the human architect, to step in and define the versions manually.

Conclusion: Mastering the Vibe

Vibe Coding is not about being lazy; it’s about being high-leverage. An advanced developer uses AI to move faster, but uses their architectural knowledge to ensure that speed doesn’t lead to a crash.

By treating your dependencies as a controlled environment rather than a free-for-all, you turn the AI from a chaotic “intern who keeps changing the config” into a powerful “architectural engine.” Pin your versions, enforce your engines, and always verify the “Shadow Dependencies.”

The goal is a project where the code is as fluid as a vibe, but the foundation is as solid as a rock. Managing dependencies effectively is the only way to achieve that balance. When the AI tries to upgrade your entire stack for a minor bug fix, you’ll be ready to say: “Nice try, but let’s stick to the plan.”