@orkastery/cli 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/adapters/claude-code/README.md +1 -1
  2. package/adapters/claude-code/agents/ork-check.md +1 -1
  3. package/adapters/claude-code/agents/ork-go.md +1 -1
  4. package/adapters/claude-code/agents/ork-goal.md +1 -1
  5. package/adapters/claude-code/agents/ork-master.md +1 -1
  6. package/adapters/claude-code/agents/ork-plan.md +1 -1
  7. package/adapters/claude-code/agents/ork-ship.md +1 -1
  8. package/adapters/claude-code/commands/ork.md +1 -1
  9. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
  10. package/adapters/codex/skills/ork/SKILL.md +1 -1
  11. package/adapters/hermes/README.md +1 -1
  12. package/adapters/openclaw/openclaw.plugin.json +1 -1
  13. package/adapters/openclaw/package.json +1 -1
  14. package/assets/adapters-en/claude-code/README.md +1 -1
  15. package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
  16. package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
  17. package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
  18. package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
  19. package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
  20. package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
  21. package/assets/adapters-en/claude-code/commands/ork.md +1 -1
  22. package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
  23. package/assets/adapters-en/hermes/README.md +1 -1
  24. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  25. package/assets/docs-en/padroes/roadmap-de-produto.md +1 -1
  26. package/assets/orkmind_fixture.py +4 -1
  27. package/assets/references-en/testing-patterns.md +1 -1
  28. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
  29. package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
  30. package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
  31. package/dist/adapters/claude-bg.js +3 -1
  32. package/dist/adapters/grok-mcp-servidor.js +22 -12
  33. package/dist/adapters/grok.js +1 -1
  34. package/dist/agendador-cpu.js +10 -2
  35. package/dist/catalogo.js +3 -3
  36. package/dist/{canarios-hitl.js → cenarios-hitl.js} +7 -7
  37. package/dist/{canarios-i43.js → cenarios-i43.js} +16 -16
  38. package/dist/{canarios-pulse.js → cenarios-pulse.js} +3 -3
  39. package/dist/{canarios-rm057.js → cenarios-rm057.js} +3 -3
  40. package/dist/{canarios-sensores.js → cenarios-sensores.js} +5 -5
  41. package/dist/{canarios.js → cenarios.js} +22 -22
  42. package/dist/changelog-check.js +217 -0
  43. package/dist/check-agil.js +242 -5
  44. package/dist/check-paralelo.js +17 -4
  45. package/dist/ci.js +3 -2
  46. package/dist/claim-lint.js +29 -0
  47. package/dist/claims.js +5 -1
  48. package/dist/docs.js +8 -1
  49. package/dist/evalrunner.js +37 -37
  50. package/dist/fila-pesada.js +121 -11
  51. package/dist/hitl-canais.js +1 -1
  52. package/dist/hitl-classificacao.js +1 -0
  53. package/dist/hitl-presentation.js +1 -1
  54. package/dist/hitl-tempo-parado.js +1 -1
  55. package/dist/index.js +47 -29
  56. package/dist/init.js +1 -1
  57. package/dist/intelligence-graph-cli.js +27 -9
  58. package/dist/locale-ajuda.js +6 -6
  59. package/dist/locale-claims.js +16 -2
  60. package/dist/locale-grafo.js +56 -1
  61. package/dist/locale-maestro.js +38 -0
  62. package/dist/locale-network.js +8 -6
  63. package/dist/locale-onboarding.js +14 -0
  64. package/dist/locale-operacao-restante.js +18 -18
  65. package/dist/locale-preflight.js +80 -0
  66. package/dist/locale-projetos.js +6 -0
  67. package/dist/locale-prompts.js +8 -8
  68. package/dist/locale-pulse.js +4 -0
  69. package/dist/locale-retry.js +16 -0
  70. package/dist/locale-security-scan.js +35 -1
  71. package/dist/locale-verify.js +15 -0
  72. package/dist/locale.js +24 -7
  73. package/dist/maestro-cli.js +12 -17
  74. package/dist/mcp-install.js +2 -1
  75. package/dist/mcp-server.js +20 -17
  76. package/dist/mcp-verify-async.js +353 -0
  77. package/dist/mcp-verify-worker.js +17 -0
  78. package/dist/mcp-verify.js +10 -1
  79. package/dist/pedido-conducao.js +51 -0
  80. package/dist/preflight.js +25 -26
  81. package/dist/prompts.js +4 -2
  82. package/dist/pulse.js +4 -2
  83. package/dist/rede.js +24 -12
  84. package/dist/retry.js +12 -0
  85. package/dist/sandbox.js +11 -11
  86. package/dist/security-scan/cli.js +2 -2
  87. package/dist/security-scan/report.js +1 -1
  88. package/dist/session-watcher.js +33 -7
  89. package/dist/verify.js +166 -34
  90. package/eval/README.md +8 -8
  91. package/eval/fixtures/fx-codex-dry/caso.json +1 -1
  92. package/eval/fixtures/fx-happy/caso.json +1 -1
  93. package/eval/fixtures/fx-indice-reversao/caso.json +1 -1
  94. package/eval/fixtures/fx-listagem-abertas/caso.json +1 -1
  95. package/eval/fixtures/fx-omnicanal/caso.json +1 -1
  96. package/eval/fixtures/fx-pedido-colado/caso.json +1 -1
  97. package/eval/fixtures/fx-schema-drift/caso.json +1 -1
  98. package/package.json +1 -1
  99. package/references/testing-patterns.md +1 -1
  100. package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
  101. package/skills/phases/check-quality/SKILL.md +1 -1
  102. package/skills/phases/go-implementation/SKILL.md +1 -1
@@ -38,6 +38,7 @@ exports.paralelismoDaVarreduraNoGrupo = paralelismoDaVarreduraNoGrupo;
38
38
  exports.passosDoComando = passosDoComando;
39
39
  exports.elosDoComando = elosDoComando;
40
40
  exports.tirarPassosDoPreparo = tirarPassosDoPreparo;
41
+ exports.tirarPassosDoPreparoAposLeituras = tirarPassosDoPreparoAposLeituras;
41
42
  exports.hashDoProduto = hashDoProduto;
