| 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>.