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 · ruwaq sergio · 2026-05-20T16:43:42Z (5 killa ñawpaq) · tayta d0a175a90 · kay commitpi sach'ata qhaway
Semántico sayk'uy · 3
crates/apps/badu/src/main.rs
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.