@neetru/cli 2.29.0 → 2.31.0

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +61 -17
  3. package/dist/commands/deploy.d.ts +45 -0
  4. package/dist/commands/deploy.js +134 -1
  5. package/dist/commands/deploy.js.map +1 -1
  6. package/dist/commands/dev-up.d.ts +7 -5
  7. package/dist/commands/dev-up.js +24 -23
  8. package/dist/commands/dev-up.js.map +1 -1
  9. package/dist/commands/docs-produto.d.ts +65 -0
  10. package/dist/commands/docs-produto.js +322 -0
  11. package/dist/commands/docs-produto.js.map +1 -0
  12. package/dist/commands/init.d.ts +2 -0
  13. package/dist/commands/init.js +7 -0
  14. package/dist/commands/init.js.map +1 -1
  15. package/dist/commands/new.d.ts +2 -0
  16. package/dist/commands/new.js +2 -0
  17. package/dist/commands/new.js.map +1 -1
  18. package/dist/index.js +72 -6
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/api-client.d.ts +8 -1
  21. package/dist/lib/api-client.js +20 -2
  22. package/dist/lib/api-client.js.map +1 -1
  23. package/dist/lib/compose-generator.d.ts +13 -9
  24. package/dist/lib/compose-generator.js +28 -60
  25. package/dist/lib/compose-generator.js.map +1 -1
  26. package/dist/lib/config-schema.d.ts +40 -6
  27. package/dist/lib/config-schema.js +11 -0
  28. package/dist/lib/config-schema.js.map +1 -1
  29. package/dist/lib/product-docs/check.d.ts +62 -0
  30. package/dist/lib/product-docs/check.js +489 -0
  31. package/dist/lib/product-docs/check.js.map +1 -0
  32. package/dist/lib/product-docs/config.d.ts +66 -0
  33. package/dist/lib/product-docs/config.js +234 -0
  34. package/dist/lib/product-docs/config.js.map +1 -0
  35. package/dist/lib/product-docs/dates.d.ts +17 -0
  36. package/dist/lib/product-docs/dates.js +41 -0
  37. package/dist/lib/product-docs/dates.js.map +1 -0
  38. package/dist/lib/product-docs/frontmatter.d.ts +43 -0
  39. package/dist/lib/product-docs/frontmatter.js +302 -0
  40. package/dist/lib/product-docs/frontmatter.js.map +1 -0
  41. package/dist/lib/product-docs/git.d.ts +16 -0
  42. package/dist/lib/product-docs/git.js +92 -0
  43. package/dist/lib/product-docs/git.js.map +1 -0
  44. package/dist/lib/product-docs/markdown.d.ts +27 -0
  45. package/dist/lib/product-docs/markdown.js +98 -0
  46. package/dist/lib/product-docs/markdown.js.map +1 -0
  47. package/dist/lib/product-docs/ops.d.ts +133 -0
  48. package/dist/lib/product-docs/ops.js +975 -0
  49. package/dist/lib/product-docs/ops.js.map +1 -0
  50. package/dist/lib/product-docs/structure.d.ts +61 -0
  51. package/dist/lib/product-docs/structure.js +239 -0
  52. package/dist/lib/product-docs/structure.js.map +1 -0
  53. package/dist/lib/product-docs/sync.d.ts +101 -0
  54. package/dist/lib/product-docs/sync.js +313 -0
  55. package/dist/lib/product-docs/sync.js.map +1 -0
  56. package/dist/lib/product-docs/taxonomy.d.ts +46 -0
  57. package/dist/lib/product-docs/taxonomy.js +163 -0
  58. package/dist/lib/product-docs/taxonomy.js.map +1 -0
  59. package/dist/lib/product-docs/templates.d.ts +53 -0
  60. package/dist/lib/product-docs/templates.js +245 -0
  61. package/dist/lib/product-docs/templates.js.map +1 -0
  62. package/dist/lib/public-secret-scan.d.ts +76 -0
  63. package/dist/lib/public-secret-scan.js +446 -0
  64. package/dist/lib/public-secret-scan.js.map +1 -0
  65. package/package.json +1 -1
  66. package/templates/dev/Dockerfile.agent +0 -39
  67. package/templates/dev/agent-entrypoint.sh +0 -41
  68. package/templates/dev/docker-compose.dev.yml +0 -63
