← All articles

Articles

Project Memory in VS Code: Refresh After a Migration

Two days after you merge the Vite migration, your coding agent opens a PR that adds a script to package.json called "start" and imports a helper from "react-scripts". The code is tidy. The tests it wrote are tidy. It is also wrong for a project that no longer has react-scripts installed.

The agent did not get dumber. It read the instructions you wrote six months ago, and those instructions still describe Create React App. Project memory is a snapshot of what you believed on the day you wrote it, and a migration is the exact moment that snapshot stops being true.

This post is a hands-on audit you can run in an afternoon. We use a clearly labeled hypothetical repo (a Create React App project moved to Vite) and walk through four steps: find every instruction that names the old stack, check each claim against package.json scripts and real files, rewrite a short conventions checklist, and verify it with the tests you already have. Product mechanics stay generic here. The method works with any agent that reads written instructions from your repo.

Why project memory goes stale the day a migration merges

Project memory is whatever your agent reads before it touches your code: a conventions file, a README section, saved notes from earlier tasks, comments pinned at the top of a folder. Different tools store it differently. The failure mode is the same everywhere. Someone wrote a sentence like "run tests with npm test" or "environment variables start with REACT_APP_", and the codebase moved on without that sentence being touched.

Migrations are the worst case because they change facts, not style. A renamed variable is cosmetic. A new test runner changes the command, the config file, the mock syntax and the import paths in one merge. Your memory file now contains several confident, specific, false statements.

The agent has no reason to doubt them. Instructions you wrote read as ground truth, so it follows them faithfully. A faithful agent plus a stale instruction produces code that looks deliberate and is wrong.

There is a second cost that Indian freelancers feel directly. Every wrong PR costs a review cycle, and review cycles on client work are unbilled hours. If you run on a daily allowance of agent runs (free tiers are commonly counted per day, as covered in Free AI Coding Agents: Daily Limits vs Monthly Quotas), runs spent on corrections are runs you do not get back for real work.

My position: a framework migration isn't finished until the project memory is audited. The migration PR is not done when the build passes. It is done when the instructions match the repo.

The hypothetical repo: a Create React App project moved to Vite (illustrative example, not a customer result)

Everything below uses an invented repo so the steps are concrete. It is not a customer result and the numbers are made up for illustration.

Call it "invoice-tracker", a small React app a freelancer built for a client. Before the migration it used Create React App with react-scripts, Jest through the CRA wrapper, and environment variables prefixed with REACT_APP_. After the migration it uses Vite, Vitest, and variables prefixed with VITE_.

Here is the before-and-after of the facts that matter:

  • Dev server: npm start (react-scripts) became npm run dev (Vite).

  • Build: npm run build still exists, but output moved from build/ to dist/.

  • Tests: npm test ran Jest in watch mode. Now npm test is wired to Vitest, with npm run test:run for a single pass.

  • Env vars: process.env.REACT_APP_API_URL became import.meta.env.VITE_API_URL.

  • Entry file: src/index.js became src/main.jsx, and index.html moved to the project root.

  • Test setup: src/setupTests.js became vitest.setup.js, referenced from vite.config.js.

The repo also has a memory file the freelancer wrote early on. It is 60 lines long and was last edited before the migration. Some of it is still right: the folder layout, the naming rules, the instruction to keep API calls inside src/api/. Some of it is now wrong in six places, and the freelancer does not know which six.

That is the realistic situation. The file is not obviously broken. It is mostly fine, and mostly fine is what makes it dangerous, because nobody rereads a file they trust.

Step 1: Find every instruction that names the old stack

Start with a plain text search. Do not read the memory file top to bottom and hope your eyes catch things. Search for the old stack by name.

In the invoice-tracker example, the search terms are: react-scripts, REACT_APP_, npm start, craco, jest, setupTests, index.js, build/, Create React App, and CRA. Run them across the whole repo, not just the memory file. In VS Code, open the search panel with Ctrl+Shift+F (Cmd+Shift+F on macOS), turn on "files to include" for markdown, JSON and config files, and search each term.

