@orkastery/cli 0.6.0 → 0.6.1
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/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/dist/adapters/claude-bg.js +6 -6
- package/dist/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +612 -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/check-agil.js +48 -20
- package/dist/check-paralelo.js +73 -18
- package/dist/conducao.js +5 -0
- package/dist/docs.js +9 -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/horario.js +4 -1
- package/dist/index.js +20 -10
- package/dist/integracoes-locais.js +2 -0
- package/dist/locale-accounts.js +11 -3
- package/dist/locale-ajuda.js +10 -6
- package/dist/locale-forja.js +2 -4
- package/dist/locale-operacao-restante.js +2 -2
- package/dist/locale-retry.js +40 -2
- package/dist/locale-ship.js +10 -2
- package/dist/manifest.js +6 -1
- package/dist/mcp-install.js +22 -12
- package/dist/mcp-server.js +42 -16
- 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/phase.js +43 -15
- package/dist/postura-local.js +7 -3
- package/dist/preflight.js +2 -1
- package/dist/ratelimit.js +1 -0
- package/dist/retry.js +103 -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/executor.js +8 -0
- package/dist/session-watcher-grok.js +425 -0
- package/dist/session-watcher.js +19 -4
- package/dist/sessoes-contas.js +33 -5
- package/dist/setup.js +11 -11
- package/dist/varredura-acoes.js +8 -15
- package/dist/verify.js +26 -10
- package/package.json +2 -2
|
@@ -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
|
+
}
|
package/dist/branch-de-estado.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/check-agil.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.prepararNoAgile = prepararNoAgile;
|
|
|
43
43
|
exports.executorDaClaimNoAgile = executorDaClaimNoAgile;
|
|
44
44
|
exports.refazerPreparo = refazerPreparo;
|
|
45
45
|
exports.testDasLanes = testDasLanes;
|
|
46
|
+
exports.porQueDasLanes = porQueDasLanes;
|
|
46
47
|
exports.comandosNoAgile = comandosNoAgile;
|
|
47
48
|
exports.pecasDoRelatorio = pecasDoRelatorio;
|
|
48
49
|
exports.listasDasLanes = listasDasLanes;
|
|
@@ -74,13 +75,11 @@ const fs = __importStar(require("node:fs"));
|
|
|
74
75
|
const os = __importStar(require("node:os"));
|
|
75
76
|
const path = __importStar(require("node:path"));
|
|
76
77
|
const check_modo_1 = require("./check-modo");
|
|
78
|
+
const agendador_cpu_1 = require("./agendador-cpu");
|
|
77
79
|
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
|
-
*/
|
|
80
|
+
/** Quantas ferramentas cabem na fatia de duas CPUs reservada pelo plano paralelo. */
|
|
82
81
|
function paralelismoDaVarreduraNoGrupo(cpus = os.availableParallelism()) {
|
|
83
|
-
return
|
|
82
|
+
return (0, agendador_cpu_1.planejarCpuDoGrupo)(cpus, { carga1: 0, stealPct: 0 }).ferramentasDaVarredura;
|
|
84
83
|
}
|
|
85
84
|
const SEPARADOR = ' && ';
|
|
86
85
|
/** Os passos de um comando encadeado por `&&` no nivel do texto (o preparo e escrito assim no manifesto). */
|
|
@@ -311,21 +310,25 @@ function caiuDeVerdade(r) {
|
|
|
311
310
|
/**
|
|
312
311
|
* 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
312
|
* 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
|
-
*
|
|
313
|
+
* O relogio sob steal so vale quando toda lane que caiu caiu so nele, e a pior causa so vale sem falha de verdade.
|
|
314
|
+
*
|
|
315
|
+
* RM-074 (F4): a lane que nao terminou sozinha fora do tempo (sinal, comando ausente, o codigo de quem nao a deixou
|
|
316
|
+
* terminar, de 124 em diante antes do prazo de fora) da ao `test` o codigo dela, e o `test` segue com veredito, como o
|
|
317
|
+
* comando unico do Classic que terminasse assim: no verify ele e comparado com a baseline (regressao, ja falhava ou falha
|
|
318
|
+
* sem baseline), e na baseline ele e `sem-veredito`, porque o codigo nao e da propria lane, em qualquer ordem das lanes;
|
|
319
|
+
* com falha de verdade junto, a causa, os testes e o trecho continuam os dela. So a lane que nao rodou (`executado`
|
|
320
|
+
* falso, como o comando que o Classic nao lancou) deixa o `test` sem veredito inteiro. O caso por tempo segue o de antes
|
|
321
|
+
* (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
|
|
322
|
+
* `test` sem veredito inteiro, e as caidas so por tempo dao o agregado por tempo.
|
|
320
323
|
*/
|
|
321
324
|
function testDasLanes(lanes) {
|
|
322
325
|
const caidas = lanes.filter((l) => !l.resultado.ok);
|
|
323
326
|
const deVerdade = caidas.filter((l) => caiuDeVerdade(l.resultado));
|
|
324
327
|
const semVeredito = caidas.filter((l) => !caiuDeVerdade(l.resultado));
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
const
|
|
328
|
-
|
|
328
|
+
const porTempo = (r) => r.causa === 'timeout' || (r.relogioSobSteal?.length ?? 0) > 0;
|
|
329
|
+
// RM-074 (F4): a lane sem veredito fora do tempo da o codigo ao `test`, que o verify compara com a baseline.
|
|
330
|
+
const foraDoTempo = semVeredito.filter((l) => !porTempo(l.resultado));
|
|
331
|
+
const tempoComFalha = deVerdade.length > 0 && semVeredito.some((l) => porTempo(l.resultado));
|
|
329
332
|
const juntar = (f) => lanes.flatMap((l) => f(l.resultado) ?? []);
|
|
330
333
|
const causas = lanes.map((l) => l.resultado.causa).filter((c) => !!c);
|
|
331
334
|
const causa = deVerdade.length ? deVerdade[0].resultado.causa ?? 'exit' : PIOR_CAUSA.find((c) => causas.includes(c));
|
|
@@ -338,12 +341,12 @@ function testDasLanes(lanes) {
|
|
|
338
341
|
nome: 'test',
|
|
339
342
|
comando: lanes.map((l) => `${l.lane}: ${l.resultado.comando}`).join('; '),
|
|
340
343
|
ok: caidas.length === 0,
|
|
341
|
-
code: (deVerdade[0] ?? caidas[0])?.resultado.code ?? 0,
|
|
344
|
+
code: (foraDoTempo[0] ?? deVerdade[0] ?? caidas[0])?.resultado.code ?? 0,
|
|
342
345
|
resumo: (caidas.length ? caidas : lanes).map((l) => `[${l.lane}] ${l.resultado.resumo}`).join('\n').slice(-800),
|
|
343
346
|
...(causa ? { causa } : {}),
|
|
344
347
|
prazoMs: Math.max(...lanes.map((l) => l.resultado.prazoMs ?? 0)),
|
|
345
348
|
duracaoMs: Math.max(...lanes.map((l) => l.resultado.duracaoMs ?? 0)),
|
|
346
|
-
executado:
|
|
349
|
+
executado: !tempoComFalha && lanes.every((l) => l.resultado.executado !== false),
|
|
347
350
|
...(testes.length ? { testeQueCaiu: testes } : {}),
|
|
348
351
|
...(falhas.length ? { falhasDeTeste: falhas } : {}),
|
|
349
352
|
...(trechos.length ? { trecho: trechos.join('\n').slice(0, 1024) } : {}),
|
|
@@ -353,6 +356,30 @@ function testDasLanes(lanes) {
|
|
|
353
356
|
...(l.resultado.causa ? { causa: l.resultado.causa } : {}), duracaoMs: l.resultado.duracaoMs ?? 0 })),
|
|
354
357
|
};
|
|
355
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* RM-074 (F4): por que cada lane caida do `test` ficou como ficou, para o texto do gate dizer qual lane ficou sem veredito
|
|
361
|
+
* 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
|
|
362
|
+
* toda lane caida caiu so nele. Vazio fora do Agile (sem lanes) e com todas as lanes ok.
|
|
363
|
+
*/
|
|
364
|
+
function porQueDasLanes(c) {
|
|
365
|
+
const caidas = (c.lanes ?? []).filter((l) => !l.ok);
|
|
366
|
+
const porque = (l) => {
|
|
367
|
+
if (l.causa === 'timeout')
|
|
368
|
+
return 'estourou o prazo';
|
|
369
|
+
if (l.causa === 'nao-encontrado')
|
|
370
|
+
return `comando nao encontrado (codigo ${l.code})`;
|
|
371
|
+
if (l.code === -1)
|
|
372
|
+
return 'sem codigo de saida: nao rodou, ou o auxiliar do grupo morreu antes de gravar o resultado';
|
|
373
|
+
if (l.causa === 'sinal')
|
|
374
|
+
return `morta por sinal (codigo ${l.code})`;
|
|
375
|
+
if (c.relogioSobSteal?.length)
|
|
376
|
+
return `caiu so em teste com relogio sob steal (${c.relogioSobSteal.join(', ')})`;
|
|
377
|
+
if (Number.isInteger(l.code) && l.code >= 1 && l.code <= MAIOR_CODIGO_DA_LANE)
|
|
378
|
+
return `caiu com o codigo ${l.code}`;
|
|
379
|
+
return `saiu com ${l.code}, o codigo de quem nao a deixou terminar`;
|
|
380
|
+
};
|
|
381
|
+
return caidas.map((l) => `lane ${l.lane}: ${porque(l)}`).join('; ');
|
|
382
|
+
}
|
|
356
383
|
/**
|
|
357
384
|
* Os comandos do manifesto no Agile (D4): `build` reaproveitado do preparo com a guarda valendo; o resto num grupo
|
|
358
385
|
* paralelo, com as lanes no lugar do `verify.test`, so com a guarda valendo; sem ela, tudo em serie (o Classic).
|
|
@@ -391,16 +418,17 @@ function comandosNoAgile(manifesto, cwd, lista, executor, grupo, guarda, preparo
|
|
|
391
418
|
if (c.nome === 'test' && lanesDeclaradas.length > 0) {
|
|
392
419
|
for (const [lane, comando] of lanesDeclaradas) {
|
|
393
420
|
if (depoisDaVarredura.has(lane))
|
|
394
|
-
encadeados.push({ nome: `test:${lane}`, comando, apos: ['security_scan'] });
|
|
421
|
+
encadeados.push({ nome: `test:${lane}`, comando, apos: ['security_scan'], papelCpu: 'integracao' });
|
|
395
422
|
else
|
|
396
|
-
itens.push({ nome: `test:${lane}`, comando });
|
|
423
|
+
itens.push({ nome: `test:${lane}`, comando, papelCpu: lane === 'hermetica' ? 'hermetica' : 'outro' });
|
|
397
424
|
}
|
|
398
425
|
}
|
|
399
426
|
else
|
|
400
|
-
itens.push({ nome: c.nome, comando: c.comando });
|
|
427
|
+
itens.push({ nome: c.nome, comando: c.comando, papelCpu: c.nome === 'security_scan' ? 'varredura' : 'outro' });
|
|
401
428
|
}
|
|
402
429
|
itens.push(...encadeados);
|
|
403
430
|
const resultados = itens.length > 1 ? grupo(itens, cwd) : itens.map((i) => executor(i.nome, i.comando, cwd));
|
|
431
|
+
pecas.push(...(grupo.pecas ?? []));
|
|
404
432
|
const lanes = [];
|
|
405
433
|
itens.forEach((item, i) => {
|
|
406
434
|
if (item.nome.startsWith('test:'))
|
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/conducao.js
CHANGED
|
@@ -113,6 +113,11 @@ exports.CANAIS_DE_CONDUCAO = {
|
|
|
113
113
|
origem: 'sessao do Codex',
|
|
114
114
|
declaracao: 'servidor MCP iniciado com --host codex',
|
|
115
115
|
},
|
|
116
|
+
// RM-067 (F4b, G5): o servidor do grok so serve a sessao filha; o canal e o do host dele, como no codex.
|
|
117
|
+
grok: {
|
|
118
|
+
origem: 'sessao filha do Grok Build',
|
|
119
|
+
declaracao: 'servidor MCP iniciado com --host grok, so na sessao filha vinculada a thread',
|
|
120
|
+
},
|
|
116
121
|
mcp: {
|
|
117
122
|
origem: 'cliente MCP sem host homologado',
|
|
118
123
|
declaracao: 'reservado: o servidor MCP atual exige host homologado e declara o canal dele',
|
package/dist/docs.js
CHANGED
|
@@ -81,6 +81,7 @@ const thread_1 = require("./thread");
|
|
|
81
81
|
const ledger_1 = require("./ledger");
|
|
82
82
|
const branch_de_estado_1 = require("./branch-de-estado");
|
|
83
83
|
const locale_1 = require("./locale");
|
|
84
|
+
const merge_da_entrega_1 = require("./merge-da-entrega");
|
|
84
85
|
exports.DIR_PRODUTO = 'docs/produto';
|
|
85
86
|
exports.DIR_ROADMAP = 'docs/roadmap';
|
|
86
87
|
exports.DIR_PADROES = 'docs/padroes';
|
|
@@ -279,7 +280,7 @@ function verificarDocs(raiz, opcoes = {}) {
|
|
|
279
280
|
verificarTabelasDeEstado(d, achados);
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
|
-
verificarIndices(raiz, docs, achados
|
|
283
|
+
verificarIndices(raiz, docs, achados);
|
|
283
284
|
return { docs, achados };
|
|
284
285
|
}
|
|
285
286
|
function verificarChaves(d, achados) {
|
|
@@ -653,7 +654,7 @@ const INDICES_GERADOS = [
|
|
|
653
654
|
* do mesmo jeito: sem a RM-051 e com a RM-031 de antes. Divergente reprova; vazio (o modelo recem-copiado
|
|
654
655
|
* pelo `ork docs init`) so avisa; sem os marcadores, nao ha indice gerado para conferir.
|
|
655
656
|
*/
|
|
656
|
-
function verificarIndices(raiz, docs, achados
|
|
657
|
+
function verificarIndices(raiz, docs, achados) {
|
|
657
658
|
for (const [arquivo, gerar] of INDICES_GERADOS) {
|
|
658
659
|
const abs = path.join(raiz, arquivo);
|
|
659
660
|
if (!fs.existsSync(abs))
|
|
@@ -668,7 +669,7 @@ function verificarIndices(raiz, docs, achados, pr = false) {
|
|
|
668
669
|
}
|
|
669
670
|
}
|
|
670
671
|
else if (atual !== gerar(docs)) {
|
|
671
|
-
achados.push(
|
|
672
|
+
achados.push(erro(arquivo, undefined, 'docs.paridade.indice', (m) => m.indiceDiverge(false), (m) => m.indiceDivergeCorrecao));
|
|
672
673
|
}
|
|
673
674
|
}
|
|
674
675
|
}
|
|
@@ -779,22 +780,14 @@ function mergeDaThread(raiz, thread, base) {
|
|
|
779
780
|
return null;
|
|
780
781
|
}
|
|
781
782
|
/**
|
|
782
|
-
* Os
|
|
783
|
+
* Os merges da entrega na base, do mais velho ao mais novo, so pelo git (o CI pode nao ter ledger).
|
|
783
784
|
* RM-037 (fatia 3): um lugar so para o `sincronizarDocs` e o `verificarDocs` acharem o mesmo merge.
|
|
784
785
|
*/
|
|
785
786
|
function mergesDaThreadNoGit(raiz, thread, base) {
|
|
786
|
-
//
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (!(0, branch_de_estado_1.branchValida)(base))
|
|
791
|
-
return [];
|
|
792
|
-
const log = git(raiz, ['log', base, '--first-parent', '--reverse', '--format=%H%x09%s', '--fixed-strings', `--grep=ship(${thread})`]);
|
|
793
|
-
if (!log.ok)
|
|
794
|
-
return [];
|
|
795
|
-
return log.saida.split('\n').map((l) => l.split('\t'))
|
|
796
|
-
.filter(([sha, assunto]) => !!sha && typeof assunto === 'string' && assunto.startsWith(`ship(${thread})`))
|
|
797
|
-
.map(([sha]) => sha);
|
|
787
|
+
// O ref resolvido pode ser local, remoto abreviado ou remoto completo; a mensagem padrao do GitLab leva so a base.
|
|
788
|
+
const destino = base.replace(/^refs\/heads\//, '').replace(/^refs\/remotes\/[^/]+\//, '').replace(/^origin\//, '');
|
|
789
|
+
const branch = (0, merge_da_entrega_1.branchRegistradaDaEntrega)(raiz, thread);
|
|
790
|
+
return ((0, merge_da_entrega_1.mergesDasEntregasNaBase)(raiz, base, destino, [{ thread, branch }], true).get(thread) ?? []).map((m) => m.mergeSha);
|
|
798
791
|
}
|
|
799
792
|
function temBranch(raiz, thread) {
|
|
800
793
|
const r = git(raiz, ['for-each-ref', '--format=%(refname:short)', `refs/heads/ork/${thread}-*`, `refs/remotes/origin/ork/${thread}-*`]);
|
package/dist/doctor-forja.js
CHANGED
|
@@ -263,7 +263,7 @@ const PROJETO_DO_GITLAB = zod_1.z.object({
|
|
|
263
263
|
const REPOSITORIO_DO_GITHUB = zod_1.z.object({
|
|
264
264
|
allow_merge_commit: zod_1.z.boolean().optional(), allow_squash_merge: zod_1.z.boolean().optional(), allow_rebase_merge: zod_1.z.boolean().optional(),
|
|
265
265
|
});
|
|
266
|
-
/** O metodo de merge e o
|
|
266
|
+
/** O metodo de merge e o que decide se a entrega chega a base com segundo pai. So avisa; o projeto nao lido reprova. */
|
|
267
267
|
function projetoDaForja(f, api, linha) {
|
|
268
268
|
const m = (0, locale_1.msg)().forja.doctor;
|
|
269
269
|
const caminho = f.tipo === 'gitlab' ? (0, pedidos_1.projetoNaApi)(f.repo) : `repos/${f.repo}`;
|
|
@@ -282,8 +282,6 @@ function projetoDaForja(f, api, linha) {
|
|
|
282
282
|
const avisos = [];
|
|
283
283
|
if (p.merge_method === 'ff')
|
|
284
284
|
avisos.push(m.avisoFf);
|
|
285
|
-
else if (!(p.merge_commit_template ?? '').trimStart().startsWith('%{title}'))
|
|
286
|
-
avisos.push(m.avisoMensagemPadrao);
|
|
287
285
|
if (p.squash_option === 'always' || p.squash_option === 'default_on')
|
|
288
286
|
avisos.push(m.avisoSquash(p.squash_option));
|
|
289
287
|
const detalhe = [m.projetoGitlab(p.merge_method, p.squash_option ?? '?', estado(p.merge_pipelines_enabled), estado(p.merge_trains_enabled)),
|
package/dist/doctor.js
CHANGED
|
@@ -105,7 +105,7 @@ function checarSeparacaoDaConducao(raiz, inventario, env = process.env) {
|
|
|
105
105
|
try {
|
|
106
106
|
if (dirAtual) {
|
|
107
107
|
const p = (0, runtime_profiles_1.lerPerfisComContas)(raiz).perfis.find((x) => x.runtime === runtime &&
|
|
108
|
-
real((
|
|
108
|
+
real((0, runtime_profiles_1.diretorioDoPerfil)((0, runtime_profiles_1.perfilDeDespacho)(x))) === real(dirAtual));
|
|
109
109
|
perfil = p?.id ?? null;
|
|
110
110
|
}
|
|
111
111
|
}
|
package/dist/entrega-pr.js
CHANGED
|
@@ -37,19 +37,19 @@ const util_1 = require("./util");
|
|
|
37
37
|
const executor_1 = require("./forja/executor");
|
|
38
38
|
const registro_1 = require("./forja/registro");
|
|
39
39
|
const locale_1 = require("./locale");
|
|
40
|
-
|
|
40
|
+
const merge_da_entrega_1 = require("./merge-da-entrega");
|
|
41
|
+
const worktree_1 = require("./worktree");
|
|
42
|
+
/** O merge da entrega mais recente na base remota, com o head do PR (segundo pai). */
|
|
41
43
|
function mergeDaEntrega(raiz, thread, remoto, base) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
let branch;
|
|
45
|
+
try {
|
|
46
|
+
branch = (0, merge_da_entrega_1.branchRegistradaDaEntrega)(raiz, thread) ?? (0, worktree_1.branchDaWorktree)((0, thread_1.lerThread)(raiz, thread));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
44
49
|
return null;
|
|
45
|
-
for (const linha of r.stdout.split('\n')) {
|
|
46
|
-
const [sha, pais, assunto] = linha.split('\t');
|
|
47
|
-
if (!sha || !assunto || !assunto.startsWith(`ship(${thread})`))
|
|
48
|
-
continue;
|
|
49
|
-
const segundo = (pais ?? '').split(' ')[1] ?? null;
|
|
50
|
-
return { mergeSha: sha, headSha: segundo };
|
|
51
50
|
}
|
|
52
|
-
|
|
51
|
+
const merge = (0, merge_da_entrega_1.mergesDasEntregasNaBase)(raiz, `refs/remotes/${remoto}/${base}`, base, [{ thread, branch }]).get(thread)?.[0];
|
|
52
|
+
return merge ? { mergeSha: merge.mergeSha, headSha: merge.headSha } : null;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* A thread chega ao SHIP quando o ciclo dela tem SHIP, e so AVANCA: o segundo PR da mesma entrega (os
|
package/dist/fabrica-estado.js
CHANGED
|
@@ -85,6 +85,8 @@ const versao_1 = require("./versao");
|
|
|
85
85
|
const saida_segura_1 = require("./saida-segura");
|
|
86
86
|
const vida_da_thread_1 = require("./vida-da-thread");
|
|
87
87
|
const locale_1 = require("./locale");
|
|
88
|
+
const merge_da_entrega_1 = require("./merge-da-entrega");
|
|
89
|
+
const worktree_1 = require("./worktree");
|
|
88
90
|
exports.CONTRATO_MAQUINA = 'ork.fabrica-maquina/v1';
|
|
89
91
|
exports.BRANCH_DA_FABRICA = 'ork/fabrica-estado';
|
|
90
92
|
/** O diretorio dos retratos na branch; a leitura sem clone (RM-054) le o mesmo. */
|
|
@@ -133,8 +135,8 @@ function arquivoDaMaquina(maquina) {
|
|
|
133
135
|
return `${nome}.json`;
|
|
134
136
|
}
|
|
135
137
|
/**
|
|
136
|
-
* As entregas que ja estao na base
|
|
137
|
-
*
|
|
138
|
+
* As entregas que ja estao na base, pelo mesmo reconhecedor do registro do PR e da paridade de docs.
|
|
139
|
+
* Um `git log` so, para todas as threads deste checkout.
|
|
138
140
|
*/
|
|
139
141
|
function entregasNaBase(raiz, base, remoto = 'origin') {
|
|
140
142
|
// A base vem do manifesto: a ref vai sempre qualificada (`refs/...`), e um valor como
|
|
@@ -142,16 +144,23 @@ function entregasNaBase(raiz, base, remoto = 'origin') {
|
|
|
142
144
|
// Na ordem: a copia remota da base, a branch local, e a base escrita como remota (`origin/main`).
|
|
143
145
|
const candidatas = [`refs/remotes/${remoto}/${base}`, `refs/heads/${base}`, `refs/remotes/${base}`];
|
|
144
146
|
const ref = candidatas.find((c) => (0, branch_de_estado_1.git)(raiz, ['rev-parse', '--verify', '--quiet', c]).ok) ?? candidatas[1];
|
|
145
|
-
// Regex basica do git: o `(` e literal.
|
|
146
|
-
const r = (0, branch_de_estado_1.git)(raiz, ['log', ref, '--format=%h%x09%s', '--grep=^ship(']);
|
|
147
147
|
const entregas = new Map();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
const threads = [];
|
|
149
|
+
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
150
|
+
try {
|
|
151
|
+
threads.push((0, thread_1.lerThread)(raiz, id));
|
|
152
|
+
}
|
|
153
|
+
catch { /* thread ilegivel fica de fora */ }
|
|
154
|
+
}
|
|
155
|
+
const destino = base.startsWith(`${remoto}/`) ? base.slice(remoto.length + 1) : base;
|
|
156
|
+
const reconhecidas = (0, merge_da_entrega_1.mergesDasEntregasNaBase)(raiz, ref, destino, threads.map((t) => ({
|
|
157
|
+
thread: t.id,
|
|
158
|
+
branch: (0, merge_da_entrega_1.branchRegistradaDaEntrega)(raiz, t.id) ?? (0, worktree_1.branchDaWorktree)(t),
|
|
159
|
+
})));
|
|
160
|
+
for (const [thread, merges] of reconhecidas) {
|
|
161
|
+
const merge = merges[0];
|
|
162
|
+
if (merge)
|
|
163
|
+
entregas.set(thread, merge.mergeSha.slice(0, 7));
|
|
155
164
|
}
|
|
156
165
|
return entregas;
|
|
157
166
|
}
|
package/dist/horario.js
CHANGED
|
@@ -215,8 +215,11 @@ function duracaoRelativa(minutos) {
|
|
|
215
215
|
}
|
|
216
216
|
function sigla(fuso, ms, locale = (0, locale_1.localeAtivo)()) {
|
|
217
217
|
try {
|
|
218
|
-
|
|
218
|
+
const valor = new Intl.DateTimeFormat(locale === 'en' ? 'en-US' : 'pt-BR', { timeZone: fuso, timeZoneName: 'short' })
|
|
219
219
|
.formatToParts(new Date(ms)).find(p => p.type === 'timeZoneName')?.value ?? '';
|
|
220
|
+
// RM-074 (F4): o deslocamento zero sai `GMT+0` no Node 20.20.2 e no 22.23.2; um ICU antigo escreve so `GMT`. O texto
|
|
221
|
+
// fica o mesmo em qualquer ICU.
|
|
222
|
+
return valor === 'GMT' ? 'GMT+0' : valor;
|
|
220
223
|
}
|
|
221
224
|
catch {
|
|
222
225
|
return '';
|