@orkastery/cli 0.4.3 → 0.5.0
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/README.md +11 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
- package/adapters/claude-code/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +2 -2
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/master.md +11 -8
- package/adapters/claude-code/commands/ork.md +8 -0
- package/adapters/claude-code/commands/ship.md +2 -2
- package/adapters/codex/skills/ork/SKILL.md +12 -1
- package/adapters/hermes/README.md +3 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
- package/adapters/hermes/bin/ork-brain.sh +3 -0
- package/adapters/hermes/bin/ork-maestro.sh +4 -1
- package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
- package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
- package/adapters/hermes/hermes.plugin.json +2 -0
- package/adapters/hermes/hitl-ingress/__init__.py +36 -3
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
- package/adapters/openclaw/README.md +34 -2
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +92 -20
- package/adapters/openclaw/openclaw.plugin.json +10 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +32 -3
- package/adapters/openclaw/src/index.ts +108 -20
- package/assets/docs/padroes/documentacao-de-produto.md +2 -2
- package/assets/docs/padroes/roadmap-de-produto.md +1 -1
- package/assets/docs/produto/_modelo-feature.md +2 -0
- package/assets/orkmind_bridge.py +214 -1
- package/dist/adapters/claude-bg.js +6 -3
- package/dist/board.js +74 -0
- package/dist/branch-de-estado.js +5 -4
- package/dist/busca-semantica.js +161 -0
- package/dist/ci.js +141 -10
- package/dist/company-brain-cli.js +27 -2
- package/dist/company-brain-context.js +132 -0
- package/dist/company-brain-dossie.js +376 -0
- package/dist/company-brain-mcp.js +4 -1
- package/dist/conducao.js +71 -18
- package/dist/decisao-autonoma.js +42 -0
- package/dist/docs.js +64 -22
- package/dist/doctor.js +33 -0
- package/dist/entrega-pr.js +109 -5
- package/dist/experiencia-instalacao.js +256 -0
- package/dist/experiencia.js +72 -0
- package/dist/fabrica-estado.js +43 -10
- package/dist/fechamento.js +105 -0
- package/dist/forja.js +435 -0
- package/dist/gates.js +9 -1
- package/dist/hitl-classificacao.js +11 -1
- package/dist/hitl-contract.js +50 -7
- package/dist/hitl-curto.js +158 -0
- package/dist/hitl-gates.js +67 -1
- package/dist/hitl-lote.js +30 -15
- package/dist/hitl-presentation.js +10 -7
- package/dist/hitl-resumo.js +33 -12
- package/dist/hitl-texto-livre.js +150 -0
- package/dist/hosts.js +86 -1
- package/dist/index.js +602 -38
- package/dist/indice-vetorial.js +317 -0
- package/dist/init.js +13 -0
- package/dist/integracoes-locais.js +1 -1
- package/dist/intelligence-benchmark-contract.js +570 -0
- package/dist/intelligence-graph-cli.js +430 -0
- package/dist/intelligence-graph-contract.js +577 -0
- package/dist/intelligence-graph-extract-md.js +469 -0
- package/dist/intelligence-graph-extract-ts.js +810 -0
- package/dist/intelligence-graph-extract.js +369 -0
- package/dist/intelligence-graph-index.js +512 -0
- package/dist/intelligence-graph-parsers.js +327 -0
- package/dist/intelligence-graph-query.js +344 -0
- package/dist/intelligence-graph-repo.js +187 -0
- package/dist/leases.js +84 -0
- package/dist/ledger.js +14 -0
- package/dist/maestro-cli.js +10 -1
- package/dist/maestro-contract.js +5 -1
- package/dist/maestro-discovery.js +16 -2
- package/dist/maestro-snapshot.js +6 -2
- package/dist/manifest.js +142 -5
- package/dist/master-batch.js +3 -2
- package/dist/master-digest.js +2 -2
- package/dist/master-nota.js +203 -0
- package/dist/master.js +4 -0
- package/dist/mcp-experiencia.js +164 -0
- package/dist/mcp-git.js +4 -0
- package/dist/mcp-install.js +3 -1
- package/dist/mcp-maestro.js +3 -2
- package/dist/mcp-server.js +84 -5
- package/dist/memoria.js +147 -1
- package/dist/network-roadmap.js +907 -0
- package/dist/ocupacao.js +38 -2
- package/dist/onboarding.js +113 -7
- package/dist/orkmind.js +165 -9
- package/dist/phase.js +166 -13
- package/dist/policies.js +2 -0
- package/dist/projeto-alvo.js +551 -0
- package/dist/prompts.js +1 -1
- package/dist/pulse-consentimento.js +4 -2
- package/dist/pulse-delivery.js +5 -0
- package/dist/pulse-resposta.js +674 -49
- package/dist/pulse.js +21 -0
- package/dist/retry.js +51 -8
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +213 -0
- package/dist/thread-close.js +3 -0
- package/dist/verify.js +4 -3
- package/eval/casos/orchestration-experience-pt-br.json +48 -0
- package/eval/casos/orchestration-experience.json +48 -0
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +581 -0
- package/schemas/graph-benchmark.v1.schema.json +2240 -0
- package/schemas/maestro-snapshot.schema.json +23 -0
- package/skills/README.md +7 -5
- package/skills/core/onboarding/SKILL.md +7 -0
- package/skills/core/orchestration-experience/SKILL.md +113 -0
- package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/go-implementation/SKILL.md +2 -0
package/dist/pulse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.JANELA_DAS_DECISOES_MS = exports.CONTRATO_PULSE = void 0;
|
|
4
|
+
exports.apresentacaoCurtaDoItem = apresentacaoCurtaDoItem;
|
|
4
5
|
exports.comporPulse = comporPulse;
|
|
5
6
|
exports.montarPulse = montarPulse;
|
|
6
7
|
exports.textoDoPulse = textoDoPulse;
|
|
@@ -20,6 +21,7 @@ const util_1 = require("./util");
|
|
|
20
21
|
const horario_1 = require("./horario");
|
|
21
22
|
const hitl_presentation_1 = require("./hitl-presentation");
|
|
22
23
|
const hitl_contract_1 = require("./hitl-contract");
|
|
24
|
+
const hitl_curto_1 = require("./hitl-curto");
|
|
23
25
|
const decisao_autonoma_1 = require("./decisao-autonoma");
|
|
24
26
|
const docs_1 = require("./docs");
|
|
25
27
|
const conducao_1 = require("./conducao");
|
|
@@ -30,6 +32,18 @@ exports.JANELA_DAS_DECISOES_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
30
32
|
const impacto = (fase) => types_1.FASES.indexOf(fase) + 1;
|
|
31
33
|
const ordenar = (a, b) => (b.paradaHaMin ?? -1) - (a.paradaHaMin ?? -1) || b.impacto - a.impacto || a.id.localeCompare(b.id);
|
|
32
34
|
const minutos = (desde, quando) => desde && Number.isFinite(Date.parse(desde)) ? Math.max(0, Math.floor((Date.parse(quando) - Date.parse(desde)) / 60000)) : null;
|
|
35
|
+
/**
|
|
36
|
+
* RM-048 (D1 e D4): o texto curto de uma pergunta v2 de gate e a linha com o codigo estavel.
|
|
37
|
+
* Pedido sem codigo (v1, sessao) continua com o comando de antes: nada e inventado para ele.
|
|
38
|
+
*/
|
|
39
|
+
function apresentacaoCurtaDoItem(pedido, desde, quando) {
|
|
40
|
+
if (!(0, hitl_contract_1.ehV2)(pedido) || pedido.classe !== 'pergunta' || pedido.alvo.tipo !== 'gate')
|
|
41
|
+
return undefined;
|
|
42
|
+
const curto = (0, hitl_curto_1.montarPedidoCurto)((0, hitl_curto_1.entradaDoPedido)(pedido, desde ?? pedido.criadoEm), { quando, responder: { tipo: 'codigo', codigo: pedido.codigo } });
|
|
43
|
+
const recomendada = pedido.alternativas.find(a => a.recomendada)?.letra ?? 'a';
|
|
44
|
+
return { textos: { telegram: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'telegram'), terminal: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'terminal') },
|
|
45
|
+
comando: `${pedido.codigo} ${recomendada}` };
|
|
46
|
+
}
|
|
33
47
|
function comporPulse(carregado, entrada) {
|
|
34
48
|
const { radar, monitor, batch, orfas } = entrada, quando = radar.consultadoEm;
|
|
35
49
|
const humanos = [], automaticas = [];
|
|
@@ -186,6 +200,13 @@ function comporPulse(carregado, entrada) {
|
|
|
186
200
|
item.opcoes = (0, hitl_contract_1.escolhasDoPedido)(pedido).map(o => `${(0, hitl_contract_1.chaveDaEscolha)(pedido, o.numero)}. ${o.texto}`);
|
|
187
201
|
item.recomendacao = (0, hitl_contract_1.recomendacaoDoPedido)(pedido);
|
|
188
202
|
item.comandoResposta = `/ork ${(0, hitl_contract_1.alvoDoPedido)(pedido)?.tipo === 'session' ? 'session' : 'gate'} ${pedido.thread} ${pedido.id} <resposta>`;
|
|
203
|
+
// RM-048 (D1 e D4): a pergunta v2 de gate sai pelo contrato curto, e a linha que o dono
|
|
204
|
+
// recebe e o codigo estavel ("DE6H a"), nao o identificador que muda a cada renovacao.
|
|
205
|
+
const curta = apresentacaoCurtaDoItem(pedido, item.desdeEm, quando);
|
|
206
|
+
if (curta) {
|
|
207
|
+
item.apresentacaoCurta = curta.textos;
|
|
208
|
+
item.comandoResposta = curta.comando;
|
|
209
|
+
}
|
|
189
210
|
// O Pulse nao e uma conexao MCP: nenhum canal mcp-local sai `disponivel` daqui.
|
|
190
211
|
item.canais = (0, hitl_presentation_1.ofertaDoPedido)(pedido);
|
|
191
212
|
}
|
package/dist/retry.js
CHANGED
|
@@ -482,10 +482,29 @@ function escalarEsforco(base, degraus) {
|
|
|
482
482
|
* de provider pago enquanto a fase esperava, a retomada recusa em vez de gastar.
|
|
483
483
|
*/
|
|
484
484
|
function redespachar(carregado, thread, fase, promptRelativo, sha, opcoes = {}) {
|
|
485
|
-
const run = () => redespacharSobLock(carregado, (0, thread_1.lerThread)(carregado.raiz, thread.id), fase, promptRelativo, sha, opcoes);
|
|
486
|
-
|
|
485
|
+
const run = (retida) => redespacharSobLock(carregado, (0, thread_1.lerThread)(carregado.raiz, thread.id), fase, promptRelativo, sha, opcoes, retida);
|
|
486
|
+
if (opcoes.dryRun)
|
|
487
|
+
return run();
|
|
488
|
+
// RM-037 (defeito 1; A2, G1 e N3 do CHECK 1 e 2, B-1 e A-1r do CHECK 4): o fallback do bloco leva a
|
|
489
|
+
// retomada ao codex. O redespacho pede a baseline depois das guardas baratas, RETENDO a conducao tomada; ela
|
|
490
|
+
// e gravada aqui, fora do lock HITL, como reentrada dessa tomada, e a retomada repete uma vez com ela.
|
|
491
|
+
const r = (0, hitl_gates_1.comLockHitl)(carregado.raiz, thread.id, () => run());
|
|
492
|
+
const retida = r.retida;
|
|
493
|
+
if (!retida)
|
|
494
|
+
return r;
|
|
495
|
+
delete r.retida;
|
|
496
|
+
try {
|
|
497
|
+
(0, phase_1.garantirBaselineDoDespacho)(carregado, thread.id, { fase, prompt: '', runtime: opcoes.runtime ?? undefined,
|
|
498
|
+
model: opcoes.model ?? undefined, effort: opcoes.effort ?? undefined, ...(opcoes.canal ? { canal: opcoes.canal } : {}) }, retida.identidade);
|
|
499
|
+
return (0, hitl_gates_1.comLockHitl)(carregado.raiz, thread.id, () => run(retida));
|
|
500
|
+
}
|
|
501
|
+
finally {
|
|
502
|
+
retida.devolver();
|
|
503
|
+
}
|
|
487
504
|
}
|
|
488
|
-
function redespacharSobLock(carregado, thread, fase, promptRelativo, sha, opcoes = {}
|
|
505
|
+
function redespacharSobLock(carregado, thread, fase, promptRelativo, sha, opcoes = {},
|
|
506
|
+
/** A tomada que a primeira passada reteve para a baseline; com ela, esta e a repeticao. */
|
|
507
|
+
tomadaPrevia) {
|
|
489
508
|
const { raiz, manifesto } = carregado;
|
|
490
509
|
const absoluto = path.isAbsolute(promptRelativo)
|
|
491
510
|
? promptRelativo
|
|
@@ -536,13 +555,24 @@ function redespacharSobLock(carregado, thread, fase, promptRelativo, sha, opcoes
|
|
|
536
555
|
// I-36 (T6): validado o pedido, a retomada toma a conducao antes de redespachar, como o
|
|
537
556
|
// `ork phase run`. A mesma fase com o mesmo prompt de quem ja conduz nao abre sessao nova;
|
|
538
557
|
// outro pedido espera a vez.
|
|
539
|
-
const identidade = (0, runtime_context_1.novaIdentidadeDeDespacho)(thread.id, fase)
|
|
558
|
+
const identidade = tomadaPrevia ? { ...(0, runtime_context_1.novaIdentidadeDeDespacho)(thread.id, fase), dispatchId: tomadaPrevia.identidade }
|
|
559
|
+
: (0, runtime_context_1.novaIdentidadeDeDespacho)(thread.id, fase);
|
|
540
560
|
const canal = opcoes.canal ?? (0, conducao_1.canalDoProcesso)();
|
|
541
561
|
const doBlocoDaConducao = (0, setup_1.limitesDoBloco)(manifesto, (0, setup_1.lerSetup)(raiz), thread.modo, fase);
|
|
542
562
|
const pedidoDeConducao = { canal, correlacao: null, operacao: 'retry.run', fase, promptSha256: sha,
|
|
543
563
|
identidade: identidade.dispatchId, prazoMs: (0, phase_1.prazoDaSessao)(doBlocoDaConducao) };
|
|
544
564
|
let conducao = null;
|
|
545
|
-
if (
|
|
565
|
+
if (tomadaPrevia) {
|
|
566
|
+
// R5-B1 do CHECK 5: a sucedida que o dono respondeu durante a baseline recebe o lease de volta.
|
|
567
|
+
if (!tomadaPrevia.sucessaoAindaVale()) {
|
|
568
|
+
tomadaPrevia.devolver();
|
|
569
|
+
const recusa = (0, conducao_1.recusaDeConducao)(thread.id, (0, conducao_1.conducaoDaThread)(raiz, thread.id), pedidoDeConducao);
|
|
570
|
+
(0, conducao_1.registrarRecusa)(raiz, recusa);
|
|
571
|
+
return { ...vazio, ok: false, motivo: 'conducao.em-andamento', detalhe: recusa.texto, dryRun: false };
|
|
572
|
+
}
|
|
573
|
+
conducao = tomadaPrevia;
|
|
574
|
+
}
|
|
575
|
+
else if (!opcoes.dryRun) {
|
|
546
576
|
const tomada = (0, conducao_1.tomarConducao)(raiz, thread.id, pedidoDeConducao);
|
|
547
577
|
if (!tomada.ok) {
|
|
548
578
|
if (tomada.idempotente && tomada.atual) {
|
|
@@ -571,6 +601,17 @@ function redespacharSobLock(carregado, thread, fase, promptRelativo, sha, opcoes
|
|
|
571
601
|
effort: opcoes.effort ?? undefined,
|
|
572
602
|
}, doBloco);
|
|
573
603
|
const rt = (0, runtimes_1.resolverRuntime)(runtime);
|
|
604
|
+
if (!opcoes.dryRun && (0, phase_1.baselineDoDespachoNecessaria)(carregado, thread.id, { fase, prompt: '', runtime, model, effort })) {
|
|
605
|
+
// Na primeira passada a tomada fica retida para a baseline (o `finally` nao a solta); na repeticao, o
|
|
606
|
+
// `finally` devolve o que a tomada consumiu (A-1 do CHECK 3).
|
|
607
|
+
const pendente = { ...vazio, ok: false, motivo: 'runtime.unavailable', dryRun: false,
|
|
608
|
+
detalhe: `baseline.pendente: o bloco com GO no ${runtime} precisa da baseline antes da retomada` };
|
|
609
|
+
if (!tomadaPrevia && conducao) {
|
|
610
|
+
pendente.retida = conducao;
|
|
611
|
+
conducao = null;
|
|
612
|
+
}
|
|
613
|
+
return pendente;
|
|
614
|
+
}
|
|
574
615
|
// D14 (GO-FIX 2): a retomada claude-bg abre com a mesma colaboração e o mesmo contexto de
|
|
575
616
|
// runtime do `ork phase run`; sem eles, o PLAN retomado saía sem negar Edit, Write e
|
|
576
617
|
// NotebookEdit e sem a allowlist do núcleo. O ramo Codex não muda.
|
|
@@ -615,7 +656,8 @@ function redespacharSobLock(carregado, thread, fase, promptRelativo, sha, opcoes
|
|
|
615
656
|
logDir: path.join((0, thread_1.dirThread)(raiz, thread.id), 'sessoes'),
|
|
616
657
|
vinculo: { thread: thread.id, fase, promptSha256: sha },
|
|
617
658
|
...(perfil ? { perfil } : {}),
|
|
618
|
-
|
|
659
|
+
// RM-037 (defeito 2): o modo plano segue a mesma regra do `ork phase run`, pelo bloco.
|
|
660
|
+
...(runtime === 'claude-bg' ? { contextoRuntime, ...((0, phase_1.modoDaSessaoDoBloco)(thread, fase).plano ? { colaboracao: 'plan' } : {}) } : {}),
|
|
619
661
|
// I-36 (D4): a sessao retomada herda a identidade do despacho e o canal, como no `ork phase run`.
|
|
620
662
|
ambienteExtra: (0, conducao_1.ambienteDaConducao)(identidade.dispatchId, thread.id, canal),
|
|
621
663
|
});
|
|
@@ -695,8 +737,9 @@ function redespacharSobLock(carregado, thread, fase, promptRelativo, sha, opcoes
|
|
|
695
737
|
};
|
|
696
738
|
}
|
|
697
739
|
finally {
|
|
698
|
-
// Retomada que nao virou sessao devolve a conducao; a que virou ja a
|
|
699
|
-
|
|
740
|
+
// Retomada que nao virou sessao devolve a conducao, com o que a tomada consumiu; a que virou ja a
|
|
741
|
+
// entregou a sessao, e devolver entao nao faz nada.
|
|
742
|
+
conducao?.devolver();
|
|
700
743
|
}
|
|
701
744
|
}
|
|
702
745
|
// ---------------------------------------------------------------------------
|
package/dist/roadmap-reservas.js
CHANGED
|
@@ -33,14 +33,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.REMOTO_PADRAO = exports.BRANCH_DE_RESERVAS = exports.CONTRATO_RESERVA = void 0;
|
|
36
|
+
exports.DIR_DE_FEATS = exports.CONTRATO_FEAT = exports.PRAZO_DO_FECHAMENTO_MS = exports.DIR_DE_RESERVAS = exports.REMOTO_PADRAO = exports.BRANCH_DE_RESERVAS = exports.CONTRATO_RESERVA = void 0;
|
|
37
|
+
exports.redeDoFechamento = redeDoFechamento;
|
|
37
38
|
exports.exigirItem = exigirItem;
|
|
38
39
|
exports.quemSouEu = quemSouEu;
|
|
40
|
+
exports.reservaValida = reservaValida;
|
|
39
41
|
exports.reservasLocais = reservasLocais;
|
|
40
42
|
exports.listarReservas = listarReservas;
|
|
43
|
+
exports.maiorFeatConhecida = maiorFeatConhecida;
|
|
44
|
+
exports.reservarFeat = reservarFeat;
|
|
41
45
|
exports.painelEmMarkdown = painelEmMarkdown;
|
|
42
46
|
exports.pegarItem = pegarItem;
|
|
43
47
|
exports.soltarItem = soltarItem;
|
|
48
|
+
exports.reservasOrfas = reservasOrfas;
|
|
49
|
+
exports.soltarReservaDaThread = soltarReservaDaThread;
|
|
50
|
+
exports.soltarReservasOrfas = soltarReservasOrfas;
|
|
44
51
|
exports.textoDasReservas = textoDasReservas;
|
|
45
52
|
/**
|
|
46
53
|
* I-47: reservas de item do roadmap entre maquinas, como documentacao em codigo.
|
|
@@ -64,15 +71,32 @@ const os = __importStar(require("node:os"));
|
|
|
64
71
|
const path = __importStar(require("node:path"));
|
|
65
72
|
const branch_de_estado_1 = require("./branch-de-estado");
|
|
66
73
|
const horario_1 = require("./horario");
|
|
74
|
+
const ledger_1 = require("./ledger");
|
|
67
75
|
const maquina_1 = require("./maquina");
|
|
76
|
+
const thread_1 = require("./thread");
|
|
68
77
|
const util_1 = require("./util");
|
|
69
78
|
exports.CONTRATO_RESERVA = 'ork.roadmap-reserva/v1';
|
|
70
79
|
exports.BRANCH_DE_RESERVAS = 'ork/roadmap-reservas';
|
|
71
80
|
exports.REMOTO_PADRAO = 'origin';
|
|
72
|
-
|
|
81
|
+
/** O diretorio das reservas na branch; a leitura sem clone (RM-054) le o mesmo. */
|
|
82
|
+
exports.DIR_DE_RESERVAS = 'reservas';
|
|
83
|
+
const DIR = exports.DIR_DE_RESERVAS;
|
|
73
84
|
const PAINEL = 'RESERVAS.md';
|
|
74
85
|
const TENTATIVAS = 5;
|
|
75
86
|
const ITEM = /^RM-\d{3}$/;
|
|
87
|
+
/**
|
|
88
|
+
* O fechamento nao pode ficar parado na rede (achado A3 do CHECK 1): prazo curto por chamada e git
|
|
89
|
+
* sem pergunta no terminal. O ssh de quem ja o configurou vale (aviso N1 do CHECK 2): com
|
|
90
|
+
* `GIT_SSH_COMMAND`, `GIT_SSH` ou `core.sshCommand`, nada muda nele e o prazo segura a espera; sem
|
|
91
|
+
* nenhum, o ssh roda em modo lote, que usa o agente e as chaves mas nunca pergunta a senha.
|
|
92
|
+
*/
|
|
93
|
+
exports.PRAZO_DO_FECHAMENTO_MS = 15000;
|
|
94
|
+
function redeDoFechamento(raiz) {
|
|
95
|
+
const sshProprio = !!process.env.GIT_SSH_COMMAND || !!process.env.GIT_SSH ||
|
|
96
|
+
(0, branch_de_estado_1.git)(raiz, ['config', '--get', 'core.sshCommand']).ok;
|
|
97
|
+
return { timeoutMs: exports.PRAZO_DO_FECHAMENTO_MS,
|
|
98
|
+
env: { GIT_TERMINAL_PROMPT: '0', ...(sshProprio ? {} : { GIT_SSH_COMMAND: 'ssh -o BatchMode=yes' }) } };
|
|
99
|
+
}
|
|
76
100
|
const PREFIXO = 'roadmap';
|
|
77
101
|
/** O item precisa existir no roadmap do projeto (`docs/roadmap/RM-NNN-*.md`). */
|
|
78
102
|
function exigirItem(raiz, item) {
|
|
@@ -95,14 +119,20 @@ function quemSouEu(raiz, opcoes = {}) {
|
|
|
95
119
|
return { por, maquina: (0, maquina_1.nomeDaMaquina)(opcoes.maquina) };
|
|
96
120
|
}
|
|
97
121
|
/** Traz a ponta da branch de reservas. `null` quando ela ainda nao existe no remoto. */
|
|
98
|
-
function buscar(raiz, remoto) {
|
|
99
|
-
return (0, branch_de_estado_1.buscarBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, PREFIXO);
|
|
122
|
+
function buscar(raiz, remoto, rede = {}) {
|
|
123
|
+
return (0, branch_de_estado_1.buscarBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, PREFIXO, rede.timeoutMs, rede.env);
|
|
100
124
|
}
|
|
101
125
|
function lerDaPonta(raiz, ponta) {
|
|
102
126
|
return (0, branch_de_estado_1.jsonsDaPonta)(raiz, ponta, DIR, PREFIXO)
|
|
103
127
|
.filter((r) => r.contrato === exports.CONTRATO_RESERVA)
|
|
104
128
|
.sort((a, b) => a.item.localeCompare(b.item));
|
|
105
129
|
}
|
|
130
|
+
/** RM-054: a reserva lida de fora do `ork` desta maquina (forja, outra copia) so vale inteira. */
|
|
131
|
+
function reservaValida(bruto) {
|
|
132
|
+
const r = bruto;
|
|
133
|
+
return !!r && typeof r === 'object' && r.contrato === exports.CONTRATO_RESERVA && typeof r.item === 'string' && ITEM.test(r.item) &&
|
|
134
|
+
typeof r.por === 'string' && typeof r.maquina === 'string' && typeof r.desdeEm === 'string';
|
|
135
|
+
}
|
|
106
136
|
/** As reservas da ultima copia lida nesta maquina, sem rede (I-51: o retrato da fabrica usa). */
|
|
107
137
|
function reservasLocais(raiz, remoto = exports.REMOTO_PADRAO) {
|
|
108
138
|
try {
|
|
@@ -114,11 +144,73 @@ function reservasLocais(raiz, remoto = exports.REMOTO_PADRAO) {
|
|
|
114
144
|
}
|
|
115
145
|
/** `ork roadmap reservas`: quem esta com cada item, lido do remoto (ou da ultima copia, sem rede). */
|
|
116
146
|
function listarReservas(raiz, opcoes = {}) {
|
|
117
|
-
const { ponta, atualizado } = buscar(raiz, opcoes.remoto ?? exports.REMOTO_PADRAO);
|
|
118
|
-
return { reservas: lerDaPonta(raiz, ponta), atualizado, ponta };
|
|
147
|
+
const { ponta, atualizado } = buscar(raiz, opcoes.remoto ?? exports.REMOTO_PADRAO, opcoes.rede);
|
|
148
|
+
return { reservas: lerDaPonta(raiz, ponta), feats: featsDaPonta(raiz, ponta), atualizado, ponta };
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// RM-037 (rm037noite, defeito 6): o numero da FEAT nova tambem e reservado.
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
/**
|
|
154
|
+
* A RM-052, a RM-026 e a RM-051 criaram a FEAT-030 cada uma, em maquinas e threads diferentes: o numero
|
|
155
|
+
* saia do `ls docs/produto` de cada branch. Agora ele sai de `ork roadmap feat`, na mesma branch de
|
|
156
|
+
* reservas e pela mesma atomicidade do item (o primeiro push vence; o segundo rele e leva o seguinte).
|
|
157
|
+
* A reserva de numero e para sempre: numero queimado nao volta, e por isso nunca colide.
|
|
158
|
+
*/
|
|
159
|
+
exports.CONTRATO_FEAT = 'ork.feat-reserva/v1';
|
|
160
|
+
exports.DIR_DE_FEATS = 'feats';
|
|
161
|
+
const NUMERO_DE_FEAT = /^FEAT-(\d{3})$/;
|
|
162
|
+
const ARQUIVO_DE_FEAT = /^FEAT-(\d{3})-/;
|
|
163
|
+
function featsDaPonta(raiz, ponta) {
|
|
164
|
+
return (0, branch_de_estado_1.jsonsDaPonta)(raiz, ponta, exports.DIR_DE_FEATS, PREFIXO)
|
|
165
|
+
.filter((r) => r && r.contrato === exports.CONTRATO_FEAT && typeof r.feat === 'string' && NUMERO_DE_FEAT.test(r.feat))
|
|
166
|
+
.sort((a, b) => a.feat.localeCompare(b.feat));
|
|
167
|
+
}
|
|
168
|
+
const numeroDaFeat = (feat) => Number(NUMERO_DE_FEAT.exec(feat)?.[1] ?? 0);
|
|
169
|
+
/**
|
|
170
|
+
* O maior numero de FEAT que o projeto ja conhece: a arvore de trabalho e a ponta de toda branch local
|
|
171
|
+
* e de toda branch do remoto ja buscada (achado A6 do CHECK 1: a FEAT criada a mao numa PR aberta, ou
|
|
172
|
+
* por um `ork` antigo, tambem conta). Branch que esta maquina nunca buscou continua invisivel.
|
|
173
|
+
*/
|
|
174
|
+
function maiorFeatConhecida(raiz, remoto = exports.REMOTO_PADRAO) {
|
|
175
|
+
const nomes = [];
|
|
176
|
+
const dir = path.join(raiz, 'docs', 'produto');
|
|
177
|
+
if (fs.existsSync(dir))
|
|
178
|
+
nomes.push(...fs.readdirSync(dir));
|
|
179
|
+
const refs = (0, branch_de_estado_1.git)(raiz, ['for-each-ref', '--format=%(objectname)', 'refs/heads', `refs/remotes/${remoto}`]);
|
|
180
|
+
const pontas = new Set(refs.ok ? refs.stdout.split('\n').map((l) => l.trim()).filter(Boolean) : []);
|
|
181
|
+
for (const ponta of pontas) {
|
|
182
|
+
const r = (0, branch_de_estado_1.git)(raiz, ['ls-tree', '--name-only', ponta, '--', 'docs/produto/']);
|
|
183
|
+
if (r.ok)
|
|
184
|
+
nomes.push(...r.stdout.split('\n').map((n) => path.posix.basename(n)));
|
|
185
|
+
}
|
|
186
|
+
return nomes.reduce((maior, nome) => Math.max(maior, Number(ARQUIVO_DE_FEAT.exec(nome)?.[1] ?? 0)), 0);
|
|
187
|
+
}
|
|
188
|
+
/** `ork roadmap feat`: reserva o proximo numero de FEAT para esta maquina, por push atomico. */
|
|
189
|
+
function reservarFeat(raiz, opcoes = {}) {
|
|
190
|
+
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
191
|
+
const eu = quemSouEu(raiz, opcoes);
|
|
192
|
+
for (let tentativa = 1; tentativa <= TENTATIVAS; tentativa++) {
|
|
193
|
+
const { ponta, atualizado } = buscar(raiz, remoto);
|
|
194
|
+
if (!atualizado)
|
|
195
|
+
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS} em ${remoto}; reservar exige rede`);
|
|
196
|
+
const feats = featsDaPonta(raiz, ponta);
|
|
197
|
+
const proximo = Math.max(maiorFeatConhecida(raiz, remoto), ...feats.map((r) => numeroDaFeat(r.feat))) + 1;
|
|
198
|
+
if (proximo > 999)
|
|
199
|
+
throw new Error('roadmap.feat: os numeros de FEAT de tres digitos acabaram');
|
|
200
|
+
const feat = `FEAT-${String(proximo).padStart(3, '0')}`;
|
|
201
|
+
const reserva = { contrato: exports.CONTRATO_FEAT, feat, por: eu.por, maquina: eu.maquina,
|
|
202
|
+
thread: opcoes.thread ?? null, nota: opcoes.nota ?? null, em: opcoes.agora ?? (0, util_1.agora)() };
|
|
203
|
+
const commit = (0, branch_de_estado_1.gravarNaBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, ponta, [
|
|
204
|
+
{ caminho: `${exports.DIR_DE_FEATS}/${feat}.json`, conteudo: JSON.stringify(reserva, null, 2) + '\n' },
|
|
205
|
+
{ caminho: PAINEL, conteudo: painelEmMarkdown(lerDaPonta(raiz, ponta), [...feats, reserva]) },
|
|
206
|
+
], `reserva: ${feat} para ${eu.por} em ${eu.maquina}${reserva.thread ? ` (thread ${reserva.thread})` : ''}`, PREFIXO);
|
|
207
|
+
if (commit)
|
|
208
|
+
return { feat, reserva, commit, tentativas: tentativa };
|
|
209
|
+
}
|
|
210
|
+
throw new Error(`roadmap.concorrencia: ${TENTATIVAS} pushes recusados seguidos; tente de novo em instantes`);
|
|
119
211
|
}
|
|
120
212
|
/** O `RESERVAS.md` da branch: a mesma lista, para quem abre o GitHub. */
|
|
121
|
-
function painelEmMarkdown(reservas) {
|
|
213
|
+
function painelEmMarkdown(reservas, feats = []) {
|
|
122
214
|
const linhas = [
|
|
123
215
|
'# Reservas do roadmap',
|
|
124
216
|
'',
|
|
@@ -126,20 +218,24 @@ function painelEmMarkdown(reservas) {
|
|
|
126
218
|
'não edite à mão. Antes de começar um item: `ork roadmap reservas`.',
|
|
127
219
|
'',
|
|
128
220
|
];
|
|
221
|
+
const numeros = feats.length === 0 ? [] : ['', '## Números de FEAT reservados', '',
|
|
222
|
+
'O número da FEAT nova sai de `ork roadmap feat`; número reservado não volta.', '',
|
|
223
|
+
'| FEAT | Com quem | Máquina | Thread | Em |', '| --- | --- | --- | --- | --- |',
|
|
224
|
+
...feats.map((f) => `| ${f.feat} | ${f.por} | ${f.maquina} | ${f.thread ?? 'sem thread'} | ${(0, horario_1.formatarDataHora)(f.em)} |`)];
|
|
129
225
|
if (reservas.length === 0)
|
|
130
|
-
return [...linhas, 'Nenhum item reservado.', ''].join('\n');
|
|
226
|
+
return [...linhas, 'Nenhum item reservado.', ...numeros, ''].join('\n');
|
|
131
227
|
linhas.push('| Item | Com quem | Máquina | Thread | Desde | Nota |', '| --- | --- | --- | --- | --- | --- |');
|
|
132
228
|
for (const r of reservas) {
|
|
133
229
|
linhas.push(`| ${r.item} | ${r.por} | ${r.maquina} | ${r.thread ?? '—'} | ${(0, horario_1.formatarDataHora)(r.desdeEm)} | ${r.nota ?? '—'} |`);
|
|
134
230
|
}
|
|
135
|
-
return [...linhas, '', (0, horario_1.legendaDoFuso)(), ''].join('\n');
|
|
231
|
+
return [...linhas, ...numeros, '', (0, horario_1.legendaDoFuso)(), ''].join('\n');
|
|
136
232
|
}
|
|
137
233
|
/** Grava a reserva (ou a remocao dela) e o painel numa versao nova da branch. Recusa volta `false`. */
|
|
138
|
-
function gravar(raiz, remoto, ponta, mudanca, todas, mensagem) {
|
|
234
|
+
function gravar(raiz, remoto, ponta, mudanca, todas, mensagem, rede = {}) {
|
|
139
235
|
return (0, branch_de_estado_1.gravarNaBranch)(raiz, remoto, exports.BRANCH_DE_RESERVAS, ponta, [
|
|
140
236
|
{ caminho: `${DIR}/${mudanca.item}.json`, conteudo: mudanca.reserva ? JSON.stringify(mudanca.reserva, null, 2) + '\n' : null },
|
|
141
|
-
{ caminho: PAINEL, conteudo: painelEmMarkdown(todas) },
|
|
142
|
-
], mensagem, PREFIXO);
|
|
237
|
+
{ caminho: PAINEL, conteudo: painelEmMarkdown(todas, featsDaPonta(raiz, ponta)) },
|
|
238
|
+
], mensagem, PREFIXO, rede);
|
|
143
239
|
}
|
|
144
240
|
function ehMinha(r, eu) {
|
|
145
241
|
return r.por === eu.por && r.maquina === eu.maquina;
|
|
@@ -163,11 +259,14 @@ function pegarItem(raiz, item, opcoes = {}) {
|
|
|
163
259
|
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
164
260
|
const eu = quemSouEu(raiz, opcoes);
|
|
165
261
|
for (let tentativa = 1; tentativa <= TENTATIVAS; tentativa++) {
|
|
166
|
-
const { ponta, atualizado } = buscar(raiz, remoto);
|
|
262
|
+
const { ponta, atualizado } = buscar(raiz, remoto, opcoes.rede);
|
|
167
263
|
if (!atualizado)
|
|
168
264
|
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS} em ${remoto}; reservar exige rede`);
|
|
169
265
|
const todas = lerDaPonta(raiz, ponta);
|
|
170
266
|
const atual = todas.find((r) => r.item === id) ?? null;
|
|
267
|
+
if (opcoes.threadEsperada !== undefined && (atual?.thread ?? null) !== opcoes.threadEsperada) {
|
|
268
|
+
return { acao: 'nada', item: id, reserva: atual, commit: null, tentativas: tentativa };
|
|
269
|
+
}
|
|
171
270
|
const quando = opcoes.agora ?? (0, util_1.agora)();
|
|
172
271
|
let acao = 'pegou';
|
|
173
272
|
let tomadaDe;
|
|
@@ -191,7 +290,7 @@ function pegarItem(raiz, item, opcoes = {}) {
|
|
|
191
290
|
const mensagem = acao === 'tomou'
|
|
192
291
|
? `reserva: ${id} tomada por ${eu.por} em ${eu.maquina} (de ${tomadaDe.por} em ${tomadaDe.maquina}): ${tomadaDe.motivo}`
|
|
193
292
|
: `reserva: ${id} ${acao === 'renovou' ? 'renovada' : 'pega'} por ${eu.por} em ${eu.maquina}`;
|
|
194
|
-
const commit = gravar(raiz, remoto, ponta, { item: id, reserva }, novas, mensagem);
|
|
293
|
+
const commit = gravar(raiz, remoto, ponta, { item: id, reserva }, novas, mensagem, opcoes.rede);
|
|
195
294
|
if (commit)
|
|
196
295
|
return { acao, item: id, reserva, commit, tentativas: tentativa };
|
|
197
296
|
}
|
|
@@ -203,13 +302,16 @@ function soltarItem(raiz, item, opcoes = {}) {
|
|
|
203
302
|
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
204
303
|
const eu = quemSouEu(raiz, opcoes);
|
|
205
304
|
for (let tentativa = 1; tentativa <= TENTATIVAS; tentativa++) {
|
|
206
|
-
const { ponta, atualizado } = buscar(raiz, remoto);
|
|
305
|
+
const { ponta, atualizado } = buscar(raiz, remoto, opcoes.rede);
|
|
207
306
|
if (!atualizado)
|
|
208
307
|
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS} em ${remoto}; soltar exige rede`);
|
|
209
308
|
const todas = lerDaPonta(raiz, ponta);
|
|
210
309
|
const atual = todas.find((r) => r.item === id);
|
|
211
310
|
if (!atual)
|
|
212
311
|
return { acao: 'nada', item: id, reserva: null, commit: null, tentativas: tentativa };
|
|
312
|
+
if (opcoes.threadEsperada !== undefined && (atual.thread ?? null) !== opcoes.threadEsperada) {
|
|
313
|
+
return { acao: 'nada', item: id, reserva: atual, commit: null, tentativas: tentativa };
|
|
314
|
+
}
|
|
213
315
|
let motivo = '';
|
|
214
316
|
if (!ehMinha(atual, eu)) {
|
|
215
317
|
if (!opcoes.forcar)
|
|
@@ -218,26 +320,166 @@ function soltarItem(raiz, item, opcoes = {}) {
|
|
|
218
320
|
}
|
|
219
321
|
const restantes = todas.filter((r) => r.item !== id);
|
|
220
322
|
const mensagem = `reserva: ${id} solta por ${eu.por} em ${eu.maquina}${motivo}`;
|
|
221
|
-
const commit = gravar(raiz, remoto, ponta, { item: id, reserva: null }, restantes, mensagem);
|
|
323
|
+
const commit = gravar(raiz, remoto, ponta, { item: id, reserva: null }, restantes, mensagem, opcoes.rede);
|
|
222
324
|
if (commit)
|
|
223
325
|
return { acao: 'soltou', item: id, reserva: atual, commit, tentativas: tentativa };
|
|
224
326
|
}
|
|
225
327
|
throw new Error(`roadmap.concorrencia: ${TENTATIVAS} pushes recusados seguidos; tente de novo em instantes`);
|
|
226
328
|
}
|
|
329
|
+
function threadsLocais(raiz) {
|
|
330
|
+
const threads = [];
|
|
331
|
+
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
332
|
+
try {
|
|
333
|
+
threads.push((0, thread_1.lerThread)(raiz, id));
|
|
334
|
+
}
|
|
335
|
+
catch { /* thread ilegivel nao decide reserva */ }
|
|
336
|
+
}
|
|
337
|
+
return threads;
|
|
338
|
+
}
|
|
339
|
+
/** A outra thread aberta do item nesta maquina, a mais recente, que herda a reserva. */
|
|
340
|
+
function sucessoraDoItem(threads, item, fora) {
|
|
341
|
+
const abertas = threads.filter((t) => t.id !== fora && t.status !== 'fechada' && t.roadmap === item)
|
|
342
|
+
.sort((a, b) => String(b.atualizadaEm ?? '').localeCompare(String(a.atualizadaEm ?? '')));
|
|
343
|
+
return abertas[0]?.id ?? null;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* As reservas orfas: desta maquina, apontando para thread que existe aqui e esta fechada. Thread que
|
|
347
|
+
* nao existe aqui nao prova nada (pode ser de outra copia), e reserva de outra maquina so sai com
|
|
348
|
+
* `--forcar`, pelo contrato da I-47.
|
|
349
|
+
*/
|
|
350
|
+
function reservasOrfas(raiz, reservas, opcoes = {}) {
|
|
351
|
+
const eu = quemSouEu(raiz, opcoes);
|
|
352
|
+
const threads = threadsLocais(raiz);
|
|
353
|
+
const status = new Map(threads.map((t) => [t.id, t.status]));
|
|
354
|
+
return reservas.filter((r) => r.thread && ehMinha(r, eu) && status.get(r.thread) === 'fechada')
|
|
355
|
+
.map((reserva) => ({ reserva, sucessora: sucessoraDoItem(threads, reserva.item, reserva.thread) }));
|
|
356
|
+
}
|
|
357
|
+
/** Solta a reserva orfa, ou a reaponta para a sucessora, e registra no ledger da thread fechada. */
|
|
358
|
+
function soltarOrfa(raiz, orfa, origem, opcoes) {
|
|
359
|
+
const thread = orfa.reserva.thread, item = orfa.reserva.item;
|
|
360
|
+
const comparando = { ...opcoes, threadEsperada: thread };
|
|
361
|
+
const r = orfa.sucessora
|
|
362
|
+
? pegarItem(raiz, item, { ...comparando, thread: orfa.sucessora })
|
|
363
|
+
: soltarItem(raiz, item, comparando);
|
|
364
|
+
// A reserva mudou de dono no meio (ou ja tinha saido): nada foi gravado e nada vai ao ledger como soltura.
|
|
365
|
+
if (r.acao === 'nada') {
|
|
366
|
+
return { item, thread, acao: 'intocada', para: null, commit: null,
|
|
367
|
+
detalhe: `${item} mudou de dono entre a leitura e a gravacao; ficou como estava` };
|
|
368
|
+
}
|
|
369
|
+
const acao = orfa.sucessora ? 'reapontada' : 'solta';
|
|
370
|
+
const detalhe = orfa.sucessora ? `${item} passou para a thread ${orfa.sucessora}, aberta no mesmo item` : `${item} devolvido`;
|
|
371
|
+
// Sugestao 3 do CHECK 2: a soltura ja foi ao remoto; erro so no registro nao a transforma em pendencia.
|
|
372
|
+
try {
|
|
373
|
+
(0, ledger_1.registrarSeExiste)((0, thread_1.dirThread)(raiz, thread), thread, ledger_1.TIPOS_DE_EVENTO.reservaLiberada, { item, acao, para: orfa.sucessora, commit: r.commit, origem, detalhe });
|
|
374
|
+
}
|
|
375
|
+
catch { /* a reserva saiu; o resultado diz o que aconteceu */ }
|
|
376
|
+
return { item, thread, acao, para: orfa.sucessora, commit: r.commit, detalhe };
|
|
377
|
+
}
|
|
378
|
+
/** A pendencia vai ao ledger da thread fechada, com a correcao; o registro tambem e de melhor esforco. */
|
|
379
|
+
function pendenteDaThread(raiz, thread, item, detalhe) {
|
|
380
|
+
try {
|
|
381
|
+
(0, ledger_1.registrarSeExiste)((0, thread_1.dirThread)(raiz, thread), thread, ledger_1.TIPOS_DE_EVENTO.reservaPendente, { item, detalhe, correcao: 'ork roadmap reservas --soltar-orfas' });
|
|
382
|
+
}
|
|
383
|
+
catch { /* sem ledger gravavel, o resultado ainda diz a pendencia */ }
|
|
384
|
+
return { item: item ?? '', thread, acao: 'pendente', para: null, commit: null, detalhe };
|
|
385
|
+
}
|
|
386
|
+
const primeiraLinha = (e) => String(e?.message ?? e).split('\n')[0].slice(0, 300);
|
|
387
|
+
/**
|
|
388
|
+
* `ork thread close` e `ork master`: a thread que fecha solta a reserva do item dela, ou a passa para
|
|
389
|
+
* outra thread aberta do mesmo item nesta maquina. De melhor esforco: sem rede ou com a reserva em
|
|
390
|
+
* outra maquina, o fechamento segue e o ledger guarda a pendencia com a correcao. So vai a rede
|
|
391
|
+
* quando ha o que soltar: a thread tem item ou a ultima copia lida das reservas aponta para ela.
|
|
392
|
+
*/
|
|
393
|
+
function soltarReservaDaThread(raiz, threadId, opcoes = {}) {
|
|
394
|
+
let item = null;
|
|
395
|
+
try {
|
|
396
|
+
item = (0, thread_1.lerThread)(raiz, threadId).roadmap ?? null;
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
const remoto = opcoes.remoto ?? exports.REMOTO_PADRAO;
|
|
402
|
+
const naCopia = reservasLocais(raiz, remoto).find((r) => r.thread === threadId);
|
|
403
|
+
if (!item && !naCopia)
|
|
404
|
+
return [];
|
|
405
|
+
// Sugestao 1 do CHECK 2: quem passa `rede` troca o que passou, sem perder o resto do fechamento.
|
|
406
|
+
const base = redeDoFechamento(raiz);
|
|
407
|
+
const rede = { timeoutMs: opcoes.rede?.timeoutMs ?? base.timeoutMs, env: { ...base.env, ...opcoes.rede?.env } };
|
|
408
|
+
const comRede = { ...opcoes, rede };
|
|
409
|
+
const oItem = item ?? naCopia?.item ?? null;
|
|
410
|
+
let painel;
|
|
411
|
+
try {
|
|
412
|
+
painel = listarReservas(raiz, { remoto, rede });
|
|
413
|
+
}
|
|
414
|
+
catch (e) {
|
|
415
|
+
return [pendenteDaThread(raiz, threadId, oItem, primeiraLinha(e))];
|
|
416
|
+
}
|
|
417
|
+
if (!painel.atualizado) {
|
|
418
|
+
return [pendenteDaThread(raiz, threadId, oItem, `sem leitura de ${exports.BRANCH_DE_RESERVAS} em ${remoto}; a reserva fica para depois`)];
|
|
419
|
+
}
|
|
420
|
+
const dela = painel.reservas.filter((r) => r.thread === threadId);
|
|
421
|
+
let orfas;
|
|
422
|
+
// Sugestao 2 do CHECK 2: quem sou eu e as threads locais tambem podem falhar; vira pendencia, nao silencio.
|
|
423
|
+
try {
|
|
424
|
+
orfas = reservasOrfas(raiz, dela, opcoes);
|
|
425
|
+
}
|
|
426
|
+
catch (e) {
|
|
427
|
+
return dela.map((r) => pendenteDaThread(raiz, threadId, r.item, primeiraLinha(e)));
|
|
428
|
+
}
|
|
429
|
+
const saida = [];
|
|
430
|
+
// Sugestao 1 do CHECK 1: toda reserva da thread, nao so a primeira; a de outra maquina fica pendente.
|
|
431
|
+
for (const r of dela.filter((x) => !orfas.some((o) => o.reserva.item === x.item))) {
|
|
432
|
+
saida.push(pendenteDaThread(raiz, threadId, r.item, `${descrever(r)}; so quem pegou solta, ou --forcar --motivo`));
|
|
433
|
+
}
|
|
434
|
+
for (const orfa of orfas) {
|
|
435
|
+
try {
|
|
436
|
+
saida.push(soltarOrfa(raiz, orfa, 'fechamento', comRede));
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
saida.push(pendenteDaThread(raiz, threadId, orfa.reserva.item, primeiraLinha(e)));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return saida;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* `ork roadmap reservas --soltar-orfas`: a mesma regra do fechamento, para toda reserva orfa desta
|
|
446
|
+
* maquina. Uma orfa que falha vira pendencia no ledger dela e nao para as outras (sugestao 2 do CHECK 1).
|
|
447
|
+
*/
|
|
448
|
+
function soltarReservasOrfas(raiz, opcoes = {}) {
|
|
449
|
+
const painel = listarReservas(raiz, { remoto: opcoes.remoto, rede: opcoes.rede });
|
|
450
|
+
if (!painel.atualizado)
|
|
451
|
+
throw new Error(`roadmap.sem-remoto: nao consegui ler ${exports.BRANCH_DE_RESERVAS}; soltar exige rede`);
|
|
452
|
+
return reservasOrfas(raiz, painel.reservas, opcoes).map((orfa) => {
|
|
453
|
+
try {
|
|
454
|
+
return soltarOrfa(raiz, orfa, 'orfas', opcoes);
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
return pendenteDaThread(raiz, orfa.reserva.thread, orfa.reserva.item, primeiraLinha(e));
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
227
461
|
/** O texto de `ork roadmap reservas` para o terminal, no fuso do dono. */
|
|
228
|
-
function textoDasReservas(p) {
|
|
462
|
+
function textoDasReservas(p, orfas = []) {
|
|
229
463
|
const linhas = [];
|
|
230
464
|
if (!p.atualizado)
|
|
231
465
|
linhas.push('AVISO: sem acesso ao remoto; esta e a ultima copia lida nesta maquina.', '');
|
|
466
|
+
const feats = p.feats ?? [];
|
|
467
|
+
const numeros = feats.length === 0 ? [] : ['', `Números de FEAT reservados (${feats.length}), os últimos: ` +
|
|
468
|
+
feats.slice(-5).map((f) => `${f.feat} (${f.thread ?? f.maquina})`).join(', ') + '. O próximo sai de: ork roadmap feat'];
|
|
232
469
|
if (p.reservas.length === 0) {
|
|
233
|
-
linhas.push('Nenhum item do roadmap reservado.');
|
|
470
|
+
linhas.push('Nenhum item do roadmap reservado.', ...numeros);
|
|
234
471
|
return linhas.join('\n');
|
|
235
472
|
}
|
|
236
473
|
linhas.push('ITEM COM QUEM MAQUINA THREAD DESDE');
|
|
474
|
+
const orfaDoItem = new Map(orfas.map((o) => [o.reserva.item, o]));
|
|
237
475
|
for (const r of p.reservas) {
|
|
476
|
+
const orfa = orfaDoItem.get(r.item);
|
|
477
|
+
const marca = orfa ? ` ÓRFÃ: thread fechada${orfa.sucessora ? `, passa para ${orfa.sucessora}` : ''}` : '';
|
|
238
478
|
linhas.push(`${r.item.padEnd(8)} ${r.por.slice(0, 19).padEnd(19)} ${r.maquina.slice(0, 15).padEnd(15)} ` +
|
|
239
|
-
`${(r.thread ?? '-').slice(0, 18).padEnd(18)} ${(0, horario_1.formatarDataHora)(r.desdeEm)}` + (r.nota ? ` ${r.nota}` : ''));
|
|
479
|
+
`${(r.thread ?? '-').slice(0, 18).padEnd(18)} ${(0, horario_1.formatarDataHora)(r.desdeEm)}` + (r.nota ? ` ${r.nota}` : '') + marca);
|
|
240
480
|
}
|
|
241
|
-
|
|
481
|
+
if (orfas.length > 0)
|
|
482
|
+
linhas.push('', `${orfas.length} reserva(s) órfã(s) desta máquina. Para soltar: ork roadmap reservas --soltar-orfas`);
|
|
483
|
+
linhas.push(...numeros, '', (0, horario_1.legendaDoFuso)());
|
|
242
484
|
return linhas.join('\n');
|
|
243
485
|
}
|