8 Commits

Author SHA1 Message Date
egutierrez 352b27d488 feat: enricher split_words para probar grouping con volumen alto
split_sentences a menudo no llega al umbral de 50 (un texto medio
tiene 5-15 frases). split_words tokeniza el mismo notes en palabras
y trivialmente lo supera con cualquier parrafo decente -> Group
visible y testeable end-to-end sin necesidad de pegar megabytes.

Diferencias respecto a split_sentences:

* Splits por regex de letras (incluye acentos espanyoles + apostrofo
  interno como "don't"). Numeros y puntuacion ignorados.
* Lowercase + filtro por min_length (default 3, filtra a/el/de/y/o).
* Param `dedupe` (default true): vocabulario unico vs cada ocurrencia.
  Con dedupe=false sirve como stress test de volumen.
* Tipo `Word` en types.yaml: amarillo, ti-letter-w, principal_field=word.
* Relacion `WORD_OF` desde cada Word al source.
* Mismo patron de grouping que split_sentences (threshold 50, K=10
  preview, batch_id en metadata, Group con count + enricher).

Tests:

* below threshold no crea Group.
* >=50 tokens unicos -> Group + 10 sueltos + resto agrupados.
* dedupe=true (default) colapsa repeticiones; dedupe=false las
  conserva como nodos separados.
* min_length filtra correctamente.
* notes prioriza sobre node_name.
* texto vacio -> exit 2.
* max_words trunca.

WSL 89 / Windows 78 + 11 skipped.
2026-05-04 00:14:57 +02:00
egutierrez 0e435c2e21 feat: enrichers offline split_sentences + extract_iocs_text
Para probar la app sin depender de red (DDG bloquea con captcha desde
ciertas IPs). Ambos aplican grouping (umbral 50, preview K=10) replicando
el patron de web_search.

- split_sentences: parte texto en frases (regex), crea nodos Sentence
  conectados con SENTENCE_OF.
- extract_iocs_text: variante de extract_text_entities que lee directo
  metadata.text/description/name, sin requerir fetch previo. Vendoriza
  extract_iocs_py_cybersecurity. Multi-tipo, agrupado en un solo Group
  heterogeneo (decision 6 multi-grupo-por-tipo es fase 2).
- Tipo Sentence en types.yaml.

Tests pytest cubren below/above threshold para ambos.
2026-05-03 15:20:39 +02:00
egutierrez 7a94160fd2 feat: catch-up de decisiones previas (Webpage→Url, anti-bot, UI 2-col, tests cross-platform)
Bloque de cambios revisados y validados con el usuario en sesiones
previas que no habian aterrizado en commits propios. Lista por tema:

* enrichers: web_search ahora usa lite.duckduckgo.com como endpoint
  primario (mas tolerante con bot detection desde IP residencial),
  con fallback al endpoint html. Detecta pagina captcha y emite
  error claro si ambos fallan. Anyade _DDGLiteParser para el formato
  lite + auto-pick de parser por contenido.

* enrichers: tipo Webpage unificado en Url (campos de cuerpo
  cacheado viven en metadata del Url). Manifests actualizados
  (applies_to: [Url]). fetch_webpage ya no convierte Url->Webpage.

* enrichers/manifest: campo `params` parseado a EnricherSpec.params
  (name, type, default_value, description). UI puede renderizar
  dialog de configuracion.

* jobs: fix de path conversion para Python embebido nativo Windows
  (no convertir a /mnt/c/... cuando el subproceso es Windows-native;
  solo cuando es bash o python via WSL).

* main.cpp: ventana ImGui (no modal) "Run enricher" con layout
  2-col (label izq, input der). Inserta job con JSON tipado. Layout
  clustering apretado: hijos del mismo anchor en un solo anillo
  alrededor del padre, sin desperdigar por anillos crecientes.

* views: inspector con layout 2-col via BeginTable (Identity,
  Schema fields, Extras). Description full-width debajo de su label.

* tests: portable conftest (auto-detecta REGISTRY_ROOT, PYTHON_BIN,
  ENRICHERS_DIR para WSL y Windows portable). _runner.py trampoline
  inyecta stub via sys.path porque embedded Python ignora PYTHONPATH.
  Tests bash-only (vendor_script, freeze, dispatcher bash, resolver
  Linux-binary) skipean en Windows. Tests existentes adaptados a
  Webpage->Url.

Resultado actual: 32 passed WSL, 21 passed + 11 skipped Windows.
2026-05-03 14:41:28 +02:00
egutierrez fc4f0824da feat(0035a): tipo Group + columna group_id en entities
Plumbing para issue 0035 — agrupacion de resultados de enrichers
cuando exceden umbral. Sin cambios visibles para el usuario todavia.

