Repository navigation
Added
-
Presets de LLM: OpenCode Zen (
https://opencode.ai/zen/v1), OpenCode
Go (https://opencode.ai/zen/go/v1) e Ollama Cloud
(https://ollama.com/v1) no fallback de qualidade — GUI (picker de
provider), docstring de env do CLI e README (#32). -
DOCX unificado com o PDF (T19):
doc_to_mdpara de embutir base64 por
padrão (decisão D2) e passa a respeitar--imagens—extrairgrava
assets com posição preservada no documento,ambosusa OCR como
alt-text,ignorardescarta. Maquinaria de segurança compartilhada via
core/image_assets.py(dedup, nomenclatura, limites, symlink).
core/pdf_images.pyfica só com a parte PyMuPDF. -
--imagens transcrever|extrair|ambos|ignorar(PDF-only): política de
imagens embutidas.extrairsalva os assets (default<stem>_assets/) e
linka;ambosextrai e usa OCR como alt-text;ignorar
descarta sem OCR; defaulttranscreveré byte-idêntico ao atual.
--assets-dirsobrescreve o diretório;--obsidianusa
vault/attachments/com wikilinks![[...]]. -
Segurança de extração (ADR-0005): nomes sempre gerados (nunca do
metadado do PDF), dedup por SHA-256, limites anti resource-bomb (500
imagens/documento, 50 MB/imagem), recusa de symlink no assets dir, ext
exótica normalizada para PNG. -
core/pdf_images.pycom 8 testes (extração, dedup, limites, nome
hostil, symlink, PDF sem imagem). -
ADR-0005: decisões D1–D5 (destino, base64 rejeitado, render de scan,
dedup, nomenclatura). -
LLM provider/model picker in the GUI (Settings window) — fixes the
silent no-op of the AI Enhance toggle in the.app:BatchProcessornow
injectsPDF2MD_LLM_URL/MODEL/KEYinto the process environment. Provider
presets (Ollama, Gemini, Groq, OpenRouter, custom), live model list and
connection status come from the newpdf2md llmsubcommands; the API key
is stored in the macOS Keychain (never in argv —ps auxsafe). -
CLI flags
--llm-url/--llm-modelowith precedence flag > env >
default, propagatedcli → batch → quality → llm_enhancerviaConfigLLM. -
pdf2md llm modelos --json— lists models from the endpoint, with
vision capability detection for local Ollama (/api/show). -
pdf2md llm testar --json— uncached connectivity probe with latency
measurement; failure returns{"ok": false}with exit 0 (JSON is the GUI
contract, no traceback). -
ADR-0007: LLM config decisions D6–D10 (Keychain, env injection, SSRF
by design, keychain/ad-hoc signing limitation).
Changed
- Desempenho do pipeline de qualidade:
corrigir_mojibake(~40 travessias
O(n) de count+replace) elimpar_artefatos(6× replace) agora rodam em
1 passada (regex sub + str.translate compilada) (#30). - Tabela do CLI determinística: resultados em batch seguem a ordem de
entrada (antes: ordem de conclusão do ThreadPool, variava entre runs) (#30). _linha_tabela_mdextraído (DRY pptx/xlsx) (#30).- GUI: drop/picker aceitam .docx, .pptx e .xlsx de novo — o filtro por
UTI hardcoded nunca casava com a resolução do LaunchServices (docx →
org.openxmlformats.wordprocessingml.documentsemofficedocument.);
filtro agora é por extensão, espelhandoEXTENSOES_PERMITIDASdo core (#31).
Fixed
-
Aviso falso de "texto corrompido" em PDF com fórmula (BAIXA): glifo de
fórmula sem ToUnicode (Σ,=de fontes TeX como pxfonts) gerava o aviso
"texto provavelmente corrompido por problema de encoding", mas as palavras
estavam intactas.classificar_fffdsepara os U+FFFD: colado em letra de
texto corrido continua como encoding corrompido; fora de palavra (vizinho
de espaço, colchete, letra grega ou letra matemática) vira aviso próprio,
"símbolo(s) sem mapeamento Unicode … revise as fórmulas". -
Ligaduras tipográficas (fl, fi, ff...) viravam U+FFFD na conversão (MÉDIA):
o engine de layout do pymupdf4llm mapeia glifos de ligadura para U+FFFD quando
a fonte embutida não traz ToUnicode — "Workflow" saía como "Work�ow" e
"reflect" como "re�ect". Reparo por dicionário do próprio documento:
page.get_text()como referência, substituição 1-para-1, sem adivinhação.
Limitação: FFFD de fonte matemática (Σ, ∫) não tem referência no texto cru e
continua avisado. -
OCR interno do pymupdf4llm duplicava trabalho (~40% do tempo de extração):
use_ocr=Falsequando o engine de layout está ativo. O projeto já tem OCR
próprio controlado porModoImagem. -
Assets com espaço/acento no nome quebravam
--obsidian/--assets-dir
(ALTA): o prefixo{stem}__com "Relatório.pdf" violava o regex de nome
seguro → ValueError → arquivo inteiro virava ERRO. Prefixo agora passa por
slugify ASCII (NFKD). Revisão fullstack 2026-08-13 (#29). -
DOCX com imagem gigante derrubava o processo (ALTA):
f.read()lia a
imagem inteira antes do check de 50 MB — docx malicioso de 2 GB causava OOM.
Tamanho agora é checado via seek/tell antes da leitura (#29). -
GUI: erros do subprocesso invisíveis — stderr era descartado e exit code
nunca verificado; todo erro virava "Falha ao executar processo". ProcessRunner
agora propaga (stdout, stderr, exitCode) e a GUI exibe o erro real (#29). -
Default-command com flags antes do positional (
pdf2md --sobrescrever in.pdf out/) falhava com "No such option" — flags são reordenadas (#29). -
Markup Rich vazava no JSON
erro([red]...[/red]renderizado cru na GUI)
— mensagem limpa no caminho--json(#29). -
XLSX com linhas vazias no topo descartava o conteúdo — primeira linha
não-vazia vira header; streaming read_only preservado (2 passadas) (#29). -
TesseractError mascarado como "arquivo corrompido" — mensagem real de OCR
(idioma ausente) agora é exibida (#29). -
CLI/GUI: o
.appnão convertia (desde v0.4.0). O CLI exigia o
subcomandoconverter, mas a GUI chamavapdf2md <arquivo> <destino> --json→ "No such command". Default-command no entry point: a forma
canônicapdf2md <origem> [destino] [opções]agora funciona (a explícita
pdf2md converter ...continua). Auditado por Grok 4.5 max (C1). -
GUI: avisos de qualidade eram descartados —
atualizarProgresso
não propagavaitem.avisos; o ícone âmbar nunca aparecia (A1). -
--imagens: crash emamboscom GIF/PPM (ocr_bytes normaliza para
PNG); renders de scan contam no limite anti bomb; colisão de stem
em dirs de assets compartilhados (prefixo pelo stem do .md destino) —
fixes da Emenda 2 do ADR-0005 (#22/cf9ab44).
Security
-
CI: gate detect-secrets era vácuo —
scan --baselineauto-atualiza o
baseline e sai 0 com segredo novo;--fail-on-unknownnão existe no 1.5.0.
Gate real:detect-secrets-hook+ grepERROR: Potential secrets(#29). -
CI: smoke
llm testarvalidava valor, não contrato — sem servidor no CI,
ok: falseé legítimo; gate agora valida o shape JSON ("ok": true|false). -
CI:
permissions: contents: read(least privilege) + actions pinadas por
SHA (checkout v4.4.0, setup-python v5.6.0, setup-uv v5.4.2, cache v4.3.0). -
mypy strict agora roda no CI — config existia mas estava morta; 21 erros
resolvidos (anotações + ignore_missing_imports para libs sem stubs). -
Prompt injection documentado em SECURITY.md (texto do documento vai ao
LLM sem isolamento — risco aceito, ADR-0004). -
Error messages from
llm modelos/llm testarare sanitized (CWE-209/532):
only HTTP code or failure category is exposed, never URL/path/credential. -
API key is transmitted only via
process.environment(D8) and the
Authorization header — never through argv or JSON output. -
Image extraction hardening (ADR-0005): path traversal, symlink and
resource-bomb mitigations with tests proving each one.
Testes
- 206 → 273 testes; cobertura 87% → 94% (cli.py 68→88%, image_converter
75→95%, pdf_images 79→100%) (#29, #30). - Teste-fantasma corrigido:
testarimportado de llm_enhancer fazia
chamada de rede real a localhost:11434 na suíte — renomeado para_testar_llm.
Instalação
- Build Apple Silicon (arm64), macOS 13+. OCR requer Tesseract:
brew install tesseract tesseract-lang. - App sem notarização: na primeira execução, botão direito → Abrir.
- SHA-256 de
PDF2MD-v0.6.0.dmg:a2c983888e5927de3159ce1c925acc300fa88802afef435bd71112ae09e86d1e