@orkastery/cli 0.6.0 → 0.6.2

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 (118) hide show
  1. package/adapters/claude-code/agents/ork-check.md +1 -1
  2. package/adapters/claude-code/agents/ork-go.md +1 -1
  3. package/adapters/claude-code/agents/ork-goal.md +1 -1
  4. package/adapters/claude-code/agents/ork-master.md +1 -1
  5. package/adapters/claude-code/agents/ork-plan.md +1 -1
  6. package/adapters/claude-code/agents/ork-ship.md +1 -1
  7. package/adapters/claude-code/commands/ork.md +1 -1
  8. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
  9. package/adapters/codex/skills/ork/SKILL.md +1 -1
  10. package/adapters/openclaw/openclaw.plugin.json +1 -1
  11. package/adapters/openclaw/package.json +1 -1
  12. package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
  13. package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
  14. package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
  15. package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
  16. package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
  17. package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
  18. package/assets/adapters-en/claude-code/commands/ork.md +1 -1
  19. package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
  20. package/assets/orkmind_fixture.py +4 -1
  21. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
  22. package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
  23. package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
  24. package/dist/adapters/claude-bg.js +9 -7
  25. package/dist/adapters/grok-events.js +227 -0
  26. package/dist/adapters/grok-mcp-servidor.js +622 -0
  27. package/dist/adapters/grok-mcp.js +178 -0
  28. package/dist/adapters/grok.js +686 -0
  29. package/dist/agendador-cpu.js +92 -0
  30. package/dist/branch-de-estado.js +7 -2
  31. package/dist/changelog-check.js +217 -0
  32. package/dist/check-agil.js +289 -25
  33. package/dist/check-paralelo.js +73 -18
  34. package/dist/ci.js +3 -2
  35. package/dist/claim-lint.js +29 -0
  36. package/dist/claims.js +4 -0
  37. package/dist/conducao.js +5 -0
  38. package/dist/docs.js +16 -16
  39. package/dist/doctor-forja.js +1 -3
  40. package/dist/doctor.js +1 -1
  41. package/dist/entrega-pr.js +10 -10
  42. package/dist/fabrica-estado.js +20 -11
  43. package/dist/fila-pesada.js +121 -11
  44. package/dist/hitl-classificacao.js +1 -0
  45. package/dist/horario.js +4 -1
  46. package/dist/index.js +63 -35
  47. package/dist/integracoes-locais.js +2 -0
  48. package/dist/intelligence-graph-cli.js +27 -9
  49. package/dist/locale-accounts.js +11 -3
  50. package/dist/locale-ajuda.js +10 -6
  51. package/dist/locale-claims.js +16 -2
  52. package/dist/locale-forja.js +2 -4
  53. package/dist/locale-grafo.js +56 -1
  54. package/dist/locale-maestro.js +38 -0
  55. package/dist/locale-network.js +8 -6
  56. package/dist/locale-onboarding.js +14 -0
  57. package/dist/locale-operacao-restante.js +2 -2
  58. package/dist/locale-preflight.js +80 -0
  59. package/dist/locale-projetos.js +6 -0
  60. package/dist/locale-prompts.js +8 -8
  61. package/dist/locale-pulse.js +4 -0
  62. package/dist/locale-retry.js +56 -2
  63. package/dist/locale-security-scan.js +35 -1
  64. package/dist/locale-ship.js +10 -2
  65. package/dist/locale-verify.js +15 -0
  66. package/dist/locale.js +24 -7
  67. package/dist/maestro-cli.js +12 -17
  68. package/dist/manifest.js +6 -1
  69. package/dist/mcp-install.js +24 -13
  70. package/dist/mcp-server.js +62 -33
  71. package/dist/mcp-verify-async.js +353 -0
  72. package/dist/mcp-verify-worker.js +17 -0
  73. package/dist/mcp-verify.js +10 -1
  74. package/dist/merge-da-entrega.js +85 -0
  75. package/dist/network-roadmap.js +18 -14
  76. package/dist/noctum-aceite.js +154 -14
  77. package/dist/noctum-cli.js +69 -12
  78. package/dist/noctum-entrega.js +55 -7
  79. package/dist/noctum-envelope.js +1 -1
  80. package/dist/noctum-execucao.js +5 -1
  81. package/dist/noctum-fechamento.js +18 -7
  82. package/dist/noctum-fila.js +6 -1
  83. package/dist/noctum-guarda.js +81 -4
  84. package/dist/noctum-maquinas.js +5 -1
  85. package/dist/noctum-modo.js +35 -7
  86. package/dist/noctum-reconciliacao.js +4 -1
  87. package/dist/noctum-rede.js +102 -0
  88. package/dist/noctum-relatorio.js +44 -10
  89. package/dist/noctum-retomada-rede.js +71 -0
  90. package/dist/noctum-vigia.js +49 -9
  91. package/dist/parado-no-condutor.js +14 -15
  92. package/dist/pedido-conducao.js +51 -0
  93. package/dist/phase.js +43 -15
  94. package/dist/postura-local.js +7 -3
  95. package/dist/preflight.js +27 -27
  96. package/dist/prompts.js +4 -2
  97. package/dist/pulse.js +4 -2
  98. package/dist/ratelimit.js +1 -0
  99. package/dist/rede.js +24 -12
  100. package/dist/retry.js +115 -10
  101. package/dist/runtime-context.js +165 -45
  102. package/dist/runtime-escada.js +7 -1
  103. package/dist/runtime-profiles.js +27 -17
  104. package/dist/runtimes.js +42 -5
  105. package/dist/security-scan/agil.js +18 -7
  106. package/dist/security-scan/cli.js +2 -2
  107. package/dist/security-scan/executor.js +8 -0
  108. package/dist/security-scan/report.js +1 -1
  109. package/dist/session-watcher-grok.js +425 -0
  110. package/dist/session-watcher.js +52 -11
  111. package/dist/sessoes-contas.js +33 -5
  112. package/dist/setup.js +11 -11
  113. package/dist/varredura-acoes.js +8 -15
  114. package/dist/verify.js +191 -43
  115. package/package.json +2 -2
  116. package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
  117. package/skills/phases/check-quality/SKILL.md +1 -1
  118. package/skills/phases/go-implementation/SKILL.md +1 -1
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JANELA_DA_SONDA_MS = exports.LIMIAR_STEAL_DO_AGENDADOR_PCT = exports.RESERVA_DO_GRUPO = exports.ENV_CPUS_DOCKER = exports.ENV_CPUS_DA_LANE = void 0;
4
+ exports.cargaDoProcLoadavg = cargaDoProcLoadavg;
5
+ exports.medirCpuDoGrupo = medirCpuDoGrupo;
6
+ exports.planejarCpuDoGrupo = planejarCpuDoGrupo;
7
+ exports.paredeClassicaSimulada = paredeClassicaSimulada;
8
+ exports.paredeAgileSimulada = paredeAgileSimulada;
9
+ /**
10
+ * RM-074: o plano de CPU do grupo do Agile Check.
11
+ *
12
+ * A lane hermetica deixa duas CPUs para a varredura e para a integracao. Cada conteiner recebe uma cota dessa fatia,
13
+ * em vez de enxergar a maquina inteira. Antes de lancar os comandos, o auxiliar do verify mede `/proc/loadavg` e uma
14
+ * janela de `/proc/stat`; sob carga ou steal, as pecas voltam a ordem do Classic e usam uma CPU a menos que a maquina.
15
+ */
16
+ const node_fs_1 = require("node:fs");
17
+ const steal_1 = require("./steal");
18
+ exports.ENV_CPUS_DA_LANE = 'ORK_CHECK_CPUS_DA_LANE';
19
+ exports.ENV_CPUS_DOCKER = 'ORK_CHECK_CPUS_DOCKER';
20
+ exports.RESERVA_DO_GRUPO = 2;
21
+ exports.LIMIAR_STEAL_DO_AGENDADOR_PCT = 10;
22
+ exports.JANELA_DA_SONDA_MS = 250;
23
+ const cpusValidas = (cpus) => Math.max(1, Math.floor(Number.isFinite(cpus) ? cpus : 1));
24
+ /** A carga de um minuto de `/proc/loadavg`; entrada invalida ou ausencia nao inventam saturacao. */
25
+ function cargaDoProcLoadavg(conteudo) {
26
+ if (!conteudo)
27
+ return null;
28
+ const carga = Number(conteudo.trim().split(/\s+/)[0]);
29
+ return Number.isFinite(carga) && carga >= 0 ? carga : null;
30
+ }
31
+ function lerProc(arquivo) {
32
+ try {
33
+ return (0, node_fs_1.readFileSync)(arquivo, 'utf8');
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /**
40
+ * Mede a maquina dentro do proprio grupo, antes de qualquer comando: a carga nas duas pontas e o steal da janela.
41
+ * A leitura maior da carga impede uma subida durante a sonda de desaparecer na media de um minuto.
42
+ */
43
+ async function medirCpuDoGrupo(janelaMs = exports.JANELA_DA_SONDA_MS) {
44
+ const statInicial = (0, steal_1.amostraDoProcStat)(lerProc('/proc/stat'));
45
+ const cargaInicial = cargaDoProcLoadavg(lerProc('/proc/loadavg'));
46
+ await new Promise((resolve) => setTimeout(resolve, janelaMs));
47
+ const cargaFinal = cargaDoProcLoadavg(lerProc('/proc/loadavg'));
48
+ const cargas = [cargaInicial, cargaFinal].filter((x) => x !== null);
49
+ return {
50
+ carga1: cargas.length ? Math.max(...cargas) : null,
51
+ stealPct: (0, steal_1.stealDaJanela)(statInicial, (0, steal_1.amostraDoProcStat)(lerProc('/proc/stat'))),
52
+ };
53
+ }
54
+ /** O plano unico que alimenta concorrencia das lanes, varredura e `docker run --cpus`. */
55
+ function planejarCpuDoGrupo(cpusInformadas, medicao) {
56
+ const cpus = cpusValidas(cpusInformadas);
57
+ const classic = Math.max(1, cpus - 1);
58
+ const motivos = [];
59
+ if (cpus < 3)
60
+ motivos.push(`${cpus} CPU(s), sem duas CPUs livres para o grupo paralelo`);
61
+ if (medicao.carga1 !== null && medicao.carga1 >= classic)
62
+ motivos.push(`carga ${medicao.carga1} em /proc/loadavg (limiar ${classic})`);
63
+ if (medicao.stealPct !== null && medicao.stealPct >= exports.LIMIAR_STEAL_DO_AGENDADOR_PCT) {
64
+ motivos.push(`steal ${medicao.stealPct}% em /proc/stat (limiar ${exports.LIMIAR_STEAL_DO_AGENDADOR_PCT}%)`);
65
+ }
66
+ const saturado = motivos.length > 0;
67
+ const fatia = saturado ? classic : Math.min(exports.RESERVA_DO_GRUPO, cpus - 1);
68
+ const ferramentas = Math.max(1, Math.floor(fatia));
69
+ return {
70
+ cpus,
71
+ ordem: saturado ? 'classic' : 'paralela',
72
+ saturado,
73
+ motivo: motivos.join('; ') || `carga e steal abaixo dos limiares; ${fatia} CPU(s) reservadas ao grupo`,
74
+ cpusHermetica: saturado ? classic : Math.max(1, cpus - fatia),
75
+ cpusIntegracao: ferramentas,
76
+ cpusDaFatia: fatia,
77
+ ferramentasDaVarredura: ferramentas,
78
+ cpusPorContainer: Math.max(0.01, Math.floor((fatia / ferramentas) * 100) / 100),
79
+ medicao,
80
+ };
81
+ }
82
+ function paredeClassicaSimulada(cpus, trabalho) {
83
+ const concorrencia = Math.max(1, cpusValidas(cpus) - 1);
84
+ return (trabalho.hermetica + trabalho.varredura + trabalho.integracao) / concorrencia;
85
+ }
86
+ function paredeAgileSimulada(plano, trabalho) {
87
+ if (plano.ordem === 'classic') {
88
+ return trabalho.hermetica / plano.cpusHermetica + trabalho.varredura / plano.cpusDaFatia
89
+ + trabalho.integracao / plano.cpusIntegracao;
90
+ }
91
+ return Math.max(trabalho.hermetica / plano.cpusHermetica, trabalho.varredura / plano.cpusDaFatia + trabalho.integracao / plano.cpusIntegracao);
92
+ }
@@ -192,12 +192,17 @@ function gravarNaBranch(raiz, remoto, branch, ponta, mudancas, mensagem, prefixo
192
192
  exigirGit(raiz, ['read-tree', ponta], prefixo, { env });
193
193
  else
194
194
  exigirGit(raiz, ['read-tree', '--empty'], prefixo, { env });
195
- for (const m of mudancas) {
195
+ for (const [i, m] of mudancas.entries()) {
196
196
  if (m.conteudo === null) {
197
197
  exigirGit(raiz, ['update-index', '--force-remove', m.caminho], prefixo, { env });
198
198
  }
199
199
  else {
200
- const blob = exigirGit(raiz, ['hash-object', '-w', '--stdin'], prefixo, { input: m.conteudo }).trim();
200
+ // `spawnSync` nem sempre fecha o pipe de entrada em runtimes hospedados, deixando o
201
+ // `hash-object --stdin` preso mesmo depois de receber todos os bytes. Um arquivo privado no
202
+ // mesmo diretorio temporario conserva o conteudo exato e torna o termino deterministico.
203
+ const conteudo = path.join(path.dirname(indice), `conteudo-${i}`);
204
+ fs.writeFileSync(conteudo, m.conteudo, { mode: 0o600 });
205
+ const blob = exigirGit(raiz, ['hash-object', '-w', conteudo], prefixo).trim();
201
206
  exigirGit(raiz, ['update-index', '--add', '--cacheinfo', `100644,${blob},${m.caminho}`], prefixo, { env });
202
207
  }
203
208
  }
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GRUPOS_NAO_PUBLICADOS = exports.SECAO_NAO_PUBLICADO = exports.CHANGELOG = void 0;
4
+ exports.ehTeste = ehTeste;
5
+ exports.pedeLinha = pedeLinha;
6
+ exports.linhasDaSecao = linhasDaSecao;
7
+ exports.versoes = versoes;
8
+ exports.changelogAplicavel = changelogAplicavel;
9
+ exports.checarChangelog = checarChangelog;
10
+ exports.basePadrao = basePadrao;
11
+ /**
12
+ * RM-037: regra unica do CHANGELOG para o script do CI, `docs verificar --pr` e `ork verify`.
13
+ * O diff decide quais linhas a thread acrescentou; o texto do HEAD decide em que secao e grupo
14
+ * cada linha caiu; uma tag local `v<versao>` prova que a secao ja foi publicada.
15
+ */
16
+ const node_child_process_1 = require("node:child_process");
17
+ exports.CHANGELOG = 'CHANGELOG.md';
18
+ exports.SECAO_NAO_PUBLICADO = 'Não publicado';
19
+ exports.GRUPOS_NAO_PUBLICADOS = ['Adicionado', 'Alterado', 'Corrigido', 'Segurança'];
20
+ const PASTAS_DE_PRODUTO = ['core/', 'adapters/', 'marketplaces/'];
21
+ const DE_CI = [/^\.github\//, /^\.ork-ci\//, /^core\/scripts\/test-ci\.js$/, /^core\/scripts\/checar-[a-z0-9-]+\.cjs$/];
22
+ const CORRECAO = 'acrescente em "## Não publicado" do CHANGELOG.md uma linha que diga o que muda para quem usa, ' +
23
+ 'sob "### Adicionado", "### Alterado", "### Corrigido" ou "### Segurança" ' +
24
+ '(PR só de testes ou só de CI fica de fora: docs/guias/contribuir/pull-request.md)';
25
+ function git(raiz, args) {
26
+ const r = (0, node_child_process_1.spawnSync)('git', args, { cwd: raiz, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
27
+ return { ok: r.status === 0, saida: r.stdout ?? '' };
28
+ }
29
+ /** Arquivo de teste: pasta `test`, `tests` ou `__tests__`, ou nome `*.test.*`/`*.spec.*`. */
30
+ function ehTeste(arquivo) {
31
+ const partes = arquivo.split('/');
32
+ return partes.slice(0, -1).some((p) => p === 'test' || p === 'tests' || p === '__tests__')
33
+ || /\.(test|spec)\.[A-Za-z0-9]+$/.test(partes[partes.length - 1]);
34
+ }
35
+ /** Arquivo de produto que exige uma entrada no CHANGELOG. */
36
+ function pedeLinha(arquivo) {
37
+ return PASTAS_DE_PRODUTO.some((p) => arquivo.startsWith(p)) && !ehTeste(arquivo) && !DE_CI.some((re) => re.test(arquivo));
38
+ }
39
+ /** Linhas de texto de `## Não publicado`, mantida para consumidores e testes legados. */
40
+ function linhasDaSecao(texto) {
41
+ const linhas = texto.replace(/\r\n/g, '\n').split('\n');
42
+ const inicio = linhas.findIndex((linha) => linha.trim() === `## ${exports.SECAO_NAO_PUBLICADO}`);
43
+ if (inicio < 0)
44
+ return null;
45
+ const corpo = [];
46
+ for (const linha of linhas.slice(inicio + 1)) {
47
+ if (/^## /.test(linha))
48
+ break;
49
+ if (linha.trim() && !/^#/.test(linha.trim()))
50
+ corpo.push(linha.trimEnd());
51
+ }
52
+ return corpo;
53
+ }
54
+ /** Versoes nos dois formatos historicos do arquivo. */
55
+ function versoes(texto) {
56
+ return new Set([...texto.replace(/\r\n/g, '\n').matchAll(/^## \[?(\d+\.\d+\.\d+[0-9A-Za-z.+-]*)\]?(?=\s|$)/gm)]
57
+ .map((m) => m[1]));
58
+ }
59
+ function changelogEm(raiz, ref) {
60
+ const r = git(raiz, ['show', `${ref}:${exports.CHANGELOG}`]);
61
+ return r.ok ? r.saida : '';
62
+ }
63
+ /**
64
+ * A regra protege um histórico publicado. Repositórios ainda sem versão `v*` ou sem
65
+ * CHANGELOG no HEAD não têm esse contrato para comparar e ficam fora do verificador.
66
+ */
67
+ function changelogAplicavel(raiz, head = 'HEAD') {
68
+ const arquivo = git(raiz, ['cat-file', '-e', `${head}:${exports.CHANGELOG}`]);
69
+ if (!arquivo.ok)
70
+ return false;
71
+ const tags = git(raiz, ['tag', '--merged', head, '--list', 'v*']);
72
+ return tags.ok && tags.saida.split('\n').some((tag) => tag.trim().length > 1);
73
+ }
74
+ /** Linhas acrescentadas no arquivo, com o numero que ocupam no HEAD. */
75
+ function linhasAcrescentadas(raiz, base, head) {
76
+ const d = git(raiz, ['diff', '--no-color', '--unified=0', '--no-renames', base, head, '--', exports.CHANGELOG]);
77
+ if (!d.ok)
78
+ return [];
79
+ const novas = [];
80
+ let numero = 0, emTrecho = false;
81
+ for (const linha of d.saida.replace(/\r\n/g, '\n').split('\n')) {
82
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(linha);
83
+ if (hunk) {
84
+ numero = Number(hunk[1]);
85
+ emTrecho = true;
86
+ continue;
87
+ }
88
+ if (!emTrecho || linha.startsWith('\\ No newline'))
89
+ continue;
90
+ if (linha.startsWith('+')) {
91
+ novas.push({ numero, texto: linha.slice(1) });
92
+ numero++;
93
+ }
94
+ else if (!linha.startsWith('-'))
95
+ numero++;
96
+ }
97
+ return novas;
98
+ }
99
+ function contextos(texto) {
100
+ const porLinha = [{ secao: null, grupo: null, versao: null }];
101
+ let secao = null, grupo = null, versao = null;
102
+ for (const linha of texto.replace(/\r\n/g, '\n').split('\n')) {
103
+ const h2 = /^## (.+?)\s*$/.exec(linha);
104
+ if (h2) {
105
+ secao = h2[1];
106
+ grupo = null;
107
+ versao = /^\[?(\d+\.\d+\.\d+[0-9A-Za-z.+-]*)\]?(?:\s|$)/.exec(secao)?.[1] ?? null;
108
+ }
109
+ else {
110
+ const h3 = /^### (.+?)\s*$/.exec(linha);
111
+ if (h3)
112
+ grupo = h3[1];
113
+ }
114
+ porLinha.push({ secao, grupo, versao });
115
+ }
116
+ return porLinha;
117
+ }
118
+ function tagPublicada(raiz, versao) {
119
+ return git(raiz, ['rev-parse', '--verify', '--quiet', `refs/tags/v${versao}^{commit}`]).ok;
120
+ }
121
+ /**
122
+ * Confere o diff da thread ate `head`.
123
+ *
124
+ * A referencia local da branch alvo pode estar atrasada em relacao ao commit que abriu a
125
+ * worktree. Quando a base carimbada tambem e ancestral do HEAD e vem depois da merge-base,
126
+ * ela delimita o que a thread de fato acrescentou. Se a branch alvo avancou para alem dela,
127
+ * a merge-base mais nova continua vencendo.
128
+ */
129
+ function checarChangelog(raiz, opcoes = {}) {
130
+ const head = opcoes.head ?? 'HEAD', base = opcoes.base;
131
+ const indisponivel = (detalhe) => ({ ok: false, motivo: 'changelog.base-indisponivel', detalhe,
132
+ correcao: 'rode com o histórico completo do git (fetch-depth: 0 no CI) ou passe --base <ref>', arquivos: [], linhas: [] });
133
+ if (!git(raiz, ['rev-parse', '--verify', '--quiet', `${head}^{commit}`]).ok)
134
+ return indisponivel(`o head ${head} não existe neste repositório`);
135
+ if (!changelogAplicavel(raiz, head)) {
136
+ const temArquivo = git(raiz, ['cat-file', '-e', `${head}:${exports.CHANGELOG}`]).ok;
137
+ return { ok: true, motivo: null, arquivos: [], linhas: [], detalhe: temArquivo
138
+ ? 'regra do CHANGELOG não aplicável: o repositório ainda não tem versão publicada em tag v*'
139
+ : `regra do CHANGELOG não aplicável: ${exports.CHANGELOG} não existe em ${head}` };
140
+ }
141
+ if (!base)
142
+ return indisponivel('nenhuma base para comparar');
143
+ if (!git(raiz, ['rev-parse', '--verify', '--quiet', `${base}^{commit}`]).ok)
144
+ return indisponivel(`a base ${base} não existe neste repositório`);
145
+ const mb = git(raiz, ['merge-base', base, head]);
146
+ let ancestral = mb.saida.trim();
147
+ if (!mb.ok || !ancestral)
148
+ return indisponivel(`${base} e ${head} não têm ancestral comum`);
149
+ const carimbada = opcoes.baseCarimbada?.trim();
150
+ if (carimbada
151
+ && git(raiz, ['rev-parse', '--verify', '--quiet', `${carimbada}^{commit}`]).ok
152
+ && git(raiz, ['merge-base', '--is-ancestor', carimbada, head]).ok
153
+ && git(raiz, ['merge-base', '--is-ancestor', ancestral, carimbada]).ok) {
154
+ ancestral = carimbada;
155
+ }
156
+ const mudados = git(raiz, ['diff', '--name-only', '--no-renames', '-z', ancestral, head]);
157
+ const arquivos = (mudados.ok ? mudados.saida.split('\0') : []).filter(Boolean);
158
+ const pedem = arquivos.filter(pedeLinha);
159
+ const antes = changelogEm(raiz, ancestral), depois = changelogEm(raiz, head);
160
+ const novas = linhasAcrescentadas(raiz, ancestral, head);
161
+ const porLinha = contextos(depois);
162
+ // D4: uma entrada correta em Nao publicado nunca esconde uma edicao retrospectiva.
163
+ for (const linha of novas) {
164
+ if (!linha.texto.trim())
165
+ continue;
166
+ const contexto = porLinha[linha.numero] ?? { secao: null, grupo: null, versao: null };
167
+ if (contexto.versao && tagPublicada(raiz, contexto.versao)) {
168
+ return { ok: false, motivo: 'changelog.secao-publicada', arquivos: pedem, linhas: [linha.texto], secao: contexto.secao ?? undefined,
169
+ detalhe: `a linha nova ${linha.numero} caiu em "## ${contexto.secao}", versão já publicada pela tag v${contexto.versao}`,
170
+ correcao: `mova a linha para "## ${exports.SECAO_NAO_PUBLICADO}", sob um dos grupos permitidos` };
171
+ }
172
+ }
173
+ const entradasNaoPublicadas = novas.filter((linha) => {
174
+ const t = linha.texto.trim(), contexto = porLinha[linha.numero];
175
+ return !!t && !/^#{2,3} /.test(t) && contexto?.secao === exports.SECAO_NAO_PUBLICADO;
176
+ });
177
+ for (const linha of entradasNaoPublicadas) {
178
+ const grupo = porLinha[linha.numero]?.grupo ?? null;
179
+ if (!grupo)
180
+ return { ok: false, motivo: 'changelog.grupo-ausente', arquivos: pedem, linhas: [linha.texto], grupo,
181
+ detalhe: `a linha nova ${linha.numero} de "## ${exports.SECAO_NAO_PUBLICADO}" não está sob um título ###`,
182
+ correcao: `ponha a linha sob ${exports.GRUPOS_NAO_PUBLICADOS.map((g) => `"### ${g}"`).join(', ')}` };
183
+ if (!exports.GRUPOS_NAO_PUBLICADOS.includes(grupo)) {
184
+ return { ok: false, motivo: 'changelog.grupo-invalido', arquivos: pedem, linhas: [linha.texto], grupo,
185
+ detalhe: `a linha nova ${linha.numero} está sob "### ${grupo}", grupo que não vale em "## ${exports.SECAO_NAO_PUBLICADO}"`,
186
+ correcao: `use ${exports.GRUPOS_NAO_PUBLICADOS.map((g) => `"### ${g}"`).join(', ')}` };
187
+ }
188
+ }
189
+ if (pedem.length === 0)
190
+ return { ok: true, motivo: null, arquivos: [], linhas: [],
191
+ detalhe: arquivos.length === 0 ? `nada a conferir: ${head} não muda nada desde ${base}`
192
+ : 'nada pede linha: o PR não muda core/, adapters/ nem marketplaces/ fora de teste e de CI' };
193
+ const versaoNova = [...versoes(depois)].filter((v) => !versoes(antes).has(v));
194
+ if (versaoNova.length > 0)
195
+ return { ok: true, motivo: null, arquivos: pedem, linhas: [],
196
+ detalhe: `versão nova no ${exports.CHANGELOG}: ${versaoNova.join(', ')} (o PR de versão leva as linhas para ela)` };
197
+ if (linhasDaSecao(depois) === null)
198
+ return { ok: false, motivo: 'changelog.secao-ausente', arquivos: pedem, linhas: [],
199
+ detalhe: `o ${exports.CHANGELOG} de ${head} não tem a seção "## ${exports.SECAO_NAO_PUBLICADO}"`,
200
+ correcao: `abra a seção "## ${exports.SECAO_NAO_PUBLICADO}" no alto do ${exports.CHANGELOG} e acrescente a linha da mudança` };
201
+ if (entradasNaoPublicadas.length > 0)
202
+ return { ok: true, motivo: null, arquivos: pedem,
203
+ linhas: entradasNaoPublicadas.map((linha) => linha.texto),
204
+ detalhe: `${entradasNaoPublicadas.length} linha(s) nova(s) em "## ${exports.SECAO_NAO_PUBLICADO}" sob grupo permitido` };
205
+ return { ok: false, motivo: 'changelog.linha-ausente', arquivos: pedem, linhas: [],
206
+ detalhe: `o PR muda ${pedem.length} arquivo(s) de core/, adapters/ ou marketplaces/ (${pedem.slice(0, 3).join(', ')}${pedem.length > 3 ? ', ...' : ''}) e não acrescenta linha válida em "## ${exports.SECAO_NAO_PUBLICADO}" do ${exports.CHANGELOG}`,
207
+ correcao: CORRECAO };
208
+ }
209
+ /** Base padrao do CI: alvo do PR, main remota, main local. */
210
+ function basePadrao(raiz, env = process.env) {
211
+ if (env.GITHUB_BASE_REF)
212
+ return `origin/${env.GITHUB_BASE_REF}`;
213
+ for (const ref of ['origin/main', 'main'])
214
+ if (git(raiz, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]).ok)
215
+ return ref;
216
+ return null;
217
+ }