@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.
Files changed (65) hide show
  1. package/adapters/openclaw/openclaw.plugin.json +1 -1
  2. package/adapters/openclaw/package.json +1 -1
  3. package/dist/adapters/claude-bg.js +6 -6
  4. package/dist/adapters/grok-events.js +227 -0
  5. package/dist/adapters/grok-mcp-servidor.js +612 -0
  6. package/dist/adapters/grok-mcp.js +178 -0
  7. package/dist/adapters/grok.js +686 -0
  8. package/dist/agendador-cpu.js +92 -0
  9. package/dist/branch-de-estado.js +7 -2
  10. package/dist/check-agil.js +48 -20
  11. package/dist/check-paralelo.js +73 -18
  12. package/dist/conducao.js +5 -0
  13. package/dist/docs.js +9 -16
  14. package/dist/doctor-forja.js +1 -3
  15. package/dist/doctor.js +1 -1
  16. package/dist/entrega-pr.js +10 -10
  17. package/dist/fabrica-estado.js +20 -11
  18. package/dist/horario.js +4 -1
  19. package/dist/index.js +20 -10
  20. package/dist/integracoes-locais.js +2 -0
  21. package/dist/locale-accounts.js +11 -3
  22. package/dist/locale-ajuda.js +10 -6
  23. package/dist/locale-forja.js +2 -4
  24. package/dist/locale-operacao-restante.js +2 -2
  25. package/dist/locale-retry.js +40 -2
  26. package/dist/locale-ship.js +10 -2
  27. package/dist/manifest.js +6 -1
  28. package/dist/mcp-install.js +22 -12
  29. package/dist/mcp-server.js +42 -16
  30. package/dist/merge-da-entrega.js +85 -0
  31. package/dist/network-roadmap.js +18 -14
  32. package/dist/noctum-aceite.js +154 -14
  33. package/dist/noctum-cli.js +69 -12
  34. package/dist/noctum-entrega.js +55 -7
  35. package/dist/noctum-envelope.js +1 -1
  36. package/dist/noctum-execucao.js +5 -1
  37. package/dist/noctum-fechamento.js +18 -7
  38. package/dist/noctum-fila.js +6 -1
  39. package/dist/noctum-guarda.js +81 -4
  40. package/dist/noctum-maquinas.js +5 -1
  41. package/dist/noctum-modo.js +35 -7
  42. package/dist/noctum-reconciliacao.js +4 -1
  43. package/dist/noctum-rede.js +102 -0
  44. package/dist/noctum-relatorio.js +44 -10
  45. package/dist/noctum-retomada-rede.js +71 -0
  46. package/dist/noctum-vigia.js +49 -9
  47. package/dist/parado-no-condutor.js +14 -15
  48. package/dist/phase.js +43 -15
  49. package/dist/postura-local.js +7 -3
  50. package/dist/preflight.js +2 -1
  51. package/dist/ratelimit.js +1 -0
  52. package/dist/retry.js +103 -10
  53. package/dist/runtime-context.js +165 -45
  54. package/dist/runtime-escada.js +7 -1
  55. package/dist/runtime-profiles.js +27 -17
  56. package/dist/runtimes.js +42 -5
  57. package/dist/security-scan/agil.js +18 -7
  58. package/dist/security-scan/executor.js +8 -0
  59. package/dist/session-watcher-grok.js +425 -0
  60. package/dist/session-watcher.js +19 -4
  61. package/dist/sessoes-contas.js +33 -5
  62. package/dist/setup.js +11 -11
  63. package/dist/varredura-acoes.js +8 -15
  64. package/dist/verify.js +26 -10
  65. 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
