Skip to content
Back to skills

Code Ratchets

ASecurity

Implement code quality ratchets to prevent proliferation of deprecated patterns. Use when (1) migrating away from legacy code patterns, (2) enforcing gradual codebase improvements, (3) preventing copy-paste proliferation of deprecated practices, or (4) setting up pre-commit hooks to count and limit specific code patterns. A ratchet fails if pattern count exceeds OR falls below expected—ensuring patterns never increase and prompting updates when they decrease.

  • 666 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added February 8, 2026
developmentpythonshellbashgitapi

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill code-ratchets --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Code Ratchets?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Code Ratchets
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-code-ratchets/badge)](https://www.skillsdirectory.com/skills/majiayu000-code-ratchets)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: code-ratchets
description: Implement code quality ratchets to prevent proliferation of deprecated patterns. Use when (1) migrating away from legacy code patterns, (2) enforcing gradual codebase improvements, (3) preventing copy-paste proliferation of deprecated practices, or (4) setting up pre-commit hooks to count and limit specific code patterns. A ratchet fails if pattern count exceeds OR falls below expected—ensuring patterns never increase and prompting updates when they decrease.
---

# Code Ratchets

A ratchet is a pre-commit check that counts deprecated patterns in your codebase against a hard-coded expected count. It fails in two cases:
- **Too many instances**: Prevents proliferation via copy-paste
- **Too few instances**: Congratulates you and prompts lowering the expected count

This automates the manual code review process of saying "don't do this, we've stopped doing this."

## Core Workflow

### 1. Identify the Pattern

Define what to count. Patterns work best when they're:
- Simple text or regex matches (grep-able)
- Unambiguous (low false positive rate)
- Discrete instances (countable)

Examples:
- `TODO:` comments
- `# type: ignore` annotations
- `var ` declarations (vs `let`/`const`)
- `Any` type annotations
- Specific function calls: `unsafe_parse(`, `legacy_auth(`
- Import statements: `from old_module import`

### 2. Create the Ratchet Script

Create `scripts/ratchet.py` (or `.sh`):

```python
#!/usr/bin/env python3
"""
Code ratchet: prevents deprecated patterns from proliferating.
Fails if count > expected (proliferation) or count < expected (time to ratchet down).
"""
import subprocess
import sys

# ============================================================
# RATCHET CONFIGURATION - Edit counts here as patterns decrease
# ============================================================
RATCHETS = {
    "TODO comments": {
        "pattern": r"TODO:",
        "expected": 47,
        "glob": "**/*.py",
        "reason": "Resolve TODOs before adding new ones",
    },
    "Type ignores": {
        "pattern": r"# type: ignore",
        "expected": 23,
        "glob": "**/*.py",
        "reason": "Fix type errors instead of ignoring them",
    },
    "Any types": {
        "pattern": r": Any[,\)\]]",
        "expected": 12,
        "glob": "**/*.py",
        "reason": "Use specific types instead of Any",
    },
}


def count_pattern(pattern: str, glob: str) -> int:
    """Count pattern occurrences using grep."""
    try:
        result = subprocess.run(
            ["grep", "-r", "-E", "--include", glob, "-c", pattern, "."],
            capture_output=True,
            text=True,
        )
        # Sum counts from all files (grep -c outputs "filename:count" per file)
        total = sum(
            int(line.split(":")[-1])
            for line in result.stdout.strip().split("\n")
            if line and ":" in line
        )
        return total
    except Exception:
        return 0


def main() -> int:
    failed = False

    for name, config in RATCHETS.items():
        actual = count_pattern(config["pattern"], config["glob"])
        expected = config["expected"]

        if actual > expected:
            print(f"❌ RATCHET FAILED: {name}")
            print(f"   Expected ≤{expected}, found {actual} (+{actual - expected})")
            print(f"   Reason: {config['reason']}")
            print(f"   Pattern: {config['pattern']}")
            print()
            failed = True
        elif actual < expected:
            print(f"🎉 RATCHET DOWN: {name}")
            print(f"   Expected {expected}, found {actual} (-{expected - actual})")
            print(f"   Update expected count in ratchet.py: {expected} → {actual}")
            print()
            failed = True  # Still fail to prompt the update
        else:
            print(f"✓ {name}: {actual}/{expected}")

    return 1 if failed else 0


if __name__ == "__main__":
    sys.exit(main())
```

### 3. Configure Pre-commit

Add to `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: local
    hooks:
      - id: code-ratchets
        name: Code Ratchets
        entry: python scripts/ratchet.py
        language: python
        pass_filenames: false
        always_run: true
```

Install hooks:
```bash
pip install pre-commit
pre-commit install
```

### 4. Initialize Counts

Run the script once to get current counts, then set those as expected values:

```bash
# Get current counts
grep -r -E "TODO:" --include="*.py" -c . | awk -F: '{sum+=$2} END {print sum}'

# Update RATCHETS dict with actual counts
```

### 5. Maintain the Ratchet

When the ratchet fails with "too few":
1. Celebrate—someone removed deprecated patterns!
2. Update the expected count in the script
3. Commit the updated script

## Alternative: Shell-based Ratchet

For simpler setups, use `scripts/ratchet.sh`:

```bash
#!/bin/bash
set -e

check_ratchet() {
    local name="$1"
    local pattern="$2"
    local expected="$3"
    local glob="$4"
    local reason="$5"

    actual=$(grep -r -E "$pattern" --include="$glob" . 2>/dev/null | wc -l | tr -d ' ')

    if [ "$actual" -gt "$expected" ]; then
        echo "❌ RATCHET FAILED: $name"
        echo "   Expected ≤$expected, found $actual"
        echo "   Reason: $reason"
        exit 1
    elif [ "$actual" -lt "$expected" ]; then
        echo "🎉 RATCHET DOWN: $name"
        echo "   Update expected: $expected → $actual"
        exit 1
    else
        echo "✓ $name: $actual/$expected"
    fi
}

# ============ RATCHET CONFIGURATION ============
check_ratchet "TODO comments" "TODO:" 47 "*.py" "Resolve TODOs first"
check_ratchet "Type ignores" "# type: ignore" 23 "*.py" "Fix type errors"

echo "All ratchets passed!"
```

## Best Practices

1. **Keep patterns simple**: Basic grep/regex. Avoid complex AST analysis—fragility outweighs precision.

2. **One ratchet per concern**: Separate ratchets for separate issues. Easier to track progress.

3. **Document the "why"**: Include `reason` field explaining why the pattern is deprecated.

4. **Fail on decrease**: Always require manual update of expected counts. This creates an audit trail of progress.

5. **Escape hatch**: For exceptional cases, consider allowing bypass via commit message:
   ```python
   # In ratchet.py, check for bypass
   import os
   if os.environ.get("RATCHET_BYPASS"):
       print("⚠️  Ratchet bypassed via RATCHET_BYPASS env var")
       sys.exit(0)
   ```

   Usage: `RATCHET_BYPASS=1 git commit -m "Emergency fix, ratchet bypass justified: ..."`

6. **Gradual rollout**: Start with high counts and let them naturally decrease. Don't set expected=0 on day one.

## Common Ratchet Patterns

| Pattern | Regex | Use Case |
|---------|-------|----------|
| TODO comments | `TODO:` | Track technical debt |
| Type ignores | `# type: ignore` | Enforce typing |
| Any types | `: Any[,\)\]]` | Specific types |
| Console logs | `console\.log\(` | Remove debug code |
| Legacy imports | `from legacy_module` | Track migrations |
| Deprecated calls | `deprecated_func\(` | API migrations |
| Broad exceptions | `except:` or `except Exception:` | Specific exceptions |
| Magic numbers | `\b\d{3,}\b` (tuned) | Named constants |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.