@neetru/cli 2.29.0 → 2.32.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 (259) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +63 -17
  3. package/dist/cli-kit/choices.d.ts +33 -0
  4. package/dist/cli-kit/choices.js +32 -0
  5. package/dist/cli-kit/command-context.d.ts +98 -0
  6. package/dist/cli-kit/command-context.js +108 -0
  7. package/dist/cli-kit/errors.d.ts +79 -0
  8. package/dist/cli-kit/errors.js +94 -0
  9. package/dist/cli-kit/index.d.ts +8 -0
  10. package/dist/cli-kit/index.js +8 -0
  11. package/dist/cli-kit/suggest.d.ts +74 -0
  12. package/dist/cli-kit/suggest.js +207 -0
  13. package/dist/commands/admin/oidc-login.js +1 -1
  14. package/dist/commands/admin/sdk-credential.js +1 -1
  15. package/dist/commands/autocomplete.d.ts +1 -1
  16. package/dist/commands/autocomplete.js +1 -0
  17. package/dist/commands/bug.d.ts +59 -0
  18. package/dist/commands/bug.js +114 -2
  19. package/dist/commands/build.d.ts +24 -1
  20. package/dist/commands/build.js +195 -73
  21. package/dist/commands/chat.d.ts +94 -1
  22. package/dist/commands/chat.js +258 -26
  23. package/dist/commands/customers.js +1 -1
  24. package/dist/commands/db-move.js +3 -3
  25. package/dist/commands/db.js +17 -24
  26. package/dist/commands/deploy.d.ts +136 -2
  27. package/dist/commands/deploy.js +547 -69
  28. package/dist/commands/deployments.js +11 -6
  29. package/dist/commands/dev-context.js +15 -5
  30. package/dist/commands/dev-up.d.ts +7 -5
  31. package/dist/commands/dev-up.js +24 -23
  32. package/dist/commands/docs-produto.d.ts +90 -0
  33. package/dist/commands/docs-produto.js +384 -0
  34. package/dist/commands/doctor.js +36 -1
  35. package/dist/commands/domains.js +85 -4
  36. package/dist/commands/env.d.ts +9 -3
  37. package/dist/commands/env.js +23 -3
  38. package/dist/commands/infra-read.js +2 -2
  39. package/dist/commands/init.d.ts +2 -0
  40. package/dist/commands/init.js +14 -1
  41. package/dist/commands/login.js +35 -8
  42. package/dist/commands/logs.js +12 -4
  43. package/dist/commands/mobile.js +33 -9
  44. package/dist/commands/new.d.ts +31 -1
  45. package/dist/commands/new.js +94 -4
  46. package/dist/commands/open.js +16 -6
  47. package/dist/commands/ops.js +1 -1
  48. package/dist/commands/products-db.d.ts +1 -0
  49. package/dist/commands/products-db.js +41 -8
  50. package/dist/commands/products.d.ts +17 -3
  51. package/dist/commands/products.js +84 -16
  52. package/dist/commands/promote.js +30 -5
  53. package/dist/commands/secrets.d.ts +91 -0
  54. package/dist/commands/secrets.js +471 -0
  55. package/dist/commands/servers.js +6 -4
  56. package/dist/commands/status.js +2 -2
  57. package/dist/commands/support.js +5 -3
  58. package/dist/commands/tenants.js +28 -16
  59. package/dist/commands/upgrade.d.ts +9 -0
  60. package/dist/commands/upgrade.js +50 -1
  61. package/dist/commands/vm-memory.js +1 -0
  62. package/dist/commands/webhooks.d.ts +2 -0
  63. package/dist/commands/webhooks.js +9 -3
  64. package/dist/commands/workspaces.js +42 -11
  65. package/dist/index.js +142 -20
  66. package/dist/lib/api-client.d.ts +22 -1
  67. package/dist/lib/api-client.js +79 -8
  68. package/dist/lib/api-error.d.ts +14 -0
  69. package/dist/lib/api-error.js +107 -46
  70. package/dist/lib/build-targets.d.ts +127 -0
  71. package/dist/lib/build-targets.js +348 -0
  72. package/dist/lib/cli-error.d.ts +13 -0
  73. package/dist/lib/cli-error.js +24 -0
  74. package/dist/lib/cli-write.d.ts +43 -9
  75. package/dist/lib/cli-write.js +154 -45
  76. package/dist/lib/compose-generator.d.ts +13 -9
  77. package/dist/lib/compose-generator.js +28 -60
  78. package/dist/lib/config-schema.d.ts +36 -2
  79. package/dist/lib/config-schema.js +11 -0
  80. package/dist/lib/db-local/db-json.d.ts +1 -1
  81. package/dist/lib/db-local/db-json.js +13 -5
  82. package/dist/lib/db-local/env.d.ts +15 -3
  83. package/dist/lib/db-local/env.js +37 -10
  84. package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
  85. package/dist/lib/db-pipeline/build-deps.js +23 -3
  86. package/dist/lib/db-pipeline/types.d.ts +6 -2
  87. package/dist/lib/db-pipeline/types.js +0 -18
  88. package/dist/lib/deploy-target-contract.d.ts +152 -0
  89. package/dist/lib/deploy-target-contract.js +411 -0
  90. package/dist/lib/env-file-do-build.d.ts +19 -0
  91. package/dist/lib/env-file-do-build.js +44 -0
  92. package/dist/lib/envs.d.ts +108 -0
  93. package/dist/lib/envs.js +127 -0
  94. package/dist/lib/fs-seguro.d.ts +63 -0
  95. package/dist/lib/fs-seguro.js +108 -0
  96. package/dist/lib/mobile-local/mobile-json.js +10 -4
  97. package/dist/lib/pickers.d.ts +44 -7
  98. package/dist/lib/pickers.js +60 -4
  99. package/dist/lib/product-docs/check.d.ts +62 -0
  100. package/dist/lib/product-docs/check.js +489 -0
  101. package/dist/lib/product-docs/config.d.ts +66 -0
  102. package/dist/lib/product-docs/config.js +234 -0
  103. package/dist/lib/product-docs/dates.d.ts +17 -0
  104. package/dist/lib/product-docs/dates.js +41 -0
  105. package/dist/lib/product-docs/frontmatter.d.ts +43 -0
  106. package/dist/lib/product-docs/frontmatter.js +302 -0
  107. package/dist/lib/product-docs/git.d.ts +16 -0
  108. package/dist/lib/product-docs/git.js +92 -0
  109. package/dist/lib/product-docs/markdown.d.ts +27 -0
  110. package/dist/lib/product-docs/markdown.js +98 -0
  111. package/dist/lib/product-docs/ops.d.ts +133 -0
  112. package/dist/lib/product-docs/ops.js +975 -0
  113. package/dist/lib/product-docs/structure.d.ts +61 -0
  114. package/dist/lib/product-docs/structure.js +239 -0
  115. package/dist/lib/product-docs/sync.d.ts +107 -0
  116. package/dist/lib/product-docs/sync.js +322 -0
  117. package/dist/lib/product-docs/taxonomy.d.ts +46 -0
  118. package/dist/lib/product-docs/taxonomy.js +163 -0
  119. package/dist/lib/product-docs/templates.d.ts +53 -0
  120. package/dist/lib/product-docs/templates.js +245 -0
  121. package/dist/lib/public-secret-scan.d.ts +117 -0
  122. package/dist/lib/public-secret-scan.js +771 -0
  123. package/dist/lib/render.d.ts +16 -0
  124. package/dist/lib/render.js +34 -4
  125. package/dist/lib/segredo-produto.d.ts +124 -0
  126. package/dist/lib/segredo-produto.js +336 -0
  127. package/dist/lib/texto-utf8.d.ts +96 -0
  128. package/dist/lib/texto-utf8.js +196 -0
  129. package/package.json +2 -1
  130. package/dist/cli-kit/format.js.map +0 -1
  131. package/dist/cli-kit/glyphs.js.map +0 -1
  132. package/dist/cli-kit/index.js.map +0 -1
  133. package/dist/cli-kit/palette.js.map +0 -1
  134. package/dist/commands/add.js.map +0 -1
  135. package/dist/commands/admin/iam-check.js.map +0 -1
  136. package/dist/commands/admin/oidc-login.js.map +0 -1
  137. package/dist/commands/admin/sdk-credential.js.map +0 -1
  138. package/dist/commands/agent-release.js.map +0 -1
  139. package/dist/commands/agent-write.js.map +0 -1
  140. package/dist/commands/ai-models.js.map +0 -1
  141. package/dist/commands/ai.js.map +0 -1
  142. package/dist/commands/api-catalog.js.map +0 -1
  143. package/dist/commands/archive.js.map +0 -1
  144. package/dist/commands/artifact-registry.js.map +0 -1
  145. package/dist/commands/audit.js.map +0 -1
  146. package/dist/commands/auth.js.map +0 -1
  147. package/dist/commands/autocomplete.js.map +0 -1
  148. package/dist/commands/billing.js.map +0 -1
  149. package/dist/commands/bootstrap.js.map +0 -1
  150. package/dist/commands/bug.js.map +0 -1
  151. package/dist/commands/build.js.map +0 -1
  152. package/dist/commands/chat-menu.js.map +0 -1
  153. package/dist/commands/chat.js.map +0 -1
  154. package/dist/commands/cloud-run.js.map +0 -1
  155. package/dist/commands/code.js.map +0 -1
  156. package/dist/commands/config.js.map +0 -1
  157. package/dist/commands/customers.js.map +0 -1
  158. package/dist/commands/db-move.js.map +0 -1
  159. package/dist/commands/db.js.map +0 -1
  160. package/dist/commands/deploy.js.map +0 -1
  161. package/dist/commands/deployments.js.map +0 -1
  162. package/dist/commands/dev-context.js.map +0 -1
  163. package/dist/commands/dev-up.js.map +0 -1
  164. package/dist/commands/dev.js.map +0 -1
  165. package/dist/commands/docs.js.map +0 -1
  166. package/dist/commands/doctor.js.map +0 -1
  167. package/dist/commands/domains.js.map +0 -1
  168. package/dist/commands/dr.js.map +0 -1
  169. package/dist/commands/env.js.map +0 -1
  170. package/dist/commands/hosting-write.js.map +0 -1
  171. package/dist/commands/infra-read.js.map +0 -1
  172. package/dist/commands/init.js.map +0 -1
  173. package/dist/commands/login.js.map +0 -1
  174. package/dist/commands/logout.js.map +0 -1
  175. package/dist/commands/logs.js.map +0 -1
  176. package/dist/commands/mobile.js.map +0 -1
  177. package/dist/commands/mocks.js.map +0 -1
  178. package/dist/commands/new.js.map +0 -1
  179. package/dist/commands/open.js.map +0 -1
  180. package/dist/commands/ops.js.map +0 -1
  181. package/dist/commands/products-db.js.map +0 -1
  182. package/dist/commands/products.js.map +0 -1
  183. package/dist/commands/promote.js.map +0 -1
  184. package/dist/commands/publish.js.map +0 -1
  185. package/dist/commands/schema.js.map +0 -1
  186. package/dist/commands/servers.js.map +0 -1
  187. package/dist/commands/smoke.js.map +0 -1
  188. package/dist/commands/status.js.map +0 -1
  189. package/dist/commands/support.js.map +0 -1
  190. package/dist/commands/surface-status.js.map +0 -1
  191. package/dist/commands/tenants.js.map +0 -1
  192. package/dist/commands/ui.js.map +0 -1
  193. package/dist/commands/upgrade.js.map +0 -1
  194. package/dist/commands/validate.js.map +0 -1
  195. package/dist/commands/vm-memory.js.map +0 -1
  196. package/dist/commands/vm.js.map +0 -1
  197. package/dist/commands/webhooks.js.map +0 -1
  198. package/dist/commands/whoami.js.map +0 -1
  199. package/dist/commands/workspaces.js.map +0 -1
  200. package/dist/index.js.map +0 -1
  201. package/dist/lib/agent-artifact-url.js.map +0 -1
  202. package/dist/lib/agent-commands.js.map +0 -1
  203. package/dist/lib/ai/auth-discovery.js.map +0 -1
  204. package/dist/lib/ai/context.js.map +0 -1
  205. package/dist/lib/ai/orchestrator.js.map +0 -1
  206. package/dist/lib/api-client.js.map +0 -1
  207. package/dist/lib/api-error.js.map +0 -1
  208. package/dist/lib/auth.js.map +0 -1
  209. package/dist/lib/cli-read.js.map +0 -1
  210. package/dist/lib/cli-write.js.map +0 -1
  211. package/dist/lib/code-agent-budget.js.map +0 -1
  212. package/dist/lib/code-agent-files.js.map +0 -1
  213. package/dist/lib/code-agent-gateway.js.map +0 -1
  214. package/dist/lib/code-agent-git-tools.js.map +0 -1
  215. package/dist/lib/code-agent-protocol.js.map +0 -1
  216. package/dist/lib/code-agent-session.js.map +0 -1
  217. package/dist/lib/code-agent-settings.js.map +0 -1
  218. package/dist/lib/code-agent-skills.js.map +0 -1
  219. package/dist/lib/code-agent-subagents.js.map +0 -1
  220. package/dist/lib/code-agent-tools.js.map +0 -1
  221. package/dist/lib/code-agent-tui.js.map +0 -1
  222. package/dist/lib/code-agent-types.js.map +0 -1
  223. package/dist/lib/code-agent-ui.js.map +0 -1
  224. package/dist/lib/code-agent-web-tools.js.map +0 -1
  225. package/dist/lib/code-agent-write-tools.js.map +0 -1
  226. package/dist/lib/compose-generator.js.map +0 -1
  227. package/dist/lib/config-schema.js.map +0 -1
  228. package/dist/lib/config.js.map +0 -1
  229. package/dist/lib/db-local/db-json.js.map +0 -1
  230. package/dist/lib/db-local/env.js.map +0 -1
  231. package/dist/lib/db-local/fingerprint.js.map +0 -1
  232. package/dist/lib/db-local/index.js.map +0 -1
  233. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  234. package/dist/lib/db-pipeline/errors.js.map +0 -1
  235. package/dist/lib/db-pipeline/index.js.map +0 -1
  236. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  237. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  238. package/dist/lib/db-pipeline/types.js.map +0 -1
  239. package/dist/lib/dev-local-context.js.map +0 -1
  240. package/dist/lib/iam-preflight.js.map +0 -1
  241. package/dist/lib/mobile-local/index.js.map +0 -1
  242. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  243. package/dist/lib/pickers.js.map +0 -1
  244. package/dist/lib/product-config.js.map +0 -1
  245. package/dist/lib/render.js.map +0 -1
  246. package/dist/lib/schema-cache.js.map +0 -1
  247. package/dist/lib/sse.js.map +0 -1
  248. package/dist/lib/telemetry.js.map +0 -1
  249. package/dist/lib/token-resolver.js.map +0 -1
  250. package/dist/lib/url-allowlist.js.map +0 -1
  251. package/dist/lib/version-check.js.map +0 -1
  252. package/dist/utils/logger.js.map +0 -1
  253. package/dist/utils/logo.js.map +0 -1
  254. package/dist/utils/safe-exit.js.map +0 -1
  255. package/dist/utils/spinner-registry.js.map +0 -1
  256. package/dist/version.js.map +0 -1
  257. package/templates/dev/Dockerfile.agent +0 -39
  258. package/templates/dev/agent-entrypoint.sh +0 -41
  259. package/templates/dev/docker-compose.dev.yml +0 -63