package/CHANGELOG.md CHANGED
@@ -4,6 +4,58 @@ Convenção: [Keep a Changelog](https://keepachangelog.com/) + [SemVer](https://
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Corrigido
8
+
9
+ - **`neetru dev up` voltou a subir o banco.** O compose gerado tinha um serviço `agent` com `build: dockerfile: Dockerfile.agent`, mas o comando só gravava o compose — o `Dockerfile.agent` nunca era escrito no projeto (a função que o copiaria não era chamada). O `docker compose up` falhava no build e nem o banco subia. Mesmo com o arquivo, o agente não rodaria: o entrypoint chamava `node` num executável `pkg` (não é JS), dependia de um `NEETRU_DEV_MODE` que o agente não implementa, e o container montava o `docker.sock` do host. Agora o `dev up` sobe **só o banco** do engine do projeto e imprime a conexão para o `.env.local` (`postgresql://postgres:neetru_dev@localhost:5432/app` etc.); o app roda com `npm run dev`. As mensagens deixaram de anunciar "App SaaS" e "Agente: conectado ao Core". O agente local volta quando o próprio agente tiver modo dev. Templates mortos removidos: `templates/dev/{Dockerfile.agent,agent-entrypoint.sh,docker-compose.dev.yml}`.
10
+ - README e `--help`: a seção de dev local descrevia flags do `dev up` que não existem (`--import`, `--export-on-exit`, `--skip-core`) e o tratava como "emulador completo, 100% offline". Agora descreve o que roda de fato na máquina do dev de produto. `dev exec` documentado como ferramenta do CI do Core (exige o `firebase.json` do Core).
11
+
12
+ ## [2.31.0] — 2026-10-03 (docs do produto: `neetru docs init|new|check|sync`)
13
+
14
+ Implementa a parte de ferramenta do **protocolo de documentação dos produtos SaaS** (Core, `docs/sistema/contrib/PROTOCOLO_DOCS_PRODUTOS_SAAS.md` v1, com a taxonomia de `PROTOCOLO_WIKI_INTERNA.md` §5–§7). Decisões do dono de 2026-10-03: o deploy **só avisa** sobre docs (nunca bloqueia); publicar no Core é **opcional** — opt-in `docs.publishOnDeploy` no deploy de produção ou manual com `neetru docs sync`; `--migrar` não move nem renomeia nada.
15
+
16
+ ### Adicionado
17
+
18
+ - **`neetru docs init [--com-api] [--migrar] [--dry-run] [--json]`** — cria o mínimo do primeiro dia (§7): pastas do §3 (com `.gitkeep` nas vazias), `docs/index.md`, `docs/produto/VISAO_DO_PRODUTO.md`, `docs/arquitetura/ARQUITETURA.md` (com a seção "Integração com o Core"), `docs/runbooks/RUNBOOK_DEPLOY.md`, `README.md` (3 seções) e `CHANGELOG.md` (Keep a Changelog PT-BR, versão do `package.json`), cada doc com frontmatter preenchido (`product`, datas de hoje, `status: draft`, `owner: produto`, `audience` da pasta) e um `<!-- o que escrever aqui -->` por seção. **Idempotente**: nunca sobrescreve; informa o que criou e o que pulou. `API_V1.md` só com `--com-api` (grava `docs.api: true`) ou quando o produto declara app móvel (`.neetru/mobile.json`) ou integração. Acrescenta a chave `docs` no `neetru.config.json` sem mexer no resto (preserva indentação, fim de linha e a quebra final).
19
+ - **`--migrar`** (repo que já tem docs): acrescenta frontmatter inferido **só** nos `.md` que não têm — `title` do H1, `description` da primeira frase útil, `type`/`category` pela pasta (inclusive as legadas: `architecture/`, `design/`, `guia-do-usuario/`, `_review/`, `deploy/`…) e pelo prefixo ou palavra-chave do nome, `status` pela família, `date`/`updated` pelo primeiro/último commit (ou `_revisado: AAAA-MM-DD_` do corpo), `tags: [migrado]`. **Não move, não renomeia e não altera o corpo** (o arquivo vira exatamente frontmatter + linha em branco + conteúdo original; BOM e CRLF preservados); quem já tem frontmatter fica intocado. Gera `docs/_nomes-legados.json` (só se não existir — a lista só diminui) e o relatório `docs/_audit/MIGRACAO_DOCS_<data>.md` com cada inferência, a origem e a confiança, as sugestões de destino pelo protocolo e o que falta do mínimo do primeiro dia (que **não** é criado no `--migrar`: vem depois de mover, com `docs init`).
20
+ - **`neetru docs new <tipo> "<título>" [--categoria] [--subcategoria] [--audience] [--descricao] [--data] [--dry-run] [--json]`** — doc a partir de modelo embutido, na pasta do §3 e com o nome do §6/§8 (sem acento, MAIÚSCULAS, prefixo do tipo — `AUDITORIA_`/`PROTOCOLO_` quando o título já começa assim —, data no fim nos registros, próximo `ADR-NNNN_` livre). Tipos: `guia`, `referencia`, `arquitetura`, `adr`, `runbook`, `norma`, `plano`, `relatorio`, `incidente`, `ata`, e os modelos-base do zelo: `rfc`/`spec` (viram `plano`), `politica` (vira `norma`), `ameacas` (modelo de ameaças, `referencia` em `seguranca/`). Nunca sobrescreve.
21
+ - **`neetru docs check [--changed] [--strict] [--offline] [--json]`** — validador **offline** com os mesmos códigos do Core (FM001–FM013, TI001–TI008, NM001–NM002, LK001–LK002, PB001, LC001–LC002) mais PR001 (`product` ausente ou diferente do slug), PR002 (mínimo do primeiro dia e seções do README; os itens "do primeiro mês" saem como **sugestão**, que nunca falha), PR003 (categoria só do Core) e PR004 (CHANGELOG sem a versão do `package.json`). `_archive/` e `_audit/` com as regras reduzidas do §9.4. `--changed` = catraca contra a branch principal (commits + staged + não commitados + não rastreados; sem git, avisa e checa tudo). `--strict` = aviso também falha. Sai 1 com erro.
22
+ - **Taxonomia embutida** (`src/lib/product-docs/taxonomy.ts`) idêntica ao bloco JSON do §5.2 do protocolo — teste confere contra a fixture copiada do protocolo e, quando há checkout do Core (`NEETRU_CORE_DIR`), contra o próprio markdown. Online, usa `GET /api/cli/v1/docs/taxonomy` do Core (outra frente cria a rota) e cai na embutida em 404, falha de rede ou formato inválido; avisa quando a do Core é mais nova.
23
+ - **`neetru docs sync [--dry-run] [--offline] [--json]`** — publica `docs/` no registro de docs por produto do Core, contra o contrato **proposto** em `PROJ_PRODUCT_DOCS` §4.2 (`GET`/`POST /api/cli/v1/products/{productId}/docs`, corpo `{slug, content, frontmatter, visibility}`). Só sobem docs **sem erro** no check (fora `_archive/` e `_audit/`); `visibility: public` só com `audience: publico` + `status: active`; doc igual ao do Core (sha256 do conteúdo + título + status + visibilidade) não é reenviado; **nada é apagado** — doc que só existe no Core é listado como órfão. Limite local de 200 docs / 10 MB (D-05). **Capacidade**: se o Core responde 404 (sem `product_not_found`), 405 ou 501, mostra "O Core ainda não aceita docs de produto" e sai **0**. `docs.publish: false` desliga.
24
+ - **`neetru init` e `neetru new` chamam `docs init`** depois do scaffold (`--sem-docs` pula; falha nos docs vira aviso, não derruba o init).
25
+ - **`neetru deploy`** roda `docs check` (taxonomia embutida, sem rede extra) e **só avisa** — em produção, lista o que falta do mínimo do primeiro dia. Com `docs.publishOnDeploy: true`, o deploy de **produção** bem-sucedido chama o sync no fim (VM, workspace e cloud-run); falha do sync vira aviso e o deploy continua com exit 0. Em `--json`, a saída ganha `docs: { check, sync }`. `--sem-docs` pula as duas coisas.
26
+ - `neetru.config.json`: bloco `docs` (`protocolVersion`, `root`, `publish`, `publishOnDeploy`, `api`) no schema.
27
+
28
+ ### Corrigido (ensaio real nos 3 produtos e alinhamento com o servidor do Core)
29
+
30
+ - Datas no fuso de Brasília: depois das 21h BRT, docs, CHANGELOG, relatório e `gerado_em` saíam com o dia seguinte (UTC).
31
+ - `suggestedName` preserva a data ISO com hífens (`…_2026-06-30.md`); antes virava `_2026_06_30` e reprovava o NM002.
32
+ - CHANGELOG do `docs init` em produto maduro: entrada da versão atual, sem "Primeira versão do produto." inventada.
33
+ - `index.md` gerado lista tudo o que existe (inclusive `superpowers/`, `_review/`, pastas fora do padrão e docs da raiz), marcando o que está fora do padrão.
34
+ - Monorepo: o `neetru.config.json` é procurado subindo pastas até a raiz do git; `docs.root` pode apontar acima do config, mas nunca para fora do repositório.
35
+ - `--migrar`: inferências de tipo/destino melhores (`DESIGN*` não vira plano; doc solto na raiz recebe tipo coerente com o destino sugerido; `deploy/README.md` não vira índice; destinos do §10).
36
+ - `resolveTaxonomy` aceita o formato real do Core (`categories` como lista + `subcategories` como mapa); a cópia embutida passa a esconder `superpowers/`, como o protocolo.
37
+ - `docs sync`: espaça os envios (janela de 25 por minuto) e respeita `Retry-After` no 429; compara também os metadados com o Core; confere localmente o limite de 2 MB por doc e os caracteres aceitos no caminho (acento/espaço/`__` são recusados antes do envio); a dica de órfão não cita mais um comando que não existe.
38
+
39
+ ### Dependência no Core
40
+
41
+ - `GET /api/cli/v1/docs/taxonomy` (Core PR #565) e `GET`/`POST /api/cli/v1/products/{productId}/docs` (Core PR #571). Sem eles no ar, a CLI funciona igual (taxonomia embutida; o sync informa e sai 0).
42
+
43
+ ## [2.30.0] — 2026-10-03 (varredura de segredo no pacote público antes do deploy)
44
+
45
+ ### Adicionado
46
+
47
+ - **`neetru deploy` bloqueia segredo no pacote PÚBLICO** (decisão do dono 2026-10-03: a credencial `nrt_` do produto é **só de servidor**; produtos hoje carregam a chave no navegador via `NEXT_PUBLIC_NEETRU_API_KEY`/`NEXT_PUBLIC_NEETRU_PUBLIC_KEY`).
48
+ - Novo `src/lib/public-secret-scan.ts` (sem dependências): lê o `.tar.gz` de deploy em **streaming** (zlib + parser ustar/GNU `@LongLink`/pax `path=`) e varre só o que é servido ao navegador — `.next/static/**` (em qualquer profundidade), `public/**`, `out/**`, `dist/**`, `build/**`; na stack `static`, o pacote inteiro; `node_modules/**` nunca; binários (imagem/fonte/mídia/wasm) e arquivos > 25 MB pulados (o grande é avisado).
49
+ - Padrões: `nrt_[A-Za-z0-9_-]{20,}`, `npk_[A-Za-z0-9_-]{20,}`, `-----BEGIN … PRIVATE KEY-----` seguido do material base64 da chave (só o cabeçalho, comum em código de biblioteca, não conta) e o **valor** de envs públicas (`NEXT_PUBLIC_*`, `EXPO_PUBLIC_*`, `VITE_*`, `REACT_APP_*`, `PUBLIC_*`) com `KEY`/`SECRET`/`TOKEN` no nome — lidas de `.env`, `.env.production`, `.env.local`, `.env.production.local` (que **não** viajam no tarball), `--env-file` e `process.env`. Valores públicos por desenho (Stripe `pk_live_`/`pk_test_`, Google/Firebase `AIza…`, reCAPTCHA `6L…`) e valores com menos de 12 caracteres são ignorados.
50
+ - Roda **antes do upload** (fluxo build→upload e `--artifact <arquivo>`; também com `--local-artifact`). Em `target=cloud-run` sem `--image`, varre as pastas públicas do disco antes de buildar a imagem.
51
+ - Achou → **falha FECHADO**: exit 1, `public_secret_in_bundle` (em `--json`, a linha `{ok:false,error,message}`), mensagem PT-BR com arquivo + tipo + como corrigir. **Nunca imprime o segredo**: no máximo os 8 primeiros caracteres (e nunca mais que 1/4 do valor), mascarados. Pacote ilegível → `public_secret_scan_failed` (também fechado).
52
+ - **`--allow-public-secret "<motivo>"`** — override de emergência. Motivo obrigatório (≥ 10 caracteres, validado antes de build/upload; senão `invalid_override_reason`). Segue com aviso alto ("trate a chave como VAZADA") e **registra no deploy**: `publicSecretOverride: { reason, findings: [{ file, pattern }], at }` no corpo do `POST /api/cli/v1/deploy` (só caminho + tipo, nunca o valor) e `publicSecretScan` na saída `--json`.
53
+ - Não varridos (com aviso): `--artifact-url` (pacote remoto) e `--image` (imagem pronta).
54
+
55
+ ### Dependência no Core
56
+
57
+ - O `deployRequestSchema` do Core faz *strip* de chaves desconhecidas: hoje o `publicSecretOverride` chega no corpo cru mas **não é persistido**. Para o registro valer de ponta a ponta, o Core precisa ler `publicSecretOverride` do corpo cru (mesmo padrão do `prodDirectOverride`) e gravar em `deployments/{id}` + auditoria. Até lá o registro fica na saída do CLI.
58
+
7
59
  ## [2.29.0] — 2026-10-01 (status live_unverified, db move decommission no contrato real, servers pgbouncer sync-userlist, TUI do code sob demanda)
8
60
 
9
61
  ### Adicionado
package/README.md CHANGED
@@ -51,7 +51,11 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
51
51
  | `neetru new <slug>` | Criar produto completo (registry + workspace + scaffold) |
52
52
  | `neetru init <name>` | Só scaffold local Next.js + Firebase |
53
53
  | `neetru add <feature>` | Adicionar features ao scaffold (auth, billing, etc) |
54
- | `neetru deploy` | Pipeline de deploy interativo (cloud-run / vm / workspace) |
54
+ | `neetru deploy` | Pipeline de deploy interativo (cloud-run / vm / workspace) — roda `docs check` e só avisa |
55
+ | `neetru docs init [--com-api] [--migrar]` | Mínimo do primeiro dia do `docs/` do produto (idempotente); `--migrar` só acrescenta frontmatter + relatório |
56
+ | `neetru docs new <tipo> "<título>"` | Doc novo a partir do modelo, na pasta e com o nome do protocolo |
57
+ | `neetru docs check [--changed] [--strict]` | Valida os docs do produto offline (códigos FM/TI/NM/LK/PB/LC + PR001–PR004) |
58
+ | `neetru docs sync [--dry-run]` | Publica os docs do produto no Core (só os sem erro; nada é apagado) |
55
59
  | `neetru build [--stack ...] [--target-env dev\|workspace\|prod] [--env-file ...] [--no-cache] [--no-install]` | Empacotar produto em tarball pra deploy |
56
60
  | `neetru smoke [--base-url ...] [--prod ...] [--health-path ...] [--routes csv] [--json]` | Percorrer as páginas do produto **local** antes do deploy (alcance + login + DB) + smoke de prod opcional |
57
61
  | `neetru promote` | Promover dev → staging → prod |
@@ -63,8 +67,8 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
63
67
  | `neetru admin database engines` | Listar engines suportados (`firestore-instance`, `cloud-sql-postgres`, `cloud-sql-mysql`, `vm-postgres-single`, `vm-mysql-single`, `nosql-vm`, …) |
64
68
  | `neetru db init / apply / migrations` | Banco por produto no fluxo dev (registra + schema + migrations) |
65
69
  | `neetru dev` | Banco Docker local + watch de schema (`salvar = aplicado`) |
66
- | `neetru dev up` | **Emulador local completo** — Firebase Emulators + Core, 100% offline |
67
- | `neetru dev exec <script>` | Roda `<script>` com emuladores ativos (CI sem credencial cloud) |
70
+ | `neetru dev up` | Banco do produto em container (Docker Compose) + URL de conexão pro `.env.local` |
71
+ | `neetru dev exec <script>` | Roda `<script>` com os emuladores Firebase do **Core** (exige o repo do Core; CI do Core) |
68
72
  | `neetru cloud-run pause/resume/delete <service>` | Controle Cloud Run |
69
73
  | `neetru products list/create/update` | Registry de produtos |
70
74
  | `neetru workspaces create/list/get/advance` | Instâncias por cliente |
@@ -91,24 +95,44 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
91
95
 
92
96
  40+ subcomandos. Quase todos suportam `--json` pra automação.
93
97
 
94
- ## Dev local — emulador (`neetru dev up`)
98
+ ## Docs do produto (`neetru docs init|new|check|sync`) — 2.31.0
95
99
 
96
- Ambiente de desenvolvimento **100% offline**, estilo Firebase Emulator Suite — sem tocar GCP/staging:
100
+ Padrão: `PROTOCOLO_DOCS_PRODUTOS_SAAS.md` do Core (versão 1). O `docs/` de cada produto segue a mesma taxonomia da wiki do Core (categoria + subcategoria por assunto, `type` à parte) e nomes `MAIÚSCULO_COM_UNDERLINE`.
97
101
 
98
102
  ```bash
99
- # Na raiz do Core (onde tem firebase.json):
100
- neetru dev up
101
- # → sobe Firebase Emulators (Firestore :8080, Auth :9099, Functions :5001)
102
- # → sobe o Core Next.js (:9003) apontando pros emuladores
103
- # → Ctrl-C encerra os dois em cascata
103
+ neetru docs init # README, CHANGELOG, docs/index.md, visão, arquitetura, runbook de deploy
104
+ neetru docs init --com-api # + docs/api/API_V1.md (o produto expõe /api/v1)
105
+ neetru docs init --migrar # repo que já tem docs: frontmatter inferido + relatório em docs/_audit/
106
+ neetru docs new adr "Usar Postgres para vendas"
107
+ neetru docs new relatorio "Auditoria de UX da tela de caixa"
108
+ neetru docs check --changed # catraca: só o que mudou contra a branch principal
109
+ neetru docs sync --dry-run # o que subiria para o Core
110
+ ```
111
+
112
+ - `neetru init` e `neetru new` já rodam `docs init` (`--sem-docs` pula).
113
+ - `neetru deploy` roda `docs check` e **só avisa** — nunca bloqueia. Com `"docs": { "publishOnDeploy": true }` no `neetru.config.json`, o deploy de **produção** publica os docs no fim; falha na publicação não derruba o deploy. `--sem-docs` pula as duas coisas.
114
+ - A taxonomia vem embutida na CLI (funciona offline); quando o Core expõe `GET /api/cli/v1/docs/taxonomy`, o `check` usa a do Core e avisa se a embutida estiver velha.
104
115
 
105
- # Seed portátil (compartilhe o estado entre devs via git):
106
- neetru dev up --import .neetru-dev/seed --export-on-exit .neetru-dev/seed
116
+ ## Dev local (`neetru dev`, `neetru dev up`)
107
117
 
108
- # Só os emuladores (sem o Core):
109
- neetru dev up --skip-core
118
+ O que roda na máquina do dev de produto hoje:
119
+
120
+ | Peça | Como |
121
+ |---|---|
122
+ | SDK sem conta Neetru | `NEETRU_ENV=dev` → mocks em memória (login fixture, uso zerado, banco em memória). `neetru mocks reset` limpa as fixtures. |
123
+ | Banco real do produto | `neetru dev` (container + reaplica `db/schema.ts` a cada save) **ou** `neetru dev up` (só sobe o container e imprime a URL de conexão). Engine vem do projeto. |
124
+ | App | `npm run dev`, fora de container. |
125
+ | Agente / Core locais | **Ainda não existem** para produto. Deploy e migração pelo agente só acontecem na VM. |
110
126
 
111
- # CI / teste de integração sem credencial cloud (propaga o exit code do script):
127
+ ```bash
128
+ # Na raiz do produto (com database.engine no neetru.config.json):
129
+ neetru dev up
130
+ # → gera neetru-compose.dev.yml e sobe o banco (docker compose up -d --wait)
131
+ # → imprime a conexão, ex. postgresql://postgres:neetru_dev@localhost:5432/app
132
+ docker compose -f neetru-compose.dev.yml down # para
133
+
134
+ # CI do Core (na raiz do Core, onde tem firebase.json): roda o script com os
135
+ # emuladores Firebase e propaga o exit code.
112
136
  neetru dev exec "npm run test:integration"
113
137
  ```
114
138
 
@@ -121,7 +145,7 @@ neetru dev use --product meu-produto --sync
121
145
  # Protótipo local ainda não cadastrado no Core:
122
146
  neetru dev use --prototype meu-rascunho
123
147
 
124
- # Ver o contexto efetivo que dev up/dev exec vão usar:
148
+ # Ver o contexto dev-local efetivo:
125
149
  neetru dev status
126
150
 
127
151
  # Sincronizar bancos dev-local do Core para .neetru/db.json:
@@ -129,7 +153,7 @@ neetru dev sync --write-db
129
153
  ```
130
154
 
131
155
  > `neetru dev` (sem subcomando) mantém o modo legado: banco Docker local + watch de schema.
132
- > Flags: `--only firestore,auth,functions`, `--core-port 9003`, `--core-path <dir>`.
156
+ > Flags: `dev up --core-port <n>` (porta do app nas instruções, default 3000); `dev exec --only firestore,auth,functions --core-path <dir>`.
133
157
  > O arquivo `.neetru/dev.json` guarda o contexto local: produto Core ou protótipo, Core path e emuladores padrão.
134
158
 
135
159
  ## Build — env do bundle (`NEXT_PUBLIC_NEETRU_ENV`)
@@ -145,6 +169,26 @@ Isso existe porque o Next.js carrega `.env.local` (arquivo do disco do dev, nunc
145
169
 
146
170
  Se o produto envolver `next.config.mjs` com `withNeetruBuildGuard` (`@neetru/sdk/build-guard`, `@neetru/sdk >= 3.1.15`), `neetru build` confere pós-build que o env realmente compilado bate com o esperado — e **recusa empacotar o tarball** se divergir, em vez de deployar um bundle quebrado silenciosamente.
147
171
 
172
+ ## Segredo no pacote público bloqueia o deploy (2.30.0)
173
+
174
+ A credencial `nrt_` do produto é **só de servidor**. Antes de enviar o pacote, `neetru deploy` varre o que é servido ao navegador — `.next/static/**`, `public/**`, `out/**`, `dist/**`, `build/**` (na stack `static`, o pacote inteiro; `node_modules` nunca) — procurando:
175
+
176
+ - `nrt_…` e `npk_…` (credenciais Neetru);
177
+ - `-----BEGIN … PRIVATE KEY-----` com o material da chave (só o cabeçalho não conta);
178
+ - o **valor** de envs públicas (`NEXT_PUBLIC_*`, `EXPO_PUBLIC_*`, `VITE_*`, `REACT_APP_*`, `PUBLIC_*`) cujo nome contém `KEY`/`SECRET`/`TOKEN` (lidas de `.env`, `.env.production`, `.env.local`, `.env.production.local`, `--env-file` e do ambiente). Valores públicos por desenho do provedor (Stripe `pk_live_`/`pk_test_`, chave web Google/Firebase `AIza…`, reCAPTCHA `6L…`) são ignorados.
179
+
180
+ Achou → **o deploy falha** (exit 1, `public_secret_in_bundle`) mostrando arquivo e tipo — **nunca o segredo** (no máximo 8 caracteres iniciais, mascarados). Pacote ilegível também falha (`public_secret_scan_failed`).
181
+
182
+ Correção: tire a chave do código do navegador/app; o navegador/app chama a API do seu produto com o token do usuário e o backend usa `@neetru/sdk` com `NEETRU_API_KEY` (ver `verifyAccessToken` no SDK ≥ 3.8.0).
183
+
184
+ Emergência (fica registrado no deploy — só caminho e tipo, nunca o valor):
185
+
186
+ ```bash
187
+ neetru deploy --allow-public-secret "chave antiga já revogada, ticket #123"
188
+ ```
189
+
190
+ Limites: `--artifact-url` (pacote remoto) e `--image` (imagem pronta, cloud-run) não passam pelo CLI e **não são varridos** — o deploy avisa. Em `target=cloud-run` sem `--image`, a varredura é nas pastas públicas do disco antes do build da imagem.
191
+
148
192
  ## Smoke local antes do deploy (`neetru smoke`)
149
193
 
150
194
  Roda contra o servidor **dev local** (`next dev` / `neetru dev up`) e percorre as páginas do produto **antes** de `neetru build` + `neetru deploy` — pega página quebrada, login que não renderiza ou banco fora do ar sem subir nada em produção.
@@ -1,3 +1,4 @@
1
+ import { type PublicSecretOverrideRecord } from '../lib/public-secret-scan.js';
1
2
  /**
2
3
  * bug_a5786e03 (2026-05-31): o Core/agente só conhecem os stacks canônicos
3
4
  * `node|docker|php-apache|static` (PRODUCT_STACKS). `nextjs` é um alias de
@@ -88,7 +89,51 @@ export interface DeployOptions {
88
89
  * pra prod (auditoria cli/v1 2026-09-29, M7). Ignorado em dev/staging.
89
90
  */
90
91
  mfaToken?: string;
92
+ /**
93
+ * 2.30.0 — `--allow-public-secret "<motivo>"`: override de EMERGÊNCIA da
94
+ * varredura de segredo no pacote público. Motivo obrigatório (≥10 chars);
95
+ * fica registrado no deploy (`publicSecretOverride` no corpo enviado ao Core
96
+ * e na saída --json) — só caminho + tipo de padrão, nunca o valor.
97
+ */
98
+ allowPublicSecret?: string;
99
+ /**
100
+ * Protocolo de docs de produto v1: pula a checagem dos docs (que só avisa)
101
+ * e a publicação automática no fim do deploy de produção (`docs.publishOnDeploy`).
102
+ */
103
+ semDocs?: boolean;
104
+ }
105
+ /** Resultado da varredura de segredo, anexado ao deploy. */
106
+ export interface PublicSecretGateResult {
107
+ /** `false` quando não deu pra varrer (artefato remoto / imagem pronta). */
108
+ scanned: boolean;
109
+ scannedFiles?: number;
110
+ reason?: string;
111
+ override?: PublicSecretOverrideRecord;
91
112
  }
113
+ /**
114
+ * Valida o motivo do `--allow-public-secret` ANTES de qualquer build/upload.
115
+ * `undefined` = flag não passada.
116
+ */
117
+ export declare function validateAllowPublicSecretReason(raw: string | undefined, json?: boolean): string | undefined;
118
+ /**
119
+ * 2.30.0 — gate de segredo no pacote PÚBLICO (decisão do dono 2026-10-03: a
120
+ * credencial nrt_ é SÓ DE SERVIDOR). Roda ANTES do upload. Achou → falha
121
+ * FECHADO (exit 1, `public_secret_in_bundle`), a não ser que haja override com
122
+ * motivo — aí segue com aviso alto e devolve o registro pro corpo do deploy.
123
+ */
124
+ export declare function runPublicSecretGate(source: {
125
+ kind: 'tar';
126
+ path: string;
127
+ } | {
128
+ kind: 'dir';
129
+ root: string;
130
+ }, ctx: {
131
+ cwd: string;
132
+ envFile?: string;
133
+ stack?: string;
134
+ allowReason?: string;
135
+ json?: boolean;
136
+ }): Promise<PublicSecretGateResult>;
92
137
  /** `true` pra `prod`/`production` (o Core normaliza `production`→`prod`). */
93
138
  export declare function isProdEnv(envName: string | undefined): boolean;
94
139
  /**
@@ -33,6 +33,7 @@ import { formatDeployConsole, isDeployUnverified, deployStatusLabel } from './de
33
33
  import { readDbJson, schemaFingerprint } from '../lib/db-local/index.js';
34
34
  // bug_863ae5: pre-flight IAM check.
35
35
  import { assertIamReady } from '../lib/iam-preflight.js';
36
+ import { ALLOW_PUBLIC_SECRET_FLAG, MIN_OVERRIDE_REASON_LENGTH, buildOverrideRecord, collectPublicEnvSecrets, formatFindingsMessage, scanDirectoryForPublicSecrets, scanTarballForPublicSecrets, } from '../lib/public-secret-scan.js';
36
37
  import { getActiveTokenSync } from '../lib/token-resolver.js';
37
38
  import { safeExit } from '../utils/safe-exit.js';
38
39
  /**
@@ -86,6 +87,57 @@ export function resolveDeployVersion(manifestVersion, explicitVersion, json) {
86
87
  }
87
88
  return version;
88
89
  }
90
+ /**
91
+ * Valida o motivo do `--allow-public-secret` ANTES de qualquer build/upload.
92
+ * `undefined` = flag não passada.
93
+ */
94
+ export function validateAllowPublicSecretReason(raw, json) {
95
+ if (raw === undefined)
96
+ return undefined;
97
+ const reason = String(raw).trim();
98
+ if (reason.length < MIN_OVERRIDE_REASON_LENGTH) {
99
+ deployFail(json, 'invalid_override_reason', `${ALLOW_PUBLIC_SECRET_FLAG} exige um motivo de verdade (mínimo ${MIN_OVERRIDE_REASON_LENGTH} caracteres) — ` +
100
+ 'ele fica registrado no deploy. Ex.: --allow-public-secret "rotação da chave em andamento, ticket #123".');
101
+ }
102
+ return reason;
103
+ }
104
+ /**
105
+ * 2.30.0 — gate de segredo no pacote PÚBLICO (decisão do dono 2026-10-03: a
106
+ * credencial nrt_ é SÓ DE SERVIDOR). Roda ANTES do upload. Achou → falha
107
+ * FECHADO (exit 1, `public_secret_in_bundle`), a não ser que haja override com
108
+ * motivo — aí segue com aviso alto e devolve o registro pro corpo do deploy.
109
+ */
110
+ export async function runPublicSecretGate(source, ctx) {
111
+ const lg = scopedLog(ctx.json);
112
+ const envSecrets = await collectPublicEnvSecrets(ctx.cwd, ctx.envFile);
113
+ let result;
114
+ try {
115
+ result =
116
+ source.kind === 'tar'
117
+ ? await scanTarballForPublicSecrets(source.path, { stack: ctx.stack, envSecrets })
118
+ : await scanDirectoryForPublicSecrets(source.root, { stack: ctx.stack, envSecrets });
119
+ }
120
+ catch (err) {
121
+ // Não conseguir LER o pacote é falha fechada também (não dá pra afirmar que está limpo).
122
+ deployFail(ctx.json, 'public_secret_scan_failed', `Não consegui varrer o pacote em busca de segredos: ${err.message}`);
123
+ }
124
+ if (result.skippedLarge.length > 0) {
125
+ lg.warn(`Varredura de segredo: ${result.skippedLarge.length} arquivo(s) público(s) grande(s) demais não foram varridos ` +
126
+ `(ex.: ${result.skippedLarge[0]}).`);
127
+ }
128
+ if (result.findings.length === 0) {
129
+ lg.dim(` Varredura de segredo no pacote público: ok (${result.scannedFiles} arquivo(s)).`);
130
+ return { scanned: true, scannedFiles: result.scannedFiles };
131
+ }
132
+ if (!ctx.allowReason) {
133
+ deployFail(ctx.json, 'public_secret_in_bundle', formatFindingsMessage(result.findings));
134
+ }
135
+ const override = buildOverrideRecord(ctx.allowReason, result.findings);
136
+ lg.warn(formatFindingsMessage(result.findings).split('\n')[0].replace('Deploy BLOQUEADO.', 'Deploy LIBERADO por override.'));
137
+ lg.warn(`OVERRIDE ${ALLOW_PUBLIC_SECRET_FLAG} — motivo registrado no deploy: "${ctx.allowReason}". ` +
138
+ 'Trate a chave exposta como VAZADA: rotacione-a assim que possível.');
139
+ return { scanned: true, scannedFiles: result.scannedFiles, override };
140
+ }
89
141
  /** `true` pra `prod`/`production` (o Core normaliza `production`→`prod`). */
90
142
  export function isProdEnv(envName) {
91
143
  const e = (envName ?? '').trim().toLowerCase();
@@ -566,7 +618,15 @@ async function uploadArtifactToGcs(tarballAbs, productSlug, envName, sizeBytes,
566
618
  async function resolveArtifact(opts, productSlug, envName, cwd) {
567
619
  // Caso 1: caller já fez upload e passou URL+sha pronta.
568
620
  if (opts.artifactUrl && opts.artifactSha256) {
569
- return { artifactUrl: opts.artifactUrl, artifactSha256: opts.artifactSha256 };
621
+ // 2.30.0: artefato remoto não passa pelo CLI — não dá pra varrer. Avisa (o
622
+ // gate vale pro fluxo padrão build→upload e pro --artifact local).
623
+ scopedLog(opts.json).warn('Varredura de segredo NÃO feita: --artifact-url aponta um pacote remoto. ' +
624
+ 'Prefira `neetru deploy` com build local ou --artifact <arquivo> (que são varridos).');
625
+ return {
626
+ artifactUrl: opts.artifactUrl,
627
+ artifactSha256: opts.artifactSha256,
628
+ publicSecretScan: { scanned: false, reason: 'remote_artifact' },
629
+ };
570
630
  }
571
631
  // Decide se vamos fazer upload pra GCS ou manter file://.
572
632
  const keepLocal = shouldKeepLocalArtifact(opts);
@@ -584,12 +644,15 @@ async function resolveArtifact(opts, productSlug, envName, cwd) {
584
644
  hash.update(chunk);
585
645
  const stat = await fsp.stat(abs);
586
646
  const sha256 = hash.digest('hex');
647
+ // 2.30.0: varredura de segredo ANTES de qualquer envio.
648
+ const publicSecretScan = await runPublicSecretGate({ kind: 'tar', path: abs }, { cwd, envFile: opts.envFile, stack: opts.stack, allowReason: opts.allowPublicSecret, json: opts.json });
587
649
  if (keepLocal) {
588
650
  lg.warn('Modo --local-artifact: mandando file:// pro Core. Só funciona se o agent compartilha FS com este CLI.');
589
651
  return {
590
652
  artifactUrl: `file://${abs.replace(/\\/g, '/')}`,
591
653
  artifactSha256: sha256,
592
654
  artifactSizeBytes: stat.size,
655
+ publicSecretScan,
593
656
  };
594
657
  }
595
658
  const uploaded = await uploadArtifactToGcs(abs, productSlug, envName, stat.size, opts.json);
@@ -598,6 +661,7 @@ async function resolveArtifact(opts, productSlug, envName, cwd) {
598
661
  artifactSha256: sha256,
599
662
  artifactSizeBytes: stat.size,
600
663
  deployId: uploaded.deployId,
664
+ publicSecretScan,
601
665
  };
602
666
  }
603
667
  // Caso 3: procura manifest existente, senão chama runBuild.
@@ -638,6 +702,14 @@ async function resolveArtifact(opts, productSlug, envName, cwd) {
638
702
  });
639
703
  }
640
704
  const tarAbs = path.resolve(cwd, manifest.tarball);
705
+ // 2.30.0: varredura de segredo no pacote que VAI ser enviado, antes do upload.
706
+ const publicSecretScan = await runPublicSecretGate({ kind: 'tar', path: tarAbs }, {
707
+ cwd,
708
+ envFile: opts.envFile,
709
+ stack: manifest.stack ?? opts.stack,
710
+ allowReason: opts.allowPublicSecret,
711
+ json: opts.json,
712
+ });
641
713
  if (keepLocal) {
642
714
  lg.warn('Modo --local-artifact: mandando file:// pro Core. Só funciona se o agent compartilha FS com este CLI.');
643
715
  return {
@@ -645,6 +717,7 @@ async function resolveArtifact(opts, productSlug, envName, cwd) {
645
717
  artifactSha256: manifest.sha256,
646
718
  artifactSizeBytes: manifest.sizeBytes,
647
719
  manifest,
720
+ publicSecretScan,
648
721
  };
649
722
  }
650
723
  const uploaded = await uploadArtifactToGcs(tarAbs, productSlug, envName, manifest.sizeBytes, opts.json);
@@ -654,6 +727,7 @@ async function resolveArtifact(opts, productSlug, envName, cwd) {
654
727
  artifactSizeBytes: manifest.sizeBytes,
655
728
  manifest,
656
729
  deployId: uploaded.deployId,
730
+ publicSecretScan,
657
731
  };
658
732
  }
659
733
  async function pollDeploymentStatus(deploymentId, json) {
@@ -1033,9 +1107,11 @@ async function runWorkspaceDeploy(opts, productSlug, envName, cwd, nonInteractiv
1033
1107
  }
1034
1108
  if (resp.status === 'live') {
1035
1109
  spinner.succeed(`Deploy ${chalk.bold('live')} · ${resp.deploymentId}`);
1110
+ const docsSync = await postDeployDocs(opts, cwd, envName);
1036
1111
  if (opts.json) {
1037
1112
  console.log(JSON.stringify({
1038
1113
  ok: true,
1114
+ ...(docsSync ? { docs: { sync: docsSync } } : {}),
1039
1115
  status: 'live',
1040
1116
  target: 'workspace',
1041
1117
  deploymentId: resp.deploymentId,
@@ -1053,9 +1129,11 @@ async function runWorkspaceDeploy(opts, productSlug, envName, cwd, nonInteractiv
1053
1129
  // aplicou o deploy mas ainda não confirmou saúde. Sucesso COM AVISO — não
1054
1130
  // tratamos como falha nem fingimos "Live" confirmado.
1055
1131
  spinner.warn(`Deploy aplicado — ${deployStatusLabel(resp.status)} · ${resp.deploymentId}`);
1132
+ const docsSync = await postDeployDocs(opts, cwd, envName);
1056
1133
  if (opts.json) {
1057
1134
  console.log(JSON.stringify({
1058
1135
  ok: true,
1136
+ ...(docsSync ? { docs: { sync: docsSync } } : {}),
1059
1137
  status: resp.status,
1060
1138
  verified: false,
1061
1139
  target: 'workspace',
@@ -1387,6 +1465,16 @@ async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
1387
1465
  if (deployToken) {
1388
1466
  await assertIamReady(deployToken, 'deploy.cloud-run', opts.json);
1389
1467
  }
1468
+ // 2.30.0: varredura de segredo nas pastas públicas ANTES de buildar a imagem.
1469
+ // Com --image (imagem pronta) não há o que varrer aqui — avisa.
1470
+ let publicSecretScan;
1471
+ if (opts.image) {
1472
+ scopedLog(opts.json).warn('Varredura de segredo NÃO feita: --image aponta uma imagem já buildada (o CLI não vê o conteúdo).');
1473
+ publicSecretScan = { scanned: false, reason: 'prebuilt_image' };
1474
+ }
1475
+ else {
1476
+ publicSecretScan = await runPublicSecretGate({ kind: 'dir', root: cwd }, { cwd, envFile: opts.envFile, stack: opts.stack, allowReason: opts.allowPublicSecret, json: opts.json });
1477
+ }
1390
1478
  const imageUrl = await resolveCloudRunImage(opts, productSlug, envName, cwd);
1391
1479
  let envVars;
1392
1480
  if (opts.envFile) {
@@ -1421,6 +1509,7 @@ async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
1421
1509
  imageUrl,
1422
1510
  ...(opts.port ? { port: opts.port } : {}),
1423
1511
  ...(envVars && Object.keys(envVars).length > 0 ? { envVars } : {}),
1512
+ ...(publicSecretScan.override ? { publicSecretOverride: publicSecretScan.override } : {}),
1424
1513
  },
1425
1514
  });
1426
1515
  }
@@ -1430,9 +1519,11 @@ async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
1430
1519
  }
1431
1520
  if (resp.status === 'live') {
1432
1521
  spinner.succeed(`Deploy ${chalk.bold('live')} · ${resp.deploymentId}`);
1522
+ const docsSync = await postDeployDocs(opts, cwd, envName);
1433
1523
  if (opts.json) {
1434
1524
  console.log(JSON.stringify({
1435
1525
  ok: true,
1526
+ ...(docsSync ? { docs: { sync: docsSync } } : {}),
1436
1527
  status: 'live',
1437
1528
  target: 'cloud-run',
1438
1529
  deploymentId: resp.deploymentId,
@@ -1454,9 +1545,11 @@ async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
1454
1545
  // aplicou o deploy mas ainda não confirmou saúde. Sucesso COM AVISO — não
1455
1546
  // tratamos como falha nem fingimos "Live" confirmado.
1456
1547
  spinner.warn(`Deploy aplicado — ${deployStatusLabel(resp.status)} · ${resp.deploymentId}`);
1548
+ const docsSync = await postDeployDocs(opts, cwd, envName);
1457
1549
  if (opts.json) {
1458
1550
  console.log(JSON.stringify({
1459
1551
  ok: true,
1552
+ ...(docsSync ? { docs: { sync: docsSync } } : {}),
1460
1553
  status: resp.status,
1461
1554
  verified: false,
1462
1555
  target: 'cloud-run',
@@ -1562,17 +1655,48 @@ async function checkSchemaGate(cwd, opts) {
1562
1655
  }
1563
1656
  return true;
1564
1657
  }
1658
+ /**
1659
+ * Protocolo de docs v1 (decisão do dono 2026-10-03): depois de um deploy bem
1660
+ * sucedido, publica os docs no Core SÓ se for produção e `docs.publishOnDeploy`
1661
+ * estiver ligado. Nunca lança — falha do sync não derruba o deploy.
1662
+ */
1663
+ async function postDeployDocs(opts, cwd, envName) {
1664
+ if (opts.semDocs)
1665
+ return undefined;
1666
+ try {
1667
+ const { deployDocsPostSync } = await import('./docs-produto.js');
1668
+ const r = await deployDocsPostSync(cwd, envName, opts.json);
1669
+ return r.attempted ? r : undefined;
1670
+ }
1671
+ catch {
1672
+ return undefined;
1673
+ }
1674
+ }
1565
1675
  export async function runDeploy(opts) {
1566
1676
  if (!opts.json)
1567
1677
  log.banner();
1568
1678
  const cwd = process.cwd();
1569
1679
  const nonInteractive = ensureNonInteractive(opts);
1680
+ // 2.30.0: motivo do override validado ANTES de build/upload.
1681
+ opts = { ...opts, allowPublicSecret: validateAllowPublicSecretReason(opts.allowPublicSecret, opts.json) };
1570
1682
  const productSlug = await pickProduct(opts, nonInteractive, cwd);
1571
1683
  // GAP-M1-5 — gate de schema: detecta drift antes de prosseguir.
1572
1684
  const canDeploy = await checkSchemaGate(cwd, opts);
1573
1685
  if (!canDeploy) {
1574
1686
  safeExit(1);
1575
1687
  }
1688
+ // Protocolo de docs v1 (decisão do dono 2026-10-03): `docs check` antes do
1689
+ // deploy SÓ AVISA — nunca bloqueia, em nenhum ambiente.
1690
+ let docsCheck;
1691
+ if (!opts.semDocs) {
1692
+ try {
1693
+ const { deployDocsPrecheck } = await import('./docs-produto.js');
1694
+ docsCheck = await deployDocsPrecheck(cwd, (opts.env ?? 'dev').toLowerCase(), opts.json);
1695
+ }
1696
+ catch {
1697
+ docsCheck = undefined;
1698
+ }
1699
+ }
1576
1700
  const target = await pickTarget(opts, nonInteractive);
1577
1701
  // Caminho workspace (Fase 1 do Fluxo de Ambientes) — sem server/tar/upload.
1578
1702
  if (target === 'workspace') {
@@ -1671,6 +1795,10 @@ export async function runDeploy(opts) {
1671
1795
  body.domain = domain;
1672
1796
  if (envVars && Object.keys(envVars).length > 0)
1673
1797
  body.envVars = envVars;
1798
+ // 2.30.0: override da varredura de segredo vai NO deploy (só caminho + tipo
1799
+ // de padrão, nunca o valor). O Core lê do corpo cru (como prodDirectOverride).
1800
+ if (artifact.publicSecretScan.override)
1801
+ body.publicSecretOverride = artifact.publicSecretScan.override;
1674
1802
  const mfaHeaders = await resolveDeployStepUpHeaders(opts, envName, nonInteractive);
1675
1803
  let deploy;
1676
1804
  const dispatchSpinner = ora({ text: 'Enviando comando ao Core…', color: 'blue' }).start();
@@ -1707,6 +1835,9 @@ export async function runDeploy(opts) {
1707
1835
  }
1708
1836
  safeExit(1);
1709
1837
  }
1838
+ // Docs no Core (opt-in `docs.publishOnDeploy`, só produção). O pipeline já
1839
+ // aplicou a versão; falha aqui só vira aviso.
1840
+ const docsSync = await postDeployDocs(opts, cwd, envName);
1710
1841
  // ── Fase 0 #1: probe de saúde INDEPENDENTE antes de declarar "Live" ────────
1711
1842
  // O pipeline pode reportar `success` com o site quebrado (outage PDV
1712
1843
  // 2026-09-07). Só declaramos "Live" se um GET real ao domínio confirmar 200 +
@@ -1724,6 +1855,8 @@ export async function runDeploy(opts) {
1724
1855
  url: final.domain ? `https://${final.domain}` : null,
1725
1856
  artifactUrl: artifact.artifactUrl,
1726
1857
  steps: final.steps,
1858
+ publicSecretScan: artifact.publicSecretScan,
1859
+ ...(docsCheck || docsSync ? { docs: { check: docsCheck ?? null, sync: docsSync ?? null } } : {}),
1727
1860
  };
1728
1861
  if (opts.skipHealthProbe) {
1729
1862
  if (opts.json) {