@orkastery/cli 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/adapters/claude-code/agents/ork-check.md +1 -1
  2. package/adapters/claude-code/agents/ork-go.md +1 -1
  3. package/adapters/claude-code/agents/ork-goal.md +1 -1
  4. package/adapters/claude-code/agents/ork-master.md +1 -1
  5. package/adapters/claude-code/agents/ork-plan.md +1 -1
  6. package/adapters/claude-code/agents/ork-ship.md +1 -1
  7. package/adapters/claude-code/commands/ork.md +1 -1
  8. package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +2 -0
  9. package/adapters/codex/skills/ork/SKILL.md +1 -1
  10. package/adapters/openclaw/openclaw.plugin.json +1 -1
  11. package/adapters/openclaw/package.json +1 -1
  12. package/assets/adapters-en/claude-code/agents/ork-check.md +1 -1
  13. package/assets/adapters-en/claude-code/agents/ork-go.md +1 -1
  14. package/assets/adapters-en/claude-code/agents/ork-goal.md +1 -1
  15. package/assets/adapters-en/claude-code/agents/ork-master.md +1 -1
  16. package/assets/adapters-en/claude-code/agents/ork-plan.md +1 -1
  17. package/assets/adapters-en/claude-code/agents/ork-ship.md +1 -1
  18. package/assets/adapters-en/claude-code/commands/ork.md +1 -1
  19. package/assets/adapters-en/codex/skills/ork/SKILL.md +1 -1
  20. package/assets/orkmind_fixture.py +4 -1
  21. package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +1 -1
  22. package/assets/skills-en/phases/check-quality/SKILL.md +1 -1
  23. package/assets/skills-en/phases/go-implementation/SKILL.md +1 -1
  24. package/dist/adapters/claude-bg.js +9 -7
  25. package/dist/adapters/grok-events.js +227 -0
  26. package/dist/adapters/grok-mcp-servidor.js +622 -0
  27. package/dist/adapters/grok-mcp.js +178 -0
  28. package/dist/adapters/grok.js +686 -0
  29. package/dist/agendador-cpu.js +92 -0
  30. package/dist/branch-de-estado.js +7 -2
  31. package/dist/changelog-check.js +217 -0
  32. package/dist/check-agil.js +289 -25
  33. package/dist/check-paralelo.js +73 -18
  34. package/dist/ci.js +3 -2
  35. package/dist/claim-lint.js +29 -0
  36. package/dist/claims.js +4 -0
  37. package/dist/conducao.js +5 -0
  38. package/dist/docs.js +16 -16
  39. package/dist/doctor-forja.js +1 -3
  40. package/dist/doctor.js +1 -1
  41. package/dist/entrega-pr.js +10 -10
  42. package/dist/fabrica-estado.js +20 -11
  43. package/dist/fila-pesada.js +121 -11
  44. package/dist/hitl-classificacao.js +1 -0
  45. package/dist/horario.js +4 -1
  46. package/dist/index.js +63 -35
  47. package/dist/integracoes-locais.js +2 -0
  48. package/dist/intelligence-graph-cli.js +27 -9
  49. package/dist/locale-accounts.js +11 -3
  50. package/dist/locale-ajuda.js +10 -6
  51. package/dist/locale-claims.js +16 -2
  52. package/dist/locale-forja.js +2 -4
  53. package/dist/locale-grafo.js +56 -1
  54. package/dist/locale-maestro.js +38 -0
  55. package/dist/locale-network.js +8 -6
  56. package/dist/locale-onboarding.js +14 -0
  57. package/dist/locale-operacao-restante.js +2 -2
  58. package/dist/locale-preflight.js +80 -0
  59. package/dist/locale-projetos.js +6 -0
  60. package/dist/locale-prompts.js +8 -8
  61. package/dist/locale-pulse.js +4 -0
  62. package/dist/locale-retry.js +56 -2
  63. package/dist/locale-security-scan.js +35 -1
  64. package/dist/locale-ship.js +10 -2
  65. package/dist/locale-verify.js +15 -0
  66. package/dist/locale.js +24 -7
  67. package/dist/maestro-cli.js +12 -17
  68. package/dist/manifest.js +6 -1
  69. package/dist/mcp-install.js +24 -13
  70. package/dist/mcp-server.js +62 -33
  71. package/dist/mcp-verify-async.js +353 -0
  72. package/dist/mcp-verify-worker.js +17 -0
  73. package/dist/mcp-verify.js +10 -1
  74. package/dist/merge-da-entrega.js +85 -0
  75. package/dist/network-roadmap.js +18 -14
  76. package/dist/noctum-aceite.js +154 -14
  77. package/dist/noctum-cli.js +69 -12
  78. package/dist/noctum-entrega.js +55 -7
  79. package/dist/noctum-envelope.js +1 -1
  80. package/dist/noctum-execucao.js +5 -1
  81. package/dist/noctum-fechamento.js +18 -7
  82. package/dist/noctum-fila.js +6 -1
  83. package/dist/noctum-guarda.js +81 -4
  84. package/dist/noctum-maquinas.js +5 -1
  85. package/dist/noctum-modo.js +35 -7
  86. package/dist/noctum-reconciliacao.js +4 -1
  87. package/dist/noctum-rede.js +102 -0
  88. package/dist/noctum-relatorio.js +44 -10
  89. package/dist/noctum-retomada-rede.js +71 -0
  90. package/dist/noctum-vigia.js +49 -9
  91. package/dist/parado-no-condutor.js +14 -15
  92. package/dist/pedido-conducao.js +51 -0
  93. package/dist/phase.js +43 -15
  94. package/dist/postura-local.js +7 -3
  95. package/dist/preflight.js +27 -27
  96. package/dist/prompts.js +4 -2
  97. package/dist/pulse.js +4 -2
  98. package/dist/ratelimit.js +1 -0
  99. package/dist/rede.js +24 -12
  100. package/dist/retry.js +115 -10
  101. package/dist/runtime-context.js +165 -45
  102. package/dist/runtime-escada.js +7 -1
  103. package/dist/runtime-profiles.js +27 -17
  104. package/dist/runtimes.js +42 -5
  105. package/dist/security-scan/agil.js +18 -7
  106. package/dist/security-scan/cli.js +2 -2
  107. package/dist/security-scan/executor.js +8 -0
  108. package/dist/security-scan/report.js +1 -1
  109. package/dist/session-watcher-grok.js +425 -0
  110. package/dist/session-watcher.js +52 -11
  111. package/dist/sessoes-contas.js +33 -5
  112. package/dist/setup.js +11 -11
  113. package/dist/varredura-acoes.js +8 -15
  114. package/dist/verify.js +191 -43
  115. package/package.json +2 -2
  116. package/skills/core/orkastery-bootstrap/SKILL.md +1 -1
  117. package/skills/phases/check-quality/SKILL.md +1 -1
  118. package/skills/phases/go-implementation/SKILL.md +1 -1
