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 { + 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); 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.