feat(infra): auto-commit con 88 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 00:16:46 +02:00
parent 6bc97df5c0
commit eb8dbf66a1
126 changed files with 10933 additions and 287 deletions
@@ -0,0 +1,71 @@
---
name: resolve_obsidian_embed
kind: function
lang: py
domain: obsidian
version: "1.0.0"
purity: impure
signature: "def resolve_obsidian_embed(vault_dir: str, embed_name: str) -> str"
description: "Resuelve el path absoluto real de un attachment embebido buscandolo por nombre dentro de un vault de Obsidian. Obsidian resuelve los embeds ![[...]] por nombre de archivo unico (no por path), asi que esta funcion recorre el vault recursivamente (una pasada con os.walk) y devuelve el primer archivo cuyo basename coincida (case-insensitive), excluyendo .obsidian/ y .trash/. Si el embed no trae extension, acepta cualquier match de ese basename. Devuelve cadena vacia si no existe (NO lanza). Lanza si vault_dir no existe. No depende de la app GUI."
tags: [obsidian, embed, attachment, resolve, filesystem, migrate]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["os"]
params:
- name: vault_dir
desc: "ruta a la raiz del vault Obsidian donde buscar el attachment"
- name: embed_name
desc: "nombre del attachment embebido (el target de un ![[...]], lo que devuelve extract_obsidian_embeds), con o sin extension"
output: "string con el path absoluto del primer archivo del vault cuyo basename coincide (case-insensitive) con embed_name, o cadena vacia '' si ningun archivo coincide. Excluye .obsidian/ y .trash/."
tested: true
tests:
- "match exacto en subcarpeta"
- "match case insensitive"
- "sin extension acepta cualquier match"
- "no existe devuelve vacio"
- "excluye obsidian y trash"
- "vault inexistente lanza"
test_file_path: "python/functions/obsidian/resolve_obsidian_embed_test.py"
file_path: "python/functions/obsidian/resolve_obsidian_embed.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from obsidian import extract_obsidian_embeds, resolve_obsidian_embed
body = "Mi DNI: ![[dni enmanuel (2).jpg]]"
for embed in extract_obsidian_embeds(body):
path = resolve_obsidian_embed("/home/me/MiVault", embed)
print(embed, "->", path or "(no encontrado)")
# dni enmanuel (2).jpg -> /home/me/MiVault/attachments/dni enmanuel (2).jpg
```
## Cuando usarla
Usala junto a `extract_obsidian_embeds` cuando migres o extraigas un subgrafo de
notas y necesites el path fisico real de cada attachment para copiarlo al nuevo
destino. Replica la resolucion por-nombre de Obsidian sin abrir la app GUI:
basta con el directorio del vault en disco.
## Gotchas
- **Resuelve por NOMBRE de archivo, no por path.** Igual que Obsidian: busca el
basename en TODO el vault. Si hay dos archivos con el mismo nombre en carpetas
distintas (`fotos/logo.png` y `assets/logo.png`), devuelve el PRIMERO que
encuentra `os.walk` — el orden de `os.walk` no esta garantizado entre sistemas,
asi que con nombres duplicados el resultado puede no ser el embed que Obsidian
mostraria. Para vaults con nombres ambiguos, deduplica los nombres antes de
migrar.
- Si el embed no existe en el vault devuelve `""` (cadena vacia), NO lanza
excepcion. Comprueba el valor antes de usarlo como ruta.
- SI lanza `FileNotFoundError` / `NotADirectoryError` si `vault_dir` no existe o
no es un directorio.
- Es impura: recorre el filesystem en cada llamada. Si vas a resolver muchos
embeds del mismo vault, considera construir tu propio indice `basename -> path`
con un unico `os.walk` en lugar de llamar a esta funcion en bucle.