@@ -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
- * - as claims, uma por vez, contra o produto do preparo: a claim que comeca pelos passos do `verify.preparo` roda sem
61
+ * - quando ha lanes, elas rodam antes das claims; `node --test` dos mesmos arquivos, HEAD e ambiente usa o resultado
62
+ * nominal da lane, enquanto comando aberto ou evidencia divergente roda inteiro; se uma claim posterior mudar o
63
+ * produto, preparo e grupo rodam outra vez sobre o produto final;
64
+ * - as demais claims, uma por vez, contra o produto do preparo: a claim que comeca pelos passos do `verify.preparo` roda sem
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 fica: o que vem antes dele pode mudar a entrada do build (o mutante que edita o fonte e compila). Quando
67
+ * da cadeia so sai quando tudo antes dele e uma leitura `grep -q` fechada; qualquer outro elo pode mudar a entrada do
68
+ * build (como o mutante que edita o fonte e compila), entao a cadeia fica inteira. Quando
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 antes do grupo, com a peca
70
+ * claim que o reaproveitaria, ate `MAXIMO_DE_PREPAROS_REFEITOS` vezes, e uma vez depois das claims se elas mudarem o produto, com a peca
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 Math.max(2, Math.floor((cpus - 1) / 3));
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 { passos, resto } = tirarPassosDoPreparo(comando, passosDoPreparo);
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. 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.
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
- // 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);
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: semVeredito.length === 0 || soPorTempo ? lanes.every((l) => l.resultado.executado !== false) : false,
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
+ }
@@ -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/ci.js CHANGED
@@ -389,10 +389,11 @@ function lintDoBundle(claims) {
389
389
  for (const claim of claims) {
390
390
  for (const achado of (0, claim_lint_1.analisarComandos)(claim.verificar)) {
391
391
  const linha = (0, claim_lint_1.linhaDoLintDeClaim)(claim.id, achado);
392
- if (achado.regra === 'suite-inteira' && claim.lint !== undefined)
392
+ const bloqueiaNova = achado.regra === 'suite-inteira' || (0, claim_lint_1.regraFatalDeClaim)(achado.regra);
393
+ if (bloqueiaNova && claim.lint !== undefined)
393
394
  recusas.push(linha);
394
395
  else
395
- avisos.push(achado.regra === 'suite-inteira' ? anteriorAoLint(linha) : linha);
396
+ avisos.push(bloqueiaNova ? anteriorAoLint(linha) : linha);
396
397
  }
397
398
  }
398
399
  return { recusas, avisos };
@@ -16,6 +16,8 @@
16
16
  * alheia e mexer em estado de quem nao pediu (D16). Nada de data de corte no codigo.
17
17
  */
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.REGRAS_FATAIS_DE_CLAIM = void 0;
20
+ exports.regraFatalDeClaim = regraFatalDeClaim;
19
21
  exports.analisarComando = analisarComando;
20
22
  exports.analisarComandos = analisarComandos;
21
23
  exports.linhaDoLintDeClaim = linhaDoLintDeClaim;
@@ -29,7 +31,18 @@ const CORRECAO = {
29
31
  'suite-inteira': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['suite-inteira'],
30
32
  'sha-intermediario': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['sha-intermediario'],
31
33
  'contagem-de-commits': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['contagem-de-commits'],
34
+ 'binario-rg': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['binario-rg'],
35
+ 'ork-fora-do-path': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['ork-fora-do-path'],
36
+ 'estado-local': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['estado-local'],
37
+ 'aspas-escapadas': (0, locale_1.msg)('pt-BR').claims.correcoesDoLint['aspas-escapadas'],
32
38
  };
39
+ /** RM-037: estes achados tornam uma claim nova inexequivel no checkout do runner. */
40
+ exports.REGRAS_FATAIS_DE_CLAIM = new Set([
41
+ 'binario-rg', 'ork-fora-do-path', 'estado-local', 'aspas-escapadas',
42
+ ]);
43
+ function regraFatalDeClaim(regra) {
44
+ return exports.REGRAS_FATAIS_DE_CLAIM.has(regra);
45
+ }
33
46
  /**
34
47
  * (a) `npm [--prefix <dir>] [run] test` sem subcomando (`test:ci` nao casa) e sem arquivos
35
48
  * nomeados depois de `--` (o comando que nomeia `.test.ts`/`.js` depois de `--` e focado).
@@ -41,8 +54,24 @@ const SO_ANCESTRALIDADE = /merge-base\s+--is-ancestor\s+\S+\s+\S+/g;
41
54
  /** (b2) Contagem de commits comparada a um numero. */
42
55
  const CONTAGEM = /rev-list\s+(?:[^|;&]*\s)?--count\b|\bgit\s+log\b[^|;&]*\|\s*wc\s+-l\b/;
43
56
  const COMPARACAO = /-(?:eq|ne|gt|lt|ge|le)\s+['"]?\d|[^=!<>]==?\s*['"]?\d|grep\s+-(?:q?x|xq)\s+['"]?\d/;
57
+ /** Executavel em posicao de comando, no inicio ou depois de um operador do shell. */
58
+ function executavelDireto(comando, nome) {
59
+ return new RegExp(`(?:^|&&|\\|\\||[;|(])\\s*(?:command\\s+|env\\s+)?(${nome})(?=\\s|$)`).exec(comando);
60
+ }
44
61
  function analisarComando(comando) {
45
62
  const achados = [];
63
+ const rg = executavelDireto(comando, 'rg');
64
+ if (rg)
65
+ achados.push({ regra: 'binario-rg', trecho: rg[1], correcao: CORRECAO['binario-rg'] });
66
+ const ork = executavelDireto(comando, 'ork');
67
+ if (ork)
68
+ achados.push({ regra: 'ork-fora-do-path', trecho: ork[1], correcao: CORRECAO['ork-fora-do-path'] });
69
+ const estado = /(?:^|[\s"'=])((?:\.\/)?\.orkastery(?:[\\/][^\s"']*)?)/.exec(comando);
70
+ if (estado)
71
+ achados.push({ regra: 'estado-local', trecho: estado[1], correcao: CORRECAO['estado-local'] });
72
+ const aspas = /\\x(?:22|27)/i.exec(comando);
73
+ if (aspas)
74
+ achados.push({ regra: 'aspas-escapadas', trecho: aspas[0], correcao: CORRECAO['aspas-escapadas'] });
46
75
  const suite = SUITE_INTEIRA.exec(comando);
47
76
  if (suite)
48
77
  achados.push({ regra: 'suite-inteira', trecho: suite[0].trim(), correcao: CORRECAO['suite-inteira'] });
package/dist/claims.js CHANGED
@@ -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);