Skip to main content

Reference Migration Standard

What changes when a rename is treated as a graph migration instead of a text rewrite?

A reference migration changes the relationship between named things. A file move, command rename, API path change, folder restructure, skill rename, or metadata relabel is not complete when the text compiles. It is complete when every live edge points to the new node, every generated edge is regenerated by its owner, and every historical note is deliberately left alone or explicitly migrated.

This standard exists because broad search-and-replace can preserve surface consistency while destroying local intent. The failure mode is simple: an agent sees many stale strings, runs a repo-wide rewrite, and edits files that were never part of the migration. The deterministic improvement is a manifest-first protocol.

Skill renames are reference migrations too. The skill directory, name metadata, trigger phrases, NOT-for clauses, registry rows, evals, docs references, settings, hooks, receipts, and historical plans are all edges around the skill node. Classify those edges before editing.

The Rule

Do not run a broad rewrite until the migration manifest exists.

The manifest is the work contract. It names:

  • Old token — the exact path, command, import, route, metadata label, or phrase being retired.
  • New token — the replacement, or none when the edge should disappear.
  • Live surfaces — files whose references affect current execution, navigation, builds, hooks, docs, or agent procedure.
  • Historical surfaces — receipts, memories, old plans, archived workcharts, or evidence where old names may remain as history.
  • Generated surfaces — files updated only by their generator.
  • Do-not-touch surfaces — files with active user edits, external ownership, or deliberate legacy examples.
  • Proof — the commands that prove live edges moved and generated edges were rebuilt.

If a file is not in the manifest, do not edit it.

Edge Types

Classify every hit before changing it.

  • Live edge — affects a current caller, import, link, hook, package alias, source comment, skill instruction, or operator command. Update it.
  • Generated edge — appears in generated artifacts. Regenerate through the owning command; do not hand-edit.
  • Historical note — records what happened before. Leave it unless the migration explicitly includes historical normalization.
  • Receipt or evidence — proves a past run. Preserve original wording unless it blocks a live gate.
  • Do-not-touch — active user work, host-owned config, archived external content, or intentionally broken example. Exclude it.

This classification turns a text list into a relationship map. The important question is not "where does the string appear?" The important question is "what does this edge do?"

Pattern: Generated Conflicts

A conflict in a generated file is not a prose disagreement. It means two source states produced different projections. Resolve the source states, then ask the owner to generate one projection of their combined intent.

Use this sequence:

  1. Name the owner. Identify the generator, its authoritative inputs, and its check command.
  2. Inspect both intentions. Review the overlapping branches and source changes. Preserve independent changes that still belong in the combined state.
  3. Merge the sources. Resolve conflicts in authored inputs before touching their generated projection.
  4. Regenerate. Run the owning generator against the combined source state.
  5. Prove the projection. Run check mode, inspect the generated diff, and confirm no conflict markers or unexplained hand edits remain.

Do not choose ours or theirs for a generated file. Do not manually combine its lines or merely remove its conflict markers. Those shortcuts can discard a valid source change while leaving a plausible-looking projection. Hand-edit the projection only after proving that its generator is defective; repair the generator in the same bounded change.

Use this decision card before resolving the conflict:

Generated conflict: <path>

Owner:
- Generator: <command>
- Check: <command>

Authoritative inputs:
- <path> — <branch intention>
- <path> — <branch intention>

Resolution:
- Sources retained:
- Projection regenerated:
- Conflict residue checked:

Proof:
- <command and observed result>

Procedure

  1. Inventory nodes. List every old token and the intended new token. Include casing, shell form, package alias, route form, and import form when they differ.
  2. Find edges. Use rg -n first. Search likely live surfaces before historical surfaces.
  3. Classify hits. Mark each hit as live, generated, historical, receipt, or do-not-touch.
  4. Review the write set. Convert only reviewed live hits into an edit list. If the edit list includes unrelated files, stop.
  5. Rewrite narrowly. Use the reviewed file list, not find .. Prefer structured edits for code and config.
  6. Regenerate artifacts. Run the owning generator for generated edges.
  7. Prove closure. Run the manifest proof commands and keep the output in the PR or close note.

Forbidden default:

find . -type f -exec perl -0pi -e 's|old|new|g' {} +

Allowed shape:

rg -l "old-token" package.json scripts .husky .claude/hooks .codex/hooks docs src \
| sort

Then inspect the file list, remove historical and do-not-touch surfaces, and edit only the approved list.

Manifest Template

Reference migration: <name>

Old token(s):
- <old>

New token(s):
- <new>

Live surfaces:
- <path or glob> — <why live>

Generated surfaces:
- <path or glob> — generated by <command>

Historical surfaces:
- <path or glob> — leave|migrate, because <reason>

Do-not-touch:
- <path or glob> — <owner or active-edit reason>

Rewrite method:
- <manual patch | reviewed rg -l list | structured parser>

Proof:
- <command>
- <command>

Proof

A migration is closed only when proof distinguishes live references from historical residue.

Minimum proof:

rg -n "<old-token>" <live-surfaces>
rg -n "<old-token>" <historical-surfaces>
git diff --check

The first command should return no unintended live hits. The second command may return historical hits only if the manifest says they are allowed.

For docs links, run:

node scripts/quality/validate-links.js --format=json

For generated docs navigation or artifacts, run the owning --check command or generator. Generated files are never the first edit surface.

When a generated file was conflicted, also prove that the regenerated file has no unresolved residue:

rg -n '^(<<<<<<<|=======|>>>>>>>)' <generated-path>
git diff --check

This protocol makes conflict resolution deterministic when ownership is clear. It does not prevent branches from overlapping, and it cannot replace human judgment about which authored intentions should survive.

Changes my mind: three comparable cases show that a simpler typed or conflict-marker check preserves the same source intent, or this protocol creates false stops without catching a unique failure.

Context

Questions

Next question: which generated conflict will provide the first comparable test of this rule?

  • Which edge type is easiest to confuse with a live edge?
  • What would break if this old token remained in a hook, package alias, or skill instruction?
  • Which generated files mention the token, and what command owns them?
  • Which hits are historical records that should stay true to the past?
  • Which files have active user edits and must be excluded from mechanical rewrite?
  • What proof would catch a file that escaped the manifest?
  • Which source intentions would be lost if you accepted one generated side unchanged?