Cross file source of truth grep
Skill Ed3Design/ed3design-skill-bundles/code-quality/skills/cross-file-source-of-truth-grep
Use when about to refactor a value, constant, helper-function, lookup, or config-pattern from old-form to new-form across the codebase — schema-drift fixes, display-name SoT migration, config-pattern updates (hardcoded ID → env-var), helper rename, deprecated-import cleanup. STOP and run `grep -r "<old-pattern>"` on the WHOLE repo BEFORE writing the new pattern anywhere. Trigger when phrases like "I'm refactoring X to Y", "fix schema drift", "migrate from old to new form", "rename helper", "single-source-of-truth" appear, OR a mental model of "only 3 files use this" exists — dispatcher, scheduler, jobs, notifications, tests often hide additional places. Method: `grep -rn "<old-pattern>" --include="*.py"` excluding cache dirs, then categorize each hit. Do NOT load for greenfield code, pure-cosmetic renames in same file, or refactors guaranteed file-local.From its SKILL.md
npx -y skills add Ed3Design/ed3design-skill-bundles --skill cross-file-source-of-truth-grepAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.6 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Cross-File Source-of-Truth Grep
✅ PROMOTED — TDD Cycle 1 PASS. RED subagent gave a heuristic "grep first" recommendation, but without the decisive 4th grep pass on mapping values. GREEN subagent added the 4th pass
grep -rn "'CL=F'\s*:\s*'WTI"— which catches copies of the mapping structure under deviating variable names (_DISPLAY_MAP,LABELS, dicts embedded inside helper functions). Precisely this pass would have caught thesignal_dispatcher.py:_get_display_namebug before the refactor. R1 refactor applied: 4th grep pass in Quick Reference as a separate line + hint block.
Overview
Before every refactor: grep -r for the OLD pattern, not the NEW one.
When you replace an old pattern (column name, helper call, inline dict, default string, deprecated import) with a new one, 2-5 additional callers often lurk that you didn't have in the mental model of "the 3 relevant files":
notifications/dispatcher (Telegram, email, webhook)- Scheduler jobs (cron-triggered, often rarely touched)
- Batch scripts (offline analytics)
- Integration tests + mock fixtures
- Deprecated-but-still-in-path modules
Skill = simple discipline: grep -rn "<old-pattern>" BEFORE you write the first new call. 30 seconds of effort prevents hours of re-review cycle.
This maxim is the refactor-variant of the "Single Source of Truth" maxim: the SoT migration is only complete when grep for the old pattern returns nothing.
When to use
Trigger phrases (the kind you'd be about to use):
- "I'm refactoring <X> to <Y>"
- "fix schema drift in <table>.<column>"
- "introduce SoT for <Display-Name / config-var / helper>"
- "migrate from old
config_loaderpath to new" - "rename helper-function from foo to bar"
- "deprecated import cleanup"
- "extract config defaults from code"
High-risk markers (additional trigger):
- You have a small list of affected files in mind (≤5) — precisely then grep, because that's the case most prone to under-estimation
- The new pattern already lives somewhere ("we standardized this in
core/") — meaning older spots are not migrated yet - Module belongs to notification / dispatcher / scheduler / cron / batch — these paths are rarely touched, drift accumulates
When NOT to use
- Greenfield code: the old pattern doesn't exist yet
- Pure cosmetic rename in a single file: local variable, no semantic shift
- Guaranteed file-local helper:
_private_helperin module, with_prefix convention - Rename of a symbol with IDE refactor-tool: when LSP refactor cleanly covers all callers, grep is redundant (but verify 1× afterwards anyway)
The 4-Step Cross-File SoT Grep Flow
Step 1 — Note the old pattern explicitly
Before grep, write as a comment or via TodoWrite:
- Old pattern:
o.yf_symbol(column alias) - New pattern:
o.symbol AS yf_symbol - Suspected caller count: 3 (
signals.py,timeline.py,take_signal) - What I expect to find: 3-5 (with ~2 extra in tests/mocks)
Step 2 — Grep with repo scope
# Standard: all Python files incl. tests, scripts, integrations
grep -rn "<old-pattern>" --include="*.py" \
--exclude-dir=node_modules \
--exclude-dir=.git \
--exclude-dir=__pycache__ \
--exclude-dir=.venv \
| grep -v "\.pyc:"
Never just in a subdirectory (grep ... /core/). That misses the skill's goal.
Step 3 — Categorize the hit list
Sort each hit into one of these categories:
| Category | Example | Action |
|---|---|---|
| Production hot-path | core/services/X.py | migrate (mandatory) |
| Production cold-path | notifications/dispatcher.py, scheduler/jobs/Y.py | migrate + pre-push hook live-smoke if possible |
| Test/Mock | tests/integration/test_Y.py with fixed data | migrate + fixture updates |
| Doc / Comment / Note | # old: yf_symbol | leave as-is (history) OR replace if fully-replace required |
| Deprecated-but-in-path | legacy/foo.py with import from production | explicitly decide: migrate OR add deprecation notice + issue |
| False positive | substring match in variable name | ignore |
Step 4 — Migrate + verification grep
After the refactor: grep again grep -rn "<old-pattern>" — result should be empty (or only the doc/comment false-positives remain).
grep -rn "<old-pattern>" --include="*.py" | wc -l
# Expectation: 0 (or list of deliberately not-migrated hits)
Quick Reference
| Refactor type | Grep pattern (example) |
|---|---|
| Column rename | `grep -rn ".yf_symbol\ |
| Helper rename | `grep -rn "from .* import old_helper\ |
| Inline-dict → SoT (variable name) | grep -rn "display_names\\s*=\\s*{" |
| Inline-dict → SoT (value substring, catches obscured names) | grep -rn "'CL=F'\\s*:\\s*'WTI" — catches _DISPLAY_MAP, LABELS, SYMBOL_NAMES etc. which copy the same mapping under another name |
| Deprecated import | grep -rn "from config_loader import" |
| Hardcoded env default | `grep -rn '"c0619ab1e363"\ |
| Magic string → enum | `grep -rn "'long'\ |
R1 refactor (Cycle 1): the substring-grep on mapping values (
'CL=F': 'WTI) is the most critical variant — it finds copies of the mapping structure under deviating variable names. Precisely this variant would have caught thesignal_dispatcher.py:_get_display_namebug BEFORE the refactor (the dict there was not nameddisplay_namesbut was embedded in a helper function).
Anti-Patterns
| Anti-Pattern | Lesson |
|---|---|
| "I know the 3 files that use this" | Mental models overlook notification/scheduler/batch — grep is 30s, less than the re-review cycle |
Only grep in core/ | Notification dispatcher often lies in notifications/, scheduler jobs in scheduler/jobs/ — grep repo-wide |
Grep without --include="*.py" | Hits in .pyc, .log, node_modules noise the output |
| Don't grep again after refactor | Verification grep is the final check — empty output = migration complete |
| Trust LSP refactor blindly | LSP usually finds everything, but dynamic imports (importlib) and string keys (getattr(o, "yf_symbol")) escape — grep finds both |
Cost of Skipping (real)
Experience from a Phase-1 re-review (code-review cleanup):
- Display-name SoT migration to
core/utils/display.instrument_label()had covered 4 hot-path files - Re-review subagent found
notifications/signal_dispatcher.py:_get_display_namewith oldconfig_loaderpath - Would have stayed unnoticed for weeks in the V1/V2 signal Telegram dispatch — symbols in Telegram instead of display names
Pattern: notification-dispatcher modules are rarely touched + often have their own helper versions that are not visible in the main refactor path.
Lesson: 30s grep upfront = hours of re-review cycle saved.
Red Flags — STOP and grep
- You're writing the first new call after a refactor right now
- Your mental model is "the 3 files" or "only in /core/"
- Notification/scheduler/batch were not explicitly mentioned in your list
- LSP refactor ran cleanly, but you have dynamic imports in the codebase
All mean: 30s repo-wide grep on old pattern, then categorize hit list, then migrate.
Cross-References
- REQUIRED COMPLEMENT:
pre-deploy-code-drift-detection(drift check AFTER the refactor) - COMPLEMENT:
silent-except-hides-schema-drift(same bug class from the symptom side) - Maxim: "Single Source of Truth — hardcoded defaults are ticking time bombs"
Background: TDD progression (Bulletproofing log)
Cycle 1 — PASS with R1 refactor
-
RED subagent (without skill, scenario "migrate display_names inline-dicts to central
instrument_label(), 3 known files"): heuristically recommended "first grep for further occurrences" — surprisingly good, but only the variable-name grep (grep -rn "display_names"). Self-critique listed 7 points (repo not inspected, DB as SoT not addressed, migration order with imports, tests before deletion, tooling hint, anti-pattern arc, no dict diff before merge). -
GREEN subagent (with skill): brought the decisive added value — the 4th grep pass on mapping values (
grep -rn "'CL=F'\s*:\s*'WTI") that catches copies of the mapping structure under deviating variable names. Plus: named concrete high-risk paths for the codebase (notifications/signal_dispatcher.py,notifications/telegram_*.py,scheduler/jobs/*.py,briefings/*.py), cross-reference topre-deploy-code-drift-detectionas complement after the refactor, schema-use-case-mismatch hint (display_name IS NULLcheck). -
R1 refactor applied: Quick Reference table extended with row "Inline-dict → SoT (value substring, catches obscured names)" + hint block that this is the most critical variant (would have caught the bug).
-
Anti-pattern avoided: GREEN predicted the bug exactly —
notifications/signal_dispatcher.pywith its own_get_display_namewould have stayed unmigrated, raw symbols in the V1/V2 Telegram dispatch for weeks.
Cycle-2-Backlog (Polish, non-blocking)
- CWD hint for subagent use-cases: "when your CWD is not the target repo: first
cdor delegate commands to user". GREEN subagent had a CWD mismatch (vault instead of repo) and had to solve that via command suggestions. - LSP find_references as complement source (not only as anti-pattern): for LSP-capable repos additional verification alongside grep.
- Schema-use-case mismatch as explicit sub-check in Step 3: for DB-backed lookups (
instruments.display_name IS NULL) a DB-data-state check before migration is needed — own drift class. - High-risk paths list for the codebase as Quick Reference: notification/, scheduler/, briefings/, analytics/, tests/integration/ — project-specifically valuable.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most refactoring skills give in ~2.4k tokens
Counted across 521 of the 525 authors here whose files we hold, read 2026-08-07
- Run tests after each changein 52 of 521
- Run the full test suite after each stepin 32 of 521
- Preserve external behaviorin 27 of 521, across 24 files
- Remove dead codein 26 of 521
- Write tests before refactoringin 26 of 521, across 25 files
- Make small incremental changesin 19 of 521, across 16 files
- Break the implementation into tiny commitsin 18 of 521, across 5 files
- Ask the user about alternative optionsin 17 of 521, across 4 files
- Create a GitHub issue with the planin 17 of 521, across 4 files
- Explore the repository to verify assertionsin 17 of 521, across 4 files
- Interview the user about the refactorin 16 of 521, across 3 files
- Check the codebase for test coveragein 16 of 521, across 3 files
Said here and by no other author read
- run repo-wide grep before writing new patterns
- exclude cache and virtual environment directories from grep
- search mapping values to catch obscured variable names
- categorize every grep hit
- migrate all production hot-path hits
- decide explicitly whether to migrate deprecated-but-in-path hits
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.