Check these places, because memory hides in more than one file:

  • The conventions or instructions file your agent reads.

  • README.md and any CONTRIBUTING.md.

  • Comments at the top of test files ("run with npm test -- --watchAll=false").

  • Pull request templates and issue templates.

  • Any saved notes or remembered preferences your agent tool keeps for the project, if it has that feature.

  • CI config, which often encodes the old test command.

Write each hit into a scratch list with the file and line. For invoice-tracker, a realistic result is nine hits across four files: three in the memory file, two in the README, two in test file comments, one in a PR template, one in a CI workflow.

Do not fix anything yet. This step is only about building the list. Fixing while searching leads to the classic mistake of correcting the obvious hits and missing the ones phrased differently.

Also search for the old stack's behaviours, not just its names. A line like "tests run in watch mode by default" never mentions Jest but is a Jest-ism. Search for watch, coverage, and snapshot and read what surrounds them.

Step 2: Check each claim against package.json scripts and real files

Now audit. For every instruction on your scratch list, ask one question: can I prove this is true by looking at the repo right now?

Open package.json first. The scripts block is the closest thing your project has to a source of truth for commands. Take each command mentioned in your instructions and compare it to the actual scripts.

For invoice-tracker, the audit table looks like this:

  • Memory says "Start the app with npm start." Reality: scripts has dev, no start. Claim is false.

  • Memory says "Run tests with npm test -- --watchAll=false." Reality: test runs vitest, and the single-pass script is test:run. The flag is a Jest flag. Claim is false.

  • Memory says "Build output goes to build/." Reality: Vite writes to dist/, and .gitignore lists dist. Claim is false.

  • Memory says "Put shared mocks in src/__mocks__/." Reality: that folder still exists and Vitest picks up mocks differently. Claim is partly true, needs a rewrite.

  • Memory says "API calls live in src/api/." Reality: folder exists, files import from it. Claim is true. Keep it.

Then check real files, not just scripts. Does src/setupTests.js exist? Does vite.config.js reference a setup file? Does any source file still read process.env.REACT_APP_? A quick search settles that last one. If three files still use it, your instruction to use VITE_ is correct as a rule but the repo is not fully migrated. You want to know that before the agent copies the old pattern from a neighbouring file.

Mark each line true, false, or partial. Anything you cannot verify gets cut, not kept on faith.

This step is where most of the value is. In the example, five of nine hits were false or partial. That is typical: a migration invalidates the commands and paths, while the architectural rules survive.

Step 3: Rewrite a short conventions checklist

After an audit, the temptation is to patch the old file: swap npm start for npm run dev and move on. Resist it. Long memory files rot faster than short ones, because every extra line is another claim that can go stale.

Rewrite instead. Aim for a checklist of ten to fifteen lines that an agent can follow and you can verify in one minute. Every line should be either a command that exists in package.json or a rule you can check in the file tree.

Here is the rewritten checklist for the hypothetical invoice-tracker repo:

  • Stack: React with Vite. Test runner is Vitest. No react-scripts, no Jest.

  • Run the dev server: npm run dev.

  • Run all tests once: npm run test:run. Run before declaring any task finished.

  • Build: npm run build. Output is dist/.

  • Env vars must be prefixed VITE_ and read through import.meta.env. Never use process.env.REACT_APP_.

  • Entry file is src/main.jsx. index.html lives at the project root.

  • Test setup lives in vitest.setup.js. Do not recreate setupTests.js.

  • API calls stay inside src/api/. Components never call fetch directly.

  • Components are function components with named exports. File names are PascalCase.

  • Do not add dependencies without asking.

