File order decided precedence, so a narrow rule had to be written above the
broad one it carves an exception out of -- an ordering constraint the file
cannot show and the user has to remember. *NIKOLA* below *NIK* silently matched
nothing, and a catch-all * could only ever be the last line.
Engine.New now sorts once and MatchIndex walks that order: most literal
characters first, then fewest *, then account-scoped over unscoped. Literals
are what a rule commits to and a * is what it gives up, so a bare * is tried
last wherever it sits. The sort is stable, so equally specific rules keep file
order and the earlier one wins -- which is all position decides now, and why
AppendRule can keep appending without displacing a rule written by hand.
The two orders must not be confused: Rules(), Usage and MatchIndex still speak
in file positions, because that is what the rules screen numbers and what
DeleteRules deletes by. A shadowed rule still reports zero usage, but a zero no
longer says anything about where the rule sits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>