From: Gabriel Dunne Date: Sat, 19 Sep 2026 20:05:54 +0000 (-0700) Subject: cd-smart Readme X-Git-Url: https://git.quilime.com/?a=commitdiff_plain;h=ac0865bdbcde7e9dff23e69027cbfd0a9bfbc329;p=cfg.git cd-smart Readme --- diff --git a/.zsh/cd-smart.README.md b/.zsh/cd-smart.README.md index d931a65..136b11b 100644 --- a/.zsh/cd-smart.README.md +++ b/.zsh/cd-smart.README.md @@ -1,35 +1,34 @@ # cd-smart A `cd` that learns where you go. About 250 lines of zsh, one plain-text -database, no external processes on the `cd` path, and no dependencies beyond -zsh itself (fzf is used by `cdi` when present). +database, no external processes, and no deps beyond zsh itself. (`fzf` is +optionally used is used by `cdi` when present). ## Install ```zsh -# ~/.zshrc, after compinit source ~/.zsh/cd-smart.zsh ``` -Files live next to the script: `cd-smart.tsv` (learned directories) and -`cd-smart.aliases` (shortcuts defined at the prompt). +Data files are siblings to the script: `cd-smart.tsv` (learned directories) and +`cd-smart.aliases` (shortcuts). ## Use ```zsh cd ~/Projects/notes # ordinary cd; the directory is learned -cd agents # jump to the best learned match for "agents" -cd open ag # words must appear in order: .../Projects/notes -cd ag # normal completion plus learned matches and shortcuts -cdi ag # pick from matches in fzf; ctrl-/ toggles a preview -cdi # browse everything learned +cd foo # jump to the best learned match for "foo" +cd proj no # words must appear in order: .../Projects/notes +cd pr # normal completion plus learned matches and shortcuts +cdi pr # pick from matches in fzf; ctrl-/ toggles a preview +cdi # browse everything learned ``` -Precedence inside the wrapper, first match wins: +"first match wins" precidence: 1. No argument, or anything with options, slashes, or a leading `.` or `~`, goes straight to the builtin. `cd ..`, `cd -`, `cd ~/x` are untouched. -2. A single word that exists as a directory here. +2. A single word that exists as a directory in the pwd. 3. A single word that is a shortcut. 4. Two words that work as the builtin's `cd old new` substitution. 5. The best-scored learned directory containing the words in order. @@ -38,17 +37,17 @@ Precedence inside the wrapper, first match wins: ### Shortcuts ```zsh -cdalias osa ~/Projects/notes # "cd osa" always goes there -cdalias dl # current directory becomes "dl" -cdalias # list -cdunalias dl # remove +cdalias notes ~/Projects/notes # "cd notes" always goes there +cdalias dl # current directory becomes "dl" +cdalias # list +cdunalias dl # remove ``` Shortcuts can also be declared in `.zshrc` before sourcing, and those win over saved ones: ```zsh -typeset -gA CDS_ALIASES=(osa ~/Projects/notes) +typeset -gA CDS_ALIASES=(notes ~/Projects/notes) ``` ### Maintenance @@ -73,43 +72,47 @@ Set before sourcing. ## How ranking works -Each learned directory has a score and a last-visit time. A visit adds 1 to -the score. At lookup time a candidate's rank is its score plus a bonus for an +This area is largely riffing on how `zoxide` does it, but in my own way. Each +learned directory has a SCORE and a LAST-VISIT time. A visit adds 1 to +the SCORE. At lookup time a candidate's rank is its SCORE plus a bonus for an exact basename match (+100) or basename prefix (+50) on the last query word, plus a recency bonus (20 within an hour, 15 within a day, 10 within a week, -5 within a month). +5 within a month). This is arbitrary, but seems to work well for now. I've continue +to tweak it. Growth is bounded by aging. When the sum of all scores exceeds `CDS_MAXAGE`, -every score is multiplied by `0.9 × CDS_MAXAGE / total`, entries that fall -below 1 are dropped, and directories that no longer exist are pruned. Places -you keep returning to stay; one-off visits fade out. +every score is multiplied by `0.9 * CDS_MAXAGE / total`, entries that fall +below 1 are dropped, and directories that no longer exist are pruned. The idea +is that places you keep returning to will stay, but one-off visits will fade out. ## Tests +I vibe coded some tests. I've barely looked at this, but it works. It runs in a +throwaway sandbox and never touches your real database. + ```zsh zsh ~/.zsh/cd-smart.test.zsh ``` -Runs in a throwaway sandbox and never touches your real database. - ## Compared with zoxide -[zoxide](https://github.com/ajeetdsouza/zoxide) solves the same problem and -is the obvious alternative. The ranking model here is deliberately close to -its design. The differences are mostly about scope. +It's not fair if I don't compare to [zoxide](https://github.com/ajeetdsouza/zoxide) +because I solve the same "problem" and `z` is the obvious alternative. +The ranking model I've come up with is deliberately close to its design. +The differences are mostly about scope. Here's the various diffs: | | cd-smart | zoxide | |---|---|---| | Implementation | ~250 lines of zsh, one file | ~2,000 lines of Rust plus a shell template per shell | -| Shells | zsh only | bash, zsh, fish, nushell, PowerShell, and others | -| Database | plain TSV you can read and edit | binary, with file locking | +| Shells | zsh only. (sorry, not sorry). | bash, zsh, fish, nushell, PowerShell, and others | +| Database | plain TSV you can read and edit as a human | binary, with file locking | | Command | wraps `cd` itself | separate `z` and `zi`; `--cmd cd` can replace `cd` | | Real directories vs. guesses | an existing local directory always wins, then shortcuts, then learned matches | with `--cmd cd`, an existing path also wins; otherwise `z` always guesses | | Tab completion | learned matches and shortcuts appear inside normal `cd ` | completion on the `z` command only | -| Shortcuts | built in (`cdalias`) | none; use shell aliases | -| Where learning happens | only under `CDS_ROOTS` | everywhere, minus `_ZO_EXCLUDE_DIRS` | +| Shortcuts | built in (`cdalias`) | none -- use shell aliases | +| Where learning happens | Exclusive: only under `CDS_ROOTS`. | Everywhere, minus `_ZO_EXCLUDE_DIRS` | | Multi-word queries | words in order anywhere in the path; last word gets basename bonuses | words in order; last word must match the last path component | -| Recency | additive bonus by age bucket | multiplier by age bucket (4×, 2×, 0.5×, 0.25×) | +| Recency | additive bonus by age | multiplier by age (4×, 2×, 0.5×, 0.25×) | | Aging | same scheme: scale by 0.9 × cap / total, drop below 1, prune dead paths | same, default cap 10,000 | | Concurrency | last writer wins if two shells `cd` at the same instant | locked writes | | External processes | none on `cd` or lookup | one binary launch per `cd` and per query |