chore(fase0): higiene do repositório + corrige gitignore que escondia fcpxml/models/
Fase 0 do roteiro de reestruturação (Engine/docs/10_MAPA_REESTRUTURACAO.md): move code/WHISPERX (2,6 GB de backups órfãos, sem uso ativo, sem .gitmodules) para ~/Archives/G-ART-WHISPERX-backup fora do workspace git; traz admin/ para o gate de lint de run_after_fix.sh; corrige fcpxml/writer/adjustment.py, que gerava um wrapper <adjustment> inexistente no DTD 1.13 (filtros agora vão direto no <clip>, na ordem exigida), com teste de regressão novo. Achado à parte: .gitignore tinha uma regra solta "models/" (pensada só para o cache do Whisper em code/models/) que também escondia do git todo o pacote fcpxml/models/ — nunca commitado, sem proteção nenhuma. Corrigida para /code/models/, ancorada na raiz. Docs atualizados no mesmo commit (02_MODULES, 09_MANUTENCAO, 10_MAPA_REESTRUTURACAO, 05_EXPERIENCIAS #34 e #36), conforme a regra do CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
0fdfe33613
commit
d13f643ebc
@@ -7,7 +7,7 @@ Mapa módulo a módulo do núcleo Python: onde cada coisa mora e o que ela faz.
|
||||
A API pública é reexportada em `fcpxml/__init__.py` — essa é a fonte da verdade
|
||||
do `__all__`.
|
||||
|
||||
Versão: `0.6.35` · Última varredura: 2026-08-19
|
||||
Versão: `0.13.1` · Última varredura: 2026-09-22
|
||||
|
||||
> **Por que existem pacotes aqui.** `writer.py` tinha 4.199 linhas e `models.py`
|
||||
> 1.091, cada um com muitos assuntos dentro. Viraram pacotes com um módulo por
|
||||
@@ -21,14 +21,16 @@ Versão: `0.6.35` · Última varredura: 2026-08-19
|
||||
|
||||
| Módulo / pacote | Linhas | Papel |
|
||||
|-----------------|-------:|-------|
|
||||
| `writer/` | 4.687 | **Edição e escrita de FCPXML** — o coração |
|
||||
| `writer/` | 5.377 | **Edição e escrita de FCPXML** — o coração |
|
||||
| `models/` | 1.195 | Data classes e enums |
|
||||
| `text_layout.py` | 901 | Diagramação das legendas dinâmicas |
|
||||
| `rough_cut.py` | 798 | Geração de timelines novas |
|
||||
| `model_manager.py` | 748 | Modelos Whisper: catálogo, download, config |
|
||||
| `voice_timeline.py` | 600 | O JSON de voz que a IA lê |
|
||||
| `analise.py` | 218 | `AnalisadorDeArquivo` — orquestra transcrição/diarização/ênfase/emoção e monta o `_voice_timeline.json`; usado por `voice_timeline.py` |
|
||||
| `phrase_review.py` | 547 | Revisão de frases (etapa 5 do assistente) |
|
||||
| `speaker_review.py` | 207 | Revisão de falantes (etapa 3 do assistente) |
|
||||
| `speaker_review.py` | 208 | Revisão de falantes (etapa 3 do assistente) |
|
||||
| `transcription/` | 325 | Pacote: `engine.py` (adapter faster-whisper), `segments.py`/`text.py`/`timestamps.py` (operações puras sobre transcript); `transcribe.py` é a fachada de compatibilidade |
|
||||
| `collision.py` | 472 | Colisão entre títulos na tela |
|
||||
| `font_metrics.py` | 445 | Largura real de glifos por fonte |
|
||||
| `templates.py` | 387 | Templates de timeline |
|
||||
@@ -68,8 +70,9 @@ editorial, todos operando sobre o mesmo documento e os mesmos índices.
|
||||
| `document.py` | 170 | Assets de vídeo, timebases, `write_fcpxml` |
|
||||
| `markers.py` | 165 | Marcadores: um, por timecode, em lote |
|
||||
| `audio.py` | 162 | Clipes de áudio e cama musical |
|
||||
| `generator.py` | 90 | `FCPXMLWriter` — orquestra a criação do zero (estado + delegação) |
|
||||
| `builders.py` | — | Um builder por tipo de elemento: `FormatBuilder`, `AssetBuilder`, `MarkerBuilder`, `KeywordBuilder`, `ClipBuilder`, `SequenceBuilder`, `LibraryBuilder` |
|
||||
| `generator.py` | 94 | `FCPXMLWriter` — orquestra a criação do zero (estado + delegação) |
|
||||
| `builders.py` | 174 | Um builder por tipo de elemento: `FormatBuilder`, `AssetBuilder`, `MarkerBuilder`, `KeywordBuilder`, `ClipBuilder`, `SequenceBuilder`, `LibraryBuilder` |
|
||||
| `adjustment.py` | 140 | `ClipDeAjuste` — camada de ajuste (filtros `filter-video`/`filter-audio` direto no `<clip>`, sem uso ainda em `server_tools`/`admin/api`) |
|
||||
| `reorder.py` | 126 | Reordenar e recalcular offsets |
|
||||
| `trim.py` | 125 | Aparar e propagar o ripple |
|
||||
| `transitions.py` | 94 | Transições entre vizinhos |
|
||||
|
||||
@@ -51,6 +51,9 @@ que merece entrada.
|
||||
| 31 | 2026-08-24 | Revisão de falantes ("Quem fica na edição") salvava certo, mas etapa 4 (Revisão de frases) lia a timeline crua, ignorando falantes mutados/linhas riscadas | `resolvido` |
|
||||
| 32 | 2026-08-24 | Separar legendas dinâmicas de convencionais por role: `<title>` aceita `role` (CDATA), NÃO `videoRole` — este último é DTD-inválido para títulos e quebra a validação | `resolvido` |
|
||||
| 33 | 2026-09-22 | Legenda comum sobreposta à composição dinâmica em `generate_subtitles_by_emphasis`; regenerar acumulava títulos em vez de substituir | `resolvido` |
|
||||
| 34 | 2026-09-22 | `ClipDeAjuste` gerava wrapper `<adjustment>` inválido no DTD; `code/WHISPERX` era 2,6 GB de backup órfão que inflava o lint quando rodado com `--exclude` explícito | `resolvido` |
|
||||
| 35 | 2026-09-22 | Indexação RAG (`admin/update_rag.py`) abortava a transação inteira ao achar um chunk que estoura o contexto do modelo de embedding | `resolvido` |
|
||||
| 36 | 2026-09-23 | `.gitignore` com regra `models/` solta escondia do git o pacote inteiro `fcpxml/models/` (dados do engine), não só o cache do Whisper | `resolvido` |
|
||||
|
||||
> Mantenha o índice acima sempre sincronizado com as entradas mais recentes.
|
||||
|
||||
@@ -1732,3 +1735,161 @@ conjunto de títulos por cima do anterior em vez de substituí-lo.
|
||||
skipped, 1 falha e 1 erro de ambiente sem relação com a mudança (WhisperX/
|
||||
`extract_pitch` ausente, torchcodec sem libs de sistema); lint dos arquivos
|
||||
alterados limpo.
|
||||
|
||||
---
|
||||
|
||||
### Entrada 34 — 2026-09-22: `<adjustment>` inválido no DTD e `WHISPERX` órfão inflando o lint
|
||||
|
||||
**Sintoma 1:** `fcpxml/writer/adjustment.py` (`ClipDeAjuste`, código de uma
|
||||
sessão anterior não commitado) montava
|
||||
`<clip><adjustment><filter-video .../></adjustment></clip>` para camadas de
|
||||
ajuste. Nada usava o módulo ainda (sem chamada em `server_tools`/
|
||||
`admin/api`), mas ficava pronto para alguém reusar do jeito errado.
|
||||
|
||||
**Causa 1:** o DTD real da Apple (`FCPXMLv1_13.dtd`) não define nenhum
|
||||
elemento `<adjustment>`. A produção real de `<clip>` é
|
||||
`(note?, %timing-params;, %intrinsic-params;, (spine|(%clip_item;)|caption)*,
|
||||
(%marker_item;)*, audio-channel-source*, (%video_filter_item;)*,
|
||||
filter-audio*, metadata?)` — ou seja, `filter-video`/`filter-audio` são
|
||||
filhos diretos do `<clip>`, sem wrapper, e nessa ordem (vídeo antes de
|
||||
áudio).
|
||||
|
||||
**Solução 1:** `ClipDeAjuste.criar()` agora anexa os filtros direto no
|
||||
`<clip>`, ordenados com vídeo antes de áudio
|
||||
(`sorted(filtros, key=lambda f: f.tag != "filter-video")`). Teste de
|
||||
regressão novo: `tests/test_writer_adjustment.py` (sem wrapper, ordem
|
||||
correta, um `<effect>` por `uid` em `resources`).
|
||||
|
||||
**Sintoma 2 (achado ao investigar o mesmo módulo):** um `ruff check .
|
||||
--exclude docs/` rodado manualmente no início desta sessão acusou **510
|
||||
erros** — muito acima do que a suíte normalmente reporta.
|
||||
|
||||
**Causa 2:** `code/WHISPERX` era uma pasta `.git` solta de **2,6 GB** dentro
|
||||
de `code/` (não um submodule registrado — sem `.gitmodules`), contendo
|
||||
cópias/backups congelados do próprio projeto, incluindo uma cópia inteira e
|
||||
antiga de `fcp-mcp-server-main` dentro de si mesma. O `pyproject.toml` já
|
||||
excluía `WHISPERX/` do lint por padrão (`[tool.ruff] exclude = ["docs/",
|
||||
"WHISPERX/"]`), mas passar `--exclude docs/` na linha de comando
|
||||
**sobrescreve** esse `exclude` em vez de complementá-lo — foi assim que o
|
||||
lint passou a varrer os 2,6 GB de código velho lá dentro. Confirmado por
|
||||
grep que só 3 arquivos no código ativo referenciam "WHISPERX", todos em
|
||||
comentários explicativos (`fcpxml/diarize.py`, `tests/test_diarize.py`,
|
||||
`admin/api/shared.py`) — nenhum import ou caminho real dependia da pasta.
|
||||
|
||||
**Solução 2:** pasta movida para `~/Archives/G-ART-WHISPERX-backup` (fora do
|
||||
workspace git), copiada com `rsync -a --no-perms` e conferida com
|
||||
`diff -rq` antes de remover o original. `WHISPERX/` também saiu do
|
||||
`exclude` do ruff em `code/pyproject.toml` (não faz mais sentido excluir um
|
||||
caminho que não existe mais em `code/`).
|
||||
|
||||
**Aprendizado:** (1) um wrapper de elemento "que faz sentido conceitualmente"
|
||||
não substitui checar o DTD real antes de escrever o gerador — o padrão do
|
||||
projeto (`dtd.py`, DTDs em `bm/*/FCPXMLv1_13.dtd`) existe exatamente para
|
||||
isso. (2) uma flag de linha de comando como `--exclude` em ferramentas de
|
||||
lint tipicamente **substitui** a config do projeto, não a estende — rodar
|
||||
`ruff check .` sem flags (herdando `pyproject.toml`) é o comando correto
|
||||
para refletir o gate real; qualquer variação manual com `--exclude` pode
|
||||
mentir sobre o estado do lint. (3) uma pasta de backup improvisada dentro do
|
||||
diretório ativo do projeto (mesmo que "só para não perder nada") é dívida
|
||||
que cresce sem ninguém perceber — 2,6 GB não apareceram de uma vez.
|
||||
|
||||
**Estado:** `resolvido` — `tests/test_writer_adjustment.py` (3 testes)
|
||||
passando; `admin/` trazido ao lint gate no mesmo commit (ver
|
||||
`09_MANUTENCAO.md` §2.3); suíte completa 1543 passando, 8 skipped, 1 falha
|
||||
+ 1 erro pré-existentes de outro trabalho em andamento (sem relação com
|
||||
esta correção).
|
||||
|
||||
### Entrada 35 — 2026-09-22: chunk grande demais derrubava a indexação RAG inteira
|
||||
|
||||
**Sintoma:** `admin/update_rag.command` (primeira indexação completa do
|
||||
G-ART, banco `rag_gart` recém-provisionado) morria sempre no mesmo ponto com
|
||||
`requests.exceptions.HTTPError: 500 Server Error` na chamada ao Ollama —
|
||||
sempre logo após imprimir `code/fcpxml/export.py`, ou seja, no arquivo
|
||||
seguinte na ordem alfabética.
|
||||
|
||||
**Causa raiz:** `code/fcpxml/font_metrics.py` é uma tabela de larguras de
|
||||
glifo (`METRICS = {...}`), texto extremamente denso em tokens (muitos
|
||||
números/pontuação curtos) — um chunk de ~4900 caracteres (dentro do limite
|
||||
`CHUNK_MAX_CHARS = 5000`) virou 2653 tokens no tokenizer do
|
||||
`nomic-embed-text`, estourando o contexto de 2048 tokens do servidor Ollama
|
||||
local (`llama.cpp`: "input length exceeds the context length"). Reproduzido
|
||||
isolando o arquivo e chamando `/api/embeddings` chunk a chunk — 6 dos 9
|
||||
chunks falhavam. `CHUNK_MAX_CHARS` mede caracteres, não tokens; assume
|
||||
implicitamente ~1 token por poucos caracteres, o que não vale para conteúdo
|
||||
não-prosa (tabelas numéricas, JSON denso).
|
||||
|
||||
Segundo problema, apontado por que a primeira tentativa não recuperou nada:
|
||||
`admin/update_rag.py::index()` roda a varredura inteira (centenas de
|
||||
arquivos) em **uma única transação**, com `commit()` só no fim e
|
||||
`rollback()` em qualquer exceção — um único chunk problemático em um único
|
||||
arquivo descartava a indexação inteira, mesmo que os outros 300+ arquivos
|
||||
já tivessem embedado e inserido com sucesso.
|
||||
|
||||
**Solução:** `_embed()` agora detecta essa resposta específica do Ollama
|
||||
(`ChunkTooLarge`, checado por `500` + `"context length"` no corpo) e o loop
|
||||
principal captura essa exceção por chunk, pula só aquele chunk (aviso em
|
||||
stderr) e continua o arquivo — sem abortar a transação. Não trunca nem
|
||||
reduz `CHUNK_MAX_CHARS` globalmente (afetaria todo o corpus por causa de
|
||||
poucos arquivos atípicos); a lacuna fica só nos poucos chunks realmente
|
||||
grandes demais, e o resto do arquivo ainda fica pesquisável.
|
||||
|
||||
**Aprendizado:** um limite de chunk em caracteres é uma aproximação, não uma
|
||||
garantia de contexto — arquivos de dados brutos (tabelas, mapeamentos
|
||||
numéricos, JSON/CSV embutido em `.py`) tokenizam bem mais denso que prosa ou
|
||||
código comum e podem violar o limite do modelo mesmo dentro do teto de
|
||||
caracteres. Uma indexação em lote sobre centenas de arquivos não deve ficar
|
||||
tudo-ou-nada numa única transação: uma falha isolada e recuperável (chunk
|
||||
específico, arquivo específico) deve ser contida ali, não descartar o
|
||||
trabalho inteiro já validado.
|
||||
|
||||
**Estado:** `resolvido` — indexação completa rodou até o fim: 304 arquivos,
|
||||
1702 chunks, 0 removidos. Também nesta sessão: criada a pasta `rag/` na raiz
|
||||
(schema, busca híbrida `search.py`/`search_gart.sh`, `SETUP.md`) — ver
|
||||
`rag/README.md` para a divisão de responsabilidades com `admin/update_rag.py`.
|
||||
|
||||
---
|
||||
|
||||
### Entrada 36 — 2026-09-23: `.gitignore` escondia `fcpxml/models/` inteiro do git
|
||||
|
||||
**Sintoma:** ao investigar por que um `git diff` de um arquivo recém-editado
|
||||
(`fcpxml/models/timeline.py`, durante a correção da Entrada 34) não mostrava
|
||||
nada, `git status` também não listava o arquivo como modificado nem como
|
||||
untracked — como se ele simplesmente não existisse para o git.
|
||||
|
||||
**Causa:** `.gitignore` tinha a regra solta `models/` (comentada como
|
||||
"WhisperX models cache", pensada para ignorar o cache de ~11 GB de modelos
|
||||
Whisper baixados em `code/models/`). Uma regra sem `/` inicial no
|
||||
`.gitignore` casa com **qualquer diretório com esse nome em qualquer
|
||||
profundidade** — não só `code/models/`, mas também `code/fcpxml/models/`, o
|
||||
pacote de data classes (`TimeValue`, `Clip`, `Timeline`, `Marker`, etc.) que
|
||||
sustenta todo o engine. Confirmado: `git ls-tree -r HEAD` não tem nenhum
|
||||
`fcpxml/models.py` nem `fcpxml/models/` em nenhum commit do histórico — o
|
||||
pacote inteiro (1.234 linhas, 7 módulos) só existia em disco, sem nenhuma
|
||||
proteção de versionamento, desde que a divisão de `models.py` em pacote foi
|
||||
feita (sessão anterior, nunca commitada).
|
||||
|
||||
**Risco:** qualquer operação que limpa arquivos não rastreados
|
||||
(`git clean -fd`, reinstalar do zero, trocar de máquina via `git clone`)
|
||||
apagaria essa base sem chance de recuperação — nenhum commit para reverter.
|
||||
|
||||
**Solução:** regra trocada para `/code/models/` (ancorada na raiz do repo,
|
||||
só o cache real), preservando `whisper/` (sem uso hoje, mas inofensiva) e
|
||||
tudo mais. Confirmado com `git check-ignore -v`: `fcpxml/models/timeline.py`
|
||||
não é mais ignorado; `code/models/models--Systran--faster-whisper-base`
|
||||
continua ignorado. `fcpxml/models/` passou a aparecer como `??` no
|
||||
`git status` — visível, pronto para ser commitado quando o dono do trabalho
|
||||
revisar.
|
||||
|
||||
**Aprendizado:** regra de `.gitignore` sem `/` inicial (ex.: `models/`) casa
|
||||
em qualquer profundidade da árvore — é fácil escrever pensando só no caso
|
||||
que motivou a regra (um cache na raiz) e esquecer que o mesmo nome de pasta
|
||||
pode existir, com sentido completamente diferente, dentro do código-fonte.
|
||||
Regra de bolso: nomes de pasta genéricos (`models/`, `build/`, `cache/`,
|
||||
`data/`) no `.gitignore` deveriam quase sempre vir ancorados (`/caminho/
|
||||
exato/`), a menos que a intenção seja mesmo ignorar toda ocorrência do nome
|
||||
em qualquer lugar da árvore.
|
||||
|
||||
**Estado:** `resolvido` — regra corrigida, `fcpxml/models/` confirmado
|
||||
visível ao git (não commitado ainda; fica para quem já está com esse
|
||||
trabalho em andamento decidir quando commitar). Nenhum código alterado,
|
||||
só o `.gitignore`.
|
||||
|
||||
@@ -7,7 +7,7 @@ Este é o documento de rota. Os outros descrevem o que **é**; este diz o que
|
||||
**fazer** e por onde começar quando chega uma implementação, uma melhoria ou
|
||||
uma correção.
|
||||
|
||||
Última varredura: 2026-08-21 · 1.498 testes · lint zerado (fora de server.py/ai_edit.py/llm_local.py, pré-existentes)
|
||||
Última varredura: 2026-09-22 · 1.543 testes passando (+1 falha pré-existente em `test_forced_align.py` e +1 erro pré-existente em `test_refine_voice_timeline_tool.py`, ver §2.6) · lint zerado em `code/` e em `admin/` (fora de server.py/ai_edit.py/fcpxml/analise.py, pré-existentes — outro trabalho em andamento na branch)
|
||||
|
||||
---
|
||||
|
||||
@@ -48,10 +48,12 @@ o olho do usuário. Não é para sair criando suíte de UI — mas lógica pura
|
||||
foi parar na camada de tela (cálculo de trim, mapeamento de tempo) deveria
|
||||
descer para o Python, onde já existe rede.
|
||||
|
||||
### 2.3 `admin/` fica fora do lint
|
||||
`run_after_fix.sh` roda o ruff de dentro de `code/`, então `admin/` — 1.751
|
||||
linhas de código que o app depende para funcionar — nunca é verificado.
|
||||
Incluir mexe no gate, então é decisão consciente, não esquecimento.
|
||||
### 2.3 ~~`admin/` fica fora do lint~~ — resolvido em 2026-09-22
|
||||
`run_after_fix.sh` agora roda um segundo passo (`ruff check --config
|
||||
pyproject.toml ../admin/`) com a mesma config do engine. Precisou de
|
||||
`# noqa: E402` em 6 imports de `admin/models_api.py`/`admin/models_gui.py`
|
||||
(padrão `sys.path.insert` antes do import local, convenção já usada no
|
||||
projeto). Lint de `admin/` está zerado.
|
||||
|
||||
### 2.4 Confirmações visuais pendentes no FCP
|
||||
Várias entradas do `05_EXPERIENCIAS.md` estão marcadas como resolvidas *no XML*
|
||||
@@ -68,9 +70,39 @@ causa raiz. Não investigado a fundo ainda.
|
||||
→ `fcpxml/writer/cut.py` (`cut_clip_ranges`, `min_keep_seconds`), padding do
|
||||
`remove_media_silence`.
|
||||
|
||||
### 2.6 Submódulo `WHISPERX` com conteúdo modificado e não commitado
|
||||
Está fora dos commits de propósito, porque ninguém verificou o que mudou lá
|
||||
dentro. Precisa ser olhado e resolvido — ou commitado, ou revertido.
|
||||
### 2.6 ~~`fcpxml/writer/adjustment.py` gerava um wrapper `<adjustment>` inválido~~ — resolvido em 2026-09-22
|
||||
`ClipDeAjuste` embrulhava filtros num `<clip><adjustment>...</adjustment></clip>`,
|
||||
que não existe no DTD real da Apple. Corrigido para anexar
|
||||
`filter-video`/`filter-audio` direto como filhos do `<clip>` (na ordem que o
|
||||
DTD exige: vídeo antes de áudio). Teste de regressão em
|
||||
`tests/test_writer_adjustment.py`. Segue sem uso em `server_tools`/`admin/api`
|
||||
— só deixou de estar pronto pra alguém reusar do jeito errado.
|
||||
→ `05_EXPERIENCIAS.md` #34.
|
||||
|
||||
### 2.7 `test_refine_voice_timeline_tool.py` quebrado: `voice_timeline.extract_pitch` ausente
|
||||
`TestRefineVoiceTimelineHandler::test_max_zooms_caps_the_list` tenta
|
||||
`monkeypatch.setattr(vt, "extract_pitch", ...)` mas `fcpxml/voice_timeline.py`
|
||||
não tem mais (ou nunca teve, nesta branch) essa função. Pertence ao trabalho
|
||||
de análise de voz já em andamento nesta branch (`voice_timeline.py`
|
||||
modificado, não commitado) — não investigado a fundo, só registrado aqui
|
||||
para não se perder.
|
||||
→ `fcpxml/voice_timeline.py`, `tests/test_refine_voice_timeline_tool.py`.
|
||||
|
||||
### 2.8 ~~Submódulo `WHISPERX` com conteúdo modificado e não commitado~~ — resolvido em 2026-09-22
|
||||
Não era um submódulo git registrado (sem `.gitmodules`) — era uma pasta
|
||||
`.git` solta de 2,6 GB dentro de `code/`, com cópias/backups congelados do
|
||||
próprio projeto (`WHISPERX_backup_88476/`, uma cópia inteira e antiga de
|
||||
`fcp-mcp-server-main`). Só 3 referências no código ativo, todas em
|
||||
comentários (`fcpxml/diarize.py`, `tests/test_diarize.py`,
|
||||
`admin/api/shared.py`), nenhum import ou caminho dependia dela. Além do
|
||||
peso morto, ela também inflava qualquer lint rodado com `--exclude`
|
||||
explícito (que sobrescreve o `exclude` do `pyproject.toml`) — foi assim que
|
||||
um `ruff check . --exclude docs/` chegou a acusar 510 erros, quase todos
|
||||
dentro dela. Movida para `~/Archives/G-ART-WHISPERX-backup` (fora do
|
||||
workspace git), copiada e verificada (`diff -rq`) antes de remover o
|
||||
original. `WHISPERX/` também saiu do `exclude` do ruff em
|
||||
`code/pyproject.toml` — não faz mais sentido excluir um caminho que não
|
||||
existe mais dentro de `code/`.
|
||||
|
||||
---
|
||||
|
||||
@@ -102,6 +134,8 @@ quanto arquivo gigante.
|
||||
```bash
|
||||
cd code && ./Engine/run_after_fix.sh # lint zerado + 1.498 testes
|
||||
admin/run_app.command # se mexeu no app (padrão de revisão)
|
||||
admin/run.command # app + atualização incremental da RAG
|
||||
rag/search_gart.sh "consulta" # busca híbrida no índice RAG (ver rag/README.md)
|
||||
```
|
||||
|
||||
E, além do script:
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# 10 - Mapa de Reestruturacao de Funcionalidades
|
||||
|
||||
> Escopo: roteiro pratico para reorganizar o codigo sem quebrar o produto.
|
||||
> Baseado na varredura de 2026-08-24 sobre engine Python, ponte do app,
|
||||
> ferramentas MCP e app SwiftUI.
|
||||
|
||||
## 1. Diagnostico rapido
|
||||
|
||||
O projeto ja tem uma arquitetura-alvo correta: `fcpxml/` como engine puro,
|
||||
`server.py` + `server_tools/` como camada MCP, `admin/` como ponte JSON-lines
|
||||
do app e `MacApp/` como interface. A melhoria agora nao e "reinventar" a
|
||||
arquitetura, e reduzir os pontos onde as responsabilidades ainda se misturam.
|
||||
|
||||
### Pontos fortes
|
||||
|
||||
- Engine Python bem testado e com regra clara: logica de timeline fica em
|
||||
`fcpxml/`.
|
||||
- `writer/` ja foi quebrado em mixins por assunto, preservando API publica.
|
||||
- `server.py` funciona como composition root e usa dispatch por dicionario.
|
||||
- Documentacao interna registra decisoes, armadilhas e padroes do projeto.
|
||||
- Fluxos criticos tem testes extensos em `code/tests/`.
|
||||
|
||||
### Dores atuais
|
||||
|
||||
- Alguns arquivos voltaram a virar centros de gravidade:
|
||||
- `server_tools/voice.py` (~999 linhas)
|
||||
- `server_tools/subtitles.py` (~760 linhas)
|
||||
- `MacApp/Sources/WizardView.swift` (~979 linhas)
|
||||
- `MacApp/Sources/TranscriptionView.swift` (~843 linhas)
|
||||
- `fcpxml/model_manager.py` (~748 linhas)
|
||||
- `admin/` e `server_tools/` expõem fluxos parecidos por caminhos diferentes,
|
||||
o que aumenta risco de uma funcionalidade existir no MCP e faltar no app.
|
||||
- ~~`admin/` ainda fica fora do lint principal~~ — resolvido na Fase 0
|
||||
(2026-09-22): `admin/` entrou no gate de `run_after_fix.sh`.
|
||||
- ~~`WHISPERX` e backups aparecem junto da base ativa~~ — resolvido na
|
||||
Fase 0 (2026-09-22): movido para fora do workspace git.
|
||||
- O app SwiftUI quase nao tem rede automatizada; compilar nao garante que uma
|
||||
tela abre.
|
||||
|
||||
## 2. Mapa de dominios desejado
|
||||
|
||||
```text
|
||||
Produto
|
||||
MacApp/ Interface e experiencia do usuario
|
||||
admin/ Ponte JSON-lines do app
|
||||
server.py + server_tools/ Entrada MCP
|
||||
|
||||
Engine
|
||||
fcpxml/models/ Dados e contratos
|
||||
fcpxml/parser.py FCPXML -> objetos
|
||||
fcpxml/writer/ Escrita e edicao de XML
|
||||
fcpxml/voice_* Analise e decisoes por voz
|
||||
fcpxml/text_layout.py Layout de legendas
|
||||
fcpxml/model_manager.py Catalogo, configs e modelos
|
||||
|
||||
Suporte
|
||||
tests/ Rede automatizada
|
||||
Engine/docs/ Decisoes e operacao
|
||||
examples/ Fixtures de uso
|
||||
|
||||
Legado / referencia
|
||||
WHISPERX/ Deve sair do caminho ativo ou virar referencia clara
|
||||
```
|
||||
|
||||
Regra de organizacao: uma funcionalidade nasce no engine, depois ganha duas
|
||||
portas finas se necessario: uma tool MCP em `server_tools/` e um comando do app
|
||||
em `admin/api/`.
|
||||
|
||||
## 3. Reestruturacao por fases
|
||||
|
||||
### Fase 0 - Higiene antes de mexer — `concluída em 2026-09-22`
|
||||
|
||||
Objetivo: reduzir ruido e proteger a base antes de mover codigo.
|
||||
|
||||
- ~~Decidir o destino de `code/WHISPERX`~~ — não era submodule (sem
|
||||
`.gitmodules`), era 2,6 GB de backups órfãos do próprio projeto sem
|
||||
nenhuma referência ativa. Movido para `~/Archives/G-ART-WHISPERX-backup`
|
||||
(fora do workspace git), copiado com `rsync` e conferido com `diff -rq`
|
||||
antes de remover o original. Detalhe: essa pasta também inflava qualquer
|
||||
`ruff check --exclude docs/` manual (a flag sobrescrevia o `exclude` do
|
||||
`pyproject.toml`, que já ignorava `WHISPERX/`) — ver `05_EXPERIENCIAS.md`
|
||||
#34.
|
||||
- ~~Incluir `admin/` em uma checagem de lint separada antes de colocar no
|
||||
gate obrigatório~~ — checado com a config real do projeto (não o default
|
||||
do ruff): só 6 erros, todos `E402` por `sys.path.insert` antes de import
|
||||
local. Resolvido com `# noqa: E402` (convenção já usada no projeto) e
|
||||
`admin/` entrou direto no gate obrigatório (`run_after_fix.sh`, passo
|
||||
2/3), sem precisar de etapa intermediária "separada".
|
||||
- Corrigido de quebra: `fcpxml/writer/adjustment.py` gerava um `<adjustment>`
|
||||
inválido no DTD — não estava no escopo original da Fase 0, mas surgiu na
|
||||
investigação e era pequeno o bastante para resolver junto (ver
|
||||
`05_EXPERIENCIAS.md` #34).
|
||||
- Atualizados: `02_MODULES.md` (versão, linhas de `writer/`, módulos novos
|
||||
`builders.py`/`adjustment.py`/`analise.py`/`transcription/`),
|
||||
`09_MANUTENCAO.md` (contagem de testes/lint, itens §2.3/§2.6/§2.8
|
||||
resolvidos, novo item §2.7 registrando `test_refine_voice_timeline_tool`).
|
||||
- **Pendente, não fechado nesta rodada:** "documentar oficialmente quais
|
||||
pastas são produto ativo, legado e backup" como um documento à parte —
|
||||
o que existia de fato como "legado" (`WHISPERX`) já foi resolvido, não
|
||||
sobrou candidato claro para justificar um novo documento agora.
|
||||
|
||||
Entrega obtida: lint de `admin/` no gate, `code/writer/adjustment.py`
|
||||
correto e testado, ~2,6 GB fora do caminho ativo, docs sincronizados com o
|
||||
código atual.
|
||||
|
||||
### Fase 1 - Contratos entre camadas
|
||||
|
||||
Objetivo: impedir que MCP, app e engine driftam entre si.
|
||||
|
||||
- Criar um registro unico de capacidades, por exemplo:
|
||||
- nome interno da funcionalidade;
|
||||
- funcao pura do engine;
|
||||
- handler MCP, se existir;
|
||||
- comando `admin`, se existir;
|
||||
- tela Swift, se existir;
|
||||
- testes associados.
|
||||
- Adicionar teste que detecta comandos importantes presentes no MCP mas ausentes
|
||||
na ponte do app, quando fizer sentido.
|
||||
- Padronizar o retorno dos comandos `admin/api`: `ok`, `path`, `message`,
|
||||
`error`, `unchanged`, `artifacts`.
|
||||
|
||||
Entrega esperada: mapa vivo de funcionalidades e menos "funciona no Claude,
|
||||
nao aparece no app".
|
||||
|
||||
### Fase 2 - Dividir `server_tools/voice.py`
|
||||
|
||||
Objetivo: separar o fluxo de voz por etapas reais do produto.
|
||||
|
||||
Divisao sugerida:
|
||||
|
||||
```text
|
||||
server_tools/voice/
|
||||
__init__.py Reexporta TOOLS e HANDLERS
|
||||
analysis.py analyze_voice_features, build_voice_timeline
|
||||
speakers.py diarize_media, remove_speakers
|
||||
refinement.py refine_voice_timeline, remove_speech_gaps
|
||||
actions.py apply_voice_actions
|
||||
local_ai.py generate_voice_script
|
||||
config.py get/save_voice_analysis_config
|
||||
```
|
||||
|
||||
Cuidados:
|
||||
|
||||
- Manter os nomes publicos reexportados para nao quebrar testes/imports.
|
||||
- Mover em uma etapa por arquivo, rodando testes de voz a cada passo.
|
||||
- Nao mover regra de negocio para `server_tools/voice/`; se aparecer regra
|
||||
nova, ela deve descer para `fcpxml/voice_*`.
|
||||
|
||||
Testes minimos: `test_voice_actions.py`, `test_voice_actions_tool.py`,
|
||||
`test_voice_timeline.py`, `test_voice_timeline_tool.py`, `test_diarize.py`,
|
||||
`test_voice_features.py`.
|
||||
|
||||
### Fase 3 - Separar `fcpxml/model_manager.py`
|
||||
|
||||
Objetivo: reduzir mistura entre catalogo, download, configuracao e estado.
|
||||
|
||||
Divisao sugerida:
|
||||
|
||||
```text
|
||||
fcpxml/model_manager/
|
||||
__init__.py API publica atual
|
||||
catalog.py models.json, recomendados, metadata
|
||||
storage.py diretorios, instalados, migracao
|
||||
download.py download/cancel/progresso
|
||||
transcription_config.py modelo selecionado, idioma
|
||||
voice_config.py analise de voz, silencio, legendas
|
||||
```
|
||||
|
||||
Cuidados:
|
||||
|
||||
- Preservar imports atuais via `__init__.py`.
|
||||
- Separar funcoes puras de funcoes com I/O para facilitar teste.
|
||||
- Nao acoplar config do app a nomes de tela Swift.
|
||||
|
||||
Testes minimos: `test_models.py`, `test_models_api.py` se existir,
|
||||
`test_voice_analysis_config.py`, `test_project_config.py`.
|
||||
|
||||
### Fase 4 - Reorganizar o Assistente SwiftUI
|
||||
|
||||
Objetivo: tornar o fluxo de 7 etapas legivel e testavel por partes.
|
||||
|
||||
Divisao sugerida:
|
||||
|
||||
```text
|
||||
MacApp/Sources/Wizard/
|
||||
WizardView.swift Casca, navegacao e estado global
|
||||
WizardState.swift Estado do fluxo e canAdvance
|
||||
ProjectStepView.swift
|
||||
TranscribeStepView.swift
|
||||
VoiceAnalysisStepView.swift
|
||||
AIScriptStepView.swift
|
||||
ReviewStepHost.swift
|
||||
ProcessStepView.swift
|
||||
DoneStepView.swift
|
||||
```
|
||||
|
||||
Boas praticas para essa fase:
|
||||
|
||||
- Extrair primeiro views pequenas, sem alterar comportamento.
|
||||
- Depois extrair calculos puros de `canAdvance`, nomes de arquivos e selecao
|
||||
de artefatos para tipos testaveis.
|
||||
- Usar harness manual documentado em `08_APP_MACOS.md` para abrir as telas
|
||||
tocadas.
|
||||
|
||||
Entrega esperada: cada etapa do wizard vira um arquivo com responsabilidade
|
||||
unica.
|
||||
|
||||
### Fase 5 - Unificar validacao e saida da ponte `admin/`
|
||||
|
||||
Objetivo: deixar os comandos do app tao disciplinados quanto os handlers MCP.
|
||||
|
||||
- Criar helpers de path/output equivalentes aos de `server_tools/_shared`,
|
||||
ou mover helpers comuns para uma camada compartilhada que nao saiba de MCP.
|
||||
- Trocar chamadas diretas a `server.generate_output_path` por helper de dominio
|
||||
que nao puxe `server.py` quando a ponte so precisa de path.
|
||||
- Adicionar lint de `admin/` ao fluxo de manutencao depois de corrigir erros
|
||||
existentes.
|
||||
|
||||
Entrega esperada: ponte mais fina, menos import acidental de transporte MCP.
|
||||
|
||||
### Fase 6 - Tests e gates de seguranca
|
||||
|
||||
Objetivo: fazer a reorganizacao ser barata de continuar.
|
||||
|
||||
- Criar testes de "arquitetura":
|
||||
- `fcpxml/` nao importa `server`, `server_tools` nem `admin`;
|
||||
- handlers MCP sempre retornam via `_text_result`;
|
||||
- comandos `admin` retornam JSON no formato padrao.
|
||||
- Criar teste de import publico para garantir que reexports antigos continuam.
|
||||
- Para SwiftUI, manter harnesses por tela critica ate existir um build mais
|
||||
estruturado.
|
||||
|
||||
Entrega esperada: mover arquivos deixa de ser aposta.
|
||||
|
||||
## 4. Prioridade recomendada
|
||||
|
||||
1. Fase 0: limpar mapa ativo vs legado.
|
||||
2. Fase 1: criar registro de capacidades.
|
||||
3. Fase 2: dividir voz em `server_tools`.
|
||||
4. Fase 5: fortalecer `admin/`.
|
||||
5. Fase 4: quebrar `WizardView`.
|
||||
6. Fase 3: dividir `model_manager.py`.
|
||||
7. Fase 6: ampliar gates conforme as fases estabilizam.
|
||||
|
||||
Motivo: primeiro se reduz incerteza, depois se separa o arquivo que mais muda
|
||||
no fluxo novo de voz, e so entao se mexe nas telas maiores.
|
||||
|
||||
## 5. Checklist para cada refatoracao
|
||||
|
||||
- Mover sem mudar comportamento na primeira passada.
|
||||
- Preservar API publica com reexports.
|
||||
- Rodar testes focados depois de cada movimento.
|
||||
- Rodar `cd code && ./Engine/run_after_fix.sh` antes de concluir.
|
||||
- Se mexeu em `MacApp/`, compilar e abrir a tela afetada.
|
||||
- Atualizar docs no mesmo commit.
|
||||
- Registrar aprendizado em `05_EXPERIENCIAS.md` quando houver bug real.
|
||||
|
||||
## 6. Principios de boas praticas para este projeto
|
||||
|
||||
- Engine puro: sem MCP, sem Swift, sem JSON de tela.
|
||||
- Camadas de entrada finas: validam, chamam engine, formatam resposta.
|
||||
- Tempo de timeline sempre racional (`TimeValue`), exceto metricas de audio e
|
||||
UI onde segundos float sao apenas apresentacao/analise.
|
||||
- Original nunca e sobrescrito.
|
||||
- XML sempre entra por `safe_xml.py`.
|
||||
- Dependencias opcionais continuam lazy.
|
||||
- Arquivo grande so e problema quando contem varios assuntos.
|
||||
- Toda funcionalidade importante deve ter dono, porta MCP/app documentada e
|
||||
teste correspondente.
|
||||
@@ -23,14 +23,19 @@ echo "==> [G-ART] Validação pós-correção iniciada..."
|
||||
echo " Diretório: $REPO_ROOT"
|
||||
echo ""
|
||||
|
||||
echo "==> 1/2 Lint (ruff) — deve passar com ZERO erros"
|
||||
echo "==> 1/3 Lint do engine (ruff, code/) — deve passar com ZERO erros"
|
||||
# A flag --exclude sobrescreve o exclude declarado em pyproject.toml
|
||||
# (que já ignora docs/ e WHISPERX/). Rode sem flag para herdar a config.
|
||||
# (que já ignora docs/). Rode sem flag para herdar a config.
|
||||
uv run ruff check .
|
||||
echo " Lint OK ✓"
|
||||
echo ""
|
||||
|
||||
echo "==> 2/2 Testes (pytest) — todos devem passar"
|
||||
echo "==> 2/3 Lint da ponte (ruff, admin/) — mesma config do engine"
|
||||
uv run ruff check --config pyproject.toml ../admin/
|
||||
echo " Lint OK ✓"
|
||||
echo ""
|
||||
|
||||
echo "==> 3/3 Testes (pytest) — todos devem passar"
|
||||
uv run pytest tests/ -v
|
||||
echo ""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user