Every AI coding assistant we pointed at this codebase made the same mistake within its first five minutes: it edited a generated file that gets overwritten on the next build. Confidently. With a clean diff and a nice explanation. And the moment someone rebuilt the front-end bundle, the change vanished.

The codebase is an older web application running on Windows/IIS that has been in production for years. Its front-end JavaScript and CSS ship as date-stamped compiled files. Picture a name like ManageMembers260224.js, where 260224 is the build date. That file is build output. The real source lives one directory deeper, under a src/ folder, with the date stripped off: .../scripts/src/ManageMembers.js. You edit the source, the build regenerates the dated file, and the dated file is what the browser actually loads.

A human learns this once, because the first time your change disappears you feel it. An AI assistant gets no such feedback. It greps for the symbol you mentioned, finds it in the generated CSS file (of course it’s in there, that’s the compiled output), edits it, and reports done. The grep was technically correct and the result was technically wrong.

Why grep lands on the wrong file

It is sharper than “there are two copies.” If the dated file and the source file sat in the same folder, any assistant would notice the naming pattern and ask. They don’t. The references to the compiled path live in server-side includes buried in the page-layer files, not in the HTML you’d think to look at. So when an assistant traces “where does this script get loaded,” it lands on a server-side include pointing at the dated build artifact, follows that path, opens the generated file, and starts typing.

Nothing about that chain looks suspicious in isolation. The include really does reference ManageMembers260224.js. That file really does contain the function. The only thing wrong is the layer: you’re editing the bottom of the stack instead of the top. There is no compiler error, no failing test, no red squiggle. The change works in the moment and rots on the next build.

Correcting it in chat, again and again

Our first move was to fix it in conversation. “Don’t edit the dated file, edit the src version.” “Strip the date, look under src/.” It worked for exactly one session each time. Chat context does not survive a new conversation, so every fresh agent started with none of our corrections and made the same mistake again.

The fix is config

If we keep saying the same sentence to the AI, that sentence wants to be a rule. Cursor reads .mdc rule files out of .cursor/rules/, and a rule with a globs field fires automatically on every edit that matches the pattern. It is part of the project, checked into the repo, applied without anyone remembering to mention it.

So we wrote .cursor/rules/edit-js-css-src-only.mdc. The front-matter scopes it:

globs: "**/*.js", "**/*.css"
alwaysApply: false

The body is the part that matters: an explicit “strip the date, insert src/” resolution table, with worked examples so there’s no ambiguity about the transform. The canonical one:

ManageMembers260224.css -> [output-dir]/src/ManageMembers.css

With that rule enabled for matching files, the source-versus-output convention is available before the edit happens. It no longer depends on us repeating the correction from a prior chat. The convention travels with the code.

That single rule killed the most expensive recurring correction in the project. Once we had the mechanism, the next move was to encode the rest of the tribal knowledge that bites every fresh agent the same way. In the same commit we shipped four more:

  • page-object-pattern.mdc: the Page.prototype OO convention this admin uses for its client-side pages, so a new assistant builds in the house style instead of inventing its own structure.
  • bottom-up-function-ordering.mdc: the ordering convention for function definitions in these files.
  • chrome-devtools-mcp.mdc: how to drive the browser for this project when verifying a change.
  • local-dev-domain.mdc: the local development domain, so the assistant tests against the right host instead of guessing.

None of those is profound. Each is one paragraph of “here’s how we do it here” that we would otherwise type into a fresh chat every session.

A prompt is a sentence, a rule is a behavior

A prompt is something you say. A rule is something the tool does. Put a convention in your prompt and you are hoping you remember to include it, hoping it survives the context window, hoping it carries to the next session. Put it in a rule with a matching glob and it fires on the exact files it applies to, every time, whether or not anyone is thinking about it.

Re-explaining a gotcha in chat is cheap per instance and expensive in aggregate: a few sentences repeated across sessions, plus the cost of the times the correction is missed. Writing the rule takes upkeep too, but it turns a repeated conversation into a project artifact that can be reviewed and improved.

Conventions that bite humans bite AIs harder. A new developer edits the generated file once, watches their work disappear, and internalizes the rule in about four seconds of frustration. The AI feels nothing, learns nothing across sessions, and will do it again tomorrow with total confidence. The durable place for that knowledge is somewhere the tool reads automatically, next to the code, in a format that fires on the matching files.

If your assistant keeps repeating the same mistake, such as editing generated build output instead of the source, stop correcting it in chat. Write the convention into a project-level rule that applies to the matching files, and check it into the repository so every new session gets it. Start with the corrections your team keeps repeating.