Repository navigation
Releases: syberloop/mcp-okf
Release list
v0.4.32 — el agente ve la vigente, el costo y el cuerpo
Seis PRs de Medardo (#41–#46), verificados uno por uno y en conjunto. Suite: 583 passed, 57 subtests.
Búsqueda y vigencia (#43, #46)
search --bodybusca también en el cuerpo; los de frontmatter van primero y los de cuerpo después, marcados(cuerpo). Sin--bodyy con menos de 5 resultados, un pie avisa cuántas notas más lo mencionan en el cuerpo.readde una nota reemplazada imprime, antes del cuerpo, la versión vigente; la correctora imprime a quién corrige.- El pie de reemplazadas de
searchdeja de ser el total del vault: cuenta solo las que coinciden con la consulta y el tipo, cada una con su vigente. - Conflicto #43/#46 resuelto: los dos reescribían el pie de
search; conviven los dos yhiddense filtra conmatches_query(..., body=body_mode).
Costo de la llamada (#45)
traverse --compact(una línea por nodo) y--budget N(corta después del BFS, «alcanzables» exacto).- Recibo en toda salida: nodos, alcanzables, bytes, tokens y si truncó.
receipten JSON.
Grafo (#41, #42)
suggest-edge-typesno propone como ALTA la inversa de una arista tipada ni los pares mutuos; no entran en--apply. En un vault de 311 notas: ALTA 391 → 271.graph impactmanda por la arista declarada; el score semántico solo anota ([ajuste: 0.65]) y avisa si es bajo.
Validación (#44)
aplicahacia una reemplazada avisa «re-anclá la arista… el trabajo puede seguir valiendo».- Apoyo en una nota muerta sin sucesora (estados
deadde su capa) avisa «quedó sin base». Aviso, nunca error.
v0.4.31 — sello de identidad de vault
[2026-10-23] — v0.4.31
Con tres vaults servidos por el mismo server.py, la única falla silenciosa posible era elegir el prefijo equivocado (mcp__okf__new en vez de mcp__okf_<cliente>__new): el docstring de las tools era idéntico en las tres instancias y la respuesta no decía sobre qué vault se había escrito. Un new mal dirigido creaba un concepto huérfano en el grafo equivocado, con la taxonomía del padre.
Sello de identidad de vault (server.py)
- El schema declara el vault (
_vault_label()+_mcp_tool()): el docstring de cada tool se sella conVault de esta instancia: <nombre> (<ruta absoluta>)antes de registrarla. El agente lo lee en el schema ANTES de llamar, así el prefijo equivocado deja de ser invisible. El sello se re-aplica sin acumularse en reimportaciones. - La escritura se verifica E2E (
_seal_output()): toda tool de_WRITE_TOOLScierra su salida con[vault: <nombre> (<ruta>)]; si el CLI cita un.mdabsoluto fuera de este vault →[ERROR: la salida cita archivos fuera de este vault: …]; si lo cita como creado y no existe →[ERROR: el CLI reporta éxito pero no existe: …]. Cubre también los caminos de timeout y excepción. --dry-runno se verifica: no escribe, así que exigir la existencia del archivo era un falso positivo. Detectado con un mini-cliente stdio real sobre los tres servers antes de cortar la etiqueta.- Tests:
tests/test_multivault_seal.py(+12): label con nombre y ruta, sello en escritura y no en lectura, archivo fuera del vault, éxito fantasma, ruta relativa que no debe confundirse con absoluta (insights/x.md≠/x.md), exit≠0 sellado sin verificar,editsellado, dry-run sin verificación, y el docstring sellado en subproceso (borrarcli.*desys.modulespara recargar contamina los monkeypatches de otros tests: rompíatest_scope_readonlyytest_stale_reads_store). Suite: 547. - Medido en los tres vaults (2026-10-23):
okf(721 nodos, 9/9),okf-skynet(306, 8/8),okf-controlresearch(79, 8/8) — cada server se identifica con su propio vault, sin filtrar el nombre de otro, y una escritura real por stdio devuelve el sello con el path absoluto correcto.
v0.4.30 — el review omite los conceptos muertos (states.dead)
Full Changelog: v0.4.29...v0.4.30
v0.4.29 — fases 4 y 5: version vigente, verificacion, Persona e iniciativa con scope
Las dos fases del rediseño del vault de cliente, juntas en una etiqueta. Todo opt-in por vault: sin las claves nuevas en .okf.config.yaml el comportamiento es el de v0.4.28.
Fase 4 (#39) — la verdad tiene fecha, quien decide es una nota y una nota reemplazada señala su vigente:
resolves: <Tipo>entypes.fields:decidida_poryresponsableresuelven a una notaPersonaen vez de a texto libre.verificado_el/revisar_elcon plazo por tipo (types.review.days) o por prefijo de ruta (paths, que gana);stalesuma la señal 8 yreviewseparakind: cyberdekind: verificacion.traverseysearchsaltan a la versión vigente por defecto (--all-versionsrecorre la cadena).healthdeja de dar 7/8 en un vault cuyos tipos exigen campos.
Fase 5 (#40) — la iniciativa como campo y el alcance por campo:
types.fields.commony campos con lista, para exigiriniciativaen toda nota salvo la capa de registro y las tareas.--scope campo=valor: la frontera de una instancia MCP puede ser un campo del frontmatter. Fail-closed, y un scope mal formado aborta en vez de degradar.
Tests: 523 (485 en v0.4.28), verificados también en un entorno limpio tipo CI. Verificación: suite en worktree y entorno limpio, baseline RED, mediciones reproducidas sobre un clon fresco del vault de cliente, y el efecto sobre OKF-Vault (6 notas ocultas en search, 2 avisos nuevos, health 9/9).
v0.4.28 — el tablero lee las aristas de cada tarjeta
El board deja de exportar tarjetas mudas: cada una trae sus aristas tipadas salientes (relaciones: tipo, destino y título del destino) y cuántas le entran (entrantes), para que el tablero diga de qué plan es parte una tarea o qué espera una nota bloqueada sin abrirla. Es el PR #38, que había quedado sin etiqueta.
Medido (2026-10-17, master del vault de cliente): 268 tarjetas, 218 con al menos una relación, 360 aristas, 0 destinos sin título; las 37 Task traen su aplica.
Tests: 485 (484 en v0.4.27).
Requiere subir OKF_VERSION a v0.4.28 en las Actions de los vaults que consumen okf board.
v0.4.26 — okf board, y el detector de obsolescencia con una sola vara
okf board (PR #32) y el detector de obsolescencia con una sola vara.
Added
okf board [--out] [--iniciativa X] [--no-tasks]: el vault como JSON de tablero en una sola pasada —columnas(los estados de cada capa segúntypes.states),tarjetas(un concepto tipado por tarjeta, fuera de la capa de registro, con capa, status,iniciativa, casillas hechas/total, edad y nivel destale),tareas(casillas abiertas con antigüedad, reutilizasearch.find_todos) ysalud(conteo por capa y estado).--iniciativafiltra por el campo; sin esquema de estados sigue funcionando con una capasin-esquemay los status observados.- PR #32, Medardo Rafael Hernandez Franco (7 tests).
Fixed
dashboard_snapshotno le pasabaconfigacollect_stale: el detector caía en los literales legacy (señal 3 =propuesta, señal 6 = Decision) mientrasstaleyboardleían el esquema del vault. Medido enOKF-Vaultcon el esquema activo: 72 de 700 conceptos divergían — el panel del DashboardView daba ATTENTION donde el CLI daba STALE—. Misma clase que el #21, el #29 y el #30: dos consumidores de la misma primitiva midiendo distinto.collect_stale(vault, config=cfg)ignoraba la lista de exentos del vault (types.exempt_from_no_backlinks) y usaba la embebida en silencio: el resultado dependía de cómo se llamaba a la función y no de la config. Ahora, conconfig, manda la lista del vault; los llamadores que la pasan explícita no cambian.
Medido (2026-10-16, sobre OKF-Vault con los dos strict activos)
board: 565 tarjetas, 271 tareas, cuatro capas (conocimiento 4 estados, compromiso 4, trabajo 5, infra 3).- El desajuste
salud.por_estadovscolumnasno puede ocurrir contypes.states.strict: true: toda tarjeta tiene un estado declarado por su capa (0 casos, 0 tarjetas sin status). staley el panel: 0 divergencias entre 700 conceptos (eran 72). STALE 6, ATTENTION 291.
Tests
- 451 (443 en v0.4.25): +7 del board, +1 de paridad panel/CLI.
v0.4.25 — El destino de una arista se resuelve como el resto del CLI
El destino de una arista se resuelve como el resto del CLI (PR #33).
Fixed
validateresolvía el destino de una arista convault / targeta secas, sin el fallback de.md, mientras el resto del CLI acepta targets sin extensión (resolve_link). Un linktarget: decisions/otra-nota—como los escribe el vault OKF— no resolvía: el tipo del destino quedaba en'?'y el par era imposible de declarar envalid_pairs, así quegraph.strict_pairs: trueera inalcanzable en un vault que escribe targets sin extensión. Medido en OKF-Vault: 6 de 148 aristas atípicas estaban en ese estado (Harness aplica ?,Plan aplica ?,Project aplica ?,Harness depende ?,Skill depende ?) y ninguna se podía declarar.- La misma resolución estaba mal en la rama de
corrige: no leía el destino, así que no comprobaba que estuviera en su estado de reemplazo. Con un target sin extensión, la comprobación no corría.
Changed
- Las dos ramas usan
_resolve_edge_target, que reusaresolve_link: la MISMA resolución quegraphy que el chequeo de wikilinks (misma clase que el #29 y el #30 — una sola resolución por concepto), con fallback al path pelado y a.mdpara targets fuera de alcance.
Tests
tests/test_validate_edge_target_sin_extension.py(+4): par declarado con target sin.mdno avisa; par atípico con target sin.mdreporta el tipo real (no'?'); constrict_pairses un error; un target inexistente sigue siendo'?'.- Suite: 443 (439 en v0.4.24).
Verificado de punta a punta: con este fix, el vault OKF declaró sus 143 pares y activó graph.strict_pairs: true + types.states.strict: true con validate --all en 701/701, 0 avisos.
v0.4.24 — La máquina de estados vive en la config del vault
Estado de los conceptos declarado en el vault (PR #31, Medardo Rafael Hernandez Franco).
Added
- Máquina de estados por tipo, declarada en la config (
types.states.schemas, agrupada por capa):types,states,initial,transitions,replaced(el estado que exige uncorrigeentrante) ypending(lo questalecuenta como sin resolver).states: []= capa de registro, no llevastatus.types.states.strict(defaultfalse) decide aviso o error. newsin--statusarranca en el estado inicial del tipo; con--status, el valor tiene que estar en el esquema. Un tipo de registro no admite ninguno.edit --statusvalida el estado y la transición desde el actual;--forcesalta la transición (migraciones).graph.inherit_default_pairs: falsedeja fuera losvalid_pairsembebidos —usan el vocabulario de un vault (LeccionAprendida, noLesson)— para que cada vault declare los suyos con sus propios nombres de tipo, conservando descripción, transitividad e inversa.graph.strict_pairs: true: un par (tipo origen, tipo destino) fuera de los declarados pasa de aviso a error envalidate, ynew/editno escriben la arista.okf stamp [--staged]: fijatimestampdesde el diff de contenido contraHEAD; con--stagedvuelve a stagear lo que ajustó, para el pre-commit.
Changed
timestampmide el contenido, no los metadatos:editlo renueva solo si cambianbody,titleodescription. Aplica también sin esquema — es un cambio global, no una rama opt-in.staleseñal 3 leependingdel esquema del tipo; sin esquema, el literal históricopropuesta. Señal 6 exige status a cualquier tipo con estados declarados; sin esquema, aDecision.- La regla de
corrigemira el estadoreplacedde la capa del destino; sin esquema conserva la heurística histórica (deprec/superced). - La validación 12 de
validate(status contra el esquema) se suma a las existentes; sin esquema no comprueba nada.
Medido (2026-10-16)
- Suite: 396 → 439 (43 nuevos). Revirtiendo
cli/amaster, los nuevos dan 24 fallos + 3 errores: no son vacíos. validate --allystale --jsonsobreOKF-Vault(699 archivos, sintypes.states): salida idéntica a v0.4.23.- El check
Timestamp↔gitdehealthno se rompe: solo alerta si el timestamp va en el futuro respecto al último commit o más de 30 días antes del primero.
Known follow-up
dashboard_snapshot._collect_stale_resultsllama acollect_stale(...)sin el nuevoconfig=: inerte sin esquema, pero con uno activo el panel DashboardView divergiría destaleyboard. Repro en el comentario de revisión del PR #32.
Esta etiqueta es la que tienen que instalar los tres entornos (Action del vault, venv del Mac, Skynet) antes de activar el esquema de estados: es el paso que el plan de fases deja como condición del merge del PR #8 del vault.
v0.4.23
[2026-10-15] — v0.4.23
Changed
- Los tipos exentos de la señal 4 de
stale(«no backlinks») salen de config:types.exempt_from_no_backlinks(con default embebido). La lista estaba hardcodeada con el vocabulario de un vault —("MarcoTeorico", "Spec", "Tool", "Agente", "Sesion")— mientrastypes.validsí es configurable: en el vault del cliente, donde el tipo esSessiony noSesion, 9 sesiones entraban como señal 4 y el mismo número significaba cosas distintas en cada vault. El default embebido es el set del Criterio de obsolescencia más los dialectos conocidos:MarcoTeorico, Spec, Tool, Agente, Sesion, Session, Skill, Insight. - Señal 3: el umbral de
status: propuestaparatype: Insightsube a 90 días (stale.propuesta_days_insight, default 90) y el resto de los tipos mantiene 30. Es la decisión del triaje automático del subgrafo dreaming (2026-09-05): una hipótesis tiene más vida que una propuesta de acción, su ciclo es integrarse o archivarse, no «aplicarse». Cierra N1 y N2 del plan del ajuste del detector, en propuesta desde el 2026-09-05. dashboard_snapshotcomparte ahora la config completa del detector: antes omitíastale.reads_zero_min_daysy no podía ver los exentos, así que su distribución podía contradecir astale(misma clase que el #21 y el #29).
Fixed
- Dos tests de
test_stale_reads_store.pyusabantype: Insightpara fabricar el caso de 3 señales. ConInsightexento de la señal 4 ese camino ya no existe: el caso se prueba con un tipo no exento (Research). El contrato del store de lecturas no cambia.
Medido (2026-10-15, sobre los vaults reales)
| Vault | Señal 4 | STALE | ATTENTION | FRESCO |
|---|---|---|---|---|
OKF-Vault (698 conceptos) |
110 → 67 | 9 → 2 | 357 → 317 | 332 → 379 |
skynet (238 conceptos) |
43 → 26 | 0 → 0 | 62 → 44 | 176 → 194 |
Tests: 396 (11 nuevos; RED 5 fallos + 2 errores contra el código anterior), verdes también en entorno limpio tipo CI (env -i + venv scratch).
v0.4.22
[2026-10-15] — v0.4.22
Fixed
healthbuscaba el hook siempre en<vault>/.git/hooks/pre-commit(#30). Concore.hooksPath hooks—la activación que recomienda elCLAUDE.mddeskynet-vault— o dentro de un worktree (donde.gites un archivo), un vault sano daba 7/8 con «pre-commit hook does not exist» aunque el hook existiera y corriera: como ese hook termina enhealth --strict, el commit quedaba abortado y no había forma de commitear con la configuración recomendada. Repro medido en un clon nuevo del vault: 7/8 sin el fix, 8/8 con él._hooks_dir(vault)le pregunta a git congit rev-parse --git-path hooks;dashboard_snapshothereda el arreglo porque importa la misma función.tests/test_health_git_hook_hookspath.py(+5).trace --layers hooksseguía leyendo.git/hooksa mano (misma clase que el #30, cerrada en el mismo lote): en un vault concore.hooksPathrespondía «(no results)» en silencio aunque el hook existiera — demostrado en vivo sobreskynetantes y después. Ahora usa_hooks_dirdehealth: un solo lugar decide dónde vive el hook.tests/test_trace_hooks_dir.py(+4, RED 3/4 contra el código anterior).- La señal 4 de
stale(«no backlinks») resolvía los wikilinks por nombre de archivo e ignoraba las aristas tipadas (#29), así que contradecía agraph. Medido sobreskynet(239 conceptos, 2026-10-15): 51 conceptos marcados antes, 34 después — 17 falsos positivos corregidos (33 %), 12 por aristas tipadas y 5 por basenames repetidos ([[projects/x/x]]caía en cualquierx.mdy la ficha real quedaba huérfana).build_backlinks_indexusa ahorabuild_graph, la misma resolución quegraph. Costo:stalepasa de ~0.5 s a ~0.68 s en 239 conceptos, porque construye el grafo completo (aceptable a esta escala).tests/test_stale_backlinks.py(+3).
Changed
- El check del hook de
healthincluye en el mensaje la ruta donde buscó («looked in …»): un 7/8 se diagnostica sin leer el código (sugerencia del autor del #30).
Tests: 385.
Fuera de alcance (verificado, no incluido)
- La tupla de tipos «naturalmente huérfanos» de la señal 4 lista
Sesion, pero el vault usatype: Session(15 conceptos, 9 sin backlinks): esos siguen contando como «no backlinks». Es la diferencia entre los 60 que reporta el CLI y los 51 del conteo sin sesiones.tests/test_dashboard_snapshot_conceptos.py:73fija la premisa con el typo. Decisión de producto pendiente (¿una sesión sin backlinks es señal de staleness?), fuera de este release.