chore(maestro): drop dead maestro-names crate

Nothing in the workspace depended on it — session handles are generated
client-side in web/src/names.ts, which carried its own copy of the
wordlists. Also drops two unused deps: rust-embed (no crate used it;
maestro-web embeds via include_dir) and urlencoding in maestro-cli.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-08-22 10:49:30 +01:00
co-authored by Claude Opus 5
parent c856333e4a
commit bf7def5cf1
7 changed files with 2 additions and 246 deletions
-4
View File
@@ -49,7 +49,6 @@ crates/
├── maestro/ # umbrella binary `maestro` (cli + daemon + tui, feature-gated) (NAP-245)
├── maestro-protocol/ # shared wire types (NAP-211)
├── maestro-config/ # shared on-disk config schema (NAP-233)
├── maestro-names/ # orchestra-themed session-handle generator (#23)
├── maestro-cli/ # `maestro list/spawn/attach/kill` dispatch (NAP-216)
├── maestro-daemon/ # `maestro daemon` — tmux wrapper + HTTP/WS API (NAP-212, NAP-214)
├── maestro-web/ # standalone `maestro-web` browser UI (NAP-241, NAP-249)
@@ -61,9 +60,6 @@ web/ # browser SPA (NAP-218)
See `docs/INSTALL.md` for end-user install instructions.
The dated design docs under `docs/superpowers/specs/` predate the single-host
refactor; they carry banners marking what no longer applies.
## Conventions
- **TDD by default.** Failing test first, then minimal impl. Commit test + impl together.
Generated
-8
View File
@@ -1077,7 +1077,6 @@ dependencies = [
"toml",
"tracing",
"tracing-subscriber",
"urlencoding",
"uuid",
]
@@ -1147,13 +1146,6 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "maestro-names"
version = "0.1.0"
dependencies = [
"rand 0.8.6",
]
[[package]]
name = "maestro-protocol"
version = "0.1.0"
-2
View File
@@ -18,13 +18,11 @@ dirs = "5"
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
libc = "0.2"
maestro-config = { path = "crates/maestro-config" }
maestro-names = { path = "crates/maestro-names" }
maestro-protocol = { path = "crates/maestro-protocol" }
maestro-tui = { path = "crates/maestro-tui" }
nix = { version = "0.29", default-features = false, features = ["term", "signal"] }
rand = "0.8"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] }
rust-embed = "8"
include_dir = "0.7"
mime_guess = "2"
bytes = "1"
-1
View File
@@ -28,7 +28,6 @@ tokio-tungstenite = { workspace = true, features = ["rustls-tls-webpki-roots"] }
toml.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
urlencoding = "2"
uuid.workspace = true
[dev-dependencies]
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "maestro-names"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
publish.workspace = true
description = "Orchestra-flavoured session-handle generator (adjective × instrument) shared across Maestro"
[dependencies]
rand.workspace = true
[dev-dependencies]
rand = { workspace = true, features = ["small_rng"] }
-214
View File
@@ -1,214 +0,0 @@
//! `maestro-names` — the shared session-handle generator.
//!
//! Maestro is a conductor's podium for every shell you care about, so the
//! handles it hands out are drawn from the orchestra pit: a musical adjective
//! (tempo, dynamics, articulation) paired with an instrument or musical form.
//! The result reads like a marking on a score — `allegro-cello`,
//! `crescendo-oboe`, `staccato-viola`.
//!
//! ## Determinism
//!
//! Every pure path takes an **injected** [`rand::Rng`]; nothing here reaches
//! for `thread_rng()` or the clock, so seeded tests are reproducible (a repo
//! invariant). [`generate_thread`] is the one impure convenience wrapper for
//! production callers that just want a name and don't care about the seed.
//!
//! The Rust generator is the source of truth. If the web UI wants an instant
//! client-side reroll it can mirror [`ADJECTIVES`] / [`NOUNS`] verbatim, but
//! the wordlists live here.
use rand::seq::SliceRandom;
use rand::Rng;
/// Musical adjectives — tempo, dynamics, and articulation markings.
pub const ADJECTIVES: &[&str] = &[
"allegro",
"andante",
"adagio",
"presto",
"vivace",
"largo",
"grave",
"lento",
"moderato",
"crescendo",
"staccato",
"legato",
"forte",
"dolce",
"agitato",
"cantabile",
"sostenuto",
"tremolo",
"rubato",
"marcato",
"brillante",
"maestoso",
"grazioso",
"espressivo",
"tranquillo",
"animato",
"spirito",
"sotto",
"vivo",
"feroce",
];
/// Musical nouns — instruments and forms from the concert hall.
pub const NOUNS: &[&str] = &[
"cello", "violin", "viola", "oboe", "flute", "clarinet", "bassoon", "trumpet", "trombone",
"tuba", "horn", "timpani", "harp", "piano", "organ", "lyre", "lute", "fugue", "sonata",
"octave", "chord", "cadence", "prelude", "rondo", "nocturne", "scherzo", "refrain", "coda",
"motif", "overture",
];
/// Generate a single `adjective-noun` handle from an injected RNG.
///
/// ```
/// use rand::SeedableRng;
/// let mut rng = rand::rngs::SmallRng::seed_from_u64(7);
/// let name = maestro_names::generate(&mut rng);
/// assert!(name.contains('-'));
/// ```
pub fn generate<R: Rng + ?Sized>(rng: &mut R) -> String {
// Both lists are compile-time non-empty (asserted in tests), so the
// `choose` calls cannot return `None`.
let adj = ADJECTIVES.choose(rng).expect("ADJECTIVES is non-empty");
let noun = NOUNS.choose(rng).expect("NOUNS is non-empty");
format!("{adj}-{noun}")
}
/// Generate a handle that isn't already taken.
///
/// Draws `adjective-noun` candidates from `rng`; if `is_taken` rejects one, a
/// numeric uniqueness suffix is appended and incremented (`-2`, `-3`, …) until
/// a free handle is found. The bare form is preferred — the suffix only
/// appears on collision, matching the score-marking aesthetic.
///
/// `is_taken` is the single source of "already used"; given the same RNG and
/// the same predicate the output is fully deterministic.
pub fn generate_unique<R, F>(rng: &mut R, is_taken: F) -> String
where
R: Rng + ?Sized,
F: Fn(&str) -> bool,
{
let base = generate(rng);
if !is_taken(&base) {
return base;
}
// Start at 2 so the first duplicate reads `name`, `name-2`, `name-3` …
for suffix in 2u64.. {
let candidate = format!("{base}-{suffix}");
if !is_taken(&candidate) {
return candidate;
}
}
unreachable!("u64 suffix space is effectively inexhaustible")
}
/// Impure convenience wrapper: generate a handle from the thread RNG.
///
/// For production callers that just want a name. Tests and any deterministic
/// path should use [`generate`] / [`generate_unique`] with a seeded RNG.
pub fn generate_thread() -> String {
generate(&mut rand::thread_rng())
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::SmallRng;
use rand::SeedableRng;
use std::collections::HashSet;
fn seeded(seed: u64) -> SmallRng {
SmallRng::seed_from_u64(seed)
}
#[test]
fn wordlists_are_non_empty() {
assert!(!ADJECTIVES.is_empty());
assert!(!NOUNS.is_empty());
}
#[test]
fn wordlists_have_no_duplicates() {
assert_eq!(
ADJECTIVES.iter().collect::<HashSet<_>>().len(),
ADJECTIVES.len(),
"duplicate adjective"
);
assert_eq!(
NOUNS.iter().collect::<HashSet<_>>().len(),
NOUNS.len(),
"duplicate noun"
);
}
#[test]
fn generate_is_adjective_dash_noun() {
let mut rng = seeded(1);
let name = generate(&mut rng);
let (adj, noun) = name.split_once('-').expect("must contain a single dash");
assert!(ADJECTIVES.contains(&adj), "{adj} not an adjective");
assert!(NOUNS.contains(&noun), "{noun} not a noun");
}
#[test]
fn generation_is_deterministic_under_seed() {
// Same seed → identical sequence; this is the core repo invariant.
let mut a = seeded(42);
let mut b = seeded(42);
for _ in 0..50 {
assert_eq!(generate(&mut a), generate(&mut b));
}
}
#[test]
fn different_seeds_diverge() {
// Not a guarantee for a single draw, but across a run the sequences
// must differ — otherwise the RNG isn't actually being threaded.
let mut a = seeded(1);
let mut b = seeded(2);
let seq_a: Vec<_> = (0..30).map(|_| generate(&mut a)).collect();
let seq_b: Vec<_> = (0..30).map(|_| generate(&mut b)).collect();
assert_ne!(seq_a, seq_b);
}
#[test]
fn unique_returns_bare_name_when_free() {
let mut rng = seeded(3);
let name = generate_unique(&mut rng, |_| false);
assert!(
!name.ends_with("-2"),
"no suffix expected when nothing taken"
);
assert!(name.contains('-'));
}
#[test]
fn unique_appends_incrementing_suffix_on_collision() {
// Reserve the bare name and the first two suffixed variants; the
// generator must walk past them to `-4`.
let mut probe = seeded(9);
let base = generate(&mut probe);
let taken: HashSet<String> = [base.clone(), format!("{base}-2"), format!("{base}-3")]
.into_iter()
.collect();
let mut rng = seeded(9);
let name = generate_unique(&mut rng, |n| taken.contains(n));
assert_eq!(name, format!("{base}-4"));
}
#[test]
fn unique_suffix_starts_at_two() {
let mut probe = seeded(11);
let base = generate(&mut probe);
let taken: HashSet<String> = [base.clone()].into_iter().collect();
let mut rng = seeded(11);
let name = generate_unique(&mut rng, |n| taken.contains(n));
assert_eq!(name, format!("{base}-2"));
}
}
+2 -3
View File
@@ -1,7 +1,6 @@
// Client-side session-handle generator for the spawn form's instant reroll
// (#23/#27). Mirrors the wordlists in `crates/maestro-names/src/lib.rs` verbatim
// — the Rust generator is the source of truth, but the web wants a zero-round-
// trip reroll, so the lists are duplicated here. Keep them in sync.
// (#23/#27). The spawn form rerolls without a round trip, so the wordlists
// live here; nothing server-side generates handles.
/** Musical adjectives — tempo, dynamics, and articulation markings. */
const ADJECTIVES = [