1# Changelog — minga (semantic_dht)
2
3### feat(minga-vfs): VFS FUSE — el repo como filesystem de sólo lectura
4`minga-vfs` deja de ser un stub de 2 LOC: ahora monta el repositorio
5direccionado por contenido como un filesystem FUSE navegable con
6`ls`/`cat`/`grep`/un editor, sin exponer `sled`.
7
8Layout del montaje:
9- `README` — explicación del propio VFS.
10- `roots/<hash>` — un archivo por raíz del MST (cada archivo
11 ingerido), con el código fuente **reconstruido** en formato
12 normalizado. `ls roots/` las enumera todas.
13- `cas/<hash>` — la S-expression del subárbol de cualquier hash.
14 Este directorio NO se enumera (decenas de miles de nodos) pero
15 `cat cas/<hash>` resuelve cualquier hash conocido: es el "blob
16 por hash bajo demanda" que la SDD pedía.
17
18Arquitectura, separando por capas (ver feedback de separabilidad):
19- **`render`** — `SemanticNode` → texto. Lógica pura, sin IO ni
20 FUSE. `render_source` re-imprime los tokens hoja con un
21 pretty-printer mínimo consciente de llaves (indenta tras `{`,
22 corta tras `;`); `render_sexp` vuelca el árbol literal. El render
23 de fuente es normalizado, no byte-exacto: el AST descartó
24 whitespace y comentarios al ingerir. Python, cuya estructura vive
25 en la indentación, sale como secuencia de tokens — esperado.
26- **`source`** — el contrato `NodeSource` (`roots()` + `get()`) y
27 `reconstruct()`. Backends: `RepoSource` sobre el `PersistentRepo`
28 de `sled`, `MemSource` en RAM (tests / índices efímeros).
29- **`fs`** — único módulo acoplado a `fuser`: implementa la
30 `Filesystem` trait. Inodos estáticos reservados (1-4), dinámicos
31 desde 16 con tabla `(padre, nombre) → inodo` estable. Cada
32 archivo se renderiza y cachea en el primer `lookup`.
33
34Dep nueva (workspace + minga-vfs):
35- `fuser = "0.15"` con `default-features = false`: prescinde de
36 `pkg-config`/`libfuse-dev` en build; el montaje pasa a Rust puro
37 (helper `fusermount3` en runtime).
38
39CLI: nuevo subcomando `minga mount <punto>` (`cmd_mount`), que abre
40el repo, lo envuelve en `RepoSource` y bloquea hasta
41`fusermount -u <punto>`. Pide la passphrase igual que `status`.
42
43Tests: **14** nuevos en minga-vfs (9 unit: render con llaves,
44S-expression con campos/escape, parseo de hash de 64 hex, roundtrip
45de `MemSource`; 5 integration: ingest→reconstruct lossless a nivel
46AST, `roots/` lista todo, vistas source/sexp, deduplicación de
47subárboles). Los 10 tests de minga-cli intactos (sin regresión).
48
49Pendientes:
50- `cas/` cachea contenido sin tope: navegar un repo gigante crece
51 en RAM. Un LRU sería el siguiente paso si molesta.
52- Sin extensión en los nombres (`roots/<hash>`): no guardamos el
53 lenguaje original, así que el editor no autodetecta sintaxis.
54
55### feat(minga-explorer): listings de items recientes en cada stat card
56Iter 12. Hasta ahora minga-explorer mostraba sólo counts (3
57números). Ahora cada stat card muestra también un sample de los
58items dentro: hashes truncados de los 5 primeros nodos AST
59(con su `kind`), atestaciones (`content_hash ← did_short`) y
60claves MST. Mucho más útil para debugging que el "tengo N items".
61
62Cambios en `minga-explorer`:
63- **`RepoSnapshot` extendido** con 3 nuevos `Vec<...>`:
64 - `recent_nodes: Vec<(String, String)>` — `(hash_short, kind)`.
65 - `recent_attestations: Vec<(String, String)>` —
66 `(content_hash_short, did_short)`.
67 - `recent_mst_keys: Vec<String>` — `hash_short`.
68 - Cap de 5 items por sección via `RECENT_LIMIT` const.
69- **`load_snapshot` itera los stores** y toma los primeros 5
70 items via `iter().filter_map(Result::ok).take(RECENT_LIMIT)`.
71 Errores per-item se silencian (`filter_map`) — el dashboard
72 muestra lo que pueda; un par de items corruptos no debería
73 tirar el panel.
74- **`short_hash(&str)` helper local**: trunca un hex a sus
75 primeros 12 chars (48 bits, distintivo dentro de un repo
76 single-machine).
77- **`stat_card` extendido**: nuevo arg `recent_items: &[String]`.
78 Si no está vacío, agrega un sub-header `"recent (N de TOTAL):"`
79 + una linea por item. Cada line es texto pequeño (`px(11)`)
80 con el color principal del theme — visualmente queda como
81 monospace listing aunque no use mono font (no hay todavía
82 en el theme).
83
84Tests: 2 → **4** (+2 sanity de los nuevos defaults + del
85`short_hash`).
86
87Beneficio operativo:
88- Después de `minga ingest archivo.rs`, el explorer muestra
89 inmediatamente los hashes de los nodos AST creados, qué `kind`
90 tienen, y las atestaciones firmadas — sin necesitar `minga
91 status` o queries SQL.
92- "5 de 247" da contexto del crecimiento sin overwhelm de
93 listing completo.
94
95Limitación documentada: los "recent" no son cronológicos — sled
96ordena lexicográfico por hash. Para timeline real, agregar
97timestamp al schema (cambio breaking del store, scope futuro).
98
99### feat(minga-explorer): nueva app dashboard del repo Minga sobre stack yahweh
100Iter 11. Cierra el último frente identificado: integración del
101módulo Minga (VCS semántico P2P) al ecosistema GUI. Antes Minga
102sólo tenía CLI (`minga init/status/ingest/listen/sync/watch`).
103Ahora hay un **dashboard GPUI** que muestra los counts del repo
104en vivo, sobre el mismo stack themed que las otras dos apps
105explorer.
106
107Crate nuevo `crates/apps/minga-explorer/`:
108- **Deps**: `minga-store` (para `PersistentRepo::open`) +
109 `yahweh-theme` + `yahweh-widget-{banner,card,theme-switcher}`.
110 Sin `minga-cli` (no necesita prompts de passphrase) ni
111 `minga-core` (counts no requieren parsear AST).
112- **Lectura sin passphrase**: el `PersistentRepo` se abre directo
113 desde `<repo>/repo` sled. Los counts (`nodes.len()`,
114 `attestations.len()`, `mst.len()`) son lectura pública. Para el
115 DID se sigue necesitando `minga status` (CLI con passphrase).
116- **Refresh por polling cada 2s**: mismo pattern que
117 `nakui-explorer`/`nouser-explorer`.
118- **3 stat cards** una por dimensión:
119 - Nodos AST (cyan) — fragments parseados del código.
120 - Atestaciones (verde) — firmas Ed25519 sobre los nodos.
121 - Claves MST (purple) — entradas del Merkle Search Tree.
122- **Helper `stat_card(cx, label, value, description, accent, ...)`**:
123 fabrica una card con border-l colored + label tenue + número
124 grande (`px(28)`) + descripción. Reutilizable.
125- **Header**: título dinámico (`Repo: <path> · reload <ms> ms`)
126 + theme switcher derecha.
127- **Error banner**: themed Banner::Error si el repo no abre.
128- 2 tests: `load_snapshot_errors_on_missing_dir` (msg claro
129 cuando el dir no existe) + sanity del `RepoSnapshot::default`.
130
131Workspace: nueva entry en `members[]`.
132
133Smoke run del binario verificado: bootstrap completo OK, panic
134esperado en open_window por falta de display.
135
136Beneficio operativo:
137- Un usuario corre `minga init` + `minga ingest archivo.rs` desde
138 CLI, después abre `minga-explorer` y ve los counts crecer en
139 vivo cuando ingiere más archivos.
140- Comparte theme switcher con `nakui-explorer` y
141 `nouser-explorer` — cualquier preset elegido se aplica
142 visualmente igual cross-app.
143- `minga` deja de ser sólo CLI; gana presencia GUI sin tocar
144 el resto del módulo.
145
146Apps GUI integradas al stack themed: **4** (nakui-ui, nakui-explorer,
147nouser-explorer, minga-explorer).
148
149### feat(minga-core): α-hashing per-language para Python, TypeScript, JavaScript, Go
150Cierra el último pendiente fundamentado del CHANGELOG. Cada lenguaje
151soportado por `minga` tiene ahora su propio profile α-equivalente —
152dos versiones del mismo programa que difieren sólo en nombres de
153variables ligadas producen el mismo hash, no importa el lenguaje.
154Refactorings tipo "rename variable" no inflan el storage del repo
155en ningún dialecto.
156
157Refactor de `alpha.rs` (639 LOC) a módulo `alpha/`:
158- **`alpha/common.rs`**: primitives compartidos (TAG_*, write_kind_and_field,
159 emit_leaf_marker, emit_binder_body, emit_identifier_ref, push_identifier_name).
160 Garantiza que el formato wire del hash sea bit-equivalente entre
161 todos los profiles.
162- **`alpha/rust.rs`**: la lógica de Rust (movida desde alpha.rs sin
163 cambios funcionales).
164- **`alpha/python.rs`**: nuevo.
165- **`alpha/ecmascript.rs`**: nuevo (cubre TypeScript + JavaScript;
166 comparten la mayoría de los kinds).
167- **`alpha/go.rs`**: nuevo.
168- **`alpha/mod.rs`**: re-exporta `hash_node_alpha` (Rust legacy) +
169 expone `hash_alpha_with(dialect, node)` que despacha al profile
170 correspondiente.
171
172Cobertura per-language:
173
174**Python** (`def`, `lambda`, `for`, comprehensions, `with`):
175- `function_definition` y `lambda`: parámetros (incluyendo
176 typed_parameter, default_parameter, *args, **kwargs) introducen
177 binders al body. El nombre de la función NO es α-anónimo.
178- `for_statement`: el `left` (identifier o tuple) introduce
179 binder(es) al body.
180- `list_comprehension`, `set_comprehension`, `dictionary_comprehension`,
181 `generator_expression`: cada `for_in_clause` añade binders que
182 viven en el body + clauses siguientes (semántica de scope
183 incremental de Python).
184- `with_statement`: `as` introduce binder al body (recursando en
185 `as_pattern_target` para llegar al identifier).
186
187**ECMAScript** (TS + JS):
188- `function_declaration`, `function_expression`, `method_definition`,
189 `generator_function_*`: parameters → body. Soporta TS
190 `required_parameter` y `optional_parameter` (`x: number`,
191 `x?: number`).
192- `arrow_function`: tanto `(x, y) => body` como shorthand `x => body`.
193- `statement_block`: `lexical_declaration` (let/const) y
194 `variable_declaration` (var) introducen binders al resto del block.
195- `for_in_statement` (cubre `for-of` y `for-in`): `left` → body.
196- `for_statement` (C-style): initializer (lexical decl) introduce
197 binders al condition + increment + body.
198- `catch_clause`: parameter → body.
199
200**Go**:
201- `function_declaration`, `method_declaration`, `func_literal` (closure):
202 `parameter_list` → body. `parameter_declaration` con varios names
203 agrupa varios binders bajo un mismo tipo (`a, b int`).
204- `block`: `short_var_declaration` (`x := ...`) introduce binders
205 al resto.
206- `for_statement` con `range_clause` (`for k, v := range m`): los
207 identifiers del `left` son binders al body.
208- `for_statement` con `for_clause` (C-style): initializer → body.
209- `if_statement` con `initializer` (`if x := init(); x > 0`):
210 binders viven en condition + consequence + alternative.
211
212API:
213- `hash_alpha_with(Dialect, &SemanticNode) -> ContentHash` —
214 despacho per-dialect.
215- `hash_node_alpha(&SemanticNode) -> ContentHash` — alias histórico
216 asume Rust (back-compat).
217
218Tests: 26 nuevos en `tests/alpha_polyglot.rs`:
219- Python (9): def rename, lambda rename, for-loop rename, list comp,
220 nested comp, with rename, function name matters, iterable name
221 matters, sanity negativo (operación distinta → hash distinto).
222- JS/TS (9): function rename, function name matters, arrow rename,
223 arrow shorthand rename, let/const rename, for-of rename, classic
224 for rename, catch rename, TS typed param rename, TS type matters.
225- Go (6): function rename, function name matters, short var decl
226 rename, range_clause rename, if-init rename, func_literal closure
227 rename.
228- Cross-language (1): mismos shapes en lenguajes distintos
229 producen hashes distintos (sanity para evitar colisiones).
230
231141 tests verdes en minga-core (115 antes; +26 polyglot). Refactor
232sin regresión: 36 α-Rust tests siguen pasando.
233
234Pendientes que quedan en Minga (orden de prioridad):
235- `minga-vfs` FUSE (proyecto independiente, scope grande).
236- Cobertura adicional por-lenguaje: Python class, JS destructuring,
237 Go type_switch, etc. — cada uno pequeño, no urgente.
238
239### feat(minga-core): cierre del α-hashing de Rust — if let, while let, let-else, or-pattern, let-chains
240Cierra los 5 pendientes documentados en `alpha.rs`. El hash
241α-equivalente ahora es estable bajo renombre de TODOS los binders
242de Rust, no sólo los del MVP (parámetros, let, for, match arms).
243
244Pendientes cerrados:
245- **`if let X = expr { ... }`**: `if_expression` detecta
246 `let_condition` en su `condition`, recolecta los binders del
247 pattern, los propaga al `consequence`. El `alternative` (else)
248 NO los ve.
249- **`while let X = expr { ... }`**: simétrico al if-let, propaga al
250 `body`. El `condition` mismo se evalúa con scope previo (los
251 binders todavía no existen).
252- **`let-else`**: `let_declaration` con campo `alternative`. El
253 alternative se procesa con el scope ANTES de los binders (ya
254 funcionaba: `feed_let` llama `feed` para no-pattern children con
255 el scope actual; `feed_block` extiende el scope DESPUÉS de
256 `feed_let`).
257- **`or_pattern`**: en `pat1 | pat2` (Rust enforcement: ambos lados
258 introducen los mismos binders). Para emit, recorremos cada lado
259 con `feed_pattern`. Para collect, sólo el primer lado — iterar
260 todos duplicaría binders y rompería los índices de Bruijn.
261- **let-chains** (`if let X = a && let Y = b { ... }`): el
262 `collect_let_condition_binders` recursa en el árbol del condition,
263 capturando todos los `let_condition` (vivan dentro de
264 `binary_expression` u otros nodos). Ambos binders quedan en scope
265 del consequence.
266
267Helper nuevo: `feed_let_condition` para que el `pattern` del
268let_condition pase por `feed_pattern` (que distingue binders vs
269constructors). Sin esto, los identifiers del pattern se hasheaban
270como variables libres y `Some(x)` ≠ `Some(y)` aún teniendo el
271mismo significado.
272
273Tests: 6 nuevos en `tests/alpha_invariants.rs`:
274- `alpha_if_let_binder_rename_invariant`
275- `alpha_if_let_else_does_not_see_binder` (sanity)
276- `alpha_while_let_binder_rename_invariant`
277- `alpha_let_else_binder_rename_invariant`
278- `alpha_or_pattern_binder_rename_invariant`
279- `alpha_let_chain_binders_propagate_to_consequence`
280- `alpha_if_let_does_not_collide_with_unrelated_program` (negativo:
281 programas distintos NO deben dar el mismo hash)
282
28336 tests α verdes (eran 30). 115 tests totales en minga-core.
284
285Lo que esto significa: el hash α-equivalente de Rust en minga es
286**completo** — cubre todos los constructos del lenguaje que
287introducen bindings. Dos versiones del mismo programa que difieren
288sólo en nombres de variables (incluyendo en `if let`, `while let`,
289`or-pattern`, etc.) producen el mismo hash y por tanto la misma
290identidad CAS. Refactorings del tipo "rename variable" no inflan
291el storage del repo.
292
293Pendientes futuros:
294- α-hashing per-language (Python: def/lambda/comprehensions; TS/JS:
295 function/arrow/destructuring; Go: func/closure). Cada uno
296 requiere conocimiento profundo de la gramática y tests
297 exhaustivos. Plantilla genérica no aplica.
298
299### feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go
300Minga deja de ser Rust-only. Cualquiera de los cinco dialectos
301(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea
302estructuralmente, sincroniza por DHT como cualquier nodo. La
303auto-detección por extensión hace que `minga ingest archivo.py` o
304`.ts` o `.go` "simplemente funcione".
305
306API nueva en `minga_core::parse`:
307- Funciones por dialecto (~6 LOC c/u sobre el `parse_with` común):
308 `python`, `typescript`, `javascript`, `go`. Más la `rust` existente.
309- Enum `Dialect` con `parse(source) -> Result<SemanticNode>` y
310 `name() -> &'static str` para logging.
311- `detect_by_extension(ext) -> Option<Dialect>`: mapea `rs`/`py`/
312 `pyi`/`ts`/`js`/`mjs`/`cjs`/`go` (case-insensitive). `None` para
313 extensiones desconocidas — el caller decide si es error o se
314 ignora silente.
315
316Wire en `minga-cli`:
317- `cmd_ingest` deja de hardcodear `parse::rust` — usa
318 `detect_dialect(file)?.parse(...)`. Acepta `.py`, `.ts`, `.js`,
319 `.go` además de `.rs`.
320- `initial_scan` y `cmd_watch` cambian `is_rs_file` → `is_supported_source`
321 para incluir todas las extensiones soportadas en el filtro.
322- `CliError::UnsupportedLanguage { path, extension }` nuevo, con
323 mensaje que lista las extensiones reconocidas.
324
325Notas sobre hashing:
326- El AST normalizado (`SemanticNode`) descarta whitespace y
327 comentarios — propiedad universal de tree-sitter (extras). Misma
328 lógica para los 5 dialectos.
329- Hashing **estructural** (`cas::hash_node`) funciona para todos:
330 dos textos semánticamente equivalentes-por-estructura producen el
331 mismo hash. NO α-equivalente (las variables ligadas distinguen).
332- Hashing **α-equivalente** (`alpha::hash_node_alpha`) sigue siendo
333 Rust-only: cada lenguaje tiene reglas distintas para qué es
334 binder vs. constructor (def/lambda en Python, arrow functions en
335 TS/JS, func + closures en Go). Implementación per-language queda
336 como work futuro — requiere conocimiento profundo de cada
337 gramática y no se plantilla genéricamente.
338- Sanity test `structural_hash_distinguishes_languages` verifica
339 que `x = 1` parseado como Python ≠ parseado como JavaScript: las
340 gramáticas no comparten kinds y los hashes salen distintos.
341 Importante para evitar colisiones cuando el mismo source se
342 ingresa bajo dialectos distintos.
343
344Deps nuevas (workspace + minga-core):
345- `tree-sitter-python = "0.23"`
346- `tree-sitter-typescript = "0.23"` (sólo el modo `LANGUAGE_TYPESCRIPT`,
347 no TSX — bumpear a TSX es agregar otro dialecto cuando se necesite).
348- `tree-sitter-javascript = "0.23"`
349- `tree-sitter-go = "0.23"`
350
351Tests:
352- 9 nuevos en `parse::tests`: parse básico para los 5 dialectos
353 (Python con type hints, TS con tipos, JS sin tipos, Go con
354 package declaration), `detect_by_extension` canonical +
355 case-insensitive, `dialect_name`, `structural_hash_distinguishes_languages`.
356- 108 tests verdes en minga-core (39 → 48 unit + integration tests
357 pre-existentes intactos).
358- 10 tests verdes en minga-cli (sin regresión en el path Rust;
359 el refactor a `detect_dialect`/`is_supported_source` no rompe
360 nada).
361
362Pendientes futuros del changelog:
363- α-hashing per-language (Python: def/lambda/comprehensions;
364 TS/JS: function/arrow/destructuring; Go: func/closure). Trabajo
365 profundo, scope independiente.
366- α-Rust pendientes documentados en `alpha.rs`: `if let`,
367 `while let`, `let-else`, let-chains, `or_pattern` con bindings.
368
minga.md
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)
- 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.