@orkastery/cli 0.5.3 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/claude-code/README.md +158 -0
- package/adapters/claude-code/mods/autoridade.json +56 -0
- package/adapters/claude-code/mods/ork-clima/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-clima/hooks/contas.ts +283 -0
- package/adapters/claude-code/mods/ork-clima/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-clima/hooks/previsao.ts +393 -0
- package/adapters/claude-code/mods/ork-clima/hooks/register.ts +205 -0
- package/adapters/claude-code/mods/ork-clima/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-clima/types/index.d.ts +80 -0
- package/adapters/claude-code/mods/ork-freio/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-freio/hooks/carga.ts +160 -0
- package/adapters/claude-code/mods/ork-freio/hooks/conferencia.ts +309 -0
- package/adapters/claude-code/mods/ork-freio/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-freio/hooks/lote.ts +270 -0
- package/adapters/claude-code/mods/ork-freio/hooks/register.ts +285 -0
- package/adapters/claude-code/mods/ork-freio/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-freio/types/index.d.ts +127 -0
- package/adapters/claude-code/mods/ork-mesa/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/cartoes.tsx +246 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/decidir.ts +110 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/fila.ts +253 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/obvia.ts +98 -0
- package/adapters/claude-code/mods/ork-mesa/hooks/register.tsx +493 -0
- package/adapters/claude-code/mods/ork-mesa/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mesa/types/index.d.ts +131 -0
- package/adapters/claude-code/mods/ork-pista/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-pista/hooks/acoes.ts +71 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +117 -0
- package/adapters/claude-code/mods/ork-pista/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-pista/hooks/pista.tsx +356 -0
- package/adapters/claude-code/mods/ork-pista/hooks/register.tsx +439 -0
- package/adapters/claude-code/mods/ork-pista/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-pista/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-ponte/.claude-plugin/plugin.json +13 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/faixa.tsx +57 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/guarda.ts +410 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/mostrado.ts +55 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/nucleo.ts +157 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/papel.ts +42 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/register.ts +331 -0
- package/adapters/claude-code/mods/ork-ponte/hooks/vigia.ts +76 -0
- package/adapters/claude-code/mods/ork-ponte/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ponte/types/index.d.ts +178 -0
- package/adapters/claude-code/mods/ork-portao/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-portao/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-portao/hooks/lease.ts +201 -0
- package/adapters/claude-code/mods/ork-portao/hooks/plano.ts +304 -0
- package/adapters/claude-code/mods/ork-portao/hooks/register.ts +339 -0
- package/adapters/claude-code/mods/ork-portao/hooks/verify.ts +152 -0
- package/adapters/claude-code/mods/ork-portao/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-portao/types/index.d.ts +60 -0
- package/adapters/claude-code/mods/ork-radar/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-radar/hooks/colisao.ts +420 -0
- package/adapters/claude-code/mods/ork-radar/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-radar/hooks/mapa.tsx +322 -0
- package/adapters/claude-code/mods/ork-radar/hooks/register.tsx +470 -0
- package/adapters/claude-code/mods/ork-radar/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-radar/types/index.d.ts +72 -0
- package/adapters/claude-code/mods/ork-sentinela/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/alertas.ts +85 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/despacho.ts +156 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/register.ts +511 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +186 -0
- package/adapters/claude-code/mods/ork-sentinela/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-sentinela/types/index.d.ts +65 -0
- package/adapters/claude-code/mods/ork-torre/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-torre/hooks/agenda.ts +60 -0
- package/adapters/claude-code/mods/ork-torre/hooks/detalhe.tsx +236 -0
- package/adapters/claude-code/mods/ork-torre/hooks/frases.ts +42 -0
- package/adapters/claude-code/mods/ork-torre/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-torre/hooks/painel.tsx +344 -0
- package/adapters/claude-code/mods/ork-torre/hooks/rede.ts +97 -0
- package/adapters/claude-code/mods/ork-torre/hooks/register.tsx +437 -0
- package/adapters/claude-code/mods/ork-torre/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-torre/types/index.d.ts +108 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +1 -1
- package/adapters/openclaw/README.md +2 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/assets/docs-en/markdownlint-cli2.jsonc +22 -0
- package/assets/docs-en/padroes/documentacao-de-produto.md +135 -0
- package/assets/docs-en/padroes/roadmap-de-produto.md +100 -0
- package/assets/docs-en/produto/README.md +14 -0
- package/assets/docs-en/produto/_modelo-feature.md +62 -0
- package/assets/docs-en/roadmap/README.md +14 -0
- package/assets/docs-en/roadmap/_modelo-item.md +75 -0
- package/dist/adapters/claude-bg.js +77 -13
- package/dist/adapters/codex.js +10 -1
- package/dist/agents-md.js +24 -15
- package/dist/auditrun.js +17 -2
- package/dist/board.js +4 -3
- package/dist/branch-de-estado.js +23 -1
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +13 -7
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +3 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-journal.js +2 -1
- package/dist/company-brain-migration.js +36 -1
- package/dist/conducao-sentinela.js +388 -0
- package/dist/conducao-texto.js +12 -18
- package/dist/conducao.js +122 -30
- package/dist/conhecimento.js +490 -0
- package/dist/docs.js +22 -8
- package/dist/doctor-pulse-cron.js +182 -27
- package/dist/doctor-rede.js +18 -17
- package/dist/doctor.js +115 -111
- package/dist/entrega-pr.js +22 -21
- package/dist/escopo-escrita.js +10 -1
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +38 -8
- package/dist/fabrica-publicar.js +3 -1
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +1 -1
- package/dist/gates.js +3 -28
- package/dist/handoff.js +10 -6
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-resumo.js +29 -3
- package/dist/hitl-tempo-parado.js +4 -4
- package/dist/hitl.js +29 -33
- package/dist/horario.js +14 -7
- package/dist/hosts.js +52 -1
- package/dist/impedimento.js +108 -14
- package/dist/index.js +623 -521
- package/dist/indice.js +7 -1
- package/dist/init.js +43 -47
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +92 -21
- package/dist/intelligence-graph-contexto.js +56 -17
- package/dist/intelligence-graph-contract.js +2 -1
- package/dist/intelligence-graph-extract-md.js +56 -4
- package/dist/intelligence-graph-extract-ts.js +71 -3
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/ledger.js +15 -1
- package/dist/liveness.js +6 -3
- package/dist/locale-ajuda.js +761 -0
- package/dist/locale-gerados.js +160 -0
- package/dist/locale-master.js +99 -0
- package/dist/locale-onboarding.js +92 -0
- package/dist/locale-phase.js +151 -0
- package/dist/locale-pulse.js +211 -0
- package/dist/locale-retry.js +385 -0
- package/dist/locale-roadmap.js +102 -0
- package/dist/locale-ship.js +494 -0
- package/dist/locale-thread.js +277 -0
- package/dist/locale-verify.js +125 -0
- package/dist/locale.js +714 -0
- package/dist/manifest.js +129 -3
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +4 -6
- package/dist/master.js +21 -24
- package/dist/mcp-artifacts.js +11 -2
- package/dist/mcp-git.js +23 -6
- package/dist/mcp-grafo.js +7 -4
- package/dist/mcp-server.js +8 -2
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +5 -2
- package/dist/noctum-arco.js +911 -0
- package/dist/noctum-capacidade.js +141 -0
- package/dist/noctum-envelope.js +810 -0
- package/dist/noctum-execucao.js +304 -0
- package/dist/noctum-fila.js +280 -0
- package/dist/ocupacao.js +29 -2
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +9 -2
- package/dist/orquestracao.js +13 -12
- package/dist/parado-no-condutor.js +34 -9
- package/dist/phase.js +94 -39
- package/dist/policies.js +4 -4
- package/dist/portfolio.js +4 -2
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +127 -4
- package/dist/prompts.js +6 -2
- package/dist/pulse-delivery.js +13 -2
- package/dist/pulse.js +52 -19
- package/dist/ratelimit.js +4 -2
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-status.js +3 -2
- package/dist/retry.js +232 -148
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +129 -33
- package/dist/runtime-context.js +181 -3
- package/dist/runtimes.js +2 -1
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +7 -1
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +1 -1
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +84 -81
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +153 -82
- package/dist/util.js +8 -1
- package/dist/verify.js +141 -52
- package/dist/vida-da-thread.js +254 -0
- package/dist/worktree-local.js +165 -0
- package/dist/worktree.js +20 -0
- package/dist/yaml.js +27 -8
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
package/dist/fabrica-estado.js
CHANGED
|
@@ -57,8 +57,9 @@ exports.painelDaFabricaEmMarkdown = painelDaFabricaEmMarkdown;
|
|
|
57
57
|
* GitHub. `ork board`, `ork fabrica` e o resumo do pulse leem as outras maquinas dali. Cada
|
|
58
58
|
* maquina so escreve o proprio arquivo: push recusado se resolve relendo a ponta.
|
|
59
59
|
*
|
|
60
|
-
* Publicar e empurrar para o remoto do projeto, entao so acontece sozinho com
|
|
61
|
-
* `fabrica
|
|
60
|
+
* Publicar e empurrar para o remoto do projeto, entao so acontece sozinho com a adesao da maquina
|
|
61
|
+
* (`ork fabrica entrar`); o `fabrica.compartilhada` do manifesto so pede (RM-047, P5), e
|
|
62
|
+
* `ork fabrica publicar` e o caminho explicito. Nada de
|
|
62
63
|
* credencial, prompt, log ou caminho local sai daqui: so identificadores, fase, status e o assunto
|
|
63
64
|
* da pausa que espera o dono.
|
|
64
65
|
*
|
|
@@ -82,6 +83,7 @@ const thread_1 = require("./thread");
|
|
|
82
83
|
const util_1 = require("./util");
|
|
83
84
|
const versao_1 = require("./versao");
|
|
84
85
|
const saida_segura_1 = require("./saida-segura");
|
|
86
|
+
const vida_da_thread_1 = require("./vida-da-thread");
|
|
85
87
|
exports.CONTRATO_MAQUINA = 'ork.fabrica-maquina/v1';
|
|
86
88
|
exports.BRANCH_DA_FABRICA = 'ork/fabrica-estado';
|
|
87
89
|
/** O diretorio dos retratos na branch; a leitura sem clone (RM-054) le o mesmo. */
|
|
@@ -162,17 +164,37 @@ function retratoDaMaquina(carregado, opcoes = {}) {
|
|
|
162
164
|
const linhas = new Map(monitor.linhas.map((l) => [l.thread, l]));
|
|
163
165
|
const entregas = entregasNaBase(raiz, carregado.manifesto.worktree.base_branch, remoto);
|
|
164
166
|
const itemDaThread = new Map((0, roadmap_reservas_1.reservasLocais)(raiz, remoto).filter((r) => r.thread).map((r) => [r.thread, r.item]));
|
|
167
|
+
let leitura;
|
|
168
|
+
const lerUmaVez = () => (leitura ??= (opcoes.runtime ?? (() => (0, vida_da_thread_1.lerRuntime)(raiz)))());
|
|
165
169
|
const threads = itens.map(({ thread: t, raiz: raizDoPerfil }) => {
|
|
166
170
|
const entregue = entregas.get(t.id) ?? null;
|
|
167
171
|
// RM-037 (fatia 4): o fim de turno sem pergunta e do condutor, e as outras maquinas nao o leem como espera
|
|
168
172
|
// do dono. Sai so a pausa que o observador abriu nele; outra pausa da thread continua contando.
|
|
169
173
|
let espera = null;
|
|
174
|
+
let eventos = [];
|
|
170
175
|
try {
|
|
171
|
-
|
|
176
|
+
eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raizDoPerfil, t.id));
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
eventos = [];
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
espera = (0, parado_no_condutor_1.esperaDoCondutor)(t, eventos, quando);
|
|
172
183
|
}
|
|
173
184
|
catch {
|
|
174
185
|
espera = null;
|
|
175
186
|
}
|
|
187
|
+
// RM-063 (F2): a fabrica so mostra a thread como andando com a sessao viva verificada no runtime.
|
|
188
|
+
let sessao = null;
|
|
189
|
+
if (!entregue) {
|
|
190
|
+
try {
|
|
191
|
+
const v = (0, vida_da_thread_1.vidaDaThread)(t, eventos, lerUmaVez, Date.parse(quando));
|
|
192
|
+
sessao = { viva: v.viva, desdeEm: v.desdeEm, motivo: v.motivo };
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
sessao = null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
176
198
|
const pausas = (linhas.get(t.id)?.pausas ?? []).filter((p) => !(espera && p.motivo === 'human.pending' && p.fonte === 'ledger' &&
|
|
177
199
|
p.fase === (espera.fase ?? t.faseAtual)));
|
|
178
200
|
const pausa = pausas[0];
|
|
@@ -184,6 +206,7 @@ function retratoDaMaquina(carregado, opcoes = {}) {
|
|
|
184
206
|
esperaVoce, pergunta: esperaVoce && pausa ? curto(pausa.pausaSobre || pausa.detalhe) : null,
|
|
185
207
|
paradaDesde: esperaVoce && pausa ? pausa.desdeEm : null,
|
|
186
208
|
...despachoDaThread((0, thread_1.dirThread)(raizDoPerfil, t.id)),
|
|
209
|
+
sessao,
|
|
187
210
|
};
|
|
188
211
|
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
189
212
|
const eu = (0, roadmap_reservas_1.quemSouEu)(raiz, { por: opcoes.por, maquina: opcoes.maquina });
|
|
@@ -323,14 +346,20 @@ function tabela(cabecalho, linhas) {
|
|
|
323
346
|
const linha = (l) => ' ' + l.map((c, i) => c.padEnd(largura[i])).join(' ').trimEnd();
|
|
324
347
|
return [linha(cabecalho), linha(largura.map((n) => '-'.repeat(n))), ...linhas.map(linha)];
|
|
325
348
|
}
|
|
349
|
+
/** RM-063 (F2): a coluna da sessao: `viva`, `parada desde HH:MM: <motivo>`, ou `-` no retrato que nao a traz. */
|
|
350
|
+
function sessaoNaFabrica(t, teto) {
|
|
351
|
+
const v = (0, vida_da_thread_1.vidaDoRetrato)(t.sessao);
|
|
352
|
+
return v.viva ? 'viva' : v.motivo === null ? '-' : curto((0, vida_da_thread_1.paradaDesde)(v), teto);
|
|
353
|
+
}
|
|
326
354
|
function blocoDaMaquina(m, eu) {
|
|
327
355
|
const vivas = ativas(m), entregues = m.threads.length - vivas.length;
|
|
328
356
|
const cabeca = `${m.maquina}${m.maquina === eu ? ' (esta maquina)' : ''}, ${m.por}, publicado ${(0, horario_1.formatarDataHora)(m.publicadoEm)}: ` +
|
|
329
357
|
`${vivas.length} thread(s) ativa(s)` + (entregues ? `, ${entregues} entregue(s) sem MASTER` : '');
|
|
330
358
|
if (vivas.length === 0)
|
|
331
359
|
return [cabeca];
|
|
332
|
-
return [cabeca, ...tabela(['THREAD', 'MODO', 'FASE', 'RUNTIME', 'ITEM', 'ESPERA VOCE'], vivas.map((t) => [
|
|
360
|
+
return [cabeca, ...tabela(['THREAD', 'MODO', 'FASE', 'RUNTIME', 'ITEM', 'ESPERA VOCE', 'SESSAO'], vivas.map((t) => [
|
|
333
361
|
t.id, t.modo, t.fase, runtimeDaThread(t), t.roadmap ?? '-', t.esperaVoce ? `sim: ${curto(t.pergunta ?? 'veredito', 40)}` : '-',
|
|
362
|
+
sessaoNaFabrica(t, 70),
|
|
334
363
|
]))];
|
|
335
364
|
}
|
|
336
365
|
/**
|
|
@@ -372,17 +401,18 @@ function painelDaFabricaEmMarkdown(maquinas) {
|
|
|
372
401
|
'O que cada máquina está conduzindo agora. Gerado pelo `ork fabrica publicar`; não edite à mão.',
|
|
373
402
|
'Antes de pegar um item do roadmap: `ork roadmap reservas`.',
|
|
374
403
|
'',
|
|
375
|
-
'| Máquina | Thread | Modo | Fase | Runtime | Item | Espera você | Publicado |',
|
|
376
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
404
|
+
'| Máquina | Thread | Modo | Fase | Runtime | Item | Espera você | Publicado | Sessão |',
|
|
405
|
+
'| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
377
406
|
];
|
|
378
407
|
const vivas = maquinas.flatMap((m) => ativas(m).map((t) => ({ m, t })));
|
|
379
408
|
if (vivas.length === 0)
|
|
380
|
-
linhas.push('| — | nenhuma thread ativa | — | — | | — | — | — |');
|
|
409
|
+
linhas.push('| — | nenhuma thread ativa | — | — | | — | — | — | — |');
|
|
381
410
|
for (const { m, t } of vivas) {
|
|
382
411
|
const pergunta = t.esperaVoce ? `sim: ${curto(t.pergunta ?? 'veredito', 60).replace(/\|/g, '/')}` : '—';
|
|
383
412
|
// RM-037 (sugestao 6 do CHECK 1): a celula sem despacho diz isso, sem travessao novo.
|
|
384
413
|
const runtime = t.runtime || t.modelo ? runtimeDaThread(t).replace(/\|/g, '/') : 'sem despacho';
|
|
385
|
-
|
|
414
|
+
const sessao = sessaoNaFabrica(t, 80).replace(/\|/g, '/').replace(/^-$/, '—');
|
|
415
|
+
linhas.push(`| ${m.maquina} | ${t.id} | ${t.modo} | ${t.fase} | ${runtime} | ${t.roadmap ?? '—'} | ${pergunta} | ${(0, horario_1.formatarDataHora)(m.publicadoEm)} | ${sessao} |`);
|
|
386
416
|
}
|
|
387
417
|
return [...linhas, '', (0, horario_1.legendaDoFuso)(), ''].join('\n');
|
|
388
418
|
}
|
package/dist/fabrica-publicar.js
CHANGED
|
@@ -48,6 +48,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
48
48
|
const path = __importStar(require("node:path"));
|
|
49
49
|
const maquina_1 = require("./maquina");
|
|
50
50
|
const manifest_1 = require("./manifest");
|
|
51
|
+
const projeto_alvo_1 = require("./projeto-alvo");
|
|
51
52
|
const rede_adesao_1 = require("./rede-adesao");
|
|
52
53
|
/**
|
|
53
54
|
* Publica sem segurar quem chamou (thread nova, despacho de fase, entrega). So com a fabrica
|
|
@@ -72,7 +73,8 @@ function publicarEmSegundoPlano(raiz) {
|
|
|
72
73
|
if (!fs.existsSync(cli))
|
|
73
74
|
return false;
|
|
74
75
|
try {
|
|
75
|
-
|
|
76
|
+
// RM-052 (fatia 2, D2): o filho publica o projeto do proprio cwd; o modo host de quem chamou nao o segue.
|
|
77
|
+
const filho = (0, node_child_process_1.spawn)(process.execPath, [cli, 'fabrica', 'publicar', '--silencioso'], { cwd: carregado.raiz, detached: true, stdio: 'ignore', env: { ...process.env, ...projeto_alvo_1.AMBIENTE_SEM_PROJETO_HERDADO } });
|
|
76
78
|
filho.unref();
|
|
77
79
|
return true;
|
|
78
80
|
}
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ErroDaFilaPesada = exports.ENV_DONO_DA_FILA = exports.CONTRATO_FILA_PESADA = void 0;
|
|
37
|
+
exports.kernelDisponivel = kernelDisponivel;
|
|
38
|
+
exports.tentarTravaExclusiva = tentarTravaExclusiva;
|
|
39
|
+
exports.dirDaFilaPesada = dirDaFilaPesada;
|
|
40
|
+
exports.ocupantesDaFilaPesada = ocupantesDaFilaPesada;
|
|
41
|
+
exports.comRecursoPesado = comRecursoPesado;
|
|
42
|
+
/**
|
|
43
|
+
* RM-058, fatia N2 (D9 da ork-rm058n2): a fila de recursos pesados.
|
|
44
|
+
*
|
|
45
|
+
* Verify rodando junto com build de outra worktree deu regressão falsa nesta máquina: com a CPU dividida, teste
|
|
46
|
+
* com relógio estoura o prazo e a suíte reprova sem que nada tenha quebrado. A fila deixa no máximo `slots`
|
|
47
|
+
* operações pesadas ao mesmo tempo por raiz de estado (todas as worktrees do projeto nesta máquina) e nunca duas
|
|
48
|
+
* no mesmo artefato (o diretório onde a operação compila e testa).
|
|
49
|
+
*
|
|
50
|
+
* Tudo é trava do kernel (`flock` exclusivo, como a condução e o lock do HITL): quem segura é o processo, e a
|
|
51
|
+
* trava solta sozinha quando ele morre, inclusive por SIGKILL. Nenhum prazo, PID ou arquivo apagado libera vaga;
|
|
52
|
+
* um processo perdido em segundo plano não prende a fila. A ordem de chegada é uma senha, também com trava do
|
|
53
|
+
* kernel: quem chegou antes e segue vivo passa antes, e a senha de quem morreu não segura ninguém.
|
|
54
|
+
*
|
|
55
|
+
* Limites ditos: a fila é da raiz de estado, e outro projeto da mesma máquina tem a sua; o descritor não passa
|
|
56
|
+
* aos filhos, então o processo que morre por SIGKILL solta a vaga mesmo que um comando dele ainda rode até o
|
|
57
|
+
* próprio prazo (o `timeout` do verify o encerra); e só o Linux com `/usr/bin/flock` tem a fila.
|
|
58
|
+
*
|
|
59
|
+
* O dono da vez passa aos comandos que roda a marca da posse (`ORK_FILA_PESADA_DONO`): um verify aninhado num
|
|
60
|
+
* comando dele (a claim que roda `ork verify` de outra thread do mesmo projeto) entra como reentrada, em vez de
|
|
61
|
+
* esperar a vaga do próprio pai até o prazo. A marca só vale com o dono vivo, ancestral do processo e segurando a
|
|
62
|
+
* vaga que diz, com o identificador da posse que gravou nela: marca copiada para fora dessa árvore de processos, ou
|
|
63
|
+
* guardada de uma vez anterior do mesmo dono, não pula a fila.
|
|
64
|
+
*/
|
|
65
|
+
const fs = __importStar(require("node:fs"));
|
|
66
|
+
const path = __importStar(require("node:path"));
|
|
67
|
+
const node_crypto_1 = require("node:crypto");
|
|
68
|
+
const node_child_process_1 = require("node:child_process");
|
|
69
|
+
const estado_thread_1 = require("./estado-thread");
|
|
70
|
+
const horario_1 = require("./horario");
|
|
71
|
+
exports.CONTRATO_FILA_PESADA = 'ork.fila-pesada/v1';
|
|
72
|
+
/** A marca da posse que o dono da vez passa aos processos filhos. */
|
|
73
|
+
exports.ENV_DONO_DA_FILA = 'ORK_FILA_PESADA_DONO';
|
|
74
|
+
const FLOCK = '/usr/bin/flock';
|
|
75
|
+
const DIR = 'fila-pesada';
|
|
76
|
+
/** Entre uma tentativa e outra. A vez costuma durar minutos; meio segundo não pesa em ninguém. */
|
|
77
|
+
const PAUSA_MS = 500;
|
|
78
|
+
/** A recusa é resposta, não falha: o CLI a imprime como a da condução, e sai com 3. */
|
|
79
|
+
class ErroDaFilaPesada extends Error {
|
|
80
|
+
recusa;
|
|
81
|
+
constructor(recusa) {
|
|
82
|
+
super(`fila-pesada.ocupada: ${recusa.texto}`);
|
|
83
|
+
this.name = 'ErroDaFilaPesada';
|
|
84
|
+
this.recusa = recusa;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.ErroDaFilaPesada = ErroDaFilaPesada;
|
|
88
|
+
function kernelDisponivel() {
|
|
89
|
+
return process.platform === 'linux' && fs.existsSync(FLOCK);
|
|
90
|
+
}
|
|
91
|
+
/** O `flock` exclusivo sem espera sobre um descritor já aberto. O helper tranca a descrição de arquivo, que fica do processo. */
|
|
92
|
+
function trancar(fd) {
|
|
93
|
+
const r = (0, node_child_process_1.spawnSync)(FLOCK, ['--exclusive', '--nonblock', '3'], { stdio: ['ignore', 'ignore', 'ignore', fd], timeout: 5000 });
|
|
94
|
+
return r.status === 0 && !r.error;
|
|
95
|
+
}
|
|
96
|
+
function abrirTrava(arquivo, criar) {
|
|
97
|
+
const fd = fs.openSync(arquivo, fs.constants.O_RDWR | fs.constants.O_NOFOLLOW | (criar ? fs.constants.O_CREAT : 0), 0o600);
|
|
98
|
+
const st = fs.fstatSync(fd);
|
|
99
|
+
if (!st.isFile() || st.uid !== process.getuid?.()) {
|
|
100
|
+
fs.closeSync(fd);
|
|
101
|
+
throw new Error(`fila-pesada: ${path.basename(arquivo)} não é arquivo de trava do núcleo`);
|
|
102
|
+
}
|
|
103
|
+
return fd;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A trava exclusiva do kernel num arquivo da raiz de estado: o descritor, ou `null` quando outro processo vivo a
|
|
107
|
+
* segura. A trava dura enquanto o descritor estiver aberto neste processo, e nunca mais que o processo.
|
|
108
|
+
*/
|
|
109
|
+
function tentarTravaExclusiva(arquivo) {
|
|
110
|
+
const fd = abrirTrava(arquivo, true);
|
|
111
|
+
if (trancar(fd))
|
|
112
|
+
return fd;
|
|
113
|
+
fs.closeSync(fd);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/** O diretório da fila na raiz de estado, criado privado. */
|
|
117
|
+
function dirDaFilaPesada(raiz) {
|
|
118
|
+
const principal = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
119
|
+
(0, estado_thread_1.exigirEstadoSemLink)(principal, ['.orkastery', DIR]);
|
|
120
|
+
const dir = path.join(principal, '.orkastery', DIR);
|
|
121
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
122
|
+
return dir;
|
|
123
|
+
}
|
|
124
|
+
/** O artefato pelo caminho real: a mesma worktree por dois caminhos é o mesmo artefato. */
|
|
125
|
+
function chaveDoArtefato(artefato) {
|
|
126
|
+
let real;
|
|
127
|
+
try {
|
|
128
|
+
real = fs.realpathSync(artefato);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
real = path.resolve(artefato);
|
|
132
|
+
}
|
|
133
|
+
return (0, node_crypto_1.createHash)('sha256').update(real).digest('hex').slice(0, 16);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A senha de chegada: criada com outro nome, trancada e só então renomeada, para ninguém vê-la destrancada e
|
|
137
|
+
* tomá-la por senha de processo morto. O nome começa pelo instante, com zeros: a ordem dos nomes é a de chegada.
|
|
138
|
+
*/
|
|
139
|
+
function tirarSenha(dir) {
|
|
140
|
+
const marca = `${String(Date.now()).padStart(15, '0')}-${String(process.pid).padStart(10, '0')}-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
|
|
141
|
+
const provisorio = path.join(dir, `.senha-${marca}.tmp`);
|
|
142
|
+
const fd = abrirTrava(provisorio, true);
|
|
143
|
+
if (!trancar(fd)) {
|
|
144
|
+
fs.closeSync(fd);
|
|
145
|
+
fs.rmSync(provisorio, { force: true });
|
|
146
|
+
throw new Error('fila-pesada: a senha nova não trancou');
|
|
147
|
+
}
|
|
148
|
+
const nome = `senha-${marca}.lock`;
|
|
149
|
+
fs.renameSync(provisorio, path.join(dir, nome));
|
|
150
|
+
return { nome, fd };
|
|
151
|
+
}
|
|
152
|
+
/** Apaga a senha com a trava na mão e só depois a solta: ninguém a vê destrancada no diretório. */
|
|
153
|
+
function devolverSenha(dir, senha) {
|
|
154
|
+
if (!senha)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
fs.unlinkSync(path.join(dir, senha.nome));
|
|
158
|
+
}
|
|
159
|
+
catch { /* já saiu */ }
|
|
160
|
+
try {
|
|
161
|
+
fs.closeSync(senha.fd);
|
|
162
|
+
}
|
|
163
|
+
catch { /* já fechado */ }
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* É a vez desta senha? Só quando nenhuma senha anterior segue viva. A de processo morto está destrancada: quem a
|
|
167
|
+
* acha a tranca, apaga e solta, para ela não segurar a fila de ninguém. A provisória de quem morreu entre criar e
|
|
168
|
+
* renomear também sai, pelo mesmo teste da trava.
|
|
169
|
+
*/
|
|
170
|
+
function minhaVez(dir, senha) {
|
|
171
|
+
const nomes = fs.readdirSync(dir);
|
|
172
|
+
for (const nome of nomes.filter(n => /^\.senha-.+\.tmp$/.test(n))) {
|
|
173
|
+
// Entre criar e trancar, a provisória viva fica um instante destrancada: só a velha é de quem morreu.
|
|
174
|
+
const st = fs.statSync(path.join(dir, nome), { throwIfNoEntry: false });
|
|
175
|
+
if (!st || Date.now() - st.mtimeMs < 30_000)
|
|
176
|
+
continue;
|
|
177
|
+
let fd;
|
|
178
|
+
try {
|
|
179
|
+
fd = abrirTrava(path.join(dir, nome), false);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (trancar(fd)) {
|
|
185
|
+
try {
|
|
186
|
+
fs.unlinkSync(path.join(dir, nome));
|
|
187
|
+
}
|
|
188
|
+
catch { /* outro já limpou */ }
|
|
189
|
+
}
|
|
190
|
+
fs.closeSync(fd);
|
|
191
|
+
}
|
|
192
|
+
const anteriores = nomes.filter(n => /^senha-.+\.lock$/.test(n) && n < senha.nome).sort();
|
|
193
|
+
for (const nome of anteriores) {
|
|
194
|
+
let fd;
|
|
195
|
+
try {
|
|
196
|
+
fd = abrirTrava(path.join(dir, nome), false);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (!trancar(fd)) {
|
|
202
|
+
fs.closeSync(fd);
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
fs.unlinkSync(path.join(dir, nome));
|
|
207
|
+
}
|
|
208
|
+
catch { /* outro já limpou */ }
|
|
209
|
+
fs.closeSync(fd);
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
function gravarDono(fd, dono, posse) {
|
|
214
|
+
const bytes = Buffer.from(JSON.stringify({ contrato: exports.CONTRATO_FILA_PESADA, ...dono, posse }) + '\n', 'utf8');
|
|
215
|
+
fs.ftruncateSync(fd, 0);
|
|
216
|
+
fs.writeSync(fd, bytes, 0, bytes.length, 0);
|
|
217
|
+
}
|
|
218
|
+
/** O artefato primeiro, depois a primeira vaga livre; sem vaga, solta o artefato. Nunca espera segurando nada. */
|
|
219
|
+
function tentarVaga(dir, pedido) {
|
|
220
|
+
const fdArtefato = tentarTravaExclusiva(path.join(dir, `artefato-${chaveDoArtefato(pedido.artefato)}.lock`));
|
|
221
|
+
if (fdArtefato === null)
|
|
222
|
+
return null;
|
|
223
|
+
for (let slot = 0; slot < pedido.slots; slot++) {
|
|
224
|
+
const fdVaga = tentarTravaExclusiva(path.join(dir, `vaga-${slot}.lock`));
|
|
225
|
+
if (fdVaga === null)
|
|
226
|
+
continue;
|
|
227
|
+
const dono = { slot, operacao: pedido.operacao, thread: pedido.thread, artefato: path.resolve(pedido.artefato),
|
|
228
|
+
pid: process.pid, desde: new Date().toISOString() };
|
|
229
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
230
|
+
gravarDono(fdVaga, dono, id);
|
|
231
|
+
gravarDono(fdArtefato, dono, id);
|
|
232
|
+
return { fdArtefato, fdVaga, slot, id };
|
|
233
|
+
}
|
|
234
|
+
fs.closeSync(fdArtefato);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function soltar(posse) {
|
|
238
|
+
for (const fd of [posse.fdVaga, posse.fdArtefato]) {
|
|
239
|
+
try {
|
|
240
|
+
fs.ftruncateSync(fd, 0);
|
|
241
|
+
}
|
|
242
|
+
catch { /* o descritor fecha de qualquer jeito */ }
|
|
243
|
+
try {
|
|
244
|
+
fs.closeSync(fd);
|
|
245
|
+
}
|
|
246
|
+
catch { /* já fechado */ }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Quem ocupa as vagas agora. Somente leitura do que os donos gravaram; a vaga que tranca na hora está livre e é
|
|
251
|
+
* solta em seguida. Vaga trancada sem dono legível ainda conta, como ocupada por desconhecido.
|
|
252
|
+
*/
|
|
253
|
+
function ocupantesDaFilaPesada(raiz) {
|
|
254
|
+
if (!kernelDisponivel())
|
|
255
|
+
return [];
|
|
256
|
+
const dir = dirDaFilaPesada(raiz);
|
|
257
|
+
const ocupantes = [];
|
|
258
|
+
for (const nome of fs.readdirSync(dir).filter(n => /^vaga-\d+\.lock$/.test(n)).sort()) {
|
|
259
|
+
let fd;
|
|
260
|
+
try {
|
|
261
|
+
fd = abrirTrava(path.join(dir, nome), false);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
if (trancar(fd))
|
|
268
|
+
continue;
|
|
269
|
+
const slot = Number(/^vaga-(\d+)\.lock$/.exec(nome)[1]);
|
|
270
|
+
let dono = {};
|
|
271
|
+
try {
|
|
272
|
+
dono = JSON.parse(fs.readFileSync(path.join(dir, nome), 'utf8'));
|
|
273
|
+
}
|
|
274
|
+
catch { /* dono ainda gravando */ }
|
|
275
|
+
ocupantes.push({ slot, operacao: typeof dono.operacao === 'string' ? dono.operacao : 'desconhecida',
|
|
276
|
+
thread: typeof dono.thread === 'string' ? dono.thread : null, artefato: typeof dono.artefato === 'string' ? dono.artefato : '',
|
|
277
|
+
pid: typeof dono.pid === 'number' ? dono.pid : 0, desde: typeof dono.desde === 'string' ? dono.desde : '' });
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
fs.closeSync(fd);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return ocupantes;
|
|
284
|
+
}
|
|
285
|
+
function recusaDaFila(pedido, esperouMs, ocupantes) {
|
|
286
|
+
const espera = esperouMs < 60000 ? `${Math.round(esperouMs / 1000)} s` : `${Math.round(esperouMs / 60000)} min`;
|
|
287
|
+
const linhas = [
|
|
288
|
+
`Operação pesada não executada: ${pedido.operacao}${pedido.thread ? ` da thread ${pedido.thread}` : ''} esperou ${espera} ` +
|
|
289
|
+
`e a fila de recursos pesados não liberou a vez (até ${pedido.slots} ao mesmo tempo, concurrency.max_parallel_heavy). ` +
|
|
290
|
+
'Verify e build juntos nesta máquina dão regressão falsa.',
|
|
291
|
+
...(ocupantes.length ? ocupantes.map(o => ` Quem ocupa: ${o.operacao}${o.thread ? ` da thread ${o.thread}` : ''}, ` +
|
|
292
|
+
`desde ${(0, horario_1.formatarDesde)(o.desde)}, processo ${o.pid}.`) : [' Quem ocupa: a vez é de outro pedido que chegou antes e ainda espera.']),
|
|
293
|
+
'O que você pode fazer:',
|
|
294
|
+
' 1. Esperar a vez: o dono da vaga segue rodando e, quando ele termina ou morre, a vaga volta sozinha.',
|
|
295
|
+
...(pedido.comandoDeEsperar ? [` ${pedido.comandoDeEsperar}`] : []),
|
|
296
|
+
' 2. Desligar a fila só nesta máquina, se ela aguenta: concurrency.max_parallel_heavy no orkastery.yaml (0 desliga).',
|
|
297
|
+
];
|
|
298
|
+
return { contrato: 'ork.fila-pesada-recusa/v1', motivo: 'fila-pesada.ocupada', operacao: pedido.operacao, thread: pedido.thread,
|
|
299
|
+
slots: pedido.slots, esperouMs, ocupantes, texto: linhas.join('\n') };
|
|
300
|
+
}
|
|
301
|
+
const pausa = new Int32Array(new SharedArrayBuffer(4));
|
|
302
|
+
function dormir(ms) {
|
|
303
|
+
if (ms > 0)
|
|
304
|
+
Atomics.wait(pausa, 0, 0, Math.ceil(ms));
|
|
305
|
+
}
|
|
306
|
+
/** A vez que este processo segura, por raiz de estado: a reentrada não pede vaga de novo. */
|
|
307
|
+
const emCurso = new Map();
|
|
308
|
+
/** O campo 22 do `/proc/<pid>/stat` (instante de início em ticks): com o pid, identifica o processo sem reuso. */
|
|
309
|
+
function inicioDoProcesso(pid) {
|
|
310
|
+
try {
|
|
311
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
312
|
+
return stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19] ?? null;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function paiDe(pid) {
|
|
319
|
+
try {
|
|
320
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
321
|
+
const pai = Number(stat.slice(stat.lastIndexOf(')') + 2).split(' ')[1]);
|
|
322
|
+
return Number.isSafeInteger(pai) && pai > 0 ? pai : null;
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** O processo é ancestral deste? Sobe pela cadeia de pais, no máximo 64 degraus. */
|
|
329
|
+
function ancestral(pid) {
|
|
330
|
+
let atual = process.ppid;
|
|
331
|
+
for (let i = 0; i < 64 && atual !== null && atual > 1; i++) {
|
|
332
|
+
if (atual === pid)
|
|
333
|
+
return true;
|
|
334
|
+
atual = paiDe(atual);
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* A vaga que um processo ancestral segura nesta raiz, pela marca que ele passou; `null` quando a marca não vale: outra
|
|
340
|
+
* raiz, dono morto ou trocado (pid com outro início), dono que não é ancestral, ou vaga que ele não segura mais, inclusive
|
|
341
|
+
* a de uma vez anterior dele (o identificador da posse gravado na vaga é outro).
|
|
342
|
+
*/
|
|
343
|
+
function vagaDoAncestral(chave, dir) {
|
|
344
|
+
const bruto = process.env[exports.ENV_DONO_DA_FILA];
|
|
345
|
+
if (!bruto)
|
|
346
|
+
return null;
|
|
347
|
+
let marca;
|
|
348
|
+
try {
|
|
349
|
+
marca = JSON.parse(bruto);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const { pid, slot } = marca;
|
|
355
|
+
if (marca.raiz !== chave || typeof pid !== 'number' || !Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid ||
|
|
356
|
+
typeof slot !== 'number' || !Number.isSafeInteger(slot) || slot < 0 || typeof marca.posse !== 'string')
|
|
357
|
+
return null;
|
|
358
|
+
if (inicioDoProcesso(pid) !== marca.inicio || !ancestral(pid))
|
|
359
|
+
return null;
|
|
360
|
+
const arquivo = path.join(dir, `vaga-${slot}.lock`);
|
|
361
|
+
try {
|
|
362
|
+
const gravado = JSON.parse(fs.readFileSync(arquivo, 'utf8'));
|
|
363
|
+
if (gravado.pid !== pid || gravado.posse !== marca.posse)
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
let fd;
|
|
370
|
+
try {
|
|
371
|
+
fd = abrirTrava(arquivo, false);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
return trancar(fd) ? null : slot;
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
fs.closeSync(fd);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Roda `executar` com a vez da fila de recursos pesados desta raiz de estado: a senha de chegada, a trava do
|
|
385
|
+
* artefato e uma das `slots` vagas, todas do kernel. Espera em primeiro plano até `esperarMs` e, sem a vez, lança
|
|
386
|
+
* `ErroDaFilaPesada` sem rodar nada. Dentro de quem já segura a vez neste processo, roda direto.
|
|
387
|
+
*/
|
|
388
|
+
function comRecursoPesado(raiz, pedido, executar) {
|
|
389
|
+
if (!(pedido.slots > 0))
|
|
390
|
+
return executar({ situacao: 'desligada', slot: null, esperouMs: 0 });
|
|
391
|
+
const chave = path.resolve((0, estado_thread_1.raizDoEstado)(raiz));
|
|
392
|
+
const atual = emCurso.get(chave);
|
|
393
|
+
if (atual)
|
|
394
|
+
return executar({ situacao: 'reentrada', slot: atual.slot, esperouMs: 0 });
|
|
395
|
+
if (!kernelDisponivel())
|
|
396
|
+
return executar({ situacao: 'sem-kernel', slot: null, esperouMs: 0 });
|
|
397
|
+
const dir = dirDaFilaPesada(raiz);
|
|
398
|
+
const doAncestral = vagaDoAncestral(chave, dir);
|
|
399
|
+
if (doAncestral !== null)
|
|
400
|
+
return executar({ situacao: 'reentrada', slot: doAncestral, esperouMs: 0 });
|
|
401
|
+
const inicio = Date.now();
|
|
402
|
+
const prazo = inicio + Math.max(0, pedido.esperarMs);
|
|
403
|
+
let senha = tirarSenha(dir);
|
|
404
|
+
let avisou = false;
|
|
405
|
+
try {
|
|
406
|
+
for (;;) {
|
|
407
|
+
const posse = minhaVez(dir, senha) ? tentarVaga(dir, pedido) : null;
|
|
408
|
+
if (posse) {
|
|
409
|
+
// Com a vaga na mão, a senha sai: quem vem depois disputa a próxima vaga livre.
|
|
410
|
+
devolverSenha(dir, senha);
|
|
411
|
+
senha = null;
|
|
412
|
+
emCurso.set(chave, { slot: posse.slot });
|
|
413
|
+
// Os comandos que rodam dentro da vez herdam a marca da posse: o verify aninhado neles é reentrada.
|
|
414
|
+
const marcaAnterior = process.env[exports.ENV_DONO_DA_FILA];
|
|
415
|
+
process.env[exports.ENV_DONO_DA_FILA] = JSON.stringify({ raiz: chave, pid: process.pid, inicio: inicioDoProcesso(process.pid), slot: posse.slot,
|
|
416
|
+
posse: posse.id });
|
|
417
|
+
try {
|
|
418
|
+
return executar({ situacao: 'adquirida', slot: posse.slot, esperouMs: Date.now() - inicio });
|
|
419
|
+
}
|
|
420
|
+
finally {
|
|
421
|
+
if (marcaAnterior === undefined)
|
|
422
|
+
delete process.env[exports.ENV_DONO_DA_FILA];
|
|
423
|
+
else
|
|
424
|
+
process.env[exports.ENV_DONO_DA_FILA] = marcaAnterior;
|
|
425
|
+
emCurso.delete(chave);
|
|
426
|
+
soltar(posse);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (Date.now() >= prazo)
|
|
430
|
+
throw new ErroDaFilaPesada(recusaDaFila(pedido, Date.now() - inicio, ocupantesDaFilaPesada(raiz)));
|
|
431
|
+
if (!avisou) {
|
|
432
|
+
avisou = true;
|
|
433
|
+
try {
|
|
434
|
+
pedido.aoEsperar?.(ocupantesDaFilaPesada(raiz));
|
|
435
|
+
}
|
|
436
|
+
catch { /* o aviso nunca derruba a espera */ }
|
|
437
|
+
}
|
|
438
|
+
dormir(Math.min(PAUSA_MS, prazo - Date.now()));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
devolverSenha(dir, senha);
|
|
443
|
+
}
|
|
444
|
+
}
|
package/dist/fix.js
CHANGED
|
@@ -213,7 +213,8 @@ function abrirRodada(carregado, threadId, opcoes = {}) {
|
|
|
213
213
|
function abrirRodadaSobConducao(carregado, threadId, opcoes) {
|
|
214
214
|
const { raiz } = carregado;
|
|
215
215
|
const thread = (0, thread_1.lerThread)(raiz, threadId);
|
|
216
|
-
|
|
216
|
+
// RM-058 N2 (A1 da rodada 2 do CHECK): a espera da fila pesada que o `--esperar` do CLI pediu.
|
|
217
|
+
const resultado = (0, verify_1.verificar)(carregado, threadId, { soClaims: opcoes.soClaims, conducao: { filaPesada: opcoes.conducao?.filaPesada } });
|
|
217
218
|
const rodada = ultimaRodada(raiz, threadId) + 1;
|
|
218
219
|
if (resultado.ok && resultado.motivos.length === 0) {
|
|
219
220
|
return {
|
|
@@ -319,7 +320,7 @@ function reverificarSobConducao(carregado, threadId, opcoes) {
|
|
|
319
320
|
: `${execucoes.length} comando(s) reexecutado(s) com sucesso no HEAD real`,
|
|
320
321
|
};
|
|
321
322
|
});
|
|
322
|
-
const verify = cobertura === 'completa' ? (0, verify_1.verificar)(carregado, threadId) : null;
|
|
323
|
+
const verify = cobertura === 'completa' ? (0, verify_1.verificar)(carregado, threadId, { conducao: { filaPesada: opcoes.conducao?.filaPesada } }) : null;
|
|
323
324
|
for (const v of vereditos) {
|
|
324
325
|
// Correcao sem comando proprio herda o veredito do verify completo, e so quando ele
|
|
325
326
|
// roda: sem ele, ela continua aberta em vez de virar aprovada por conveniencia.
|
package/dist/forja.js
CHANGED
|
@@ -38,7 +38,7 @@ const HOST = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9]
|
|
|
38
38
|
/**
|
|
39
39
|
* Sem shell, sem token: a CLI usa a autenticacao que ja tem; prompt e aviso de versao desligados.
|
|
40
40
|
* RM-054 (fatia 3): o binario e procurado como na rede (D11 da RM-053), no PATH e depois nas pastas de
|
|
41
|
-
* usuario (`~/.local/bin` e as outras): o gateway e o cron rodam com PATH curto, e o `gh` da
|
|
41
|
+
* usuario (`~/.local/bin` e as outras): o gateway e o cron rodam com PATH curto, e o `gh` da maquina-b
|
|
42
42
|
* mora em `~/.local/bin`. Sem achar, o nome vai cru ao spawn, e o ENOENT continua `forja.ausente`.
|
|
43
43
|
*/
|
|
44
44
|
const executorPadrao = (cmd, args, entrada, timeoutMs) => {
|
package/dist/gates.js
CHANGED
|
@@ -28,35 +28,10 @@ const thread_1 = require("./thread");
|
|
|
28
28
|
const hitl_ingress_receipt_1 = require("./hitl-ingress-receipt");
|
|
29
29
|
const hitl_local_receipt_1 = require("./hitl-local-receipt");
|
|
30
30
|
const hitl_contract_1 = require("./hitl-contract");
|
|
31
|
+
const locale_pulse_1 = require("./locale-pulse");
|
|
31
32
|
/** O que cada motivo tipado significa, em uma linha, para a saida do CLI. */
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
'claims.failed': 'alegacao verificavel reprovou na reexecucao no HEAD real',
|
|
35
|
-
'claims.unverifiable': 'alegacao sem comando de verificacao declarado',
|
|
36
|
-
'policy.violation': 'policy do manifesto com severidade block foi violada',
|
|
37
|
-
'runtime.autoconferencia': 'o CHECK foi despachado no mesmo runtime que fez o GO, e a thread exige validacao cruzada',
|
|
38
|
-
'verify.regression': 'comando que passava na baseline falha agora',
|
|
39
|
-
'verify.failed': 'comando de verificacao falhou (sem baseline para separar divida pre-existente)',
|
|
40
|
-
'verify.timeout': 'comando de verificacao estourou o prazo antes de terminar: nao ha veredito sobre ele',
|
|
41
|
-
'verify.sem-veredito': 'a rodada nao produziu veredito valido: comando que nao chegou a rodar, ou produto alterado entre o preparo e o fim',
|
|
42
|
-
'ci.failed': 'CHECK independente ausente, pendente ou reprovado no commit candidato',
|
|
43
|
-
'runtime.unavailable': 'runtime adapter indisponivel para o despacho',
|
|
44
|
-
'runtime.silencio': 'fase despachada sem heartbeat dentro do limite de liveness',
|
|
45
|
-
'runtime.rate-limited': 'o runtime bateu o limite de uso da assinatura e a fase morreu antes de terminar (bloco B3: entra na fila duravel e e retomada na janela seguinte)',
|
|
46
|
-
'runtime.quota-exhausted': 'a cota ou os creditos da conta do runtime acabaram (I-33: o perfil sai do rodizio e a fase segue em outro perfil, outro runtime ou na fila)',
|
|
47
|
-
'runtime.auth-missing': 'a conta do runtime nao esta autenticada (I-33: o perfil nunca recebe despacho ate o login ser refeito pelo proprio CLI)',
|
|
48
|
-
'runtime.model-unavailable': 'o modelo pedido nao existe ou a conta nao tem acesso a ele (RM-037: o perfil segue no rodizio e a fase vai a outro perfil com o mesmo modelo ou ao fallback do bloco)',
|
|
49
|
-
'runtime.profile-invalid': 'o perfil pedido no despacho (--perfil) nao existe no store ou e de outro runtime (RM-056: o despacho nunca troca de perfil sozinho)',
|
|
50
|
-
'runtime.workspace-untrusted': 'o runtime nao confia no diretorio da worktree e recusou o despacho (RM-055: so o dono aceita a confianca no terminal; depois o ork re-despacha o mesmo prompt)',
|
|
51
|
-
'runtime.consent-pending': 'o runtime espera o dono aceitar termos novos e recusou o despacho (RM-055: so o dono aceita no terminal; depois o ork re-despacha o mesmo prompt)',
|
|
52
|
-
'cost.violation': 'o despacho seria redirecionado para provider pago (violacao de custo: e o unico motivo que NUNCA recebe retry automatico)',
|
|
53
|
-
'tree.blocked': 'a arvore de destino nao esta disponivel para o merge (branch em check-out com alteracao nao commitada, ou worktree que nao pode ser montada)',
|
|
54
|
-
'lease.busy': 'lease ocupado por outra thread',
|
|
55
|
-
'lease.resume-unavailable': 'retomada indisponivel; libere o lease explicitamente com --forcar',
|
|
56
|
-
'conducao.em-andamento': 'outra conducao ja executa na worktree da thread (I-36): o pedido foi recusado com quem conduz e as tres acoes (esperar, acompanhar, assumir)',
|
|
57
|
-
'hitl.formato': 'o pedido HITL saiu fora do formato obrigatorio (pergunta em uma frase, de 3 a 5 alternativas rotuladas com consequencia, exatamente uma com o selo Recomendacao e o porque, corpo em lista; texto livre so com dependencia tecnica tipada)',
|
|
58
|
-
'human.pending': 'gate humano do modo de conducao ainda nao autorizou',
|
|
59
|
-
};
|
|
33
|
+
// RM-061 (F2b): a fonte e o catalogo pt-BR (`locale-pulse.ts`), com o ingles ao lado; o texto nao muda.
|
|
34
|
+
exports.DESCRICAO_DO_MOTIVO = locale_pulse_1.PULSE_PT_BR.motivos;
|
|
60
35
|
/**
|
|
61
36
|
* Motivos que reprovam em QUALQUER modo, inclusive `#Auto`.
|
|
62
37
|
*
|