+ }
@@ -192,12 +192,17 @@ function gravarNaBranch(raiz, remoto, branch, ponta, mudancas, mensagem, prefixo
192
192
  exigirGit(raiz, ['read-tree', ponta], prefixo, { env });
193
193
  else
194
194
  exigirGit(raiz, ['read-tree', '--empty'], prefixo, { env });
195
- for (const m of mudancas) {
195
+ for (const [i, m] of mudancas.entries()) {
196
196
  if (m.conteudo === null) {
197
197
  exigirGit(raiz, ['update-index', '--force-remove', m.caminho], prefixo, { env });
198
198
  }
199
199
  else {
200
- const blob = exigirGit(raiz, ['hash-object', '-w', '--stdin'], prefixo, { input: m.conteudo }).trim();
200
+ // `spawnSync` nem sempre fecha o pipe de entrada em runtimes hospedados, deixando o
201
+ // `hash-object --stdin` preso mesmo depois de receber todos os bytes. Um arquivo privado no
202
+ // mesmo diretorio temporario conserva o conteudo exato e torna o termino deterministico.
203
+ const conteudo = path.join(path.dirname(indice), `conteudo-${i}`);
204
+ fs.writeFileSync(conteudo, m.conteudo, { mode: 0o600 });
205
+ const blob = exigirGit(raiz, ['hash-object', '-w', conteudo], prefixo).trim();
201
206
  exigirGit(raiz, ['update-index', '--add', '--cacheinfo', `100644,${blob},${m.caminho}`], prefixo, { env });
202
207
  }
203
208
  }
@@ -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 Math.max(2, Math.floor((cpus - 1) / 3));
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. N1 do
315
- * CHECK 2 e N6 do CHECK 4: com uma lane caida sem veredito (fora o caso em que todas as caidas estouraram ou cairam so
316
- * no relogio, que o agregado ja diz por tempo), o `test` nao tem veredito inteiro (`executado` falso): no verify ele
317
- * bloqueia como sem veredito, mesmo com a baseline `ja-falhava`, e na baseline ele e `sem-veredito`, em qualquer ordem das
318
- * lanes; com falha de verdade junto, a causa, os testes e o trecho continuam os dela. A lane sozinha que sai com 124 antes
319
- * do prazo de fora fica sem veredito, mais estrita que o comando unico do Classic, que a compara com a baseline.
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
- // N6 do CHECK 4: so o estouro e o relogio sob steal ja dizem "sem veredito" pelo agregado (a causa ou o relogio dele);
326
- // qualquer outra lane sem veredito deixa o `test` sem veredito inteiro, em qualquer ordem das lanes.
327
- const soPorTempo = deVerdade.length === 0 && semVeredito.length > 0
328
- && semVeredito.every((l) => l.resultado.causa === 'timeout' || (l.resultado.relogioSobSteal?.length ?? 0) > 0);
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: semVeredito.length === 0 || soPorTempo ? lanes.every((l) => l.resultado.executado !== false) : false,
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:'))
@@ -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 base = comGrupo
59
- ? [TIMEOUT_BIN, '--kill-after=15', `${Math.ceil(item.prazoMs / 1000)}`, 'bash', '-c', item.comando]
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 limite = vagas(esp.maxParalelo && esp.maxParalelo > 0 ? esp.maxParalelo : esp.itens.length);
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 terminados = await Promise.all(esp.itens.map((item, i) => {
89
- const vez = (async () => {
90
- for (const nome of item.apos ?? [])
91
- await porNome.get(nome);
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
- fs.writeFileSync(path.join(esp.dir, 'resultado.json'), JSON.stringify(terminados), { mode: 0o600 });
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 terminados = [];
205
+ let lido = { terminados: [] };
154
206
  try {
155
- terminados = JSON.parse(fs.readFileSync(path.join(dir, 'resultado.json'), 'utf8'));
207
+ lido = JSON.parse(fs.readFileSync(path.join(dir, 'resultado.json'), 'utf8'));
156
208
  }
157
209
  catch { /* abaixo */ }
158
- return itens.map((item, i) => {
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, opcoes.pr === true);
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, pr = false) {
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((pr ? aviso : erro)(arquivo, undefined, 'docs.paridade.indice', (m) => m.indiceDiverge(pr), (m) => m.indiceDivergeCorrecao));
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 commits `ship(<thread>)` da base, do mais velho ao mais novo, so pelo git (o CI nao tem ledger).
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
- // Sem `-n 1`: o git corta antes de inverter, e `--reverse -n 1` devolveria o mais recente.
787
- // GO-FIX 2 (achado 4 do CHECK): so a linha de primeiro pai da base e o assunto que COMECA com
788
- // `ship(<thread>)`, como no `mergeDaEntrega`. Commit que cita o merge no corpo, ou o Revert dele, nao e merge.
789
- // RM-047 (fronteira de confiança): a base pode ser o `worktree.base_branch` cru do clone.
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}-*`]);
@@ -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 resto do que decide se o merge `ship(<thread>)` chega a base. So avisa; o projeto nao lido reprova. */
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(((0, runtime_profiles_1.perfilDeDespacho)(x).configDir ?? (0, runtime_profiles_1.perfilDeDespacho)(x).codexHome)) === real(dirAtual));
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
  }
@@ -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
- /** O merge `ship(<thread>)` mais recente na base remota, com o head do PR (segundo pai). */
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
- const r = (0, util_1.exec)('git', ['log', `refs/remotes/${remoto}/${base}`, '--first-parent', '--format=%H%x09%P%x09%s', `--grep=^ship(${thread})`], raiz);
43
- if (!r.ok)
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
- return null;
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
@@ -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: `ship(<thread>): ...` e o assunto do merge que o fluxo de
137
- * entrega usa, o mesmo fato que o `ork docs sincronizar` le. Um `git log` so, para todas.
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
- if (!r.ok)
149
- return entregas;
150
- for (const linha of r.stdout.split('\n')) {
151
- const m = /^([0-9a-f]+)\tship\(([A-Za-z0-9._-]+)\)/.exec(linha);
152
- // O log vem do mais novo para o mais velho: a primeira entrega de cada thread e a que vale.
153
- if (m && !entregas.has(m[2]))
154
- entregas.set(m[2], m[1]);
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
- return new Intl.DateTimeFormat(locale === 'en' ? 'en-US' : 'pt-BR', { timeZone: fuso, timeZoneName: 'short' })
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 '';