Look at what changed. The commands are copy-paste exact. The negative rules ("no react-scripts", "do not recreate setupTests.js") are there on purpose, because an agent pattern-matching on older code in the wild will reach for the old tool unless told not to. And the file says which command proves the work is done.

Delete anything you cannot justify. A rule about commit message style that nobody enforces is noise. Keep only what changes the agent's output.

Put the date of the audit and the migration commit at the bottom. Next time you wonder whether the file is current, you will know what it was checked against.

Step 4: Verify with your existing tests, and plan the change first

A rewritten checklist is a claim. Your existing tests are how you check it, and you do not need to write new ones to do that.

First, confirm the checklist's own commands work. Run npm run test:run and npm run build yourself. If either fails, the checklist is lying and the agent will inherit the lie. Fix the checklist or the repo before involving the agent.

Second, give the agent a small, boring task that exercises the new conventions. For invoice-tracker: "Add a VITE_FEATURE_EXPORT flag and a component that renders only when it is on, with a test." The task touches env vars, the test runner, the setup file and file naming in one go. If the stale memory is gone, the result uses import.meta.env, a .test.jsx file Vitest can run, and no mention of Jest.

Third, ask for a plan before any code. A written plan is where stale assumptions show up cheaply: if the plan says "add REACT_APP_FEATURE_EXPORT to .env", you have caught a leftover for the price of one read instead of one review. This is the habit covered in Plan Before Coding With AI: Review Changes in VS Code. OxCode has a Plan task mode for this. It runs what it writes in a sandbox and fixes its own bugs before calling a task done, so a wrong convention tends to surface as a failing test rather than a surprise in review.

Fourth, treat the existing test suite as the referee. If the agent's change passes npm run test:run and the build, and your search for old-stack terms returns zero new hits, the memory is doing its job. If you want a method for keeping that suite green while the agent edits, Fix AI-Generated Code Without Breaking Existing Tests covers it.

A green run proves the commands and conventions work together. That is the evidence you wanted.

When the agent still follows the old convention: debug it

Sometimes you rewrite the checklist, run the task, and the agent still reaches for REACT_APP_. Do not retype the rule louder. Debug it like any other bug: narrow down where the old instruction is coming from.

Work through these causes in order, cheapest first.

  1. A second copy of the old rule exists. Re-run the Step 1 search, including hidden folders and any notes your agent tool stores per project. A stale line in a README or a test comment often outvotes your clean checklist.

  2. The repo itself still teaches the old pattern. Agents imitate neighbouring files. If three components still read process.env.REACT_APP_, the agent sees a local convention stronger than your written one. Finish the migration in code, then rerun the task.

  3. The rule is vague. "Use the new env style" fails, while "read env vars via import.meta.env, prefix VITE_" works. Rewrite any line that depends on the agent knowing which style is new.

  4. The checklist is too long. If the file has grown back to 80 lines, the important negatives are buried. Cut it back to the ten lines that change output.

  5. The task is outside the checklist. If the agent invents a new pattern for something the file never mentions, add one line about it, only if you can verify it.

Keep a one-line record of each failure and the fix. After two or three, you will see which kinds of rules go stale in your repo (usually commands and paths), and you can audit those first next time.

If the agent follows an instruction you removed, that is a real finding, not a fluke. Something still contains it. Find the file, and the mystery ends.

Conclusion

A migration that merges with stale project memory in VS Code leaves a quiet tax on every task after it. Pay it once: search for the old stack, check each claim against package.json and the file tree, cut the file to a checklist you can verify in a minute, and prove it with the tests you already have.

My prediction: the next time your agent writes something wrong right after a migration, the cause will be a sentence you wrote, not a flaw in the model. Audit that sentence first.

If you want to try this on your own project, install the OxCode extension in VS Code (or Cursor, Antigravity and other VS Code-based IDEs), start on the Free plan at 15 runs a day, and give it the Step 4 task: a small feature, a plan first, and a test run before it calls the work done. Compare what it produces against your new checklist.