@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.
Files changed (63) hide show
  1. package/adapters/claude-code/README.md +11 -2
  2. package/adapters/claude-code/commands/onboarding.md +3 -2
  3. package/adapters/openclaw/README.md +11 -0
  4. package/adapters/openclaw/dist/index.js +1 -1
  5. package/adapters/openclaw/openclaw.plugin.json +1 -1
  6. package/adapters/openclaw/package.json +1 -1
  7. package/adapters/openclaw/src/index.ts +1 -1
  8. package/dist/adapters/claude-bg.js +20 -6
  9. package/dist/auditrun.js +1 -1
  10. package/dist/claim-lint.js +3 -1
  11. package/dist/docs.js +71 -3
  12. package/dist/doctor.js +209 -32
  13. package/dist/entrega-pr.js +7 -0
  14. package/dist/fabrica-estado.js +21 -3
  15. package/dist/fix.js +3 -3
  16. package/dist/hitl-resumo.js +24 -1
  17. package/dist/hosts.js +7 -3
  18. package/dist/index.js +42 -14
  19. package/dist/init.js +11 -5
  20. package/dist/integracoes-locais.js +2 -0
  21. package/dist/intelligence-graph-cli.js +181 -27
  22. package/dist/intelligence-graph-contract.js +71 -12
  23. package/dist/intelligence-graph-extract-md.js +41 -23
  24. package/dist/intelligence-graph-extract-ts.js +181 -11
  25. package/dist/intelligence-graph-extract.js +186 -9
  26. package/dist/intelligence-graph-index.js +205 -24
  27. package/dist/intelligence-graph-repo.js +10 -0
  28. package/dist/leases.js +16 -0
  29. package/dist/ledger.js +9 -0
  30. package/dist/licoes.js +5 -4
  31. package/dist/liveness.js +2 -1
  32. package/dist/maestro-runtime.js +3 -0
  33. package/dist/manifest.js +24 -0
  34. package/dist/mcp-artifacts.js +2 -0
  35. package/dist/mcp-git.js +60 -12
  36. package/dist/mcp-grafo-worker.js +91 -0
  37. package/dist/mcp-grafo.js +233 -0
  38. package/dist/mcp-install.js +2 -1
  39. package/dist/mcp-server.js +9 -0
  40. package/dist/mcp-ship.js +2 -0
  41. package/dist/mcp-verify.js +3 -1
  42. package/dist/network-roadmap.js +5 -5
  43. package/dist/ocupacao.js +6 -2
  44. package/dist/onboarding.js +9 -4
  45. package/dist/orquestracao.js +2 -1
  46. package/dist/parado-no-condutor.js +933 -0
  47. package/dist/policies.js +27 -2
  48. package/dist/prova-ativacao.js +345 -0
  49. package/dist/pulse-delivery.js +27 -6
  50. package/dist/pulse.js +53 -3
  51. package/dist/roadmap-status.js +142 -7
  52. package/dist/runtime-context.js +5 -1
  53. package/dist/sessoes-inventario.js +7 -1
  54. package/dist/sessoes.js +4 -1
  55. package/dist/ship.js +25 -1
  56. package/dist/thread.js +21 -2
  57. package/dist/util.js +71 -4
  58. package/dist/verify.js +2 -2
  59. package/dist/worktree.js +29 -6
  60. package/eval/casos/onboarding.json +1 -1
  61. package/package.json +1 -1
  62. package/skills/core/onboarding/SKILL.md +3 -2
  63. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -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
- for (const motivo of ['human.pending', ...escalacoes]) {
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
- return montarStatusDeFatos((0, docs_1.carregarDocs)(raiz).docs, fatosLocais(raiz, quando), { quando, projeto: opcoes.projeto, consulta: opcoes.consulta });
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
- const conduz = abertas[0];
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
- // RM-052: logo abaixo do titulo aprovado, qual projeto foi lido e o que nao foi.
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 => `• ${x.item}: sigo ${x.thread} na fase ${x.fase}${naMaquina(x.maquina)}.`)
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
  }
@@ -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
- { origem: `claude agents --json${opcoes.todas ? ' --all' : ''}`, ok: claude.ok, detalhe: claude.detalhe },
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 ${v.commit.slice(0, 8)} ` +
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() : 'desconhecido',
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.slice(0, 8)}`);
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 binario esta no PATH? */
70
- function noPath(bin) {
71
- const r = exec('which', [bin]);
72
- return r.ok ? r.stdout.trim() : null;
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() : 'desconhecido';
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.slice(0, 8)} de ${(0, horario_1.formatarDataHoraRotulada)(r.baseline.gravadaEm)}` : '(nao gravada: falha nao pode ser chamada de regressao)'}`);
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 contagem = git('rev-list', '--count', 'HEAD', '--not', ...conhecidas, shaBase);
218
- const proprios = contagem.ok && /^\d+$/.test(contagem.stdout.trim()) ? Number(contagem.stdout.trim()) : null;
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', detalhe, correcao: `reaplique so os commits da thread sobre a base nova: ${onto}` };
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}`);
@@ -29,7 +29,7 @@
29
29
  "assercoes": [
30
30
  {
31
31
  "tipo": "contem",
32
- "valor": "Valores secretos ficam em",
32
+ "valor": "Valores secretos ficam no ambiente do",
33
33
  "porque": "Âncora operacional fora da tabela, removê-la precisa reprovar o corpus."
34
34
  },
35
35
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkastery/cli",
3
- "version": "0.5.0",
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 em
28
- `~/.hermes/.env`; não leia esse arquivo nem leve credenciais para conversa, onboarding ou ledger.
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, ou origem igual a branch base
42
- do projeto, reprova antes de o `ork` tocar no remoto. Arvore de destino ocupada sai como
43
- `tree.blocked`; lease tomado por outra thread sai como `lease.busy`.
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