@fidera/collector
v0.8.0
Published
O coletor forge-native: lê `git` e a forja **na borda do adotante** e empurra os primitivos crus para o Fidera. Zero dependência de runtime — roda via `npx` no CI do adotante.
Downloads
1,292
Readme
@fidera/collector
O coletor forge-native: lê git e a forja na borda do adotante e empurra os primitivos crus
para o Fidera. Zero dependência de runtime — roda via npx no CI do adotante.
Passo 0 — o kit do repositório (#454)
Antes do workflow, ou depois: nenhum passo do kit é pré-requisito da coleta. O que ele faz é pôr no seu repositório, de uma vez e como proposta, as convenções que a derivação precisa para produzir dado bom — as mesmas que, sem o kit, você aprenderia depois de derivar N vezes.
npx --yes @fidera/[email protected] init --dry-run # mostra o plano, escreve nada
npx --yes @fidera/[email protected] init # escreve o que não existe; nunca sobrescreve
npx --yes @fidera/[email protected] init --forge gitlab
npx --yes @fidera/[email protected] init --with-check # + o check opcional de PR sem chave (GitHub)O que entra, e por quê:
- Template de PR e de issue (
.github/ou.gitlab/) com a convenção da chave do rastreador e o bloco de veredito de review — ver a seção abaixo. .fidera/politica-proposta.md: a taxonomia por pasta rastreada (DOMINIO=pasta, comGERAL=.como resto) e o catálogoskill=F<n>das skills instaladas. É proposta: quem aplica é um ADMIN, em/painel/politica, colando os blocos — nada é aplicado em silêncio (a política é história versionada). O workflow de CI fica de fora de propósito (#526): o campo casa peloname:publicado, não pelo arquivo, e é a tela que lista, depois da primeira coleta, os nomes que a forja publicou nas suas entregas, separando as rotinas.- As skills instrumentadas em
.claude/skills— as do superpowers que terminam com a seção## Fidera, dizendo o ato de fase que produzem (F1 entrega o bloco ao humano; F3 pede o trailerFidera-Skill/Fidera-Tagno primeiro commit da construção; F4 garante o bloco de veredito). Skill que já existe no seu repositório é pulada inteira. --with-check(GitHub): um workflow que barra PR sem chave do rastreador. É portão seu, no seu repositório, escolhido por você — o Fidera não depende dele e não o liga sozinho. No GitLab, a validação da chave é do pipeline (o adotante medido já a tinha).
O que o kit não faz, de propósito: não instala credencial nenhuma, e a F3 não pede credencial
nenhuma — as skills de construção escrevem o trailer no commit (seção "A F3 no trailer do
commit", abaixo) e a coleta diária o lê. O token de escopo EMIT do tenant (FIDERA_INGEST_TOKEN no
ambiente; no cofre da pipeline para agente em CI) só existe para o canal de exceção, o
fidera-collector emit, para código fora do que o coletor lê. O kit também não manda instalar o
App fidera-claude na organização: nenhuma skill instalada pede marcador de agente (a F3 vai no
trailer, a F1 é marcador humano e a F4 é o bloco de veredito no PR), e pedir a um admin da org um
ato que nada usa seria ruído. A instrução volta quando o servidor passar a postar como
fidera[bot] (#545). Se algum passo pedir um .pem na sua máquina, está errado.
A chave do rastreador — e o prefixo de domínio
A delivery é o nome da unidade de intenção, o elo entre a entrega e a issue aprovada. O
coletor a lê de dois lugares, e um basta:
- Issue na forja: a branch chama
<n>-slug(123-cobranca-recorrente) e o PR fecha comCloses #123— em inglês, é keyword da máquina. - Rastreador externo (Jira e afins): a chave no início do título do PR/MR —
[PROJ-123] Cobrança recorrente. Vale também para entrega sem commit de merge (squash, fast-forward): o coletor lê o assunto do próprio commit (#440).
Sem uma das duas, a entrega vira o SHA do commit no painel (fallback-sha, contado na fila
de atenção). E o domínio vem do primeiro commit da branch: DOMINIO/tipo(#123): …, com um
domínio da taxonomia da política — é o degrau 1 da cascata (#431), convenção declarada, que
vence a inferência por caminho. Só o primeiro commit: dois prefixos diferentes na mesma
entrega empatam e anulam.
Quickstart
O escopo
@fiderano npm é da organizaçãofidera(reivindicado em 19/08/2026 pelo governador — antes disso o 404 deixava qualquer terceiro publicá-lo e ser executado no runner do adotante, com oGITHUB_TOKENdele em mãos; a razão da versão exata abaixo é a mesma).
permissions:
contents: read # o checkout, e o `git log`/`git diff` locais
actions: read # `/actions/runs` — o eixo de CI
pull-requests: read # `/commits/{sha}/pulls` e os comentários do portão
issues: read # `/issues/comments` — a via harvest do nível C (#186: sem isto o endpoint
# devolve 200 FILTRADO, só comentários de PR — os marcadores `fidera:<ato>` em issues
# somem em silêncio, sem 403) — e `/issues/events`, a fila antes do trabalho (#539)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # clone completo: a PRIMEIRA corrida traz o histórico inteiro (ver "A primeira carga")
- run: npx --yes @fidera/[email protected] --mode tail --fidera-url https://api.fidera.tec.br --tenant SEU-SLUG
env:
GITHUB_TOKEN: ${{ github.token }}
FIDERA_INGEST_TOKEN: ${{ secrets.FIDERA_INGEST_TOKEN }}⚠️ O bloco permissions: não é opcional. Com o default restrito de GITHUB_TOKEN — o padrão de
organizações novas — o token recebe contents: read e nada mais, e aí /actions/runs,
/commits/{sha}/pulls e /issues/{n}/comments devolvem 403. Esse 403 não traz
x-ratelimit-remaining: 0, então não é limite de taxa: é permissão. O coletor passou a dizer isso
na mensagem de erro, mas evitar o erro é melhor do que diagnosticá-lo.
⚠️ A versão é exata, e não é preciosismo. Um range (@1, @latest) resolve no runner do
adotante, num processo que ninguém está olhando, com o GITHUB_TOKEN do repositório em mãos.
Versão exata é o que faz o adotante decidir quando o código que ele executa muda. A guarda
fitness/quickstart-exact-version.test.ts recusa range e exige que este número seja o do
package.json.
--yes é da mesma família: sem ele, npx num pacote ainda não baixado abre um prompt, e num
runner sem tty isso é um step pendurado até o timeout — com um diagnóstico que não aponta para cá.
⚠️ O host é api.fidera.tec.br, e só ele. Até a 0.4.0 este bloco apontava api.fidera.app —
um host que nunca existiu, sob um domínio que não é do Fidera (#436). O FIDERA_INGEST_TOKEN
vai no Authorization de toda corrida: confira o host antes de colar. A mesma guarda recusa
qualquer --fidera-url concreto neste documento que não seja o host para onde o próprio Fidera
coleta (.github/workflows/coleta.yml).
Governança ao nascer
O tenant já nasce com uma política aplicada (versão 1, o preset da governança default), então a
primeira coleta já pode ser derivada, no grão do repositório inteiro, com um único domínio
GERAL. Nada deriva sozinho: quem deriva é um ADMIN, no botão "Derivar" do painel. E a
política não viaja com o backfill: o backfill traz o que a forja produziu, e a governança é
ato do ADMIN. Revise a política em /painel/politica antes do primeiro "Derivar" (#428).
Derivar com o preset dá número errado e obriga a derivar de novo. O painel mostra a política
como não revisada até um ADMIN aplicar a dele. Os três campos que mais mudam o sinal:
- o workflow de CI (
ciWorkflow) — o nome publicado do workflow (oname:do YAML), não o nome do arquivo. O preset trazci.yml, que quase nunca casa, e aí toda entrega sai sem portão observado (#429). Com CI fora do Actions, veja a fonte do portão (#444) abaixo. O coletor carrega, em cada run, o gatilho que a forja declara (event), e o painel só oferece como candidato a portão o que reage à entrega (push,pull_request): workflow agendado, disparado à mão ou encadeado roda no commit que estava na ponta — a coleta do próprio Fidera é um deles — e aparece como rotina, sem botão (#540). - os marcadores de retrabalho — se estiverem incompletos, o sinal de aprovação sai otimista.
- a taxonomia — o
GERAL=.do preset casa qualquer arquivo. A cobertura parece perfeita porque só existe um domínio (medido em 03/09: 228 de 228 com o preset, 34 sem domínio depois da taxonomia real).
A taxonomia é o modulos.properties do repositório, verbatim: DOMINIO=pasta1,pasta2,...,
uma linha por domínio, prefixo mais longo vence. Extensão do Fidera ao formato: o valor
. é o caminho raiz — casa todo arquivo e, sendo o prefixo mais curto possível, perde de
qualquer pasta declarada. É o que o preset usa (GERAL=.), e serve de fallback se o adotante
quiser manter um domínio "resto" ao lado da taxonomia real.
O que é uma entrega
Cada commit da linha de primeiro pai da branch principal é uma unidade de entrega — o que
entrou na main como unidade. Com commit de merge, parents é o [base, head] do próprio merge;
com squash, fast-forward ou push direto, é [pai, o próprio commit], e o range da entrega é
esse commit (#430). O coletor não exige commit de merge: um adotante de produção que integra
por fast-forward estava 90% invisível antes disso, com a coleta reportando sucesso.
Dois efeitos declarados: sem Merge pull request #N from … no assunto, a delivery cai para o
SHA curto (origem fallback-sha, contada no relatório; o assunto do commit é a #431); e commits de
automação na main (bumps de release) entram como entrega — quem os nomeia é a política do
tenant, por markers.botEmailDomain (o domínio do bot ou, quando ele divide domínio com pessoas,
o endereço completo), não o coletor. Entrega só de automação aparece na trilha como sistema e
não ocupa a janela de trabalho nem avança o nível (#518); sem a declaração, o bump conta como
trabalho de uma pessoa. A raiz do repositório não é entrega (não tem
de onde partir), e um clone raso cuja fronteira esconde os pais de tudo o que ele tem é recusa,
não coleta vazia.
Modos
| --mode | o que faz | fetch-depth |
|---|---|---|
| tail (padrão) | re-observa a cauda de merges. É a corrida diária. Sem merge assentado no servidor, vira all sozinha (a primeira carga, abaixo). | 0 (200 basta para a cauda, mas não para a primeira carga) |
| all | backfill: todos os merges, mais a varredura de drift na forja. | 0 |
| drift | só a observação de drift deste run, lida do workspace. Zero git, zero forja. | 200 |
A cauda é re-observada de propósito: re-run de CI e edição do comentário do portão acontecem
depois de o merge assentar, e o servidor faz upsert por (tenant, mergeSha, collectorVersion).
O teto da cauda. A janela é o maior entre --tail-k e os merges desde o último assentado,
até 50. Acima disso (dias sem coleta, por exemplo), ela pega os 50 mais antigos desde o
último assentado e o stderr diz quantas corridas faltam. O marcador avança sem pular merge, e a
corrida seguinte continua de onde esta parou; --mode all cobre o resto de uma vez. Até alcançar
os mais novos, a re-observação deles espera.
--mode drift devolve merges: [] por desenho — uma corrida de PR não pode reportar como fato o
que ainda não foi integrado.
A primeira carga (#420)
A porta do legado. O coletor consulta o estado da ingestão antes de coletar; sem merge
assentado para a versão dele, a corrida tail vira all por conta própria: índice de runs,
histórico inteiro do clone, varredura de drift e a colheita de atos desde o início. Antes de
gastar a forja, o stderr diz a contagem — "carga do histórico: 470 merges no clone, 470 entram
nesta corrida (desde 2026-03-12…)" — e a retomada por marcador (do mais antigo ao mais novo)
cobre interrupção por teto de API: a corrida seguinte continua de onde parou.
- Teto, opcional:
--history-since AAAA-MM-DD(só merges a partir da data) e--history-cap N(os N mais recentes); combinam pela interseção. Valem paraallexplícito também. Sem eles, tudo. - Clone raso na primeira carga é aviso, não erro — a carga sai parcial e o stderr diz
para usar
fetch-depth: 0. Repositório sem runs de CI também é aviso na primeira carga (noallexplícito continua recusa). - Tempo: o tessena carregou ~470 merges em menos de uma hora. Dê
timeout-minutes: 60ao job — a diária, depois, cabe em minutos. - Depois de um bump de versão do coletor, a primeira corrida da versão nova é de novo "primeira carga" e recarrega o histórico: a leitura de merges mudou, e é assim que a leitura nova chega ao passado.
Contrato de entrada
| flag | obrigatória | padrão |
|---|---|---|
| --fidera-url | sim | — |
| --tenant | sim — o slug do tenant, o mesmo do host <slug>.… (nunca um id interno) | — |
| --mode | não | tail |
| --main | não | main |
| --tail-k | não | 5 |
| --lookback-days | não | 90 |
| --repo-root | não | . |
| --observacao | não | build/maturity/drift-observacao.json |
| --dry-run | não | false |
| --history-since | não — teto por data da carga do histórico (AAAA-MM-DD) | sem teto |
| --history-cap | não — teto por contagem da carga do histórico (os N mais recentes) | sem teto |
| variável | para quê |
|---|---|
| FIDERA_INGEST_TOKEN | obrigatória — autoriza o POST. Tenant-scoped, escopo de ingestão. |
| GITHUB_TOKEN | leitura da forja. github.token basta. |
| GITHUB_REPOSITORY | obrigatória — o runner já a publica. |
Tudo isso é validado na entrada, antes de qualquer I/O: um --tenant esquecido falha em
milissegundos, e não depois de gastar o orçamento de requisições da forja.
CI fora do Actions: declare a fonte do portão (#444)
Se o seu CI é Jenkins, CircleCI, Buildkite ou outro que publica na forja, declare na política (painel → Política → fonte do portão):
| gateSource | o que o coletor consulta | e aí o ciWorkflow nomeia |
|---|---|---|
| actions (padrão) | GET /actions/runs — os runs do workflow | o workflow |
| check-runs | GET /commits/{sha}/check-runs | o check (ex.: Jenkins) |
| status | GET /commits/{sha}/status | o contexto (ex.: continuous-integration/jenkins/branch) |
Três coisas antes de declarar:
- As fontes são exclusivas. Medido no
jenkinsci/jenkins: o mesmo Jenkins publica 1 commit status agregado e 12 check-runs. Ler as duas contaria o mesmo portão duas vezes — escolha a que o seu CI publica. Em dúvida,gh api repos/OWNER/REPO/commits/<sha>/statuse.../check-runsmostram o que existe. - Declarar custa. A fonte nova é uma requisição por entrega e não tem índice, ao contrário dos runs (que o backfill lê em lote). Quem não declara não paga nada: a coleta segue idêntica.
- Nada muda no workflow. A declaração vive só na política; a próxima coleta já obedece, porque
o coletor a lê no
GET /v1/observacoes:estadoantes de coletar.
A consulta é no HEAD da entrega, não em cada commit do range: status e check-run de CI externo são publicados no commit que o CI construiu.
A F5 vem de graça, do CI que você já roda (#452)
O ciclo do workflow declarado em ciWorkflow — o mesmo que o Fidera já lê para decidir o
terminal — passa a nascer também como VerificacaoConcluida: { workflow, resultado, ref,
confianca }, com resultado PASSOU (conclusão success) ou FALHOU. É fato, nunca nota:
não há rubrica, não há score.
Nada a instalar e nada a marcar — se a sua entrega atravessa CI, a F5 já tem evidência. Medido neste repo em 22/09: 59 de 60 merges recentes têm o check do workflow declarado.
No painel, a F5 aparece na árvore de processo como entrega com os atos por domínio, sem nível:
não há driver de nível para F2–F5 (#452). A fase vem do próprio ato (#588), então não há linha a
declarar no catálogo, e uma linha entrega= lá não decide nada: a rota de aplicar política a
descarta e devolve um aviso (a tela ainda não o mostra: #590).
Não existe emit verificacao-concluida, e a razão é a mesma que mantém o terminal fora do
emissor: prontidão por build é atestada por quem executa o build. Um emissor que a aceitasse
deixaria o adotante pintar a própria aprovação. Adotante cujo CI não é Actions fica sem F5 até o
commit status entrar (#444) — como já acontece com o portão.
A F3 no trailer do commit — sem credencial na máquina de ninguém (#594)
O ato de construção (TarefaIniciada) nasce de duas linhas no corpo de um commit da entrega,
escritas pelo agente quando a skill de construção começa:
Fidera-Skill: subagent-driven-development
Fidera-Tag: featureA coleta diária já traz o corpo de cada commit, e a derivação lê o par: um ato por entrega e por
skill, do commit mais antigo que traz o par completo, no domínio da cascata, com a origem do
Co-Authored-By do mesmo commit. @<n> no valor declara a versão; sem ele, vale a do método em
vigor, e sem método declarado para a skill, vale 1. Fidera-Tag é kebab-case minúsculo, como
feature ou bugfix: uma tag fora disso (Feature, nova feature) não vira ato, só a perda
tag-invalida no relatório. Nada a instalar e nenhum token: é o critério de que ato humano que o
Fidera lê é ato que o humano já faria (#595).
A entrega construída por agente sem o trailer não vira evento: entra no relatório da derivação
como agentDeliveriesWithoutSkill, a falta contada.
Para a mesma tarefa, um canal de F3 só: o trailer, ou o marcador fidera:tarefa-iniciada /
o emit. A ref do trailer é <entrega>@<sha12> e não converge com as do marcador e do emit,
que convergem entre si; combinar o trailer com um deles conta o ato duas vezes, e o log é
append-only: a duplicata fica para sempre. Quem usa o trailer não precisa do marcador para a fila,
porque o início da fila é a primeira atribuição da issue OU o marcador, o mais cedo dos dois (seção
"A fila antes do trabalho", abaixo). O emit tarefa-iniciada continua sendo o canal de exceção,
para quem tem o código fora do que o coletor lê.
Nível C — a via padrão: o marcador na issue (#147)
Antes do emit, prefira o marcador: um comentário estruturado na issue/PR, que a coleta
diária colhe sozinha — sem token na máquina de quem trabalha, sem rede no instante do ato, com
autor e instante ATESTADOS pela forja, e recuperável (bloco faltando se edita; a próxima coleta
pega). É o mesmo vocabulário do emit, em forma de bloco:
fidera:proposta-editada
skill: brainstorming@1
domain: CRM
delivery: 986-ciclo-vida
doc-path: docs/specs/2026-08-26-design.md
diff-pct: 32
ref: abc1234Regras que importam:
- Em COMENTÁRIO, nunca no corpo da issue — é o comentário que a forja atesta.
- Sem
actor/originno bloco: a proveniência deriva do AUTOR do comentário + dosagentActorsda política. Proposta comentada por um autor classificado como agente é perda contada, nunca evento — agente não atesta a própria aprovação. refausente ⇒ o id do comentário assume (determinístico, contado).deliveryausente ⇒issue-<n>/pull-<n>.refpresente é escopada pela entrega (#547): o ato grava<delivery>@<ref>, a menos que arefjá comece por<delivery>@. Duas entregas que partem do mesmo commit podem dar o mesmo sha; sem a entrega naref, a segunda seriaduplicateda primeira e sumiria.- Usar marcador E
emitpara o mesmo ato é seguro: a identidade converge (duplicate). O trailer da F3 não converge com nenhum dos dois — a seção dele, acima, explica por quê. - Issues antigas contam:
--mode allvarre a história desde o início (ou desde--history-since), ignorando a marca d'água da coleta anterior — o backfill de atos, de graça (#552: até a 0.6.0 a marca encurtava oall).
Nível C — emitindo atos da esteira (#144)
O mesmo binário emite os atos de esteira que a forja não vê — é o que dá evidência às
fases F1/F2 no painel. A F3 é outro caso: o git log que a coleta já lê carrega o commit, e a tag
da construção (a variedade que as recomendações citam) nasce, por padrão, do trailer Fidera-Tag
dele (a seção do trailer, acima). O emit tarefa-iniciada é só o canal de exceção, para código
fora do que o coletor lê. O emit exige um token de escopo EMIT (permanente e
estreito: só os três atos abaixo entram; qualquer veredito — terminal, drift, avaliação,
concessão — volta invalid, porque o emissor testemunha atos, nunca vereditos).
A --ref é a identidade do ato, e o emit a escopa pela entrega (#547): grava
<delivery>@<ref> (aqui, 986-ciclo-vida@abc1234def56), a menos que ela já comece por
<delivery>@. Assim o sha do commit basta, mesmo quando duas entregas partem do mesmo commit da
main. Use git rev-parse --short=12 HEAD: o --short sem número cresce com o repositório, e o
mesmo commit viraria duas refs. A regra vale para os três atos abaixo; o review-concluida grava a
--ref como veio, porque converge com a review colhida da forja.
# o humano aprovou a spec editando — o diff é calculado dos dois arquivos (LCS de linhas)
fidera-collector emit proposta-editada --skill brainstorming --domain CRM --delivery 986-ciclo-vida --doc-path docs/specs/2026-08-25-design.md --draft rascunho.md --approved aprovado.md --ref abc1234def56 --actor alexandre --fidera-url https://api.fidera.tec.br
# aprovou sem editar
fidera-collector emit proposta-aprovada --skill brainstorming --domain CRM --delivery 986-ciclo-vida --doc-path docs/specs/2026-08-25-design.md --ref abc1234def56 --actor alexandre --fidera-url ...
# despacho com tag — é a evidência de variedade que as recomendações citam (não é sinal nem cap, #449)
# canal de exceção: só para código fora do que o coletor lê (ver a seção do trailer)
fidera-collector emit tarefa-iniciada --skill subagent-driven-development --domain CRM --delivery 986-ciclo-vida --tag migration --ref abc1234def56 --actor agente --fidera-url ...O veredito de review (#300)
A F4 — revisão adversarial — é fase de agente por definição (norte §3), e desde a #300 o veredito dela entra por dois canais, sem marcador no meio:
Pela forja, de graça. Se o seu portão já posta o bloco ```json com escopo, veredito
e findings no comentário do PR, a coleta diária já o transforma em ReviewConcluida — a
mesma leitura que decide o terminal, agora também virando fato de F4. Zero mudança de processo:
é o nível A fazendo o trabalho.
Pelo emit, quando a review roda fora da forja. O bloco vai verbatim, num arquivo:
fidera-collector emit review-concluida --skill requesting-code-review --domain CRM --delivery 986-ciclo-vida --ref abc1234 --actor claude --block review-block.json --fidera-url https://api.fidera.tec.brRegras que importam:
findingsé obrigatório, e não pode ser lista inventada: veredito só com contagens é o formato agregado, que decide terminal e não vira evento de fase. A recusa é na entrada.- Não existe marcador
fidera:review-concluida. Um blocochave: valornão carrega array de objetos, e deixá-lo emitirfindings: []faria só review limpa ser marcável — viés que o produto recusa. Veredito entra pela forja ou peloemit. - A aprovação do HUMANO continua sendo só do humano:
proposta-aprovadapostada por autor classificado como agente segue sendo perda contada. O que a #300 abriu foi o veredito das fases que são de agente, não a assinatura do humano.
O veredito da INTENÇÃO (#452) — a F2, no mesmo bloco
escopo aceita três valores, e é ele que diz de que fase o veredito é:
| escopo | fase | o que foi julgado | skill derivada |
|---|---|---|---|
| INTENCAO | F2 | a proposta: spec, plano, desenho — antes de construir | intent-review |
| BRANCH | F4 | a entrega inteira | branch-review |
| TASK | F4 | uma tarefa do plano | task-review |
O bloco é o mesmo, pelos mesmos dois canais, e a máquina de findings e de destino
(FindingDisposto, #457) serve às duas fases sem cópia.
O veredito de intenção não move portão, e isso é desenho: as contagens do bloco BRANCH
decidem o terminal da entrega, e um achado sobre a spec reprovando a entrega mudaria a
semântica do portão por efeito colateral. Um REJEITADO de intenção é fato de F2, e nada mais.
Para a fase aparecer no painel, mapeie intent-review=F2 no catálogo de fases da política — até
lá a skill é órfã, e o card de declarar é o degrau 1 pedindo exatamente isso.
O destino de cada finding (#457)
Quando a review entrou pelo emit review-concluida, a linha de saída traz o eventId dela
(accepted: <eventId>). Quem recebe a review registra o que fez de cada finding citando esse id:
fidera-collector emit finding-disposto --skill receiving-code-review --domain CRM --delivery 986-ciclo-vida --ref def5678 --actor claude --review <eventId> --finding 3 --disposition DEFERIDO --reason "fora do escopo da entrega" --fidera-url https://api.fidera.tec.br--disposition:CORRIGIDO·CONTESTADO·DEFERIDO.--reasoné opcional, e o motivo nunca é inventado.- É o insumo da regra
ruido-de-review@1: a classe de finding (severidade × categoria) que é mais deferida do que corrigida ganha card.CONTESTADOnunca conta como ruído. - Review que entrou pela forja ainda não tem esta via: o
eventIddela só nasce na derivação, depois do merge. O destino pela forja é trabalho em andamento na #457. A doutrina do invocador. O emit tem timeout curto (5s), UMA tentativa e exit honesto — quem garante o "nunca trava" é a forma de chamar:... emit ... || true, ou em background. Re-emitir o mesmo ato é seguro (duplicatesai 0 — oeventIdé determinístico pelo ato).
Nunca portão. É proibido, por contrato de uso, condicionar merge ou passo de esteira ao
exit do emit: ele é telemetria do processo, e um emissor que trava a esteira é o arrasto
que este produto existe para não criar. O único portão do sistema é o que o próprio adotante
opera no painel.
A versão da skill (#451)
--skill-version é opcional, e o normal é não passar: sem ele, o emit lê o version: do
frontmatter de .claude/skills/<skill>/SKILL.md no --repo-root (default: o diretório atual)
no instante do ato, e o stream sai <skill>@<versão>|<DOMINIO>. Quem edita a skill bumpa
o version: — é a única coisa a lembrar, e ela mora no arquivo que se está editando.
- A convenção é inteiro positivo (
version: 3).1.2.0,v2ou0não viram número por adivinhação: o ato vai para@1e oemitdiz no stderr qual é o problema. Sem arquivo ou sem o campo, também@1, cada caso com a sua linha. - A precedência, do mais perto do fato ao mais longe:
--skill-version(ouskill: nome@nno marcador) › o frontmatter lido peloemit› o método da última coleta anterior ao ato (para o marcador sem versão, na derivação) ›1. - Bumpar não zera nível. A versão separa linhagens para medir a eficácia das recomendações; o nível e a histerese vivem no domínio. E o ato já gravado nunca muda de stream: o passado fica na versão em que entrou.
- O
fidera-collector initcopia as skills como estão — inclusive oversion:.
A fila antes do trabalho (#539)
Em toda corrida que varre a forja (tail e all), o coletor lê os eventos de issue do
repositório (/issues/events, do mais novo ao mais antigo, até a marca d'água que o servidor
derivou da coleta anterior) e manda, por issue tocada, a intenção registrada (createdAt), o
fechamento (closedAt/closedReason, crus) e a primeira atribuição atestada pela forja
(firstAssignedAt). PR não entra. É a lista issues do envelope — opcional: coletor anterior a
0.6.0 não a manda e continua válido.
O que o Fidera faz com isso: a fila — intenção registrada → trabalho iniciado — ao lado das leituras de tempo por fase, antes da F1, como espera e não como fase. É tempo de priorização, não de execução: fila longa se resolve decidindo, não acelerando. Não entra em nível nenhum.
- O início é a primeira atribuição OU o marcador
fidera:tarefa-iniciadana issue — o mais cedo dos dois; quem não atribui nem marca sai "sem início observado", com o denominador dito, nunca como fila zero. O board (Projects v2) não serve: a REST lista a mudança de status, mas não diz qual status. - Permissão: nenhuma nova —
/issues/eventscai sob oissues: readdo bloco acima. - Custo: no tessena, ~3 páginas por coleta diária; a história desde julho custou 217.
--history-sincelimita a varredura inicial, como limita a de merges. - Atribuição vista uma vez não se perde: a coleta diária vê só a janela, e o servidor guarda o instante mais cedo que já viu para cada issue.
- O que ficou antes da janela da primeira corrida entra por
--mode all(desde o início, ou desde--history-since): a marca d'água vale notail, nunca encurta o backfill (#552).
⚠️ Ordem de deploy (spec #539 §7): o envelope recusa chave desconhecida no topo, então um coletor 0.6.0 contra um servidor anterior à #539 tem o envelope inteiro recusado (400). O servidor sobe e é promovido antes de o coletor ser publicado — e é por isso que a versão acima só muda depois da promoção.
O método detectado (#414)
Em toda corrida — tail, all, drift e --dry-run — o coletor lê
.claude/skills/<dir>/SKILL.md no --repo-root e manda a lista no envelope, como method:
nome (o name: do frontmatter; o diretório é a reserva), versão se houver, caminho. É o que o
marcador carimba em skill: e o que o catálogo da Política mapeia — a Política passa a
sugerir o catálogo a partir daqui; quem aplica é o humano.
skills: [] é dado ("nenhuma skill no repositório"). Campo ausente é coletor anterior a 0.4.0.
Nome fora de [a-z0-9-] não viaja e sai como perda no stderr. Plugins instalados no usuário
não estão no checkout e ficam de fora.
A skill que mudou sem versão nova (#467)
Para cada skill, o coletor pergunta ao git a história do SKILL.md. Se o conteúdo mudou depois
do commit que fixou o version: em vigor, a skill viaja com changedSinceVersion: o instante
da mudança mais recente. Sem version: também conta, porque o @1 implícito se fixa na criação
do arquivo. O painel mostra um item de convenção na fila, "N de M skills mudaram sem versão nova",
com os nomes. O item some quando alguém faz o bump: a próxima coleta chega sem o campo.
- A resposta é retroativa. Uma edição de meses atrás aparece já na primeira coleta, porque a resposta vem da história e não de comparar duas coletas.
- Não conta: mudança só de fim de linha (CRLF × LF), espaço no fim da linha ou linha em branco no fim do arquivo.
- Precisa da história inteira. Fora de um repositório git, ou com o
gitfalhando, a skill viaja sem o campo e a perda sai no stderr. Renomear a pasta da skill reinicia a história dela. - Ordem de deploy: o servidor recusa campo que não conhece na skill e, com ele, o método inteiro. O coletor que manda o campo só é publicado depois do servidor que o aceita.
Canais
- stdout — com
--dry-run, o envelopecollector-v1, um documento JSON parseável. É contrato. - stderr — o resumo do run real (lotes e contagem por status, emitido mesmo no sucesso), além de avisos, perdas contadas e recusas do servidor. stderr não-vazio não indica falha; o sinal de falha é o código de saída.
Nenhum canal ecoa o FIDERA_INGEST_TOKEN: o log de um run é público em repositório aberto.
Lotes
O envelope sai em lotes que respeitam os dois tetos do portão: até 1000 itens por lista (o
maxItems do ingest-v1; acima dele, 400) e até 10.000.000 bytes no corpo (o orçamento do
portão; acima dele, 413), contados em UTF-8 e com o envelope de topo dentro da conta. A ordem de
cada lista é preservada de lote em lote — os merges vão do mais antigo ao mais novo —, de modo que
uma interrupção deixa assentado um prefixo contíguo do histórico.
Até a 0.7.0 o lote era só por contagem: a primeira carga de um repositório com histórico longo (865 merges, 16,9 MB no caso medido) levava 413 e não gravava nada.
Uma observação que sozinha passa do orçamento não cabe em lote nenhum: ela fica fora do push,
nomeada no stderr, e o resto entra. O run sai 1 — o portão a recusaria de qualquer jeito, e
ela não volta sozinha numa corrida seguinte.
O que "sucesso" significa aqui
O código de saída é 0 só quando o POST foi aceito e o relatório por item não trouxe
invalid. Um 200 cujo corpo diz que todas as observações foram recusadas — tenant divergente do
token é o caso real — não é sucesso, e sai 1.
Quando nada pôde ser lido, o resumo sai como não verificado, nunca como zero: "não li" não
pode ser confundido com "zero inválidos". A completude é medida por item — um relatório com
menos itens do que o lote enviado não passa por completo, senão o run afirmaria {"invalid":0}
sobre observações de que nada sabe.
Numa carga multi-lote a leitura pode ser parcial: um lote responde relatório legível, outro
não. O que foi apurado é piso confirmado, não estimativa — uma recusa contada num lote sai 1
mesmo que o relatório de outro lote nunca tenha chegado, e o resumo sai como
ao menos {…} (relatório incompleto). O que falta é o total, nunca o piso. O contrário —
absolver o run porque um lote veio ilegível — era o furo da issue #47.
Segue daí uma assimetria que vale conhecer: com uma carga toda inválida, o run sai 1 se a recusa
cair num lote lido, e 0 se o único lote lido não a contiver. O piso é honesto sobre o que
mediu; ele não adivinha o que não leu.
Não ler o relatório, sozinho, não é motivo de falha: o POST foi aceito, a retomada cobre o
resto e o run sai 0. O sinal de falha é a recusa, não a ignorância.
Se o próprio POST falha, o push é interrompido — e o que já assentou não morre com o erro. Os
dois modos de morte do transporte estão cobertos: o status de erro (5xx, limite) e a rede caindo
(ECONNRESET, DNS, timeout do runner), que é o caso mais comum. O run sai 1 e relata os lotes
concluídos, o piso apurado e as recusas que já conhecia — e quando nada foi apurado diz
não verificado, nunca ao menos zero, pela mesma razão de sempre: zero é uma afirmação.
(push interrompido) distingue esse caso do (relatório incompleto), que é o push que terminou
sem conseguir ler tudo. Re-rodar é seguro: a ingestão é idempotente e o GET :estado retoma do
último assentado.
Perdas
Toda perda é contada e sai por stderr — linha de merge não parseada, range sem commits, commit não
parseado, skill com nome fora do alfabeto do stream, SKILL.md ilegível (#414), história do
SKILL.md ilegível no git (#467). Um corpus com
buraco não pode ser indistinguível de um corpus completo.
