refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG

Reorganización física de crates/:
- core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/
- shared/ (3 crates) se redistribuye en protocol/ e init/
- lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/

Renames de proyectos:
- shipote → shuma (runtime de sandboxes)
- nouser → akasha (explorador de Mónadas)
- yahweh → nahual (motor GPUI, antes ui_engine/)
- lapaloma → pineal (data-viz agnóstica)

Fraccionamiento UI → core agnóstico:
- vista-core (DeckState + snap, 175 LOC, 5 tests verdes)
- barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes)
- vista-web y barra-web ahora son thin DOM bindings

Documentación nueva:
- 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat
  + 10 módulos + apps/
- docs/STATUS.md con cifras reales por proyecto
- docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas)
- CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets)

Automatización:
- scripts/reorg.py — script idempotente que: git mv directorios, renombra
  package names, recomputa path = refs, reescribe imports rust, actualiza
  workspace Cargo.toml. Soporta --dry-run.
- scripts/split-changelog.py — particiona CHANGELOG por componente.

Validación:
- cargo check --workspace pasa (124 crates + 2 nuevos cores).
- 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-19 14:48:34 +00:00
parent 86fb6ae20b
commit 550c98f275
375 changed files with 8512 additions and 7155 deletions
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "akasha-nous"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
publish.workspace = true
description = "Nouser — protocolo Nous: contrato JSON line-delimited entre nouser-core y los proveedores de embeddings (mock o LLM real)."
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
+196
View File
@@ -0,0 +1,196 @@
//! `akasha-nous` — el contrato del proveedor de embeddings.
//!
//! Define el wire-format compartido entre `akasha-core` (consumidor) y
//! cualquier implementación de Nous (mock determinista o LLM real). El
//! protocolo es **line-delimited JSON** sobre Unix socket: cada conexión
//! envía una request, recibe una response, y cierra. Single-shot por
//! conexión, igual al admin de brahman.
//!
//! ## Contrato
//!
//! ```text
//! C → S: {"kind":"embed_file","payload":{...}}\n
//! S → C: {"embedding":[...],"model":"mock-pseudo-32d","elapsed_ms":1}\n
//! ```
//!
//! En caso de error:
//!
//! ```text
//! S → C: {"error":"unsupported kind"}\n
//! ```
//!
//! ## Por qué un crate aparte
//!
//! El consumidor (akasha-core) y el proveedor (akasha-nous-mock,
//! akasha-nous-real) deben acordar en types EXACTOS. Tener el contrato
//! en su crate evita que cada lado declare structs paralelos que se
//! desincronizan. Si bumpeás el wire, bumpeás aquí.
//!
//! ## Swap por priority_contexts
//!
//! Cuando existan dos proveedores (mock-nous y real-nous), ambos declaran
//! el mismo `flow.output: { name: "embed-result", type: ... }` y
//! `flow.input: "embed-request"`. El broker brahman los matchea contra
//! los consumidores; el `priority_offset` per-contexto del Card hace que
//! mock-nous gane en `test` y real-nous en `prod`. akasha-core sólo
//! consume el flow, sin saber cuál implementación corre.
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms)]
use serde::{Deserialize, Serialize};
use thiserror::Error;
// =====================================================================
// Wire types
// =====================================================================
/// Request al proveedor Nous.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbedRequest {
pub kind: RequestKind,
pub payload: serde_json::Value,
}
/// Tipo de request. El payload se interpreta según el kind.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RequestKind {
/// payload = `EmbedFilePayload` (path + metadata mínima).
EmbedFile,
/// payload = `EmbedTextPayload` (string libre).
EmbedText,
/// payload = `{}`. Devuelve `PingResponse`.
Ping,
}
/// Payload para `EmbedFile`. Es la información mínima que el proveedor
/// necesita para producir un embedding de archivo determinista.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbedFilePayload {
pub path: String,
pub extension: Option<String>,
pub size: u64,
/// `mtime` en ms desde UNIX_EPOCH.
pub mtime_ms: u64,
}
/// Payload para `EmbedText`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbedTextPayload {
pub text: String,
}
/// Response exitosa con un embedding.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbedResponse {
/// Vector. Su longitud depende del modelo (mock=32, llama=384, etc.).
pub embedding: Vec<f32>,
/// Identificador del modelo que produjo el embedding (útil para logs
/// y para invalidar caches al cambiar de proveedor).
pub model: String,
/// Tiempo de cómputo en ms (proveedor lo reporta).
pub elapsed_ms: u64,
}
/// Response a Ping. Útil para health-checks y discovery.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PingResponse {
pub model: String,
pub embed_dim: u32,
}
/// Error retornado por el proveedor en lugar de una response normal.
#[derive(Debug, Clone, Serialize, Deserialize, Error)]
#[error("nous: {error}")]
pub struct ErrorResponse {
pub error: String,
}
// =====================================================================
// Transport
// =====================================================================
pub mod transport {
use std::path::PathBuf;
/// Variable de entorno para sobreescribir la ruta del socket.
pub const SOCKET_ENV: &str = "NOUSER_NOUS_SOCKET";
/// Nombre genérico del socket cuando hay un solo proveedor.
pub const SOCKET_NAME: &str = "akasha-nous.sock";
/// Ruta canónica al socket cuando un único proveedor está activo
/// (consumidores que no quieren elegir).
pub fn default_socket_path() -> PathBuf {
if let Ok(p) = std::env::var(SOCKET_ENV) {
return PathBuf::from(p);
}
runtime_base().join(SOCKET_NAME)
}
/// Ruta default para un proveedor identificado (`"mock"`, `"real"`,
/// etc). Permite que mock y real coexistan sin clash de socket.
/// `NOUSER_NOUS_SOCKET` igual override esta función si está set.
pub fn provider_socket_path(provider: &str) -> PathBuf {
if let Ok(p) = std::env::var(SOCKET_ENV) {
return PathBuf::from(p);
}
runtime_base().join(format!("akasha-nous-{}.sock", provider))
}
fn runtime_base() -> PathBuf {
std::env::var_os("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(std::env::temp_dir)
}
}
// =====================================================================
// Names compartidos para el broker brahman
// =====================================================================
/// Nombre del flow output del proveedor (entrada del consumidor).
pub const FLOW_EMBED_RESULT: &str = "embed-result";
/// Nombre del flow input del proveedor (salida del consumidor).
pub const FLOW_EMBED_REQUEST: &str = "embed-request";
/// Tipo del flow: el wire es JSON serializado, así que el TypeRef
/// declarado en la Card es `primitive::json`.
pub const FLOW_TYPE_NAME: &str = "json";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_roundtrip_json() {
let req = EmbedRequest {
kind: RequestKind::EmbedFile,
payload: serde_json::to_value(EmbedFilePayload {
path: "/x/y.rs".into(),
extension: Some("rs".into()),
size: 1024,
mtime_ms: 1_700_000_000_000,
})
.unwrap(),
};
let s = serde_json::to_string(&req).unwrap();
let parsed: EmbedRequest = serde_json::from_str(&s).unwrap();
assert_eq!(parsed.kind, RequestKind::EmbedFile);
}
#[test]
fn response_roundtrip() {
let resp = EmbedResponse {
embedding: vec![0.1, 0.2, 0.3],
model: "mock-pseudo-32d".into(),
elapsed_ms: 1,
};
let s = serde_json::to_string(&resp).unwrap();
let parsed: EmbedResponse = serde_json::from_str(&s).unwrap();
assert_eq!(parsed.model, "mock-pseudo-32d");
assert_eq!(parsed.embedding.len(), 3);
}
}