feat(shuma): shuma-infer — motor de inferencia de intenciones

Detecta patrones de comandos repetidos en el historial: ventana
deslizante sobre las firmas de binarios (sólo ventanas 100%
exitosas), abstracción de argumentos variables (cd /a vs cd /b →
cd <…>), patrones maximales, puntaje por largo × frecuencia.
10 tests, agnóstico y determinista.

El shell lo corre tras cada comando terminado y promueve el patrón
más fuerte a un grupo « ...» en el panel [RUN] — la rehidratación
que convierte la repetición orgánica en una receta de un clic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-20 19:12:47 +00:00
parent 3bfb42f1cc
commit 37ea535cb7
7 changed files with 436 additions and 1 deletions
+1
View File
@@ -16,6 +16,7 @@ path = "src/main.rs"
shuma-line = { path = "../../modules/shuma/shuma-line" }
shuma-session = { path = "../../modules/shuma/shuma-session" }
shuma-exec = { path = "../../modules/shuma/shuma-exec" }
shuma-infer = { path = "../../modules/shuma/shuma-infer" }
shuma-sysmon = { path = "../../modules/shuma/shuma-sysmon" }
nahual-theme = { path = "../../modules/nahual/libs/theme" }
nahual-launcher = { path = "../../modules/nahual/libs/launcher" }
+31
View File
@@ -356,13 +356,44 @@ impl Shell {
}
}
}
let finished = self.active.iter().any(|(_, h)| h.is_finished());
self.active.retain(|(_, h)| !h.is_finished());
if finished {
// Al cerrarse un comando, el motor de inferencia revisa si
// emergió un patrón repetido y lo promueve a un grupo.
self.infer_patterns();
}
if changed {
self.scroll.scroll_to_bottom();
}
changed
}
/// Corre el motor de inferencia sobre el historial y promueve el
/// patrón más fuerte a un grupo reutilizable (rehidratación).
fn infer_patterns(&mut self) {
let records: Vec<shuma_infer::CommandRecord> = self
.session
.history()
.iter()
.map(|r| {
shuma_infer::CommandRecord::parse(
&r.line,
&r.cwd,
r.status == RunStatus::Ok,
)
})
.collect();
let patterns =
shuma_infer::detect_patterns(&records, &shuma_infer::InferConfig::default());
if let Some(top) = patterns.first() {
let name = format!("{}", top.suggested_name());
if self.session.group(&name).is_none() {
self.session.save_group(name, top.example.clone());
}
}
}
/// Resuelve el destino de un `cd` contra el cwd de la sesión.
fn resolve_cd(&self, arg: &str) -> Result<String, String> {
let home = std::env::var("HOME").unwrap_or_else(|_| "/".into());