42
43
  exports.prepararNoAgile = prepararNoAgile;
43
44
  exports.executorDaClaimNoAgile = executorDaClaimNoAgile;
@@ -47,6 +48,9 @@ exports.porQueDasLanes = porQueDasLanes;
47
48
  exports.comandosNoAgile = comandosNoAgile;
48
49
  exports.pecasDoRelatorio = pecasDoRelatorio;
49
50
  exports.listasDasLanes = listasDasLanes;
51
+ exports.resultadosDasLanes = resultadosDasLanes;
52
+ exports.assinaturaDoAmbiente = assinaturaDoAmbiente;
53
+ exports.executorDaClaimPelasLanes = executorDaClaimPelasLanes;
50
54
  /**
51
55
  * RM-072 (D3 a D8 da ork-check5x): o motor do Agile Check no verify.
52
56
  *
@@ -54,12 +58,16 @@ exports.listasDasLanes = listasDasLanes;
54
58
  *
55
59
  * - o preparo do Agile (`check.agile.preparo`, como o `tsgo` deste projeto) e, se ele falhar ou nao existir, o
56
60
  * `verify.preparo`, com a peca `preparo` em fallback;
57
- * - as claims, uma por vez, contra o produto do preparo: a claim que comeca pelos passos do `verify.preparo` roda sem
61
+ * - quando ha lanes, elas rodam antes das claims; `node --test` dos mesmos arquivos, HEAD e ambiente usa o resultado
62
+ * nominal da lane, enquanto comando aberto ou evidencia divergente roda inteiro; se uma claim posterior mudar o
63
+ * produto, preparo e grupo rodam outra vez sobre o produto final;
64
+ * - as demais claims, uma por vez, contra o produto do preparo: a claim que comeca pelos passos do `verify.preparo` roda sem
58
65
  * eles quando a guarda vale (o preparo passou, a arvore e a mesma do fim do preparo e o hash dos caminhos de
59
66
  * `check.agile.produto` tambem); qualquer divergencia roda o comando inteiro, e o motivo vai ao ledger. Passo no meio
60
- * da cadeia fica: o que vem antes dele pode mudar a entrada do build (o mutante que edita o fonte e compila). Quando
67
+ * da cadeia so sai quando tudo antes dele e uma leitura `grep -q` fechada; qualquer outro elo pode mudar a entrada do
68
+ * build (como o mutante que edita o fonte e compila), entao a cadeia fica inteira. Quando
61
69
  * so o produto mudou (a claim anterior compilou de novo no meio da cadeia), o preparo roda de novo antes da proxima
62
- * claim que o reaproveitaria, ate `MAXIMO_DE_PREPAROS_REFEITOS` vezes, e uma vez antes do grupo, com a peca
70
+ * claim que o reaproveitaria, ate `MAXIMO_DE_PREPAROS_REFEITOS` vezes, e uma vez depois das claims se elas mudarem o produto, com a peca
63
71
  * `preparo-refeito`;
64
72
  * - o `build` que e so passos do preparo, com a guarda valendo, sai do preparo desta rodada sem rodar de novo;
65
73
  * - `typecheck`, as lanes de `test` (`check.agile.test`, ou o `verify.test`) e o `security_scan` num grupo paralelo, so
@@ -157,6 +165,78 @@ function tirarPassosDoPreparo(comando, passosDoPreparo) {
157
165
  }
158
166
  return { passos, resto };
159
167
  }
168
+ /** Palavras de um comando simples, sem operadores ou expansoes do shell. */
169
+ function palavrasDoComando(comando) {
170
+ const palavras = [];
171
+ let atual = '', aspas = null, escape = false;
172
+ const fechar = () => { if (atual !== '')
173
+ palavras.push(atual); atual = ''; };
174
+ for (const c of comando) {
175
+ if (escape) {
176
+ atual += c;
177
+ escape = false;
178
+ continue;
179
+ }
180
+ if (c === '\\' && aspas !== "'") {
181
+ escape = true;
182
+ continue;
183
+ }
184
+ if (aspas) {
185
+ if (c === aspas)
186
+ aspas = null;
187
+ else if (aspas === '"' && (c === '$' || c === '`'))
188
+ return null;
189
+ else
190
+ atual += c;
191
+ continue;
192
+ }
193
+ if (c === "'" || c === '"') {
194
+ aspas = c;
195
+ continue;
196
+ }
197
+ if (/\s/.test(c)) {
198
+ fechar();
199
+ continue;
200
+ }
201
+ if (';&|<>$`(){}'.includes(c))
202
+ return null;
203
+ atual += c;
204
+ }
205
+ if (escape || aspas)
206
+ return null;
207
+ fechar();
208
+ return palavras;
209
+ }
210
+ /** Uma leitura sem efeito colateral aceita antes do preparo unico. */
211
+ function comandoGrepQ(comando) {
212
+ const palavras = palavrasDoComando(comando);
213
+ if (!palavras || palavras[0] !== 'grep' || palavras[1] !== '-q')
214
+ return false;
215
+ const argumentos = palavras[2] === '--' ? palavras.slice(3) : palavras.slice(2);
216
+ return argumentos.length === 2 && argumentos[0] !== '' && argumentos[1] !== '' && !argumentos[1].startsWith('-');
217
+ }
218
+ /**
219
+ * Tira passos exatos do preparo que aparecem depois apenas de `grep -q`. A leitura nao altera a entrada do build;
220
+ * qualquer outro elo antes do preparo conserva a regra antiga e faz a cadeia rodar inteira.
221
+ */
222
+ function tirarPassosDoPreparoAposLeituras(comando, passosDoPreparo) {
223
+ const elos = elosDoComando(comando);
224
+ if (!elos)
225
+ return { passos: [], resto: comando.trim() };
226
+ const passos = [];
227
+ const resto = [];
228
+ let somenteLeiturasAntes = true;
229
+ for (const elo of elos) {
230
+ if (somenteLeiturasAntes && passosDoPreparo.includes(elo)) {
231
+ passos.push(elo);
232
+ continue;
233
+ }
234
+ resto.push(elo);
235
+ if (!comandoGrepQ(elo))
236
+ somenteLeiturasAntes = false;
237
+ }
238
+ return passos.length > 0 ? { passos, resto: resto.join(SEPARADOR) } : { passos: [], resto: comando.trim() };
239
+ }
160
240
  /**
161
241
  * O sha256 do conteudo dos caminhos do produto (arquivos em ordem, com o caminho relativo). Caminho ausente entra como
162
242
  * ausente, para o produto que some nao passar por igual; link simbolico entra pelo destino que ele aponta, sem segui-lo.
@@ -256,7 +336,8 @@ function doPreparo(nome, comando, preparo, passos) {
256
336
  function executorDaClaimNoAgile(manifesto, executor, vigente, refazer) {
257
337
  const passosDoPreparo = manifesto.verify.preparo ? passosDoComando(manifesto.verify.preparo) : [];
258
338
  return (nome, comando, cwd) => {
259
- const { passos, resto } = tirarPassosDoPreparo(comando, passosDoPreparo);
339
+ const daFrente = tirarPassosDoPreparo(comando, passosDoPreparo);
340
+ const { passos, resto } = daFrente.passos.length > 0 ? daFrente : tirarPassosDoPreparoAposLeituras(comando, passosDoPreparo);
260
341
  if (passos.length === 0)
261
342
  return executor(nome, comando, cwd);
262
343
  let motivo = vigente.guarda.conferir();
@@ -353,7 +434,8 @@ function testDasLanes(lanes) {
353
434
  ...(lanes.some((l) => l.resultado.stealPct !== undefined) ? { stealPct: lanes.find((l) => l.resultado.stealPct !== undefined).resultado.stealPct } : {}),
354
435
  ...(relogio.length ? { relogioSobSteal: relogio } : {}),
355
436
  lanes: lanes.map((l) => ({ lane: l.lane, comando: l.resultado.comando, ok: l.resultado.ok, code: l.resultado.code,
356
- ...(l.resultado.causa ? { causa: l.resultado.causa } : {}), duracaoMs: l.resultado.duracaoMs ?? 0 })),
437
+ ...(l.resultado.causa ? { causa: l.resultado.causa } : {}), duracaoMs: l.resultado.duracaoMs ?? 0,
438
+ ...(l.resultado.testeQueCaiu?.length ? { testeQueCaiu: l.resultado.testeQueCaiu } : {}) })),
357
439
  };
358
440
  }
359
441
  /**
@@ -526,3 +608,158 @@ function listasDasLanes(linhas) {
526
608
  return { lane: l.lane, arquivos, sha256: (0, node_crypto_1.createHash)('sha256').update(arquivos.join('\n')).digest('hex') };
527
609
  });
528
610
  }
611
+ /** Evidencia nominal produzida pelo reporter, sem confiar em campos abertos do JSONL. */
612
+ function resultadosDasLanes(linhas) {
613
+ return linhas.filter((l) => l.tipo === 'resultados' && typeof l.lane === 'string' && typeof l.head === 'string'
614
+ && typeof l.ambiente === 'string' && Array.isArray(l.testes))
615
+ .map((l) => ({ lane: l.lane, head: l.head, ambiente: l.ambiente,
616
+ testes: l.testes.flatMap((t) => {
617
+ if (!t || typeof t !== 'object' || Array.isArray(t))
618
+ return [];
619
+ const x = t;
620
+ if (typeof x.arquivo !== 'string' || typeof x.nome !== 'string' || !Number.isInteger(x.nesting)
621
+ || !['pass', 'fail', 'skip', 'todo'].includes(String(x.estado)))
622
+ return [];
623
+ return [{ arquivo: x.arquivo, nome: x.nome, nesting: x.nesting,
624
+ estado: x.estado }];
625
+ }) }));
626
+ }
627
+ /** Hash do ambiente funcional; transporte do CHECK, npm e bookkeeping do shell nao mudam o teste. */
628
+ function assinaturaDoAmbiente(env) {
629
+ const ignorada = (nome) => nome.startsWith('npm_') || nome.startsWith('NPM_')
630
+ || ['ORK_CHECK_RELATORIO', 'ORK_CHECK_CPUS_DA_LANE', 'ORK_CHECK_CPUS_DA_LANE_APOS_RESERVA', 'ORK_CHECK_RESERVA_DEVOLVIDA',
631
+ 'ORK_CHECK_CPUS_DOCKER',
632
+ 'NODE_TEST_CONTEXT', 'NODE_COMPILE_CACHE', 'PWD', 'OLDPWD', 'SHLVL', '_', 'INIT_CWD'].includes(nome);
633
+ const pares = Object.entries(env).filter(([nome, valor]) => valor !== undefined && !ignorada(nome))
634
+ .sort(([a], [b]) => a.localeCompare(b));
635
+ return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(pares)).digest('hex');
636
+ }
637
+ /** Le somente a gramatica aceita para reaproveitar: `node --test [--test-name-pattern P] arquivo...`. */
638
+ function comandoNodeTest(comando, cwd) {
639
+ const palavras = palavrasDoComando(comando);
640
+ if (!palavras)
641
+ return null;
642
+ if (palavras[0] !== 'node' || palavras[1] !== '--test')
643
+ return null;
644
+ let padrao;
645
+ const arquivos = [];
646
+ for (let i = 2; i < palavras.length; i++) {
647
+ const p = palavras[i];
648
+ if (p === '--test-name-pattern') {
649
+ if (padrao !== undefined || !palavras[++i])
650
+ return null;
651
+ padrao = palavras[i];
652
+ continue;
653
+ }
654
+ if (p.startsWith('--test-name-pattern=')) {
655
+ if (padrao !== undefined)
656
+ return null;
657
+ padrao = p.slice('--test-name-pattern='.length);
658
+ continue;
659
+ }
660
+ if (p.startsWith('-') || /[*?\[]/.test(p))
661
+ return null;
662
+ const absoluto = path.resolve(cwd, p);
663
+ const relativo = path.relative(path.join(cwd, 'core', 'dist-test', 'test'), absoluto);
664
+ if (!relativo || relativo.startsWith('..') || path.isAbsolute(relativo) || relativo !== path.basename(relativo)
665
+ || !relativo.endsWith('.test.js'))
666
+ return null;
667
+ arquivos.push(path.basename(absoluto));
668
+ }
669
+ if (padrao !== undefined) {
670
+ try {
671
+ void new RegExp(padrao);
672
+ }
673
+ catch {
674
+ return null;
675
+ }
676
+ }
677
+ return arquivos.length > 0 ? { arquivos: [...new Set(arquivos)], ...(padrao !== undefined ? { padrao } : {}) } : null;
678
+ }
679
+ /** A forma fechada usada pelas claims para provar contagem, zero falhas e zero skips no TAP. */
680
+ function auditoriaTap(comando, cwd) {
681
+ const divisor = ' | ';
682
+ const i = comando.indexOf(divisor);
683
+ if (i < 0 || comando.indexOf(divisor, i + divisor.length) >= 0)
684
+ return null;
685
+ const node = comando.slice(0, i).trim();
686
+ const awk = comando.slice(i + divisor.length).trim();
687
+ const pedido = comandoNodeTest(node.replace(/^node --test-reporter=tap\b/, 'node --test'), cwd);
688
+ if (!pedido || pedido.arquivos.length !== 1 || pedido.padrao !== undefined)
689
+ return null;
690
+ const m = /^awk '\/\^ \*not ok\/{f\+\+} \/# SKIP\/{s\+\+} \/\^ \*ok \[0-9\]\+ - ([A-Z][A-Z0-9_-]*)\[0-9\]\+:\/{p\+\+} END\{exit !\(p>=([1-9][0-9]*) && f==0 && s==0\)\}'$/.exec(awk);
691
+ return m ? { pedido, prefixo: m[1], minimo: Number(m[2]) } : null;
692
+ }
693
+ function resultadoReaproveitado(nome, comando, pedido, usadas, esperado, nomes) {
694
+ const lanes = [...new Set(usadas.filter((e) => e.testes.some((t) => pedido.arquivos.includes(t.arquivo))).map((e) => e.lane))];
695
+ return { nome, comando, ok: true, code: 0, causa: 'exit', duracaoMs: 0, executado: true,
696
+ resumo: `reaproveitado das lanes ${lanes.join(', ')} desta rodada`,
697
+ lane: { reaproveitada: true, lanes, arquivos: pedido.arquivos,
698
+ nomes: nomes ?? (pedido.padrao === undefined ? pedido.arquivos.map((a) => `${a}:todos`) : pedido.arquivos.map((a) => `${a}:/${pedido.padrao}/`)),
699
+ head: esperado.head, ambiente: esperado.ambiente } };
700
+ }
701
+ /**
702
+ * Reaproveita uma claim somente quando a lane terminou todos os arquivos pedidos no mesmo HEAD e ambiente. Um arquivo
703
+ * inteiro verde e evidencia mais forte que `--test-name-pattern`: o padrao fica registrado em `nomes`.
704
+ */
705
+ function executorDaClaimPelasLanes(base, evidencias, esperado) {
706
+ const assinatura = assinaturaDoAmbiente(esperado.ambiente);
707
+ return (nome, comando, cwd) => {
708
+ const executar = esperado.executorEquivalente ?? base;
709
+ if (esperado.podeReaproveitar?.() === false)
710
+ return executar(nome, comando, cwd);
711
+ const elos = elosDoComando(comando);
712
+ if (!elos)
713
+ return base(nome, comando, cwd);
714
+ const planejados = [];
715
+ for (const elo of elos) {
716
+ if (comandoGrepQ(elo)) {
717
+ planejados.push({ tipo: 'grep', comando: elo });
718
+ continue;
719
+ }
720
+ const tap = auditoriaTap(elo, cwd);
721
+ const pedido = tap?.pedido ?? comandoNodeTest(elo, cwd);
722
+ if (!pedido)
723
+ return base(nome, comando, cwd);
724
+ const usadas = evidencias.filter((e) => e.head === esperado.head && e.ambiente === assinatura
725
+ && e.testes.some((t) => pedido.arquivos.includes(t.arquivo)));
726
+ const cobertos = pedido.arquivos.every((arquivo) => {
727
+ const vistos = usadas.flatMap((e) => e.testes.filter((t) => t.arquivo === arquivo));
728
+ if (vistos.length === 0 || vistos.some((t) => t.estado === 'fail'))
729
+ return false;
730
+ if (vistos.some((t) => t.estado === 'skip' || t.estado === 'todo'))
731
+ return false;
732
+ if (!tap)
733
+ return true;
734
+ const doArquivo = vistos.filter((t) => t.nesting === 0);
735
+ return doArquivo.filter((t) => t.estado === 'pass' && new RegExp(`^${tap.prefixo}[0-9]+:`).test(t.nome)).length >= tap.minimo;
736
+ });
737
+ if (!cobertos)
738
+ return executar(nome, comando, cwd);
739
+ const nomes = tap ? pedido.arquivos.map((a) => `${a}:${tap.prefixo}>=${tap.minimo};sem-falha;sem-skip`) : undefined;
740
+ planejados.push({ tipo: 'lane', resultado: resultadoReaproveitado(nome, elo, pedido, usadas, { head: esperado.head, ambiente: assinatura }, nomes) });
741
+ }
742
+ let duracaoMs = 0;
743
+ const resumos = [];
744
+ let ultimo = null;
745
+ const lanes = new Set(), arquivos = new Set(), nomes = new Set();
746
+ for (const item of planejados) {
747
+ const r = item.tipo === 'grep' ? base(nome, item.comando, cwd) : item.resultado;
748
+ duracaoMs += r.duracaoMs ?? 0;
749
+ resumos.push(r.resumo);
750
+ ultimo = r;
751
+ if (!r.ok)
752
+ return { ...r, comando, comandoExecutado: item.tipo === 'grep' ? item.comando : r.comando, duracaoMs };
753
+ for (const lane of r.lane?.lanes ?? [])
754
+ lanes.add(lane);
755
+ for (const arquivo of r.lane?.arquivos ?? [])
756
+ arquivos.add(arquivo);
757
+ for (const n of r.lane?.nomes ?? [])
758
+ nomes.add(n);
759
+ }
760
+ if (!ultimo || lanes.size === 0)
761
+ return base(nome, comando, cwd);
762
+ return { ...ultimo, nome, comando, duracaoMs, resumo: resumos.filter(Boolean).join('\n'),
763
+ lane: { reaproveitada: true, lanes: [...lanes], arquivos: [...arquivos], nomes: [...nomes], head: esperado.head, ambiente: assinatura } };
764
+ };
765
+ }
@@ -85,7 +85,8 @@ async function rodarEspecificacao(esp) {
85
85
  const comGrupo = fs.existsSync(TIMEOUT_BIN);
86
86
  const usaAgendador = esp.itens.some((i) => i.papelCpu === 'hermetica');
87
87
  const plano = usaAgendador ? (0, agendador_cpu_1.planejarCpuDoGrupo)(esp.cpus ?? os.availableParallelism(), esp.medicaoCpu ?? await (0, agendador_cpu_1.medirCpuDoGrupo)()) : undefined;
88
- const itens = plano ? aplicarPlano(esp.itens, plano) : esp.itens;
88
+ const sinalDaReserva = plano && !plano.saturado ? path.join(esp.dir, 'reserva-devolvida.json') : undefined;
89
+ const itens = plano ? aplicarPlano(esp.itens, plano, sinalDaReserva) : esp.itens;
89
90
  const limite = vagas(esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : itens.length);
90
91
  const porNome = new Map();
91
92
  const indice = new Map(itens.map((item, i) => [item.nome, i]));
@@ -112,7 +113,16 @@ async function rodarEspecificacao(esp) {
112
113
  porNome.set(item.nome, vez);
113
114
  return vez;
114
115
  };
115
- const terminados = await Promise.all(itens.map(vezDoItem));
116
+ const todos = Promise.all(itens.map(vezDoItem));
117
+ const reservados = itens.filter((item) => item.papelCpu === 'varredura' || item.papelCpu === 'integracao');
118
+ const devolverReserva = async () => {
119
+ if (!sinalDaReserva || !reservados.some((item) => item.papelCpu === 'varredura')
120
+ || !reservados.some((item) => item.papelCpu === 'integracao'))
121
+ return;
122
+ await Promise.all(reservados.map((item) => vezDoItem(item, indice.get(item.nome))));
123
+ fs.writeFileSync(sinalDaReserva, JSON.stringify({ em: new Date().toISOString(), vagas: plano.cpusHermeticaAposReserva }), { mode: 0o600 });
124
+ };
125
+ const [, terminados] = await Promise.all([devolverReserva(), todos]);
116
126
  fs.writeFileSync(path.join(esp.dir, 'resultado.json'), JSON.stringify({ terminados, ...(plano ? { agendamentoCpu: plano } : {}) }), { mode: 0o600 });
117
127
  function lancar(item, i) {
118
128
  return new Promise((resolve) => {
@@ -141,7 +151,7 @@ async function rodarEspecificacao(esp) {
141
151
  }
142
152
  }
143
153
  /** Aplica a mesma conta aos processos, as lanes e aos conteineres que eles criarem. */
144
- function aplicarPlano(originais, plano) {
154
+ function aplicarPlano(originais, plano, sinalDaReserva) {
145
155
  const itens = originais.map((item) => ({ ...item, ambiente: { ...item.ambiente } }));
146
156
  const porPapel = (papel) => itens.filter((i) => i.papelCpu === papel);
147
157
  const hermetica = porPapel('hermetica')[0];
@@ -158,7 +168,10 @@ function aplicarPlano(originais, plano) {
158
168
  item.ambiente = { ...item.ambiente, ...valores };
159
169
  };
160
170
  const cpusContainer = String(plano.cpusPorContainer);
161
- ambiente(hermetica, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusHermetica), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer });
171
+ ambiente(hermetica, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusHermetica), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
172
+ ...(sinalDaReserva && varredura && integracoes.length > 0 ? {
173
+ [agendador_cpu_1.ENV_CPUS_DA_LANE_APOS_RESERVA]: String(plano.cpusHermeticaAposReserva), [agendador_cpu_1.ENV_SINAL_DA_RESERVA]: sinalDaReserva,
174
+ } : {}) });
162
175
  ambiente(varredura, {
163
176
  ORK_CHECK_VARREDURA_PARALELA: String(plano.ferramentasDaVarredura),
164
177
  [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
package/dist/ci.js CHANGED
@@ -389,10 +389,11 @@ function lintDoBundle(claims) {
389
389
  for (const claim of claims) {
390
390
  for (const achado of (0, claim_lint_1.analisarComandos)(claim.verificar)) {
391
391
  const linha = (0, claim_lint_1.linhaDoLintDeClaim)(claim.id, achado);
392
- if (achado.regra === 'suite-inteira' && claim.lint !== undefined)
392
+ const bloqueiaNova = achado.regra === 'suite-inteira' || (0, claim_lint_1.regraFatalDeClaim)(achado.regra);
393
+ if (bloqueiaNova && claim.lint !== undefined)
393
394
  recusas.push(linha);
394
395
  else
395
- avisos.push(achado.regra === 'suite-inteira' ? anteriorAoLint(linha) : linha);
396
+ avisos.push(bloqueiaNova ? anteriorAoLint(linha) : linha);
396
397
  }
397
398
  }
398
399
  return { recusas, avisos };
@@ -16,6 +16,8 @@
16
16
  * alheia e mexer em estado de quem nao pediu (D16). Nada de data de corte no codigo.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.REGRAS_FATAIS_DE_CLAIM = void 0;
20
+ exports.regraFatalDeClaim = regraFatalDeClaim;
19
21
  exports.analisarComando = analisarComando;
20
22
  exports.analisarComandos = analisarComandos;
21
23
  exports.linhaDoLintDeClaim = linhaDoLintDeClaim;
@@ -29,7 +31,18 @@ const CORRECAO = {
29
31
  'suite-inteira': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['suite-inteira'],
30
32
  'sha-intermediario': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['sha-intermediario'],
31
33
  'contagem-de-commits': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['contagem-de-commits'],
34
+ 'binario-rg': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['binario-rg'],
35
+ 'ork-fora-do-path': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['ork-fora-do-path'],
36
+ 'estado-local': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['estado-local'],
37
+ 'aspas-escapadas': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['aspas-escapadas'],
32
38
  };
39
+ /** RM-037: estes achados tornam uma claim nova inexequivel no checkout do runner. */
40
+ exports.REGRAS_FATAIS_DE_CLAIM = new Set([
41
+ 'binario-rg', 'ork-fora-do-path', 'estado-local', 'aspas-escapadas',
42
+ ]);
43
+ function regraFatalDeClaim(regra) {
44
+ return exports.REGRAS_FATAIS_DE_CLAIM.has(regra);
45
+ }
33
46
  /**
34
47
  * (a) `npm [--prefix <dir>] [run] test` sem subcomando (`test:ci` nao casa) e sem arquivos
35
48
  * nomeados depois de `--` (o comando que nomeia `.test.ts`/`.js` depois de `--` e focado).
@@ -41,8 +54,24 @@ const SO_ANCESTRALIDADE = /merge-base\s+--is-ancestor\s+\S+\s+\S+/g;
41
54
  /** (b2) Contagem de commits comparada a um numero. */
42
55
  const CONTAGEM = /rev-list\s+(?:[^|;&]*\s)?--count\b|\bgit\s+log\b[^|;&]*\|\s*wc\s+-l\b/;
43
56
  const COMPARACAO = /-(?:eq|ne|gt|lt|ge|le)\s+['"]?\d|[^=!<>]==?\s*['"]?\d|grep\s+-(?:q?x|xq)\s+['"]?\d/;
57
+ /** Executavel em posicao de comando, no inicio ou depois de um operador do shell. */
58
+ function executavelDireto(comando, nome) {
59
+ return new RegExp(`(?:^|&&|\\|\\||[;|(])\\s*(?:command\\s+|env\\s+)?(${nome})(?=\\s|$)`).exec(comando);
60
+ }
44
61
  function analisarComando(comando) {
45
62
  const achados = [];
63
+ const rg = executavelDireto(comando, 'rg');
64
+ if (rg)
65
+ achados.push({ regra: 'binario-rg', trecho: rg[1], correcao: CORRECAO['binario-rg'] });
66
+ const ork = executavelDireto(comando, 'ork');
67
+ if (ork)
68
+ achados.push({ regra: 'ork-fora-do-path', trecho: ork[1], correcao: CORRECAO['ork-fora-do-path'] });
69
+ const estado = /(?:^|[\s"'=])((?:\.\/)?\.orkastery(?:[\\/][^\s"']*)?)/.exec(comando);
70
+ if (estado)
71
+ achados.push({ regra: 'estado-local', trecho: estado[1], correcao: CORRECAO['estado-local'] });
72
+ const aspas = /\\x(?:22|27)/i.exec(comando);
73
+ if (aspas)
74
+ achados.push({ regra: 'aspas-escapadas', trecho: aspas[0], correcao: CORRECAO['aspas-escapadas'] });
46
75
  const suite = SUITE_INTEIRA.exec(comando);
47
76
  if (suite)
48
77
  achados.push({ regra: 'suite-inteira', trecho: suite[0].trim(), correcao: CORRECAO['suite-inteira'] });
package/dist/claims.js CHANGED
@@ -127,7 +127,7 @@ function montarClaim(entrada) {
127
127
  criadoEm: (0, util_1.agora)(),
128
128
  estado: verificar.length > 0 ? 'pendente' : 'nao-verificavel',
129
129
  ...(entrada.origem ? { origem: entrada.origem } : {}),
130
- // I-53 (RM-037, P6): a mesma regra para CLI, MCP, canarios e auditor, porque todos passam aqui.
130
+ // I-53 (RM-037, P6): a mesma regra para CLI, MCP, cenarios e auditor, porque todos passam aqui.
131
131
  lint: (0, claim_lint_1.analisarComandos)(verificar),
132
132
  };
133
133
  }
@@ -154,6 +154,10 @@ function adicionarClaim(raiz, threadId, opcoes) {
154
154
  verificar: opcoes.verificar,
155
155
  origem: opcoes.origem,
156
156
  });
157
+ const recusas = (claim.lint ?? []).filter((a) => (0, claim_lint_1.regraFatalDeClaim)(a.regra));
158
+ if (recusas.length > 0) {
159
+ throw new locale_1.ErroDoCatalogo((m) => m.claims.lintRecusado(recusas.map((a) => `claims.lint.${a.regra}: ${(0, claim_lint_1.linhaDoLintDeClaim)(claim.id, a)}`).join('\n ')));
160
+ }
157
161
  const negativa = claim.negativa;
158
162
  const verificar = claim.verificar;
159
163
  gravarClaim(raiz, claim);
package/dist/docs.js CHANGED
@@ -82,6 +82,7 @@ const ledger_1 = require("./ledger");
82
82
  const branch_de_estado_1 = require("./branch-de-estado");
83
83
  const locale_1 = require("./locale");
84
84
  const merge_da_entrega_1 = require("./merge-da-entrega");
85
+ const changelog_check_1 = require("./changelog-check");
85
86
  exports.DIR_PRODUTO = 'docs/produto';
86
87
  exports.DIR_ROADMAP = 'docs/roadmap';
87
88
  exports.DIR_PADROES = 'docs/padroes';
@@ -109,7 +110,7 @@ exports.DIMENSOES = {
109
110
  codigo: ['Não iniciado', 'Branch criada', 'PR aberto', 'Mesclado'],
110
111
  testes: ['Não iniciados', 'Em execução', 'Aprovados', 'Falhando'],
111
112
  deploy: ['Não implantado', 'Dev', 'Staging', 'Produção'],
112
- exposicao: ['Flag desligada', 'Piloto/Canary', 'Parcial', 'Geral'],
113
+ exposicao: ['Flag desligada', 'Piloto/Gradual', 'Parcial', 'Geral'],
113
114
  habilitacao: ['Pendente', 'Em andamento', 'Concluída'],
114
115
  };
115
116
  exports.CATEGORIAS = ['iniciativa', 'épico', 'feature', 'melhoria'];
@@ -265,6 +266,12 @@ function verificarDocs(raiz, opcoes = {}) {
265
266
  if (!opcoes.semGit && !base) {
266
267
  achados.push(aviso('.', undefined, 'docs.git.base', (m) => m.baseAusente(opcoes.baseBranch ?? 'main'), (m) => m.baseAusenteCorrecao));
267
268
  }
269
+ if (opcoes.pr && base) {
270
+ const changelog = (0, changelog_check_1.checarChangelog)(raiz, { base, head: 'HEAD', baseCarimbada: opcoes.baseCarimbada });
271
+ if (!changelog.ok) {
272
+ achados.push(erro(changelog_check_1.CHANGELOG, undefined, changelog.motivo ?? 'changelog.invalido', () => changelog.detalhe, () => changelog.correcao ?? 'corrija o CHANGELOG e repita a verificação'));
273
+ }
274
+ }
268
275
  const comandos = opcoes.ajudaDoCli ? comandosDaAjuda(opcoes.ajudaDoCli) : null;
269
276
  for (const d of docs) {
270
277
  if (!TIPOS.includes(d.tipo))
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Ele julga duas coisas, e diz em voz alta que so uma delas ele consegue julgar.
6
6
  *
7
- * 1. **Os canarios** (`eval/fixtures/fx-*`): comportamento real do nucleo, exercitado ponta a
7
+ * 1. **Os cenarios** (`eval/fixtures/fx-*`): comportamento real do nucleo, exercitado ponta a
8
8
  * ponta contra git de verdade, comparado com o que a fixture declara esperar.
9
9
  * 2. **O corpus das skills** (`eval/casos/*.json`): as regras que precisam EXISTIR no arquivo
10
10
  * da skill para o comportamento ser possivel. Isso e deterministico, e e o que o runner
@@ -60,12 +60,12 @@ exports.posicoesDaAssercao = posicoesDaAssercao;
60
60
  exports.dirDeCasos = dirDeCasos;
61
61
  exports.dirDeFixtures = dirDeFixtures;
62
62
  exports.lerCasos = lerCasos;
63
- exports.rodarCanarios = rodarCanarios;
63
+ exports.rodarCenarios = rodarCenarios;
64
64
  exports.rodarEval = rodarEval;
65
65
  exports.textoDoEval = textoDoEval;
66
66
  const fs = __importStar(require("node:fs"));
67
67
  const path = __importStar(require("node:path"));
68
- const canarios_1 = require("./canarios");
68
+ const cenarios_1 = require("./cenarios");
69
69
  const catalogo_1 = require("./catalogo");
70
70
  const util_1 = require("./util");
71
71
  const locale_1 = require("./locale");
@@ -271,20 +271,20 @@ function comparar(esperado, observado) {
271
271
  }
272
272
  return divergencias;
273
273
  }
274
- /** Roda os canarios cujas fixtures existem em `eval/fixtures/fx-*`. */
275
- function rodarCanarios(catalogo, filtro) {
274
+ /** Roda os cenarios cujas fixtures existem em `eval/fixtures/fx-*`. */
275
+ function rodarCenarios(catalogo, filtro) {
276
276
  const dir = dirDeFixtures(catalogo);
277
277
  const resultados = [];
278
278
  const falhas = [];
279
279
  const m = (0, locale_1.msg)().operacao.restante.eval;
280
- const temGit = (0, canarios_1.gitDisponivel)();
280
+ const temGit = (0, cenarios_1.gitDisponivel)();
281
281
  const nomes = fs.existsSync(dir) ? fs.readdirSync(dir).sort() : [];
282
282
  if (filtro && filtro.length === 0) {
283
283
  falhas.push({ codigo: 'fixture-invalida', detalhe: m.filtroVazio });
284
284
  }
285
285
  for (const id of new Set(filtro ?? [])) {
286
- if (!nomes.includes(id) || !(0, canarios_1.canarioPorId)(id)) {
287
- falhas.push({ codigo: 'canario-sem-executor', caso: id, detalhe: m.canarioAusente(id) });
286
+ if (!nomes.includes(id) || !(0, cenarios_1.cenarioPorId)(id)) {
287
+ falhas.push({ codigo: 'cenario-sem-executor', caso: id, detalhe: m.cenarioAusente(id) });
288
288
  }
289
289
  }
290
290
  for (const nome of nomes) {
@@ -305,22 +305,22 @@ function rodarCanarios(catalogo, filtro) {
305
305
  falhas.push({ codigo: 'fixture-invalida', detalhe: `${nome}: ${e.message}` });
306
306
  continue;
307
307
  }
308
- const canario = (0, canarios_1.canarioPorId)(fixture.id ?? nome);
308
+ const cenario = (0, cenarios_1.cenarioPorId)(fixture.id ?? nome);
309
309
  if (fixture.id !== nome) {
310
310
  falhas.push({ codigo: 'fixture-invalida', caso: nome, detalhe: m.fixtureDiverge });
311
311
  continue;
312
312
  }
313
- if (!canario) {
313
+ if (!cenario) {
314
314
  falhas.push({
315
- codigo: 'canario-sem-executor',
315
+ codigo: 'cenario-sem-executor',
316
316
  detalhe: m.semExecutor(nome, fixture.id ?? nome),
317
317
  });
318
318
  continue;
319
319
  }
320
- if (canario.precisaDeGit && !temGit) {
320
+ if (cenario.precisaDeGit && !temGit) {
321
321
  resultados.push({
322
- id: canario.id,
323
- sobre: canario.sobre,
322
+ id: cenario.id,
323
+ sobre: cenario.sobre,
324
324
  estado: 'unavailable',
325
325
  divergencias: [],
326
326
  motivo: m.semGit,
@@ -329,11 +329,11 @@ function rodarCanarios(catalogo, filtro) {
329
329
  continue;
330
330
  }
331
331
  try {
332
- const observado = canario.rodar({ catalogo });
332
+ const observado = cenario.rodar({ catalogo });
333
333
  const divergencias = comparar(fixture.esperado ?? {}, observado);
334
334
  resultados.push({
335
- id: canario.id,
336
- sobre: canario.sobre,
335
+ id: cenario.id,
336
+ sobre: cenario.sobre,
337
337
  estado: divergencias.length === 0 ? 'passou' : 'falhou',
338
338
  divergencias,
339
339
  motivo: divergencias.length === 0 ? '' : `${divergencias.length} chave(s) divergente(s)`,
@@ -341,33 +341,33 @@ function rodarCanarios(catalogo, filtro) {
341
341
  });
342
342
  for (const d of divergencias) {
343
343
  falhas.push({
344
- codigo: 'canario',
345
- caso: `${canario.id}/${d.chave}`,
344
+ codigo: 'cenario',
345
+ caso: `${cenario.id}/${d.chave}`,
346
346
  detalhe: m.divergencia(JSON.stringify(d.esperado), JSON.stringify(d.observado)),
347
347
  });
348
348
  }
349
349
  }
350
350
  catch (e) {
351
351
  resultados.push({
352
- id: canario.id,
353
- sobre: canario.sobre,
352
+ id: cenario.id,
353
+ sobre: cenario.sobre,
354
354
  estado: 'falhou',
355
355
  divergencias: [],
356
356
  motivo: e.message,
357
357
  observado: {},
358
358
  });
359
- falhas.push({ codigo: 'canario', caso: canario.id, detalhe: e.message });
359
+ falhas.push({ codigo: 'cenario', caso: cenario.id, detalhe: e.message });
360
360
  }
361
361
  }
362
- // Canario registrado no codigo e sem fixture em disco tambem e defeito: a expectativa
362
+ // Cenario registrado no codigo e sem fixture em disco tambem e defeito: a expectativa
363
363
  // precisa ser dado versionado, revisavel por quem nao le TypeScript.
364
364
  if (!filtro) {
365
- for (const c of canarios_1.CANARIOS) {
365
+ for (const c of cenarios_1.CENARIOS) {
366
366
  if (!fs.existsSync(path.join(dir, c.id, 'caso.json'))) {
367
367
  falhas.push({
368
368
  codigo: 'fixture-invalida',
369
369
  caso: c.id,
370
- detalhe: m.canarioSemFixture(c.id),
370
+ detalhe: m.cenarioSemFixture(c.id),
371
371
  });
372
372
  }
373
373
  }
@@ -377,20 +377,20 @@ function rodarCanarios(catalogo, filtro) {
377
377
  // ---------------------------------------------------------------------------
378
378
  // Orquestracao
379
379
  // ---------------------------------------------------------------------------
380
- /** Roda o eval inteiro: canarios mais corpus das skills. */
380
+ /** Roda o eval inteiro: cenarios mais corpus das skills. */
381
381
  function rodarEval(opcoes) {
382
382
  const { catalogo } = opcoes;
383
383
  const falhas = [];
384
384
  const linhas = [];
385
- let canarios = [];
385
+ let cenarios = [];
386
386
  if (!opcoes.soSkills) {
387
- const r = rodarCanarios(catalogo, opcoes.canarios);
388
- canarios = r.resultados;
387
+ const r = rodarCenarios(catalogo, opcoes.cenarios);
388
+ cenarios = r.resultados;
389
389
  falhas.push(...r.falhas);
390
390
  }
391
391
  let totalDeCasos = 0;
392
392
  let totalDeAssercoes = 0;
393
- if (!opcoes.soCanarios) {
393
+ if (!opcoes.soCenarios) {
394
394
  const { arquivos, falhas: falhasDeLeitura } = lerCasos(catalogo);
395
395
  falhas.push(...falhasDeLeitura);
396
396
  const doCatalogo = (0, catalogo_1.skillsDoCatalogo)(catalogo);
@@ -428,7 +428,7 @@ function rodarEval(opcoes) {
428
428
  return {
429
429
  catalogo,
430
430
  skills: linhas,
431
- canarios,
431
+ cenarios,
432
432
  falhas,
433
433
  totalDeCasos,
434
434
  totalDeAssercoes,
@@ -442,10 +442,10 @@ function textoDoEval(r) {
442
442
  const linhas = [];
443
443
  linhas.push(m.titulo(r.catalogo));
444
444
  linhas.push('');
445
- if (r.canarios.length > 0) {
446
- linhas.push(m.canarios);
445
+ if (r.cenarios.length > 0) {
446
+ linhas.push(m.cenarios);
447
447
  linhas.push('');
448
- linhas.push((0, util_1.tabela)([...m.colunasCanarios], r.canarios.map((c) => [c.id, m.estados[c.estado] ?? c.estado, c.estado === 'passou' ? c.sobre : c.motivo || c.sobre])));
448
+ linhas.push((0, util_1.tabela)([...m.colunasCenarios], r.cenarios.map((c) => [c.id, m.estados[c.estado] ?? c.estado, c.estado === 'passou' ? c.sobre : c.motivo || c.sobre])));
449
449
  linhas.push('');
450
450
  }
451
451
  if (r.skills.length > 0) {
@@ -463,9 +463,9 @@ function textoDoEval(r) {
463
463
  }
464
464
  linhas.push('');
465
465
  }
466
- const passaram = r.canarios.filter((c) => c.estado === 'passou').length;
467
- const indisponiveis = r.canarios.filter((c) => c.estado === 'unavailable').length;
468
- linhas.push(m.resumoCanarios(passaram, r.canarios.length, indisponiveis));
466
+ const passaram = r.cenarios.filter((c) => c.estado === 'passou').length;
467
+ const indisponiveis = r.cenarios.filter((c) => c.estado === 'unavailable').length;
468
+ linhas.push(m.resumoCenarios(passaram, r.cenarios.length, indisponiveis));
469
469
  linhas.push(m.resumoSkills(r.skills.length, r.totalDeCasos, r.totalDeAssercoes, r.falhas.length));
470
470
  linhas.push(m.metade);
471
471
  linhas.push(m.veredito(r.ok));