@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.
- package/adapters/claude-code/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +1 -1
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/ork.md +1 -1
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
- package/adapters/codex/skills/ork/SKILL.md +1 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
- package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
- package/assets/adapters-en/claude-code/commands/ork.md +1 -1
- package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
- package/assets/orkmind_fixture.py +4 -1
- package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
- package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
- package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
- package/dist/adapters/claude-bg.js +9 -7
- package/dist/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +622 -0
- package/dist/adapters/grok-mcp.js +178 -0
- package/dist/adapters/grok.js +686 -0
- package/dist/agendador-cpu.js +92 -0
- package/dist/branch-de-estado.js +7 -2
- package/dist/changelog-check.js +217 -0
- package/dist/check-agil.js +289 -25
- package/dist/check-paralelo.js +73 -18
- package/dist/ci.js +3 -2
- package/dist/claim-lint.js +29 -0
- package/dist/claims.js +4 -0
- package/dist/conducao.js +5 -0
- package/dist/docs.js +16 -16
- package/dist/doctor-forja.js +1 -3
- package/dist/doctor.js +1 -1
- package/dist/entrega-pr.js +10 -10
- package/dist/fabrica-estado.js +20 -11
- package/dist/fila-pesada.js +121 -11
- package/dist/hitl-classificacao.js +1 -0
- package/dist/horario.js +4 -1
- package/dist/index.js +63 -35
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +27 -9
- package/dist/locale-accounts.js +11 -3
- package/dist/locale-ajuda.js +10 -6
- package/dist/locale-claims.js +16 -2
- package/dist/locale-forja.js +2 -4
- package/dist/locale-grafo.js +56 -1
- package/dist/locale-maestro.js +38 -0
- package/dist/locale-network.js +8 -6
- package/dist/locale-onboarding.js +14 -0
- package/dist/locale-operacao-restante.js +2 -2
- package/dist/locale-preflight.js +80 -0
- package/dist/locale-projetos.js +6 -0
- package/dist/locale-prompts.js +8 -8
- package/dist/locale-pulse.js +4 -0
- package/dist/locale-retry.js +56 -2
- package/dist/locale-security-scan.js +35 -1
- package/dist/locale-ship.js +10 -2
- package/dist/locale-verify.js +15 -0
- package/dist/locale.js +24 -7
- package/dist/maestro-cli.js +12 -17
- package/dist/manifest.js +6 -1
- package/dist/mcp-install.js +24 -13
- package/dist/mcp-server.js +62 -33
- package/dist/mcp-verify-async.js +353 -0
- package/dist/mcp-verify-worker.js +17 -0
- package/dist/mcp-verify.js +10 -1
- package/dist/merge-da-entrega.js +85 -0
- package/dist/network-roadmap.js +18 -14
- package/dist/noctum-aceite.js +154 -14
- package/dist/noctum-cli.js +69 -12
- package/dist/noctum-entrega.js +55 -7
- package/dist/noctum-envelope.js +1 -1
- package/dist/noctum-execucao.js +5 -1
- package/dist/noctum-fechamento.js +18 -7
- package/dist/noctum-fila.js +6 -1
- package/dist/noctum-guarda.js +81 -4
- package/dist/noctum-maquinas.js +5 -1
- package/dist/noctum-modo.js +35 -7
- package/dist/noctum-reconciliacao.js +4 -1
- package/dist/noctum-rede.js +102 -0
- package/dist/noctum-relatorio.js +44 -10
- package/dist/noctum-retomada-rede.js +71 -0
- package/dist/noctum-vigia.js +49 -9
- package/dist/parado-no-condutor.js +14 -15
- package/dist/pedido-conducao.js +51 -0
- package/dist/phase.js +43 -15
- package/dist/postura-local.js +7 -3
- package/dist/preflight.js +27 -27
- package/dist/prompts.js +4 -2
- package/dist/pulse.js +4 -2
- package/dist/ratelimit.js +1 -0
- package/dist/rede.js +24 -12
- package/dist/retry.js +115 -10
- package/dist/runtime-context.js +165 -45
- package/dist/runtime-escada.js +7 -1
- package/dist/runtime-profiles.js +27 -17
- package/dist/runtimes.js +42 -5
- package/dist/security-scan/agil.js +18 -7
- package/dist/security-scan/cli.js +2 -2
- package/dist/security-scan/executor.js +8 -0
- package/dist/security-scan/report.js +1 -1
- package/dist/session-watcher-grok.js +425 -0
- package/dist/session-watcher.js +52 -11
- package/dist/sessoes-contas.js +33 -5
- package/dist/setup.js +11 -11
- package/dist/varredura-acoes.js +8 -15
- package/dist/verify.js +191 -43
- package/package.json +2 -2
- package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
- package/skills/phases/check-quality/SKILL.md +1 -1
- package/skills/phases/go-implementation/SKILL.md +1 -1
package/dist/check-agil.js
CHANGED
|
@@ -38,14 +38,19 @@ 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;
|
|
44
45
|
exports.refazerPreparo = refazerPreparo;
|
|
45
46
|
exports.testDasLanes = testDasLanes;
|
|
47
|
+
exports.porQueDasLanes = porQueDasLanes;
|
|
46
48
|
exports.comandosNoAgile = comandosNoAgile;
|
|
47
49
|
exports.pecasDoRelatorio = pecasDoRelatorio;
|
|
48
50
|
exports.listasDasLanes = listasDasLanes;
|
|
51
|
+
exports.resultadosDasLanes = resultadosDasLanes;
|
|
52
|
+
exports.assinaturaDoAmbiente = assinaturaDoAmbiente;
|
|
53
|
+
exports.executorDaClaimPelasLanes = executorDaClaimPelasLanes;
|
|
49
54
|
/**
|
|
50
55
|
* RM-072 (D3 a D8 da ork-check5x): o motor do Agile Check no verify.
|
|
51
56
|
*
|
|
@@ -53,12 +58,16 @@ exports.listasDasLanes = listasDasLanes;
|
|
|
53
58
|
*
|
|
54
59
|
* - o preparo do Agile (`check.agile.preparo`, como o `tsgo` deste projeto) e, se ele falhar ou nao existir, o
|
|
55
60
|
* `verify.preparo`, com a peca `preparo` em fallback;
|
|
56
|
-
* -
|
|
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
|
|
57
65
|
* eles quando a guarda vale (o preparo passou, a arvore e a mesma do fim do preparo e o hash dos caminhos de
|
|
58
66
|
* `check.agile.produto` tambem); qualquer divergencia roda o comando inteiro, e o motivo vai ao ledger. Passo no meio
|
|
59
|
-
* da cadeia
|
|
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
|
|
60
69
|
* so o produto mudou (a claim anterior compilou de novo no meio da cadeia), o preparo roda de novo antes da proxima
|
|
61
|
-
* claim que o reaproveitaria, ate `MAXIMO_DE_PREPAROS_REFEITOS` vezes, e uma vez
|
|
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
|
|
62
71
|
* `preparo-refeito`;
|
|
63
72
|
* - o `build` que e so passos do preparo, com a guarda valendo, sai do preparo desta rodada sem rodar de novo;
|
|
64
73
|
* - `typecheck`, as lanes de `test` (`check.agile.test`, ou o `verify.test`) e o `security_scan` num grupo paralelo, so
|
|
@@ -74,13 +83,11 @@ const fs = __importStar(require("node:fs"));
|
|
|
74
83
|
const os = __importStar(require("node:os"));
|
|
75
84
|
const path = __importStar(require("node:path"));
|
|
76
85
|
const check_modo_1 = require("./check-modo");
|
|
86
|
+
const agendador_cpu_1 = require("./agendador-cpu");
|
|
77
87
|
const redacao_url_1 = require("./redacao-url");
|
|
78
|
-
/**
|
|
79
|
-
* Quantas ferramentas a varredura roda ao mesmo tempo dentro do grupo, ao lado das lanes: um terco das CPUs que sobram
|
|
80
|
-
* de uma, e no minimo duas. Numa maquina de 7 CPUs, duas.
|
|
81
|
-
*/
|
|
88
|
+
/** Quantas ferramentas cabem na fatia de duas CPUs reservada pelo plano paralelo. */
|
|
82
89
|
function paralelismoDaVarreduraNoGrupo(cpus = os.availableParallelism()) {
|
|
83
|
-
return
|
|
90
|
+
return (0, agendador_cpu_1.planejarCpuDoGrupo)(cpus, { carga1: 0, stealPct: 0 }).ferramentasDaVarredura;
|
|
84
91
|
}
|
|
85
92
|
const SEPARADOR = ' && ';
|
|
86
93
|
/** Os passos de um comando encadeado por `&&` no nivel do texto (o preparo e escrito assim no manifesto). */
|
|
@@ -158,6 +165,78 @@ function tirarPassosDoPreparo(comando, passosDoPreparo) {
|
|
|
158
165
|
}
|
|
159
166
|
return { passos, resto };
|
|
160
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
|
+
}
|
|
161
240
|
/**
|
|
162
241
|
* O sha256 do conteudo dos caminhos do produto (arquivos em ordem, com o caminho relativo). Caminho ausente entra como
|
|
163
242
|
* ausente, para o produto que some nao passar por igual; link simbolico entra pelo destino que ele aponta, sem segui-lo.
|
|
@@ -257,7 +336,8 @@ function doPreparo(nome, comando, preparo, passos) {
|
|
|
257
336
|
function executorDaClaimNoAgile(manifesto, executor, vigente, refazer) {
|
|
258
337
|
const passosDoPreparo = manifesto.verify.preparo ? passosDoComando(manifesto.verify.preparo) : [];
|
|
259
338
|
return (nome, comando, cwd) => {
|
|
260
|
-
const
|
|
339
|
+
const daFrente = tirarPassosDoPreparo(comando, passosDoPreparo);
|
|
340
|
+
const { passos, resto } = daFrente.passos.length > 0 ? daFrente : tirarPassosDoPreparoAposLeituras(comando, passosDoPreparo);
|
|
261
341
|
if (passos.length === 0)
|
|
262
342
|
return executor(nome, comando, cwd);
|
|
263
343
|
let motivo = vigente.guarda.conferir();
|
|
@@ -311,21 +391,25 @@ function caiuDeVerdade(r) {
|
|
|
311
391
|
/**
|
|
312
392
|
* O `test` das lanes (D4): ok so com todas ok; a evidencia de quem caiu; a parede e a da lane mais longa. A falha de
|
|
313
393
|
* verdade de uma lane manda, como num comando so do Classic: o estouro ou o relogio sob steal de outra lane nao a esconde.
|
|
314
|
-
* O relogio sob steal so vale quando toda lane que caiu caiu so nele, e a pior causa so vale sem falha de verdade.
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
394
|
+
* O relogio sob steal so vale quando toda lane que caiu caiu so nele, e a pior causa so vale sem falha de verdade.
|
|
395
|
+
*
|
|
396
|
+
* RM-074 (F4): a lane que nao terminou sozinha fora do tempo (sinal, comando ausente, o codigo de quem nao a deixou
|
|
397
|
+
* terminar, de 124 em diante antes do prazo de fora) da ao `test` o codigo dela, e o `test` segue com veredito, como o
|
|
398
|
+
* comando unico do Classic que terminasse assim: no verify ele e comparado com a baseline (regressao, ja falhava ou falha
|
|
399
|
+
* sem baseline), e na baseline ele e `sem-veredito`, porque o codigo nao e da propria lane, em qualquer ordem das lanes;
|
|
400
|
+
* com falha de verdade junto, a causa, os testes e o trecho continuam os dela. So a lane que nao rodou (`executado`
|
|
401
|
+
* falso, como o comando que o Classic nao lancou) deixa o `test` sem veredito inteiro. O caso por tempo segue o de antes
|
|
402
|
+
* (N1 do CHECK 2 e N6 do CHECK 4): o estouro ou o relogio de uma lane junto de falha de verdade de outra tambem deixa o
|
|
403
|
+
* `test` sem veredito inteiro, e as caidas so por tempo dao o agregado por tempo.
|
|
320
404
|
*/
|
|
321
405
|
function testDasLanes(lanes) {
|
|
322
406
|
const caidas = lanes.filter((l) => !l.resultado.ok);
|
|
323
407
|
const deVerdade = caidas.filter((l) => caiuDeVerdade(l.resultado));
|
|
324
408
|
const semVeredito = caidas.filter((l) => !caiuDeVerdade(l.resultado));
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
const
|
|
328
|
-
|
|
409
|
+
const porTempo = (r) => r.causa === 'timeout' || (r.relogioSobSteal?.length ?? 0) > 0;
|
|
410
|
+
// RM-074 (F4): a lane sem veredito fora do tempo da o codigo ao `test`, que o verify compara com a baseline.
|
|
411
|
+
const foraDoTempo = semVeredito.filter((l) => !porTempo(l.resultado));
|
|
412
|
+
const tempoComFalha = deVerdade.length > 0 && semVeredito.some((l) => porTempo(l.resultado));
|
|
329
413
|
const juntar = (f) => lanes.flatMap((l) => f(l.resultado) ?? []);
|
|
330
414
|
const causas = lanes.map((l) => l.resultado.causa).filter((c) => !!c);
|
|
331
415
|
const causa = deVerdade.length ? deVerdade[0].resultado.causa ?? 'exit' : PIOR_CAUSA.find((c) => causas.includes(c));
|
|
@@ -338,20 +422,45 @@ function testDasLanes(lanes) {
|
|
|
338
422
|
nome: 'test',
|
|
339
423
|
comando: lanes.map((l) => `${l.lane}: ${l.resultado.comando}`).join('; '),
|
|
340
424
|
ok: caidas.length === 0,
|
|
341
|
-
code: (deVerdade[0] ?? caidas[0])?.resultado.code ?? 0,
|
|
425
|
+
code: (foraDoTempo[0] ?? deVerdade[0] ?? caidas[0])?.resultado.code ?? 0,
|
|
342
426
|
resumo: (caidas.length ? caidas : lanes).map((l) => `[${l.lane}] ${l.resultado.resumo}`).join('\n').slice(-800),
|
|
343
427
|
...(causa ? { causa } : {}),
|
|
344
428
|
prazoMs: Math.max(...lanes.map((l) => l.resultado.prazoMs ?? 0)),
|
|
345
429
|
duracaoMs: Math.max(...lanes.map((l) => l.resultado.duracaoMs ?? 0)),
|
|
346
|
-
executado:
|
|
430
|
+
executado: !tempoComFalha && lanes.every((l) => l.resultado.executado !== false),
|
|
347
431
|
...(testes.length ? { testeQueCaiu: testes } : {}),
|
|
348
432
|
...(falhas.length ? { falhasDeTeste: falhas } : {}),
|
|
349
433
|
...(trechos.length ? { trecho: trechos.join('\n').slice(0, 1024) } : {}),
|
|
350
434
|
...(lanes.some((l) => l.resultado.stealPct !== undefined) ? { stealPct: lanes.find((l) => l.resultado.stealPct !== undefined).resultado.stealPct } : {}),
|
|
351
435
|
...(relogio.length ? { relogioSobSteal: relogio } : {}),
|
|
352
436
|
lanes: lanes.map((l) => ({ lane: l.lane, comando: l.resultado.comando, ok: l.resultado.ok, code: l.resultado.code,
|
|
353
|
-
...(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 } : {}) })),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* RM-074 (F4): por que cada lane caida do `test` ficou como ficou, para o texto do gate dizer qual lane ficou sem veredito
|
|
443
|
+
* e por que. Le so o que o ledger grava da lane (codigo e causa); o relogio sob steal vem do agregado, que so o traz quando
|
|
444
|
+
* toda lane caida caiu so nele. Vazio fora do Agile (sem lanes) e com todas as lanes ok.
|
|
445
|
+
*/
|
|
446
|
+
function porQueDasLanes(c) {
|
|
447
|
+
const caidas = (c.lanes ?? []).filter((l) => !l.ok);
|
|
448
|
+
const porque = (l) => {
|
|
449
|
+
if (l.causa === 'timeout')
|
|
450
|
+
return 'estourou o prazo';
|
|
451
|
+
if (l.causa === 'nao-encontrado')
|
|
452
|
+
return `comando nao encontrado (codigo ${l.code})`;
|
|
453
|
+
if (l.code === -1)
|
|
454
|
+
return 'sem codigo de saida: nao rodou, ou o auxiliar do grupo morreu antes de gravar o resultado';
|
|
455
|
+
if (l.causa === 'sinal')
|
|
456
|
+
return `morta por sinal (codigo ${l.code})`;
|
|
457
|
+
if (c.relogioSobSteal?.length)
|
|
458
|
+
return `caiu so em teste com relogio sob steal (${c.relogioSobSteal.join(', ')})`;
|
|
459
|
+
if (Number.isInteger(l.code) && l.code >= 1 && l.code <= MAIOR_CODIGO_DA_LANE)
|
|
460
|
+
return `caiu com o codigo ${l.code}`;
|
|
461
|
+
return `saiu com ${l.code}, o codigo de quem nao a deixou terminar`;
|
|
354
462
|
};
|
|
463
|
+
return caidas.map((l) => `lane ${l.lane}: ${porque(l)}`).join('; ');
|
|
355
464
|
}
|
|
356
465
|
/**
|
|
357
466
|
* Os comandos do manifesto no Agile (D4): `build` reaproveitado do preparo com a guarda valendo; o resto num grupo
|
|
@@ -391,16 +500,17 @@ function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo
|
|
|
391
500
|
if (c.nome === 'test' && lanesDeclaradas.length > 0) {
|
|
392
501
|
for (const [lane, comando] of lanesDeclaradas) {
|
|
393
502
|
if (depoisDaVarredura.has(lane))
|
|
394
|
-
encadeados.push({ nome: `test:${lane}`, comando, apos: ['security_scan'] });
|
|
503
|
+
encadeados.push({ nome: `test:${lane}`, comando, apos: ['security_scan'], papelCpu: 'integracao' });
|
|
395
504
|
else
|
|
396
|
-
itens.push({ nome: `test:${lane}`, comando });
|
|
505
|
+
itens.push({ nome: `test:${lane}`, comando, papelCpu: lane === 'hermetica' ? 'hermetica' : 'outro' });
|
|
397
506
|
}
|
|
398
507
|
}
|
|
399
508
|
else
|
|
400
|
-
itens.push({ nome: c.nome, comando: c.comando });
|
|
509
|
+
itens.push({ nome: c.nome, comando: c.comando, papelCpu: c.nome === 'security_scan' ? 'varredura' : 'outro' });
|
|
401
510
|
}
|
|
402
511
|
itens.push(...encadeados);
|
|
403
512
|
const resultados = itens.length > 1 ? grupo(itens, cwd) : itens.map((i) => executor(i.nome, i.comando, cwd));
|
|
513
|
+
pecas.push(...(grupo.pecas ?? []));
|
|
404
514
|
const lanes = [];
|
|
405
515
|
itens.forEach((item, i) => {
|
|
406
516
|
if (item.nome.startsWith('test:'))
|
|
@@ -498,3 +608,157 @@ function listasDasLanes(linhas) {
|
|
|
498
608
|
return { lane: l.lane, arquivos, sha256: (0, node_crypto_1.createHash)('sha256').update(arquivos.join('\n')).digest('hex') };
|
|
499
609
|
});
|
|
500
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_DOCKER',
|
|
631
|
+
'NODE_TEST_CONTEXT', 'NODE_COMPILE_CACHE', 'PWD', 'OLDPWD', 'SHLVL', '_', 'INIT_CWD'].includes(nome);
|
|
632
|
+
const pares = Object.entries(env).filter(([nome, valor]) => valor !== undefined && !ignorada(nome))
|
|
633
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
634
|
+
return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(pares)).digest('hex');
|
|
635
|
+
}
|
|
636
|
+
/** Le somente a gramatica aceita para reaproveitar: `node --test [--test-name-pattern P] arquivo...`. */
|
|
637
|
+
function comandoNodeTest(comando, cwd) {
|
|
638
|
+
const palavras = palavrasDoComando(comando);
|
|
639
|
+
if (!palavras)
|
|
640
|
+
return null;
|
|
641
|
+
if (palavras[0] !== 'node' || palavras[1] !== '--test')
|
|
642
|
+
return null;
|
|
643
|
+
let padrao;
|
|
644
|
+
const arquivos = [];
|
|
645
|
+
for (let i = 2; i < palavras.length; i++) {
|
|
646
|
+
const p = palavras[i];
|
|
647
|
+
if (p === '--test-name-pattern') {
|
|
648
|
+
if (padrao !== undefined || !palavras[++i])
|
|
649
|
+
return null;
|
|
650
|
+
padrao = palavras[i];
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (p.startsWith('--test-name-pattern=')) {
|
|
654
|
+
if (padrao !== undefined)
|
|
655
|
+
return null;
|
|
656
|
+
padrao = p.slice('--test-name-pattern='.length);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (p.startsWith('-') || /[*?\[]/.test(p))
|
|
660
|
+
return null;
|
|
661
|
+
const absoluto = path.resolve(cwd, p);
|
|
662
|
+
const relativo = path.relative(path.join(cwd, 'core', 'dist-test', 'test'), absoluto);
|
|
663
|
+
if (!relativo || relativo.startsWith('..') || path.isAbsolute(relativo) || relativo !== path.basename(relativo)
|
|
664
|
+
|| !relativo.endsWith('.test.js'))
|
|
665
|
+
return null;
|
|
666
|
+
arquivos.push(path.basename(absoluto));
|
|
667
|
+
}
|
|
668
|
+
if (padrao !== undefined) {
|
|
669
|
+
try {
|
|
670
|
+
void new RegExp(padrao);
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return arquivos.length > 0 ? { arquivos: [...new Set(arquivos)], ...(padrao !== undefined ? { padrao } : {}) } : null;
|
|
677
|
+
}
|
|
678
|
+
/** A forma fechada usada pelas claims para provar contagem, zero falhas e zero skips no TAP. */
|
|
679
|
+
function auditoriaTap(comando, cwd) {
|
|
680
|
+
const divisor = ' | ';
|
|
681
|
+
const i = comando.indexOf(divisor);
|
|
682
|
+
if (i < 0 || comando.indexOf(divisor, i + divisor.length) >= 0)
|
|
683
|
+
return null;
|
|
684
|
+
const node = comando.slice(0, i).trim();
|
|
685
|
+
const awk = comando.slice(i + divisor.length).trim();
|
|
686
|
+
const pedido = comandoNodeTest(node.replace(/^node --test-reporter=tap\b/, 'node --test'), cwd);
|
|
687
|
+
if (!pedido || pedido.arquivos.length !== 1 || pedido.padrao !== undefined)
|
|
688
|
+
return null;
|
|
689
|
+
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);
|
|
690
|
+
return m ? { pedido, prefixo: m[1], minimo: Number(m[2]) } : null;
|
|
691
|
+
}
|
|
692
|
+
function resultadoReaproveitado(nome, comando, pedido, usadas, esperado, nomes) {
|
|
693
|
+
const lanes = [...new Set(usadas.filter((e) => e.testes.some((t) => pedido.arquivos.includes(t.arquivo))).map((e) => e.lane))];
|
|
694
|
+
return { nome, comando, ok: true, code: 0, causa: 'exit', duracaoMs: 0, executado: true,
|
|
695
|
+
resumo: `reaproveitado das lanes ${lanes.join(', ')} desta rodada`,
|
|
696
|
+
lane: { reaproveitada: true, lanes, arquivos: pedido.arquivos,
|
|
697
|
+
nomes: nomes ?? (pedido.padrao === undefined ? pedido.arquivos.map((a) => `${a}:todos`) : pedido.arquivos.map((a) => `${a}:/${pedido.padrao}/`)),
|
|
698
|
+
head: esperado.head, ambiente: esperado.ambiente } };
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Reaproveita uma claim somente quando a lane terminou todos os arquivos pedidos no mesmo HEAD e ambiente. Um arquivo
|
|
702
|
+
* inteiro verde e evidencia mais forte que `--test-name-pattern`: o padrao fica registrado em `nomes`.
|
|
703
|
+
*/
|
|
704
|
+
function executorDaClaimPelasLanes(base, evidencias, esperado) {
|
|
705
|
+
const assinatura = assinaturaDoAmbiente(esperado.ambiente);
|
|
706
|
+
return (nome, comando, cwd) => {
|
|
707
|
+
const executar = esperado.executorEquivalente ?? base;
|
|
708
|
+
if (esperado.podeReaproveitar?.() === false)
|
|
709
|
+
return executar(nome, comando, cwd);
|
|
710
|
+
const elos = elosDoComando(comando);
|
|
711
|
+
if (!elos)
|
|
712
|
+
return base(nome, comando, cwd);
|
|
713
|
+
const planejados = [];
|
|
714
|
+
for (const elo of elos) {
|
|
715
|
+
if (comandoGrepQ(elo)) {
|
|
716
|
+
planejados.push({ tipo: 'grep', comando: elo });
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const tap = auditoriaTap(elo, cwd);
|
|
720
|
+
const pedido = tap?.pedido ?? comandoNodeTest(elo, cwd);
|
|
721
|
+
if (!pedido)
|
|
722
|
+
return base(nome, comando, cwd);
|
|
723
|
+
const usadas = evidencias.filter((e) => e.head === esperado.head && e.ambiente === assinatura
|
|
724
|
+
&& e.testes.some((t) => pedido.arquivos.includes(t.arquivo)));
|
|
725
|
+
const cobertos = pedido.arquivos.every((arquivo) => {
|
|
726
|
+
const vistos = usadas.flatMap((e) => e.testes.filter((t) => t.arquivo === arquivo));
|
|
727
|
+
if (vistos.length === 0 || vistos.some((t) => t.estado === 'fail'))
|
|
728
|
+
return false;
|
|
729
|
+
if (vistos.some((t) => t.estado === 'skip' || t.estado === 'todo'))
|
|
730
|
+
return false;
|
|
731
|
+
if (!tap)
|
|
732
|
+
return true;
|
|
733
|
+
const doArquivo = vistos.filter((t) => t.nesting === 0);
|
|
734
|
+
return doArquivo.filter((t) => t.estado === 'pass' && new RegExp(`^${tap.prefixo}[0-9]+:`).test(t.nome)).length >= tap.minimo;
|
|
735
|
+
});
|
|
736
|
+
if (!cobertos)
|
|
737
|
+
return executar(nome, comando, cwd);
|
|
738
|
+
const nomes = tap ? pedido.arquivos.map((a) => `${a}:${tap.prefixo}>=${tap.minimo};sem-falha;sem-skip`) : undefined;
|
|
739
|
+
planejados.push({ tipo: 'lane', resultado: resultadoReaproveitado(nome, elo, pedido, usadas, { head: esperado.head, ambiente: assinatura }, nomes) });
|
|
740
|
+
}
|
|
741
|
+
let duracaoMs = 0;
|
|
742
|
+
const resumos = [];
|
|
743
|
+
let ultimo = null;
|
|
744
|
+
const lanes = new Set(), arquivos = new Set(), nomes = new Set();
|
|
745
|
+
for (const item of planejados) {
|
|
746
|
+
const r = item.tipo === 'grep' ? base(nome, item.comando, cwd) : item.resultado;
|
|
747
|
+
duracaoMs += r.duracaoMs ?? 0;
|
|
748
|
+
resumos.push(r.resumo);
|
|
749
|
+
ultimo = r;
|
|
750
|
+
if (!r.ok)
|
|
751
|
+
return { ...r, comando, comandoExecutado: item.tipo === 'grep' ? item.comando : r.comando, duracaoMs };
|
|
752
|
+
for (const lane of r.lane?.lanes ?? [])
|
|
753
|
+
lanes.add(lane);
|
|
754
|
+
for (const arquivo of r.lane?.arquivos ?? [])
|
|
755
|
+
arquivos.add(arquivo);
|
|
756
|
+
for (const n of r.lane?.nomes ?? [])
|
|
757
|
+
nomes.add(n);
|
|
758
|
+
}
|
|
759
|
+
if (!ultimo || lanes.size === 0)
|
|
760
|
+
return base(nome, comando, cwd);
|
|
761
|
+
return { ...ultimo, nome, comando, duracaoMs, resumo: resumos.filter(Boolean).join('\n'),
|
|
762
|
+
lane: { reaproveitada: true, lanes: [...lanes], arquivos: [...arquivos], nomes: [...nomes], head: esperado.head, ambiente: assinatura } };
|
|
763
|
+
};
|
|
764
|
+
}
|
package/dist/check-paralelo.js
CHANGED
|
@@ -51,13 +51,13 @@ const node_child_process_1 = require("node:child_process");
|
|
|
51
51
|
const fs = __importStar(require("node:fs"));
|
|
52
52
|
const os = __importStar(require("node:os"));
|
|
53
53
|
const path = __importStar(require("node:path"));
|
|
54
|
+
const agendador_cpu_1 = require("./agendador-cpu");
|
|
54
55
|
const TIMEOUT_BIN = '/usr/bin/timeout';
|
|
55
56
|
const NICE_BIN = '/usr/bin/nice';
|
|
56
|
-
/** O argv de um item: `nice` (se pedido e existir), `timeout(1)` (se existir) e `bash -c`. */
|
|
57
|
+
/** O argv de um item: `nice` (se pedido e existir), `timeout(1)` (se existir) e o `argv` do item ou `bash -c`. */
|
|
57
58
|
function argvDoItem(item, comGrupo = fs.existsSync(TIMEOUT_BIN), comNice = fs.existsSync(NICE_BIN)) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
: ['bash', '-c', item.comando];
|
|
59
|
+
const programa = item.argv?.length ? item.argv : ['bash', '-c', item.comando];
|
|
60
|
+
const base = comGrupo ? [TIMEOUT_BIN, '--kill-after=15', `${Math.ceil(item.prazoMs / 1000)}`, ...programa] : programa;
|
|
61
61
|
return item.nice && comNice ? [NICE_BIN, '-n', String(item.nice), ...base] : base;
|
|
62
62
|
}
|
|
63
63
|
/** As vagas do grupo: no maximo `max` comandos ao mesmo tempo, na ordem em que pediram. */
|
|
@@ -83,12 +83,24 @@ function vagas(max) {
|
|
|
83
83
|
*/
|
|
84
84
|
async function rodarEspecificacao(esp) {
|
|
85
85
|
const comGrupo = fs.existsSync(TIMEOUT_BIN);
|
|
86
|
-
const
|
|
86
|
+
const usaAgendador = esp.itens.some((i) => i.papelCpu === 'hermetica');
|
|
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;
|
|
89
|
+
const limite = vagas(esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : itens.length);
|
|
87
90
|
const porNome = new Map();
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const indice = new Map(itens.map((item, i) => [item.nome, i]));
|
|
92
|
+
const vezDoItem = (item, i) => {
|
|
93
|
+
const existente = porNome.get(item.nome);
|
|
94
|
+
if (existente)
|
|
95
|
+
return existente;
|
|
96
|
+
// O primeiro `then` deixa a promessa entrar no mapa antes de resolver dependencias; assim `apos` funciona mesmo
|
|
97
|
+
// quando o plano põe uma dependencia que aparece depois na ordem canonica dos resultados.
|
|
98
|
+
const vez = Promise.resolve().then(async () => {
|
|
99
|
+
for (const nome of item.apos ?? []) {
|
|
100
|
+
const j = indice.get(nome);
|
|
101
|
+
if (j !== undefined)
|
|
102
|
+
await vezDoItem(itens[j], j);
|
|
103
|
+
}
|
|
92
104
|
await limite.tomar();
|
|
93
105
|
try {
|
|
94
106
|
return await lancar(item, i);
|
|
@@ -96,11 +108,12 @@ async function rodarEspecificacao(esp) {
|
|
|
96
108
|
finally {
|
|
97
109
|
limite.soltar();
|
|
98
110
|
}
|
|
99
|
-
})
|
|
111
|
+
});
|
|
100
112
|
porNome.set(item.nome, vez);
|
|
101
113
|
return vez;
|
|
102
|
-
}
|
|
103
|
-
|
|
114
|
+
};
|
|
115
|
+
const terminados = await Promise.all(itens.map(vezDoItem));
|
|
116
|
+
fs.writeFileSync(path.join(esp.dir, 'resultado.json'), JSON.stringify({ terminados, ...(plano ? { agendamentoCpu: plano } : {}) }), { mode: 0o600 });
|
|
104
117
|
function lancar(item, i) {
|
|
105
118
|
return new Promise((resolve) => {
|
|
106
119
|
const saida = fs.openSync(path.join(esp.dir, `${i}.out`), 'w', 0o600);
|
|
@@ -127,12 +140,50 @@ async function rodarEspecificacao(esp) {
|
|
|
127
140
|
});
|
|
128
141
|
}
|
|
129
142
|
}
|
|
143
|
+
/** Aplica a mesma conta aos processos, as lanes e aos conteineres que eles criarem. */
|
|
144
|
+
function aplicarPlano(originais, plano) {
|
|
145
|
+
const itens = originais.map((item) => ({ ...item, ambiente: { ...item.ambiente } }));
|
|
146
|
+
const porPapel = (papel) => itens.filter((i) => i.papelCpu === papel);
|
|
147
|
+
const hermetica = porPapel('hermetica')[0];
|
|
148
|
+
const varredura = porPapel('varredura')[0];
|
|
149
|
+
const integracoes = porPapel('integracao');
|
|
150
|
+
const outros = porPapel('outro');
|
|
151
|
+
const depender = (item, nomes) => {
|
|
152
|
+
if (!item)
|
|
153
|
+
return;
|
|
154
|
+
item.apos = [...new Set([...(item.apos ?? []), ...nomes.filter((n) => !!n && n !== item.nome)])];
|
|
155
|
+
};
|
|
156
|
+
const ambiente = (item, valores) => {
|
|
157
|
+
if (item)
|
|
158
|
+
item.ambiente = { ...item.ambiente, ...valores };
|
|
159
|
+
};
|
|
160
|
+
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 });
|
|
162
|
+
ambiente(varredura, {
|
|
163
|
+
ORK_CHECK_VARREDURA_PARALELA: String(plano.ferramentasDaVarredura),
|
|
164
|
+
[agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer,
|
|
165
|
+
});
|
|
166
|
+
let anterior = varredura?.nome;
|
|
167
|
+
for (const item of integracoes) {
|
|
168
|
+
ambiente(item, { [agendador_cpu_1.ENV_CPUS_DA_LANE]: String(plano.cpusIntegracao), [agendador_cpu_1.ENV_CPUS_DOCKER]: cpusContainer });
|
|
169
|
+
depender(item, [anterior]);
|
|
170
|
+
anterior = item.nome;
|
|
171
|
+
}
|
|
172
|
+
if (plano.saturado)
|
|
173
|
+
depender(varredura, [hermetica?.nome]);
|
|
174
|
+
// Comandos sem papel conhecido nao disputam CPU com as tres pecas: fecham a cadeia, um por vez.
|
|
175
|
+
for (const item of outros) {
|
|
176
|
+
depender(item, [hermetica?.nome, anterior]);
|
|
177
|
+
anterior = item.nome;
|
|
178
|
+
}
|
|
179
|
+
return itens;
|
|
180
|
+
}
|
|
130
181
|
/**
|
|
131
182
|
* A espera do grupo inteiro: o maior prazo, ou a soma deles quando ha fila (item com `apos` ou vagas a menos que itens),
|
|
132
183
|
* que e o teto do pior caso.
|
|
133
184
|
*/
|
|
134
185
|
function esperaDoGrupo(itens, maxParalelo) {
|
|
135
|
-
const comFila = itens.some((i) => i.apos?.length) || (!!maxParalelo && maxParalelo < itens.length);
|
|
186
|
+
const comFila = itens.some((i) => i.apos?.length || i.papelCpu) || (!!maxParalelo && maxParalelo < itens.length);
|
|
136
187
|
return comFila ? itens.reduce((t, i) => t + i.prazoMs, 0) : Math.max(...itens.map((i) => i.prazoMs));
|
|
137
188
|
}
|
|
138
189
|
/**
|
|
@@ -145,18 +196,19 @@ function rodarEmParalelo(itens, ambiente = process.env, opcoes = {}) {
|
|
|
145
196
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ork-check-paralelo-'));
|
|
146
197
|
try {
|
|
147
198
|
const especificacao = path.join(dir, 'especificacao.json');
|
|
148
|
-
const esp = { itens, dir, ...(opcoes.maxParalelo ? { maxParalelo: opcoes.maxParalelo } : {})
|
|
199
|
+
const esp = { itens, dir, ...(opcoes.maxParalelo ? { maxParalelo: opcoes.maxParalelo } : {}),
|
|
200
|
+
...(opcoes.cpus ? { cpus: opcoes.cpus } : {}), ...(opcoes.medicaoCpu ? { medicaoCpu: opcoes.medicaoCpu } : {}) };
|
|
149
201
|
fs.writeFileSync(especificacao, JSON.stringify(esp), { mode: 0o600 });
|
|
150
202
|
const prazo = esperaDoGrupo(itens, opcoes.maxParalelo) + 60_000;
|
|
151
203
|
const r = (0, node_child_process_1.spawnSync)(process.execPath, [__filename, especificacao], { env: ambiente, encoding: 'utf8', timeout: prazo,
|
|
152
204
|
killSignal: 'SIGKILL' });
|
|
153
|
-
let
|
|
205
|
+
let lido = { terminados: [] };
|
|
154
206
|
try {
|
|
155
|
-
|
|
207
|
+
lido = JSON.parse(fs.readFileSync(path.join(dir, 'resultado.json'), 'utf8'));
|
|
156
208
|
}
|
|
157
209
|
catch { /* abaixo */ }
|
|
158
|
-
|
|
159
|
-
const t = terminados[i];
|
|
210
|
+
const processos = itens.map((item, i) => {
|
|
211
|
+
const t = lido.terminados[i];
|
|
160
212
|
const ler = (sufixo) => { try {
|
|
161
213
|
return fs.readFileSync(path.join(dir, `${i}.${sufixo}`), 'utf8');
|
|
162
214
|
}
|
|
@@ -172,6 +224,9 @@ function rodarEmParalelo(itens, ambiente = process.env, opcoes = {}) {
|
|
|
172
224
|
return { nome: item.nome, ok: t.code === 0, code: t.code ?? -1, signal: t.signal, ...(t.error ? { error: t.error } : {}),
|
|
173
225
|
stdout: ler('out'), stderr: ler('err'), duracaoMs: t.duracaoMs, comGrupo: t.comGrupo };
|
|
174
226
|
});
|
|
227
|
+
if (lido.agendamentoCpu)
|
|
228
|
+
processos.agendamentoCpu = lido.agendamentoCpu;
|
|
229
|
+
return processos;
|
|
175
230
|
}
|
|
176
231
|
finally {
|
|
177
232
|
fs.rmSync(dir, { recursive: true, force: true });
|
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
|
-
|
|
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(
|
|
396
|
+
avisos.push(bloqueiaNova ? anteriorAoLint(linha) : linha);
|
|
396
397
|
}
|
|
397
398
|
}
|
|
398
399
|
return { recusas, avisos };
|
package/dist/claim-lint.js
CHANGED
|
@@ -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
|
@@ -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);
|