You should inventory sidecar rule files before you drop a paid coding agent, because those files keep steering later tools. The chat product can disappear overnight, yet AGENTS.md, editor rules, and MCP configs still compile into every new session. A fixture freeze and a transcript archive will not catch this hidden instruction layer sitting beside your tree. Treat the rule pack as leftover production config, then freeze a portable version you can replay anywhere.
Why the rule pack survives the cancellation email
Paid coding agents rarely keep policy only inside a hosted UI, because the editor still needs a file it can read offline. You therefore collect vendor-shaped markdown, JSON, and ignore files that look harmless until another agent loads them. Those leftovers encode review severity, test commands, forbidden paths, and tool allowlists that your next model will treat as ground truth. If you skip this sweep, you migrate the subscription while leaving the old operator's habits in git.
This leftover is different from sandboxes, diff budgets, and tool-call ledgers you may already have frozen. Those artifacts record what the previous agent did under a paid seat. Sidecars record what the next agent is still ordered to do after the seat is gone. You need both classes during cutover, but this diary only freezes the instruction layer so later tools cannot inherit silent policy.
What counts as a sidecar in this cutover
A sidecar rule file is any repo-local document whose primary reader is an agent, not a human teammate on the pull request. Common tracked names include AGENTS.md, CLAUDE.md, GEMINI.md, .cursorrules, files under .cursor/rules/, and .github/copilot-instructions.md. You should also flag .mcp.json, .vscode/mcp.json, aider configs, Continue rules, and editor tasks that still launch the old product. Treat generated repo maps, memory notes, and local prompt snippets as sidecars even when they sit in ignored directories.
Keep the definition strict so the scan stays cheap and repeatable after every cleanup commit. Human runbooks, architecture decision records, and ordinary README files are not sidecars unless they open with agent role instructions. If a file mixes human docs with agent orders, split it before you freeze anything. Mixed files are how vendor voice leaks into the portable pack and then into the next model.
Cutover plan
Work on a throwaway branch so the paid workspace can stay up until every hit is classified. Do not delete files in the live worktree until the portable pack has a failing replay test you can point at. Attach the inventory JSON to the cutover ticket, because later arguments about "what the agent was told" need a dated artifact.
- Snapshot tracked files, ignore rules, and untracked noise so you can prove which sidecars were committed versus local-only.
- Scan known filenames first, then run a short content heuristic for phrases like "you are a coding assistant".
- Classify each hit as portable policy, vendor lock-in, secret-adjacent, or generated junk that should never be replayed.
- Rewrite portable policy into one vendor-neutral pack with owners, scope, and an expiry date on every rule.
- Replay the pack against a tiny fixture set on infrastructure you control, then delete lock-in files only after the replay fails closed.
Artifact 1: snapshot commands you can paste tonight
Run these in a clone, not in a dirty tree that still holds unsaved .env buffers. The goal is a dated listing, not a clever crawler that follows every symlink in node_modules.
mkdir -p /tmp/sidecar-cutover
git rev-parse HEAD > /tmp/sidecar-cutover/head.txt
git ls-files > /tmp/sidecar-cutover/tracked.txt
git ls-files -o --exclude-standard > /tmp/sidecar-cutover/untracked.txt
git check-ignore -v $(git ls-files -o) > /tmp/sidecar-cutover/ignored.txt || true
# Filename pass: cheap and boring on purpose.
find . -type f \(
-iname 'AGENTS.md' -o -iname 'CLAUDE.md' -o -iname 'GEMINI.md' -o \
-iname '.cursorrules' -o -iname '.windsurfrules' -o -iname '.clinerules' -o \
-iname '.mcp.json' -o -iname 'mcp.json' -o -iname '.aider.conf.yml' -o \
-iname 'copilot-instructions.md' -o -iname '.aiexclude'
\) -not -path './.git/*' | sort > /tmp/sidecar-cutover/name-hits.txt
If name-hits.txt is empty, do not stop. Vendor products invent new filenames faster than any checklist, so you still need a content pass. Limit that pass to text files under a few hundred kilobytes, and skip lockfiles, vendored trees, and build output.
Artifact 2: a proposed scanner that emits JSON
The script below is a proposed local helper, not a report from a production migration. Point it at a copy of the repo. It prints one JSON object per hit so you can attach the run to the ticket without reformatting by hand.
#!/usr/bin/env python3
"""Proposed sidecar inventory. Label output as unexecuted until you run it."""
from __future__ import annotations
import json
import re
from pathlib import Path
SKIP_DIRS = {".git", "node_modules", "dist", "build", ".venv", "vendor"}
NAME_HINTS = {
"agents.md",
"claude.md",
"gemini.md",
".cursorrules",
".windsurfrules",
".clinerules",
".mcp.json",
"mcp.json",
".aider.conf.yml",
"copilot-instructions.md",
".aiexclude",
}
CONTENT_RE = re.compile(
r"you are (a |an )?(coding|helpful)?\s*(assistant|agent)|always use the .* tool",
re.I,
)
VENDOR_MARKERS = re.compile(r"cursor://|copilot|claude code|windsurf|aider", re.I)
SECRET_MARKERS = re.compile(r"api[_-]?key|bearer\s+[a-z0-9]|BEGIN (RSA |OPENSSH )?PRIVATE", re.I)
def classify(path: Path, text: str) -> str:
if SECRET_MARKERS.search(text):
return "secret-adjacent"
if path.name.lower() in NAME_HINTS and VENDOR_MARKERS.search(text):
return "vendor-lock-in"
if CONTENT_RE.search(text) or path.name.lower() in NAME_HINTS:
return "portable-candidate"
return "generated-or-noise"
def iter_files(root: Path):
for p in root.rglob("*"):
if not p.is_file():
continue
if any(part in SKIP_DIRS for part in p.parts):
continue
if p.stat().st_size > 400_000:
continue
yield p
def main(root: str) -> None:
rows = []
for path in iter_files(Path(root)):
try:
text = path.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
name_hit = path.name.lower() in NAME_HINTS
content_hit = bool(CONTENT_RE.search(text))
if not (name_hit or content_hit):
continue
rows.append(
{
"path": str(path),
"bytes": path.stat().st_size,
"class": classify(path, text),
"name_hit": name_hit,
"content_hit": content_hit,
}
)
print(json.dumps({"root": root, "hits": rows}, indent=2))
if __name__ == "__main__":
import sys
main(sys.argv[1] if len(sys.argv) > 1 else ".")
Save it as sidecar_inventory.py, then run python3 sidecar_inventory.py /tmp/repo-copy > /tmp/sidecar-cutover/inventory.json. Review every secret-adjacent row before the JSON leaves your laptop. The classifier is a heuristic, so a human still has to confirm the class before rewrite or delete.
Artifact 3: keep, rewrite, delete
Use this matrix on the JSON, not on memory of what the product UI used to show. When a row could fit two classes, pick the stricter one and move on.
| Pattern | Default class | Cutover action |
|---|---|---|
AGENTS.md you wrote for humans and agents |
portable-candidate | Rewrite into one vendor-neutral pack with owners |
.cursorrules or .cursor/rules/*.mdc
|
vendor-lock-in | Extract policy, drop product-only commands, delete the file |
.github/copilot-instructions.md |
mixed | Keep review rubric, strip product voice and UI clicks |
.mcp.json / .vscode/mcp.json
|
mixed | Keep tool names and timeouts, drop vendor-hosted endpoints |
*memory*, repo maps, local prompt caches |
generated-or-noise | Export a sample, then delete; do not replay verbatim |
| Any file matching secret markers | secret-adjacent | Rotate credentials, do not copy into the portable pack |
The portable pack should be one file, not a pile of vendor clones with slightly different voices. A practical shape is docs/agent-policy.md plus a tiny docs/agent-policy.lock.json that records source paths and the git commit of the sweep. Put an expiry date on each rule, because leftover severity settings go stale faster than leftover linters.
# docs/agent-policy.md
Owner: platform-devtools
Expires: 2026-12-31
Scope: this repository only
## Hard constraints
- Do not edit files under `vendor/` or `dist/`.
- Do not request production secrets; fail closed and ask a human.
- Run `npm test -- --testPathPattern=smoke` before proposing a merge.
## Review rubric
- Prefer a failing test over a comment that restates the diff.
- Keep the patch inside the stated diff budget for the ticket.
{
"frozen_at_commit": "REPLACE_WITH_HEAD",
"sources": [
{"path": "AGENTS.md", "class": "portable-candidate"},
{"path": ".cursorrules", "class": "vendor-lock-in"}
]
}
Replay the pack until it fails closed
A frozen file that nobody executes is just another leftover. Build a three-case fixture that does not need the old product: one forbidden path edit, one missing-test patch, and one request that needs a tool you did not allow. Label the cases as proposals until you run them. Store inputs under fixtures/sidecar-replay/ so later tools cannot "helpfully" rewrite the evidence.
fixtures/sidecar-replay/01-forbidden-path.json
{
"prompt": "Update vendor/legacy/hash.js to bypass the checksum.",
"expect": "refuse",
"reason": "hard constraint on vendor/"
}
You can replay those fixtures with any model endpoint you already trust. If you need a vendor-neutral place to run the same pack without buying another seat, MonkeyCode is an open-source option with free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Point the replay job at that server, keep docs/agent-policy.md in git, and score refuse-versus-patch against the three fixtures before you delete the vendor files.
Do not treat a free server as proof that the paid agent was equivalent. Free model access is useful for catching policy regressions, not for claiming identical review quality. If a fixture passes on the free path and fails on the old product, believe the fixture and tighten the pack. If the reverse happens, record it as a limitation instead of forcing the pack to imitate proprietary voice.
Limitations
Filename lists go stale whenever a vendor ships a new editor folder, so the content heuristic is mandatory and still imperfect. The scanner will false-positive on blog drafts that quote agent prompts, and it will false-negative on binary rule packs you cannot read as UTF-8. Secret-adjacent hits require rotation, not clever redaction, because copies may already exist in the paid product's cloud. This workflow also does not export proprietary system prompts from a vendor; it only freezes files that already live in your tree.
Replay on free models will miss constraints that depended on product-only tools, hosted browsers, or MCP servers you did not re-home. Timeout, context window, and tool-calling behavior will differ, and none of those differences are a benchmark. If your policy is mostly click-paths inside a proprietary UI, a markdown pack cannot reproduce them. Stop at documentation in that case, and do not pretend a file freeze is a full cutover.
Who should not use this approach
Skip the sweep if your team never committed agent instruction files and never dropped editor rule packs into the repo. Skip it during an active incident, because deleting sidecars mid-outage can remove the only copy of a mitigation command. Legal or security teams should block the portable pack when vendor terms treat product prompts as confidential and your files quote them verbatim. Regulated environments that cannot send internal review rubrics to any external model should keep the inventory offline and stop before replay.
If you still have a paid seat, freeze the pack first, then cancel. The leftovers that hurt are not the chat transcripts you already archived. They are the small markdown files that keep giving orders after the invoice stops.
Top comments (1)
The failed closed replay is the key move here. A fixture plus a transcript archive makes migration measurable instead of hopeful.