tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definiciones

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 · (hace 5 meses) · padre 11fc95629 · ver el árbol en este commit

Delta semántico · 6

crates/modules/nouser/core/src/cluster.rs

2 archivos · +56 −8

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]