f0d8a5ad04
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
166 lines
6.7 KiB
C++
166 lines
6.7 KiB
C++
#pragma once
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
// Operaciones CRUD sobre operations.db (entities + relations) y deteccion
|
|
// heuristica de tipo a partir de texto libre. Pensado para que la toolbar y
|
|
// el menu contextual del viewport puedan modificar el grafo y luego pedir
|
|
// reload (issue 0049g flow).
|
|
//
|
|
// Convencion edge labels: si el caller no pasa nombre de relacion, se usa
|
|
// k_default_relation_name = "RELATED_TO". Los enrichers deben pasar siempre
|
|
// un nombre semantico (ej: "EXTRACTED_FROM", "RESOLVES_TO", ...).
|
|
|
|
namespace ge {
|
|
|
|
constexpr const char* k_default_relation_name = "RELATED_TO";
|
|
|
|
enum DetectedType {
|
|
DT_TEXT = 0,
|
|
DT_EMAIL,
|
|
DT_IP_ADDRESS,
|
|
DT_URL,
|
|
DT_DOMAIN,
|
|
DT_PHONE,
|
|
};
|
|
|
|
DetectedType detect_type(const char* text);
|
|
const char* detected_type_name(DetectedType dt);
|
|
|
|
// Inserta una entidad nueva. Si type_ref es NULL/vacio se infiere via
|
|
// detect_type(name). Genera un id unico ("<type>_<unix_ms>"). Devuelve el id
|
|
// en out_id (caller-owned buffer >= 64). Retorna false si SQLite falla.
|
|
bool entity_insert(const char* db_path,
|
|
const char* name,
|
|
const char* type_ref,
|
|
char* out_id, size_t out_id_n);
|
|
|
|
bool entity_delete(const char* db_path, const char* id);
|
|
|
|
bool entity_update_type(const char* db_path, const char* id, const char* new_type);
|
|
|
|
// Saca la entidad de su grupo: UPDATE entities SET group_id = NULL.
|
|
// Idempotente — si la entidad ya tenia group_id NULL, no falla. Devuelve
|
|
// true si la entidad existe (independientemente de si tenia o no grupo).
|
|
// Devuelve false si la entidad no existe o SQLite falla. En BDs antiguas
|
|
// sin la columna `group_id` retorna true como no-op (la entidad no puede
|
|
// pertenecer a un grupo si la columna no existe).
|
|
//
|
|
// Issue 0036d: pareja del flujo Promote en NodeGroups window cuando
|
|
// kind=Group — saca el nodo del grupo para que aparezca suelto en el
|
|
// canvas tras reload.
|
|
bool entity_clear_group_id(const char* db_path, const char* entity_id);
|
|
|
|
// Duplica una entidad existente. Mismo type/metadata, sufijo "_copy" en id
|
|
// y "(copia)" en name. Devuelve el nuevo id en out_id.
|
|
bool entity_duplicate(const char* db_path, const char* id,
|
|
char* out_id, size_t out_id_n);
|
|
|
|
// Inserta una relacion. Si name es NULL/vacio usa k_default_relation_name.
|
|
bool relation_insert(const char* db_path,
|
|
const char* from_id, const char* to_id,
|
|
const char* name);
|
|
|
|
// Lee la columna `notes` (markdown) de una entidad. out se reasigna; vacio si
|
|
// no existe la entidad.
|
|
bool entity_get_notes(const char* db_path, const char* id, std::string* out);
|
|
|
|
// Sobrescribe `notes` con el contenido proporcionado. Toca `updated_at`.
|
|
bool entity_set_notes(const char* db_path, const char* id, const char* notes);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Inspector editable (issue 0008)
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Un campo de la columna `metadata` JSON. `is_string=true` se emite como
|
|
// "..." en JSON; false se emite como literal (number/bool/null). El parser
|
|
// rellena esto al leer; el caller puede sobreescribirlo basado en el schema
|
|
// del tipo antes de guardar.
|
|
struct MetadataField {
|
|
std::string key;
|
|
std::string value_str;
|
|
bool is_string = true;
|
|
};
|
|
|
|
// Snapshot completo de los campos editables de una entidad. No incluye
|
|
// `notes` (panel separado) ni `created_at` (no editable).
|
|
struct EntityRecord {
|
|
std::string id;
|
|
std::string name;
|
|
std::string type_ref;
|
|
std::string description;
|
|
std::string status; // active|stale|corrupted|archived
|
|
std::vector<std::string> tags;
|
|
std::vector<MetadataField> metadata; // orden preservado del JSON original
|
|
};
|
|
|
|
// Carga el snapshot editable de la entidad. Devuelve false si no existe o
|
|
// SQLite falla. Tags y metadata se parsean desde JSON.
|
|
bool entity_load_full(const char* db_path, const char* id, EntityRecord* out);
|
|
|
|
// Persiste el snapshot. Toca `updated_at`. Tags y metadata se serializan a
|
|
// JSON. Devuelve false si SQLite falla.
|
|
bool entity_update(const char* db_path, const EntityRecord& rec);
|
|
|
|
// Lista las tags distintas en uso en toda la BD (para autocomplete del
|
|
// chip-input del Inspector). Sin orden particular.
|
|
bool entity_list_distinct_tags(const char* db_path,
|
|
std::vector<std::string>* out);
|
|
|
|
// Resultado de busqueda FTS5 sobre entities_fts.
|
|
struct EntityHit {
|
|
std::string id;
|
|
std::string name;
|
|
std::string type_ref;
|
|
double rank = 0.0; // bm25 (mas bajo = mejor)
|
|
};
|
|
|
|
// Busca en entities_fts (columnas: id, name, description, tags, domain).
|
|
// Tokeniza `query` por whitespace, descarta caracteres especiales FTS5 y
|
|
// aplica busqueda por prefijo a cada token (token*). Limit clampeado en [1,200].
|
|
// Devuelve hits ordenados por rank ASC. Si query queda vacio tras sanear,
|
|
// devuelve true con out->empty(). Si la BD no tiene la tabla entities_fts
|
|
// (operations.db antiguos), devuelve false.
|
|
bool entity_search_fts(const char* db_path, const char* query, int limit,
|
|
std::vector<EntityHit>* out);
|
|
|
|
// Devuelve los ids de entidades cuyo array `tags` contiene TODOS los tags
|
|
// pasados (AND). Si `tags` vacio, out queda vacio y retorna true.
|
|
bool entity_list_by_tags(const char* db_path,
|
|
const std::vector<std::string>& tags,
|
|
std::vector<std::string>* out_ids);
|
|
|
|
// Snapshot ligero por entidad para la vista tabla (issue 0004). No incluye
|
|
// metadata ni notes — solo identidad + estado para tabular y ordenar.
|
|
struct EntityRowSnapshot {
|
|
std::string id;
|
|
std::string name;
|
|
std::string type_ref;
|
|
std::string status;
|
|
std::string updated_at;
|
|
std::string group_id; // "" si la fila no pertenece a ningun grupo
|
|
};
|
|
|
|
// Carga todas las filas de `entities` ordenadas por type_ref, name. Tolera BD
|
|
// sin la columna `status` o `updated_at` — esos campos quedan vacios.
|
|
bool entity_list_rows(const char* db_path,
|
|
std::vector<EntityRowSnapshot>* out);
|
|
|
|
// Mapa user_data (FNV1a hash) -> sql id. Se reconstruye despues de cada
|
|
// carga del grafo (graph_sources usa FNV1a sobre id como user_data).
|
|
struct EntityIndex {
|
|
std::unordered_map<uint64_t, std::string> by_hash;
|
|
};
|
|
|
|
// Escanea operations.db y rellena el indice. Reentrante (clear+repoblar).
|
|
bool entity_index_build(const char* db_path, EntityIndex* idx);
|
|
|
|
// Resuelve user_data a sql id. NULL si no existe.
|
|
const char* entity_index_lookup(const EntityIndex& idx, uint64_t user_data);
|
|
|
|
} // namespace ge
|