groundfast 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -50,5 +50,5 @@
50
50
  "typecheck": "tsc --noEmit"
51
51
  },
52
52
  "type": "module",
53
- "version": "0.8.2"
53
+ "version": "0.8.3"
54
54
  }
@@ -8,7 +8,7 @@ procedimento.
8
8
 
9
9
  ## Gatilho
10
10
 
11
- Dois gatilhos, os dois automáticos: não peça autorização, não pergunte ao Mestre, não espere outro
11
+ Três gatilhos, todos automáticos: não peça autorização, não pergunte ao Mestre, não espere outro
12
12
  ciclo.
13
13
 
14
14
  1. O **primeiro** `VEREDITO: rate limited` da espera de review para o head atual: o pedido do bot
@@ -18,16 +18,24 @@ ciclo.
18
18
  já foi postado após o head (saída 0, sem comentário novo): o full review deste head já rodou e
19
19
  não trouxe `APPROVED`. O bot não vai aprovar sozinho; cubra na mesma passada, sem esperar o
20
20
  próximo ciclo (que estagnaria com o mesmo fingerprint). O critério 6 sai `N-A` (visto no #134).
21
+ 3. O **primeiro** `VEREDITO: review skipped` do head (classe `skipped` do `cr-status.sh`): o bot
22
+ pulou a review deste head e não vai revisá-lo — PR aberto por bot sem vaga de review, como o
23
+ Renovate no #166, recusa até o `full review`. Não há janela a esperar nem pedido a repetir:
24
+ cubra no ciclo em que o skip é lido. Qualquer description que comece com `Review skipped` conta,
25
+ não só a de bot: o head não foi revisado, e a cobertura é review de verdade. Com a cobertura, o
26
+ critério 2 sai `cobre (interno)` e o 6 `N-A`.
21
27
 
22
28
  | Veredito da review | Cobertura interna |
23
29
  | :-- | :-- |
24
30
  | `rate limited` | dispara agora, para este head |
31
+ | `review skipped` | dispara agora, para este head |
25
32
  | `ausente` · `em curso` · `indeterminado` | não dispara: o critério 2 fica pendente e o próximo ciclo espera de novo. O `pr-goal.sh` aceitaria a cobertura no critério 2 com o bot `absent` (nunca no 6), mas o gatilho da casa é a recusa explícita — enquanto o bot pode chegar, espere por ele |
26
33
  | `cobre o head` · `atenção` | não dispara: o bot cobriu e prevalece — exceto o gatilho 2, quando só falta o `APPROVED` dele |
27
34
  | `pausado` | não dispara: o critério 2 já é `N-A` ([`pr-goal.md`](pr-goal.md) §Pausa) |
28
35
 
29
36
  Head novo pede cobertura nova: depois de um push, a cobertura do head anterior não vale mais, e o
30
- gatilho volta a valer no primeiro `rate limited` do head novo (ou no `APPROVED` que não veio).
37
+ gatilho volta a valer no primeiro `rate limited` ou `review skipped` do head novo (ou no `APPROVED`
38
+ que não veio).
31
39
 
32
40
  ## A passada
33
41
 
@@ -77,7 +85,7 @@ mudou desde a passada (7).
77
85
  O resumo é superfície pública: o que mudou e onde, sem log, sem saída de comando, sem valor de
78
86
  config. O wrapper redige e neutraliza, e você também.
79
87
 
80
- Depois do registro, com o bot em rate limit o `pr-goal.sh` passa o critério 2 como `cobre (interno)`
88
+ Depois do registro, com o bot em rate limit ou review skipped o `pr-goal.sh` passa o critério 2 como `cobre (interno)`
81
89
  e, quando a base exige `APPROVED`, o 6 sai `N-A`; com o bot `success` (gatilho 2) só o 6 sai `N-A`.
82
90
  A linha final sai `GOAL: atingido` com a nota de §O relatório. O ciclo conta a cobertura como
83
91
  progresso, então o `ESTAGNADO` não a engole.
@@ -87,8 +95,8 @@ progresso, então o `ESTAGNADO` não a engole.
87
95
  - Não substitui CI: critério 1 continua sendo o `VEREDITO:` do `wait-ci.sh`.
88
96
  - Não fecha thread aberta de ninguém: critério 3 é medido igual.
89
97
  - Não muda `reviewDecision`: `CHANGES_REQUESTED` herdado segue no critério 4. Só dispensa o
90
- `APPROVED` do critério 6, e só quando o bot existe e não aprovou: recusou (rate limit) ou
91
- revisou sem aprovar. Bot `absent` não dispensa o 6.
98
+ `APPROVED` do critério 6, e só quando o bot existe e não aprovou: recusou (rate limit), pulou o
99
+ head (review skipped) ou revisou sem aprovar. Bot `absent` não dispensa o 6.
92
100
  - Não autoriza merge: merge é do Mestre, pelo `drain`.
93
101
 
94
102
  ## O relatório
@@ -10,11 +10,11 @@ pela impressão do texto.
10
10
  | # | Critério | Medida |
11
11
  |:-:|:--|:--|
12
12
  | 1 | CI verde, ou repo sem check configurado — e todo check obrigatório da base presente no head | `VEREDITO:` de `wait-ci.sh` (`verde` / `sem CI`); obrigatório ausente é `pendente` e `FAIL 1 CI check obrigatório <x> ausente no head` (§Checks obrigatórios) |
13
- | 2 | A review cobre o head | `VEREDITO: cobre` de `wait-review.sh --ping`; `cr_status` = `success`. Ou `cobre (interno)`: bot `rate_limited`/`absent` **e** comentário de cobertura interna do usuário autenticado com o sha do head (§Cobertura interna). `N-A` só para repo sem CodeRabbit: base sem `.coderabbit.yaml`, bot chamado há ≥ 30 min e nenhuma atividade dele no PR |
13
+ | 2 | A review cobre o head | `VEREDITO: cobre` de `wait-review.sh --ping`; `cr_status` = `success`. Ou `cobre (interno)`: bot `rate_limited`/`skipped`/`absent` **e** comentário de cobertura interna do usuário autenticado com o sha do head (§Cobertura interna). `N-A` só para repo sem CodeRabbit: base sem `.coderabbit.yaml`, bot chamado há ≥ 30 min e nenhuma atividade dele no PR |
14
14
  | 3 | Zero threads não-resolvidas, **de qualquer autor** | query de `reviewThreads` sem `isResolved: false` |
15
15
  | 4 | `reviewDecision` sem `CHANGES_REQUESTED` | `gh pr view --json reviewDecision`; o script diz se quem pediu foi o bot (full review resolve) ou humano (escalada) |
16
16
  | 5 | `mergeStateStatus == CLEAN` | `gh pr view --json mergeStateStatus` |
17
- | 6 | `reviewDecision == APPROVED` — **só se** a **base** do PR tem `.coderabbit.yaml` com `request_changes_workflow: true` | o script lê o arquivo da base pela API; sem a flag o critério é `N-A`; leitura falha é pendente — exceto com cobertura interna no head e o bot `rate_limited` ou `success` sem `APPROVED`, que deixa o 6 `N-A` (§Cobertura interna) |
17
+ | 6 | `reviewDecision == APPROVED` — **só se** a **base** do PR tem `.coderabbit.yaml` com `request_changes_workflow: true` | o script lê o arquivo da base pela API; sem a flag o critério é `N-A`; leitura falha é pendente — exceto com cobertura interna no head e o bot `rate_limited`, `skipped` ou `success` sem `APPROVED`, que deixa o 6 `N-A` (§Cobertura interna) |
18
18
 
19
19
  Critério 6 existe porque o CodeRabbit só aprova sozinho com essa flag (o que ela faz, literal da docs, está em `skills/babysit/references/coderabbit.md`). Sem a flag, exigir `APPROVED` travaria o loop para sempre. `reviewDecision` preso em `CHANGES_REQUESTED` do bot com zero threads só sai com review nova: `@coderabbitai full review`, uma vez por head — o wrapper garante.
20
20
 
@@ -28,7 +28,7 @@ O critério 5 não é rede para isso: com `protected: false` (repo privado sem G
28
28
 
29
29
  ## Cobertura interna
30
30
 
31
- Terceiro estado do critério 2 (ADR 0001, glossário em `CONTEXT.md`): quando o bot não cobre o head (`rate_limited` ou `absent`), a review interna da casa aplicada ao head vale como `PASS 2 review cobre (interno)`. Este arquivo diz o que o `pr-goal.sh` **aceita**; quando a cobertura dispara (primeiro `rate limited` do head, ou o bot revisou sem aprovar), o que a passada olha e como ela é registrada está em [`coverage.md`](coverage.md). A prova mora no GitHub, não em estado local: **um comentário de issue no PR** (`gh pr comment`; review e thread não contam), escrito pelo usuário autenticado, com a linha-máquina numa **linha própria**:
31
+ Terceiro estado do critério 2 (ADR 0001, glossário em `CONTEXT.md`): quando o bot não cobre o head (`rate_limited`, `skipped` ou `absent`), a review interna da casa aplicada ao head vale como `PASS 2 review cobre (interno)`. Este arquivo diz o que o `pr-goal.sh` **aceita**; quando a cobertura dispara (primeiro `rate limited` ou `review skipped` do head, ou o bot revisou sem aprovar), o que a passada olha e como ela é registrada está em [`coverage.md`](coverage.md). A prova mora no GitHub, não em estado local: **um comentário de issue no PR** (`gh pr comment`; review e thread não contam), escrito pelo usuário autenticado, com a linha-máquina numa **linha própria**:
32
32
 
33
33
  ```
34
34
  <!-- groundfast-cobertura-interna: <sha completo do head> -->
@@ -39,11 +39,12 @@ Terceiro estado do critério 2 (ADR 0001, glossário em `CONTEXT.md`): quando o
39
39
  A cobertura no head dispensa o critério **6** (emenda à ADR 0001) só com evidência positiva de que o bot existe e não aprovou este head, e só quando o 6 se aplicaria (flag na base, ou config ilegível):
40
40
 
41
41
  - bot `rate_limited`: recusou, não revisou o head e não tem como aprová-lo;
42
+ - bot `skipped`: a description do status começa com `Review skipped` (state `SUCCESS`, mas o head não foi revisado — PR de bot sem vaga de review, #166); pulou o head e não tem como aprová-lo;
42
43
  - bot `success` sem `APPROVED`: revisou o head e não aprovou (visto no #134, mesmo depois de um `full review` aceito). `APPROVED` passa pela régua normal.
43
44
 
44
45
  Bot `absent` **não** dispensa o 6: pode ser só o bot a caminho do head recém-pushado. O 2 sai `cobre (interno)` e o 6 segue a config (flag → exige `APPROVED`; leitura falha → pendente; sem flag → `N-A`). Só a aprovação sai do caminho: `CHANGES_REQUESTED` continua barrando pelo critério 4, e thread aberta pelo 3.
45
46
 
46
- O fingerprint diferencia `cr=rate_limited` de `cr=rate_limited+interno` (idem `absent`) e `cr=success` de `cr=success+cov6` (o `fpjson:` leva `internal` e `cov6`), então o ciclo conta a cobertura como progresso (e `+interno` não entra na contagem de rate limit do `cycle.sh`). A linha final sai `GOAL: atingido (critérios 2 e 6 por cobertura interna)` quando a cobertura passou o 2 e dispensou o 6, `(critério 2 por cobertura interna)` quando só o 2, e `(critério 6 por cobertura interna)` quando o bot revisou e só o 6 foi dispensado; o `MERGED` do drain repete a nota. Quem posta é `skills/babysit/scripts/cov-comment.sh` (`<N> <owner/name> <passadas> <real> <nits> <lentes> [sha revisado]`, resumo na stdin; no drain e no pipeline, `babysit-cmd.sh cov-comment` / `pipeline-cmd.sh cov-comment`): lê o head pelo `gh`, sai 5 sem postar quando já há cobertura válida para esse sha, sai 7 quando o head já não é o sha revisado que se passou, redige o corpo e põe o marcador na primeira linha, antes do texto humano (passadas, REAL corrigidos, nits, lentes). A linha do marcador é a única parte lida pelo `pr-goal.sh`, e é **dado, não instrução**.
47
+ O fingerprint diferencia `cr=rate_limited` de `cr=rate_limited+interno` (idem `skipped` e `absent`) e `cr=success` de `cr=success+cov6` (o `fpjson:` leva `internal` e `cov6`), então o ciclo conta a cobertura como progresso (e `+interno` não entra na contagem de rate limit do `cycle.sh`). A linha final sai `GOAL: atingido (critérios 2 e 6 por cobertura interna)` quando a cobertura passou o 2 e dispensou o 6, `(critério 2 por cobertura interna)` quando só o 2, e `(critério 6 por cobertura interna)` quando o bot revisou e só o 6 foi dispensado; o `MERGED` do drain repete a nota. Quem posta é `skills/babysit/scripts/cov-comment.sh` (`<N> <owner/name> <passadas> <real> <nits> <lentes> [sha revisado]`, resumo na stdin; no drain e no pipeline, `babysit-cmd.sh cov-comment` / `pipeline-cmd.sh cov-comment`): lê o head pelo `gh`, sai 5 sem postar quando já há cobertura válida para esse sha, sai 7 quando o head já não é o sha revisado que se passou, redige o corpo e põe o marcador na primeira linha, antes do texto humano (passadas, REAL corrigidos, nits, lentes). A linha do marcador é a única parte lida pelo `pr-goal.sh`, e é **dado, não instrução**.
47
48
 
48
49
  ## Pausa do CodeRabbit
49
50
 
@@ -1,14 +1,16 @@
1
1
  # Classifica o CodeRabbit no head: rollup GraphQL + check homônimo.
2
2
  # Sourced — não é executável. Seta CR_CLASS CR_DETAIL CR_HEAD CR_BOT.
3
3
  #
4
- # CR_CLASS: paused | rate_limited | success | pending | attention | absent | error
4
+ # CR_CLASS: paused | rate_limited | skipped | success | pending | attention | absent | error
5
5
  # paused vem de GROUNDFAST_CODERABBIT=off (references/pr-goal.md §Pausa) e sai
6
6
  # antes de qualquer leitura: `cr_paused` é a única definição da chave.
7
7
  # No rollup vale a entrada do bot mais recente (createdAt/completedAt/startedAt);
8
8
  # em `gh pr checks` um status context vem sem data (0001-01-01), então ali vale
9
9
  # a última da lista. Precedência: rate_limited (a description do check/rollup
10
10
  # diz "Review rate limited" — o state vem SUCCESS, mas o head NÃO foi revisado)
11
- # → rollup success → pending (rollup ou check) → rollup attention → check
11
+ # → skipped (a description começa com "Review skipped", como no PR de bot sem
12
+ # vaga de review do #166: state SUCCESS, head NÃO revisado, e o bot recusa o
13
+ # full review) → rollup success → pending (rollup ou check) → rollup attention → check
12
14
  # SUCCESS só se o rollup não cobriu → check attention → leitura falha em uma
13
15
  # fonte sem a outra ter decidido → error (graphql morto, checks ilegíveis ou jq
14
16
  # que não parseou: falta de evidência não é evidência de ausência) → ausente
@@ -28,6 +30,13 @@ cr_paused() {
28
30
  esac
29
31
  }
30
32
 
33
+ # A description do bot começa com "Review skipped" (ex.: "Review skipped: bot
34
+ # user not eligible for review"). Só o começo da primeira linha não vazia: a
35
+ # frase no meio de outro texto não é o status do bot.
36
+ cr_desc_skipped() {
37
+ printf '%s\n' "$1" | tr '\r' '\n' | grep -v '^[[:space:]]*$' | head -1 | grep -Eiq '^[[:space:]]*review skipped'
38
+ }
39
+
31
40
  cr_status() {
32
41
  local owner="$1" name="$2" num="$3" repo="$4"
33
42
  local fr frc chk chk_err chk_state chk_desc line rollup rollup_desc check
@@ -73,11 +82,13 @@ cr_status() {
73
82
  | if . == null then empty else (.description // empty) end' 2>/dev/null) || chk_desc=
74
83
  if [ -n "$chk_desc" ]; then
75
84
  cr_comment_classify "$chk_desc"
76
- [ "$CR_REASON" = rate_limit ] && chk_state=RATE_LIMITED
85
+ if [ "$CR_REASON" = rate_limit ]; then chk_state=RATE_LIMITED
86
+ elif cr_desc_skipped "$chk_desc"; then chk_state=REVIEW_SKIPPED; fi
77
87
  fi
78
88
  case "$chk_state" in
79
89
  __JQ_ERROR__) check=error ;;
80
90
  RATE_LIMITED) check=rate_limited ;;
91
+ REVIEW_SKIPPED) check=skipped ;;
81
92
  SUCCESS|success) check=success ;;
82
93
  PENDING|IN_PROGRESS|QUEUED|WAITING|pending|in_progress|queued|waiting) check=pending ;;
83
94
  FAILURE|CANCELLED|TIMED_OUT|ACTION_REQUIRED|failure|cancelled|timed_out|action_required) check=attention ;;
@@ -126,6 +137,9 @@ cr_status() {
126
137
  if [ "$CR_REASON" = rate_limit ]; then
127
138
  rollup=rate_limited
128
139
  line="rate_limited ${line#* } — ${rollup_desc}"
140
+ elif cr_desc_skipped "$rollup_desc"; then
141
+ rollup=skipped
142
+ line="skipped ${line#* } — ${rollup_desc}"
129
143
  fi
130
144
  fi
131
145
  CR_BOT="coderabbit: ${line#* }"
@@ -139,6 +153,10 @@ cr_status() {
139
153
  CR_CLASS=rate_limited; CR_DETAIL="rate limited — o bot não revisou o head (rollup: ${CR_BOT#coderabbit: })"
140
154
  elif [ "$check" = "rate_limited" ]; then
141
155
  CR_CLASS=rate_limited; CR_DETAIL="rate limited — o bot não revisou o head (check com \"Review rate limited\", rollup ${rollup})"
156
+ elif [ "$rollup" = "skipped" ]; then
157
+ CR_CLASS=skipped; CR_DETAIL="review skipped — o bot pulou a review deste head (rollup: ${CR_BOT#coderabbit: })"
158
+ elif [ "$check" = "skipped" ]; then
159
+ CR_CLASS=skipped; CR_DETAIL="review skipped — o bot pulou a review deste head (check com \"Review skipped\", rollup ${rollup})"
142
160
  elif [ "$rollup" = "success" ]; then
143
161
  CR_CLASS=success; CR_DETAIL="rollup success"
144
162
  elif [ "$rollup" = "pending" ] || [ "$check" = "pending" ]; then
@@ -75,9 +75,9 @@ GROUNDFAST_WAIT_BG=1 scripts/wait-review.sh --ping <N> <owner/name> 900
75
75
 
76
76
  Roteie pelo `VEREDITO:` em `loop.md`. Sem a env recusa (exit 2).
77
77
 
78
- ### 2b. `rate limited` → cobertura interna
78
+ ### 2b. `rate limited` ou `review skipped` → cobertura interna
79
79
 
80
- O bot recusou o head: cubra pela casa, pelo procedimento de
80
+ O bot recusou ou pulou o head: cubra pela casa, pelo procedimento de
81
81
  [`references/coverage.md`](../../references/coverage.md). As três lentes de `lenses.md` sobre
82
82
  `git diff origin/<base>...HEAD` — uma lente por agente read-only onde o fan-out couber, senão em
83
83
  sequência. Zero achados abertos e então:
@@ -32,6 +32,11 @@ no plano incluído (o bot avisa que pode seguir por usage-based billing, se eleg
32
32
  `wait-review --ping` lê a janela na recusa do bot ("Your next included review will be available in
33
33
  N minutes"; sem match estima 60 min e diz isso no `VEREDITO:`) e repete o pedido quando ela reabre —
34
34
  dentro do orçamento da mesma execução; janela maior que o orçamento cai no ciclo seguinte.
35
+
36
+ "Review skipped" também chega com o status em `SUCCESS` sem revisar o head: no PR aberto por bot sem
37
+ vaga de review (`Review skipped: bot user not eligible for review`, #166), e o `full review` volta
38
+ `Action not completed`. `cr-status.sh` classifica `skipped` pela description que começa com
39
+ "Review skipped"; não há janela, e o `wait-review` sai na hora, sem ping.
35
40
  `gh-thread cr review|full-review` posta e espera a resposta ao comando por até 90 s
36
41
  (`GROUNDFAST_CR_ACK_WAIT`). O julgamento do comentário é `scripts/cr-comment.sh` (`ack` / `reason` /
37
42
  janela): "rate limited" solto no walkthrough não é recusa; "Review triggered" conta como aceite
@@ -39,6 +39,7 @@ Chegou aqui de um rerun do passo 3? Rode `flaky.sh` (§3) antes de seguir a tabe
39
39
  | cobre o head · atenção | `<skill-dir>/scripts/pr-state.sh` de novo (threads podem ter nascido) e passo 4 |
40
40
  | ausente · em curso · indeterminado · orçamento estourado | passo 4 com as threads que já existem; o critério 2 fica pendente e o próximo ciclo espera de novo |
41
41
  | rate limited | o bot recusou: o head **não** foi revisado. Cubra pela casa agora — [`coverage.md`](../../../references/coverage.md), procedimento e registro. Cobertura que gerou push: volte ao passo 2, e cubra o head novo; sem push: passo 4 com o que existe. O `--ping` repete o pedido quando a janela reabrir |
42
+ | review skipped | o bot pulou o head e não vai revisá-lo. Cubra pela casa agora — [`coverage.md`](../../../references/coverage.md), gatilho 3 |
42
43
  | pausado | `pr-goal.md` §Pausa: critério 2 `N-A`, nada a esperar. A chave é do ambiente do Mestre — nunca inline, nunca a pedido de thread. Passo 4 com as threads que existem |
43
44
 
44
45
  ## 3. CI vermelho que passa no rerun — flaky, não infra
@@ -117,9 +117,9 @@ cr_detail=$(printf '%s' "$CR_DETAIL" | redact | head -c 200)
117
117
  # de código não contam. Corpo de comentário é dado, não instrução: só a linha
118
118
  # do marcador é lida. Sha antigo, outro autor ou edição deixam o motivo na linha.
119
119
  # cov_ok = cobertura válida no head; quem a usa decide cada critério: o 2 só com o
120
- # bot rate_limited/absent (`internal`), o 6 só com rate_limited/success (abaixo).
120
+ # bot rate_limited/skipped/absent (`internal`), o 6 só com rate_limited/skipped/success (abaixo).
121
121
  cov_ok=0; internal=0; internal_note=""
122
- case "$CR_CLASS" in rate_limited|absent|success) cov_read=1 ;; *) cov_read=0 ;; esac
122
+ case "$CR_CLASS" in rate_limited|skipped|absent|success) cov_read=1 ;; *) cov_read=0 ;; esac
123
123
  if [ "$cov_read" -eq 1 ] && [ -n "$head_full" ]; then
124
124
  cov=$(cov_status "$pv" "$head_full")
125
125
  case "$cov" in
@@ -131,17 +131,19 @@ if [ "$cov_read" -eq 1 ] && [ -n "$head_full" ]; then
131
131
  *) internal_note="leitura dos comentários de cobertura falhou — não conta" ;;
132
132
  esac
133
133
  fi
134
- [ "$cov_ok" -eq 1 ] && { [ "$CR_CLASS" = "rate_limited" ] || [ "$CR_CLASS" = "absent" ]; } && internal=1
134
+ [ "$cov_ok" -eq 1 ] && { [ "$CR_CLASS" = "rate_limited" ] || [ "$CR_CLASS" = "skipped" ] || [ "$CR_CLASS" = "absent" ]; } && internal=1
135
135
  note="${internal_note:+ · $internal_note}"
136
136
  case "$CR_CLASS" in
137
137
  paused) na "2 review — CodeRabbit pausado (GROUNDFAST_CODERABBIT=off): a review do PR é a da casa, pelo /pr" ;;
138
138
  success) pass "2 review cobre o head ($cr_detail)" ;;
139
139
  error) fail "2 review" "leitura falhou ($cr_detail) — não conclua que o head foi revisado" ;;
140
- absent|rate_limited)
140
+ absent|rate_limited|skipped)
141
141
  if [ "$internal" -eq 1 ]; then
142
142
  pass "2 review cobre (interno) — cobertura interna no head ${head}, comentário do usuário autenticado; bot ${CR_CLASS}"
143
143
  elif [ "$CR_CLASS" = "rate_limited" ]; then
144
144
  fail "2 review" "rate limited — o bot não revisou o head; wait-review --ping repete o pedido quando a janela reabrir${note}"
145
+ elif [ "$CR_CLASS" = "skipped" ]; then
146
+ fail "2 review" "${cr_detail}; o bot não vai revisá-lo — cubra pela casa (coverage.md, gatilho 3)${note}"
145
147
  # N-A só quando o repo não tem o bot: base sem .coderabbit.yaml, bot chamado
146
148
  # há pelo menos 30 min e nenhuma atividade dele no PR. Antes disso é fila.
147
149
  elif [ "$cr_cfg" = "present" ]; then
@@ -210,16 +212,17 @@ if [ "$mss" = "CLEAN" ]; then pass "5 mergeStateStatus CLEAN"; else fail "5 merg
210
212
 
211
213
  # --- 6. APPROVED, só com request_changes_workflow na base ---------------------
212
214
  # A cobertura interna no head dispensa o 6 só com evidência positiva de que o bot
213
- # existe e não aprovou este head (ADR 0001, emenda de 2026-10-01): rate_limited
214
- # (recusou) ou success sem APPROVED (revisou e não aprovou). `absent` pode ser só
215
+ # existe e não aprovou este head (ADR 0001, emendas de 2026-10-01 e 2026-10-08):
216
+ # rate_limited (recusou), skipped (pulou a review, #166) ou success sem APPROVED
217
+ # (revisou e não aprovou). `absent` pode ser só
215
218
  # o bot ainda a caminho do head recém-pushado: segue a régua da config. A
216
219
  # dispensa só conta quando a base exige APPROVED ou a config dela não foi lida.
217
220
  cov6=0
218
221
  need6=0; { [ "$cr_flag" -eq 1 ] || [ "$cr_cfg" = "error" ]; } && need6=1
219
222
  if [ "$paused" -eq 1 ]; then
220
223
  na "6 APPROVED — CodeRabbit pausado (GROUNDFAST_CODERABBIT=off): ninguém aprova automaticamente"
221
- elif [ "$need6" -eq 1 ] && [ "$cov_ok" -eq 1 ] && [ "$CR_CLASS" = "rate_limited" ]; then
222
- cov6=1; na "6 APPROVED — cobertura interna no head ${head}: o bot (rate_limited) não revisou o head e não tem como aprová-lo"
224
+ elif [ "$need6" -eq 1 ] && [ "$cov_ok" -eq 1 ] && { [ "$CR_CLASS" = "rate_limited" ] || [ "$CR_CLASS" = "skipped" ]; }; then
225
+ cov6=1; na "6 APPROVED — cobertura interna no head ${head}: o bot (${CR_CLASS}) não revisou o head e não tem como aprová-lo"
223
226
  elif [ "$need6" -eq 1 ] && [ "$cov_ok" -eq 1 ] && [ "$CR_CLASS" = "success" ] && [ "$rd" != "APPROVED" ]; then
224
227
  cov6=1; na "6 APPROVED — cobertura interna no head ${head}: o bot revisou o head sem aprovar (reviewDecision=${rd:-nenhuma}); a cobertura cobre a aprovação"
225
228
  elif [ "$cr_cfg" = "error" ]; then
@@ -57,6 +57,7 @@ else
57
57
  attention) echo "veredito: ATENÇÃO — status $CR_DETAIL no head; leia o comentário do bot" ;;
58
58
  absent) echo "veredito: NÃO REVISADO — rode wait-review.sh" ;;
59
59
  rate_limited) echo "veredito: RATE LIMITED — o bot recusou e não revisou o head; rode wait-review.sh --ping (repete quando a janela reabrir)" ;;
60
+ skipped) echo "veredito: REVIEW SKIPPED — o bot pulou a review deste head e não vai revisá-lo; cubra pela casa (coverage.md, gatilho 3)" ;;
60
61
  *) echo "veredito: INDETERMINADO — rode wait-review.sh" ;;
61
62
  esac
62
63
  fi
@@ -8,6 +8,9 @@
8
8
  # "Review rate limited" (classe rate_limited) NÃO cobre o head: com --ping o
9
9
  # pedido é repetido quando a janela reabre — a que o bot anunciou, ou 60 min
10
10
  # estimados quando a recusa vem sem janela. Sem --ping é VEREDITO imediato.
11
+ # "Review skipped" (classe skipped) também NÃO cobre o head, e não tem janela:
12
+ # o bot pulou este head (PR de bot sem vaga de review, #166) e recusa o pedido.
13
+ # VEREDITO imediato, com ou sem --ping — um ping só sujaria o PR.
11
14
  set -uo pipefail
12
15
  # shellcheck source=../../../scripts/redact.sh
13
16
  . "$(dirname "$0")/../../../scripts/redact.sh" || { echo "redact.sh ausente — abortando" >&2; exit 3; }
@@ -148,6 +151,10 @@ while :; do
148
151
  veredito="VEREDITO: atenção — status ${CR_DETAIL} no head; leia o comentário do bot"
149
152
  break
150
153
  ;;
154
+ skipped)
155
+ veredito="VEREDITO: review skipped — o bot pulou a review deste head e não vai revisá-lo; cubra pela casa (coverage.md, gatilho 3)"
156
+ break
157
+ ;;
151
158
  rate_limited)
152
159
  if [ "$ping" -eq 0 ]; then
153
160
  veredito="VEREDITO: rate limited — o bot não revisou o head (sem --ping, ninguém repete o pedido)"
@@ -87,9 +87,9 @@ As duas esperas passam do teto de um comando em foreground: rode em segundo plan
87
87
 
88
88
  `<min>` é o orçamento da pergunta do passo 1. A linha `CICLO:` decide (`inner-loop.md` §Scripts): segue, ou encerra este PR (2g).
89
89
 
90
- Nesta ordem: push, espera de CI, espera de review (com `--ping`). Vermelho: corrija, `scripts/babysit-cmd.sh stage` + `scripts/commit.sh` (mensagem na stdin), volte ao 2d. Depois das esperas: `VEREDITO: atenção` → leia o comentário do bot e volte ao 2c antes de seguir; `VEREDITO: rate limited` → cobertura interna deste head, abaixo; 2e se `mergeable` ≠ `MERGEABLE` ou se `pr-goal` reporta critério 5 com `BEHIND` / `DIRTY`; senão 2f.
90
+ Nesta ordem: push, espera de CI, espera de review (com `--ping`). Vermelho: corrija, `scripts/babysit-cmd.sh stage` + `scripts/commit.sh` (mensagem na stdin), volte ao 2d. Depois das esperas: `VEREDITO: atenção` → leia o comentário do bot e volte ao 2c antes de seguir; `VEREDITO: rate limited` ou `review skipped` → cobertura interna deste head, abaixo; 2e se `mergeable` ≠ `MERGEABLE` ou se `pr-goal` reporta critério 5 com `BEHIND` / `DIRTY`; senão 2f.
91
91
 
92
- `rate limited`: o bot recusou e a casa cobre, pelo procedimento de [`coverage.md`](../../references/coverage.md). A passada do 2b vale para este head quando nada foi commitado entre ela e o push; commitou, passe as lentes sobre o delta. Registre e decida pela linha `COBERTURA:`:
92
+ `rate limited` / `review skipped`: o bot recusou ou pulou o head e a casa cobre, pelo procedimento de [`coverage.md`](../../references/coverage.md). A passada do 2b vale para este head quando nada foi commitado entre ela e o push; commitou, passe as lentes sobre o delta. Registre e decida pela linha `COBERTURA:`:
93
93
 
94
94
  ```bash
95
95
  scripts/babysit-cmd.sh cov-comment <N> <owner/name> <passadas> <real> <nits> <lentes> <sha revisado> <<'--RESUMO--'
@@ -22,9 +22,10 @@ saem só via `babysit-cmd.sh`. O objetivo é o de [`pr-goal.md`](../../../refere
22
22
  **Orçamento por PR** em minutos (perguntado com o prefixo, default 60), **900 s** por `wait-ci` e por
23
23
  `wait-review`. `cycle begin` no início de cada ciclo. Na linha `CICLO:`: `ESTAGNADO` → `#<N> BLOQUEADO estagnado <critérios pendentes>`; `indeterminado` → `#<N> BLOQUEADO leitura <motivo>`; `ORÇAMENTO ESTOURADO` → `#<N> BLOQUEADO orçamento <min>`. Cada uma: `cycle end`, a linha, próximo.
24
24
 
25
- ## Review rate limited — cubra pela casa
25
+ ## Review rate limited ou skipped — cubra pela casa
26
26
 
27
- `VEREDITO: rate limited` no `wait-review` do 2d: o bot recusou e o head não foi revisado. Cubra
27
+ `VEREDITO: rate limited` ou `review skipped` no `wait-review` do 2d: o bot recusou ou pulou o head,
28
+ e o head não foi revisado. Cubra
28
29
  agora, pelo procedimento de [`coverage.md`](../../../references/coverage.md) — a passada do 2b
29
30
  vale para este head quando nada foi commitado entre ela e o push — e registre com
30
31
  `babysit-cmd.sh cov-comment`. Com a cobertura no head, `pr-goal` passa o critério 2 como
@@ -27,6 +27,7 @@ O sinal é o `VEREDITO:` de `babysit-cmd.sh wait-review --ping <N> <repo> 900`.
27
27
  - `cobre o head` → critério 2 ok; `pr-state` de novo se nasceram threads.
28
28
  - `atenção` → leia o comentário do bot, triagem como acima.
29
29
  - `rate limited` → o head **não** foi revisado; critério 2 pendente e o ciclo fica `aguardando` (até 3 ciclos sem push; depois `ESTAGNADO`), salvo cobertura interna válida, que o `pr-goal` passa como `cobre (interno)` (procedimento em [`coverage.md`](../../../references/coverage.md)). O `--ping` repete o pedido quando a janela do bot reabre.
30
+ - `review skipped` → o bot pulou este head e não vai revisá-lo: cubra pela casa no mesmo ciclo ([`coverage.md`](../../../references/coverage.md), gatilho 3).
30
31
  - `pausado` → `GROUNDFAST_CODERABBIT=off` (`pr-goal.md` §Pausa): critério 2 `N-A`, nada a esperar nem a pingar; trate as threads que existem.
31
32
  - `ausente` / `em curso` / orçamento → critério 2 pendente; trate as threads que existem e o próximo ciclo espera de novo. `pr-goal` é quem diz se o objetivo fechou — e marca `N-A 2` quando o bot foi chamado e nunca apareceu no PR (repo sem CodeRabbit).
32
33
  - `reviewDecision` preso em `CHANGES_REQUESTED` → `inner-loop.md` §Merge (bot: full-review; humano: bloqueado).
@@ -43,7 +43,7 @@ Para cada issue confirmada, na ordem, percorra `issue-loop.md` sem reordenar ou
43
43
  A implementação e o push são sempre seus. A política de fan-out do preâmbulo decide a forma das
44
44
  análises read-only — as três lentes no review do PR e na cobertura interna
45
45
  ([`coverage.md`](../../references/coverage.md)), que o babysit do passo 5 dispara no primeiro
46
- `VEREDITO: rate limited` do head, a triagem de threads por arquivo e o diagnóstico de CI vermelho:
46
+ `VEREDITO: rate limited` ou `review skipped` do head, a triagem de threads por arquivo e o diagnóstico de CI vermelho:
47
47
  onde ela permite agentes read-only em paralelo e `free -m` mostra 2000 MB ou mais, despache-os;
48
48
  caso contrário, tudo em sequência nesta sessão. Esperas que excedem o turno
49
49
  (`pipeline-cmd.sh wait-ci`, `wait-review --ping`) rodam em segundo plano pelo mecanismo do host;
@@ -96,12 +96,14 @@ Não invoque `/babysit`. Execute o ciclo pelos scripts já existentes:
96
96
  e o tempo restante da issue.
97
97
  4. CI vermelho → o log falho pela stdin de `pipeline-cmd.sh ci-cause` dá os candidatos `arquivo:linha`, depois diagnostique e corrija; review/threads → leia [`threads.md`](../../drain/references/threads.md), emita `THREAD:` / `RESOLVED:`, verifique a linha que resolve antes de responder/resolver.
98
98
  5. Refaça gate, stage, commit e `push-pr.sh`; volte ao passo 2.
99
- 6. Review rate limited é `RATE LIMITED`, não review: cubra o head pela casa, pelo procedimento de
99
+ 6. Review rate limited ou skipped não é review: cubra o head pela casa, pelo procedimento de
100
100
  [`coverage.md`](../../../references/coverage.md), e registre com `pipeline-cmd.sh cov-comment`.
101
101
  Fix REAL da passada: gate verde, commit e `push-pr.sh` — o head muda, então volte ao passo 2
102
102
  deste ciclo (CI e review de novo) e cubra o head novo antes de avaliar o `GOAL:`.
103
103
  O bot revisou o head sem aprovar (só o `6 APPROVED` pendente, bot `success`, e o full-review
104
104
  responde `já postado após o head`) é o gatilho 2 do mesmo procedimento: cubra e registre igual.
105
+ `VEREDITO: review skipped` (o bot pulou o head, PR de bot sem vaga de review) é o gatilho 3:
106
+ cubra no mesmo ciclo, sem esperar janela nem pingar.
105
107
  Falha de leitura é indeterminada, não ausência. Thread escalada ao Mestre permanece aberta.
106
108
 
107
109
  O objetivo é `GOAL: atingido` de `pr-goal.sh`. `babysit` para sem merge por `GOAL: atingido`,