@orkastery/cli 0.5.0 → 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/claude-code/README.md +11 -2
- package/adapters/claude-code/commands/onboarding.md +3 -2
- package/adapters/openclaw/README.md +11 -0
- package/adapters/openclaw/dist/index.js +1 -1
- package/adapters/openclaw/openclaw.plugin.json +1 -1
- package/adapters/openclaw/package.json +1 -1
- package/adapters/openclaw/src/index.ts +1 -1
- package/dist/adapters/claude-bg.js +20 -6
- package/dist/auditrun.js +1 -1
- package/dist/claim-lint.js +3 -1
- package/dist/docs.js +71 -3
- package/dist/doctor.js +209 -32
- package/dist/entrega-pr.js +7 -0
- package/dist/fabrica-estado.js +21 -3
- package/dist/fix.js +3 -3
- package/dist/hitl-resumo.js +24 -1
- package/dist/hosts.js +7 -3
- package/dist/index.js +42 -14
- package/dist/init.js +11 -5
- package/dist/integracoes-locais.js +2 -0
- package/dist/intelligence-graph-cli.js +181 -27
- package/dist/intelligence-graph-contract.js +71 -12
- package/dist/intelligence-graph-extract-md.js +41 -23
- package/dist/intelligence-graph-extract-ts.js +181 -11
- package/dist/intelligence-graph-extract.js +186 -9
- package/dist/intelligence-graph-index.js +205 -24
- package/dist/intelligence-graph-repo.js +10 -0
- package/dist/leases.js +16 -0
- package/dist/ledger.js +9 -0
- package/dist/licoes.js +5 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-runtime.js +3 -0
- package/dist/manifest.js +24 -0
- package/dist/mcp-artifacts.js +2 -0
- package/dist/mcp-git.js +60 -12
- package/dist/mcp-grafo-worker.js +91 -0
- package/dist/mcp-grafo.js +233 -0
- package/dist/mcp-install.js +2 -1
- package/dist/mcp-server.js +9 -0
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/network-roadmap.js +5 -5
- package/dist/ocupacao.js +6 -2
- package/dist/onboarding.js +9 -4
- package/dist/orquestracao.js +2 -1
- package/dist/parado-no-condutor.js +933 -0
- package/dist/policies.js +27 -2
- package/dist/prova-ativacao.js +345 -0
- package/dist/pulse-delivery.js +27 -6
- package/dist/pulse.js +53 -3
- package/dist/roadmap-status.js +142 -7
- 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.js +21 -2
- package/dist/util.js +71 -4
- package/dist/verify.js +2 -2
- package/dist/worktree.js +29 -6
- package/eval/casos/onboarding.json +1 -1
- package/package.json +1 -1
- package/skills/core/onboarding/SKILL.md +3 -2
- package/skills/phases/ship-release/SKILL.md +6 -3
package/dist/roadmap-status.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TETO_DO_FECHO = exports.GRUPO_DO_CICLO = exports.GRUPOS_DO_ROADMAP = exports.CONTRATO_STATUS_DO_ROADMAP = void 0;
|
|
4
4
|
exports.esperaDoDono = esperaDoDono;
|
|
5
5
|
exports.fatosLocais = fatosLocais;
|
|
6
|
+
exports.batidaDaFabrica = batidaDaFabrica;
|
|
6
7
|
exports.montarStatusDoRoadmap = montarStatusDoRoadmap;
|
|
7
8
|
exports.montarStatusDeFatos = montarStatusDeFatos;
|
|
8
9
|
exports.textoDoStatusDoRoadmap = textoDoStatusDoRoadmap;
|
|
@@ -34,6 +35,12 @@ const hitl_gates_1 = require("./hitl-gates");
|
|
|
34
35
|
const hitl_classificacao_1 = require("./hitl-classificacao");
|
|
35
36
|
const horario_1 = require("./horario");
|
|
36
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");
|
|
37
44
|
exports.CONTRATO_STATUS_DO_ROADMAP = 'ork.roadmap-status/v1';
|
|
38
45
|
/** Os grupos, na ordem e com os icones que o dono aprovou. */
|
|
39
46
|
exports.GRUPOS_DO_ROADMAP = Object.freeze([
|
|
@@ -79,7 +86,9 @@ function esperaDoDono(raiz, t, quando) {
|
|
|
79
86
|
const escalacoes = [...new Set(eventos.filter(e => e.tipo === 'gate_blocked' && e.fase === t.faseAtual &&
|
|
80
87
|
hitl_gates_1.MOTIVOS_DE_ESCALACAO_HUMANA.includes(String(e.motivo)) && (0, hitl_classificacao_1.quemDecide)(String(e.motivo)) === 'dono')
|
|
81
88
|
.map(e => String(e.motivo)))];
|
|
82
|
-
|
|
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]) {
|
|
83
92
|
try {
|
|
84
93
|
const preparado = (0, hitl_gates_1.prepararPedidoGate)(raiz, t.id, motivo, quando);
|
|
85
94
|
const pedido = 'aberto' in preparado ? preparado.aberto : preparado.novo;
|
|
@@ -114,6 +123,25 @@ function esperaDoDono(raiz, t, quando) {
|
|
|
114
123
|
function fatosLocais(raiz, quando, fuso) {
|
|
115
124
|
const hoje = (0, horario_1.dataLocal)(quando, fuso);
|
|
116
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
|
+
};
|
|
117
145
|
for (const id of (0, thread_1.listarIds)(raiz)) {
|
|
118
146
|
let t;
|
|
119
147
|
try {
|
|
@@ -126,14 +154,75 @@ function fatosLocais(raiz, quando, fuso) {
|
|
|
126
154
|
id: t.id, roadmap: t.roadmap ?? null, aberta: t.status !== 'fechada', fase: t.faseAtual,
|
|
127
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),
|
|
128
156
|
espera: () => esperaDoDono(raiz, t, quando),
|
|
157
|
+
entrega: () => (t.status === 'fechada' ? undefined : entrega(t.id)),
|
|
129
158
|
});
|
|
130
159
|
}
|
|
131
160
|
return fatos;
|
|
132
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
|
+
}
|
|
133
214
|
/** Monta o relatorio. Leitura pura: nada e escrito, nenhum pedido e aberto. */
|
|
134
215
|
function montarStatusDoRoadmap(raiz, opcoes = {}) {
|
|
135
216
|
const quando = opcoes.quando ?? new Date().toISOString();
|
|
136
|
-
|
|
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 } : {}) });
|
|
137
226
|
}
|
|
138
227
|
/** O relatorio a partir das paginas do roadmap e dos fatos das threads, de onde quer que venham. */
|
|
139
228
|
function montarStatusDeFatos(docs, fatos, opcoes) {
|
|
@@ -157,7 +246,10 @@ function montarStatusDeFatos(docs, fatos, opcoes) {
|
|
|
157
246
|
if (hitl)
|
|
158
247
|
break;
|
|
159
248
|
}
|
|
160
|
-
|
|
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;
|
|
161
253
|
const emAberto = exports.GRUPO_DO_CICLO[ciclo] === 'disponiveis' ? [
|
|
162
254
|
...(texto(estado.exposicao) && texto(estado.exposicao) !== 'Geral' ? [`exposição ${texto(estado.exposicao)}`] : []),
|
|
163
255
|
...(texto(estado.habilitacao) && texto(estado.habilitacao) !== 'Concluída' ? [`habilitação ${texto(estado.habilitacao)}`] : []),
|
|
@@ -166,7 +258,8 @@ function montarStatusDeFatos(docs, fatos, opcoes) {
|
|
|
166
258
|
id: d.id, titulo: nomeCurto(texto(d.dados.titulo) || d.id), ciclo,
|
|
167
259
|
grupo: entregueHoje ? 'hoje' : exports.GRUPO_DO_CICLO[ciclo] ?? 'proposto',
|
|
168
260
|
emAberto, threads: ligadas,
|
|
169
|
-
...(conduz ? { conduzindo: { thread: conduz.id, fase: conduz.fase, ...(conduz.maquina ? { maquina: conduz.maquina } : {})
|
|
261
|
+
...(conduz ? { conduzindo: { thread: conduz.id, fase: conduz.fase, ...(conduz.maquina ? { maquina: conduz.maquina } : {}),
|
|
262
|
+
...(entrega ? { entrega } : {}) } } : {}),
|
|
170
263
|
...(hitl ? { hitl } : {}),
|
|
171
264
|
});
|
|
172
265
|
}
|
|
@@ -177,17 +270,57 @@ function montarStatusDeFatos(docs, fatos, opcoes) {
|
|
|
177
270
|
precisaDeVoce: itens.filter(i => i.hitl).map(i => ({ item: i.id, espera: i.hitl })),
|
|
178
271
|
emSeguida: itens.filter(i => i.conduzindo && !i.hitl).map(i => ({ item: i.id, ...i.conduzindo })),
|
|
179
272
|
...(opcoes.consulta ? { consulta: opcoes.consulta } : {}),
|
|
273
|
+
...(opcoes.fabrica ? { fabrica: opcoes.fabrica } : {}),
|
|
180
274
|
};
|
|
181
275
|
}
|
|
182
276
|
/** "orkastery" vira "Orkastery": o titulo diz o nome do projeto como gente escreve. */
|
|
183
277
|
const capitalizar = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
184
278
|
/** A maquina entre parenteses, so quando o fato a traz (visao da rede, D7 da RM-054). */
|
|
185
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
|
+
}
|
|
186
315
|
/**
|
|
187
316
|
* O texto do relatorio, igual em todo canal: os icones sao parte do formato aprovado. `fuso`: o do
|
|
188
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).
|
|
189
322
|
*/
|
|
190
|
-
function textoDoStatusDoRoadmap(s, fuso) {
|
|
323
|
+
function textoDoStatusDoRoadmap(s, fuso, opcoes = {}) {
|
|
191
324
|
if (s.contrato !== exports.CONTRATO_STATUS_DO_ROADMAP)
|
|
192
325
|
throw new Error('status do roadmap: contrato inválido');
|
|
193
326
|
const p = (0, horario_1.partesLocais)(s.consultadoEm, fuso);
|
|
@@ -199,15 +332,17 @@ function textoDoStatusDoRoadmap(s, fuso) {
|
|
|
199
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}`] : [])];
|
|
200
333
|
return [
|
|
201
334
|
`Roadmap do ${capitalizar(s.projeto)} (${p.dia}/${p.mes}, ${p.hora}:${p.minuto})`,
|
|
202
|
-
|
|
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.
|
|
203
337
|
...(s.consulta ? (0, projeto_alvo_1.linhasDaConsulta)(s.consulta) : []),
|
|
338
|
+
...linhaDaFabrica(s.fabrica, s.consultadoEm, fuso),
|
|
204
339
|
...s.grupos.filter(g => g.itens.length).flatMap(g => ['', `${g.icone} ${g.titulo}`, ...g.itens.map(linhaDoItem)]),
|
|
205
340
|
'', 'O que precisa de você',
|
|
206
341
|
...(s.precisaDeVoce.length ? cortar(s.precisaDeVoce, x => `• ${x.item}${naMaquina(x.espera.maquina)}: ${x.espera.pergunta}` +
|
|
207
342
|
(x.espera.codigo ? ` Responda ${x.espera.codigo} ${x.espera.recomendada ?? 'a'} (ou outra letra).` : ' A pergunta chega no próximo resumo.'))
|
|
208
343
|
: ['• Nada agora.']),
|
|
209
344
|
'', 'O que eu faço em seguida',
|
|
210
|
-
...(s.emSeguida.length ? cortar(s.emSeguida, x =>
|
|
345
|
+
...(s.emSeguida.length ? cortar(s.emSeguida, x => linhaDoQueVem(x, s.consultadoEm, fuso))
|
|
211
346
|
: ['• Nada em andamento; sigo o que você priorizar.']),
|
|
212
347
|
].join('\n');
|
|
213
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.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) {
|
package/dist/verify.js
CHANGED
|
@@ -183,7 +183,7 @@ function cwdDaThread(raiz, thread) {
|
|
|
183
183
|
/** HEAD real do diretorio de trabalho, carimbado em toda verificacao. */
|
|
184
184
|
function commitReal(cwd) {
|
|
185
185
|
const r = (0, util_1.exec)('git', ['rev-parse', 'HEAD'], cwd);
|
|
186
|
-
return r.ok ? r.stdout.trim() :
|
|
186
|
+
return r.ok ? r.stdout.trim() : util_1.COMMIT_DESCONHECIDO;
|
|
187
187
|
}
|
|
188
188
|
/** O pedido de conducao de uma verificacao, com o prazo derivado dos comandos que ela vai rodar (D3). */
|
|
189
189
|
function pedidoDoVerify(carregado, threadId, operacao, comandos, conducao) {
|
|
@@ -456,7 +456,7 @@ function textoDoVerify(r) {
|
|
|
456
456
|
linhas.push(`ork verify da thread ${r.thread}`);
|
|
457
457
|
linhas.push(` HEAD real ${r.commit}`);
|
|
458
458
|
linhas.push(` diretorio ${r.cwd}`);
|
|
459
|
-
linhas.push(` baseline ${r.baseline ? `${r.baseline.commit
|
|
459
|
+
linhas.push(` baseline ${r.baseline ? `${(0, util_1.shaCurto)(r.baseline.commit)} de ${(0, horario_1.formatarDataHoraRotulada)(r.baseline.gravadaEm)}` : '(nao gravada: falha nao pode ser chamada de regressao)'}`);
|
|
460
460
|
if (r.preparo) {
|
|
461
461
|
const p = r.preparo;
|
|
462
462
|
linhas.push(` preparo ${p.resultado.ok ? 'ok' : situacaoDoComando(p.resultado)} em ` +
|
package/dist/worktree.js
CHANGED
|
@@ -196,6 +196,11 @@ function garantirWorktree(carregado, id) {
|
|
|
196
196
|
* commit que o git nao tem mais nao entra. `proprios` conta o que o HEAD tem fora delas e da base
|
|
197
197
|
* atual (`null` sem nenhuma base conhecida); `pontoDePartida` e a base conhecida mais nova que e
|
|
198
198
|
* ancestral do HEAD; `semAncestral` diz que HEAD e base atual nao tem historia em comum.
|
|
199
|
+
*
|
|
200
|
+
* RM-037 (fatia 3, defeito 3): `baseReescrita` diz que o ponto de partida nao esta mais na base atual.
|
|
201
|
+
* A base so anda para a frente; quando o ponto em que a thread saiu dela deixa de ser ancestral, a base
|
|
202
|
+
* foi reescrita, mesmo com ancestral comum mais antigo (force-push que tirou commits). Nesse caso o
|
|
203
|
+
* `git rebase <base>` reaplicaria, junto com os commits da thread, os `sairamDaBase` que a reescrita tirou.
|
|
199
204
|
*/
|
|
200
205
|
function historiaPropria(raiz, thread, dir, shaBase) {
|
|
201
206
|
const git = (...args) => (0, util_1.exec)('git', args, dir);
|
|
@@ -212,11 +217,13 @@ function historiaPropria(raiz, thread, dir, shaBase) {
|
|
|
212
217
|
const conhecidas = [...new Set([pontoDaCriacao, ...sincronizadas])].filter(valido);
|
|
213
218
|
const semAncestral = git('merge-base', 'HEAD', shaBase).code === 1;
|
|
214
219
|
const pontoDePartida = [...conhecidas].reverse().find(sha => git('merge-base', '--is-ancestor', sha, 'HEAD').ok) ?? null;
|
|
220
|
+
const baseReescrita = pontoDePartida !== null && !git('merge-base', '--is-ancestor', pontoDePartida, shaBase).ok;
|
|
221
|
+
const numero = (r) => r.ok && /^\d+$/.test(r.stdout.trim()) ? Number(r.stdout.trim()) : null;
|
|
222
|
+
const sairamDaBase = baseReescrita ? numero(git('rev-list', '--count', pontoDePartida, '--not', shaBase)) : null;
|
|
215
223
|
if (!conhecidas.length)
|
|
216
|
-
return { proprios: null, pontoDePartida, semAncestral };
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
return { proprios, pontoDePartida, semAncestral };
|
|
224
|
+
return { proprios: null, pontoDePartida, semAncestral, baseReescrita, sairamDaBase };
|
|
225
|
+
const proprios = numero(git('rev-list', '--count', 'HEAD', '--not', ...conhecidas, shaBase));
|
|
226
|
+
return { proprios, pontoDePartida, semAncestral, baseReescrita, sairamDaBase };
|
|
220
227
|
}
|
|
221
228
|
/**
|
|
222
229
|
* Sincroniza a branch da thread com a base ATUAL (rebase), quando a base avancou.
|
|
@@ -360,9 +367,25 @@ function sincronizarWorktree(carregado, id, opcoes = {}) {
|
|
|
360
367
|
`${historia.proprios ?? 'um numero desconhecido de'} commit(s) proprio(s): o rebase reaplicaria a historia antiga inteira`;
|
|
361
368
|
if (!opcoes.dryRun)
|
|
362
369
|
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, id), id, ledger_1.TIPOS_DE_EVENTO.worktreeSincronizada, { dir, branch, base, shaBase, shaAntes,
|
|
363
|
-
rebaseFeito: false, motivo: 'tree.blocked', semAncestral: true, detalhe });
|
|
370
|
+
rebaseFeito: false, motivo: 'tree.blocked', causa: 'base-reescrita', semAncestral: true, detalhe });
|
|
371
|
+
return { ...vazio, shaBase, shaAntes, shaDepois: shaAntes, ok: false, jaAtualizada: false, rebaseFeito: false,
|
|
372
|
+
motivo: 'tree.blocked', causa: 'base-reescrita', detalhe, correcao: `reaplique so os commits da thread sobre a base nova: ${onto}` };
|
|
373
|
+
}
|
|
374
|
+
// RM-037 (fatia 3, defeito 3): com ancestral comum, o sync caia no `git rebase <base>` abaixo e
|
|
375
|
+
// reaplicava tambem o que a reescrita tirou da base. O ponto de partida fora da base atual e o sinal.
|
|
376
|
+
if (historia.baseReescrita) {
|
|
377
|
+
const ponto = historia.pontoDePartida;
|
|
378
|
+
passos.push(`git merge-base --is-ancestor ${ponto.slice(0, 8)} ${shaBase.slice(0, 8)} (falso: a base foi reescrita)`);
|
|
379
|
+
const detalhe = `a base ${base} foi reescrita: o ponto de partida ${ponto.slice(0, 8)} da branch ${branch} nao esta mais nela, ` +
|
|
380
|
+
`e o rebase comum reaplicaria ${historia.sairamDaBase ?? 'um numero desconhecido de'} commit(s) que sairam da base junto com ` +
|
|
381
|
+
`o(s) ${historia.proprios ?? '?'} da thread (git rev-list --count HEAD --not ${ponto.slice(0, 8)} ${shaBase.slice(0, 8)})`;
|
|
382
|
+
if (!opcoes.dryRun)
|
|
383
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, id), id, ledger_1.TIPOS_DE_EVENTO.worktreeSincronizada, { dir, branch, base, shaBase, shaAntes,
|
|
384
|
+
rebaseFeito: false, motivo: 'tree.blocked', causa: 'base-reescrita', baseReescrita: true, pontoDePartida: ponto,
|
|
385
|
+
sairamDaBase: historia.sairamDaBase, proprios: historia.proprios, detalhe });
|
|
364
386
|
return { ...vazio, shaBase, shaAntes, shaDepois: shaAntes, ok: false, jaAtualizada: false, rebaseFeito: false,
|
|
365
|
-
motivo: 'tree.blocked',
|
|
387
|
+
motivo: 'tree.blocked', causa: 'base-reescrita', detalhe,
|
|
388
|
+
correcao: `reaplique so os commits da thread sobre a base nova: git -C ${dir} rebase --onto ${shaBase} ${ponto}` };
|
|
366
389
|
}
|
|
367
390
|
if (opcoes.dryRun) {
|
|
368
391
|
passos.push(`git rebase ${shaBase}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkastery/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A software factory of AI agents that proves its work: parallel threads, verified results, and short decisions only when they matter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/orkastery/orkastery#readme",
|
|
@@ -24,8 +24,9 @@ preservando as demais respostas. Exemplo de opt-out:
|
|
|
24
24
|
`ork onboarding set maestro --conteudo '{"owner":{"experience":false}}' --por equipe`.
|
|
25
25
|
Sem resposta, mantenha a pergunta pendente: defaults efetivos não são autoria humana.
|
|
26
26
|
|
|
27
|
-
Peça somente referências de variáveis nas etapas sensíveis. Valores secretos ficam
|
|
28
|
-
`~/.hermes/.env
|
|
27
|
+
Peça somente referências de variáveis nas etapas sensíveis. Valores secretos ficam no ambiente do
|
|
28
|
+
processo ou no cofre do host (no Hermes, `~/.hermes/.env`); não os leia nem leve credenciais para
|
|
29
|
+
conversa, onboarding ou ledger.
|
|
29
30
|
Se o builder fornecer um segredo, não o repita: oriente a configuração local e registre apenas
|
|
30
31
|
a referência pública aceita pelo núcleo.
|
|
31
32
|
|
|
@@ -38,9 +38,12 @@ ork phase list <thread> # a prova: mergeSha, shaRemoto, push
|
|
|
38
38
|
|
|
39
39
|
## O que o nucleo verifica por voce
|
|
40
40
|
|
|
41
|
-
A policy `push_direto_na_base` e bloqueante: origem igual ao destino,
|
|
42
|
-
do projeto,
|
|
43
|
-
|
|
41
|
+
A policy `push_direto_na_base` e bloqueante: origem igual ao destino, origem igual a branch base
|
|
42
|
+
do projeto, ou entrega sem delta com a base local a frente do remoto (o push publicaria commit que
|
|
43
|
+
nao passou por merge de thread) reprova antes de o `ork` tocar no remoto. O merge que o proprio
|
|
44
|
+
ship ja fez e nao chegou ao remoto (push recusado, `--sem-push`) nao conta: o ship repetido empurra.
|
|
45
|
+
Arvore de destino ocupada sai como `tree.blocked`; lease tomado por outra thread sai como
|
|
46
|
+
`lease.busy`.
|
|
44
47
|
|
|
45
48
|
## Racionalizacoes comuns
|
|
46
49
|
|