O acervo wiki do usuário vive em disco como um diretório por pacote — items/<categoria>/<id>/ com sete arquivos + ponteiro raw — mas o ETL do funil só lê envelopes de UMA linha. A ponte é o walkFamily: enumera a família inteira, junta o master index e emite exatamente um package.jsonl por pacote, com header llm_wiki e uma evidence REAL destilada do próprio conteúdo. Read-only sobre o corpus, idempotente, e com a doutrina de exclusão INJETADA de cima.
Todo pacote wiki real (Bookmarks / Transcripts / Repos sob ~/Documents/Resources) é um DIRETÓRIO de arquivos separados: source.md, understanding.md, research-index.md, qa.md, metadata.json, chunks.jsonl, media_manifest.json e um ponteiro raw — raw.pointer.json ou um diretório raw/ (packages/ingestion/src/family-package.ts:14–22, 30–46). O pipeline ETL, por sua vez, "only reads single-line package.jsonl envelopes" — então o leitor readWikiPackageDir é "the first half of the bridge": carrega o pacote em memória tipada para o adapter serializar em uma linha (family-package.ts:19–22). Todo o IO passa por um PackageReader injetado; os testes nunca tocam a árvore real de Resources (family-package.ts:24–26).
Pense como… uma biblioteca antiga ganhando fichário: cada estante-pasta (o pacote) continua intocada no lugar; o bibliotecário escreve UMA ficha catalográfica por pasta — resumo real do conteúdo, status, gaps — e é só a ficha que circula pelo resto do prédio. Onde quebra: ficha de papel desatualiza; aqui a ficha é derivada dos arquivos e reescrever é proibido (append-only, no-op se já existe).
A evidence é o sinal T1 load-bearing — "the funnel feeds it straight to the local extraction model" — então ela é montada do conteúdo real: os corpos dos 3 primeiros chunks (EVIDENCE_TOP_CHUNKS), os primeiros 600 caracteres do understanding.md (EVIDENCE_UNDERSTANDING_CHARS) e o summary+tags do master record, tudo achatado de markdown e limitado a 2000 caracteres (EVIDENCE_MAX_CHARS) (family-adapter.ts:33–37, 52–57, 150–175). Pacote sem texto extraível nenhum não vira linha vazia: vira o erro permanente family.empty_evidence (family-adapter.ts:231–239).
O emitFamilyBridge cuida de UM pacote; walkFamily materializa a família inteira: enumera items/<categoria>/<id>/ (categorias e ids em ordem sorted, entradas ocultas tipo .DS_Store puladas — family-walk.ts:203–219), carrega index/<família>.master.jsonl num Map por canonical_package_path (linha ruim pulada; última linha do mesmo path vence — family-walk.ts:141–156) e chama o adapter uma vez por diretório. A sutileza central são os dois formatos de path mantidos distintos de propósito (family-walk.ts:37–42, 258–263):
| forma | exemplo | para quê |
|---|---|---|
| master-join key | items/design/195…173 | lookup no master index (o path é RELATIVO à família no disco, sem prefixo) |
| corpus-relative | Bookmarks/items/design/195…173 | o isExcluded injetado e o header — o 1º segmento É a família |
As três doutrinas do walker estão gravadas no header do arquivo (family-walk.ts:44–53): CONTRACTS-ONLY — este pacote nunca importa @alembic/etl; o predicado isExcluded é INJETADO pela camada de cima (a CLI injeta o canônico do etl em commands.ts:620–621), então a camada de baixo não carrega doutrina de destilação; COLLECT, NEVER ABORT — falha por pacote vira item de errors[] e a caminhada continua; APPEND-ONLY / IDEMPOTENT — a segunda caminhada escreve 0. O que fica excluído pelo predicado do etl: subárvores como Repos/Models e Repos/Prompts (family-adapter.ts:44–47).
A família Transcripts tem uma particularidade de layout REAL: o master index não fica em Transcripts/index/, e sim em Transcripts/YouTube/index/transcripts.master.jsonl — confirmado no próprio doc do walker (family-walk.ts:31–35). Isso funciona por design: walkFamily aceita QUALQUER diretório que contenha items/ + index/ como familyRoot; a identidade da família (identity.family) e o stem do índice (indexFamily) são parâmetros separados do path. Na CLI, resolveWikiFamily tenta classificar o basename e depois o path completo pelo 1º segmento; quando nada resolve (ex.: basename YouTube), --family Transcripts pina a identidade e o root aninhado caminha normalmente (commands.ts:560–580).
classifyFamily lê o PRIMEIRO segmento do path relativo ao corpus (packages/etl/src/priors.ts:70–76). Consequência no distill: apontar alembic distill para a RAIZ DA FAMÍLIA (ex.: …/Resources/Bookmarks) faz cada path relativo começar em items/… ⇒ família Unknown ⇒ residue 0, silencioso. Aponte para o PAI (…/Resources) — foi esse detalhe que separou "rota learnings nunca produziu" de 1.022 learnings em 06-07. Escopo de uma família só = o truque cpio com os package.jsonl.A cadeia típica completa, cada elo um comando inspecionável: alembic wiki-bridge <família> (escreve as bridge lines) → alembic distill <pai-de-Resources> --offline (roteia as linhas prefixadas pela família para residue/T1) → alembic embed-index <família> (índice offline de embeddings) → alembic search "<consulta>" <família> (recuperar os chunks mais próximos — a próxima lição).
walkFamily mantém DOIS formatos de path para o mesmo pacote (com e sem o prefixo da família)?canonical_package_path é family-relativo (items/<cat>/<id>) — essa é a MASTER-JOIN KEY; mas o predicado de exclusão do etl e a classificação de família leem o path CORPUS-relativo, cujo 1º segmento é a família (family-walk.ts:37–42, 258–263). Misturar os dois é exatamente o que gera o gotcha do distill apontado na raiz errada.family-adapter.ts:231–239). A evidence é o payload que o T1 come direto — emitir stub vazio geraria chamada paga sobre nada. O walker coleta o erro em errors[] e segue para o próximo pacote (collect, never abort).Bridge e caminhada ($0; --dry-run nem escreve):
# prova sem escrita: conta o que SERIA escrito alembic wiki-bridge ~/Documents/Resources/Bookmarks --dry-run --json # a caminhada real (idempotente: rode 2× e veja written cair a 0) alembic wiki-bridge ~/Documents/Resources/Bookmarks # o caso YouTube: root aninhado + família pinada alembic wiki-bridge ~/Documents/Resources/Transcripts/YouTube --family Transcripts --dry-run # distill: SEMPRE no PAI de Resources (gotcha do 1º segmento) alembic distill ~/Documents/Resources --offline --json # as doutrinas e os dois formatos de path, na fonte sed -n '37,53p' packages/ingestion/src/family-walk.ts sed -n '52,57p' packages/ingestion/src/family-adapter.ts sed -n '70,76p' packages/etl/src/priors.ts
Nota: walkCorpus/o walker NÃO seguem symlinks — pacote symlinkado não entra na caminhada.
items/<cat>/<id>/ com 7 arquivos + raw; o ETL só lê a linha-ponte (family-package.ts:14–22).evidence é destilado REAL (top-3 chunks + understanding 600ch + master, cap 2000); vazia = erro permanente (family-adapter.ts:150–175, 231–239).family-walk.ts:37–42).family-walk.ts:44–53).items/+index/ é familyRoot; --family pina a identidade (commands.ts:560–580).classifyFamily = 1º segmento ⇒ distill aponta no PAI de Resources, nunca na raiz da família (priors.ts:70–76).@alembic/embeddings indexa os chunks INTEIROS e o alembic search os recupera por cosseno — o read-path RAG.