Skip to main content

Overview

Safe, verified patterns for renaming identifiers across hundreds of files in a monorepo. Tested on STRATT (39 files, ~2,000 identifiers, 0 regressions). Core principle: Dry-run verification → count validation → execution → post-execution verification. Tools: rg (ripgrep), sed, git (for safety).

Pattern 1: Simple Identifier Rename (95% of cases)

Scenario: Rename a type name, function name, or config key across multiple files. Example: SkillRefDoctrineRef

Step 1: Dry-Run Count

# Count occurrences before
rg "SkillRef" packages/ --count-matches
# Output: 47 matches across X files
Record this number. You’ll verify post-execution.

Step 2: Dry-Run Preview

# Preview replacements (no modification)
rg "SkillRef" packages/ -l | head -5  # Show first 5 files
rg "SkillRef" packages/ | head -20     # Show first 20 matches
Verify: Looks like the right pattern? Proceed.

Step 3: Execute with sed (File-by-File)

Option A: Using ripgrep + xargs + sed
rg "SkillRef" packages/ --files-with-matches -l | xargs sed -i 's/SkillRef/DoctrineRef/g'
Breakdown:
  • rg "SkillRef" packages/ -l — list files containing “SkillRef”
  • xargs sed -i 's/SkillRef/DoctrineRef/g' — apply sed to each file
  • -i — in-place edit (modifies files)
  • s/old/new/g — sed substitute (global in file)
Option B: Using find + sed (if rg not available)
find packages/ -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.yaml" \) -print0 | \
  xargs -0 sed -i 's/SkillRef/DoctrineRef/g'
Option C: Using rg —replace (modern ripgrep)
rg "SkillRef" packages/ --replace "DoctrineRef" --type ts --type tsx
# Preview only; doesn't modify files
Recommendation: Use Option A (ripgrep + sed) for safety and speed.

Step 4: Verify Count Matches

# Count occurrences after
rg "SkillRef" packages/ --count-matches
# Should output: 0 (all replaced)

# Sanity check: Verify new name exists
rg "DoctrineRef" packages/ --count-matches
# Should output: 47 (matching pre-execution count)
If counts don’t match: Revert with git checkout, debug, try again.

Step 5: Type Check + Test

# Ensure no syntax errors introduced
bun run typecheck

# Run tests for affected layers
bun packages/schema && npm run test
If failures: Revert, fix, re-execute.

Step 6: Commit

git add packages/
git commit -m "Rename: SkillRef → DoctrineRef"

Pattern 2: Regex-Based Replacement (Complex Identifiers)

