feat(0036d): promote kind-aware (Group → clear group_id)

NodeGroups window kind=Group ahora expone un boton SmallButton(TI_ARROW_UP)
por fila que saca la entidad del grupo (group_id = NULL) y dispara
reload del grafo. kind=Table mantiene el comportamiento de issue 0011.

- entity_ops: nueva op `entity_clear_group_id(db, id)` idempotente. Si
  la columna group_id no existe (BD pre-0035a) retorna true como no-op.
  Falla solo si la entidad no existe o SQLite revienta.
- views.cpp: extra columna "promote" en kind=Group, tooltip header
  diferenciado por kind, boton conectado a app.want_clear_group_id_entity.
- main.cpp: handler que ejecuta entity_clear_group_id, marca windows
  como dirty, llama reload_after_mutation y loguea
  `[node_groups] promoted X out of group`.
- gx-cli: flag `node update --clear-group-id` (booleano) y exposicion
  MCP en inputSchema + MCP_DISPATCH defaults para que el agente Echo
  pueda promover via tool calls.
- tests: 3 nuevos CLI (clear, idempotente, combinable con --name) y
  4 MCP (defaults, schema, dispatch end-to-end, idempotente).

WSL: 102 passed (95 base + 7).
Windows: 91 passed, 11 skipped (84 base + 7).

Refs: issues/0036d-promote-kind-aware.md
This commit is contained in:
2026-05-04 01:03:11 +02:00
parent 98e744ea4e
commit f0d8a5ad04
8 changed files with 311 additions and 5 deletions
+14
View File
@@ -1819,6 +1819,20 @@ static void render() {
g_app.want_demote_entity = false;
g_app.demote_entity_id.clear();
}
// Issue 0036d: promote out-of-group (kind=Group de NodeGroups window).
if (g_app.want_clear_group_id_entity
&& !g_app.clear_group_id_entity_id.empty()
&& !g_input_path.empty()) {
if (ge::entity_clear_group_id(g_input_path.c_str(),
g_app.clear_group_id_entity_id.c_str())) {
std::fprintf(stdout, "[node_groups] promoted %s out of group\n",
g_app.clear_group_id_entity_id.c_str());
for (auto& kv : g_app.node_groups_windows) kv.second.page_dirty = true;
reload_after_mutation();
}
g_app.want_clear_group_id_entity = false;
g_app.clear_group_id_entity_id.clear();
}
if (g_app.want_focus_entity && !g_app.focus_entity_id.empty()) {
for (int i = 0; i < g_graph.node_count; ++i) {
const char* sid = ge::entity_index_lookup(