@orkastery/cli 0.5.1 → 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/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/ork.md +9 -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/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +7 -6
- package/adapters/openclaw/README.md +26 -5
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +46 -11
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +49 -11
- 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/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +116 -18
- package/dist/adapters/codex.js +14 -4
- package/dist/agents-md.js +24 -14
- package/dist/auditrun.js +22 -3
- package/dist/board.js +20 -17
- package/dist/branch-de-estado.js +88 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/catalogo.js +33 -0
- package/dist/ci.js +21 -8
- package/dist/claims-conferir.js +342 -0
- package/dist/claims.js +41 -1
- package/dist/company-brain-client.js +36 -1
- package/dist/company-brain-context.js +158 -22
- 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 +70 -17
- package/dist/doctor-pulse-cron.js +349 -0
- package/dist/doctor-rede.js +150 -0
- package/dist/doctor.js +195 -111
- package/dist/entrega-pr.js +42 -29
- package/dist/escopo-escrita.js +10 -1
- package/dist/esgotamentos.js +181 -0
- package/dist/estado-thread.js +23 -1
- package/dist/experiencia-instalacao.js +45 -25
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-batida.js +310 -0
- package/dist/fabrica-estado.js +52 -14
- package/dist/fabrica-publicar.js +9 -1
- package/dist/fechamento.js +20 -66
- package/dist/fila-pesada.js +444 -0
- package/dist/fix.js +3 -2
- package/dist/forja.js +31 -7
- package/dist/gates.js +3 -24
- package/dist/handoff.js +10 -6
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-local-receipt.js +2 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +38 -3
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/hitl.js +29 -33
- package/dist/horario.js +41 -7
- package/dist/hosts.js +104 -11
- package/dist/impedimento.js +299 -0
- package/dist/index.js +961 -542
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +9 -1
- package/dist/init.js +56 -43
- package/dist/instabilidade.js +144 -0
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +170 -16
- package/dist/intelligence-graph-contexto.js +254 -0
- 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-parsers.js +6 -4
- package/dist/intelligence-graph-query.js +66 -19
- package/dist/leases.js +664 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +18 -1
- package/dist/licoes.js +1 -0
- 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/maestro-runtime.js +4 -1
- package/dist/manifest.js +149 -4
- package/dist/maquina.js +23 -4
- package/dist/master-digest.js +6 -1
- package/dist/master-nota.js +18 -11
- package/dist/master.js +145 -31
- package/dist/mcp-artifacts.js +43 -3
- package/dist/mcp-git.js +42 -8
- package/dist/mcp-grafo-worker.js +5 -3
- package/dist/mcp-grafo.js +55 -6
- package/dist/mcp-server.js +22 -3
- package/dist/mcp-ship.js +3 -1
- package/dist/mcp-verify.js +6 -1
- package/dist/memoria.js +48 -6
- package/dist/modos.js +3 -6
- package/dist/network-roadmap.js +196 -18
- 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 +40 -5
- package/dist/onboarding.js +40 -21
- package/dist/orkmind.js +99 -7
- package/dist/orquestracao.js +24 -21
- package/dist/parado-no-condutor.js +219 -38
- package/dist/phase.js +160 -52
- package/dist/policies.js +56 -5
- package/dist/portfolio.js +8 -3
- package/dist/postura-local.js +234 -0
- package/dist/preflight.js +11 -10
- package/dist/procedencia.js +225 -0
- package/dist/projeto-alvo.js +132 -5
- package/dist/prompts.js +40 -1
- package/dist/prova-ativacao.js +75 -6
- package/dist/pulse-delivery.js +47 -4
- package/dist/pulse.js +98 -19
- package/dist/ratelimit.js +24 -6
- package/dist/recall.js +3 -1
- package/dist/redacao-saida.js +63 -0
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +337 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +291 -140
- package/dist/roadmap-reservas.js +20 -3
- package/dist/roadmap-status.js +150 -34
- package/dist/runtime-context.js +181 -3
- package/dist/runtime-profiles.js +22 -1
- package/dist/runtimes.js +2 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-events.js +7 -3
- package/dist/session-watcher-claude.js +120 -24
- package/dist/session-watcher.js +185 -33
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -18
- package/dist/sessoes.js +83 -2
- package/dist/ship-publicar.js +800 -0
- package/dist/ship.js +157 -88
- package/dist/steal.js +77 -0
- package/dist/thread-close.js +3 -2
- package/dist/thread.js +299 -49
- 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 +25 -2
- package/dist/yaml.js +27 -8
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +2 -1
- package/skills/README.md +1 -1
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
|
@@ -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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mesmaForja = exports.rotuloDaForja = exports.executorPadrao = void 0;
|
|
4
|
+
exports.enderecoDoRemoto = enderecoDoRemoto;
|
|
5
|
+
exports.repositorioGithubNoHost = repositorioGithubNoHost;
|
|
4
6
|
exports.identidadeDaForja = identidadeDaForja;
|
|
5
7
|
exports.forjaDoArgumento = forjaDoArgumento;
|
|
6
8
|
exports.detalheSeguro = detalheSeguro;
|
|
@@ -27,14 +29,20 @@ exports.lerDaForja = lerDaForja;
|
|
|
27
29
|
const node_child_process_1 = require("node:child_process");
|
|
28
30
|
const hitl_1 = require("./hitl");
|
|
29
31
|
const policies_1 = require("./policies");
|
|
32
|
+
const rede_forja_1 = require("./rede-forja");
|
|
30
33
|
const PRAZO_MS = 30000;
|
|
31
34
|
const SHA = /^[0-9a-f]{40}$/;
|
|
32
35
|
const SEGMENTO = /^[A-Za-z0-9_.-]+$/;
|
|
33
36
|
/** Hostname de verdade: rotulos alfanumericos separados por ponto (`..` ou `a..b` nao passam). */
|
|
34
37
|
const HOST = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;
|
|
35
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Sem shell, sem token: a CLI usa a autenticacao que ja tem; prompt e aviso de versao desligados.
|
|
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 maquina-b
|
|
42
|
+
* mora em `~/.local/bin`. Sem achar, o nome vai cru ao spawn, e o ENOENT continua `forja.ausente`.
|
|
43
|
+
*/
|
|
36
44
|
const executorPadrao = (cmd, args, entrada, timeoutMs) => {
|
|
37
|
-
const r = (0, node_child_process_1.spawnSync)(cmd, [...args], {
|
|
45
|
+
const r = (0, node_child_process_1.spawnSync)((0, rede_forja_1.acharBinario)(cmd) ?? cmd, [...args], {
|
|
38
46
|
input: entrada, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024,
|
|
39
47
|
env: { ...process.env, GH_PROMPT_DISABLED: '1', GH_NO_UPDATE_NOTIFIER: '1', GLAB_CHECK_UPDATE: 'false', NO_COLOR: '1' },
|
|
40
48
|
});
|
|
@@ -63,10 +71,10 @@ function montar(tipo, host, caminho) {
|
|
|
63
71
|
return { tipo, host, repo: partes.join('/') };
|
|
64
72
|
}
|
|
65
73
|
/**
|
|
66
|
-
*
|
|
67
|
-
* caminho local e
|
|
74
|
+
* RM-037 (fatia 5, A5): o host e o caminho de uma URL de remoto (https, ssh ou scp), de qualquer forja. Credencial
|
|
75
|
+
* na URL nunca entra; caminho local e host que nao e hostname voltam null.
|
|
68
76
|
*/
|
|
69
|
-
function
|
|
77
|
+
function enderecoDoRemoto(url) {
|
|
70
78
|
const t = url.trim();
|
|
71
79
|
const esquema = /^(?:https?|ssh|git|git\+ssh|ssh\+git):\/\/(?:[^@/]*@)?([^/:?#@]+)(?::\d+)?\/([^?#\s]+)$/i.exec(t);
|
|
72
80
|
const scp = esquema ? null : /^(?:[^@\s/:]+@)?([^:\s/@]+):(?!\/\/)([^\s]+)$/.exec(t);
|
|
@@ -74,8 +82,24 @@ function identidadeDaForja(url) {
|
|
|
74
82
|
if (!achado)
|
|
75
83
|
return null;
|
|
76
84
|
const host = achado[1].toLowerCase();
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
return HOST.test(host) ? { host, caminho: achado[2].replace(/^\/+/, '') } : null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* RM-037 (fatia 5, A5): o `dono/nome` de um repositorio do GitHub no host do remoto, com qualquer host: o GitHub
|
|
89
|
+
* Enterprise mora em host proprio, com ou sem "github" no nome. Quem diz se o host e mesmo um GitHub e o `gh`.
|
|
90
|
+
*/
|
|
91
|
+
function repositorioGithubNoHost(url) {
|
|
92
|
+
const endereco = enderecoDoRemoto(url);
|
|
93
|
+
return endereco && tipoDoHost(endereco.host) !== 'gitlab' ? montar('github', endereco.host, endereco.caminho) : null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A forja de uma URL de remoto (https, ssh ou scp). Credencial na URL nunca entra na identidade;
|
|
97
|
+
* caminho local e forja desconhecida voltam null.
|
|
98
|
+
*/
|
|
99
|
+
function identidadeDaForja(url) {
|
|
100
|
+
const endereco = enderecoDoRemoto(url);
|
|
101
|
+
const tipo = endereco ? tipoDoHost(endereco.host) : null;
|
|
102
|
+
return endereco && tipo ? montar(tipo, endereco.host, endereco.caminho) : null;
|
|
79
103
|
}
|
|
80
104
|
/** `github:dono/repo`, `gitlab:grupo/sub/repo` (com host proprio: `gitlab:host.tld/grupo/repo`) ou uma URL. */
|
|
81
105
|
function forjaDoArgumento(texto) {
|
package/dist/gates.js
CHANGED
|
@@ -28,31 +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
|
-
'cost.violation': 'o despacho seria redirecionado para provider pago (violacao de custo: e o unico motivo que NUNCA recebe retry automatico)',
|
|
50
|
-
'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)',
|
|
51
|
-
'lease.busy': 'lease ocupado por outra thread',
|
|
52
|
-
'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)',
|
|
53
|
-
'hitl.formato': 'o pedido HITL saiu fora do formato obrigatorio (pergunta em uma frase, de 2 a 4 alternativas rotuladas com consequencia, exatamente uma recomendada com o porque, corpo em lista)',
|
|
54
|
-
'human.pending': 'gate humano do modo de conducao ainda nao autorizou',
|
|
55
|
-
};
|
|
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;
|
|
56
35
|
/**
|
|
57
36
|
* Motivos que reprovam em QUALQUER modo, inclusive `#Auto`.
|
|
58
37
|
*
|
package/dist/handoff.js
CHANGED
|
@@ -73,6 +73,7 @@ const phase_1 = require("./phase");
|
|
|
73
73
|
const thread_1 = require("./thread");
|
|
74
74
|
const memoria_1 = require("./memoria");
|
|
75
75
|
const util_1 = require("./util");
|
|
76
|
+
const procedencia_1 = require("./procedencia");
|
|
76
77
|
/** sha256 de um arquivo em disco, a prova de proveniencia de cada item. */
|
|
77
78
|
function sha256DoArquivo(caminho) {
|
|
78
79
|
if (!fs.existsSync(caminho))
|
|
@@ -98,7 +99,8 @@ function caminhoDoArtefato(raiz, thread, arquivo) {
|
|
|
98
99
|
? [path.resolve(thread.worktree, arquivo), path.resolve(raiz, arquivo)]
|
|
99
100
|
: [path.resolve(raiz, arquivo)];
|
|
100
101
|
for (const c of candidatos) {
|
|
101
|
-
|
|
102
|
+
// RM-047 (P2): o arquivo da claim vem do estado; fora da raiz e da worktree registrada, não vira ponteiro.
|
|
103
|
+
if (fs.existsSync(c) && fs.statSync(c).isFile() && (0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, c))
|
|
102
104
|
return c;
|
|
103
105
|
}
|
|
104
106
|
return null;
|
|
@@ -222,7 +224,7 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
|
|
|
222
224
|
if (s.origem === 'adocao')
|
|
223
225
|
continue;
|
|
224
226
|
const caminhoPrompt = path.resolve(raiz, s.promptPath);
|
|
225
|
-
if (!fs.existsSync(caminhoPrompt))
|
|
227
|
+
if (!fs.existsSync(caminhoPrompt) || !(0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, caminhoPrompt))
|
|
226
228
|
continue;
|
|
227
229
|
addPonteiro(`Prompt da sessao ${s.slug} (fase ${s.fase})`, caminhoPrompt, 'pedido-do-builder', s.fase);
|
|
228
230
|
}
|
|
@@ -261,7 +263,7 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
|
|
|
261
263
|
for (const e of anteriores) {
|
|
262
264
|
const arquivo = (0, memoria_1.arquivoDoHandoff)(e);
|
|
263
265
|
const emDisco = arquivo ? path.resolve(raiz, arquivo) : '';
|
|
264
|
-
if (!emDisco || !fs.existsSync(emDisco))
|
|
266
|
+
if (!emDisco || !fs.existsSync(emDisco) || !(0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, emDisco))
|
|
265
267
|
continue;
|
|
266
268
|
addPonteiro(`Handoff anterior desta thread (${arquivo})`, emDisco, 'tudo', proximaFase, `orkmind://handoff/${e.id}`);
|
|
267
269
|
}
|
|
@@ -270,7 +272,7 @@ function exportarHandoff(carregado, threadId, opcoes = {}) {
|
|
|
270
272
|
for (const e of (0, memoria_1.licoesDoProduto)(memoria, manifesto, threadId)) {
|
|
271
273
|
const postmortem = (0, memoria_1.postmortemDaLicao)(e);
|
|
272
274
|
const emDisco = postmortem ? path.resolve(raiz, postmortem) : '';
|
|
273
|
-
if (!emDisco || !fs.existsSync(emDisco))
|
|
275
|
+
if (!emDisco || !fs.existsSync(emDisco) || !(0, procedencia_1.caminhoContidoOuNulo)(raiz, thread, emDisco))
|
|
274
276
|
continue;
|
|
275
277
|
addPonteiro(`Licao da thread ${(0, memoria_1.threadDaEntrada)(e) ?? '(anterior)'}`, emDisco, 'json:resumo', 'MASTER', `orkmind://learning/${e.id}`);
|
|
276
278
|
}
|
|
@@ -398,9 +400,11 @@ function recall(carregado, threadId, ponteiro) {
|
|
|
398
400
|
if (!fs.existsSync(caminho) || !fs.statSync(caminho).isFile()) {
|
|
399
401
|
throw new Error(`ponteiro nao resolve: arquivo "${bruto}" nao existe a partir de ${raiz}`);
|
|
400
402
|
}
|
|
401
|
-
|
|
403
|
+
// RM-047 (P2): o `location` vem do handoff.json (estado, talvez versionado); só lê dentro do projeto.
|
|
404
|
+
const real = (0, procedencia_1.exigirCaminhoContido)(raiz, (0, thread_1.lerThread)(raiz, threadId), caminho, bruto);
|
|
405
|
+
const conteudoBruto = fs.readFileSync(real, 'utf8');
|
|
402
406
|
const linhas = conteudoBruto.split('\n');
|
|
403
|
-
const sha = sha256DoArquivo(
|
|
407
|
+
const sha = sha256DoArquivo(real);
|
|
404
408
|
const base = { location: `${bruto}#${ancora}`, caminho, ancora, sha256: sha };
|
|
405
409
|
const registrarRecall = (r) => {
|
|
406
410
|
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, ledger_1.TIPOS_DE_EVENTO.handoffRecuperado, {
|