Similarity requirements now scale with word length (typo.floor_ok:
single edit or ratio >= max(0.6, 1 - 3/max(len,3)), first char kept;
zsh spdist / nushell did_you_mean derivation in the source). The
fixed-cutoff gates are swapped: history_resolver._similar ->
floor_ok with the divergence cap deleted (every diverged token is
gated per-token, not counted), learned.guess_from_path and the help
resolver candidate gates -> floor_ok, so _TOKEN_CUTOFF,
_MAX_DIVERGED, GUESS_CUTOFF, _CUTOFF and their difflib plumbing are
gone; unique-survivor, no-op and which/'/'/'.'/flag guards kept.
New thefuck/danger.py is_dangerous(script) parses via bashlex
directly and fail-safes to True when bashlex is unavailable or the
script refuses to parse (the flat fallback is head-only); with a
tree it matches rm/rmdir recursive flags, dd/mkfs*/shred/wipefs/
mkswap heads, git push --force/-f (not --force-with-lease),
chmod/chown -R with a 777-style mode, kill -9, fork-bomb shapes,
pipe-to-shell tails and file redirects outside /tmp and /dev/null.
fix_command checks it before ANY auto-run, learned-db exact hits
included: dangerous candidates fall through to rules+ask.
Test-migration inventory (authorized semantic inversions):
- tests/resolvers/test_history_resolver.py: declines-3-diverged ->
corrects (cap deleted); 0.8-cutoff boundary arithmetic re-based to
floor boundaries (len-3 0.6 / len-10 0.7 / len-30 0.9);
_TOKEN_CUTOFF import removed with the constant; declines-just-
below-cutoff re-based to the len-20 floor 0.85; added a 17-char
below-floor decline.
- tests/test_learned.py: returns-none-below-cutoff re-based to a
same-first-char below-floor pair (0.6 < len-10 floor 0.7); added a
ratio-0.7 acceptance pin; single-edit-under-cutoff renamed.
- tests/resolvers/test_help_resolver.py: transposition comments
re-based to the floor; added a below-floor subcommand decline.
- tests/entrypoints/test_fix_command_learned.py: exact-learned-wins
fixture's 'git push --force' correction (now correctly refused)
replaced by benign scripts; mock_learned stubs danger benign for
platform-neutral auto-apply tests; added TestDangerOverride
(real module, all four sources: reaches select_command, nothing
auto-runs).
difflib's SequenceMatcher underrates adjacent transpositions, so the
flagship examples (gti->git 0.667, greo->grep 0.75, psuh->push 0.75)
fell below the 0.8 ratio gates and were declined. Per the plan
amendment, the token and candidate gates in the history resolver,
guess_from_path and the help resolver additionally accept a
first-char-equal single edit (one substitution, insertion, deletion
or adjacent transposition; new thefuck/typo.py), with the ratio
cutoffs, the exact-one-candidate rule and the 0.79 boundary (edit
distance >= 2) unchanged.
When no learned correction matches, fuzzy-match the mistyped token
against executables from $PATH and shell aliases. A guess is auto-run
and recorded into the learned-corrections db only when it is
unambiguous: the token is not an existing executable, contains no
path separators or extensions, shares its first character with the
candidate, has similarity of at least 0.8, and exactly one candidate
survives. Asking the user stays the last resort.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Record user-accepted corrections in a shelve-backed store and replay
them on future matching mistakes, bypassing rule evaluation entirely.
Two-level matching: exact full-command lookup, then word-level
reconstruction that generalises to different arguments (e.g. learning
'git psuh origin main' also fixes 'git psuh origin dev').