- Migracion idempotente: ALTER TABLE entities ADD COLUMN group_id si
  no existe (detectado via PRAGMA table_info). Se ejecuta al abrir
  el proyecto en switch_to_project y en el bootstrap inicial.
- Tipo Group en examples/types.yaml (template) y en el types.yaml
  del proyecto default activo en Windows.
- shape=square (regla en types_registry.cpp extendida a Group),
  color=#94A3B8, icon=ti-stack-2.
- Fields: name (req), count (int), enricher (string), batch_id (string).

Refs: issues/0035a-group-type-and-schema.md
2026-05-03 14:23:23 +02:00
egutierrez 020f5dabbe feat(types): tipo Webpage + .gitignore del subrepo (issue 0027)
- examples/types.yaml: nuevo tipo Webpage (icono ti-file-text, fields
  url/title/status_code/content_type/fetched_at/html_path/markdown_path/
  screenshot_path/text_length/lang). Url queda como link suelto.
- types_registry.cpp: anade ti-file-text al mapa de codepoints Tabler.
- .gitignore: cache/, graph_explorer.db (jobs+layouts), build artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:24:19 +02:00
egutierrez fb7538088b feat(types): semilla con fields para 11 tipos + CLI --test-types-yaml
- examples/types.yaml: principal_field + fields para Person, Email,
  Domain, Phone, Org, IBAN, Account, Document, Address, Url, Table.
  44 fields totales. Documentacion del formato en cabecera.
- project_manager.cpp: seed con fields para los tipos basicos (fallback
  cuando no se encuentra examples/types.yaml).
- main.cpp:
  - Log de carga incluye conteo de schemas y total de fields.
  - --test-types-yaml <path>: smoke test que carga, serializa a temp y
    recarga. Compara entidades/relaciones/fields field-a-field. Salida
    PASS/FAIL con exit code 0/1. Permite verificar round-trip sin
    framework de tests.

Verificado: examples/types.yaml round-trip estable (11 entities, 44
fields, 6 relations).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:00:53 +02:00
egutierrez f0148ac368 fix(viz): forzar circulo a todos los nodos salvo Table; tapar energia inicial
- types_registry.cpp::apply_types_yaml: tras aplicar el yaml, sobreescribe
  shape de cada tipo: 'Table' → SHAPE_SQUARE, todo lo demas → SHAPE_CIRCLE.
  Convencion fija — ediciones futuras del Type Editor (issue 0007) o del
  yaml no rompen la regla.
- examples/types.yaml + project_manager.cpp seed: quitar campo `shape`,
  añadir tipo Table (cuadrado) y relacion CONTAINS_ROW (preview de 0010).
- main.cpp run_force_step: damping=0.7, max_velocity=8 explicitos para
  evitar que el grafo "explote" al cargar grafos pequenos.
- AppState repulsion: 1500 → 800 (lo mismo, aplicado al force layout).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:53:21 +02:00
egutierrez b767b5b85e feat: graph_explorer app — agnostic operations.db viewer (issue 0049k)
App C++ ImGui que abre cualquier operations.db del registry y lo visualiza
como grafo con shapes/iconos/layouts/filtros/labels.

Composicion del registry:
- viz/graph_renderer + graph_force_layout(_gpu) + graph_layouts +
  graph_viewport + graph_labels + graph_icons + graph_sources
- core: toolbar, modal_dialog, select, text_input, tree_view, page_header,
  fullscreen_window, button, badge, empty_state

Capas:
- data.{h,cpp}    — dispatcher GraphLoadFn (operations hoy; json/graphml manana).
- types_registry.{h,cpp} — parser YAML minimal + tabler_codepoint_by_name +
  apply_types_yaml + IconAtlas builder.
- views.{h,cpp}   — Toolbar, Legend, Inspector, Stats, modal Filters/Open.
- layout_store.{h,cpp} — graph_explorer.db SQLite con tabla layouts(graph_hash,
  node_id, x, y, pinned, updated_at). UPSERT por nodo.
- main.cpp        — CLI (--input/--types/--layout) + fn::run_app + bucle
  force layout (CPU/GPU toggle) + render con 3 columnas (Legend / Viewport /
  Inspector+Stats).

examples/types.yaml: 10 entidades OSINT (Person/Email/Domain/Phone/Org/IBAN/
Account/Document/Address/Url) + 5 relaciones (owns/knows/located_in/
transfers_to/member_of) con shapes Tabler reales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:13:59 +02:00