@orkastery/cli 0.4.3 → 0.5.1
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/README.md +11 -2
- 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/onboarding.md +3 -2
- 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 +45 -2
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +93 -21
- 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 +109 -21
- 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 +24 -7
- package/dist/auditrun.js +1 -1
- 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/claim-lint.js +3 -1
- 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 +135 -25
- package/dist/doctor.js +242 -32
- package/dist/entrega-pr.js +116 -5
- package/dist/experiencia-instalacao.js +256 -0
- package/dist/experiencia.js +72 -0
- package/dist/fabrica-estado.js +63 -12
- package/dist/fechamento.js +105 -0
- package/dist/fix.js +3 -3
- 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 +57 -13
- package/dist/hitl-texto-livre.js +150 -0
- package/dist/hosts.js +92 -3
- package/dist/index.js +637 -45
- package/dist/indice-vetorial.js +317 -0
- package/dist/init.js +24 -5
- package/dist/integracoes-locais.js +3 -1
- package/dist/intelligence-benchmark-contract.js +570 -0
- package/dist/intelligence-graph-cli.js +584 -0
- package/dist/intelligence-graph-contract.js +636 -0
- package/dist/intelligence-graph-extract-md.js +487 -0
- package/dist/intelligence-graph-extract-ts.js +980 -0
- package/dist/intelligence-graph-extract.js +546 -0
- package/dist/intelligence-graph-index.js +693 -0
- package/dist/intelligence-graph-parsers.js +327 -0
- package/dist/intelligence-graph-query.js +344 -0
- package/dist/intelligence-graph-repo.js +197 -0
- package/dist/leases.js +100 -0
- package/dist/ledger.js +23 -0
- package/dist/licoes.js +5 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-cli.js +10 -1
- package/dist/maestro-contract.js +5 -1
- package/dist/maestro-discovery.js +16 -2
- package/dist/maestro-runtime.js +3 -0
- package/dist/maestro-snapshot.js +6 -2
- package/dist/manifest.js +166 -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-artifacts.js +2 -0
- package/dist/mcp-experiencia.js +164 -0
- package/dist/mcp-git.js +64 -12
- package/dist/mcp-grafo-worker.js +91 -0
- package/dist/mcp-grafo.js +233 -0
- package/dist/mcp-install.js +5 -2
- package/dist/mcp-maestro.js +3 -2
- package/dist/mcp-server.js +93 -5
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/memoria.js +147 -1
- package/dist/network-roadmap.js +907 -0
- package/dist/ocupacao.js +43 -3
- package/dist/onboarding.js +122 -11
- package/dist/orkmind.js +165 -9
- package/dist/orquestracao.js +2 -1
- package/dist/parado-no-condutor.js +933 -0
- package/dist/phase.js +166 -13
- package/dist/policies.js +29 -2
- package/dist/projeto-alvo.js +551 -0
- package/dist/prompts.js +1 -1
- package/dist/prova-ativacao.js +345 -0
- package/dist/pulse-consentimento.js +4 -2
- package/dist/pulse-delivery.js +32 -6
- package/dist/pulse-resposta.js +674 -49
- package/dist/pulse.js +74 -3
- package/dist/retry.js +51 -8
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +348 -0
- package/dist/runtime-context.js +5 -1
- package/dist/sessoes-inventario.js +7 -1
- package/dist/sessoes.js +4 -1
- package/dist/ship.js +25 -1
- package/dist/thread-close.js +3 -0
- package/dist/thread.js +21 -2
- package/dist/util.js +71 -4
- package/dist/verify.js +6 -5
- package/dist/worktree.js +29 -6
- package/eval/casos/onboarding.json +1 -1
- 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 +10 -2
- 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/skills/phases/ship-release/SKILL.md +6 -3
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TETO_DO_FECHO = exports.GRUPO_DO_CICLO = exports.GRUPOS_DO_ROADMAP = exports.CONTRATO_STATUS_DO_ROADMAP = void 0;
|
|
4
|
+
exports.esperaDoDono = esperaDoDono;
|
|
5
|
+
exports.fatosLocais = fatosLocais;
|
|
6
|
+
exports.batidaDaFabrica = batidaDaFabrica;
|
|
7
|
+
exports.montarStatusDoRoadmap = montarStatusDoRoadmap;
|
|
8
|
+
exports.montarStatusDeFatos = montarStatusDeFatos;
|
|
9
|
+
exports.textoDoStatusDoRoadmap = textoDoStatusDoRoadmap;
|
|
10
|
+
/**
|
|
11
|
+
* RM-048 (item 7, D7): o status report UNICO do roadmap, no formato aprovado pelo dono.
|
|
12
|
+
*
|
|
13
|
+
* Ate aqui cada canal escrevia o proprio relatorio de roadmap, e o dono lia uma coisa no Telegram
|
|
14
|
+
* e outra no terminal. O formato que ele aprovou (27/09 e 28/09):
|
|
15
|
+
*
|
|
16
|
+
* titulo "Roadmap do <Projeto> (DD/MM, HH:MM)", no fuso do dono;
|
|
17
|
+
* grupos com icones, nesta ordem: Concluidos, Disponiveis com algo em aberto, Entregue hoje,
|
|
18
|
+
* Piloto, Em desenvolvimento, Refinamento, Proposto, Descontinuado;
|
|
19
|
+
* um item por linha, com #HITL no que depende do dono;
|
|
20
|
+
* fecho com "O que precisa de voce" e "O que eu faco em seguida".
|
|
21
|
+
*
|
|
22
|
+
* Tudo sai de FATO: o frontmatter do item (`ork docs`), o ledger das threads ligadas a ele e o gate
|
|
23
|
+
* que o nucleo confere sem escrever (`prepararPedidoGate`). E leitura pura: nao abre pedido, nao
|
|
24
|
+
* grava ledger, nao toca a rede. Os canais chamam `ork roadmap status` e transportam o texto.
|
|
25
|
+
*
|
|
26
|
+
* RM-054: o montador recebe FATOS de thread (`FatoDeThread`), nao so as threads deste disco. O
|
|
27
|
+
* relatorio local continua com os fatos locais; o panorama da rede soma os retratos das outras
|
|
28
|
+
* maquinas, e so ai o fecho diz entre parenteses em que maquina a thread anda.
|
|
29
|
+
*/
|
|
30
|
+
const docs_1 = require("./docs");
|
|
31
|
+
const ledger_1 = require("./ledger");
|
|
32
|
+
const thread_1 = require("./thread");
|
|
33
|
+
const hitl_contract_1 = require("./hitl-contract");
|
|
34
|
+
const hitl_gates_1 = require("./hitl-gates");
|
|
35
|
+
const hitl_classificacao_1 = require("./hitl-classificacao");
|
|
36
|
+
const horario_1 = require("./horario");
|
|
37
|
+
const projeto_alvo_1 = require("./projeto-alvo");
|
|
38
|
+
const fabrica_estado_1 = require("./fabrica-estado");
|
|
39
|
+
const hitl_resumo_1 = require("./hitl-resumo");
|
|
40
|
+
const manifest_1 = require("./manifest");
|
|
41
|
+
const maquina_1 = require("./maquina");
|
|
42
|
+
const parado_no_condutor_1 = require("./parado-no-condutor");
|
|
43
|
+
const util_1 = require("./util");
|
|
44
|
+
exports.CONTRATO_STATUS_DO_ROADMAP = 'ork.roadmap-status/v1';
|
|
45
|
+
/** Os grupos, na ordem e com os icones que o dono aprovou. */
|
|
46
|
+
exports.GRUPOS_DO_ROADMAP = Object.freeze([
|
|
47
|
+
{ id: 'concluidos', icone: '✅', titulo: 'Concluídos' },
|
|
48
|
+
{ id: 'disponiveis', icone: '🟢', titulo: 'Disponíveis com algo em aberto' },
|
|
49
|
+
{ id: 'hoje', icone: '🚀', titulo: 'Entregue hoje' },
|
|
50
|
+
{ id: 'piloto', icone: '🧪', titulo: 'Piloto' },
|
|
51
|
+
{ id: 'desenvolvimento', icone: '🔨', titulo: 'Em desenvolvimento' },
|
|
52
|
+
{ id: 'refinamento', icone: '🔍', titulo: 'Refinamento' },
|
|
53
|
+
{ id: 'proposto', icone: '🆕', titulo: 'Proposto' },
|
|
54
|
+
{ id: 'descontinuado', icone: '⛔', titulo: 'Descontinuado' },
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* D7: o grupo de cada ciclo do padrao de roadmap (`docs/padroes/roadmap-de-produto.md`). Mapa
|
|
58
|
+
* TOTAL sobre os doze ciclos; ciclo fora dele cai em "Proposto" e o texto o mostra como e.
|
|
59
|
+
*/
|
|
60
|
+
exports.GRUPO_DO_CICLO = Object.freeze({
|
|
61
|
+
'Concluído': 'concluidos',
|
|
62
|
+
'Disponível': 'disponiveis',
|
|
63
|
+
'Piloto': 'piloto',
|
|
64
|
+
'Em desenvolvimento': 'desenvolvimento', 'Em validação': 'desenvolvimento', 'Bloqueado': 'desenvolvimento',
|
|
65
|
+
'Refinamento': 'refinamento', 'Pronto para desenvolvimento': 'refinamento',
|
|
66
|
+
'Discovery': 'proposto', 'Backlog': 'proposto',
|
|
67
|
+
'Cancelado': 'descontinuado', 'Descontinuado': 'descontinuado',
|
|
68
|
+
});
|
|
69
|
+
/** Quantas linhas cada parte do fecho mostra antes de contar o resto. */
|
|
70
|
+
exports.TETO_DO_FECHO = 5;
|
|
71
|
+
/** A letra recomendada de uma pergunta v2, para o fecho dizer exatamente o que digitar. */
|
|
72
|
+
const recomendadaDe = (p) => (0, hitl_contract_1.ehV2)(p) && p.classe === 'pergunta' ? p.alternativas.find(a => a.recomendada)?.letra : undefined;
|
|
73
|
+
const mapa = (v) => (!!v && typeof v === 'object' && !Array.isArray(v) ? v : {});
|
|
74
|
+
const texto = (v) => (v === undefined || v === null ? '' : String(v));
|
|
75
|
+
/** O nome curto do item: o titulo ate o primeiro ":" ou 60 caracteres. */
|
|
76
|
+
function nomeCurto(titulo) {
|
|
77
|
+
const antes = titulo.split(/:\s/)[0].trim();
|
|
78
|
+
return antes.length > 60 ? `${antes.slice(0, 57)}...` : antes;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* O que espera o dono nesta thread, lido sem escrever: um gate que ainda espera (pausa do modo ou
|
|
82
|
+
* escalacao tipada do dono) ou uma pergunta de sessao aberta. Impedimento tecnico nao conta (D6).
|
|
83
|
+
*/
|
|
84
|
+
function esperaDoDono(raiz, t, quando) {
|
|
85
|
+
const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id));
|
|
86
|
+
const escalacoes = [...new Set(eventos.filter(e => e.tipo === 'gate_blocked' && e.fase === t.faseAtual &&
|
|
87
|
+
hitl_gates_1.MOTIVOS_DE_ESCALACAO_HUMANA.includes(String(e.motivo)) && (0, hitl_classificacao_1.quemDecide)(String(e.motivo)) === 'dono')
|
|
88
|
+
.map(e => String(e.motivo)))];
|
|
89
|
+
// RM-037 (fatia 4): o fim de turno sem pergunta e do condutor, nunca "O que precisa de voce".
|
|
90
|
+
const doCondutor = (0, parado_no_condutor_1.esperaDoCondutor)(t, eventos, quando) !== null;
|
|
91
|
+
for (const motivo of [...(doCondutor ? [] : ['human.pending']), ...escalacoes]) {
|
|
92
|
+
try {
|
|
93
|
+
const preparado = (0, hitl_gates_1.prepararPedidoGate)(raiz, t.id, motivo, quando);
|
|
94
|
+
const pedido = 'aberto' in preparado ? preparado.aberto : preparado.novo;
|
|
95
|
+
const codigo = 'aberto' in preparado ? pedido.codigo : undefined;
|
|
96
|
+
const recomendada = recomendadaDe(pedido);
|
|
97
|
+
return { thread: t.id, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido), ...(typeof codigo === 'string' ? { codigo } : {}),
|
|
98
|
+
...(recomendada ? { recomendada } : {}) };
|
|
99
|
+
}
|
|
100
|
+
catch { /* este motivo nao espera o dono agora */ }
|
|
101
|
+
}
|
|
102
|
+
const contexto = (0, hitl_gates_1.contextoHitlDosEventos)(t, eventos);
|
|
103
|
+
const respondidos = new Set(eventos.filter(e => ['human_gate', 'session_answered'].includes(e.tipo)).map(e => String(e.pedidoId)));
|
|
104
|
+
for (const e of [...eventos].reverse()) {
|
|
105
|
+
if (e.tipo !== 'hitl_requested' || e.contexto !== contexto)
|
|
106
|
+
continue;
|
|
107
|
+
const pedido = e.pedido;
|
|
108
|
+
try {
|
|
109
|
+
if ((0, hitl_contract_1.alvoDoPedido)(pedido)?.tipo !== 'session' || respondidos.has(pedido.id) || (0, hitl_contract_1.estadoDoPedido)(pedido, quando) !== 'aberto')
|
|
110
|
+
continue;
|
|
111
|
+
const codigo = (0, hitl_contract_1.ehV2)(pedido) && pedido.classe === 'pergunta' ? pedido.codigo : undefined;
|
|
112
|
+
const recomendada = recomendadaDe(pedido);
|
|
113
|
+
return { thread: t.id, pergunta: (0, hitl_contract_1.textoDoPedido)(pedido), ...(codigo ? { codigo } : {}), ...(recomendada ? { recomendada } : {}) };
|
|
114
|
+
}
|
|
115
|
+
catch { /* pedido invalido nao e espera */ }
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Os fatos das threads deste disco: `ship_done` do dia e espera do dono lidos do ledger local.
|
|
121
|
+
* `fuso`: o do projeto consultado, na visao da rede; sem ele, o do dono deste processo.
|
|
122
|
+
*/
|
|
123
|
+
function fatosLocais(raiz, quando, fuso) {
|
|
124
|
+
const hoje = (0, horario_1.dataLocal)(quando, fuso);
|
|
125
|
+
const fatos = [];
|
|
126
|
+
// RM-037 (fatia 4): o estado das entregas sai uma vez, sob demanda, do git local e do retrato de PRs do pulse.
|
|
127
|
+
let entregas;
|
|
128
|
+
const lerEntregas = () => {
|
|
129
|
+
if (entregas !== undefined)
|
|
130
|
+
return entregas;
|
|
131
|
+
try {
|
|
132
|
+
const retrato = (0, parado_no_condutor_1.lerRetratoDePrs)(raiz);
|
|
133
|
+
entregas = (0, parado_no_condutor_1.entregasDoProjeto)((0, manifest_1.exigirManifesto)(raiz), { quando, lerPrs: () => (retrato ? { ok: true, retrato } : null) });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
entregas = null;
|
|
137
|
+
}
|
|
138
|
+
return entregas;
|
|
139
|
+
};
|
|
140
|
+
// A leitura que falhou e dita, e nao vira o "sigo" de sempre.
|
|
141
|
+
const entrega = (id) => {
|
|
142
|
+
const lidas = lerEntregas();
|
|
143
|
+
return lidas ? entregaNoStatus(lidas.estados.find(e => e.thread === id)) : { estado: 'estado da entrega não lido' };
|
|
144
|
+
};
|
|
145
|
+
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
146
|
+
let t;
|
|
147
|
+
try {
|
|
148
|
+
t = (0, thread_1.lerThread)(raiz, id);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
continue; /* thread ilegivel fica de fora */
|
|
152
|
+
}
|
|
153
|
+
fatos.push({
|
|
154
|
+
id: t.id, roadmap: t.roadmap ?? null, aberta: t.status !== 'fechada', fase: t.faseAtual,
|
|
155
|
+
entregueHoje: () => (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, t.id)).some(e => e.tipo === 'ship_done' && (0, horario_1.dataLocal)(e.ts, fuso) === hoje),
|
|
156
|
+
espera: () => esperaDoDono(raiz, t, quando),
|
|
157
|
+
entrega: () => (t.status === 'fechada' ? undefined : entrega(t.id)),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return fatos;
|
|
161
|
+
}
|
|
162
|
+
/** O estado da entrega como o status o diz: so quando ha o que dizer. */
|
|
163
|
+
function entregaNoStatus(e) {
|
|
164
|
+
if (!e || (!e.resumo && !e.parado))
|
|
165
|
+
return undefined;
|
|
166
|
+
return { estado: e.resumo, ...(e.conduzidaAgora ? { conduzida: true } : {}),
|
|
167
|
+
...(e.parado ? { parado: { desdeEm: e.parado.desdeEm, proximoPasso: e.parado.proximoPasso,
|
|
168
|
+
...(e.parado.prLidoEm ? { prLidoEm: e.parado.prLidoEm } : {}) } } : {}),
|
|
169
|
+
...(e.prLidoEm ? { prLidoEm: e.prLidoEm } : {}) };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* RM-037 (fatia 4, D8): as outras maquinas da fabrica sem batida alem do limiar da rede, lidas da copia
|
|
173
|
+
* local de `ork/fabrica-estado`, sem fetch. Sem fabrica compartilhada, nada; sem copia local, "nao lido".
|
|
174
|
+
*/
|
|
175
|
+
function batidaDaFabrica(carregado, quando) {
|
|
176
|
+
if (!(0, maquina_1.fabricaCompartilhada)(carregado.manifesto))
|
|
177
|
+
return undefined;
|
|
178
|
+
// Sem rede escondida: num clone parcial, o `git show` dos retratos buscaria o objeto no remoto. O git anterior
|
|
179
|
+
// a 2.45 ignora o GIT_NO_LAZY_FETCH, entao o clone parcial nem e lido aqui (N4 da seguranca, rodada 2).
|
|
180
|
+
// N4 da seguranca (rodada 3): o git novo registra o clone parcial no `remote.<r>.promisor`, o velho no `extensions`.
|
|
181
|
+
if ((0, util_1.exec)('git', ['config', '--get-regexp', '^(extensions\\.partialclone|remote\\..+\\.promisor)$'], carregado.raiz).stdout.trim()) {
|
|
182
|
+
return { lido: false, motivo: `clone parcial: a cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA} não é lida sem rede`, semBatida: [] };
|
|
183
|
+
}
|
|
184
|
+
const antes = process.env.GIT_NO_LAZY_FETCH;
|
|
185
|
+
process.env.GIT_NO_LAZY_FETCH = '1';
|
|
186
|
+
let painel;
|
|
187
|
+
try {
|
|
188
|
+
painel = (0, fabrica_estado_1.lerFabrica)(carregado.raiz, { remoto: carregado.manifesto.fabrica.remoto, semRemoto: true });
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return { lido: false, motivo: `a cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA} está ilegível`, semBatida: [] };
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
if (antes === undefined)
|
|
195
|
+
delete process.env.GIT_NO_LAZY_FETCH;
|
|
196
|
+
else
|
|
197
|
+
process.env.GIT_NO_LAZY_FETCH = antes;
|
|
198
|
+
}
|
|
199
|
+
if (!painel.ponta)
|
|
200
|
+
return { lido: false, semBatida: [] };
|
|
201
|
+
const eu = (0, maquina_1.nomeDaMaquina)(), agora = Date.parse(quando);
|
|
202
|
+
// A copia so diz quem esta sem batida quando esta maquina publicou nela ha pouco: e o fetch daqui que a atualiza.
|
|
203
|
+
const { publicadoEm: minha } = painel.maquinas.find(m => m.maquina === eu) ?? { publicadoEm: null };
|
|
204
|
+
if (!minha || !(agora - Date.parse(minha) <= fabrica_estado_1.LIMIAR_SEM_BATIDA_MS)) {
|
|
205
|
+
return { lido: true, copiaVelha: { ultimoDestaMaquina: minha }, semBatida: [] };
|
|
206
|
+
}
|
|
207
|
+
const semBatida = painel.maquinas.filter(m => m.maquina !== eu).map(m => {
|
|
208
|
+
const { publicadoEm: publicado } = m;
|
|
209
|
+
return { maquina: m.maquina, publicadoEm: publicado, idadeMs: agora - Date.parse(publicado) };
|
|
210
|
+
}).filter(m => Number.isFinite(m.idadeMs) && m.idadeMs > fabrica_estado_1.LIMIAR_SEM_BATIDA_MS)
|
|
211
|
+
.map(m => ({ maquina: m.maquina, publicadoEm: m.publicadoEm, idadeMin: Math.floor(m.idadeMs / 60000) }));
|
|
212
|
+
return { lido: true, semBatida };
|
|
213
|
+
}
|
|
214
|
+
/** Monta o relatorio. Leitura pura: nada e escrito, nenhum pedido e aberto. */
|
|
215
|
+
function montarStatusDoRoadmap(raiz, opcoes = {}) {
|
|
216
|
+
const quando = opcoes.quando ?? new Date().toISOString();
|
|
217
|
+
let fabrica;
|
|
218
|
+
try {
|
|
219
|
+
fabrica = batidaDaFabrica((0, manifest_1.exigirManifesto)(raiz), quando);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
fabrica = undefined;
|
|
223
|
+
}
|
|
224
|
+
return montarStatusDeFatos((0, docs_1.carregarDocs)(raiz).docs, fatosLocais(raiz, quando), { quando, projeto: opcoes.projeto, consulta: opcoes.consulta,
|
|
225
|
+
...(fabrica ? { fabrica } : {}) });
|
|
226
|
+
}
|
|
227
|
+
/** O relatorio a partir das paginas do roadmap e dos fatos das threads, de onde quer que venham. */
|
|
228
|
+
function montarStatusDeFatos(docs, fatos, opcoes) {
|
|
229
|
+
const quando = opcoes.quando;
|
|
230
|
+
const porId = new Map(fatos.map(f => [f.id, f]));
|
|
231
|
+
const porItem = new Map();
|
|
232
|
+
for (const f of fatos)
|
|
233
|
+
if (f.roadmap)
|
|
234
|
+
porItem.set(f.roadmap, new Set([...(porItem.get(f.roadmap) ?? []), f.id]));
|
|
235
|
+
const itens = [];
|
|
236
|
+
for (const d of docs.filter(x => x.tipo === 'roadmap')) {
|
|
237
|
+
const estado = mapa(d.dados.estado), sdlc = mapa(d.dados.sdlc);
|
|
238
|
+
const ciclo = texto(estado.ciclo);
|
|
239
|
+
const ligadas = [...new Set([...(porItem.get(d.id) ?? []), ...(texto(sdlc.thread) ? [texto(sdlc.thread)] : [])])]
|
|
240
|
+
.filter(id => porId.has(id)).sort();
|
|
241
|
+
const entregueHoje = ligadas.some(id => porId.get(id).entregueHoje());
|
|
242
|
+
const abertas = ligadas.map(id => porId.get(id)).filter(f => f.aberta);
|
|
243
|
+
let hitl;
|
|
244
|
+
for (const f of abertas) {
|
|
245
|
+
hitl = f.espera();
|
|
246
|
+
if (hitl)
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
// RM-037 (fatia 4): a thread que diz o estado real vem primeiro: a parada no condutor, depois a entrega em curso.
|
|
250
|
+
const entregas = new Map(abertas.map(f => [f.id, f.entrega?.()]));
|
|
251
|
+
const conduz = abertas.find(f => entregas.get(f.id)?.parado) ?? abertas.find(f => entregas.get(f.id)?.estado) ?? abertas[0];
|
|
252
|
+
const entrega = conduz ? entregas.get(conduz.id) : undefined;
|
|
253
|
+
const emAberto = exports.GRUPO_DO_CICLO[ciclo] === 'disponiveis' ? [
|
|
254
|
+
...(texto(estado.exposicao) && texto(estado.exposicao) !== 'Geral' ? [`exposição ${texto(estado.exposicao)}`] : []),
|
|
255
|
+
...(texto(estado.habilitacao) && texto(estado.habilitacao) !== 'Concluída' ? [`habilitação ${texto(estado.habilitacao)}`] : []),
|
|
256
|
+
] : [];
|
|
257
|
+
itens.push({
|
|
258
|
+
id: d.id, titulo: nomeCurto(texto(d.dados.titulo) || d.id), ciclo,
|
|
259
|
+
grupo: entregueHoje ? 'hoje' : exports.GRUPO_DO_CICLO[ciclo] ?? 'proposto',
|
|
260
|
+
emAberto, threads: ligadas,
|
|
261
|
+
...(conduz ? { conduzindo: { thread: conduz.id, fase: conduz.fase, ...(conduz.maquina ? { maquina: conduz.maquina } : {}),
|
|
262
|
+
...(entrega ? { entrega } : {}) } } : {}),
|
|
263
|
+
...(hitl ? { hitl } : {}),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const grupos = exports.GRUPOS_DO_ROADMAP.map(g => ({ ...g, itens: itens.filter(i => i.grupo === g.id) }));
|
|
267
|
+
return {
|
|
268
|
+
contrato: exports.CONTRATO_STATUS_DO_ROADMAP, consultadoEm: quando,
|
|
269
|
+
projeto: opcoes.projeto ?? 'projeto', grupos,
|
|
270
|
+
precisaDeVoce: itens.filter(i => i.hitl).map(i => ({ item: i.id, espera: i.hitl })),
|
|
271
|
+
emSeguida: itens.filter(i => i.conduzindo && !i.hitl).map(i => ({ item: i.id, ...i.conduzindo })),
|
|
272
|
+
...(opcoes.consulta ? { consulta: opcoes.consulta } : {}),
|
|
273
|
+
...(opcoes.fabrica ? { fabrica: opcoes.fabrica } : {}),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** "orkastery" vira "Orkastery": o titulo diz o nome do projeto como gente escreve. */
|
|
277
|
+
const capitalizar = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
278
|
+
/** A maquina entre parenteses, so quando o fato a traz (visao da rede, D7 da RM-054). */
|
|
279
|
+
const naMaquina = (maquina) => maquina ? ` (${maquina})` : '';
|
|
280
|
+
/**
|
|
281
|
+
* RM-037 (fatia 4): a linha do que vem a seguir diz o estado real. Parada no condutor sai com o desde e o
|
|
282
|
+
* proximo passo; entrega em curso, com o estado do PR e a hora em que ele foi lido; sem nada disso, a fase.
|
|
283
|
+
*/
|
|
284
|
+
function linhaDoQueVem(x, agora, fuso) {
|
|
285
|
+
const e = x.entrega;
|
|
286
|
+
if (e?.parado) {
|
|
287
|
+
const { prLidoEm: lido } = e.parado;
|
|
288
|
+
return `• ${x.item}: ${(0, hitl_resumo_1.linhaDoParadoNoCondutor)({ thread: x.thread, ...e.parado }, { agora, fuso })}${naMaquina(x.maquina)}` +
|
|
289
|
+
`${lido ? ` (PR lido às ${(0, horario_1.formatarHora)(lido, { agora, fuso })})` : ''}.`;
|
|
290
|
+
}
|
|
291
|
+
if (e?.estado) {
|
|
292
|
+
const { prLidoEm: lido } = e;
|
|
293
|
+
const daLeitura = lido ? ` (PR lido às ${(0, horario_1.formatarHora)(lido, { agora, fuso })})` : '';
|
|
294
|
+
return e.conduzida ? `• ${x.item}: sigo ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}; ${e.estado}${daLeitura}.`
|
|
295
|
+
: `• ${x.item}: ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}, ${e.estado}${daLeitura}.`;
|
|
296
|
+
}
|
|
297
|
+
return `• ${x.item}: sigo ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}.`;
|
|
298
|
+
}
|
|
299
|
+
/** RM-037 (fatia 4, D8): uma linha so para a fabrica, e so quando ha o que avisar. */
|
|
300
|
+
function linhaDaFabrica(f, agora, fuso) {
|
|
301
|
+
if (!f)
|
|
302
|
+
return [];
|
|
303
|
+
if (!f.lido)
|
|
304
|
+
return [`Fábrica: não lido (${f.motivo ?? `esta máquina não tem cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA}`}).`];
|
|
305
|
+
if (f.copiaVelha) {
|
|
306
|
+
const { ultimoDestaMaquina: ultimo } = f.copiaVelha;
|
|
307
|
+
return [`Fábrica: a cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA} está velha (${ultimo ? `esta máquina publicou nela por último em ` +
|
|
308
|
+
`${(0, horario_1.formatarDataHora)(ultimo, { agora, fuso })}` : 'esta máquina não aparece nela'}); não dá para dizer quem está sem batida.`];
|
|
309
|
+
}
|
|
310
|
+
if (!f.semBatida.length)
|
|
311
|
+
return [];
|
|
312
|
+
return [`Fábrica: ${f.semBatida.map(m => `${m.maquina} sem batida há ${(0, horario_1.duracaoCurta)(m.idadeMin)} ` +
|
|
313
|
+
`(último retrato ${(0, horario_1.formatarDataHora)(m.publicadoEm, { agora, fuso })})`).join('; ')}, pela cópia local de ${fabrica_estado_1.BRANCH_DA_FABRICA}.`];
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* O texto do relatorio, igual em todo canal: os icones sao parte do formato aprovado. `fuso`: o do
|
|
317
|
+
* projeto consultado, na visao da rede; sem ele, o do dono deste processo.
|
|
318
|
+
*
|
|
319
|
+
* Fatia 2 do ensaio da 0.5.0 (P7): a hora do titulo vem com o fuso dito logo abaixo dele, na linha
|
|
320
|
+
* 2, sem mudar o titulo aprovado. `legenda: false` e para quem ja diz o fuso na mesma mensagem (o
|
|
321
|
+
* panorama da rede diz no fim).
|
|
322
|
+
*/
|
|
323
|
+
function textoDoStatusDoRoadmap(s, fuso, opcoes = {}) {
|
|
324
|
+
if (s.contrato !== exports.CONTRATO_STATUS_DO_ROADMAP)
|
|
325
|
+
throw new Error('status do roadmap: contrato inválido');
|
|
326
|
+
const p = (0, horario_1.partesLocais)(s.consultadoEm, fuso);
|
|
327
|
+
const linhaDoItem = (i) => {
|
|
328
|
+
const detalhe = i.emAberto.length ? `: ${i.emAberto.join(', ')}` : i.conduzindo && i.grupo !== 'concluidos' && i.grupo !== 'hoje'
|
|
329
|
+
? ` (${i.conduzindo.fase}${i.conduzindo.maquina ? `, ${i.conduzindo.maquina}` : ''})` : '';
|
|
330
|
+
return `• ${i.id} ${i.titulo}${detalhe}${i.hitl ? ' #HITL' : ''}`;
|
|
331
|
+
};
|
|
332
|
+
const cortar = (lista, f) => [...lista.slice(0, exports.TETO_DO_FECHO).map(f), ...(lista.length > exports.TETO_DO_FECHO ? [`• e mais ${lista.length - exports.TETO_DO_FECHO}`] : [])];
|
|
333
|
+
return [
|
|
334
|
+
`Roadmap do ${capitalizar(s.projeto)} (${p.dia}/${p.mes}, ${p.hora}:${p.minuto})`,
|
|
335
|
+
...(opcoes.legenda === false ? [] : [(0, horario_1.legendaDoFuso)(fuso, s.consultadoEm)]),
|
|
336
|
+
// RM-052: logo abaixo do titulo aprovado (e do fuso), qual projeto foi lido e o que nao foi.
|
|
337
|
+
...(s.consulta ? (0, projeto_alvo_1.linhasDaConsulta)(s.consulta) : []),
|
|
338
|
+
...linhaDaFabrica(s.fabrica, s.consultadoEm, fuso),
|
|
339
|
+
...s.grupos.filter(g => g.itens.length).flatMap(g => ['', `${g.icone} ${g.titulo}`, ...g.itens.map(linhaDoItem)]),
|
|
340
|
+
'', 'O que precisa de você',
|
|
341
|
+
...(s.precisaDeVoce.length ? cortar(s.precisaDeVoce, x => `• ${x.item}${naMaquina(x.espera.maquina)}: ${x.espera.pergunta}` +
|
|
342
|
+
(x.espera.codigo ? ` Responda ${x.espera.codigo} ${x.espera.recomendada ?? 'a'} (ou outra letra).` : ' A pergunta chega no próximo resumo.'))
|
|
343
|
+
: ['• Nada agora.']),
|
|
344
|
+
'', 'O que eu faço em seguida',
|
|
345
|
+
...(s.emSeguida.length ? cortar(s.emSeguida, x => linhaDoQueVem(x, s.consultadoEm, fuso))
|
|
346
|
+
: ['• Nada em andamento; sigo o que você priorizar.']),
|
|
347
|
+
].join('\n');
|
|
348
|
+
}
|
package/dist/runtime-context.js
CHANGED
|
@@ -46,6 +46,7 @@ const thread_1 = require("./thread");
|
|
|
46
46
|
const hosts_1 = require("./hosts");
|
|
47
47
|
const worktree_1 = require("./worktree");
|
|
48
48
|
const mcp_install_1 = require("./mcp-install");
|
|
49
|
+
const mcp_grafo_1 = require("./mcp-grafo");
|
|
49
50
|
const hitl_public_receipt_1 = require("./hitl-public-receipt");
|
|
50
51
|
/** Correlação interna; nenhuma identidade de despacho concede poder de gate. */
|
|
51
52
|
function novaIdentidadeDeDespacho(threadId, fase) {
|
|
@@ -207,9 +208,12 @@ function validarContextoRuntime(contexto, cwd) {
|
|
|
207
208
|
throw Error('runtime.context.installation: catalogo ou plugin incompleto');
|
|
208
209
|
// Capacidade derivada do bloco atual, nunca aceita como entrada do modelo.
|
|
209
210
|
const permiteEditarProduto = permissoesFilho === 'worktree' && (0, thread_1.blocoDaThread)(thread, thread.faseAtual).fases.includes('GO');
|
|
211
|
+
// RM-031 KG5 (D2, D7): as tools do grafo seguem a flag do manifesto da raiz, que o servidor do filho tambem le;
|
|
212
|
+
// o manifesto da worktree da thread e o pedido do modelo nao ligam nada.
|
|
213
|
+
const grafoMcp = (0, mcp_grafo_1.grafoLigado)((0, manifest_1.exigirManifesto)(raiz).manifesto);
|
|
210
214
|
const publicVerifiers = (0, hitl_public_receipt_1.publicHitlVerifiers)();
|
|
211
215
|
const servidor = { ...(0, mcp_install_1.configuracaoServidorMcp)(raiz, contexto.host, contexto.threadId, transporteShip, permissoesFilho, contexto.identidade?.dispatchId),
|
|
212
216
|
...(publicVerifiers ? { env: { [hitl_public_receipt_1.ENV_HITL_VERIFIERS]: publicVerifiers } } : {}),
|
|
213
217
|
...(contexto.host === 'codex' && permissoesFilho === 'worktree' ? (0, mcp_install_1.politicasFilhoCodex)(perfil.politicasCodex ?? {}) : {}) };
|
|
214
|
-
return { projeto: raiz, instalacao, bootstrap, permissoesFilho, permiteEditarProduto, servidor };
|
|
218
|
+
return { projeto: raiz, instalacao, bootstrap, permissoesFilho, permiteEditarProduto, grafoMcp, servidor };
|
|
215
219
|
}
|
|
@@ -73,8 +73,14 @@ function inventariarSessoes(raiz, opcoes = {}) {
|
|
|
73
73
|
const canonica = (0, estado_thread_1.raizDoEstado)(raiz);
|
|
74
74
|
const claude = (0, claude_bg_1.consultarSessoes)(undefined, opcoes.todas);
|
|
75
75
|
const codex = (0, codex_1.consultarRollouts)(opcoes.todas);
|
|
76
|
+
const origemClaude = `claude agents --json${opcoes.todas ? ' --all' : ''}`;
|
|
77
|
+
// Fatia 2 do ensaio da 0.5.0 (P1): como o diretório opcional do Codex, só o ENOENT antes da consulta
|
|
78
|
+
// prova a fonte ausente; qualquer outra falha do `claude` continua deixando o inventário incompleto.
|
|
76
79
|
const fontes = [
|
|
77
|
-
|
|
80
|
+
claude.ausente
|
|
81
|
+
? { origem: origemClaude, ok: true, ausente: true, detalhe: `${claude.detalhe}: nenhuma sessão claude-bg a listar`,
|
|
82
|
+
correcao: 'para despachar e listar pelo claude-bg, instale o Claude Code e garanta `claude` no PATH' }
|
|
83
|
+
: { origem: origemClaude, ok: claude.ok, detalhe: claude.detalhe },
|
|
78
84
|
...codex.resultadosFontes,
|
|
79
85
|
];
|
|
80
86
|
const todas = [
|
package/dist/sessoes.js
CHANGED
|
@@ -51,7 +51,7 @@ const util_1 = require("./util");
|
|
|
51
51
|
function textoDoInventario(r) {
|
|
52
52
|
const linhas = [
|
|
53
53
|
`Inventário ${r.escopo.global ? 'global' : 'do projeto'} da conta ${r.escopo.usuario}; histórico: ${r.escopo.historico ? 'incluído' : 'omitido'}.`,
|
|
54
|
-
`Fontes: ${r.fontes.map(f => `${f.origem} (${f.ok ? 'ok' : 'FALHA'})`).join('; ')}`,
|
|
54
|
+
`Fontes: ${r.fontes.map(f => `${f.origem} (${f.ausente ? 'ausente' : f.ok ? 'ok' : 'FALHA'})`).join('; ')}`,
|
|
55
55
|
`Total: ${r.total}; sem thread: ${r.semThread}; ambíguas: ${r.ambiguas}; consulta: ${r.ok ? 'válida' : 'INCOMPLETA'}.`,
|
|
56
56
|
];
|
|
57
57
|
if (r.sessoes.length)
|
|
@@ -59,6 +59,9 @@ function textoDoInventario(r) {
|
|
|
59
59
|
s.vinculos.map(v => `${v.thread}/${v.fase}`).join(', ') || '(fora do ork)'])));
|
|
60
60
|
for (const f of r.fontes.filter(f => !f.ok))
|
|
61
61
|
linhas.push(`Falha: ${f.detalhe}`);
|
|
62
|
+
// Fatia 2 do ensaio da 0.5.0 (P1): a fonte ausente nao e falha, mas diz o que falta e como ter.
|
|
63
|
+
for (const f of r.fontes.filter(f => f.ausente))
|
|
64
|
+
linhas.push(`Ausente: ${f.detalhe}${f.correcao ? `; correção: ${f.correcao}` : ''}`);
|
|
62
65
|
return linhas.join('\n');
|
|
63
66
|
}
|
|
64
67
|
function tabelaDeSessoes(raiz, todas = false) {
|
package/dist/ship.js
CHANGED
|
@@ -348,6 +348,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
348
348
|
correcao,
|
|
349
349
|
de,
|
|
350
350
|
para,
|
|
351
|
+
// Fatia 2 do ensaio da 0.5.0 (P2): o gate do ensaio e marcado, e os leitores de estado o ignoram.
|
|
352
|
+
...(r.dryRun ? { dryRun: true } : {}),
|
|
351
353
|
});
|
|
352
354
|
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipBloqueado, {
|
|
353
355
|
de,
|
|
@@ -373,6 +375,26 @@ function ship(carregado, threadId, opcoes) {
|
|
|
373
375
|
const branchAtrasDaBase = pontaDe && pontaPara && de !== para
|
|
374
376
|
? !(0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaPara, pontaDe], raiz).ok
|
|
375
377
|
: undefined;
|
|
378
|
+
// Fatia 2 do ensaio da 0.5.0 (P9): sem delta (a branch da thread ja esta contida na base) e com a base
|
|
379
|
+
// local a frente da ref de rastreio do remoto, o push publicaria a base sem merge de thread. A medida e
|
|
380
|
+
// local, sem rede; sem remoto, sem ref de rastreio ou com --sem-push, o fato nao vem e nada muda.
|
|
381
|
+
// CHECK, rodada 1 (B1): o merge que o proprio ship ja fez e nao chegou ao remoto (push recusado,
|
|
382
|
+
// --sem-push, retomada do MCP) tambem deixa a base a frente sem delta, mas leva a ponta da thread como
|
|
383
|
+
// pai que nao e o primeiro: e entrega de thread, e o retry empurra como antes. Rodada 2 (S-R2-1): so
|
|
384
|
+
// quando a linha principal da base, alem do remoto, e toda de merge; commit direto nela segue barrado.
|
|
385
|
+
const rastreio = pontaDe && pontaPara && de !== para && !opcoes.semPush && remotoConfigurado(raiz, remoto)
|
|
386
|
+
? shaDaRef(raiz, `refs/remotes/${remoto}/${para}`) : null;
|
|
387
|
+
const mergeDaThreadNaBase = (desde) => {
|
|
388
|
+
const diretos = (0, util_1.exec)('git', ['rev-list', '--first-parent', '--no-merges', `${desde}..${pontaPara}`], raiz);
|
|
389
|
+
return diretos.ok && diretos.stdout.trim() === '' &&
|
|
390
|
+
(0, util_1.exec)('git', ['rev-list', '--merges', '--parents', `${desde}..${pontaPara}`], raiz).stdout.split('\n')
|
|
391
|
+
.some((linha) => linha.trim().split(' ').slice(2).includes(pontaDe));
|
|
392
|
+
};
|
|
393
|
+
const semDeltaComBaseAFrente = rastreio
|
|
394
|
+
? (0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaDe, pontaPara], raiz).ok &&
|
|
395
|
+
!(0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaPara, rastreio], raiz).ok &&
|
|
396
|
+
!mergeDaThreadNaBase(rastreio)
|
|
397
|
+
: undefined;
|
|
376
398
|
const violacoes = (0, policies_1.avaliarPolicies)(manifesto, {
|
|
377
399
|
gate: 'ship',
|
|
378
400
|
de,
|
|
@@ -380,6 +402,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
380
402
|
baseBranch: manifesto.worktree.base_branch,
|
|
381
403
|
threadId,
|
|
382
404
|
branchAtrasDaBase,
|
|
405
|
+
semDeltaComBaseAFrente,
|
|
406
|
+
remoto,
|
|
383
407
|
});
|
|
384
408
|
r.violacoes = violacoes;
|
|
385
409
|
if (!r.dryRun) {
|
|
@@ -647,7 +671,7 @@ function textoDoShip(r) {
|
|
|
647
671
|
linhas.push(` ${local(r.autorizacao.razao)}`);
|
|
648
672
|
if (r.verificacao) {
|
|
649
673
|
const v = r.verificacao;
|
|
650
|
-
linhas.push(` verificacao ${v.ok ? 'passou' : 'REPROVOU'} no HEAD ${
|
|
674
|
+
linhas.push(` verificacao ${v.ok ? 'passou' : 'REPROVOU'} no HEAD ${(0, util_1.shaCurto)(v.commit)} ` +
|
|
651
675
|
`(${v.claims.filter((c) => c.verificado).length}/${v.claims.length} claims, ` +
|
|
652
676
|
`${v.comandos.filter((c) => c.ok).length}/${v.comandos.length} comandos)`);
|
|
653
677
|
}
|
package/dist/thread-close.js
CHANGED
|
@@ -47,6 +47,7 @@ const ledger_1 = require("./ledger");
|
|
|
47
47
|
const estado_thread_1 = require("./estado-thread");
|
|
48
48
|
const util_1 = require("./util");
|
|
49
49
|
const fabrica_publicar_1 = require("./fabrica-publicar");
|
|
50
|
+
const fechamento_1 = require("./fechamento");
|
|
50
51
|
function validarArtefato(raiz, arquivo, sha256) {
|
|
51
52
|
if (!/^[a-f0-9]{64}$/.test(sha256))
|
|
52
53
|
throw new Error('sha256 inválido');
|
|
@@ -111,5 +112,7 @@ function fecharAdministrativamente(raiz, id, opcoes) {
|
|
|
111
112
|
t.status = 'fechada';
|
|
112
113
|
(0, thread_1.gravarThread)(raiz, t);
|
|
113
114
|
(0, fabrica_publicar_1.publicarEmSegundoPlano)(raiz);
|
|
115
|
+
// RM-037 (rm037noite): a thread fechada solta o que segurava, com registro no ledger dela.
|
|
116
|
+
(0, fechamento_1.liberarAoFechar)(raiz, id);
|
|
114
117
|
return t;
|
|
115
118
|
}
|
package/dist/thread.js
CHANGED
|
@@ -45,6 +45,7 @@ exports.caminhoThread = caminhoThread;
|
|
|
45
45
|
exports.listarIds = listarIds;
|
|
46
46
|
exports.lerThread = lerThread;
|
|
47
47
|
exports.gravarThread = gravarThread;
|
|
48
|
+
exports.avisoDeThreadSemBase = avisoDeThreadSemBase;
|
|
48
49
|
exports.idDisponivel = idDisponivel;
|
|
49
50
|
exports.criarWorktree = criarWorktree;
|
|
50
51
|
exports.novaThread = novaThread;
|
|
@@ -113,9 +114,22 @@ function carimbarBase(raiz) {
|
|
|
113
114
|
const commit = (0, util_1.exec)('git', ['rev-parse', 'HEAD'], raiz);
|
|
114
115
|
return {
|
|
115
116
|
branch: branch.ok ? branch.stdout.trim() : 'desconhecida',
|
|
116
|
-
commit: commit.ok ? commit.stdout.trim() :
|
|
117
|
+
commit: commit.ok ? commit.stdout.trim() : util_1.COMMIT_DESCONHECIDO,
|
|
117
118
|
};
|
|
118
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Ensaio da 0.5.0: a thread aberta antes do primeiro commit nascia sem base, e nada avisava; o
|
|
122
|
+
* ship depois nao tem de onde partir. O aviso vai ao stderr e nao muda a saida do comando.
|
|
123
|
+
*/
|
|
124
|
+
function avisoDeThreadSemBase(thread, gravada) {
|
|
125
|
+
if (thread.base.commit !== util_1.COMMIT_DESCONHECIDO)
|
|
126
|
+
return null;
|
|
127
|
+
return gravada
|
|
128
|
+
? `Aviso: o repositório ainda não tem commit, e a thread ${thread.id} nasceu sem base: o ship não tem de onde partir. ` +
|
|
129
|
+
`Faça o primeiro commit, feche esta com ork thread close ${thread.id} --motivo engano --por <quem> ` +
|
|
130
|
+
'--justificativa "aberta antes do primeiro commit" e abra outra.'
|
|
131
|
+
: 'Aviso: o repositório ainda não tem commit, e a thread nasceria sem base: faça o primeiro commit antes de abrir a primeira thread.';
|
|
132
|
+
}
|
|
119
133
|
/** Resolve um id livre a partir do assunto normalizado (`<abbrev>-<assunto>`). */
|
|
120
134
|
function idDisponivel(raiz, abbrev, assunto) {
|
|
121
135
|
let candidato = assunto;
|
|
@@ -144,6 +158,9 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
|
|
|
144
158
|
const base = opcoes.base ?? manifesto.worktree.base_branch;
|
|
145
159
|
const commitBase = (0, util_1.exec)('git', ['rev-parse', '--verify', base], raiz);
|
|
146
160
|
const ref = commitBase.ok ? commitBase.stdout.trim() : 'HEAD';
|
|
161
|
+
// Fatia 2 do ensaio da 0.5.0 (P3): a pasta das worktrees que o ork cria agora nasce fora do git.
|
|
162
|
+
const pastaDasWorktrees = path.dirname(dir);
|
|
163
|
+
const pastaNova = !fs.existsSync(pastaDasWorktrees);
|
|
147
164
|
// Branch nova (`-b`) no caso comum; branch que ja existe no ciclo `merge-branch`.
|
|
148
165
|
const argumentos = opcoes.branchExistente
|
|
149
166
|
? ['worktree', 'add', dir, opcoes.branchExistente]
|
|
@@ -152,6 +169,8 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
|
|
|
152
169
|
if (!r.ok) {
|
|
153
170
|
throw new Error(`git worktree add falhou: ${(r.stderr || r.stdout).trim()}`);
|
|
154
171
|
}
|
|
172
|
+
if (pastaNova)
|
|
173
|
+
(0, util_1.ignorarPastaNoGit)(pastaDasWorktrees, raiz);
|
|
155
174
|
const lista = (0, util_1.exec)('git', ['worktree', 'list', '--porcelain'], raiz);
|
|
156
175
|
const criada = lista.ok && lista.stdout.split('\n').some((l) => l.trim() === `worktree ${dir}`);
|
|
157
176
|
if (!criada) {
|
|
@@ -362,7 +381,7 @@ function resumoDaThread(thread, conducao) {
|
|
|
362
381
|
linhas.push(` ciclo ${thread.variante ?? 'padrao do modo'}`);
|
|
363
382
|
linhas.push(` fase ${thread.faseAtual}`);
|
|
364
383
|
linhas.push(` status ${thread.status}`);
|
|
365
|
-
linhas.push(` base ${thread.base.branch} @ ${thread.base.commit
|
|
384
|
+
linhas.push(` base ${thread.base.branch} @ ${(0, util_1.shaCurto)(thread.base.commit)}`);
|
|
366
385
|
linhas.push(` worktree ${thread.worktree ?? '(raiz do projeto)'}`);
|
|
367
386
|
// I-36 (T17): quem passa a leitura da conducao ve a linha unica; sem ela o resumo fica como era.
|
|
368
387
|
if (conducao !== undefined)
|
package/dist/util.js
CHANGED
|
@@ -34,8 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.GITIGNORE_DA_MAQUINA = exports.COMMIT_DESCONHECIDO = void 0;
|
|
37
38
|
exports.exec = exec;
|
|
39
|
+
exports.shaCurto = shaCurto;
|
|
40
|
+
exports.branchDoHead = branchDoHead;
|
|
38
41
|
exports.noPath = noPath;
|
|
42
|
+
exports.ignorarPastaNoGit = ignorarPastaNoGit;
|
|
39
43
|
exports.subirAte = subirAte;
|
|
40
44
|
exports.gravar = gravar;
|
|
41
45
|
exports.lerJson = lerJson;
|
|
@@ -66,10 +70,73 @@ function exec(cmd, args, cwd, timeoutMs = 120000, env = process.env) {
|
|
|
66
70
|
...(r.error ? { error: r.error.code ?? r.error.message } : {}),
|
|
67
71
|
};
|
|
68
72
|
}
|
|
69
|
-
/** O
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
/** O commit quando o repositorio ainda nao tem commit (base da thread, HEAD do verify). */
|
|
74
|
+
exports.COMMIT_DESCONHECIDO = 'desconhecido';
|
|
75
|
+
/**
|
|
76
|
+
* Sha para exibir: 8 caracteres quando o valor e um sha hexadecimal (SHA-1 ou SHA-256); qualquer
|
|
77
|
+
* outro valor, como o marcador de repositorio sem commit, sai inteiro (o ensaio da 0.5.0 viu "desconhe").
|
|
78
|
+
*/
|
|
79
|
+
function shaCurto(valor) {
|
|
80
|
+
return /^[0-9a-f]{7,64}$/i.test(valor) ? valor.slice(0, 8) : valor;
|
|
81
|
+
}
|
|
82
|
+
/** A branch para onde o HEAD aponta, com ou sem commit; `null` com o HEAD destacado. */
|
|
83
|
+
function branchDoHead(dir) {
|
|
84
|
+
const r = exec('git', ['symbolic-ref', '--quiet', '--short', 'HEAD'], dir);
|
|
85
|
+
return r.ok && r.stdout.trim() ? r.stdout.trim() : null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* O binario esta no PATH? A busca e do proprio processo, como o `which` fazia: o primeiro arquivo
|
|
89
|
+
* regular executavel na ordem do PATH. Registro R3 do ensaio da 0.5.0: sem o binario `which` na
|
|
90
|
+
* maquina, o doctor dizia que o `git` e o `claude` faltavam. Entrada vazia do PATH e pulada (nao vira
|
|
91
|
+
* o diretorio atual), e nome com barra e conferido direto.
|
|
92
|
+
*/
|
|
93
|
+
function noPath(bin, env = process.env) {
|
|
94
|
+
const executavel = (alvo) => {
|
|
95
|
+
try {
|
|
96
|
+
if (!fs.statSync(alvo).isFile())
|
|
97
|
+
return false;
|
|
98
|
+
fs.accessSync(alvo, fs.constants.X_OK);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
if (bin.includes('/'))
|
|
106
|
+
return executavel(bin) ? bin : null;
|
|
107
|
+
for (const dir of (env.PATH ?? '').split(path.delimiter)) {
|
|
108
|
+
if (dir && executavel(path.join(dir, bin)))
|
|
109
|
+
return path.join(dir, bin);
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/** O `.gitignore` que o ork poe na pasta que e da maquina (fatia 2 do ensaio da 0.5.0, P3). */
|
|
114
|
+
exports.GITIGNORE_DA_MAQUINA = '# Criado pelo ork: esta pasta é da máquina, não do repositório.\n*\n';
|
|
115
|
+
/**
|
|
116
|
+
* Fatia 2 do ensaio da 0.5.0 (P3): a pasta que o ork cria para a maquina (o estado em `.orkastery/`
|
|
117
|
+
* e a pasta das worktrees) fica fora do git por um `.gitignore` proprio com `*`; o `.gitignore` do
|
|
118
|
+
* usuario nao e tocado. Nunca sobrescreve um existente, so vale para pasta dentro do repositorio e
|
|
119
|
+
* nao cria quando o repositorio ja rastreia algo nela: o projeto que versiona o proprio estado segue
|
|
120
|
+
* versionando. Devolve se criou.
|
|
121
|
+
*/
|
|
122
|
+
function ignorarPastaNoGit(dir, raizDoRepo) {
|
|
123
|
+
const relativo = path.relative(raizDoRepo, dir);
|
|
124
|
+
if (!relativo || relativo.startsWith('..') || path.isAbsolute(relativo))
|
|
125
|
+
return false;
|
|
126
|
+
const arquivo = path.join(dir, '.gitignore');
|
|
127
|
+
if (fs.existsSync(arquivo))
|
|
128
|
+
return false;
|
|
129
|
+
const rastreados = exec('git', ['ls-files', '-z', '--', relativo], raizDoRepo);
|
|
130
|
+
if (rastreados.ok && rastreados.stdout.length > 0)
|
|
131
|
+
return false;
|
|
132
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
133
|
+
try {
|
|
134
|
+
fs.writeFileSync(arquivo, exports.GITIGNORE_DA_MAQUINA, { flag: 'wx' });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
73
140
|
}
|
|
74
141
|
/** Sobe a arvore de diretorios procurando um arquivo. Retorna o diretorio que o contem. */
|
|
75
142
|
function subirAte(dirInicial, arquivo) {
|