Files
brahman/docs/changelog/minga.md
T
sergio e77a32f4d6 feat(minga): minga-vfs — proyecta el repo como filesystem FUSE
minga-vfs deja de ser un stub: monta el repositorio direccionado por
contenido como un filesystem FUSE de sólo lectura. roots/<hash> da el
código fuente reconstruido (formato normalizado) de cada raíz del MST;
cas/<hash> resuelve cualquier hash bajo demanda como S-expression.

Capas separadas: render (SemanticNode→texto, puro) + source (contrato
NodeSource, backends sled/memoria) + fs (único módulo con fuser).
Nuevo subcomando `minga mount <punto>`. Dep fuser 0.15 sin libfuse-dev
(default-features = false). 14 tests nuevos, sin regresión en minga-cli.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 13:23:44 +00:00

369 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog — minga (semantic_dht)
### feat(minga-vfs): VFS FUSE — el repo como filesystem de sólo lectura
`minga-vfs` deja de ser un stub de 2 LOC: ahora monta el repositorio
direccionado por contenido como un filesystem FUSE navegable con
`ls`/`cat`/`grep`/un editor, sin exponer `sled`.
Layout del montaje:
- `README` — explicación del propio VFS.
- `roots/<hash>` — un archivo por raíz del MST (cada archivo
ingerido), con el código fuente **reconstruido** en formato
normalizado. `ls roots/` las enumera todas.
- `cas/<hash>` — la S-expression del subárbol de cualquier hash.
Este directorio NO se enumera (decenas de miles de nodos) pero
`cat cas/<hash>` resuelve cualquier hash conocido: es el "blob
por hash bajo demanda" que la SDD pedía.
Arquitectura, separando por capas (ver feedback de separabilidad):
- **`render`** — `SemanticNode` → texto. Lógica pura, sin IO ni
FUSE. `render_source` re-imprime los tokens hoja con un
pretty-printer mínimo consciente de llaves (indenta tras `{`,
corta tras `;`); `render_sexp` vuelca el árbol literal. El render
de fuente es normalizado, no byte-exacto: el AST descartó
whitespace y comentarios al ingerir. Python, cuya estructura vive
en la indentación, sale como secuencia de tokens — esperado.
- **`source`** — el contrato `NodeSource` (`roots()` + `get()`) y
`reconstruct()`. Backends: `RepoSource` sobre el `PersistentRepo`
de `sled`, `MemSource` en RAM (tests / índices efímeros).
- **`fs`** — único módulo acoplado a `fuser`: implementa la
`Filesystem` trait. Inodos estáticos reservados (1-4), dinámicos
desde 16 con tabla `(padre, nombre) → inodo` estable. Cada
archivo se renderiza y cachea en el primer `lookup`.
Dep nueva (workspace + minga-vfs):
- `fuser = "0.15"` con `default-features = false`: prescinde de
`pkg-config`/`libfuse-dev` en build; el montaje pasa a Rust puro
(helper `fusermount3` en runtime).
CLI: nuevo subcomando `minga mount <punto>` (`cmd_mount`), que abre
el repo, lo envuelve en `RepoSource` y bloquea hasta
`fusermount -u <punto>`. Pide la passphrase igual que `status`.
Tests: **14** nuevos en minga-vfs (9 unit: render con llaves,
S-expression con campos/escape, parseo de hash de 64 hex, roundtrip
de `MemSource`; 5 integration: ingest→reconstruct lossless a nivel
AST, `roots/` lista todo, vistas source/sexp, deduplicación de
subárboles). Los 10 tests de minga-cli intactos (sin regresión).
Pendientes:
- `cas/` cachea contenido sin tope: navegar un repo gigante crece
en RAM. Un LRU sería el siguiente paso si molesta.
- Sin extensión en los nombres (`roots/<hash>`): no guardamos el
lenguaje original, así que el editor no autodetecta sintaxis.
### feat(minga-explorer): listings de items recientes en cada stat card
Iter 12. Hasta ahora minga-explorer mostraba sólo counts (3
números). Ahora cada stat card muestra también un sample de los
items dentro: hashes truncados de los 5 primeros nodos AST
(con su `kind`), atestaciones (`content_hash ← did_short`) y
claves MST. Mucho más útil para debugging que el "tengo N items".
Cambios en `minga-explorer`:
- **`RepoSnapshot` extendido** con 3 nuevos `Vec<...>`:
- `recent_nodes: Vec<(String, String)>``(hash_short, kind)`.
- `recent_attestations: Vec<(String, String)>`
`(content_hash_short, did_short)`.
- `recent_mst_keys: Vec<String>``hash_short`.
- Cap de 5 items por sección via `RECENT_LIMIT` const.
- **`load_snapshot` itera los stores** y toma los primeros 5
items via `iter().filter_map(Result::ok).take(RECENT_LIMIT)`.
Errores per-item se silencian (`filter_map`) — el dashboard
muestra lo que pueda; un par de items corruptos no debería
tirar el panel.
- **`short_hash(&str)` helper local**: trunca un hex a sus
primeros 12 chars (48 bits, distintivo dentro de un repo
single-machine).
- **`stat_card` extendido**: nuevo arg `recent_items: &[String]`.
Si no está vacío, agrega un sub-header `"recent (N de TOTAL):"`
+ una linea por item. Cada line es texto pequeño (`px(11)`)
con el color principal del theme — visualmente queda como
monospace listing aunque no use mono font (no hay todavía
en el theme).
Tests: 2 → **4** (+2 sanity de los nuevos defaults + del
`short_hash`).
Beneficio operativo:
- Después de `minga ingest archivo.rs`, el explorer muestra
inmediatamente los hashes de los nodos AST creados, qué `kind`
tienen, y las atestaciones firmadas — sin necesitar `minga
status` o queries SQL.
- "5 de 247" da contexto del crecimiento sin overwhelm de
listing completo.
Limitación documentada: los "recent" no son cronológicos — sled
ordena lexicográfico por hash. Para timeline real, agregar
timestamp al schema (cambio breaking del store, scope futuro).
### feat(minga-explorer): nueva app dashboard del repo Minga sobre stack yahweh
Iter 11. Cierra el último frente identificado: integración del
módulo Minga (VCS semántico P2P) al ecosistema GUI. Antes Minga
sólo tenía CLI (`minga init/status/ingest/listen/sync/watch`).
Ahora hay un **dashboard GPUI** que muestra los counts del repo
en vivo, sobre el mismo stack themed que las otras dos apps
explorer.
Crate nuevo `crates/apps/minga-explorer/`:
- **Deps**: `minga-store` (para `PersistentRepo::open`) +
`yahweh-theme` + `yahweh-widget-{banner,card,theme-switcher}`.
Sin `minga-cli` (no necesita prompts de passphrase) ni
`minga-core` (counts no requieren parsear AST).
- **Lectura sin passphrase**: el `PersistentRepo` se abre directo
desde `<repo>/repo` sled. Los counts (`nodes.len()`,
`attestations.len()`, `mst.len()`) son lectura pública. Para el
DID se sigue necesitando `minga status` (CLI con passphrase).
- **Refresh por polling cada 2s**: mismo pattern que
`nakui-explorer`/`nouser-explorer`.
- **3 stat cards** una por dimensión:
- Nodos AST (cyan) — fragments parseados del código.
- Atestaciones (verde) — firmas Ed25519 sobre los nodos.
- Claves MST (purple) — entradas del Merkle Search Tree.
- **Helper `stat_card(cx, label, value, description, accent, ...)`**:
fabrica una card con border-l colored + label tenue + número
grande (`px(28)`) + descripción. Reutilizable.
- **Header**: título dinámico (`Repo: <path> · reload <ms> ms`)
+ theme switcher derecha.
- **Error banner**: themed Banner::Error si el repo no abre.
- 2 tests: `load_snapshot_errors_on_missing_dir` (msg claro
cuando el dir no existe) + sanity del `RepoSnapshot::default`.
Workspace: nueva entry en `members[]`.
Smoke run del binario verificado: bootstrap completo OK, panic
esperado en open_window por falta de display.
Beneficio operativo:
- Un usuario corre `minga init` + `minga ingest archivo.rs` desde
CLI, después abre `minga-explorer` y ve los counts crecer en
vivo cuando ingiere más archivos.
- Comparte theme switcher con `nakui-explorer` y
`nouser-explorer` — cualquier preset elegido se aplica
visualmente igual cross-app.
- `minga` deja de ser sólo CLI; gana presencia GUI sin tocar
el resto del módulo.
Apps GUI integradas al stack themed: **4** (nakui-ui, nakui-explorer,
nouser-explorer, minga-explorer).
### feat(minga-core): α-hashing per-language para Python, TypeScript, JavaScript, Go
Cierra el último pendiente fundamentado del CHANGELOG. Cada lenguaje
soportado por `minga` tiene ahora su propio profile α-equivalente —
dos versiones del mismo programa que difieren sólo en nombres de
variables ligadas producen el mismo hash, no importa el lenguaje.
Refactorings tipo "rename variable" no inflan el storage del repo
en ningún dialecto.
Refactor de `alpha.rs` (639 LOC) a módulo `alpha/`:
- **`alpha/common.rs`**: primitives compartidos (TAG_*, write_kind_and_field,
emit_leaf_marker, emit_binder_body, emit_identifier_ref, push_identifier_name).
Garantiza que el formato wire del hash sea bit-equivalente entre
todos los profiles.
- **`alpha/rust.rs`**: la lógica de Rust (movida desde alpha.rs sin
cambios funcionales).
- **`alpha/python.rs`**: nuevo.
- **`alpha/ecmascript.rs`**: nuevo (cubre TypeScript + JavaScript;
comparten la mayoría de los kinds).
- **`alpha/go.rs`**: nuevo.
- **`alpha/mod.rs`**: re-exporta `hash_node_alpha` (Rust legacy) +
expone `hash_alpha_with(dialect, node)` que despacha al profile
correspondiente.
Cobertura per-language:
**Python** (`def`, `lambda`, `for`, comprehensions, `with`):
- `function_definition` y `lambda`: parámetros (incluyendo
typed_parameter, default_parameter, *args, **kwargs) introducen
binders al body. El nombre de la función NO es α-anónimo.
- `for_statement`: el `left` (identifier o tuple) introduce
binder(es) al body.
- `list_comprehension`, `set_comprehension`, `dictionary_comprehension`,
`generator_expression`: cada `for_in_clause` añade binders que
viven en el body + clauses siguientes (semántica de scope
incremental de Python).
- `with_statement`: `as` introduce binder al body (recursando en
`as_pattern_target` para llegar al identifier).
**ECMAScript** (TS + JS):
- `function_declaration`, `function_expression`, `method_definition`,
`generator_function_*`: parameters → body. Soporta TS
`required_parameter` y `optional_parameter` (`x: number`,
`x?: number`).
- `arrow_function`: tanto `(x, y) => body` como shorthand `x => body`.
- `statement_block`: `lexical_declaration` (let/const) y
`variable_declaration` (var) introducen binders al resto del block.
- `for_in_statement` (cubre `for-of` y `for-in`): `left` → body.
- `for_statement` (C-style): initializer (lexical decl) introduce
binders al condition + increment + body.
- `catch_clause`: parameter → body.
**Go**:
- `function_declaration`, `method_declaration`, `func_literal` (closure):
`parameter_list` → body. `parameter_declaration` con varios names
agrupa varios binders bajo un mismo tipo (`a, b int`).
- `block`: `short_var_declaration` (`x := ...`) introduce binders
al resto.
- `for_statement` con `range_clause` (`for k, v := range m`): los
identifiers del `left` son binders al body.
- `for_statement` con `for_clause` (C-style): initializer → body.
- `if_statement` con `initializer` (`if x := init(); x > 0`):
binders viven en condition + consequence + alternative.
API:
- `hash_alpha_with(Dialect, &SemanticNode) -> ContentHash`
despacho per-dialect.
- `hash_node_alpha(&SemanticNode) -> ContentHash` — alias histórico
asume Rust (back-compat).
Tests: 26 nuevos en `tests/alpha_polyglot.rs`:
- Python (9): def rename, lambda rename, for-loop rename, list comp,
nested comp, with rename, function name matters, iterable name
matters, sanity negativo (operación distinta → hash distinto).
- JS/TS (9): function rename, function name matters, arrow rename,
arrow shorthand rename, let/const rename, for-of rename, classic
for rename, catch rename, TS typed param rename, TS type matters.
- Go (6): function rename, function name matters, short var decl
rename, range_clause rename, if-init rename, func_literal closure
rename.
- Cross-language (1): mismos shapes en lenguajes distintos
producen hashes distintos (sanity para evitar colisiones).
141 tests verdes en minga-core (115 antes; +26 polyglot). Refactor
sin regresión: 36 α-Rust tests siguen pasando.
Pendientes que quedan en Minga (orden de prioridad):
- `minga-vfs` FUSE (proyecto independiente, scope grande).
- Cobertura adicional por-lenguaje: Python class, JS destructuring,
Go type_switch, etc. — cada uno pequeño, no urgente.
### feat(minga-core): cierre del α-hashing de Rust — if let, while let, let-else, or-pattern, let-chains
Cierra los 5 pendientes documentados en `alpha.rs`. El hash
α-equivalente ahora es estable bajo renombre de TODOS los binders
de Rust, no sólo los del MVP (parámetros, let, for, match arms).
Pendientes cerrados:
- **`if let X = expr { ... }`**: `if_expression` detecta
`let_condition` en su `condition`, recolecta los binders del
pattern, los propaga al `consequence`. El `alternative` (else)
NO los ve.
- **`while let X = expr { ... }`**: simétrico al if-let, propaga al
`body`. El `condition` mismo se evalúa con scope previo (los
binders todavía no existen).
- **`let-else`**: `let_declaration` con campo `alternative`. El
alternative se procesa con el scope ANTES de los binders (ya
funcionaba: `feed_let` llama `feed` para no-pattern children con
el scope actual; `feed_block` extiende el scope DESPUÉS de
`feed_let`).
- **`or_pattern`**: en `pat1 | pat2` (Rust enforcement: ambos lados
introducen los mismos binders). Para emit, recorremos cada lado
con `feed_pattern`. Para collect, sólo el primer lado — iterar
todos duplicaría binders y rompería los índices de Bruijn.
- **let-chains** (`if let X = a && let Y = b { ... }`): el
`collect_let_condition_binders` recursa en el árbol del condition,
capturando todos los `let_condition` (vivan dentro de
`binary_expression` u otros nodos). Ambos binders quedan en scope
del consequence.
Helper nuevo: `feed_let_condition` para que el `pattern` del
let_condition pase por `feed_pattern` (que distingue binders vs
constructors). Sin esto, los identifiers del pattern se hasheaban
como variables libres y `Some(x)``Some(y)` aún teniendo el
mismo significado.
Tests: 6 nuevos en `tests/alpha_invariants.rs`:
- `alpha_if_let_binder_rename_invariant`
- `alpha_if_let_else_does_not_see_binder` (sanity)
- `alpha_while_let_binder_rename_invariant`
- `alpha_let_else_binder_rename_invariant`
- `alpha_or_pattern_binder_rename_invariant`
- `alpha_let_chain_binders_propagate_to_consequence`
- `alpha_if_let_does_not_collide_with_unrelated_program` (negativo:
programas distintos NO deben dar el mismo hash)
36 tests α verdes (eran 30). 115 tests totales en minga-core.
Lo que esto significa: el hash α-equivalente de Rust en minga es
**completo** — cubre todos los constructos del lenguaje que
introducen bindings. Dos versiones del mismo programa que difieren
sólo en nombres de variables (incluyendo en `if let`, `while let`,
`or-pattern`, etc.) producen el mismo hash y por tanto la misma
identidad CAS. Refactorings del tipo "rename variable" no inflan
el storage del repo.
Pendientes futuros:
- α-hashing per-language (Python: def/lambda/comprehensions; TS/JS:
function/arrow/destructuring; Go: func/closure). Cada uno
requiere conocimiento profundo de la gramática y tests
exhaustivos. Plantilla genérica no aplica.
### feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go
Minga deja de ser Rust-only. Cualquiera de los cinco dialectos
(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea
estructuralmente, sincroniza por DHT como cualquier nodo. La
auto-detección por extensión hace que `minga ingest archivo.py` o
`.ts` o `.go` "simplemente funcione".
API nueva en `minga_core::parse`:
- Funciones por dialecto (~6 LOC c/u sobre el `parse_with` común):
`python`, `typescript`, `javascript`, `go`. Más la `rust` existente.
- Enum `Dialect` con `parse(source) -> Result<SemanticNode>` y
`name() -> &'static str` para logging.
- `detect_by_extension(ext) -> Option<Dialect>`: mapea `rs`/`py`/
`pyi`/`ts`/`js`/`mjs`/`cjs`/`go` (case-insensitive). `None` para
extensiones desconocidas — el caller decide si es error o se
ignora silente.
Wire en `minga-cli`:
- `cmd_ingest` deja de hardcodear `parse::rust` — usa
`detect_dialect(file)?.parse(...)`. Acepta `.py`, `.ts`, `.js`,
`.go` además de `.rs`.
- `initial_scan` y `cmd_watch` cambian `is_rs_file``is_supported_source`
para incluir todas las extensiones soportadas en el filtro.
- `CliError::UnsupportedLanguage { path, extension }` nuevo, con
mensaje que lista las extensiones reconocidas.
Notas sobre hashing:
- El AST normalizado (`SemanticNode`) descarta whitespace y
comentarios — propiedad universal de tree-sitter (extras). Misma
lógica para los 5 dialectos.
- Hashing **estructural** (`cas::hash_node`) funciona para todos:
dos textos semánticamente equivalentes-por-estructura producen el
mismo hash. NO α-equivalente (las variables ligadas distinguen).
- Hashing **α-equivalente** (`alpha::hash_node_alpha`) sigue siendo
Rust-only: cada lenguaje tiene reglas distintas para qué es
binder vs. constructor (def/lambda en Python, arrow functions en
TS/JS, func + closures en Go). Implementación per-language queda
como work futuro — requiere conocimiento profundo de cada
gramática y no se plantilla genéricamente.
- Sanity test `structural_hash_distinguishes_languages` verifica
que `x = 1` parseado como Python ≠ parseado como JavaScript: las
gramáticas no comparten kinds y los hashes salen distintos.
Importante para evitar colisiones cuando el mismo source se
ingresa bajo dialectos distintos.
Deps nuevas (workspace + minga-core):
- `tree-sitter-python = "0.23"`
- `tree-sitter-typescript = "0.23"` (sólo el modo `LANGUAGE_TYPESCRIPT`,
no TSX — bumpear a TSX es agregar otro dialecto cuando se necesite).
- `tree-sitter-javascript = "0.23"`
- `tree-sitter-go = "0.23"`
Tests:
- 9 nuevos en `parse::tests`: parse básico para los 5 dialectos
(Python con type hints, TS con tipos, JS sin tipos, Go con
package declaration), `detect_by_extension` canonical +
case-insensitive, `dialect_name`, `structural_hash_distinguishes_languages`.
- 108 tests verdes en minga-core (39 → 48 unit + integration tests
pre-existentes intactos).
- 10 tests verdes en minga-cli (sin regresión en el path Rust;
el refactor a `detect_dialect`/`is_supported_source` no rompe
nada).
Pendientes futuros del changelog:
- α-hashing per-language (Python: def/lambda/comprehensions;
TS/JS: function/arrow/destructuring; Go: func/closure). Trabajo
profundo, scope independiente.
- α-Rust pendientes documentados en `alpha.rs`: `if let`,
`while let`, `let-else`, let-chains, `or_pattern` con bindings.