Replace typewriter/hash worktree naming with adjective-noun pairs (#52221)

Richard Feldman created

Replace the typewriter-brand + random-hash naming scheme for
auto-generated git worktree branches with an adjective-noun approach
(e.g. `swift-falcon`, `calm-meadow`).

- Removed the ~650-entry typewriter brand name list and hash generation
- Added 235 adjectives and 228 nouns (~53,500 unique combinations)
- Branch names are now checked for exact-match collisions against
existing branches
- Updated error messaging

Closes AI-101

Release Notes:

- Improved auto-generated git worktree branch names to use friendlier
adjective-noun pairs (e.g. `swift-falcon`) instead of typewriter brand
names with random hashes.

Change summary

crates/agent_ui/src/agent_panel.rs  |   3 
crates/agent_ui/src/branch_names.rs | 922 ++++--------------------------
2 files changed, 124 insertions(+), 801 deletions(-)

Detailed changes

crates/agent_ui/src/agent_panel.rs 🔗

@@ -2828,8 +2828,7 @@ impl AgentPanel {
                     None => {
                         this.update_in(cx, |this, window, cx| {
                             this.set_worktree_creation_error(
-                                "Failed to generate a branch name: all typewriter names are taken"
-                                    .into(),
+                                "Failed to generate a unique branch name".into(),
                                 window,
                                 cx,
                             );

crates/agent_ui/src/branch_names.rs 🔗

@@ -1,710 +1,77 @@
 use collections::HashSet;
 use rand::Rng;
 
-/// Names of historical typewriter brands, for use in auto-generated branch names.
-/// (Hyphens and parens have been dropped so that the branch names are one-word.)
-///
-/// Thanks to https://typewriterdatabase.com/alph.0.brands for the names!
-const TYPEWRITER_NAMES: &[&str] = &[
-    "abeille",
-    "acme",
-    "addo",
-    "adler",
-    "adlerette",
-    "adlerita",
-    "admiral",
-    "agamli",
-    "agar",
-    "agidel",
-    "agil",
-    "aguia",
-    "aguila",
-    "ahram",
-    "aigle",
-    "ajax",
-    "aktiv",
-    "ala",
-    "alba",
-    "albus",
-    "alexander",
-    "alexis",
-    "alfa",
-    "allen",
-    "alonso",
-    "alpina",
-    "amata",
-    "amaya",
-    "amka",
-    "anavi",
-    "anderson",
-    "andina",
-    "antares",
-    "apex",
-    "apsco",
-    "aquila",
-    "archo",
-    "ardita",
-    "argyle",
-    "aristocrat",
-    "aristokrat",
-    "arlington",
-    "armstrong",
-    "arpha",
-    "artus",
-    "astoria",
-    "atlantia",
-    "atlantic",
-    "atlas",
-    "augusta",
-    "aurora",
-    "austro",
-    "automatic",
-    "avanti",
-    "avona",
-    "azzurra",
-    "bajnok",
-    "baldwin",
-    "balkan",
-    "baltica",
-    "baltimore",
-    "barlock",
-    "barr",
-    "barrat",
-    "bartholomew",
-    "bashkiriya",
-    "bavaria",
-    "beaucourt",
-    "beko",
-    "belka",
-    "bennett",
-    "bennington",
-    "berni",
-    "bianca",
-    "bijou",
-    "bing",
-    "bisei",
-    "biser",
-    "bluebird",
-    "bolida",
-    "borgo",
-    "boston",
-    "boyce",
-    "bradford",
-    "brandenburg",
-    "brigitte",
-    "briton",
-    "brooks",
-    "brosette",
-    "buddy",
-    "burns",
-    "burroughs",
-    "byron",
-    "calanda",
-    "caligraph",
-    "cappel",
-    "cardinal",
-    "carissima",
-    "carlem",
-    "carlton",
-    "carmen",
-    "cawena",
-    "cella",
-    "celtic",
-    "century",
-    "champignon",
-    "cherryland",
-    "chevron",
-    "chicago",
-    "cicero",
-    "cifra",
-    "citizen",
-    "claudia",
-    "cleveland",
-    "clover",
-    "coffman",
-    "cole",
-    "columbia",
-    "commercial",
-    "companion",
-    "concentra",
-    "concord",
-    "concordia",
-    "conover",
-    "constanta",
-    "consul",
-    "conta",
-    "contenta",
-    "contimat",
-    "contina",
-    "continento",
-    "cornelia",
-    "coronado",
-    "cosmopolita",
-    "courier",
-    "craftamatic",
-    "crandall",
-    "crown",
-    "culema",
-    "dactyle",
-    "dankers",
-    "dart",
-    "daugherty",
-    "davis",
-    "dayton",
-    "dea",
-    "delmar",
-    "densmore",
-    "depantio",
-    "diadema",
-    "dial",
-    "diamant",
-    "diana",
-    "dictatype",
-    "diplomat",
-    "diskret",
-    "dolfus",
-    "dollar",
-    "domus",
-    "drake",
-    "draper",
-    "duplex",
-    "durabel",
-    "dynacord",
-    "eagle",
-    "eclipse",
-    "edelmann",
-    "edelweiss",
-    "edison",
-    "edita",
-    "edland",
-    "efka",
-    "eldorado",
-    "electa",
-    "electromatic",
-    "elektro",
-    "elgin",
-    "elliot",
-    "emerson",
-    "emka",
-    "emona",
-    "empire",
-    "engadine",
-    "engler",
-    "erfurt",
-    "erika",
-    "esko",
-    "essex",
-    "eureka",
-    "europa",
-    "everest",
-    "everlux",
-    "excelsior",
-    "express",
-    "fabers",
-    "facit",
-    "fairbanks",
-    "faktotum",
-    "famos",
-    "federal",
-    "felio",
-    "fidat",
-    "filius",
-    "fips",
-    "fish",
-    "fitch",
-    "fleet",
-    "florida",
-    "flott",
-    "flyer",
-    "flying",
-    "fontana",
-    "ford",
-    "forto",
-    "fortuna",
-    "fox",
-    "framo",
-    "franconia",
-    "franklin",
-    "friden",
-    "frolio",
-    "furstenberg",
-    "galesburg",
-    "galiette",
-    "gallia",
-    "garbell",
-    "gardner",
-    "geka",
-    "generation",
-    "genia",
-    "geniatus",
-    "gerda",
-    "gisela",
-    "glashutte",
-    "gloria",
-    "godrej",
-    "gossen",
-    "gourland",
-    "grandjean",
-    "granta",
-    "granville",
-    "graphic",
-    "gritzner",
-    "groma",
-    "guhl",
-    "guidonia",
-    "gundka",
-    "hacabo",
-    "haddad",
-    "halberg",
-    "halda",
-    "hall",
-    "hammond",
-    "hammonia",
-    "hanford",
-    "hansa",
-    "harmony",
-    "harris",
-    "hartford",
-    "hassia",
-    "hatch",
-    "heady",
-    "hebronia",
-    "hebros",
-    "hega",
-    "helios",
-    "helma",
-    "herald",
-    "hercules",
-    "hermes",
-    "herold",
-    "heros",
-    "hesperia",
-    "hogar",
-    "hooven",
-    "hopkins",
-    "horton",
-    "hugin",
-    "hungaria",
-    "hurtu",
-    "iberia",
-    "idea",
-    "ideal",
-    "imperia",
-    "impo",
-    "industria",
-    "industrio",
-    "ingersoll",
-    "international",
-    "invicta",
-    "irene",
-    "iris",
-    "iskra",
-    "ivitsa",
-    "ivriah",
-    "jackson",
-    "janalif",
-    "janos",
-    "jolux",
-    "juki",
-    "junior",
-    "juventa",
-    "juwel",
-    "kamkap",
-    "kamo",
-    "kanzler",
-    "kappel",
-    "karli",
-    "karstadt",
-    "keaton",
-    "kenbar",
-    "keystone",
-    "kim",
-    "klein",
-    "kneist",
-    "knoch",
-    "koh",
-    "kolibri",
-    "kolumbus",
-    "komet",
-    "kondor",
-    "koniger",
-    "konryu",
-    "kontor",
-    "kosmopolit",
-    "krypton",
-    "lambert",
-    "lasalle",
-    "lectra",
-    "leframa",
-    "lemair",
-    "lemco",
-    "liberty",
-    "libia",
-    "liga",
-    "lignose",
-    "lilliput",
-    "lindeteves",
-    "linowriter",
-    "listvitsa",
-    "ludolf",
-    "lutece",
-    "luxa",
-    "lyubava",
-    "mafra",
-    "magnavox",
-    "maher",
-    "majestic",
-    "majitouch",
-    "manhattan",
-    "mapuua",
-    "marathon",
-    "marburger",
-    "maritsa",
-    "maruzen",
-    "maskelyne",
-    "masspro",
-    "matous",
-    "mccall",
-    "mccool",
-    "mcloughlin",
-    "mead",
-    "mechno",
-    "mehano",
-    "meiselbach",
-    "melbi",
-    "melior",
-    "melotyp",
-    "mentor",
-    "mepas",
-    "mercedesia",
-    "mercurius",
-    "mercury",
-    "merkur",
-    "merritt",
-    "merz",
-    "messa",
-    "meteco",
-    "meteor",
-    "micron",
-    "mignon",
-    "mikro",
-    "minerva",
-    "mirian",
-    "mirina",
-    "mitex",
-    "molle",
-    "monac",
-    "monarch",
-    "mondiale",
-    "monica",
-    "monofix",
-    "monopol",
-    "monpti",
-    "monta",
-    "montana",
-    "montgomery",
-    "moon",
-    "morgan",
-    "morris",
-    "morse",
-    "moya",
-    "moyer",
-    "munson",
-    "musicwriter",
-    "nadex",
-    "nakajima",
-    "neckermann",
-    "neubert",
-    "neya",
-    "ninety",
-    "nisa",
-    "noiseless",
-    "noor",
-    "nora",
-    "nord",
-    "norden",
-    "norica",
-    "norma",
-    "norman",
-    "north",
-    "nototyp",
-    "nova",
-    "novalevi",
-    "odell",
-    "odhner",
-    "odo",
-    "odoma",
-    "ohio",
-    "ohtani",
-    "oliva",
-    "oliver",
-    "olivetti",
-    "olympia",
-    "omega",
-    "optima",
-    "orbis",
-    "orel",
-    "orga",
-    "oriette",
-    "orion",
-    "orn",
-    "orplid",
-    "pacior",
-    "pagina",
-    "parisienne",
-    "passat",
-    "pearl",
-    "peerless",
-    "perfect",
-    "perfecta",
-    "perkeo",
-    "perkins",
-    "perlita",
-    "pettypet",
-    "phoenix",
-    "piccola",
-    "picht",
-    "pinnock",
-    "pionier",
-    "plurotyp",
-    "plutarch",
-    "pneumatic",
-    "pocket",
-    "polyglott",
-    "polygraph",
-    "pontiac",
-    "portable",
-    "portex",
-    "pozzi",
-    "premier",
-    "presto",
-    "primavera",
-    "progress",
-    "protos",
-    "pterotype",
-    "pullman",
-    "pulsatta",
-    "quick",
-    "racer",
-    "radio",
-    "rally",
-    "rand",
-    "readers",
-    "reed",
-    "referent",
-    "reff",
-    "regent",
-    "regia",
-    "regina",
-    "rekord",
-    "reliable",
-    "reliance",
-    "remagg",
-    "rembrandt",
-    "remer",
-    "remington",
-    "remsho",
-    "remstar",
-    "remtor",
-    "reporters",
-    "resko",
-    "rex",
-    "rexpel",
-    "rheinita",
-    "rheinmetall",
-    "rival",
-    "roberts",
-    "robotron",
-    "rocher",
-    "rochester",
-    "roebuck",
-    "rofa",
-    "roland",
-    "rooy",
-    "rover",
-    "roxy",
-    "roy",
-    "royal",
-    "rundstatler",
-    "sabaudia",
-    "sabb",
-    "saleem",
-    "salter",
-    "sampo",
-    "sarafan",
-    "saturn",
-    "saxonia",
-    "schade",
-    "schapiro",
-    "schreibi",
-    "scripta",
-    "sears",
-    "secor",
-    "selectric",
-    "selekta",
-    "senator",
-    "sense",
-    "senta",
-    "serd",
-    "shilling",
-    "shimade",
-    "shimer",
-    "sholes",
-    "shuang",
-    "siegfried",
-    "siemag",
-    "silma",
-    "silver",
-    "simplex",
-    "simtype",
-    "singer",
-    "smith",
-    "soemtron",
-    "sonja",
-    "speedwriter",
-    "sphinx",
-    "starlet",
-    "stearns",
-    "steel",
-    "stella",
-    "steno",
-    "sterling",
-    "stoewer",
-    "stolzenberg",
-    "stott",
-    "strangfeld",
-    "sture",
-    "stylotyp",
-    "sun",
-    "superba",
-    "superia",
-    "supermetall",
-    "surety",
-    "swintec",
-    "swissa",
-    "talbos",
-    "talleres",
-    "tatrapoint",
-    "taurus",
-    "taylorix",
-    "tell",
-    "tempotype",
-    "tippco",
-    "titania",
-    "tops",
-    "towa",
-    "toyo",
-    "tradition",
-    "transatlantic",
-    "traveller",
-    "trebla",
-    "triumph",
-    "turia",
-    "typatune",
-    "typen",
-    "typorium",
-    "ugro",
-    "ultima",
-    "unda",
-    "underwood",
-    "unica",
-    "unitype",
-    "ursula",
-    "utax",
-    "varityper",
-    "vasanta",
-    "vendex",
-    "venus",
-    "victor",
-    "victoria",
-    "video",
-    "viking",
-    "vira",
-    "virotyp",
-    "visigraph",
-    "vittoria",
-    "volcan",
-    "vornado",
-    "voss",
-    "vultur",
-    "waltons",
-    "wanamaker",
-    "wanderer",
-    "ward",
-    "warner",
-    "waterloo",
-    "waverley",
-    "wayne",
-    "webster",
-    "wedgefield",
-    "welco",
-    "wellington",
-    "wellon",
-    "weltblick",
-    "westphalia",
-    "wiedmer",
-    "williams",
-    "wilson",
-    "winkel",
-    "winsor",
-    "wizard",
-    "woodstock",
-    "woodwards",
-    "yatran",
-    "yost",
-    "zenit",
-    "zentronik",
-    "zeta",
-    "zeya",
+const ADJECTIVES: &[&str] = &[
+    "able", "agate", "agile", "alpine", "amber", "ample", "aqua", "arctic", "arid", "astral",
+    "autumn", "avid", "azure", "balmy", "birch", "bold", "boreal", "brave", "breezy", "brief",
+    "bright", "brisk", "broad", "bronze", "calm", "cerith", "civil", "clean", "clear", "clever",
+    "cobalt", "cool", "copper", "coral", "cozy", "crisp", "cubic", "cyan", "deft", "dense", "dewy",
+    "direct", "dusky", "dusty", "eager", "early", "earnest", "elder", "elfin", "equal", "even",
+    "exact", "faint", "fair", "fast", "fawn", "ferny", "fiery", "fine", "firm", "fleet", "floral",
+    "focal", "fond", "frank", "fresh", "frosty", "full", "gentle", "gilded", "glacial", "glad",
+    "glossy", "golden", "grand", "green", "gusty", "hale", "happy", "hardy", "hazel", "hearty",
+    "hilly", "humble", "hushed", "icy", "ideal", "inner", "iron", "ivory", "jade", "jovial",
+    "keen", "kind", "lapis", "leafy", "level", "light", "lilac", "limber", "lively", "local",
+    "lofty", "lucid", "lunar", "major", "maple", "mellow", "merry", "mild", "milky", "misty",
+    "modal", "modest", "mossy", "muted", "native", "naval", "neat", "nimble", "noble", "north",
+    "novel", "oaken", "ochre", "olive", "onyx", "opal", "open", "optic", "outer", "owed", "ozone",
+    "pale", "pastel", "pearl", "pecan", "peppy", "pilot", "placid", "plain", "plum", "plush",
+    "poised", "polar", "polished", "poplar", "prime", "proof", "proud", "pure", "quartz", "quick",
+    "quiet", "rapid", "raspy", "ready", "regal", "rooted", "rosy", "round", "royal", "ruby",
+    "ruddy", "russet", "rustic", "sage", "salty", "sandy", "satin", "scenic", "sedge", "serene",
+    "sharp", "sheer", "silky", "silver", "sleek", "smart", "smooth", "snowy", "solar", "solid",
+    "south", "spry", "stark", "steady", "steel", "steep", "still", "stoic", "stony", "stout",
+    "sturdy", "suede", "sunny", "supple", "sure", "swift", "tall", "tawny", "teal", "terse",
+    "thick", "tidal", "tidy", "timber", "topaz", "total", "trim", "tropic", "true", "tulip",
+    "upper", "urban", "valid", "vast", "velvet", "verde", "vivid", "vocal", "warm", "waxen",
+    "west", "whole", "wide", "wild", "wise", "witty", "woven", "young", "zealous", "zephyr",
+    "zesty", "zinc",
 ];
 
-/// Picks a typewriter name that isn't already taken by an existing branch.
-///
-/// Each entry in `existing_branches` is expected to be a full branch name
-/// like `"olivetti-a3f9b2c1"`. The prefix before the last `'-'` is treated
-/// as the taken typewriter name. Branches without a `'-'` are ignored.
+const NOUNS: &[&str] = &[
+    "anchor", "anvil", "arbor", "arch", "arrow", "atlas", "badge", "badger", "basin", "bay",
+    "beacon", "beam", "bell", "birch", "blade", "bloom", "bluff", "bolt", "bower", "breeze",
+    "bridge", "brook", "bunting", "cabin", "cairn", "canyon", "cape", "cedar", "chasm", "cliff",
+    "cloud", "clover", "coast", "cobble", "colt", "comet", "condor", "coral", "cove", "crane",
+    "crater", "creek", "crest", "curlew", "cypress", "dale", "dawn", "delta", "den", "dove",
+    "drake", "drift", "drum", "dune", "dusk", "eagle", "echo", "egret", "elk", "elm", "ember",
+    "falcon", "fawn", "fern", "ferry", "field", "finch", "fjord", "flame", "flint", "flower",
+    "forge", "fossil", "fox", "frost", "gale", "garnet", "gate", "gazelle", "geyser", "glade",
+    "glen", "gorge", "granite", "grove", "gull", "harbor", "hare", "haven", "hawk", "hazel",
+    "heath", "hedge", "heron", "hill", "hollow", "horizon", "ibis", "inlet", "isle", "ivy",
+    "jackal", "jasper", "juniper", "kestrel", "kinglet", "knoll", "lagoon", "lake", "lantern",
+    "larch", "lark", "laurel", "lava", "leaf", "ledge", "lily", "linden", "lodge", "loft", "lotus",
+    "lynx", "mantle", "maple", "marble", "marsh", "marten", "meadow", "merlin", "mesa", "mill",
+    "mint", "moon", "moose", "moss", "newt", "north", "nutmeg", "oak", "oasis", "obsidian",
+    "orbit", "orchid", "oriole", "osprey", "otter", "owl", "palm", "panther", "pass", "path",
+    "peak", "pebble", "pelican", "peony", "perch", "pier", "pine", "plover", "plume", "pond",
+    "poppy", "prairie", "prism", "puma", "quail", "quarry", "quartz", "rain", "rampart", "range",
+    "raven", "ravine", "reed", "reef", "ridge", "river", "robin", "rowan", "sage", "salmon",
+    "sequoia", "shore", "shrike", "sigma", "sky", "slate", "slope", "snow", "spark", "sparrow",
+    "spider", "spruce", "stag", "star", "stone", "stork", "storm", "stream", "summit", "swift",
+    "sycamore", "tern", "terrace", "thistle", "thorn", "thrush", "tide", "timber", "torch",
+    "tower", "trail", "trout", "tulip", "tundra", "vale", "valley", "veranda", "viper", "vista",
+    "vole", "walrus", "warbler", "willow", "wolf", "wren", "yew", "zenith",
+];
+
+/// Generates a branch name in `"adjective-noun"` format (e.g. `"swift-falcon"`).
 ///
-/// Returns `None` when every name in the pool is already taken.
-pub fn pick_typewriter_name(
-    existing_branches: &[&str],
-    rng: &mut impl Rng,
-) -> Option<&'static str> {
-    let disallowed: HashSet<&str> = existing_branches
-        .iter()
-        .filter_map(|branch| branch.rsplit_once('-').map(|(prefix, _)| prefix))
-        .collect();
+/// Tries up to 100 random combinations, skipping any name that already appears
+/// in `existing_branches`. Returns `None` if no unused name is found.
+pub fn generate_branch_name(existing_branches: &[&str], rng: &mut impl Rng) -> Option<String> {
+    let existing: HashSet<&str> = existing_branches.iter().copied().collect();
 
-    let available: Vec<&'static str> = TYPEWRITER_NAMES
-        .iter()
-        .copied()
-        .filter(|name| !disallowed.contains(name))
-        .collect();
+    for _ in 0..100 {
+        let adjective = ADJECTIVES[rng.random_range(0..ADJECTIVES.len())];
+        let noun = NOUNS[rng.random_range(0..NOUNS.len())];
+        let name = format!("{adjective}-{noun}");
 
-    if available.is_empty() {
-        return None;
+        if !existing.contains(name.as_str()) {
+            return Some(name);
+        }
     }
 
-    let index = rng.random_range(0..available.len());
-    Some(available[index])
-}
-
-/// Generates a branch name like `"olivetti-a3f9b2c1"` by picking a typewriter
-/// name that isn't already taken and appending an 8-character alphanumeric hash.
-///
-/// Returns `None` when every typewriter name in the pool is already taken.
-pub fn generate_branch_name(existing_branches: &[&str], rng: &mut impl Rng) -> Option<String> {
-    let typewriter_name = pick_typewriter_name(existing_branches, rng)?;
-    let hash: String = (0..8)
-        .map(|_| {
-            let idx: u8 = rng.random_range(0..36);
-            if idx < 10 {
-                (b'0' + idx) as char
-            } else {
-                (b'a' + idx - 10) as char
-            }
-        })
-        .collect();
-    Some(format!("{typewriter_name}-{hash}"))
+    None
 }
 
 #[cfg(test)]
@@ -713,134 +80,91 @@ mod tests {
     use rand::rngs::StdRng;
 
     #[gpui::test(iterations = 10)]
-    fn test_pick_typewriter_name_with_no_disallowed(mut rng: StdRng) {
-        let name = pick_typewriter_name(&[], &mut rng);
-        assert!(name.is_some());
-        assert!(TYPEWRITER_NAMES.contains(&name.unwrap()));
-    }
-
-    #[gpui::test(iterations = 10)]
-    fn test_pick_typewriter_name_excludes_taken_names(mut rng: StdRng) {
-        let branch_names = &["olivetti-abc12345", "selectric-def67890"];
-        let name = pick_typewriter_name(branch_names, &mut rng).unwrap();
-        assert_ne!(name, "olivetti");
-        assert_ne!(name, "selectric");
-    }
-
-    #[gpui::test]
-    fn test_pick_typewriter_name_all_taken(mut rng: StdRng) {
-        let branch_names: Vec<String> = TYPEWRITER_NAMES
-            .iter()
-            .map(|name| format!("{name}-00000000"))
-            .collect();
-        let branch_name_refs: Vec<&str> = branch_names.iter().map(|s| s.as_str()).collect();
-        let name = pick_typewriter_name(&branch_name_refs, &mut rng);
-        assert!(name.is_none());
-    }
-
-    #[gpui::test(iterations = 10)]
-    fn test_pick_typewriter_name_ignores_branches_without_hyphen(mut rng: StdRng) {
-        let branch_names = &["main", "develop", "feature"];
-        let name = pick_typewriter_name(branch_names, &mut rng);
-        assert!(name.is_some());
-        assert!(TYPEWRITER_NAMES.contains(&name.unwrap()));
+    fn test_generate_branch_name_format(mut rng: StdRng) {
+        let name = generate_branch_name(&[], &mut rng).unwrap();
+        let (adjective, noun) = name.split_once('-').expect("name should contain a hyphen");
+        assert!(
+            ADJECTIVES.contains(&adjective),
+            "{adjective:?} is not in ADJECTIVES"
+        );
+        assert!(NOUNS.contains(&noun), "{noun:?} is not in NOUNS");
     }
 
-    #[gpui::test(iterations = 10)]
-    fn test_generate_branch_name_format(mut rng: StdRng) {
-        let branch_name = generate_branch_name(&[], &mut rng).unwrap();
-        let (prefix, suffix) = branch_name.rsplit_once('-').unwrap();
-        assert!(TYPEWRITER_NAMES.contains(&prefix));
-        assert_eq!(suffix.len(), 8);
-        assert!(suffix.chars().all(|c| c.is_ascii_alphanumeric()));
+    #[gpui::test(iterations = 100)]
+    fn test_generate_branch_name_avoids_existing(mut rng: StdRng) {
+        let existing = &["swift-falcon", "calm-river", "bold-cedar"];
+        let name = generate_branch_name(existing, &mut rng).unwrap();
+        for &branch in existing {
+            assert_ne!(
+                name, branch,
+                "generated name should not match an existing branch"
+            );
+        }
     }
 
     #[gpui::test]
-    fn test_generate_branch_name_returns_none_when_exhausted(mut rng: StdRng) {
-        let branch_names: Vec<String> = TYPEWRITER_NAMES
+    fn test_generate_branch_name_returns_none_when_stuck(mut rng: StdRng) {
+        let all_names: Vec<String> = ADJECTIVES
             .iter()
-            .map(|name| format!("{name}-00000000"))
+            .flat_map(|adj| NOUNS.iter().map(move |noun| format!("{adj}-{noun}")))
             .collect();
-        let branch_name_refs: Vec<&str> = branch_names.iter().map(|s| s.as_str()).collect();
-        let result = generate_branch_name(&branch_name_refs, &mut rng);
+        let refs: Vec<&str> = all_names.iter().map(|s| s.as_str()).collect();
+        let result = generate_branch_name(&refs, &mut rng);
         assert!(result.is_none());
     }
 
-    #[gpui::test(iterations = 100)]
-    fn test_generate_branch_name_never_reuses_taken_prefix(mut rng: StdRng) {
-        let existing = &["olivetti-123abc", "selectric-def456"];
-        let branch_name = generate_branch_name(existing, &mut rng).unwrap();
-        let (prefix, _) = branch_name.rsplit_once('-').unwrap();
-        assert_ne!(prefix, "olivetti");
-        assert_ne!(prefix, "selectric");
-    }
+    #[test]
+    fn test_adjectives_are_valid() {
+        let mut seen = HashSet::default();
+        for &word in ADJECTIVES {
+            assert!(seen.insert(word), "duplicate entry in ADJECTIVES: {word:?}");
+        }
 
-    #[gpui::test(iterations = 100)]
-    fn test_generate_branch_name_avoids_multiple_taken_prefixes(mut rng: StdRng) {
-        let existing = &[
-            "olivetti-aaa11111",
-            "selectric-bbb22222",
-            "corona-ccc33333",
-            "remington-ddd44444",
-            "underwood-eee55555",
-        ];
-        let taken_prefixes: HashSet<&str> = existing
-            .iter()
-            .filter_map(|b| b.rsplit_once('-').map(|(prefix, _)| prefix))
-            .collect();
-        let branch_name = generate_branch_name(existing, &mut rng).unwrap();
-        let (prefix, _) = branch_name.rsplit_once('-').unwrap();
-        assert!(
-            !taken_prefixes.contains(prefix),
-            "generated prefix {prefix:?} collides with an existing branch"
-        );
-    }
+        for window in ADJECTIVES.windows(2) {
+            assert!(
+                window[0] < window[1],
+                "ADJECTIVES is not sorted: {0:?} should come before {1:?}",
+                window[0],
+                window[1],
+            );
+        }
 
-    #[gpui::test(iterations = 100)]
-    fn test_generate_branch_name_with_varied_hash_suffixes(mut rng: StdRng) {
-        let existing = &[
-            "olivetti-aaaaaaaa",
-            "olivetti-bbbbbbbb",
-            "olivetti-cccccccc",
-        ];
-        let branch_name = generate_branch_name(existing, &mut rng).unwrap();
-        let (prefix, _) = branch_name.rsplit_once('-').unwrap();
-        assert_ne!(
-            prefix, "olivetti",
-            "should avoid olivetti regardless of how many variants exist"
-        );
+        for &word in ADJECTIVES {
+            assert!(
+                !word.contains('-'),
+                "ADJECTIVES entry contains a hyphen: {word:?}"
+            );
+            assert!(
+                word.chars().all(|c| c.is_lowercase()),
+                "ADJECTIVES entry is not all lowercase: {word:?}"
+            );
+        }
     }
 
     #[test]
-    fn test_typewriter_names_are_valid() {
+    fn test_nouns_are_valid() {
         let mut seen = HashSet::default();
-        for &name in TYPEWRITER_NAMES {
-            assert!(
-                seen.insert(name),
-                "duplicate entry in TYPEWRITER_NAMES: {name:?}"
-            );
+        for &word in NOUNS {
+            assert!(seen.insert(word), "duplicate entry in NOUNS: {word:?}");
         }
 
-        for window in TYPEWRITER_NAMES.windows(2) {
+        for window in NOUNS.windows(2) {
             assert!(
-                window[0] <= window[1],
-                "TYPEWRITER_NAMES is not sorted: {0:?} should come after {1:?}",
-                window[1],
+                window[0] < window[1],
+                "NOUNS is not sorted: {0:?} should come before {1:?}",
                 window[0],
+                window[1],
             );
         }
 
-        for &name in TYPEWRITER_NAMES {
+        for &word in NOUNS {
             assert!(
-                !name.contains('-'),
-                "TYPEWRITER_NAMES entry contains a hyphen: {name:?}"
+                !word.contains('-'),
+                "NOUNS entry contains a hyphen: {word:?}"
             );
-        }
-
-        for &name in TYPEWRITER_NAMES {
             assert!(
-                name.chars().all(|c| c.is_lowercase() || !c.is_alphabetic()),
-                "TYPEWRITER_NAMES entry is not lowercase: {name:?}"
+                word.chars().all(|c| c.is_lowercase()),
+                "NOUNS entry is not all lowercase: {word:?}"
             );
         }
     }