# ResearchOwl — developer tasks. # # SEO engine vendoring: src/seo/rules.py is a byte-for-byte copy of the canonical # seo_rules.py (repo git.chemavx.xyz/chemavx/chemavx-seo-tools; local working copy # ~/seo-tools). These targets keep the vendored copy in sync; CI enforces it too. SEO_TOOLS_DIR ?= $(HOME)/seo-tools CANON := $(SEO_TOOLS_DIR)/seo_rules.py VENDORED := src/seo/rules.py HEADER := src/seo/_vendor_header.py MARKER := BEGIN VENDORED seo_rules.py .PHONY: sync-seo check-seo-sync test golden-db golden shortsmith-live test: ## Suite completa (los tests vivos se saltan solos) python3 -m pytest tests/ -q # --- Shorts: pruebas contra el servicio vivo --------------------------------- # El renderizador no se expone fuera del cluster, así que desde el nodo se le # habla por la ClusterIP. Los dos targets de abajo la resuelven solos. SHORTSMITH_IP = $(shell kubectl get svc shortsmith-svc -n shortsmith \ -o jsonpath='{.spec.clusterIP}' 2>/dev/null) GOLDEN_SESSION ?= 153 GOLDEN_DB ?= /tmp/researchowl-golden.db golden-db: ## Extrae UNA sesión de la DB de producción a un fichero pequeño @test -n "$(SHORTSMITH_IP)" || echo "aviso: shortsmith-svc no encontrado" kubectl exec -n researchowl deployment/researchowl -- python3 -c "\ import sqlite3, os; \ src = sqlite3.connect('file:/data/researchowl.db?immutable=1', uri=True); \ out = '/tmp/golden.db'; os.path.exists(out) and os.remove(out); \ src.execute('ATTACH DATABASE ? AS g', (out,)); \ src.executescript('''CREATE TABLE g.research_sessions AS SELECT * FROM research_sessions WHERE 0; \ CREATE TABLE g.sources AS SELECT * FROM sources WHERE 0; \ CREATE TABLE g.chunks AS SELECT * FROM chunks WHERE 0; \ CREATE TABLE g.outputs AS SELECT * FROM outputs WHERE 0; \ CREATE TABLE g.api_usage AS SELECT * FROM api_usage WHERE 0;'''); \ [src.execute(f'INSERT INTO g.{t} SELECT * FROM {t} WHERE ' + ('id=$(GOLDEN_SESSION)' if t=='research_sessions' else 'session_id=$(GOLDEN_SESSION)')) \ for t in ('research_sessions','sources','chunks','outputs')]; \ src.commit(); print('sesión $(GOLDEN_SESSION) ->', os.path.getsize(out), 'bytes')" kubectl cp -n researchowl \ $$(kubectl get pod -n researchowl -o name | head -1 | cut -d/ -f2):/tmp/golden.db \ $(GOLDEN_DB) @echo "escrito en $(GOLDEN_DB)" golden: ## Eval dorada (GASTA dinero: una llamada a Claude + un render) @test -f $(GOLDEN_DB) || { echo "falta $(GOLDEN_DB) — ejecuta 'make golden-db'"; exit 1; } RESEARCHOWL_GOLDEN_DB=$(GOLDEN_DB) \ RESEARCHOWL_GOLDEN_SESSION=$(GOLDEN_SESSION) \ SHORTSMITH_LIVE_URL=http://$(SHORTSMITH_IP):8080 \ SHORTS_DIR=/tmp/researchowl-shorts \ python3 -m pytest tests/test_short_golden.py -v -s shortsmith-live: ## Fontanería contra el shortsmith vivo (renderiza el ejemplo) SHORTSMITH_LIVE_URL=http://$(SHORTSMITH_IP):8080 \ python3 -m pytest tests/test_shortsmith_live.py -v -s sync-seo: ## Re-copy canonical seo_rules.py into the vendored file + record hash @test -f "$(CANON)" || { echo "canonical not found at $(CANON)"; exit 1; } @cat "$(HEADER)" "$(CANON)" > "$(VENDORED)" @sha256sum "$(CANON)" | cut -d' ' -f1 > src/seo/.rules.sha256 @echo "synced $(VENDORED) from $(CANON) (sha $$(cat src/seo/.rules.sha256))" check-seo-sync: ## Fail if the vendored copy diverges from the local canonical @test -f "$(CANON)" || { echo "canonical not found at $(CANON) — skipping (run on a machine with seo-tools)"; exit 0; } @N=$$(grep -n "$(MARKER)" "$(VENDORED)" | head -1 | cut -d: -f1); \ tail -n +$$((N+1)) "$(VENDORED)" > /tmp/_vendored_body.py; \ if cmp -s /tmp/_vendored_body.py "$(CANON)"; then \ echo "seo engine in sync ($(VENDORED) == $(CANON))"; \ else \ echo "SEO ENGINE DRIFT: $(VENDORED) != $(CANON). Run 'make sync-seo' and commit."; \ diff -u "$(CANON)" /tmp/_vendored_body.py | head -40; exit 1; \ fi