tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 83JSON 198635 Definiciones

feat(badu): app demo — cuaderno con grafo de enlaces y gravedad

CLI que siembra un cuaderno (cocina/jardín/oficina), imprime el grafo
de wiki-links (forward/backlinks, huérfanas, colgantes) y los
clústeres por gravedad semántica + vecinos + layout 2D.
cargo run -p badu.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

ea079a0b230d488998428ea617976779379070b0 · por sergio · (hace 5 meses) · padre d0a175a90 · ver el árbol en este commit

Delta semántico · 3

crates/apps/badu/src/main.rs

5 archivos · +166 −4

diff --git a/Cargo.lock b/Cargo.lock
index 97768db..a6493f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1363,6 +1363,14 @@ dependencies = [
  "windows-link 0.2.1",
 ]
 
+[[package]]
+name = "badu"
+version = "0.1.0"
+dependencies = [
+ "badu-core",
+ "badu-gravity",
+]
+
 [[package]]
 name = "badu-core"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index bf7d424..fa203a9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -242,6 +242,7 @@ members = [
     "crates/apps/dominium",
     "crates/apps/fana",
     "crates/apps/agorapura",
+    "crates/apps/badu",
 ]
 
 [workspace.package]
diff --git a/crates/apps/badu/Cargo.toml b/crates/apps/badu/Cargo.toml
new file mode 100644
index 0000000..8ce9de6
--- /dev/null
+++ b/crates/apps/badu/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "badu"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+authors.workspace = true
+publish.workspace = true
+description = "badu — demostración de la toma de notas: un cuaderno con wiki-links, backlinks, enlaces colgantes y clústeres por gravedad semántica."
+
+[[bin]]
+name = "badu"
+path = "src/main.rs"
+
+[dependencies]
+badu-core = { path = "../../modules/badu/badu-core" }
+badu-gravity = { path = "../../modules/badu/badu-gravity" }
diff --git a/crates/apps/badu/src/main.rs b/crates/apps/badu/src/main.rs
new file mode 100644
index 0000000..69b1db6
--- /dev/null
+++ b/crates/apps/badu/src/main.rs
@@ -0,0 +1,134 @@
+//! `badu` — demostración del cuaderno de notas.
+//!
+//! Siembra un cuaderno personal, imprime el grafo de wiki-links
+//! (forward-links, backlinks, huérfanas, enlaces colgantes) y luego la
+//! gravedad semántica: los clústeres por afinidad y los vecinos más
+//! cercanos de una nota.
+//!
+//! Los vectores semánticos van a mano —tres tópicos: cocina, jardín,
+//! oficina— para que el clustering se vea con claridad. En la app real
+//! los produce `verbo`. Corre con `cargo run -p badu`.
+
+use badu_core::{NoteId, NoteStore};
+use badu_gravity::{GravityConfig, SemanticField};
+
+/// Vector de tópico con un leve sesgo — notas del mismo tema quedan
+/// afines sin ser idénticas.
+fn topic(base: [f32; 3], nudge: f32) -> Vec<f32> {
+    vec![base[0] + nudge, base[1] + nudge * 0.3, base[2] - nudge * 0.2]
+}
+
+fn main() {
+    let cocina = [1.0, 0.0, 0.0];
+    let jardin = [0.0, 1.0, 0.0];
+    let oficina = [0.0, 0.0, 1.0];
+
+    let mut store = NoteStore::new();
+    let mut field = SemanticField::new();
+
+    // (título, cuerpo, etiquetas, vector de tópico)
+    let seed: [(&str, &str, &[&str], Vec<f32>); 7] = [
+        (
+            "Índice",
+            "mi cuaderno: [[Recetas de la abuela]], [[Jardín]] y [[Oficina]]",
+            &["meta"],
+            topic(cocina, 0.0),
+        ),
+        (
+            "Recetas de la abuela",
+            "sopa de auyama; ver también [[Lista del mercado]]",
+            &["cocina"],
+            topic(cocina, 0.05),
+        ),
+        (
+            "Lista del mercado",
+            "auyama, cilantro, pan; vuelve al [[Índice]]",
+            &["cocina"],
+            topic(cocina, 0.10),
+        ),
+        (
+            "Jardín",
+            "riego semanal; las [[Semillas de cilantro]] van en marzo",
+            &["jardín"],
+            topic(jardin, 0.05),
+        ),
+        (
+            "Semillas de cilantro",
+            "germinan en diez días",
+            &["jardín"],
+            topic(jardin, 0.10),
+        ),
+        (
+            "Oficina",
+            "[[Reunión del lunes]] y pendientes varios",
+            &["trabajo"],
+            topic(oficina, 0.05),
+        ),
+        (
+            "Diario sin enlaces",
+            "una nota suelta, no la enlaza nadie y enlaza a [[Algo Perdido]]",
+            &["personal"],
+            topic(oficina, 0.50),
+        ),
+    ];
+
+    let mut ids: Vec<(NoteId, String)> = Vec::new();
+    for (title, body, tags, vector) in seed {
+        let tags = tags.iter().map(|t| t.to_string()).collect();
+        let id = store.create(title, body, tags, 1_700_000_000);
+        field.insert(id, vector);
+        ids.push((id, title.to_string()));
+    }
+
+    let name = |id: NoteId| {
+        ids.iter()
+            .find(|(i, _)| *i == id)
+            .map(|(_, n)| n.as_str())
+            .unwrap_or("?")
+    };
+
+    println!("\n  badu · cuaderno de notas — {} notas\n", store.len());
+
+    println!("  grafo de enlaces:");
+    for note in store.iter() {
+        let fwd: Vec<&str> = store.forward_links(note.id).into_iter().map(name).collect();
+        let back: Vec<&str> = store.backlinks(note.id).into_iter().map(name).collect();
+        println!("    «{}»", note.title);
+        println!("       enlaza a  : {}", fmt_list(&fwd));
+        println!("       backlinks : {}", fmt_list(&back));
+    }
+
+    let orphans: Vec<&str> = store.orphans().iter().map(|n| n.title.as_str()).collect();
+    println!("\n  notas huérfanas (sin backlinks): {}", fmt_list(&orphans));
+    let dangling_owned = store.dangling_links();
+    let dangling: Vec<&str> = dangling_owned.iter().map(|s| s.as_str()).collect();
+    println!("  enlaces colgantes (destino inexistente): {}", fmt_list(&dangling));
+
+    println!("\n  gravedad semántica — clústeres (afinidad ≥ 0.85):");
+    for (n, cluster) in field.clusters(0.85).iter().enumerate() {
+        let titles: Vec<&str> = cluster.iter().map(|id| name(*id)).collect();
+        println!("    grupo {}: {}", n + 1, fmt_list(&titles));
+    }
+
+    let pivot = ids[1].0; // "Recetas de la abuela"
+    println!("\n  vecinos más afines a «{}»:", name(pivot));
+    for (id, score) in field.nearest(pivot, 3) {
+        println!("    {:.3}  {}", score, name(id));
+    }
+
+    let layout = field.gravity_layout(&GravityConfig::default());
+    println!("\n  layout 2D por gravedad ({} posiciones):", layout.len());
+    for p in &layout {
+        println!("    ({:7.1}, {:7.1})  {}", p.x, p.y, name(p.id));
+    }
+    println!();
+}
+
+/// Formatea una lista de nombres, o `—` si está vacía.
+fn fmt_list(items: &[&str]) -> String {
+    if items.is_empty() {
+        "—".to_string()
+    } else {
+        items.join(", ")
+    }
+}
diff --git a/crates/modules/badu/SDD.md b/crates/modules/badu/SDD.md
index 2a9a5e9..65b7647 100644
--- a/crates/modules/badu/SDD.md
+++ b/crates/modules/badu/SDD.md
@@ -35,7 +35,9 @@ notas por afinidad de significado.
 
 ## Estado
 
-`core` + `gravity` implementados y verdes (29 tests). **Pendiente**: las
-4 lentes visuales (lista, grafo, gravedad espacial, línea de tiempo),
-los «Susurros» (sugerencias vía `verbo`) y el frontend GPUI —
-separabilidad UI estricta, el núcleo ya es agnóstico.
+`core` + `gravity` implementados y verdes (29 tests). Demo CLI en
+`apps/badu` (`cargo run -p badu`): cuaderno sembrado con grafo de
+enlaces + clústeres por gravedad. **Pendiente**: las 4 lentes visuales
+(lista, grafo, gravedad espacial, línea de tiempo), los «Susurros»
+(sugerencias vía `verbo`) y el frontend GPUI — separabilidad UI
+estricta, el núcleo ya es agnóstico.