Skip to content

Migrating a Prompt Library's Linting Rules Between Model Families

10 min read · updated August 11, 2026

A prompt linter that has been running for a year is enforcing two kinds of rule that look identical in the config file: house style, which is yours and permanent, and vendor guidance, which belonged to a model you are about to stop using. Nothing in the file distinguishes them, so the migration either keeps all of them or argues about each one.

The rule set is model-specific and does not say so

Look at a typical rule list. “No trailing whitespace in the system message.” “Every prompt must have an OUTPUT section.” “Do not use the phrase ‘think step by step’.” “System message must not exceed 2,000 tokens.” “Examples must precede instructions.”

The first two are yours: they express how your team writes prompts and they are true whatever model runs them. The last three are derived from one vendor’s documented prompting guidance at one point in time, and at least one of them is likely to be wrong — or reversed — for the model you are migrating to. Some model families document a preference for instructions before examples; some document the opposite; some say nothing and the rule is folklore.

The defect is not that the rules are wrong. It is that the file does not record which model each rule is a claim about, so the only way to migrate the rule set is to reconstruct the reasoning behind every entry from memory. That is why prompt linters ossify: the cost of auditing them exceeds the cost of ignoring them, and they end up running in warn mode forever.

The storage shape

One record per rule, with the applicability and the source as first-class fields:

# prompt-lint/rules/examples-before-instructions.yaml
id: examples-before-instructions
severity: warn                  # error | warn | info
kind: vendor                    # vendor | house
check:
  type: order
  before: examples
  after: instructions
appliesTo:
  - family: provider-b/model-x
    minVersion: "2026-01"
    maxVersion: null            # null = still current
rationale: >
  Provider B's prompting guide states that few-shot examples placed
  before instructions improve adherence on this family. Not asserted
  for any other family.
source: https://example-vendor.invalid/docs/prompting#examples
sourceReadOn: "2026-07-14"
added: "2026-07-14"
owner: platform-prompts
# prompt-lint/rules/output-section-required.yaml
id: output-section-required
severity: error
kind: house                     # ours; survives every migration
check:
  type: section-present
  section: OUTPUT
appliesTo: []                   # empty = universal
rationale: >
  Every prompt in this library declares its output contract in an
  OUTPUT section so the schema and the prompt can be diffed together.
source: internal/prompt-style-guide.md
added: "2025-02-03"
owner: platform-prompts

Four fields are load-bearing. kind separates the two populations, so “re-derive every vendor rule” becomes a query rather than a meeting. appliesTo is a list of family plus version range, with an empty list meaning universal. source plus sourceReadOn make a vendor rule falsifiable: a future maintainer can open the link, see whether the guidance is still there, and act. And owner gives the rule someone to answer for it.

Prompts carry the other half of the pairing, in front matter: which model families they target. A prompt that targets two families is linted against the union of the rules that match either, which is exactly right — it must satisfy both to be portable.

---
id: triage-classifier
targets:
  - provider-a/model-y@2026-01
  - provider-b/model-x@2026-06
schema: schemas/triage.v3.json
---
SYSTEM
...

Resolution at lint time

Given a prompt and its targets, the resolver produces the applicable rule set. The rules are small enough to state completely:

  1. Start with every rule whose appliesTo is empty. These are universal and always apply.
  2. Add every rule with at least one appliesTo entry whose family matches one of the prompt’s targets and whose version range contains that target’s version.
  3. If two rules share an id after that (a family-specific override of a universal rule), the more specific match wins: a rule matching an exact version beats one matching a version range, which beats a universal rule.
  4. If two rules with different ids are in direct contradiction — declared explicitly via a conflictsWith field, since a linter cannot infer semantic contradiction — and both resolve for the same prompt, the lint run fails as a configuration error, naming both rule ids and the prompt.
  5. A prompt whose targets match no family-specific rules is linted against the universal set only, and emits an info diagnostic saying so, because that usually means somebody added a model family to the roster and never added rules for it.

Step four is the one that earns its place during a migration. A prompt targeting two families with opposed vendor guidance cannot satisfy both, and the honest outcome is a hard failure that forces a decision — fork the prompt, or drop one target — rather than a silent precedence rule that quietly picks a winner.

Retiring a rule

Without a retirement condition, the rule set only grows and the linter becomes advisory. The condition, stated once:

A vendor rule is deleted when both hold: the guidance it cites is no longer present at its source, and no prompt in the library has a target matching its appliesTo. A house rule is deleted only by a decision from its owner, recorded in the style guide.

Both halves are necessary. Guidance that has disappeared from the docs but still governs prompts you are still running is a rule you keep and re-verify; an appliesTo that matches nothing but whose guidance still stands is a rule waiting for the next prompt on that family. Only when neither is true is the rule genuinely dead.

Delete, do not disable. A rule left in the file with severity: off is a rule that gets re-enabled by someone who does not know why it was turned off. Write a line in the rule set’s changelog — rule id, date, which half of the condition fired — and let version control hold the body. Both checks are mechanisable: a nightly job that fetches each source and flags the ones that no longer contain the cited text, plus a query over the prompt front matter, gives you a monthly candidate list instead of a memory exercise.

Fork a prompt, or parameterise it

The same question arises one level up, and the rule set answers it. Parameterise when the difference between two models is a value the resolver can supply: an output token cap, a model name in a string, the presence or absence of one instruction line. Those go in the front matter or a per-target values file, and there is one prompt.

Fork when the difference is structural: a different section order, a different number of examples, a different output contract. Structural differences cannot be parameterised without turning the prompt into a template with conditionals, at which point nobody can read the prompt as it will actually be sent, which is the one property a prompt library exists to preserve.

The practical test: if you cannot render the prompt for a single target and have it be obviously readable as prose, you have templated too far and should have forked. A fork costs a duplicated file and a note in both saying which is which; that is cheaper than a template nobody can review. The registry-level bookkeeping for forks is covered in the prompt registry.

Doing the migration

  1. Label every existing rule vendor or house. If nobody can say which a rule is, it is vendor, because a house rule would have a style-guide entry. This step usually takes an hour and is the whole audit.
  2. Attach appliesTo to every vendor rule, naming the family it was written for. Every one of them gets a non-empty list; that is what makes them vendor rules.
  3. Re-derive the vendor rules for the target from the target’s current documented prompting guidance. Open the doc, write new records with today’s sourceReadOn. Do not translate the old rules — read the new guidance and write rules for it, because a translated rule inherits an assumption you cannot see.
  4. Add the target to the front matter of the prompts you are migrating, so the resolver has something to match.
  5. Run the whole library in warn mode and read the diagnostics as a list. This is the single most informative artifact of the migration: it is every place your library disagrees with the target’s documented guidance, enumerated.
  6. Triage that list. Fix the prompt, downgrade the rule with a rationale, or delete the rule under the retirement condition. Do not leave entries untriaged; an unread warning list is the failure state this whole exercise exists to avoid.
  7. Promote to error and wire it into CI for the rules that survived triage, so the next prompt written against the target cannot reintroduce the pattern.
  8. Schedule the source check as a job rather than a reminder, and let it produce the retirement candidate list on its own.

What you end up with is a rule set that states, for each entry, who it binds and why — which means the next migration is a query and a re-read rather than a reconstruction. The related discipline of keeping the surrounding documentation in step is in migrating internal prompt documentation.

The specific rules a vendor documents change with every model release and are sometimes reversed between families. Nothing above asserts what any vendor currently recommends; it asserts that the recommendation needs a home, a source link and a date, so that the next change is a diff rather than an argument.