| name | typo-sweep |
| description | Walk a folder of git repos and open one typo-fix PR per repo.
Per repo: fetch trunk, scan docs and comments only, fix up to ~50 substitutions
in atomic per-file commits, push, open PR, monitor CI, chain fix-pr-checks on Danger failures.
Use when asked to "sweep typos", "fix typos across repos", or similar.
|
| user-invocable | true |
| allowed-tools | Bash, Edit, Read, Write, Glob, Grep, Skill, AskUserQuestion, Task |
Typo Sweep
Walk a folder of git repos and open one mechanical typo-fix PR per repo.
Arguments
$ARGUMENTS — absolute path to a folder containing repos.
If empty, ask the user via AskUserQuestion.
Sidecar files (next to this SKILL.md)
typos.txt — curated misspelling → correction list.
scan.sh — ripgrep wrapper that scans a directory using typos.txt.
apply-fix.py — word-boundary substitution with two guards:
- Content match (drift-safe): pass the scan's matched line text as a 5th arg and the fix lands on the line with that content wherever it moved, skipping (loudly) a line that changed or was already fixed upstream — never guessing.
- Single-quote guard: refuses an apostrophe correction (
don't, today's, …) that would land inside a single-quoted string literal (it '...', it('...')) and break it. Comments and double-quoted strings proceed.
- Still refuses when the word isn't present, so misalignments fail loudly.
test_apply_fix.py pins these behaviors: python3 test_apply_fix.py.
Reference them via $(dirname "$0") inside scripts, or via the absolute paths under ~/.claude/skills/typo-sweep/ (or ~/.dotfiles/agents/skills/typo-sweep/).
Workflow
0. Resolve the target folder
If $ARGUMENTS names an existing directory, use it.
Otherwise ask the user with AskUserQuestion — don't guess.
1. Enumerate repos
For each subdirectory of the target, check git -C <dir> rev-parse --is-inside-work-tree.
Skip non-repos.
Skip worktree containers (.git-worktrees/, *-worktrees).
Present the list back to the user briefly, then process sequentially.
One repo at a time — no parallelism.
2. Per-repo loop
For each repo:
a. Fetch the default branch
git -C <repo> fetch origin
default=$(git -C <repo> remote show origin | awk '/HEAD branch/ {print $NF}')
If the fetch fails (VPN-only or otherwise unreachable remote), skip and report.
b. Create a worktree
Path: ~/Developer/git-worktrees/<repo-basename>/typo-fixes.
Branch: mokagio/typo-fixes (the user's prefix for shared repos; see ~/.config/agents/rules/git.md).
git -C <repo> worktree add ~/Developer/git-worktrees/<basename>/typo-fixes -b mokagio/typo-fixes origin/<default>
If the branch already exists locally (prior sweep mid-flight), surface and ask the user before clobbering.
c. Scan
$SKILL_DIR/scan.sh <worktree> /tmp/typo-sweep/<basename>.txt
Output is path:line:col:matched line per finding.
d. Curate findings
Drop (do not fix):
- Vendored / third-party code —
Pods/, node_modules/, vendor/, Carthage/, External/, ThirdParty/, third_party/, third-party/. The scanner already excludes most of these, but watch for repo-specific vendored dirs.
- Generated files — anything whose header says "Generated by", "DO NOT EDIT", "Autogenerated".
Notable case:
*+Generated.swift from SwiftGen (scanner excludes by glob).
If the typo lives in an .strings file comment (/* ... */), that's the source of truth — fix it there.
- Code identifiers — function/class/variable/property names, enum cases, constant values, struct fields, JSON/dict keys, parameter labels, IBOutlet names.
Rule of thumb: if the match is on a line that doesn't start with a comment marker and isn't inside a string/comment, skip it. When in doubt,
Read the surrounding lines.
- User-facing localization VALUES — the english string in
NSLocalizedString("text", ...), Android <string> resources, formatMessage('key') translation keys.
Only fix the comment / translator-hint argument (NSLocalizedString("...", comment: "fix typos HERE")).
- URL slugs, settings keys, translation keys — anything kebab-cased that looks like a stable identifier (
why-doesnt-npm-upgrade-install, todays-stats-card-enabled-site-settings, 'dont-ask-again').
- Foreign-language metadata —
fastlane/metadata/<locale>/* and fastlane/*_metadata/<locale>/* for non-English locales (scanner excludes by glob, but verify).
Fix (target):
.md, .txt, .rst files (docs, READMEs).
- Source-code comments (
//, /* */, #, ///, * in JavaDoc, etc.).
- Test description strings —
expectation(description: "..."), XCTFail("msg"), XCTAssert(..., "msg"), backticked Kotlin test function names.
- Translator-hint comments — the
comment: arg of NSLocalizedString.
- Comments inside
.strings files (/* ... */ — these annotate strings for SwiftGen and translators).
Watch out for context-sensitive corrections:
happend → "happen" or "happened" depending on tense. "Should never happend" wants happen, not happened.
zeroes is a valid English plural and a verb — leave it alone.
doesnt in a Kotlin backticked test name fixes to doesn't (apostrophe inside backticks is fine), but verify the rename doesn't break a golden filename or CI test filter.
- Apostrophe into a single-quoted string breaks code.
dont/doesnt/todays → don't/doesn't/today's inside a single-quoted Ruby/JS string (RSpec it '...', JS it('...')) terminates the string. apply-fix.py refuses these, but prefer not to queue them. Comments and double-quoted strings are safe.
- Lint runs on changed files. A comment fix pulls its file into the PR's changed set, surfacing pre-existing lint debt (e.g. TS typecheck, detekt), and adding a char (
today's) can push a backtick test name past a line-length limit. If CI flags a file you only touched a comment in, drop that file/line from the PR rather than fixing unrelated debt.
When unsure, skip the fix.
e. Apply fixes — cap at ~50 substitutions per PR
python3 $SKILL_DIR/apply-fix.py <worktree>/<file> <line> <old> <new> "<matched line text>"
Pass the scan's matched line text (the part after path:line:col: in the scan.sh output) as the 5th arg. This makes the fix drift-safe — it lands on the line with that content even if line numbers shifted, and skips loudly if the line changed or was already fixed upstream. The 4-arg form (no matched line) still works but addresses by line number only.
After each substitution count, stop adding more fixes for this PR once you hit 50 substitutions (≈ 50 changed file lines / ≈ 100 diff lines).
A small follow-up sweep can pick up the remainder later.
If the repo has fewer than 50 typos, fix them all.
f. Commit per file
One commit per file, atomic. Title pattern:
Fix typo in `<basename>` # exactly 1 substitution
Fix typos in `<basename>` # >1 substitutions
If the title exceeds 50 characters, drop the backticks first.
If still too long, the un-backticked form is preferable to truncating the filename.
Use agentic-commit with a heredoc message footer:
agentic-commit -C <worktree> -- <file> <<'EOF'
<title>
---
Generated with the help of Claude Code, https://claude.com/claude-code
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EOF
g. Push and open the PR
git -C <worktree> push -u origin mokagio/typo-fixes
Then gh pr create. Use --head mokagio/typo-fixes explicitly to avoid the "branch must first be pushed" race on freshly-pushed branches.
PR description: lean — see ~/.config/agents/rules/github.md.
State that this is a mechanical, comment-only change with no behaviour impact and that each commit is one file for easy review.
Follow the repo's PULL_REQUEST_TEMPLATE.md if present.
Sign off with:
*Posted by Claude (Opus 4.7) on behalf of @mokagio with approval.*
Assign to @mokagio.
h. Monitor CI immediately — not optional
Per AGENTS.md:
Skill(skill="ci-monitor", args="<owner/repo>#<number>")
If ci-monitor reports danger/pr-check failures (missing label, missing milestone, past-due milestone):
Skill(skill="fix-pr-checks", args="<owner/repo>#<number>")
Then continue monitoring until Danger re-runs and resolves.
Don't move to the next repo until the current PR's checks are green or only known-flaky failures remain (report those).
i. Loop
Move to the next repo.
3. Empty / unfixable repos
A repo with no typos, or only unfixable ones (all generated / identifiers / i18n values): clean up the worktree and branch, don't open an empty PR.
git -C <repo> worktree remove ~/Developer/git-worktrees/<basename>/typo-fixes
git -C <repo> branch -D mokagio/typo-fixes
4. Stop conditions
- No repos left.
- The user interrupts.
Constraints
- One PR per repo per sweep. Cap at ~50 substitutions; leftovers belong to a later sweep.
- Never modify the main checkout — always work in the worktree.
- Never amend — new commits only.
- Sequential — one repo at a time.
- No PR for zero diffs — clean up the worktree and branch instead.
Reporting
After each PR:
<owner/repo>#<number> — <PR URL>, N fixes, CI: <status>
At the end:
Sweep complete.
N PRs opened, M repos skipped (reason).
Outstanding: <any PRs still red after fix-pr-checks>.