@orkastery/cli 0.6.3 → 0.6.5
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/codex-controller-sensor.js +21 -2
- package/dist/adapters/codex-events.js +15 -6
- package/dist/adapters/grok-events.js +43 -2
- package/dist/adapters/grok.js +28 -5
- package/dist/agendador-cpu.js +120 -30
- package/dist/cenarios-sensores.js +5 -4
- package/dist/check-agil.js +87 -11
- package/dist/check-paralelo.js +385 -13
- package/dist/check-selecao.js +149 -0
- package/dist/codex-task-complete.js +95 -0
- package/dist/junit.js +546 -0
- package/dist/locale-verify.js +37 -0
- package/dist/manifest.js +237 -13
- package/dist/retry.js +35 -0
- package/dist/security-scan/agil.js +28 -12
- package/dist/security-scan/executor.js +1 -0
- package/dist/security-scan/incremental.js +289 -0
- package/dist/security-scan/trabalhador.js +53 -5
- package/dist/session-watcher-claude.js +38 -22
- package/dist/session-watcher-grok.js +10 -0
- package/dist/session-watcher.js +47 -18
- package/dist/ship.js +9 -3
- package/dist/verify.js +215 -41
- package/package.json +1 -1
package/dist/manifest.js
CHANGED
|
@@ -257,6 +257,197 @@ function lerCheck(v, erros, avisos) {
|
|
|
257
257
|
}
|
|
258
258
|
/** Nome de lane: o que vira `test:<lane>` no ledger. */
|
|
259
259
|
const NOME_DE_LANE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
260
|
+
/** RM-081 F1: o relatorio e sempre um caminho POSIX relativo, nunca uma busca ou um caminho da maquina. */
|
|
261
|
+
function caminhoJUnit(chave, v, erros) {
|
|
262
|
+
if (typeof v !== 'string' || !v.trim()) {
|
|
263
|
+
erros.push(`${chave} precisa ser um caminho relativo, em texto`);
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const bruto = v.trim();
|
|
267
|
+
const normal = path.posix.normalize(bruto);
|
|
268
|
+
if (bruto.includes('\\') || bruto.includes('\0') || path.posix.isAbsolute(bruto) || path.win32.isAbsolute(bruto)
|
|
269
|
+
|| normal === '.' || normal === '..' || normal.startsWith('../')) {
|
|
270
|
+
erros.push(`${chave}: ${exibir(v)} precisa ser um caminho relativo dentro da worktree`);
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
return normal;
|
|
274
|
+
}
|
|
275
|
+
/** RM-081 F3: o runner recebe a lista somente no marcador fechado, uma vez. */
|
|
276
|
+
function filtroDoComando(chave, v, erros) {
|
|
277
|
+
if (v === undefined || v === null)
|
|
278
|
+
return undefined;
|
|
279
|
+
if (typeof v !== 'string' || !v.trim()) {
|
|
280
|
+
erros.push(`${chave} precisa ser um comando em texto com o marcador {testes}`);
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
const filtro = v.trim();
|
|
284
|
+
const marcadores = filtro.match(/\{testes\}/g)?.length ?? 0;
|
|
285
|
+
if (marcadores !== 1) {
|
|
286
|
+
erros.push(`${chave} precisa ter o marcador {testes} exatamente uma vez`);
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
return filtro;
|
|
290
|
+
}
|
|
291
|
+
/** RM-081 F5: caminho ou glob POSIX relativo, sem sair da worktree nem alcançar estado do nucleo. */
|
|
292
|
+
function caminhoDaSelecao(chave, v, erros) {
|
|
293
|
+
if (typeof v !== 'string' || !v.trim()) {
|
|
294
|
+
erros.push(`${chave} precisa ser um caminho ou glob relativo, em texto`);
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const bruto = v.trim().replace(/\/+$/, '');
|
|
298
|
+
const normal = path.posix.normalize(bruto);
|
|
299
|
+
if (!bruto || bruto.includes('\\') || bruto.includes('\0') || path.posix.isAbsolute(bruto)
|
|
300
|
+
|| path.win32.isAbsolute(bruto) || normal === '.' || normal === '..' || normal.startsWith('../')
|
|
301
|
+
|| /^(\.orkastery|\.git)(\/|$)/.test(normal)) {
|
|
302
|
+
erros.push(`${chave}: ${exibir(v)} precisa ficar dentro da worktree, fora de .orkastery e .git`);
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
return normal;
|
|
306
|
+
}
|
|
307
|
+
/** RM-081 F5: o manifesto declara o mecanismo e a superficie que o runner sabe relacionar. */
|
|
308
|
+
function selecaoDoComando(chave, v, erros) {
|
|
309
|
+
if (v === undefined || v === null)
|
|
310
|
+
return undefined;
|
|
311
|
+
if (typeof v !== 'object' || Array.isArray(v)) {
|
|
312
|
+
erros.push(`${chave} precisa ser um mapa com comando, mapeia e configuracao opcional`);
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
for (const nome of Object.keys(v)) {
|
|
316
|
+
if (!['comando', 'mapeia', 'configuracao'].includes(nome)) {
|
|
317
|
+
erros.push(`${chave}.${nome} desconhecida; use somente comando, mapeia e configuracao`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const comando = typeof v.comando === 'string' ? v.comando.trim() : '';
|
|
321
|
+
const bases = comando.match(/\{base\}/g)?.length ?? 0;
|
|
322
|
+
const arquivos = comando.match(/\{arquivos\}/g)?.length ?? 0;
|
|
323
|
+
if (!comando || bases + arquivos !== 1) {
|
|
324
|
+
erros.push(`${chave}.comando precisa ter exatamente um marcador {base} ou {arquivos}`);
|
|
325
|
+
}
|
|
326
|
+
const lista = (nome) => {
|
|
327
|
+
const valor = v[nome];
|
|
328
|
+
if (valor === undefined || valor === null)
|
|
329
|
+
return [];
|
|
330
|
+
const itens = Array.isArray(valor) ? valor : [valor];
|
|
331
|
+
return itens.flatMap((item, indice) => {
|
|
332
|
+
const lido = caminhoDaSelecao(`${chave}.${nome}[${indice}]`, item, erros);
|
|
333
|
+
return lido ? [lido] : [];
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
const mapeia = lista('mapeia');
|
|
337
|
+
const configuracao = lista('configuracao');
|
|
338
|
+
return comando && bases + arquivos === 1
|
|
339
|
+
? { comando, mapeia, ...(configuracao.length ? { configuracao } : {}) }
|
|
340
|
+
: undefined;
|
|
341
|
+
}
|
|
342
|
+
/** RM-081 F2: uma unica estrategia, fechada e validada antes de qualquer teste rodar. */
|
|
343
|
+
function relogioDoComando(chave, v, erros) {
|
|
344
|
+
if (v === undefined || v === null)
|
|
345
|
+
return undefined;
|
|
346
|
+
if (typeof v !== 'object' || Array.isArray(v)) {
|
|
347
|
+
erros.push(`${chave} precisa ser um mapa com exatamente uma de nome, marcador ou tag`);
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
const conhecidas = ['nome', 'marcador', 'tag'];
|
|
351
|
+
for (const nome of Object.keys(v)) {
|
|
352
|
+
if (!conhecidas.includes(nome))
|
|
353
|
+
erros.push(`${chave}.${nome} desconhecida; use somente nome, marcador ou tag`);
|
|
354
|
+
}
|
|
355
|
+
const presentes = conhecidas.filter((nome) => v[nome] !== undefined && v[nome] !== null);
|
|
356
|
+
if (presentes.length !== 1) {
|
|
357
|
+
erros.push(`${chave} precisa declarar exatamente uma de nome, marcador ou tag`);
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
const estrategia = presentes[0];
|
|
361
|
+
if (estrategia === 'nome') {
|
|
362
|
+
const valores = Array.isArray(v.nome) ? v.nome : [v.nome];
|
|
363
|
+
const padroes = valores.flatMap((item) => typeof item === 'string' && item.trim() ? [item.trim()] : []);
|
|
364
|
+
if (padroes.length !== valores.length || padroes.length === 0 || padroes.length > 20) {
|
|
365
|
+
erros.push(`${chave}.nome precisa ter de 1 a 20 expressoes regulares em texto`);
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
for (const padrao of padroes) {
|
|
369
|
+
try {
|
|
370
|
+
void new RegExp(padrao);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
erros.push(`${chave}.nome contem expressao regular invalida: ${exibir(padrao)}`);
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return { nome: padroes };
|
|
378
|
+
}
|
|
379
|
+
const valor = v[estrategia];
|
|
380
|
+
if (typeof valor !== 'string' || !valor.trim()) {
|
|
381
|
+
erros.push(`${chave}.${estrategia} precisa ser texto nao vazio`);
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
return estrategia === 'marcador' ? { marcador: valor.trim() } : { tag: valor.trim() };
|
|
385
|
+
}
|
|
386
|
+
/** RM-081 F4: recursos da lane sao nomes locais a rodada, unicos e de uma linha. */
|
|
387
|
+
function recursosDoComando(chave, v, erros) {
|
|
388
|
+
if (v === undefined)
|
|
389
|
+
return undefined;
|
|
390
|
+
if (!Array.isArray(v) || v.length === 0) {
|
|
391
|
+
erros.push(`${chave} precisa ser uma lista nao vazia de textos`);
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
const lidos = [];
|
|
395
|
+
let valido = true;
|
|
396
|
+
for (const recurso of v) {
|
|
397
|
+
if (typeof recurso !== 'string' || !recurso.trim() || recurso.trim().length > 80 || /[\r\n]/.test(recurso)) {
|
|
398
|
+
erros.push(`${chave}: ${exibir(recurso)} precisa ser texto de uma linha, de 1 a 80 caracteres`);
|
|
399
|
+
valido = false;
|
|
400
|
+
}
|
|
401
|
+
else if (lidos.includes(recurso.trim())) {
|
|
402
|
+
erros.push(`${chave}: ${exibir(recurso.trim())} esta repetido`);
|
|
403
|
+
valido = false;
|
|
404
|
+
}
|
|
405
|
+
else
|
|
406
|
+
lidos.push(recurso.trim());
|
|
407
|
+
}
|
|
408
|
+
return valido ? lidos : undefined;
|
|
409
|
+
}
|
|
410
|
+
/** Texto continua sendo o contrato legado; o objeto exige comando e aceita os opt-ins da superficie. */
|
|
411
|
+
function lerComando(chave, v, erros, aceitaSelecao = false, aceitaRecursos = false) {
|
|
412
|
+
if (v === undefined || v === null)
|
|
413
|
+
return {};
|
|
414
|
+
if (typeof v === 'string')
|
|
415
|
+
return { comando: v };
|
|
416
|
+
if (typeof v !== 'object' || Array.isArray(v)) {
|
|
417
|
+
erros.push(`${chave} precisa ser o comando em texto ou um mapa com comando (recebido ${exibir(v)})`);
|
|
418
|
+
return {};
|
|
419
|
+
}
|
|
420
|
+
const conhecidas = ['comando', 'junit', 'filtro', 'relogio', ...(aceitaSelecao ? ['selecao'] : []),
|
|
421
|
+
...(aceitaRecursos ? ['recursos'] : [])];
|
|
422
|
+
for (const nome of Object.keys(v)) {
|
|
423
|
+
if (!conhecidas.includes(nome)) {
|
|
424
|
+
erros.push(`${chave}.${nome} desconhecida; use somente ${conhecidas.join(', ')}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const comando = typeof v.comando === 'string' && v.comando.trim() ? v.comando.trim() : undefined;
|
|
428
|
+
if (!comando)
|
|
429
|
+
erros.push(`${chave}.comando precisa ser texto nao vazio`);
|
|
430
|
+
const junit = v.junit === undefined ? undefined : caminhoJUnit(`${chave}.junit`, v.junit, erros);
|
|
431
|
+
const dispensaJUnit = aceitaRecursos && v.recursos !== undefined && v.recursos !== null;
|
|
432
|
+
if (v.junit === undefined) {
|
|
433
|
+
const dependencias = ['filtro', 'relogio']
|
|
434
|
+
.filter((dependencia) => v[dependencia] !== undefined && v[dependencia] !== null);
|
|
435
|
+
for (const dependencia of dependencias) {
|
|
436
|
+
if (v[dependencia] !== undefined && v[dependencia] !== null) {
|
|
437
|
+
erros.push(`${chave}.junit precisa ser declarado porque ${chave}.${dependencia} depende do relatorio JUnit`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (!dependencias.length && !dispensaJUnit) {
|
|
441
|
+
erros.push(`${chave}.junit precisa ser declarado no objeto de comando`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const filtro = filtroDoComando(`${chave}.filtro`, v.filtro, erros);
|
|
445
|
+
const relogio = relogioDoComando(`${chave}.relogio`, v.relogio, erros);
|
|
446
|
+
const selecao = aceitaSelecao ? selecaoDoComando(`${chave}.selecao`, v.selecao, erros) : undefined;
|
|
447
|
+
const recursos = aceitaRecursos ? recursosDoComando(`${chave}.recursos`, v.recursos, erros) : undefined;
|
|
448
|
+
return { ...(comando ? { comando } : {}), ...(junit ? { junit } : {}), ...(filtro ? { filtro } : {}),
|
|
449
|
+
...(relogio ? { relogio } : {}), ...(selecao ? { selecao } : {}), ...(recursos ? { recursos } : {}) };
|
|
450
|
+
}
|
|
260
451
|
/**
|
|
261
452
|
* RM-072 (D3): o bloco `check.agile`. Cada chave liga uma peca do Agile; tipo errado e erro do manifesto, porque uma
|
|
262
453
|
* peca mal escrita que sumisse em silencio mudaria o que o CHECK roda. `produto` so aceita caminho relativo dentro da
|
|
@@ -302,16 +493,42 @@ function lerAgile(v, erros, avisos) {
|
|
|
302
493
|
}
|
|
303
494
|
else {
|
|
304
495
|
const lanes = {};
|
|
305
|
-
|
|
496
|
+
const junit = {};
|
|
497
|
+
const filtro = {};
|
|
498
|
+
const relogio = {};
|
|
499
|
+
const selecao = {};
|
|
500
|
+
const recursos = {};
|
|
501
|
+
for (const [lane, valor] of Object.entries(v.test)) {
|
|
306
502
|
if (!NOME_DE_LANE.test(lane))
|
|
307
503
|
erros.push(`check.agile.test.${lane}: nome de lane aceita [a-z0-9-], ate 32 caracteres`);
|
|
308
|
-
else
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
504
|
+
else {
|
|
505
|
+
const lido = lerComando(`check.agile.test.${lane}`, valor, erros, true, true);
|
|
506
|
+
if (lido.comando)
|
|
507
|
+
lanes[lane] = lido.comando.trim();
|
|
508
|
+
if (lido.junit)
|
|
509
|
+
junit[lane] = lido.junit;
|
|
510
|
+
if (lido.filtro)
|
|
511
|
+
filtro[lane] = lido.filtro;
|
|
512
|
+
if (lido.relogio)
|
|
513
|
+
relogio[lane] = lido.relogio;
|
|
514
|
+
if (lido.selecao)
|
|
515
|
+
selecao[lane] = lido.selecao;
|
|
516
|
+
if (lido.recursos)
|
|
517
|
+
recursos[lane] = lido.recursos;
|
|
518
|
+
}
|
|
312
519
|
}
|
|
313
520
|
if (Object.keys(lanes).length)
|
|
314
521
|
agile.test = lanes;
|
|
522
|
+
if (Object.keys(junit).length)
|
|
523
|
+
agile.junit = junit;
|
|
524
|
+
if (Object.keys(filtro).length)
|
|
525
|
+
agile.filtro = filtro;
|
|
526
|
+
if (Object.keys(relogio).length)
|
|
527
|
+
agile.relogio = relogio;
|
|
528
|
+
if (Object.keys(selecao).length)
|
|
529
|
+
agile.selecao = selecao;
|
|
530
|
+
if (Object.keys(recursos).length)
|
|
531
|
+
agile.recursos = recursos;
|
|
315
532
|
}
|
|
316
533
|
}
|
|
317
534
|
if (v.apos_a_varredura !== undefined && v.apos_a_varredura !== null) {
|
|
@@ -666,6 +883,14 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
|
|
|
666
883
|
'(esperado nome de branch do git, sem "-" no começo e sem "..")');
|
|
667
884
|
}
|
|
668
885
|
const verify = mapa(dados.verify);
|
|
886
|
+
const comandosVerify = Object.fromEntries(['build', 'test', 'typecheck', 'security_scan']
|
|
887
|
+
.map((nome) => [nome, lerComando(`verify.${nome}`, verify[nome], erros)]));
|
|
888
|
+
const junitVerify = Object.fromEntries(Object.entries(comandosVerify)
|
|
889
|
+
.flatMap(([nome, lido]) => lido.junit ? [[nome, lido.junit]] : []));
|
|
890
|
+
const filtroVerify = Object.fromEntries(Object.entries(comandosVerify)
|
|
891
|
+
.flatMap(([nome, lido]) => lido.filtro ? [[nome, lido.filtro]] : []));
|
|
892
|
+
const relogioVerify = Object.fromEntries(Object.entries(comandosVerify)
|
|
893
|
+
.flatMap(([nome, lido]) => lido.relogio ? [[nome, lido.relogio]] : []));
|
|
669
894
|
const ci = mapa(dados.ci);
|
|
670
895
|
const concurrency = mapa(dados.concurrency);
|
|
671
896
|
const handoff = mapa(dados.handoff);
|
|
@@ -879,15 +1104,14 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
|
|
|
879
1104
|
...dependenciasDaWorktree(worktree.dependencias, erros),
|
|
880
1105
|
},
|
|
881
1106
|
verify: {
|
|
882
|
-
build:
|
|
883
|
-
test:
|
|
1107
|
+
build: comandosVerify.build.comando,
|
|
1108
|
+
test: comandosVerify.test.comando,
|
|
884
1109
|
...prazosDoVerify(verify, erros),
|
|
885
|
-
typecheck:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
: texto(verify.security_scan, ''),
|
|
1110
|
+
typecheck: comandosVerify.typecheck.comando,
|
|
1111
|
+
security_scan: comandosVerify.security_scan.comando,
|
|
1112
|
+
...(Object.keys(junitVerify).length ? { junit: junitVerify } : {}),
|
|
1113
|
+
...(Object.keys(filtroVerify).length ? { filtro: filtroVerify } : {}),
|
|
1114
|
+
...(Object.keys(relogioVerify).length ? { relogio: relogioVerify } : {}),
|
|
891
1115
|
// I-54 (RM-037, D10): o preparo unico da rodada de verify, quando declarado.
|
|
892
1116
|
...(typeof verify.preparo === 'string' && verify.preparo.trim() ? { preparo: verify.preparo.trim() } : {}),
|
|
893
1117
|
},
|
package/dist/retry.js
CHANGED
|
@@ -117,6 +117,8 @@ const postura_local_1 = require("./postura-local");
|
|
|
117
117
|
const locale_1 = require("./locale");
|
|
118
118
|
const locale_retry_1 = require("./locale-retry");
|
|
119
119
|
const retomada_checkpoint_1 = require("./retomada-checkpoint");
|
|
120
|
+
const codex_task_complete_1 = require("./codex-task-complete");
|
|
121
|
+
const session_events_1 = require("./session-events");
|
|
120
122
|
/** RM-061 (F2b, D3): `porque` e `correcao` de cada motivo tem a fonte no catalogo pt-BR; o `--json` nao muda. */
|
|
121
123
|
const TEXTOS_DA_POLITICA = locale_retry_1.RETRY_PT_BR.politica;
|
|
122
124
|
/**
|
|
@@ -385,6 +387,31 @@ function blocoPausa(thread, fase) {
|
|
|
385
387
|
function estadoTerminalDeRetry(estado) {
|
|
386
388
|
return ['completed', 'failed', 'stopped', 'cancelled', 'interrupted', 'terminated'].includes(estado.trim().toLowerCase());
|
|
387
389
|
}
|
|
390
|
+
/** Prova estreita: somente o rollout fixado da sessão Codex que originou o gate da fase. */
|
|
391
|
+
function faseJaTerminouPorTaskComplete(raiz, thread, fase, evento) {
|
|
392
|
+
const candidata = [...thread.sessoes].reverse().find(s => s.runtime === 'codex' && s.fase === fase &&
|
|
393
|
+
typeof s.despachadaEm === 'string' && (typeof evento?.sessionId !== 'string' || s.sessionId === evento.sessionId) &&
|
|
394
|
+
(typeof evento?.despachoEm !== 'string' || s.despachadaEm === evento.despachoEm));
|
|
395
|
+
if (!candidata)
|
|
396
|
+
return false;
|
|
397
|
+
try {
|
|
398
|
+
const prova = (0, codex_task_complete_1.taskCompleteFixadoDaSessao)(raiz, thread, (0, session_events_1.exigirSessaoDespachada)(candidata, candidata.sessionId), (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread.id)));
|
|
399
|
+
// task_complete encerra o turno, mas pode carregar uma falha estruturada de runtime.
|
|
400
|
+
// Só o resultado que concluiu a fase impede nova tentativa; cota/auth/modelo mantêm
|
|
401
|
+
// a política tipada de recuperação já existente.
|
|
402
|
+
return prova?.terminal.classificacao === 'fase_concluida';
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function planoProtegidoPorTaskComplete(thread, fase, limite, effort) {
|
|
409
|
+
return { thread: thread.id, modo: thread.modo, fase, motivo: null,
|
|
410
|
+
detalhe: 'rollout autenticado da sessão despachada já contém task_complete', politica: null,
|
|
411
|
+
acao: 'sem-retry', tentativas: 0, limite, effort, effortAnterior: effort, automatica: false,
|
|
412
|
+
bloqueio: 'gate.sem-reprovacao',
|
|
413
|
+
razao: 'rollout autenticado já contém task_complete; a fase terminou e não será redespachada' };
|
|
414
|
+
}
|
|
388
415
|
/**
|
|
389
416
|
* Calcula o plano de retry SEM executar nada.
|
|
390
417
|
*
|
|
@@ -417,6 +444,8 @@ function planejarRetry(carregado, threadId, opcoes = {}) {
|
|
|
417
444
|
};
|
|
418
445
|
}
|
|
419
446
|
const fase = opcoes.fase ?? (evento ? faseDoEvento(evento, thread) : thread.faseAtual);
|
|
447
|
+
if (faseJaTerminouPorTaskComplete(raiz, thread, fase, evento))
|
|
448
|
+
return planoProtegidoPorTaskComplete(thread, fase, limite, effortBase);
|
|
420
449
|
const politica = politicaDoMotivo(motivo);
|
|
421
450
|
const tentativas = tentativasFeitas(raiz, threadId, fase, motivo);
|
|
422
451
|
const detalhe = evento && typeof evento.detalhe === 'string' ? evento.detalhe : '';
|
|
@@ -1429,6 +1458,12 @@ function executarRetry(carregado, threadId, opcoes = {}) {
|
|
|
1429
1458
|
}
|
|
1430
1459
|
}
|
|
1431
1460
|
const thread = (0, thread_1.lerThread)(raiz, threadId);
|
|
1461
|
+
// D4: reconfirma imediatamente antes do primeiro efeito do retry; o plano anterior pode ter
|
|
1462
|
+
// sido calculado enquanto o runtime ainda anexava o terminal ao rollout.
|
|
1463
|
+
if (plano.motivo !== null && plano.fase &&
|
|
1464
|
+
faseJaTerminouPorTaskComplete(raiz, thread, plano.fase, ultimaReprovacao(raiz, threadId))) {
|
|
1465
|
+
plano = planoProtegidoPorTaskComplete(thread, plano.fase, plano.limite, plano.effortAnterior);
|
|
1466
|
+
}
|
|
1432
1467
|
const dir = (0, thread_1.dirThread)(raiz, threadId);
|
|
1433
1468
|
const vazio = {
|
|
1434
1469
|
plano,
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.PRAZO_MINIMO_DO_CLASSIC_MS = exports.PRAZO_DO_BUILD_DA_IMAGEM_MS = exports.PRAZO_POR_REGRA_DO_SEMGREP_S =
|
|
36
|
+
exports.PRAZO_MINIMO_DO_CLASSIC_MS = exports.PRAZO_DO_BUILD_DA_IMAGEM_MS = exports.PRAZO_POR_REGRA_DO_SEMGREP_S = void 0;
|
|
37
37
|
exports.especificacaoDoAgile = especificacaoDoAgile;
|
|
38
38
|
exports.prazoDoBuildDaImagem = prazoDoBuildDaImagem;
|
|
39
39
|
exports.estourouNoAgile = estourouNoAgile;
|
|
@@ -48,9 +48,9 @@ exports.runScanAgil = runScanAgil;
|
|
|
48
48
|
* ferramentas do catalogo e a mesma classificacao contra a base. O que muda e o empacotamento: a arvore da base sai uma
|
|
49
49
|
* vez, antes, e cada ferramenta roda num trabalhador proprio (`trabalhador.ts`), ao mesmo tempo, chamando o mesmo
|
|
50
50
|
* `scanTool` do Classic; o relatorio sai na ordem do catalogo. As ferramentas do Trivy montam um diretorio persistente
|
|
51
|
-
* por ferramenta no `/tmp/trivy-cache` do catalogo
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* por ferramenta no `/tmp/trivy-cache` do catalogo. O resultado normalizado do Semgrep e das ferramentas de dependência
|
|
52
|
+
* fica no cache da máquina, por versão, regras e conteúdo; o Classic continua varrendo toda a árvore. Cada peça do Agile
|
|
53
|
+
* que não pode rodar cai para o equivalente do Classic e vai ao relatório
|
|
54
54
|
* (`check.pecas`) e ao `ORK_CHECK_RELATORIO` do verify.
|
|
55
55
|
*/
|
|
56
56
|
const fs = __importStar(require("node:fs"));
|
|
@@ -63,6 +63,7 @@ const redacao_url_1 = require("../redacao-url");
|
|
|
63
63
|
const compare_1 = require("./compare");
|
|
64
64
|
const config_1 = require("./config");
|
|
65
65
|
const guard_1 = require("./guard");
|
|
66
|
+
const incremental_1 = require("./incremental");
|
|
66
67
|
const run_1 = require("./run");
|
|
67
68
|
const executor_1 = require("./executor");
|
|
68
69
|
const imagem_de_lint_1 = require("./imagem-de-lint");
|
|
@@ -71,12 +72,9 @@ const TRABALHADOR = path.join(__dirname, 'trabalhador.js');
|
|
|
71
72
|
/** Sem prazo da rodada, o trabalhador ganha o maior prazo do catalogo para a HEAD e para a base, com folga. */
|
|
72
73
|
const FOLGA_MS = 60_000;
|
|
73
74
|
/**
|
|
74
|
-
* O
|
|
75
|
-
*
|
|
76
|
-
* fica de fora, declarado como peca em fallback.
|
|
75
|
+
* O cache guarda os achados normalizados junto da identidade das regras do registro. Assim uma rodada morna devolve o
|
|
76
|
+
* `check_id` original, em vez de reler uma regra por arquivo local e reescrever a impressão digital.
|
|
77
77
|
*/
|
|
78
|
-
exports.SEMGREP_SEM_CACHE = { peca: 'cache-semgrep', estado: 'fallback',
|
|
79
|
-
motivo: 'regra lida de arquivo local troca o check_id e a impressao digital', fallback: 'regras do registro a cada rodada, como no Classic' };
|
|
80
78
|
/**
|
|
81
79
|
* O prazo por regra e arquivo do Semgrep no Agile, em segundos. O padrao dele e 5 s; com as ferramentas juntas (e, no
|
|
82
80
|
* verify, junto das lanes de teste) a disputa de CPU fazia uma regra estourar (`partial-timeout` medido na primeira
|
|
@@ -284,8 +282,8 @@ function runScanAgil(opts, rodar = check_paralelo_1.rodarEmParalelo) {
|
|
|
284
282
|
return report;
|
|
285
283
|
}
|
|
286
284
|
report.base = base;
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
pecas.push({ peca: 'varredura-incremental', estado: 'agile',
|
|
286
|
+
motivo: 'arquivo pela versao e regras da ferramenta; dependencias pelo lockfile; demais ferramentas rodam inteiras' });
|
|
289
287
|
const count = (0, run_1.git)(root, ['rev-list', '--count', `${base}..${head}`]);
|
|
290
288
|
const deadline = opts.deadlineMs === undefined ? null : Date.now() + opts.deadlineMs;
|
|
291
289
|
// A arvore da base sai uma vez, aqui, e os trabalhadores a dividem (cada um varre a sua ferramenta nela).
|
|
@@ -298,6 +296,19 @@ function runScanAgil(opts, rodar = check_paralelo_1.rodarEmParalelo) {
|
|
|
298
296
|
}
|
|
299
297
|
const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ork-security-scan-agil-')));
|
|
300
298
|
guard.add('outDirs', dir);
|
|
299
|
+
// A peça do cache já fazia parte do contrato ordenado do relatório antes das peças de executor. Resolver aqui mantém
|
|
300
|
+
// essa ordem pública; o mesmo resultado segue no trabalho que será entregue ao processo da ferramenta.
|
|
301
|
+
const regrasSemgrepPorIndice = new Map();
|
|
302
|
+
tools.forEach((t, i) => {
|
|
303
|
+
if (t.name !== 'semgrep')
|
|
304
|
+
return;
|
|
305
|
+
const regrasSemgrep = (0, incremental_1.resolverRegrasSemgrep)(t, (0, run_1.unitsOf)(t, pairs));
|
|
306
|
+
regrasSemgrepPorIndice.set(i, regrasSemgrep);
|
|
307
|
+
pecas.push(regrasSemgrep.reutilizavel
|
|
308
|
+
? { peca: 'cache-semgrep', estado: 'agile', motivo: 'hash do conteúdo das regras resolvidas; check_id preservado' }
|
|
309
|
+
: { peca: 'cache-semgrep', estado: 'fallback', motivo: regrasSemgrep.motivo,
|
|
310
|
+
fallback: 'varredura integral, como no Classic' });
|
|
311
|
+
});
|
|
301
312
|
// RM-072 (D12): quem tem o binario nativo na versao do catalogo roda sem o Docker; a peca diz quem faltou.
|
|
302
313
|
const probe = opts.probe ?? (0, executor_1.defaultProbe)();
|
|
303
314
|
const comNativo = tools.filter(t => t.native && !t.install);
|
|
@@ -310,7 +321,12 @@ function runScanAgil(opts, rodar = check_paralelo_1.rodarEmParalelo) {
|
|
|
310
321
|
}
|
|
311
322
|
const montados = tools.map((t, i) => {
|
|
312
323
|
const restante = deadline === null ? t.timeoutMs : Math.min(t.timeoutMs, deadline - Date.now());
|
|
313
|
-
const
|
|
324
|
+
const units = (0, run_1.unitsOf)(t, pairs);
|
|
325
|
+
const tool = eslintDoAgile(especificacaoDoAgile(t, pecas), restante, pecas);
|
|
326
|
+
const regrasSemgrep = regrasSemgrepPorIndice.get(i);
|
|
327
|
+
const trabalho = { tool, units, config,
|
|
328
|
+
incremental: true,
|
|
329
|
+
...(regrasSemgrep ? { regrasSemgrep } : {}),
|
|
314
330
|
...(nativos.has(t.name) ? { nativoPrimeiro: true } : {}),
|
|
315
331
|
ctx: { headTree, mainRoot, base, head, rangeCommits: count === null ? null : Number(count), deadline, ...(baseTree ? { baseTree } : {}) } };
|
|
316
332
|
const arquivo = path.join(dir, `${i}.json`);
|
|
@@ -160,6 +160,7 @@ function runDocker(inv) {
|
|
|
160
160
|
...Object.entries(tool.env ?? {}).flatMap(([k, v]) => ['-e', `${k}=${v}`]),
|
|
161
161
|
'--entrypoint', tool.containerBin ?? inv.argv[0], tool.image,
|
|
162
162
|
...inv.argv.slice(1).map(a => expand(a, values))];
|
|
163
|
+
(0, agendador_cpu_1.registrarContainerDaLane)(name);
|
|
163
164
|
inv.guard?.add('containers', name);
|
|
164
165
|
const r = (0, node_child_process_1.spawnSync)('docker', args, { encoding: 'utf8', timeout: inv.timeoutMs, killSignal: 'SIGKILL', maxBuffer: MAX_BUFFER, env: process.env });
|
|
165
166
|
const timedOut = r.error?.code === 'ETIMEDOUT';
|