@@ -0,0 +1,127 @@
1
+ import { formatChoices, invalidChoiceError } from '../cli-kit/choices.js';
2
+ import { exitWithCliError } from './cli-error.js';
3
+ /** Ambientes de tenant/workspace/deployment aceitos pelo Core. */
4
+ export const TENANT_ENVIRONMENT_IDS = ['dev-local', 'staging', 'prod'];
5
+ /**
6
+ * Padrão canônico. É `'dev-local'`, nunca `'dev'` — ver o cabeçalho do módulo.
7
+ */
8
+ export const DEFAULT_TENANT_ENVIRONMENT_ID = 'dev-local';
9
+ /**
10
+ * Apelidos históricos tolerados na entrada do usuário. Existem só para que
11
+ * scripts e dedos-na-memória que escrevem `--env dev` continuem funcionando
12
+ * em vez de tomar 400 do Core — a saída é sempre o token canônico.
13
+ */
14
+ const APELIDOS = {
15
+ dev: 'dev-local',
16
+ devlocal: 'dev-local',
17
+ 'dev-local': 'dev-local',
18
+ local: 'dev-local',
19
+ stage: 'staging',
20
+ staging: 'staging',
21
+ prod: 'prod',
22
+ producao: 'prod',
23
+ production: 'prod',
24
+ };
25
+ /** Lista legível para mensagens de erro e textos de ajuda. */
26
+ export function listarAmbientesTenant() {
27
+ return formatChoices(TENANT_ENVIRONMENT_IDS);
28
+ }
29
+ /**
30
+ * Normaliza um `--env` cru para o token que o Core aceita.
31
+ *
32
+ * @returns o id canônico, ou `null` quando o valor não corresponde a nenhum
33
+ * ambiente conhecido — o chamador decide como reportar.
34
+ */
35
+ export function normalizarAmbienteTenant(bruto) {
36
+ if (bruto === undefined || bruto === null)
37
+ return null;
38
+ const chave = String(bruto).trim().toLowerCase();
39
+ if (!chave)
40
+ return null;
41
+ return APELIDOS[chave] ?? null;
42
+ }
43
+ /**
44
+ * Opções do prompt "Ambiente:" — mesma ordem de promoção do painel, e da
45
+ * MESMA lista que valida (P105: era uma cópia à mão de `TENANT_ENVIRONMENT_IDS`).
46
+ */
47
+ export const ESCOLHAS_AMBIENTE_TENANT = TENANT_ENVIRONMENT_IDS.map((value) => ({
48
+ name: value === DEFAULT_TENANT_ENVIRONMENT_ID ? `${value} (padrão — sua máquina)` : value,
49
+ value,
50
+ }));
51
+ /**
52
+ * Os OUTROS vocabulários de ambiente da CLI, derivados deste alfabeto por UMA
53
+ * tabela (P105 / bug_89d2497e, item 7). Antes, `deploy.ts` tinha
54
+ * `new Set(['dev','staging','prod'])` e `build.ts`/`env.ts` repetiam
55
+ * `'dev' | 'workspace' | 'prod'` à mão em oito lugares — cópias que podiam
56
+ * envelhecer em separado, como o `--env dev` envelheceu (bug_7dcb68e2).
57
+ *
58
+ * - `deploy`: o que `neetru deploy --env` aceita e manda como
59
+ * `environmentId`. O Core aceita `dev` como apelido de `dev-local` no
60
+ * deploy (`deployRequestSchema` faz `dev → dev-local`, conferido na
61
+ * origin/main do neetru-core em 2026-10-18).
62
+ * - `runtime`: o `NEETRU_ENV` do SDK / `neetru build --target-env` /
63
+ * `neetru env switch`. `staging` vira `workspace` (o mesmo estágio
64
+ * não-dev/não-prod, com o nome que o SDK usa).
65
+ *
66
+ * A tabela é exaustiva por tipo: um ambiente novo em `TENANT_ENVIRONMENT_IDS`
67
+ * não compila até ganhar sua linha aqui.
68
+ */
69
+ export const VOCABULARIOS_DE_AMBIENTE = {
70
+ 'dev-local': { deploy: 'dev', runtime: 'dev' },
71
+ staging: { deploy: 'staging', runtime: 'workspace' },
72
+ prod: { deploy: 'prod', runtime: 'prod' },
73
+ };
74
+ /** `dev | staging | prod` — o `--env` do `neetru deploy`, na ordem de promoção. */
75
+ export const DEPLOY_ENVIRONMENT_IDS = TENANT_ENVIRONMENT_IDS.map((id) => VOCABULARIOS_DE_AMBIENTE[id].deploy);
76
+ /** `dev | workspace | prod` — o `NEETRU_ENV` do SDK, na ordem de promoção. */
77
+ export const RUNTIME_ENVIRONMENT_IDS = TENANT_ENVIRONMENT_IDS.map((id) => VOCABULARIOS_DE_AMBIENTE[id].runtime);
78
+ /**
79
+ * `--env` do deploy → `NEETRU_ENV` do build, pela tabela. `undefined` para
80
+ * valor fora do vocabulário do deploy (quem chama decide o padrão).
81
+ */
82
+ export function deployEnvParaRuntime(env) {
83
+ const id = TENANT_ENVIRONMENT_IDS.find((t) => VOCABULARIOS_DE_AMBIENTE[t].deploy === env);
84
+ return id ? VOCABULARIOS_DE_AMBIENTE[id].runtime : undefined;
85
+ }
86
+ /**
87
+ * Erro único de `--env` inválido (bug_89d2497e): os valores válidos saem de
88
+ * `TENANT_ENVIRONMENT_IDS` — a MESMA lista que valida —, e o JSON leva
89
+ * `flag`, `value` e `valid` além de `error`/`message`.
90
+ */
91
+ export function erroAmbienteTenantInvalido(bruto) {
92
+ return invalidChoiceError({
93
+ flag: '--env',
94
+ value: bruto,
95
+ valid: TENANT_ENVIRONMENT_IDS,
96
+ subject: 'Ambiente',
97
+ code: 'invalid_environment',
98
+ hint: 'Dica: o ambiente de desenvolvimento chama-se "dev-local" (não "dev").',
99
+ });
100
+ }
101
+ /**
102
+ * Mensagem única de `--env` inválido. Mantida aqui para que todos os comandos
103
+ * falhem com o mesmo texto e o dev aprenda o alfabeto de uma vez só.
104
+ */
105
+ export function mensagemAmbienteInvalido(bruto) {
106
+ return erroAmbienteTenantInvalido(bruto).message;
107
+ }
108
+ /**
109
+ * Normaliza um `--env` vindo da linha de comando e ABORTA quando não dá para
110
+ * mapear. Falhar aqui — antes de qualquer chamada de rede — é melhor do que
111
+ * mandar lixo pro Core e traduzir um 400 genérico depois.
112
+ */
113
+ export function resolverAmbienteTenant(bruto, json) {
114
+ const canonico = normalizarAmbienteTenant(bruto);
115
+ if (canonico) {
116
+ // Mesma convenção de `normalizeEnv` em src/lib/db-local/env.ts: traduzir o
117
+ // apelido em silêncio faria o dev continuar escrevendo "dev" pra sempre.
118
+ // Vai pro stderr de propósito — o stdout em `--json` é contrato de máquina.
119
+ if (bruto.trim().toLowerCase() !== canonico) {
120
+ process.stderr.write(`Aviso: ambiente "${bruto}" é um apelido — o nome canônico é "${canonico}". ` +
121
+ `Atualize seus scripts e config para usar "${canonico}" diretamente.\n`);
122
+ }
123
+ return canonico;
124
+ }
125
+ return exitWithCliError(erroAmbienteTenantInvalido(bruto), json);
126
+ }
127
+ //# sourceMappingURL=envs.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * fs-seguro — apagar DE VERDADE com caminho não-ASCII, e conferir.
3
+ *
4
+ * POR QUE (reproduzido no Node 24.13, Windows, página ANSI 1252, 2026-10-13):
5
+ * algumas APIs síncronas do `fs`, reescritas em C++, leem o caminho na página
6
+ * ANSI em vez de UTF-8. Com acento (NFC ou NFD) ou fora do cp1252 (`日本`):
7
+ *
8
+ * - `fs.rmSync(arquivo)` volta SEM ERRO e não apaga, com ou sem `force`;
9
+ * - `fs.rmSync(dir, { recursive })` não apaga, inclusive quando o acento está
10
+ * só num diretório acima (`C:\Users\João\AppData\Local\Temp\...`);
11
+ * - com um `ação.txt` na mesma pasta, `rmSync('ação.txt')` apaga o
12
+ * `ação.txt`, o arquivo ERRADO;
13
+ * - `fs.rmdirSync(dir, { recursive: true })` não apaga;
14
+ * - `fs.cpSync(dirAcentuado, ...)` MATA o processo calado (exit 127, sem
15
+ * stdout nem stderr) e cria o destino em mojibake.
16
+ *
17
+ * A CLI roda na máquina do dev: uma home com acento (`C:\Users\João`) põe o
18
+ * `%TEMP%` e o projeto inteiro nesse caminho.
19
+ *
20
+ * O QUE FUNCIONA (testado com NFC, NFD, `日本`, arquivo somente-leitura e
21
+ * junction em `fs-seguro.test.ts`): `unlinkSync` e `fs.promises.rm`. Mesmo
22
+ * assim, os dois helpers abaixo CONFEREM que o caminho sumiu e LANÇAM se não
23
+ * sumiu: "apagou" foi justamente a mentira, e remoção que não aconteceu não
24
+ * pode ser relatada como feita.
25
+ *
26
+ * `rmSync`, `rmdirSync` e `cpSync` estão proibidos no repo inteiro pela guarda
27
+ * `src/__tests__/regressions/fs-sync-acento.regression.test.ts`.
28
+ */
29
+ import * as fs from 'node:fs';
30
+ /** Dependências injetáveis só para teste. */
31
+ export interface OpcoesRemoverArquivo {
32
+ unlink?: (caminho: string) => void;
33
+ }
34
+ /**
35
+ * Apaga UM arquivo (ou link) e confere que ele sumiu; se não sumiu, LANÇA.
36
+ *
37
+ * Caminho inexistente não é erro (como o `force: true` do `rmSync`). Diretório
38
+ * de verdade é recusado: use `removerDiretorio`.
39
+ */
40
+ export declare function removerArquivo(caminho: string, { unlink }?: OpcoesRemoverArquivo): void;
41
+ /** Dependências injetáveis só para teste. */
42
+ export interface OpcoesRemoverDiretorio {
43
+ rm?: (caminho: string, opcoes: fs.RmOptions) => Promise<void>;
44
+ }
45
+ /**
46
+ * Apaga um diretório com tudo dentro (ou um arquivo) e confere que o caminho
47
+ * sumiu; se não sumiu, LANÇA.
48
+ *
49
+ * Caminho inexistente não é erro. Não segue link nem junction: apaga o link,
50
+ * e o alvo fica intacto. `maxRetries` cobre o EBUSY/EPERM passageiro do
51
+ * Windows (antivírus, indexador).
52
+ */
53
+ export declare function removerDiretorio(caminho: string, { rm }?: OpcoesRemoverDiretorio): Promise<void>;
54
+ /**
55
+ * Avisa no stderr que um temporário NÃO foi apagado, com o caminho e o motivo.
56
+ * Nunca lança.
57
+ *
58
+ * Para limpeza best-effort, em que a falha não pode derrubar o resultado nem
59
+ * esconder o erro original, mas também não pode sumir calada: o temporário
60
+ * que sobra (`neetru-drizzle-*`, `*.tmp-*`) é o sintoma que o `fs-seguro`
61
+ * existe para evitar. Vai pro stderr para não sujar a saída `--json` no stdout.
62
+ */
63
+ export declare function avisarSobraDeTemporario(caminho: string, motivo: unknown, escrever?: (texto: string) => unknown): void;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * fs-seguro — apagar DE VERDADE com caminho não-ASCII, e conferir.
3
+ *
4
+ * POR QUE (reproduzido no Node 24.13, Windows, página ANSI 1252, 2026-10-13):
5
+ * algumas APIs síncronas do `fs`, reescritas em C++, leem o caminho na página
6
+ * ANSI em vez de UTF-8. Com acento (NFC ou NFD) ou fora do cp1252 (`日本`):
7
+ *
8
+ * - `fs.rmSync(arquivo)` volta SEM ERRO e não apaga, com ou sem `force`;
9
+ * - `fs.rmSync(dir, { recursive })` não apaga, inclusive quando o acento está
10
+ * só num diretório acima (`C:\Users\João\AppData\Local\Temp\...`);
11
+ * - com um `ação.txt` na mesma pasta, `rmSync('ação.txt')` apaga o
12
+ * `ação.txt`, o arquivo ERRADO;
13
+ * - `fs.rmdirSync(dir, { recursive: true })` não apaga;
14
+ * - `fs.cpSync(dirAcentuado, ...)` MATA o processo calado (exit 127, sem
15
+ * stdout nem stderr) e cria o destino em mojibake.
16
+ *
17
+ * A CLI roda na máquina do dev: uma home com acento (`C:\Users\João`) põe o
18
+ * `%TEMP%` e o projeto inteiro nesse caminho.
19
+ *
20
+ * O QUE FUNCIONA (testado com NFC, NFD, `日本`, arquivo somente-leitura e
21
+ * junction em `fs-seguro.test.ts`): `unlinkSync` e `fs.promises.rm`. Mesmo
22
+ * assim, os dois helpers abaixo CONFEREM que o caminho sumiu e LANÇAM se não
23
+ * sumiu: "apagou" foi justamente a mentira, e remoção que não aconteceu não
24
+ * pode ser relatada como feita.
25
+ *
26
+ * `rmSync`, `rmdirSync` e `cpSync` estão proibidos no repo inteiro pela guarda
27
+ * `src/__tests__/regressions/fs-sync-acento.regression.test.ts`.
28
+ */
29
+ import * as fs from 'node:fs';
30
+ /** Diz se ALGO (arquivo, diretório ou link, mesmo quebrado) ainda ocupa o caminho. */
31
+ function aindaExiste(caminho) {
32
+ try {
33
+ fs.lstatSync(caminho);
34
+ return true;
35
+ }
36
+ catch (err) {
37
+ const code = err.code;
38
+ if (code === 'ENOENT' || code === 'ENOTDIR')
39
+ return false;
40
+ throw err;
41
+ }
42
+ }
43
+ function naoApagou(caminho, tipo) {
44
+ return new Error(`não consegui apagar o ${tipo} "${caminho}": a remoção voltou sem erro, mas ele continua lá`);
45
+ }
46
+ /**
47
+ * Apaga UM arquivo (ou link) e confere que ele sumiu; se não sumiu, LANÇA.
48
+ *
49
+ * Caminho inexistente não é erro (como o `force: true` do `rmSync`). Diretório
50
+ * de verdade é recusado: use `removerDiretorio`.
51
+ */
52
+ export function removerArquivo(caminho, { unlink = fs.unlinkSync } = {}) {
53
+ let st;
54
+ try {
55
+ st = fs.lstatSync(caminho);
56
+ }
57
+ catch (err) {
58
+ const code = err.code;
59
+ if (code === 'ENOENT' || code === 'ENOTDIR')
60
+ return;
61
+ throw err;
62
+ }
63
+ if (st.isDirectory()) {
64
+ throw new Error(`"${caminho}" é um diretório; use removerDiretorio`);
65
+ }
66
+ try {
67
+ unlink(caminho);
68
+ }
69
+ catch (err) {
70
+ // Sumiu entre o lstat e o unlink: o objetivo foi atingido.
71
+ if (err.code !== 'ENOENT')
72
+ throw err;
73
+ }
74
+ if (aindaExiste(caminho))
75
+ throw naoApagou(caminho, 'arquivo');
76
+ }
77
+ /**
78
+ * Apaga um diretório com tudo dentro (ou um arquivo) e confere que o caminho
79
+ * sumiu; se não sumiu, LANÇA.
80
+ *
81
+ * Caminho inexistente não é erro. Não segue link nem junction: apaga o link,
82
+ * e o alvo fica intacto. `maxRetries` cobre o EBUSY/EPERM passageiro do
83
+ * Windows (antivírus, indexador).
84
+ */
85
+ export async function removerDiretorio(caminho, { rm = fs.promises.rm } = {}) {
86
+ await rm(caminho, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
87
+ if (aindaExiste(caminho))
88
+ throw naoApagou(caminho, 'diretório');
89
+ }
90
+ /**
91
+ * Avisa no stderr que um temporário NÃO foi apagado, com o caminho e o motivo.
92
+ * Nunca lança.
93
+ *
94
+ * Para limpeza best-effort, em que a falha não pode derrubar o resultado nem
95
+ * esconder o erro original, mas também não pode sumir calada: o temporário
96
+ * que sobra (`neetru-drizzle-*`, `*.tmp-*`) é o sintoma que o `fs-seguro`
97
+ * existe para evitar. Vai pro stderr para não sujar a saída `--json` no stdout.
98
+ */
99
+ export function avisarSobraDeTemporario(caminho, motivo, escrever = (texto) => process.stderr.write(texto)) {
100
+ const msg = motivo instanceof Error ? motivo.message : String(motivo);
101
+ try {
102
+ escrever(`aviso: o temporário "${caminho}" não foi apagado (${msg}). Pode apagar à mão.\n`);
103
+ }
104
+ catch {
105
+ /* avisar não pode virar outro erro */
106
+ }
107
+ }
108
+ //# sourceMappingURL=fs-seguro.js.map
@@ -11,6 +11,7 @@
11
11
  import * as fs from 'node:fs';
12
12
  import * as path from 'node:path';
13
13
  import { randomBytes } from 'node:crypto';
14
+ import { avisarSobraDeTemporario, removerArquivo } from '../fs-seguro.js';
14
15
  /** Registry v1 vazio — usado quando o arquivo ainda não existe. */
15
16
  function emptyMobileJson() {
16
17
  return { version: 1, apps: [] };
@@ -147,12 +148,17 @@ export function writeMobileJson(filePath, data) {
147
148
  fs.renameSync(tmpPath, filePath);
148
149
  }
149
150
  catch (err) {
150
- // Best-effort: remove o temp pra não deixar lixo se algo falhou.
151
+ // Best-effort: remove o temp pra não deixar lixo se algo falhou. Não usa
152
+ // `fs.rmSync`: com acento no caminho (`C:\Users\João\...`), no Node 24 do
153
+ // Windows ele volta sem erro e não apaga (ver `lib/fs-seguro.ts`).
154
+ // `removerArquivo` apaga e confere; se ainda assim falhar, o erro que
155
+ // importa é o original, que segue abaixo — e a sobra vira aviso no stderr
156
+ // com o caminho, em vez de sumir calada (revisão do #79).
151
157
  try {
152
- fs.rmSync(tmpPath, { force: true });
158
+ removerArquivo(tmpPath);
153
159
  }
154
- catch {
155
- /* ignora — limpeza best-effort */
160
+ catch (limpeza) {
161
+ avisarSobraDeTemporario(tmpPath, limpeza);
156
162
  }
157
163
  throw err;
158
164
  }
@@ -3,16 +3,42 @@ export interface InteractivityOptions {
3
3
  nonInteractive?: boolean;
4
4
  }
5
5
  export declare function isInteractive(opts?: InteractivityOptions): boolean;
6
- interface ResolveOptions<T> extends InteractivityOptions {
6
+ /**
7
+ * Como o comando em execução recebe o valor que o picker escolhe: flags
8
+ * (`--server`) e/ou posicional como no `.command()` (`<serverId>`). O picker
9
+ * cita só o que o comando declara (P105 / bug_89d2497e).
10
+ */
11
+ export interface PickerInputOptions {
12
+ inputs?: readonly string[];
13
+ }
14
+ interface ResolveOptions<T> extends InteractivityOptions, PickerInputOptions {
7
15
  fromFlag?: string;
8
16
  fetcher: () => Promise<T[]>;
9
17
  format: (item: T) => string;
10
18
  getId: (item: T) => string;
11
19
  message: string;
12
20
  emptyError: string;
21
+ /** A entrada padrão do picker — usada quando `inputs` não vem. */
13
22
  flagName: string;
23
+ /** O que está sendo escolhido, para a frase sem flag (ex.: `'o produto'`). */
24
+ subject?: string;
14
25
  allowManual?: boolean;
15
26
  }
27
+ /**
28
+ * A entrada que o conselho pode citar, já validada contra o comando em
29
+ * execução: `'--tenant'`, `'<id>'`, `'<serverId> ou --server'`… — ou `null`
30
+ * quando o comando não declara nenhuma das candidatas (ou não há comando).
31
+ *
32
+ * P105: o texto fixo citava a entrada padrão do picker. Medido na árvore do
33
+ * Commander em 2026-10-18, ela não existia em 10 comandos: `tenants get|
34
+ * update|suspend|reactivate` (recebem `[id]`, não `--tenant`), `servers
35
+ * deactivate|dispatch` (`[serverId]`, não `--server`), `products publish|
36
+ * unpublish` (`[slug]`, não `--product`) e `admin database create`/`products
37
+ * db create` (`--product-id`/`--server-id`, não `--product`/`--server`).
38
+ */
39
+ export declare function entradaDoPicker(opts: {
40
+ flagName: string;
41
+ } & PickerInputOptions): string | null;
16
42
  export declare function resolveOrPick<T>(opts: ResolveOptions<T>): Promise<string>;
17
43
  export declare function promptText(message: string, opts?: InteractivityOptions & {
18
44
  default?: string;
@@ -25,6 +51,17 @@ export declare function promptChoice<T extends string>(message: string, choices:
25
51
  }>, opts?: InteractivityOptions & {
26
52
  default?: T;
27
53
  }): Promise<T>;
54
+ /**
55
+ * Versão só-slug da heurística abaixo, para quando o produto AINDA NÃO existe
56
+ * no Core — caso do `neetru new`, que precisa decidir o customer antes de
57
+ * criar qualquer coisa.
58
+ *
59
+ * bug_7dcb68e2666a43f69ef362d2cbff94ed: como a lista de fallback tem apenas
60
+ * dois slugs, todo produto novo cai no caminho "não é Neetru-owned", que é
61
+ * justamente o caminho em que o Core exige `--customer`. O ponto é avisar
62
+ * ANTES, e não quebrar no passo [2/5] com o produto já criado.
63
+ */
64
+ export declare function isNeetruOwnedSlug(slug: string | null | undefined): boolean;
28
65
  /**
29
66
  * Verifica (via GET /api/cli/v1/products/{id}) se um produto é Neetru-owned.
30
67
  * Usa heurísticas de cliente: ownedBy==='neetru' | ownerTeam==='Neetru Core' | slug fallback.
@@ -100,7 +137,7 @@ export declare function listProductDatabases(token: string, filters?: {
100
137
  status?: string;
101
138
  }): Promise<ProductDatabaseSummary[]>;
102
139
  export declare function productLabel(product: ProductSummary): string;
103
- export declare function pickProductId(token: string, opts?: InteractivityOptions & {
140
+ export declare function pickProductId(token: string, opts?: InteractivityOptions & PickerInputOptions & {
104
141
  fromFlag?: string;
105
142
  message?: string;
106
143
  }): Promise<string>;
@@ -116,29 +153,29 @@ export declare function pickProductId(token: string, opts?: InteractivityOptions
116
153
  * `--product qkcWzQ43LthAIRlVUSQ6` em qualquer comando que aceite produto.
117
154
  */
118
155
  export declare function resolveProductIdentifier(token: string, input: string, opts?: InteractivityOptions): Promise<string>;
119
- export declare function pickProductSlug(token: string, opts?: InteractivityOptions & {
156
+ export declare function pickProductSlug(token: string, opts?: InteractivityOptions & PickerInputOptions & {
120
157
  fromFlag?: string;
121
158
  message?: string;
122
159
  }): Promise<string>;
123
- export declare function pickTenant(token: string, opts?: InteractivityOptions & {
160
+ export declare function pickTenant(token: string, opts?: InteractivityOptions & PickerInputOptions & {
124
161
  fromFlag?: string;
125
162
  product?: string;
126
163
  status?: string;
127
164
  message?: string;
128
165
  }): Promise<string>;
129
- export declare function pickWorkspace(token: string, opts?: InteractivityOptions & {
166
+ export declare function pickWorkspace(token: string, opts?: InteractivityOptions & PickerInputOptions & {
130
167
  fromFlag?: string;
131
168
  product?: string;
132
169
  status?: string;
133
170
  message?: string;
134
171
  }): Promise<string>;
135
- export declare function pickServer(token: string, opts?: InteractivityOptions & {
172
+ export declare function pickServer(token: string, opts?: InteractivityOptions & PickerInputOptions & {
136
173
  fromFlag?: string;
137
174
  status?: string;
138
175
  capacity?: boolean;
139
176
  message?: string;
140
177
  }): Promise<string>;
141
- export declare function pickDatabase(token: string, opts?: InteractivityOptions & {
178
+ export declare function pickDatabase(token: string, opts?: InteractivityOptions & PickerInputOptions & {
142
179
  fromFlag?: string;
143
180
  productId?: string;
144
181
  engine?: string;
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
3
  import { apiRequest } from './api-client.js';
4
4
  import { handleApiError } from './api-error.js';
5
+ import { adviseCommand, declaredInputs } from '../cli-kit/command-context.js';
5
6
  import { log } from '../utils/logger.js';
6
7
  import { safeExit } from '../utils/safe-exit.js';
7
8
  export function isInteractive(opts = {}) {
@@ -26,12 +27,32 @@ function failLookup(error, json) {
26
27
  // →2) divergentes dos comandos, contribuindo pro conflito que quebrava CI.
27
28
  return handleApiError(error, json);
28
29
  }
30
+ /**
31
+ * A entrada que o conselho pode citar, já validada contra o comando em
32
+ * execução: `'--tenant'`, `'<id>'`, `'<serverId> ou --server'`… — ou `null`
33
+ * quando o comando não declara nenhuma das candidatas (ou não há comando).
34
+ *
35
+ * P105: o texto fixo citava a entrada padrão do picker. Medido na árvore do
36
+ * Commander em 2026-10-18, ela não existia em 10 comandos: `tenants get|
37
+ * update|suspend|reactivate` (recebem `[id]`, não `--tenant`), `servers
38
+ * deactivate|dispatch` (`[serverId]`, não `--server`), `products publish|
39
+ * unpublish` (`[slug]`, não `--product`) e `admin database create`/`products
40
+ * db create` (`--product-id`/`--server-id`, não `--product`/`--server`).
41
+ */
42
+ export function entradaDoPicker(opts) {
43
+ const declaradas = declaredInputs(opts.inputs ?? [opts.flagName]);
44
+ return declaradas.length > 0 ? declaradas.join(' ou ') : null;
45
+ }
29
46
  export async function resolveOrPick(opts) {
30
47
  const flagged = opts.fromFlag?.trim();
31
48
  if (flagged)
32
49
  return flagged;
50
+ const entrada = entradaDoPicker(opts);
51
+ const sujeito = opts.subject ?? 'o valor';
33
52
  if (opts.json) {
34
- failInput(`${opts.flagName} e obrigatorio em modo --json.`, true);
53
+ failInput(entrada
54
+ ? `${entrada} e obrigatorio em modo --json.`
55
+ : `Este comando precisa escolher ${sujeito} e, em modo --json, não há como perguntar qual.`, true);
35
56
  }
36
57
  let items;
37
58
  try {
@@ -50,7 +71,10 @@ export async function resolveOrPick(opts) {
50
71
  const available = items
51
72
  .map((item) => ` - ${opts.getId(item)}: ${stripAnsi(opts.format(item))}`)
52
73
  .join('\n');
53
- failInput(`Multiplos candidatos encontrados. Passe ${opts.flagName} explicitamente.\nDisponiveis:\n${available}`, opts.json);
74
+ failInput(entrada
75
+ ? `Multiplos candidatos encontrados. Passe ${entrada} explicitamente.\nDisponiveis:\n${available}`
76
+ : `Múltiplos candidatos encontrados, e este comando não recebe ${sujeito} por argumento: ` +
77
+ `rode num terminal interativo para escolher.\nDisponiveis:\n${available}`, opts.json);
54
78
  }
55
79
  const choices = items.map((item) => ({
56
80
  name: opts.format(item),
@@ -122,6 +146,21 @@ export async function promptChoice(message, choices, opts = {}) {
122
146
  // Slugs Neetru-owned conhecidos no cliente CLI (espelho do fallback server-side).
123
147
  // Atualizar junto com NEETRU_PRODUCT_SLUGS_FALLBACK em resolve-default.ts.
124
148
  const NEETRU_SLUGS_CLI = ['gestovendas', 'pdv-agiliza'];
149
+ /**
150
+ * Versão só-slug da heurística abaixo, para quando o produto AINDA NÃO existe
151
+ * no Core — caso do `neetru new`, que precisa decidir o customer antes de
152
+ * criar qualquer coisa.
153
+ *
154
+ * bug_7dcb68e2666a43f69ef362d2cbff94ed: como a lista de fallback tem apenas
155
+ * dois slugs, todo produto novo cai no caminho "não é Neetru-owned", que é
156
+ * justamente o caminho em que o Core exige `--customer`. O ponto é avisar
157
+ * ANTES, e não quebrar no passo [2/5] com o produto já criado.
158
+ */
159
+ export function isNeetruOwnedSlug(slug) {
160
+ if (!slug)
161
+ return false;
162
+ return NEETRU_SLUGS_CLI.includes(slug.trim().toLowerCase());
163
+ }
125
164
  /**
126
165
  * Verifica (via GET /api/cli/v1/products/{id}) se um produto é Neetru-owned.
127
166
  * Usa heurísticas de cliente: ownedBy==='neetru' | ownerTeam==='Neetru Core' | slug fallback.
@@ -220,6 +259,17 @@ export function productLabel(product) {
220
259
  const team = product.ownerTeam ? chalk.dim(`- ${product.ownerTeam}`) : '';
221
260
  return compact([statusDot(product.status), chalk.bold(name), slug, status, team]).join(' ');
222
261
  }
262
+ /**
263
+ * "Nenhum produto": a dica de criar só cita `neetru products create` se o
264
+ * comando existe na árvore (P105).
265
+ */
266
+ function semProdutoAinda() {
267
+ return adviseCommand({
268
+ command: 'products create',
269
+ withCommand: 'Nenhum produto encontrado. Crie com: neetru products create',
270
+ withoutCommand: 'Nenhum produto encontrado.',
271
+ });
272
+ }
223
273
  export async function pickProductId(token, opts = {}) {
224
274
  return resolveOrPick({
225
275
  ...opts,
@@ -227,8 +277,9 @@ export async function pickProductId(token, opts = {}) {
227
277
  format: productLabel,
228
278
  getId: (product) => product.id,
229
279
  message: opts.message ?? 'Escolha o produto:',
230
- emptyError: 'Nenhum produto encontrado. Crie com: neetru products create',
280
+ emptyError: semProdutoAinda(),
231
281
  flagName: '--product',
282
+ subject: 'o produto',
232
283
  });
233
284
  }
234
285
  /**
@@ -272,8 +323,9 @@ export async function pickProductSlug(token, opts = {}) {
272
323
  format: productLabel,
273
324
  getId: (product) => product.slug ?? product.id,
274
325
  message: opts.message ?? 'Escolha o produto:',
275
- emptyError: 'Nenhum produto encontrado. Crie com: neetru products create',
326
+ emptyError: semProdutoAinda(),
276
327
  flagName: '--product',
328
+ subject: 'o produto',
277
329
  });
278
330
  }
279
331
  export async function pickTenant(token, opts = {}) {
@@ -285,6 +337,7 @@ export async function pickTenant(token, opts = {}) {
285
337
  message: opts.message ?? 'Escolha o tenant:',
286
338
  emptyError: 'Nenhum tenant encontrado no escopo atual.',
287
339
  flagName: '--tenant',
340
+ subject: 'o tenant',
288
341
  });
289
342
  }
290
343
  export async function pickWorkspace(token, opts = {}) {
@@ -296,6 +349,7 @@ export async function pickWorkspace(token, opts = {}) {
296
349
  message: opts.message ?? 'Escolha o workspace:',
297
350
  emptyError: 'Nenhum workspace encontrado no escopo atual.',
298
351
  flagName: '<workspaceId>',
352
+ subject: 'o workspace',
299
353
  });
300
354
  }
301
355
  export async function pickServer(token, opts = {}) {
@@ -307,6 +361,7 @@ export async function pickServer(token, opts = {}) {
307
361
  message: opts.message ?? 'Escolha o server:',
308
362
  emptyError: 'Nenhum server encontrado no escopo atual.',
309
363
  flagName: '--server',
364
+ subject: 'o server',
310
365
  });
311
366
  }
312
367
  export async function pickDatabase(token, opts = {}) {
@@ -322,6 +377,7 @@ export async function pickDatabase(token, opts = {}) {
322
377
  message: opts.message ?? 'Escolha o banco:',
323
378
  emptyError: 'Nenhum banco encontrado no escopo atual.',
324
379
  flagName: '<id>',
380
+ subject: 'o banco',
325
381
  });
326
382
  }
327
383
  function workspaceLabel(workspace) {
@@ -0,0 +1,62 @@
1
+ import { type DocsTaxonomy } from './taxonomy.js';
2
+ import { type ProductRepo } from './config.js';
3
+ export type Level = 'erro' | 'aviso' | 'sugestao';
4
+ export interface Finding {
5
+ /** Caminho relativo à raiz do repositório, com `/`. */
6
+ file: string;
7
+ line?: number;
8
+ code: string;
9
+ level: Level;
10
+ message: string;
11
+ }
12
+ export interface FileContext {
13
+ taxonomy: DocsTaxonomy;
14
+ slug: string;
15
+ /** Caminho relativo à raiz do repo da pasta de docs (ex.: `docs`). */
16
+ docsRoot: string;
17
+ /** Existe este caminho (relativo à raiz do repo)? */
18
+ exists: (repoRel: string) => boolean;
19
+ legacyNames: Set<string>;
20
+ /** AAAA-MM-DD de hoje (injetável em teste). */
21
+ today: string;
22
+ /** O arquivo foi alterado na branch (LK001 vira erro). */
23
+ changed: boolean;
24
+ }
25
+ /** Valida UM doc. `relInDocs` é relativo à pasta de docs, com `/`. Função pura. */
26
+ export declare function checkDocFile(relInDocs: string, raw: string, ctx: FileContext): Finding[];
27
+ export interface CheckOptions {
28
+ /** Só os arquivos alterados contra a branch principal (catraca). */
29
+ changed?: boolean;
30
+ /** Aviso vira erro. */
31
+ strict?: boolean;
32
+ taxonomy: DocsTaxonomy;
33
+ today?: string;
34
+ }
35
+ export interface CheckResult {
36
+ ok: boolean;
37
+ findings: Finding[];
38
+ counts: {
39
+ erro: number;
40
+ aviso: number;
41
+ sugestao: number;
42
+ };
43
+ filesChecked: number;
44
+ docsRoot: string;
45
+ changedBase?: string;
46
+ /** --changed pedido mas o git não respondeu: checou tudo. */
47
+ changedFallback?: boolean;
48
+ }
49
+ /** Lista os `.md` da pasta de docs (relativos a ela, com `/`). */
50
+ export declare function listDocFiles(repo: ProductRepo): Promise<string[]>;
51
+ /** Lê `docs/_nomes-legados.json` (array ou `{ arquivos: [...] }`). */
52
+ export declare function readLegacyNames(repo: ProductRepo): Set<string>;
53
+ /** Hoje no fuso dos docs (America/Sao_Paulo ou NEETRU_TZ) — nunca UTC. */
54
+ export { todayIso } from './dates.js';
55
+ /** Checagens do repositório inteiro: mínimo do primeiro dia (PR002), README, CHANGELOG (PR004). */
56
+ export declare function checkRepoLevel(repo: ProductRepo): Finding[];
57
+ export declare function summarize(findings: Finding[], strict: boolean): {
58
+ ok: boolean;
59
+ counts: CheckResult['counts'];
60
+ };
61
+ /** Roda o validador no repositório do produto. Nunca lança por conteúdo de doc. */
62
+ export declare function checkProductDocs(repo: ProductRepo, opts: CheckOptions): Promise<CheckResult>;