Scenario: Rename with context or multiple variations. Example: Rename export interface Skill {export interface Doctrine { (only for specific pattern, not all “Skill” occurrences)

Step 1: Build Regex Pattern

# Match: export interface Skill { (exact)
# Replace: export interface Doctrine {

rg "export interface Skill \{" packages/ -l  # Preview files
rg "export interface Skill \{" packages/     # Preview matches
Verify: Only matches the intended pattern.

Step 2: Execute with sed Regex

rg "export interface Skill \{" packages/ --files-with-matches -l | \
  xargs sed -i 's/export interface Skill {/export interface Doctrine {/g'
Regex breakdown:
  • . = any char, \{ = literal { (escaped for safety)
  • s/pattern/replacement/g = substitute all occurrences in line

Step 3: Verify

rg "export interface Skill \{" packages/  # Should be 0
rg "export interface Doctrine \{" packages/  # Should show all instances

Pattern 3: Multi-Step Replacement (Chained Identifiers)

Scenario: One name change leads to multiple derived changes. Example: Rename SkillRefDoctrineRef AND skill_refdoctrine_ref (snake_case variant)

Strategy: Replace in Dependency Order

# Step 1: Replace SkillRef first
rg "SkillRef" packages/ --files-with-matches -l | xargs sed -i 's/SkillRef/DoctrineRef/g'

# Step 2: Replace skill_ref (depends on Step 1)
rg "skill_ref" packages/ --files-with-matches -l | xargs sed -i 's/skill_ref/doctrine_ref/g'

# Step 3: Verify both
rg "SkillRef" packages/  # 0
rg "DoctrineRef" packages/  # X count
rg "skill_ref" packages/  # 0
rg "doctrine_ref" packages/  # X count
Why order matters:
  • If you replace skill_ref first, you might accidentally hit SkillRefDoctrineRef partials
  • Always replace most-specific (SkillRef) before less-specific (skill_ref)

Pattern 4: File Extension Rename (Hard-Cut)

Scenario: Rename file extensions, e.g., .skill.md.doctrine.md

Step 1: Find All

find . -name "*.skill.md" | wc -l  # Count
find . -name "*.skill.md" -type f  # List

Step 2: Rename All

# Using find + rename utility (if available)
find . -name "*.skill.md" -type f -exec rename 's/\.skill\.md$/.doctrine.md/' {} +

# Using find + mv loop (portable)
find . -name "*.skill.md" -type f | while read file; do
  mv "$file" "${file%.skill.md}.doctrine.md"
done

# Verify
find . -name "*.skill.md" | wc -l  # Should be 0
find . -name "*.doctrine.md" | wc -l  # Should match Step 1 count

Step 3: Update File References in Code

# Find references to .skill.md in code
rg "\.skill\.md" packages/ src/

# Replace glob patterns, import paths, etc.
rg "\.skill\.md" packages/ --files-with-matches -l | \
  xargs sed -i 's/\.skill\.md/.doctrine.md/g'

# Verify
rg "\.skill\.md" packages/  # 0

Step 4: Commit (File + Code Changes in Same Commit)

git add .
git commit -m "Hard-cut: .skill.md → .doctrine.md (39 files + parser update)"

Pattern 5: Exclude-Based Replacement (Comments, Strings, Docs)

Scenario: Replace in code only, NOT in comments or docs. Example: Rename SkillDoctrine in TypeScript, but leave // Skill-based... comments alone

Challenge

sed doesn’t easily distinguish code from comments. Use a smarter approach:
# Option 1: Use rg with type filters
rg "Skill" packages/ --type ts --type tsx  # Only .ts/.tsx files, not .md

# Option 2: Use rg with negative lookahead (if needed)
# Exclude lines starting with // or /*
rg "Skill" packages/ --type ts -v "^.*//.*Skill"  # Exclude comments

Safer Approach: Manual Review + Commit Per File

# For each file that needs changes:
git diff packages/schema/src/types.ts  # Review
# Edit manually or use targeted sed
sed -i 's/type Skill =/type Doctrine =/g' packages/schema/src/types.ts
git add packages/schema/src/types.ts
Trade-off: Slower (manual per file) but safer (you see every change).

Pattern 6: Config Key Rename (YAML/JSON)

Scenario: Rename a config key, but support both old + new during transition. Example: Rename skills:doctrines: in council.yaml, but keep old key working

Step 1: Add Fallback in Code

// packages/cli/src/lib/council.ts
const doctrines = agentConfig.doctrines ?? agentConfig.skills;
No need to rename the config file itself. Fallback handles both.

Step 2: Gradually Update Configs (Optional)

# When ready, rename key in user configs
rg "skills:" councils/  --files-with-matches -l | \
  xargs sed -i 's/^[[:space:]]*skills:/  doctrines:/g'

Step 3: Remove Fallback Later

Once all configs migrated:
// Old fallback removed; use doctrines only
const doctrines = agentConfig.doctrines;

Safety Checklist

Before executing any bulk replacement:
  • Record pre-execution count with rg --count-matches
  • Preview with rg "pattern" | head -20
  • Use dry-run (show files with -l before applying sed)
  • Test on 1–2 files manually before xargs
  • Never use sed -i without a backup (use git)
  • After execution, verify count with rg "new_name" --count-matches
  • Run tests + type check
  • Review git diff for unintended changes
  • Commit with clear message showing what changed

Common Pitfalls

Using sed -i without git
  • One typo = data loss
  • Always work in git repo; use git checkout to revert
Forgetting to escape regex metacharacters
  • . matches any char, * means repeat
  • Use \. for literal dot, \{ for literal brace
  • Test on small files first
Replacing globally without scoping
  • sed 's/Skill/Doctrine/g' hits all files
  • Use rg --files-with-matches -l | xargs sed to scope correctly
Not verifying count post-execution
  • “I think it worked” ≠ verified
  • Always run the count check
Mixing file extension + identifier renames in one commit
  • If things break, hard to debug
  • Separate: file extension first (commit 1), then identifiers (commit 2)

Command Reference Card

# Count occurrences before
rg "PATTERN" DIR --count-matches

# List files containing pattern
rg "PATTERN" DIR -l

# Preview matches (first 20)
rg "PATTERN" DIR | head -20

# Execute replacement (safe with xargs)
rg "PATTERN" DIR -l | xargs sed -i 's/PATTERN/REPLACEMENT/g'

# Verify after
rg "PATTERN" DIR --count-matches  # Should be 0
rg "REPLACEMENT" DIR --count-matches  # Should match count from "before"

# Review changes before commit
git diff

# Type check
bun run typecheck

# Test
bun packages/[LAYER] && npm run test

# Commit
git add .
git commit -m "Description"

Scaling Examples

Example 1: Small Rename (50 occurrences, 5 files)

rg "OldName" --count-matches
rg "OldName" -l | xargs sed -i 's/OldName/NewName/g'
rg "OldName" --count-matches  # 0
rg "NewName" --count-matches  # ~50
bun run typecheck
git commit -m "Rename: OldName → NewName"
Time: 5 min

Example 2: Medium Rename (500 occurrences, 20 files, multi-variant)

# Step 1: PascalCase variant
rg "SkillRef" --count-matches
rg "SkillRef" -l | xargs sed -i 's/SkillRef/DoctrineRef/g'

# Step 2: snake_case variant
rg "skill_ref" --count-matches
rg "skill_ref" -l | xargs sed -i 's/skill_ref/doctrine_ref/g'

# Step 3: Verify both
rg "SkillRef" --count-matches  # 0
rg "DoctrineRef" --count-matches  # ~250
rg "skill_ref" --count-matches  # 0
rg "doctrine_ref" --count-matches  # ~250

bun run typecheck
git commit -m "Rename: Skill* → Doctrine* (PascalCase + snake_case)"
Time: 15 min

Example 3: Large Rename with File Extension (2,000 occurrences, 39 files)

# Step 1: Files
find . -name "*.skill.md" -type f | while read f; do mv "$f" "${f%.skill.md}.doctrine.md"; done
find . -name "*.skill.md" | wc -l  # 0
find . -name "*.doctrine.md" | wc -l  # 39

# Step 2: Code identifiers (3 variants)
rg "SkillRef" --count-matches
rg "SkillRef" -l | xargs sed -i 's/SkillRef/DoctrineRef/g'

rg "SkillBinding" --count-matches
rg "SkillBinding" -l | xargs sed -i 's/SkillBinding/DoctrineBinding/g'

rg "\.skill\.md" --count-matches
rg "\.skill\.md" -l | xargs sed -i 's/\.skill\.md/.doctrine.md/g'

# Step 3: Verify all three
rg "SkillRef|SkillBinding|\.skill\.md" --count-matches  # 0

bun run typecheck && bun packages/schema && npm run test
git commit -m "Rename: Skill* → Doctrine* (files + code, hard-cut extension)"
Time: 45 min
  • Operational pattern: devarno-cloud/atlas/campaigns/2026-04-03-mechanical-refactor-workflow.md
  • STRATT implementation: stratt-hq (commits 1–7, bulk replacements executed in each)
  • Ripgrep docs: https://github.com/BurntSushi/ripgrep