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