9.5 KiB
Impl: OPS85 — Estratégia para eliminar conflitos de merge no changelog e reavaliar migração da documentação para fora do repo
Status: implementado Atualizado em: 2026-08-24 Issue: #830 Intenção: docs/plans/ops85-changelog-e-docs-externos.md
Leitura da intenção
- Outcome: zero conflito em arquivo de changelog; o guard de sync deixa de ser requisito de CI; a leitura "Recently resolved" permanece acessível com o mesmo conteúdo, por comando; a decisão de migrar docs para fora sai documentada com evidência (arquivamento explícito).
- Anti-goals: leitura preservada; sem cerimônia de merge por PR; ciclo claim→plano→PR intacto; sem segunda fonte de verdade; núcleo do
AGENTS.mdtocado só no apontamento mínimo.
Descoberta do explorador (mudou o desenho da opção A)
docs/changelog/ tem 65 arquivos (todos pós-OPS44, ≥2026-08-13), mas o agregado tinha 171 blocos "Recently resolved" — ou seja, 106 entradas (2026-07-23 → 2026-08-13) existiam apenas no agregado. Matar o agregado commitado sem preservá-las violaria o anti-goal "não perder a leitura estabelecida". Solução: snapshot congelado docs/CHANGELOG-AGENTS-HISTORY.md (D2), commitado 1× nesta entrega.
Abordagem recomendada (Opção A — aprovada no gate)
O agregado deixa de ser commitado; docs/changelog/ é o único registro versionado; a leitura é gerada sob demanda a partir do snapshot congelado + entradas; o guard de sync morre no CI/gate; o append-only vira additions-only por-arquivo + HISTORY congelado + agregado não-commitado.
Rejeitadas: B (merge driver git: desloca o custo para cada rebase, depende de config em toda máquina do pool; o incidente B183/C102 foi um merge "silencioso" que o driver não pega); C (status quo com serializes/rebase: é a evidência do humano provando que não funciona).
Decisões de engenharia
- D1 — destino do arquivo: agregado gitignored no mesmo path (
git rm --cached, working tree preservado). Remoção total do histórico (rewrite de 63 commits) rejeitada; congelar no lugar rejeitado (um agente poderia re-commitar e devolver a classe de conflito). - D2 — seed histórico:
docs/CHANGELOG-AGENTS-HISTORY.md= cópia integral do agregado atual + marcador de congelamento; o gerador usa.blockscomo seed quando o arquivo local não existe. Fora dedocs/changelog/(olistChangelogEntriesé fail-closed para nomes fora do padrão). - D3 — comandos:
changelog:build(grava gitignored) +changelog:read(stdout, novo) +changelog:check(sanidade local opcional, fora de CI/gate). Uma flag no script existente, zero módulo novo. - D4 — mecanismo substituto do append-only: (1) entradas por-arquivo additions-only (já existia); (2) HISTORY congelado zero-diff (só criação); (3) agregado proibido no diff — remoção única (status D) permitida só junto com a criação do HISTORY (a migração OPS85); (4) pós-condição do gerador: toda entrada parseada aparece no output. O multiset (
missingLines) morre — a garantia de não-perda vira additions-only + pós-condição. Escapechangelog-rewrite:permanece, CI-only, agora cobrindo correção do HISTORY. - D5 — docs externos: arquivados com evidência — o conflito era do agregado (arquivo comum derivado), não das entradas por-arquivo; migrar 12+ skills criaria segunda fonte de verdade que os guards não enxergam. Alvo registrado se um dia for: Wiki.js. Nada instalado.
Correção pós-revisão (self-hosting do guard)
O guard com --name-status detectou a migração como rename (R099 docs/CHANGELOG-AGENTS-HISTORY.md), o que falharia a regra "HISTORY congelado". Fix: git diff --no-renames no guard — a semântica é por path (deleção do agregado + criação do HISTORY são paths distintos), rename é ruído; o shape A HISTORY + D agregado + A entrada passa limpo, e diffs futuros ficam inequívocos.
Componentes / mudanças
scripts/lib/changelog.mjs: +CHANGELOG_HISTORY, +CHANGELOG_HEADER(cabeçalho estático novo usado no seed);assertChangelogAppendOnlyreescrito ({ changelogDiff, historyDiff, aggregateDiff });missingLinesremovido.scripts/build-changelog.mjs: leitura do agregado opcional (ausente → seed do HISTORY);--stdout;--checklocal (arquivo ausente → ok); pós-condição de não-perda.scripts/check-changelog-append-only.mjs: scope = agregado + HISTORY + dir;--no-renames; regras D4; escape intacto.scripts/gate-ci.mjs: remoção do run "aggregate sync" (fase 1 docs-guards)..github/workflows/ci-pr.yml: remoção do step "Aggregate is up to date".package.json: +changelog:read..gitignore: +/docs/CHANGELOG-AGENTS.md.docs/CHANGELOG-AGENTS-HISTORY.md(novo, commitado): snapshot integral do agregado (212 blocos: 171 "Recently resolved" + Known Gaps + duplicatas históricas) + marcador de congelamento.- Git index:
git rm --cached docs/CHANGELOG-AGENTS.md. - Contratos:
AGENTS.md,docs/AGENT-OPS.md(item 4 + "Registries compartilhados" + seção "Decisões arquivadas"),.agents/skills/work-issue/execution-pipeline.md,.github/pull_request_template.md,docs/roadmap.md,scripts/lib/agent-pool-prompt.mjs,.agents/rules/engineering-standards.mdc,.agents/rules/agent-pr-workflow.mdc,AGENTS-infra.md. - Testes:
tests/unit/changelog.unit.spec.ts(assinatura nova + migração OPS85 + HISTORY congelado + agregado proibido;missingLinesremovido),tests/unit/agentPoolPrompt.unit.spec.ts(pin semchangelog:build).
Fases executadas
- Morte do agregado commitado + gerador sob demanda (lib + build + check + package.json + .gitignore).
- HISTORY +
git rm --cached; verificação em clone-fresco (213 blocos, órfãs pré-OPS44 preservadas). - Guard de sync fora de CI/gate (gate-ci.mjs, ci-pr.yml, AGENTS-infra.md).
- Contratos (lista acima).
- Testes + gates (
pnpm gate:fastverde: 2440 testes;--no-renamesfix). - Entrada
docs/changelog/2026-08-24-ops85.mdsemchangelog:buildantes do push — tracer bullet do fluxo novo.
Riscos e mitigação
- Perda das 106 entradas pré-OPS44: seed HISTORY commitado + guard zero-diff + pós-condição do gerador + verificação em clone-fresco (fase 2). O HISTORY nunca é escrito pelo fluxo — a classe de conflito não volta por ele.
- Alguém re-commita o agregado: guard proíbe qualquer diff no path regenerável (exceto a migração única); PR vermelho com mensagem clara.
- Clone-fresco sem arquivo em disco:
changelog:read(stdout) é o caminho canônico;changelog:buildregenera. - Duplicação seed-vs-arquivos (65 blocos nos dois): idempotência por texto exato do
buildChangelog(verificado: 213 blocos, sem duplicatas). - Drift de contrato skills/tests/pool: mesma entrega (fase 4+5); pin
agentPoolPromptquebra no CI se esquecido. - Escape mal usado: semântica CI-only preservada; nada de bypass local novo.
Aceite de engenharia
- Zero conflito de changelog (arquivo comum mutável sumiu do git); leitura "Recently resolved" com o mesmo conteúdo (seed + entradas); guard de sync fora de CI; decisão docs externos documentada (arquivada com evidência, alvo Wiki.js registrado).
- Nada commitado além da entrada por-arquivo; HISTORY é o único arquivo de changelog commitado e é congelado; escape CI-only preservado;
git rm --cachedsem rewrite de histórico. - Testes: unit changelog (assinatura nova + migração), pin agentPoolPrompt; verificação manual em clone-fresco;
pnpm gate:fastverde; guard append-only passa no próprio diff da migração.
Triage pós-simplify (capture-review-debts, 2026-08-24)
Já resolvido no simplify/critique (não reabrir): 106 entradas pré-OPS44 só no agregado → HISTORY congelado commitado + verificação em clone-fresco (fase 2); classe de conflito do changelog → agregado gitignored; docs externos → arquivado com evidência (AGENT-OPS "Decisões arquivadas", alvo Wiki.js registrado).
Explicitamente fora / Adiado com gatilho:
- Adiado com gatilho — pós-condição substring do gerador (
missingAggregateEntries): entrada que seja substring de bloco existente passa sem bloco próprio, e entrada com\n\ninterno escapa da idempotência por bloco (pré-OPS44). Garantia honesta para "perda silenciosa" (bloco que some falha); reforço para igualdade de bloco só quando 1ª entrada real com\n\ninterno for observada no agregado regenerado (duplicata visível, cosmético). - Adiado com gatilho —
--checkno-op com agregado local ausente: documentado no docblock; viraria falso-verde só se--checkvoltar a rodar em CI/gate (OPS85 tirou do gate) — gatilho: re-entrada do check em CI. - Descartados: guard "A do HISTORY sem D do agregado" (heuristic aceita; janela de migração fechada, HISTORY já em main); header antigo pré-OPS85 no build local (artefato regenerável, leitura canônica
changelog:read, convergência via seed); CLI do gerador sem spec (convenção: camada CLI de scripts sem infra de teste; lógica pinada na lib); rótulodocs-guardssem step homônimo no ci-pr.yml (rótulo conceitual vivo nogate-ci.mjs— alinhar nomes = churn sem valor);--stdout --checkcom--checkvencendo (precedência documentada, operação mais forte); knip sem carregarpayload.config.ts(P3 ledgered pré-existente, TECH-DEBT.md).
Self-score decision-quality
5/5 — decisões caras com rejeitadas explícitas (A/B/C + D1-D5); cabe no appetite; rabbit holes nomeados; depth check (zero módulo novo, seed é dado não código); intenção satisfeita — a extensão (seed HISTORY) existe para cumprir o anti-goal de conteúdo íntegro.