refcheck¶
Fast reference validator for codebases. Finds broken file references, fragile path patterns, and validates variable-based paths.
What it does¶
refcheck validates file references across your codebase, checking for:
Errors (always exit 1)¶
- Broken source statements - Missing files in
sourcecommands (including variable paths like$SCRIPT_DIR/file.sh) - Broken script references - Missing files in
bashorshcommands - Old path patterns - Stale references after refactoring
Warnings (exit 0 unless --strict)¶
- Fragile to working directory - Relative paths that only work from specific directories
- Fragile to refactoring - Variable assignments using
../traversal (breaks when files move)
Why use it¶
Proactive error detection:
- Catch broken references before running expensive test suites
- Find issues in seconds vs minutes for full e2e tests
- Validate changes before committing
Refactoring safety:
- After moving files, verify all references updated
- Find stale patterns across entire codebase
- Custom pattern checking for any refactoring
Better than grep:
- Validates file existence, not just pattern matching
- Automatically filters false positives (docs, planning, dynamic paths)
- Structured output with suggestions
- Exit codes for CI/CD integration
Variable path validation:
- Resolves shell variables like
$SCRIPT_DIRand$DOTFILES_DIRbefore validation - Detects broken paths hidden behind variables
- Shows both original and resolved paths in error messages
- Gracefully skips unresolvable variables to avoid false positives
Warning system:
- Detects fragile patterns that may break in different contexts
- Configurable severity: warnings (default) or errors (--strict)
- Can be disabled for legacy codebases (--no-warn)
- Actionable suggestions for each warning
Installation¶
Installed as a Python tool via uv:
This is handled automatically by management/common/install/language-tools/uv-tools.sh.
Usage¶
# Validate all references in current directory
refcheck
# Check specific directory
refcheck management/
# Find old pattern after refactoring
refcheck --pattern "old/path/" --desc "Update to new/path/"
# Filter by file type (like fd -e)
refcheck --type sh apps/
# Skip documentation files
refcheck --skip-docs
# Combine filters
refcheck --pattern "FooClass" --type py --skip-docs src/
# Disable warnings (only check for errors)
refcheck --no-warn
# Treat warnings as errors (strict mode for CI)
refcheck --strict
Common workflows¶
After moving files¶
# Moved tests/install/ to tests/install/
refcheck --pattern "tests/install/"
# Finds all stale references across repo
Before running tests¶
# Quick validation (2 seconds vs 10+ minutes for e2e tests)
refcheck --skip-docs
# Catches broken references early
Check specific component¶
# Validate management/ directory only
refcheck management/
# Check only shell scripts in apps/
refcheck apps/ --type sh
Use in CI/CD¶
# Strict mode - fail build on warnings
refcheck --strict
# Regular mode - warnings don't fail build
refcheck
# Disable warnings for legacy code
refcheck --no-warn
Detect fragile patterns¶
# Find paths that only work from specific directories
refcheck # Shows warnings for fragile relative paths
# Find variable assignments using ../ traversal
refcheck # Shows warnings for SCRIPT_DIR="$(cd "$DIR/../../.." && pwd)"
Learn from git history¶
# Generate rules from git rename history (last 6 months by default)
refcheck --learn-rules
# Rules are stored per-repo at ~/.config/refcheck/repos/{repo-name}/rules.json
# These help suggest correct paths when references are broken
Configuration¶
Create ~/.config/refcheck/config.toml to customize behavior:
[learn]
time_window = "6 months" # How far back to analyze git history
[warnings]
stale_threshold = "7 days" # Warn when rules are older than this
show_no_rules_hint = true # Show hint to run --learn-rules
Output¶
When errors found:
❌ Found 2 error(s)
Errors:
Broken Source (2):
────────────────────────────────────────────────────────────
tests/broken.sh:4
Missing: $SCRIPT_DIR/nonexistent.sh → /path/to/nonexistent.sh
→ Verify path exists or update reference
src/install.sh:15
Missing: /path/to/missing.sh
→ Verify path exists or update reference
When warnings found:
⚠️ Found 2 warning(s)
Warnings:
Fragile to Working Directory (1):
────────────────────────────────────────────────────────────
scripts/deploy.sh:3
Relative path only valid from: repo root
source tests/helpers.sh
→ Use root directory variable (e.g., $PROJECT_ROOT, $REPO_ROOT)
Fragile to Refactoring (1):
────────────────────────────────────────────────────────────
scripts/setup.sh:8
SCRIPT_DIR uses relative directory traversal (../) - fragile to file moves
→ Consider dynamic root detection: git rev-parse --show-toplevel
When errors and warnings found:
❌ Found 1 error(s) and 2 warning(s)
Errors:
[... errors listed ...]
Warnings:
[... warnings listed ...]
When all valid:
Exit codes¶
0- All references valid, or only warnings found (default mode)1- Found errors, or warnings in strict mode (--strict)
Exit code behavior:
# Always exits 1 if errors found
refcheck # Exit 1 if errors, exit 0 if only warnings
# Strict mode: treat warnings as errors
refcheck --strict # Exit 1 if errors OR warnings
# Disable warnings: only check errors
refcheck --no-warn # Exit 1 if errors, never warns
Use in scripts:
# Normal mode - warnings don't fail
if refcheck; then
echo "All references valid (warnings OK)"
fi
# Strict mode - warnings fail
if refcheck --strict; then
echo "All references valid (no errors or warnings)"
else
echo "Issues found, fix before deploying"
exit 1
fi
Flags¶
| Flag | Description | Example |
|---|---|---|
path |
Directory to check (positional) | refcheck management/ |
--pattern PATTERN |
Find old pattern | --pattern "old/" |
--desc DESC |
Description for pattern | --desc "Now new/" |
--type, -t TYPE |
Filter by file type | --type sh |
--skip-docs |
Skip markdown files | --skip-docs |
--strict |
Treat warnings as errors (exit 1) | --strict |
--no-warn |
Disable fragile path warnings | --no-warn |
--learn-rules |
Generate rules from git history | --learn-rules |
--test-mode |
Include test fixtures (normally excluded) | --test-mode |
--help, -h |
Show help | --help |
Smart filtering¶
Automatically excludes:
- Build artifacts:
.git,node_modules,.venv,__pycache__,site/ - Historical files:
.planning/,.claude/metrics/ - Dynamic paths: Container paths (
/root/,/home/), temp files (/tmp/) - Self-references: Usage examples in scripts referencing themselves
Testing¶
Comprehensive pytest test suite (69 tests):
Tests cover config parsing, rules management, file suggestions, and end-to-end CLI behavior.
Implementation¶
Location: ~/tools/refcheck (standalone Python package)
Language: Python 3.11+
Dependencies: None (uses stdlib only)
Modular structure:
cli.py- argparse CLI entry pointconfig.py- Config dataclass, TOML loadingchecker.py- ReferenceChecker class (core logic)rules.py- Rules loading/learning from gitsuggestions.py- File similarity matchingoutput.py- Result formatting
Comparison to alternatives¶
vs grep:
grepfinds patterns but doesn't validate file existencerefcheckvalidates references point to real filesrefcheckauto-filters false positives
vs shellcheck:
shellcheckchecks literal paths in single filesrefcheckchecks across entire codebaserefcheckhandles dynamic paths and patterns
vs manual testing:
- Manual testing requires running full test suite (minutes)
refcheckvalidates in seconds- Catches issues before expensive CI/CD runs