refactor(nouser): labels de Mónada con 2 componentes del path
Resuelve la fricción visual de monorepos donde múltiples Mónadas
quedaban con label "src" (ambiguo). Nueva función label_from_path
toma los últimos hasta 2 componentes normales del path:
$ nouser scan crates/core
[01K..] brahman-admin/src card=5
[01K..] brahman-handshake/src card=6
[01K..] ente-brain/src card=11
[01K..] ente-kernel/src card=4
Tests añadidos: label_from_root_only_one_component,
label_from_deep_path_takes_last_two. Tests existentes actualizados
con los nuevos labels (proj/src en lugar de src).
22 tests en nouser-core (era 20, +2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
794884a90f0ecd8493cd29ac4e32f14f83d1c4f2 · por Sergio · 2026-05-08T19:28:19Z (hace 5 meses) · padre 11fc95629 · ver el árbol en este commit
Delta semántico · 6
crates/modules/nouser/core/src/cluster.rs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a6196a..b47dad6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,22 @@ ratio/diff ver `git show <sha>`.
## 2026-05-08
+### refactor(nouser): labels de Mónada con 2 componentes del path
+Resuelve la fricción visual de monorepos donde múltiples Mónadas se
+llamaban "src". Nueva función `label_from_path` toma los últimos hasta
+2 componentes normales del path y los une con `/`.
+
+ $ nouser scan crates/core
+ [01K..] brahman-admin/src card=5
+ [01K..] brahman-handshake/src card=6
+ [01K..] ente-brain/src card=11
+ [01K..] ente-kernel/src card=4
+ ...
+
+Tests añadidos: `label_from_root_only_one_component`,
+`label_from_deep_path_takes_last_two`. Tests existentes actualizados
+con los nuevos labels.
+
### feat(nouser): Phase D-2 — proveedor Nous real (LLM) detrás de feature flag
Cierra el ciclo del módulo Nous: existe un proveedor que produce
embeddings reales con un modelo LLM, mientras que `cargo build` sin
diff --git a/crates/modules/nouser/core/src/cluster.rs b/crates/modules/nouser/core/src/cluster.rs
index 62c5ac6..2ddc432 100644
--- a/crates/modules/nouser/core/src/cluster.rs
+++ b/crates/modules/nouser/core/src/cluster.rs
@@ -46,11 +46,7 @@ pub fn by_directory(files: &[FileEntry], min_files: usize) -> Vec<MonadManifest>
}
fn build_monad(parent: &std::path::Path, group: &[&FileEntry]) -> MonadManifest {
- let label = parent
- .file_name()
- .and_then(|s| s.to_str())
- .unwrap_or("unnamed")
- .to_string();
+ let label = label_from_path(parent);
let keywords = top_extensions(group, 5);
let lens = pick_lens(group);
@@ -75,6 +71,27 @@ fn build_monad(parent: &std::path::Path, group: &[&FileEntry]) -> MonadManifest
m
}
+/// Construye un label legible tomando los últimos hasta 2 componentes
+/// del path. Esto desambigua `src/` repetidos en monorepos: en lugar
+/// de 5 Mónadas con label "src", quedan "ente-zero/src", "ente-brain/src",
+/// etc. Para directorios shallow (root o un nivel), cae al
+/// `file_name()` simple.
+fn label_from_path(p: &std::path::Path) -> String {
+ let normals: Vec<&str> = p
+ .components()
+ .filter_map(|c| match c {
+ std::path::Component::Normal(s) => s.to_str(),
+ _ => None,
+ })
+ .collect();
+ if normals.is_empty() {
+ return "unnamed".to_string();
+ }
+ let take = normals.len().min(2);
+ let start = normals.len() - take;
+ normals[start..].join("/")
+}
+
fn build_summary(parent: &std::path::Path, group: &[&FileEntry], keywords: &[String]) -> String {
let path_str = parent.display();
let n = group.len();
@@ -173,8 +190,10 @@ mod tests {
let monads = by_directory(&files, 3);
assert_eq!(monads.len(), 2);
let labels: std::collections::BTreeSet<_> = monads.iter().map(|m| &m.label).collect();
- assert!(labels.iter().any(|l| l.as_str() == "src"));
- assert!(labels.iter().any(|l| l.as_str() == "docs"));
+ // Phase B: labels usan los últimos 2 componentes del path para
+ // desambiguar (proj/src vs proj/docs en lugar de src vs docs).
+ assert!(labels.iter().any(|l| l.as_str() == "proj/src"));
+ assert!(labels.iter().any(|l| l.as_str() == "proj/docs"));
}
#[test]
@@ -188,7 +207,20 @@ mod tests {
// min=3 → /proj/single solo no se promueve, /proj/sub sí.
let monads = by_directory(&files, 3);
assert_eq!(monads.len(), 1);
- assert_eq!(monads[0].label, "sub");
+ assert_eq!(monads[0].label, "proj/sub");
+ }
+
+ #[test]
+ fn label_from_root_only_one_component() {
+ // Un solo componente normal en el path → no hay "padre" útil.
+ let p = std::path::Path::new("/onlyone");
+ assert_eq!(label_from_path(p), "onlyone");
+ }
+
+ #[test]
+ fn label_from_deep_path_takes_last_two() {
+ let p = std::path::Path::new("/a/b/c/d/e");
+ assert_eq!(label_from_path(p), "d/e");
}
#[test]