Skip to content

chore(scripts): scripts/git.ts como dono único de processos filhos nos gates - #263

Merged
fm1randa merged 2 commits into
mainfrom
chore/260-scripts-git-owner
Nov 6, 2026
Merged

fm1randa merged 2 commits into
mainfrom
chore/260-scripts-git-owner

Conversation

@fm1randa

@fm1randa fm1randa commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Problema

Cada gate em scripts/ tinha o próprio wrapper de Bun.spawnSync para git, gh e bun (replay-gates, risk, prova, private-terms*, new-tests-fail-on-main, check-eslint-disables, risk-label), cada um com sua mensagem de erro, seu quoting de caminho e seu default origin/main (7 cópias). O contorno do oven-sh/bun#34069 só existia em check-private-messages.ts, e check-eslint-disables.ts e private-terms-history.ts tinham parsers de diff próprios apesar do dono em behavior-change.ts (#236/#245). Mesma causa C1, agora na infra de scripts/.

Mudança

  • scripts/git.ts, dono único de processos filhos: spawn (síncrono), spawnAsync (o caminho do contorno #34069, usado por check-private-messages), git / gitBytes / gitResult / gitQuiet / revisionExists / repoRoot (sempre com core.quotepath=false, que antes só private-terms-history passava) e DEFAULT_BASE. Todos os chamadores foram migrados.
  • Parsers de diff no dono: parseDiff (behavior-change.ts) agora numera cada linha no seu lado (old para remoção, new para o resto). check-eslint-disables e deletedLines (corpus de termos privados) passam a lê-lo em vez dos parsers próprios. Agora parseDiff também mantém um arquivo cujo cabeçalho o git coloca entre aspas (caminho com aspas ou tab). Antes, as linhas desse arquivo caíam no arquivo anterior ou sumiam.
  • Lint local/no-raw-spawn: proíbe Bun.spawn, Bun.spawnSync, child_process e o shell do Bun ($) em scripts/**, com isenção só para scripts/git.ts (via ignores no eslint.config.js). Tem fixture before de 1045a83:scripts/replay-gates.ts, testes em test/eslint-no-raw-spawn.test.ts, isenções nomeadas para src/** e test/** e uma linha na tabela de cobertura do AGENTS.md.
  • Testes: test/git.test.ts cobre caminho não ASCII sem quoting, o erro com o stderr do git e HEAD sem commits. test/behavior-change.test.ts cobre a numeração por lado entre hunks e o cabeçalho com aspas; as duas expectativas existentes ganharam o campo line, e nenhuma asserção foi removida.

Escopo

Closes #260

Checklist

  • Se a mudança introduz ou altera um termo do domínio, o GLOSSARY.md foi atualizado neste PR. (Não introduz termo de domínio.)

Verificação

Risco (bun scripts/risk.ts → exit 2, label risco)

O PR é arriscado por tocar superfícies de segurança: eslint-rules/, scripts/private-terms*.ts, scripts/check-private-messages.ts, scripts/git.ts e scripts/behavior-change.ts. O raio de impacto é o seguinte. A guarda passa a chamar o git por scripts/git.ts: mesmos comandos e mesmos -z em ls-tree/ls-files/diff --name-only. A única mudança é que core.quotepath=false agora vale em todo lugar, e esses comandos já usam -z, que não faz quoting. deletedLines passa a usar parseDiff. Com --unified=0 não há linhas de contexto, então a numeração do lado old é a mesma de antes. O replay do #177 e test/private-terms.test.ts provam a guarda sem mudança. /blast-radius não estava disponível nesta sessão; este parágrafo é a análise manual. Aguarda a decisão do mantenedor.

Decisões para o Filipe:

  1. Incluir scripts/git.ts e scripts/behavior-change.ts em SECURITY_SURFACES? Resolvido no merge da main: o teste test/risk-guard-imports.test.ts (chore(lint): estende no-loose-number, no-raw-cents, no-double-cast e knip a scripts/** #262) exige que todo módulo carregado pela guarda seja superfície de segurança, e a guarda agora carrega os dois. Eles entraram em scripts/risk.ts e na lista do AGENTS.md. Consequência: uma mudança futura no parser de diff (behavior-change.ts) passa a ser risco, como mostra o replay abaixo para o refactor(ci): dono único para "este diff muda comportamento" #245.
  2. spawnAsync (contorno #34069) continua só em check-private-messages, que é o único gate que roda in-process sob bun test --parallel com gh lento. Os demais seguem síncronos via spawn. Migrar tudo para async cascatearia async por todos os gates. Fica centralizado no dono para quando for preciso.

Fails-on-main: isento — PR chore que não muda src/ nem bin/.

Prova

Web: sem equivalente na web

Antes (main)

origin/main com só eslint-rules/ e eslint.config.js desta branch copiados (bunx eslint --format json scripts/, resumido com jq):

exit=1
scripts/check-eslint-disables.ts:127 local/no-raw-spawn (Bun.spawnSync)
scripts/check-eslint-disables.ts:169 local/no-raw-spawn (Bun.spawnSync)
scripts/check-private-messages.ts:40 local/no-raw-spawn (Bun.spawn)
scripts/new-tests-fail-on-main.ts:96 local/no-raw-spawn (Bun.spawnSync)
scripts/new-tests-fail-on-main.ts:123 local/no-raw-spawn (Bun.spawnSync)
scripts/private-terms-cli.ts:5 local/no-raw-spawn (Bun.spawnSync)
scripts/private-terms-history.ts:16 local/no-raw-spawn (Bun.spawnSync)
scripts/private-terms-history.ts:54 local/no-raw-spawn (Bun.spawnSync)
scripts/private-terms.ts:216 local/no-raw-spawn (Bun.spawnSync)
scripts/private-terms.ts:327 local/no-raw-spawn (Bun.spawnSync)
scripts/prova.ts:95 local/no-raw-spawn (Bun.spawnSync)
scripts/replay-gates.ts:124 local/no-raw-spawn (Bun.spawnSync)
scripts/replay-gates.ts:132 local/no-raw-spawn (Bun.spawnSync)
scripts/risk-label.ts:69 local/no-raw-spawn (Bun.spawnSync)
scripts/risk.ts:144 local/no-raw-spawn (Bun.spawnSync)

Depois (branch)

$ bunx eslint scripts/; echo exit=$?
exit=0

Replay dos gates (o PR toca scripts/risk.ts, scripts/prova.ts e scripts/replay-gates.ts), após o merge da main (ec8ae54). fails-on-main e prova saem idênticos aos da main. risk difere em dois commits, ambos pelas novas superfícies scripts/behavior-change.ts / scripts/git.ts: o #262 ganha mais um motivo (já disparava) e o #245 passa de skipped para FIRED, como esperado para uma mudança no parser de diff que a guarda usa.

diff main branch -- replay-gates --gate risk
3c3
<   risk: FIRED (scripts/private-terms-corpus.ts (security): ...; scripts/private-terms-history.ts ...; scripts/private-terms.ts ...)
---
>   risk: FIRED (scripts/behavior-change.ts (security): diff parser the private-data guard imports changed; scripts/private-terms-corpus.ts ...)
21,22c21,22
<   risk: skipped (safe: no authentication, session, credential, data write or security surface change)
< replay-gates: 4 fired, 6 skipped
---
>   risk: FIRED (scripts/behavior-change.ts (security): diff parser the private-data guard imports changed)
> replay-gates: 5 fired, 5 skipped
bun scripts/replay-gates.ts --gate risk (branch)
replay-gates: 10 commits from origin/main
ec8ae54 chore(lint): estende no-loose-number, no-raw-cents, no-double-cast e knip a scripts/** (#262)
  risk: FIRED (scripts/behavior-change.ts (security): diff parser the private-data guard imports changed; scripts/private-terms-corpus.ts (security): private-data corpus check changed; scripts/private-terms-history.ts (security): private-data history helper changed; scripts/private-terms.ts (security): private-data guard changed)
1045a83 docs: align README, GLOSSARY and api-filters with the CLI (#261)
  risk: skipped (safe: no authentication, session, credential, data write or security surface change)
d1530d3 refactor(api): lê a MDCLI_API_URL na hora da chamada (#257)
  risk: FIRED (src/lib/api.ts (auth): unclassified change in a sensitive file; src/lib/api.ts (write): unclassified change in a sensitive file)
c6a404c refactor(browser-session): type the page global as MdWindow instead of any (#256)
  risk: FIRED (src/lib/browser-session/index.ts (auth): unclassified change in a sensitive file)
1c801d4 refactor(dates): stamp importId through nowMs() instead of Date.now() (#255)
  risk: FIRED (src/commands/entries/import/commit.ts (write): unclassified change in a sensitive file)
d3c672f chore(release): v2026.11.5 (#254)
  risk: skipped (safe: no authentication, session, credential, data write or security surface change)
0a96f92 test(ci): run browser tests outside the parallel pool (#253)
  risk: skipped (safe: no authentication, session, credential, data write or security surface change)
7b4a5e8 docs(agents): report a CI failure from its log, not a passing rerun (#252)
  risk: skipped (safe: no authentication, session, credential, data write or security surface change)
bf8f6d3 docs(agents): merge main before opening or pushing an implementation PR (#251)
  risk: skipped (safe: no authentication, session, credential, data write or security surface change)
4c0332a refactor(ci): dono único para "este diff muda comportamento" (#245)
  risk: FIRED (scripts/behavior-change.ts (security): diff parser the private-data guard imports changed)
replay-gates: 5 fired, 5 skipped
bun scripts/replay-gates.ts --gate fails-on-main
replay-gates: 10 commits from origin/main
ec8ae54 chore(lint): estende no-loose-number, no-raw-cents, no-double-cast e knip a scripts/** (#262)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
1045a83 docs: align README, GLOSSARY and api-filters with the CLI (#261)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
d1530d3 refactor(api): lê a MDCLI_API_URL na hora da chamada (#257)
  fails-on-main: skipped (exempt from the title: the title declares `refactor`, a behavior-preserving change)
c6a404c refactor(browser-session): type the page global as MdWindow instead of any (#256)
  fails-on-main: skipped (exempt from the title: the title declares `refactor`, a behavior-preserving change)
1c801d4 refactor(dates): stamp importId through nowMs() instead of Date.now() (#255)
  fails-on-main: skipped (exempt from the title: the title declares `refactor`, a behavior-preserving change)
d3c672f chore(release): v2026.11.5 (#254)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
0a96f92 test(ci): run browser tests outside the parallel pool (#253)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
7b4a5e8 docs(agents): report a CI failure from its log, not a passing rerun (#252)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
bf8f6d3 docs(agents): merge main before opening or pushing an implementation PR (#251)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
4c0332a refactor(ci): dono único para "este diff muda comportamento" (#245)
  fails-on-main: skipped (not required: the diff does not change src/ or bin/)
replay-gates: 0 fired, 10 skipped
bun scripts/replay-gates.ts --gate prova
replay-gates: 10 commits from origin/main
ec8ae54 chore(lint): estende no-loose-number, no-raw-cents, no-double-cast e knip a scripts/** (#262)
  prova: skipped (not required: no src/ behavior change)
1045a83 docs: align README, GLOSSARY and api-filters with the CLI (#261)
  prova: skipped (not required: no src/ behavior change)
d1530d3 refactor(api): lê a MDCLI_API_URL na hora da chamada (#257)
  prova: FIRED (required and missing the "## Prova" section, a "Web:" line, a filled "### Antes (main)" block, a filled "### Depois (branch)" block)
c6a404c refactor(browser-session): type the page global as MdWindow instead of any (#256)
  prova: FIRED (required and missing the "## Prova" section, a "Web:" line, a filled "### Antes (main)" block, a filled "### Depois (branch)" block)
1c801d4 refactor(dates): stamp importId through nowMs() instead of Date.now() (#255)
  prova: FIRED (required and missing the "## Prova" section, a "Web:" line, a filled "### Antes (main)" block, a filled "### Depois (branch)" block)
d3c672f chore(release): v2026.11.5 (#254)
  prova: skipped (not required: no src/ behavior change)
0a96f92 test(ci): run browser tests outside the parallel pool (#253)
  prova: skipped (not required: no src/ behavior change)
7b4a5e8 docs(agents): report a CI failure from its log, not a passing rerun (#252)
  prova: skipped (not required: no src/ behavior change)
bf8f6d3 docs(agents): merge main before opening or pushing an implementation PR (#251)
  prova: skipped (not required: no src/ behavior change)
4c0332a refactor(ci): dono único para "este diff muda comportamento" (#245)
  prova: skipped (not required: no src/ behavior change)
replay-gates: 3 fired, 7 skipped
  • Modelo: claude-opus-5-5
  • Harness: t3/claude code

Every gate in scripts/ spawned git, gh and bun through its own
Bun.spawnSync wrapper, with its own error text, path quoting and
origin/main default, and the oven-sh/bun#34069 workaround reached only
check-private-messages.ts.

- scripts/git.ts owns spawn/spawnAsync, the git helpers (always with
  core.quotepath=false) and DEFAULT_BASE; every caller migrates to it.
- check-eslint-disables and the private-terms corpus read numbered lines
  from behavior-change's parseDiff instead of their own parsers;
  parseDiff now keeps a file whose header git quotes.
- local/no-raw-spawn bans Bun.spawn, Bun.spawnSync, child_process and the
  Bun shell in scripts/ outside the owner.

Closes #260

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the risco PR arriscado: exige /blast-radius e decisão do mantenedor label Nov 6, 2026
Resolve the conflicts with #262: check-eslint-disables, private-terms-history
and replay-gates keep the scripts/git.ts helpers and the shared parseDiff,
which now reads hunk numbers through parseInteger as #262 required of the old
parsers. scripts/git.ts and scripts/behavior-change.ts join the security
surfaces, since the private-data guard now loads both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fm1randa
fm1randa merged commit e5849f3 into main Nov 6, 2026
7 of 8 checks passed
@fm1randa
fm1randa deleted the chore/260-scripts-git-owner branch November 6, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risco PR arriscado: exige /blast-radius e decisão do mantenedor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(ci): scripts/git.ts dono único de spawn git/Bun nos gates

1 participant