# Spec: Limpeza interna — deduplicação e tipagem do write pipeline

## Objetivo
Eliminar duplicações de funções utilitárias e tipar o retorno do write pipeline, removendo também referências legadas no código.

## Contexto
A análise do codebase (2026-03-19) identificou três débitos técnicos:

1. **`_cosine_similarity()` duplicada em 5 módulos** — `entity_resolution.py`, `consolidation.py`, `clustering.py`, `memify.py`, `procedural.py`. Todas são cópias idênticas da mesma implementação (dot product / norms).
2. **`_strip_markdown_fences()` é uma função privada importada cross-module** — Definida em `write/extract.py`, importada por `write/reconcile.py`, `write/entity_resolution.py` e `read/reranker.py`. Funções `_` prefixadas são privadas por convenção; importá-las de outro módulo quebra o encapsulamento.
3. **Write pipeline retorna `dict` em vez de dataclass tipada** — `run_write_pipeline()` retorna `dict`, enquanto `run_read_pipeline()` retorna `ReadResult` dataclass. Inconsistência no padrão pipeline-orchestration.

Bônus: Docstring de `models.py` referencia "Personal Genius codebase" — referência legada que deve ser removida.

## Definition of Done

- [ ] **DD1**: `_cosine_similarity()` existe em **exatamente 1 local** (`src/arandu/_helpers.py`) e todos os 5 módulos que a usavam importam dela.
- [ ] **DD2**: `_strip_markdown_fences()` existe em **exatamente 1 local** (`src/arandu/_helpers.py`) como `strip_markdown_fences()` (sem `_` prefix — agora é API interna pública) e todos os importadores usam o novo path.
- [ ] **DD3**: `run_write_pipeline()` retorna um `WritePipelineResult` dataclass (não `dict`). `MemoryClient.write()` consome a dataclass.
- [ ] **DD4**: `models.py` docstring não contém "Personal Genius".
- [ ] **DD5**: `ruff check .` e `mypy src/` passam sem erros novos.
- [ ] **DD6**: `pytest` passa sem falhas.

## Escopo

### Dentro do escopo
- Criar `src/arandu/_helpers.py` com `cosine_similarity()` e `strip_markdown_fences()`
- Atualizar imports em: `entity_resolution.py`, `consolidation.py`, `clustering.py`, `memify.py`, `procedural.py`, `reconcile.py`, `reranker.py`, `extract.py`
- Criar `WritePipelineResult` dataclass em `write/pipeline.py`
- Atualizar `MemoryClient.write()` para consumir a dataclass
- Limpar docstring de `models.py`

### Anti-escopo
- **NÃO** alterar a API pública (`WriteResult`, `RetrieveResult`, `ScoredFact`) — são contratos do SDK
- **NÃO** renomear ou mover módulos existentes
- **NÃO** refatorar lógica de negócio — apenas mover/deduplicar funções utilitárias
- **NÃO** adicionar testes novos (os testes existentes devem continuar passando sem mudanças)
- **NÃO** alterar `read/pipeline.py` ou `ReadResult`

## Decisões Técnicas

### DT1: Onde colocar as funções utilitárias?
**Decisão:** `src/arandu/_helpers.py` (novo arquivo).
**Trade-off:** Poderia ir em `utils.py`, mas a convenção do projeto usa `_helpers.py` (já existe `read/_helpers.py`). Mantém consistência.
**Alternativa descartada:** Colocar em `constants.py` — esse arquivo é para tipos/constantes, não funções utilitárias.

### DT2: `strip_markdown_fences` com ou sem `_` prefix?
**Decisão:** Sem `_` prefix → `strip_markdown_fences()`. É uma função utilitária usada por 4 módulos — não é privada de nenhum deles. Manter `_` no novo módulo seria contraditório.
**Nota:** Manter a versão antiga `_strip_markdown_fences` em `extract.py` como alias deprecated não é necessário — nenhum consumidor externo importa de `extract.py`.

### DT3: `WritePipelineResult` vs expandir `WriteResult`
**Decisão:** Criar `WritePipelineResult` dataclass **interna** em `write/pipeline.py`. `WriteResult` (API pública em `client.py`) permanece inalterada. `MemoryClient.write()` mapeia de `WritePipelineResult` para `WriteResult`.
**Justificativa:** Segue o padrão do read pipeline (`ReadResult` interno → `RetrieveResult` público).

## Applicable Patterns

- **pipeline-orchestration** — O `WritePipelineResult` segue o mesmo padrão do `ReadResult`: dataclass interna no pipeline, mapeada para tipo público no client.
- **error-handling** — O pipeline continua retornando resultado seguro (dataclass com campos zerados) em caso de falha, em vez de dict vazio.

## Riscos

| Risco | Mitigação |
|---|---|
| Import circular ao criar `_helpers.py` | `_helpers.py` não importa nenhum módulo do arandu — só stdlib. Sem risco. |
| Testes quebram por import path mudado | Os testes importam de `conftest`, não de `extract.py` diretamente. Risco baixo. |
| `cosine_similarity` tem variações sutis entre módulos | Verificado: todas as 5 implementações são idênticas. Sem risco. |

## Budget
6 arquivos modificados/criados (dentro do budget ≤ 6 de escopo principal — os imports auxiliares são mudanças de 1 linha).
