@orkastery/cli 0.5.0 → 0.5.3

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 (131) hide show
  1. package/adapters/claude-code/README.md +11 -2
  2. package/adapters/claude-code/agents/ork-master.md +2 -0
  3. package/adapters/claude-code/commands/master.md +8 -0
  4. package/adapters/claude-code/commands/onboarding.md +3 -2
  5. package/adapters/claude-code/commands/ork.md +9 -0
  6. package/adapters/codex/skills/ork/SKILL.md +18 -3
  7. package/adapters/hermes/README.md +1 -0
  8. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  9. package/adapters/hermes/hermes.plugin.json +1 -0
  10. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  11. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
  12. package/adapters/openclaw/README.md +35 -4
  13. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  14. package/adapters/openclaw/dist/index.js +47 -12
  15. package/adapters/openclaw/openclaw.plugin.json +5 -1
  16. package/adapters/openclaw/package.json +2 -2
  17. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  18. package/adapters/openclaw/src/index.ts +50 -12
  19. package/assets/orkmind_bridge.py +117 -9
  20. package/dist/adapters/claude-bg.js +59 -11
  21. package/dist/adapters/codex.js +4 -3
  22. package/dist/agents-md.js +1 -0
  23. package/dist/auditrun.js +6 -2
  24. package/dist/board.js +16 -14
  25. package/dist/branch-de-estado.js +66 -10
  26. package/dist/busca-semantica.js +52 -12
  27. package/dist/canarios-rm057.js +149 -0
  28. package/dist/canarios.js +2 -0
  29. package/dist/ci.js +8 -1
  30. package/dist/claim-lint.js +3 -1
  31. package/dist/claims.js +39 -1
  32. package/dist/company-brain-context.js +158 -22
  33. package/dist/docs.js +110 -3
  34. package/dist/doctor-pulse-cron.js +194 -0
  35. package/dist/doctor-rede.js +149 -0
  36. package/dist/doctor.js +294 -37
  37. package/dist/entrega-pr.js +27 -8
  38. package/dist/esgotamentos.js +181 -0
  39. package/dist/experiencia.js +12 -2
  40. package/dist/fabrica-estado.js +35 -9
  41. package/dist/fabrica-publicar.js +6 -0
  42. package/dist/fechamento.js +20 -66
  43. package/dist/fix.js +3 -3
  44. package/dist/forja.js +31 -7
  45. package/dist/gates.js +5 -1
  46. package/dist/hitl-contract.js +59 -4
  47. package/dist/hitl-curto.js +11 -6
  48. package/dist/hitl-gates.js +5 -0
  49. package/dist/hitl-lote.js +5 -2
  50. package/dist/hitl-resumo.js +33 -1
  51. package/dist/hitl-tempo-parado.js +201 -0
  52. package/dist/hitl-texto-livre.js +5 -5
  53. package/dist/horario.js +27 -0
  54. package/dist/hosts.js +59 -13
  55. package/dist/impedimento.js +205 -0
  56. package/dist/index.js +433 -88
  57. package/dist/indice-vetorial.js +87 -10
  58. package/dist/indice.js +3 -1
  59. package/dist/init.js +28 -5
  60. package/dist/integracoes-locais.js +4 -0
  61. package/dist/intelligence-graph-cli.js +267 -30
  62. package/dist/intelligence-graph-contexto.js +215 -0
  63. package/dist/intelligence-graph-contract.js +71 -12
  64. package/dist/intelligence-graph-extract-md.js +41 -23
  65. package/dist/intelligence-graph-extract-ts.js +181 -11
  66. package/dist/intelligence-graph-extract.js +186 -9
  67. package/dist/intelligence-graph-index.js +205 -24
  68. package/dist/intelligence-graph-parsers.js +6 -4
  69. package/dist/intelligence-graph-repo.js +10 -0
  70. package/dist/leases.js +680 -87
  71. package/dist/ledger-stats.js +15 -1
  72. package/dist/ledger.js +12 -0
  73. package/dist/licoes.js +6 -4
  74. package/dist/liveness.js +2 -1
  75. package/dist/maestro-runtime.js +7 -1
  76. package/dist/manifest.js +44 -1
  77. package/dist/master-nota.js +14 -5
  78. package/dist/master.js +129 -12
  79. package/dist/mcp-artifacts.js +36 -3
  80. package/dist/mcp-git.js +80 -15
  81. package/dist/mcp-grafo-worker.js +93 -0
  82. package/dist/mcp-grafo.js +279 -0
  83. package/dist/mcp-install.js +2 -1
  84. package/dist/mcp-server.js +23 -1
  85. package/dist/mcp-ship.js +2 -0
  86. package/dist/mcp-verify.js +3 -1
  87. package/dist/memoria.js +48 -6
  88. package/dist/network-roadmap.js +195 -20
  89. package/dist/ocupacao.js +17 -5
  90. package/dist/onboarding.js +9 -4
  91. package/dist/orkmind.js +90 -5
  92. package/dist/orquestracao.js +14 -11
  93. package/dist/parado-no-condutor.js +1089 -0
  94. package/dist/phase.js +68 -15
  95. package/dist/policies.js +79 -3
  96. package/dist/portfolio.js +4 -1
  97. package/dist/projeto-alvo.js +5 -1
  98. package/dist/prompts.js +36 -1
  99. package/dist/prova-ativacao.js +414 -0
  100. package/dist/pulse-delivery.js +60 -7
  101. package/dist/pulse.js +99 -3
  102. package/dist/ratelimit.js +20 -4
  103. package/dist/recall.js +3 -1
  104. package/dist/rede-adesao.js +399 -0
  105. package/dist/rede-forja.js +289 -0
  106. package/dist/rede-projetos.js +239 -0
  107. package/dist/rede-status.js +336 -0
  108. package/dist/rede.js +980 -0
  109. package/dist/retry.js +69 -2
  110. package/dist/roadmap-status.js +162 -7
  111. package/dist/runtime-context.js +5 -1
  112. package/dist/runtime-profiles.js +22 -1
  113. package/dist/saida-segura.js +58 -0
  114. package/dist/session-watcher-claude.js +113 -23
  115. package/dist/sessoes-contas.js +187 -0
  116. package/dist/sessoes-inventario.js +6 -12
  117. package/dist/sessoes.js +87 -3
  118. package/dist/ship.js +110 -20
  119. package/dist/thread.js +212 -14
  120. package/dist/util.js +71 -4
  121. package/dist/verify.js +2 -2
  122. package/dist/worktree.js +34 -8
  123. package/eval/casos/onboarding.json +1 -1
  124. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  125. package/package.json +7 -3
  126. package/references/definition-of-done.md +1 -1
  127. package/skills/README.md +1 -1
  128. package/skills/core/onboarding/SKILL.md +3 -2
  129. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  130. package/skills/phases/master-metrics/SKILL.md +8 -3
  131. package/skills/phases/ship-release/SKILL.md +6 -3
package/dist/pulse.js CHANGED
@@ -13,6 +13,7 @@ const liveness_1 = require("./liveness");
13
13
  const master_1 = require("./master");
14
14
  const master_batch_1 = require("./master-batch");
15
15
  const orquestracao_1 = require("./orquestracao");
16
+ const impedimento_1 = require("./impedimento");
16
17
  const thread_1 = require("./thread");
17
18
  const sessoes_adopt_1 = require("./sessoes-adopt");
18
19
  const ledger_1 = require("./ledger");
@@ -26,6 +27,9 @@ const decisao_autonoma_1 = require("./decisao-autonoma");
26
27
  const docs_1 = require("./docs");
27
28
  const conducao_1 = require("./conducao");
28
29
  const conducao_texto_1 = require("./conducao-texto");
30
+ const hitl_resumo_1 = require("./hitl-resumo");
31
+ const hitl_tempo_parado_1 = require("./hitl-tempo-parado");
32
+ const parado_no_condutor_1 = require("./parado-no-condutor");
29
33
  exports.CONTRATO_PULSE = 'ork.pulse/v1';
30
34
  /** Uma semana: o resumo cita a decisao uma vez, e ela continua lida ate sair da janela. */
31
35
  exports.JANELA_DAS_DECISOES_MS = 7 * 24 * 60 * 60 * 1000;
@@ -47,6 +51,17 @@ function apresentacaoCurtaDoItem(pedido, desde, quando) {
47
51
  function comporPulse(carregado, entrada) {
48
52
  const { radar, monitor, batch, orfas } = entrada, quando = radar.consultadoEm;
49
53
  const humanos = [], automaticas = [];
54
+ // RM-037 (fatia 4): o fim de turno sem pergunta e do condutor. Ele sai da fila do dono aqui e volta
55
+ // como linha propria (`paradoNoCondutor`); leitura que falha nao esconde nada do dono.
56
+ let entregas = entrada.entregas;
57
+ if (!entregas) {
58
+ try {
59
+ entregas = (0, parado_no_condutor_1.entregasDoProjeto)(carregado, { quando, sessoes: radar.sessoes });
60
+ }
61
+ catch {
62
+ entregas = undefined;
63
+ }
64
+ }
50
65
  const registros = new Map();
51
66
  const ehRegistro = (id) => {
52
67
  if (!id)
@@ -79,6 +94,11 @@ function comporPulse(carregado, entrada) {
79
94
  };
80
95
  const TERMINAIS = ['failed', 'done', 'stopped', 'completed'];
81
96
  for (const s of radar.sessoes.filter(s => s.precisaDeHumano)) {
97
+ // RM-037 (fatia 4): sessao `blocked` de quem o turno acabou sem pergunta e do condutor: a que o ledger provou
98
+ // (Stop sem atividade depois; a lista numerada da mensagem final nao e menu) e a sobra sem menu na tela.
99
+ if (entregas?.doCondutor.sessoes.has(s.sessionId) &&
100
+ ((0, parado_no_condutor_1.sessaoSemPergunta)(s) || entregas.doCondutor.turnosEncerrados.has(s.sessionId)))
101
+ continue;
82
102
  // Job morto ou estado terminal é história. Bloqueio vivo ou desconhecido continua visível.
83
103
  if (ehRegistro(s.thread?.id ?? null) && s.jobVivo !== true &&
84
104
  (s.jobVivo === false || TERMINAIS.includes(s.estadoBruto)))
@@ -123,6 +143,9 @@ function comporPulse(carregado, entrada) {
123
143
  for (const p of l.paradas) {
124
144
  if (VERIFICACAO.includes(p.motivo) && jaMesclada(l.thread))
125
145
  continue;
146
+ // RM-037 (fatia 4): o `human.pending` que o observador grava no fim de turno sem pergunta nao e do dono.
147
+ if (p.motivo === 'human.pending' && p.fonte === 'ledger' && entregas?.doCondutor.gates.has(`${l.thread}|${p.fase}`))
148
+ continue;
126
149
  if (ehRegistro(l.thread)) {
127
150
  if (p.fonte === 'escalonador' || ['lease.busy', 'conducao.em-andamento', 'concurrency.limite', 'vaga.stale', 'runtime.silencio'].includes(p.motivo))
128
151
  continue;
@@ -148,6 +171,19 @@ function comporPulse(carregado, entrada) {
148
171
  thread: l.thread, fase: p.fase, sessionId: null, desdeEm: p.desdeEm, paradaHaMin: p.paradaHaMin, impacto: impacto(p.fase),
149
172
  pergunta: p.detalhe, opcoes: [], recomendacao: p.correcao, comandoResposta: p.correcao, evidencia: [p.evidencia],
150
173
  fontes: ['monitor'], contextoLogs: [] };
174
+ // RM-055: o impedimento do despacho sai no contrato curto, com o comando exato e o re-despacho depois.
175
+ if (p.impedimento) {
176
+ try {
177
+ const curto = (0, impedimento_1.pedidoCurtoDoImpedimento)({ thread: l.thread, fase: p.fase, motivo: p.motivo, detalhe: p.detalhe,
178
+ desdeEm: p.desdeEm, impedimento: p.impedimento }, quando);
179
+ item.apresentacaoCurta = { telegram: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'telegram'), terminal: (0, hitl_curto_1.textoDoPedidoCurto)(curto, 'terminal') };
180
+ item.opcoes = curto.alternativas.map(a => `${a.chave}. ${a.texto}`);
181
+ item.comandoResposta = `ork retry run ${l.thread}`;
182
+ }
183
+ catch {
184
+ item.evidencia.push('Pedido curto do impedimento indisponível; a correção acima vale.');
185
+ }
186
+ }
151
187
  // I-45: vaga parada e sinal do escalonador (`ork board reap` devolve a vaga), nao pergunta ao dono.
152
188
  const esperaAutomatica = ['lease.busy', 'conducao.em-andamento', 'concurrency.limite', 'runtime.rate-limited', 'vaga.stale'].includes(p.motivo);
153
189
  (esperaAutomatica ? automaticas : humanos).push(item);
@@ -216,7 +252,8 @@ function comporPulse(carregado, entrada) {
216
252
  }
217
253
  }
218
254
  return { contrato: exports.CONTRATO_PULSE, consultadoEm: quando, runtime: { ok: radar.runtimeConsultado, detalhe: radar.runtimeDetalhe },
219
- precisaDeHumanoAgora: h, acoesAutomaticas: a, resumo: { humanos: h.length, automaticas: a.length, scores: batch.length, fasesOrfas: orfas.length } };
255
+ precisaDeHumanoAgora: h, acoesAutomaticas: a, resumo: { humanos: h.length, automaticas: a.length, scores: batch.length, fasesOrfas: orfas.length },
256
+ ...(entregas?.parados.length ? { paradoNoCondutor: entregas.parados } : {}) };
220
257
  }
221
258
  function montarPulse(carregado, opcoes = {}) {
222
259
  if (opcoes.registrar)
@@ -243,7 +280,42 @@ function montarPulse(carregado, opcoes = {}) {
243
280
  }
244
281
  }
245
282
  const monitor = (0, orquestracao_1.montarMonitor)(carregado, { agora: quando, estados });
246
- const pulse = comporPulse(carregado, { radar, monitor, orfas, batch: (0, master_1.pendentesDeScore)(carregado.raiz) });
283
+ // RM-037 (fatia 4, D2): a forja so e lida quando alguma thread tem branch publicada; a leitura boa vira o
284
+ // retrato que o status do roadmap le sem rede. Falha de leitura e diagnostico, nunca "sem PR".
285
+ let entregas;
286
+ try {
287
+ entregas = (0, parado_no_condutor_1.entregasDoProjeto)(carregado, { quando, sessoes: radar.sessoes, lerPrs: (candidatas) => {
288
+ const leitura = (0, parado_no_condutor_1.lerPrsDaForja)(carregado, { quando, executor: opcoes.executorDoGh, candidatas });
289
+ if (leitura.ok) {
290
+ try {
291
+ (0, parado_no_condutor_1.gravarRetratoDePrs)(carregado.raiz, leitura.retrato);
292
+ }
293
+ catch { /* o retrato e economia do status */ }
294
+ try {
295
+ (0, parado_no_condutor_1.esquecerForjaSemLeitura)(carregado.raiz);
296
+ }
297
+ catch { /* sem a marca, a proxima perda de leitura e dita de novo */ }
298
+ }
299
+ return leitura;
300
+ } });
301
+ const leitura = entregas.prs;
302
+ if (leitura && !leitura.ok && leitura.semLeitura) {
303
+ // RM-037 (fatia 5, A5): a forja sem leitura de PR e dita uma vez por remoto e host; as batidas seguintes nao repetem.
304
+ let novidade = true;
305
+ try {
306
+ novidade = (0, parado_no_condutor_1.avisarForjaSemLeitura)(carregado.raiz, leitura.semLeitura, leitura.erro, quando);
307
+ }
308
+ catch { /* sem marca, diz de novo */ }
309
+ if (novidade)
310
+ diagnosticos.push(`prs.sem-leitura: ${leitura.erro.slice(0, 160)}`);
311
+ }
312
+ else if (leitura && !leitura.ok)
313
+ diagnosticos.push(`prs.nao-lidos: ${leitura.erro.slice(0, 160)}`);
314
+ }
315
+ catch (e) {
316
+ diagnosticos.push(`entregas.indisponiveis: ${e.message.slice(0, 120)}`);
317
+ }
318
+ const pulse = comporPulse(carregado, { radar, monitor, orfas, batch: (0, master_1.pendentesDeScore)(carregado.raiz), ...(entregas ? { entregas } : {}) });
247
319
  const conducoes = monitor.linhas.filter((l) => l.conducao).map((l) => ({ thread: l.thread, linha: (0, conducao_texto_1.linhaDeConducao)(l.conducao, { agora: quando }) }));
248
320
  if (conducoes.length > 0)
249
321
  pulse.conducoes = conducoes;
@@ -256,6 +328,15 @@ function montarPulse(carregado, opcoes = {}) {
256
328
  catch (e) {
257
329
  diagnosticos.push(`decisoes.indisponiveis: ${e.message.slice(0, 120)}`);
258
330
  }
331
+ // RM-057 (fatia 3): leitura pura do ledger; falhar aqui nunca derruba a fila de atencao.
332
+ try {
333
+ const hitl = (0, hitl_tempo_parado_1.lerHitlDeConducaoAgora)(carregado.raiz, quando);
334
+ if (hitl.abertas.length || hitl.seteDias.pedidos || hitl.seteDias.respondidos)
335
+ pulse.hitlDeConducao = hitl;
336
+ }
337
+ catch (e) {
338
+ diagnosticos.push(`hitl-conducao.indisponivel: ${e.message.slice(0, 120)}`);
339
+ }
259
340
  if (diagnosticos.length)
260
341
  pulse.runtime.detalhe = [pulse.runtime.detalhe, ...diagnosticos].filter(Boolean).join('; ');
261
342
  return pulse;
@@ -264,8 +345,10 @@ function textoDoPulse(p) {
264
345
  // I-35: o pulse guarda ISO; o texto ao dono sai no fuso dele, rotulado no cabeçalho.
265
346
  const local = (texto) => (0, horario_1.localizarTexto)(texto, { agora: p.consultadoEm });
266
347
  const linhas = [`Pulse (${p.contrato}) ${(0, horario_1.formatarDataHoraRotulada)(p.consultadoEm, { agora: p.consultadoEm })}`, `Precisa de humano agora: ${p.resumo.humanos}`, `${p.resumo.scores} entrega(s) sem nota, aceitas por padrão com registro`];
267
- if (p.runtime.ok && p.runtime.detalhe.includes('liveness.snapshot.invalid'))
348
+ // RM-037 (fatia 4): PR nao lido tambem e dito, para a falta de linha do condutor nao parecer "nada parado".
349
+ if (p.runtime.ok && ['liveness.snapshot.invalid', 'prs.nao-lidos', 'prs.sem-leitura', 'entregas.indisponiveis'].some(d => p.runtime.detalhe.includes(d))) {
268
350
  linhas.push(`[diagnostico] ${local(p.runtime.detalhe)}`);
351
+ }
269
352
  if (!p.runtime.ok)
270
353
  linhas.push(`[consulta incompleta] ${local(p.runtime.detalhe)}`);
271
354
  for (const i of p.precisaDeHumanoAgora) {
@@ -276,6 +359,19 @@ function textoDoPulse(p) {
276
359
  for (const c of p.conducoes)
277
360
  linhas.push(`${c.thread}: ${c.linha}`);
278
361
  }
362
+ // RM-037 (fatia 4): o que espera o condutor, uma linha por thread; nao e pergunta ao dono.
363
+ if (p.paradoNoCondutor?.length) {
364
+ linhas.push('', `Parado no condutor: ${p.paradoNoCondutor.length}`);
365
+ for (const x of p.paradoNoCondutor)
366
+ linhas.push((0, hitl_resumo_1.linhaDoParadoNoCondutor)(x, { agora: p.consultadoEm }));
367
+ }
368
+ // RM-057 (fatia 3): quanto tempo cada pergunta de conducao aberta ja parou a thread, e a mediana da semana.
369
+ if (p.hitlDeConducao) {
370
+ const h = p.hitlDeConducao;
371
+ linhas.push('', `HITL de condução: ${h.abertas.length} aberta(s); ${(0, hitl_tempo_parado_1.textoDaMediana)(h)}`);
372
+ for (const a of h.abertas)
373
+ linhas.push((0, hitl_tempo_parado_1.linhaDaPerguntaParada)(a, { agora: p.consultadoEm }));
374
+ }
279
375
  linhas.push('', `Ações automáticas pendentes: ${p.resumo.automaticas}`);
280
376
  for (const i of p.acoesAutomaticas)
281
377
  linhas.push(`${i.thread}: ${i.motivo}; ${i.comandoResposta}`);
package/dist/ratelimit.js CHANGED
@@ -61,6 +61,7 @@ exports.lerFilaDeRetomada = lerFilaDeRetomada;
61
61
  exports.janelaPadraoMs = janelaPadraoMs;
62
62
  exports.enfileirar = enfileirar;
63
63
  exports.prazoDaConta = prazoDaConta;
64
+ exports.prazoDaCotaVista = prazoDaCotaVista;
64
65
  exports.marcarContaDaFalha = marcarContaDaFalha;
65
66
  exports.gravarPedido = gravarPedido;
66
67
  exports.exigirPedido = exigirPedido;
@@ -151,15 +152,30 @@ function prazoDaConta(manifesto, falha, agoraMs = Date.now()) {
151
152
  return falha.resetEm;
152
153
  return new Date(agoraMs + janelaPadraoMs(manifesto)).toISOString();
153
154
  }
154
- /** Marca no store o perfil que falhou pela conta. Sem perfil (ambiente do processo), nada a marcar. */
155
- function marcarContaDaFalha(carregado, perfil, falha, agoraMs = Date.now()) {
155
+ /**
156
+ * RM-037 (fatia 5): ate quando a conta sai do rodizio pela cota que o observador viu na transcricao, com a
157
+ * sessao viva. Vale a hora dita (ja ancorada no instante da mensagem) ou a janela padrao contada da mensagem,
158
+ * nunca do relogio de quem leu. Prazo que ja venceu volta `null`: a conta ja voltou, e marcar agora tiraria do
159
+ * rodizio uma conta boa (a regra A10 do despacho recusado nao vale aqui, porque ninguem acabou de recusar nada).
160
+ */
161
+ function prazoDaCotaVista(manifesto, falha, mensagemMs, agoraMs = Date.now()) {
162
+ if (falha.motivo !== 'runtime.quota-exhausted' || !Number.isFinite(mensagemMs))
163
+ return null;
164
+ const prazo = falha.resetEm ?? new Date(mensagemMs + janelaPadraoMs(manifesto)).toISOString();
165
+ return Date.parse(prazo) > agoraMs ? prazo : null;
166
+ }
167
+ /**
168
+ * Marca no store o perfil que falhou pela conta. Sem perfil (ambiente do processo), nada a marcar. `prazo`,
169
+ * quando vem, e o ja calculado por quem viu a falha (`prazoDaCotaVista`); sem ele, vale `prazoDaConta`.
170
+ */
171
+ function marcarContaDaFalha(carregado, perfil, falha, agoraMs = Date.now(), prazo) {
156
172
  // RM-037 (defeitosdeco D-6): modelo inacessivel nao e falha da conta inteira; o perfil fica no rodizio.
157
173
  if (!perfil || falha.motivo === 'runtime.model-unavailable')
158
174
  return null;
159
175
  const esgotado = falha.motivo === 'runtime.quota-exhausted';
160
176
  return (0, runtime_profiles_1.marcarFalhaDePerfil)(carregado.raiz, perfil.id, { estado: esgotado ? 'esgotado' : 'sem-auth',
161
- esgotadoAte: prazoDaConta(carregado.manifesto, falha, agoraMs), motivo: falha.motivo, detalhe: falha.trecho,
162
- em: new Date(agoraMs).toISOString() });
177
+ esgotadoAte: esgotado && prazo ? prazo : prazoDaConta(carregado.manifesto, falha, agoraMs), motivo: falha.motivo,
178
+ detalhe: falha.trecho, em: new Date(agoraMs).toISOString() });
163
179
  }
164
180
  /** Regrava um pedido (append-only: a versao nova vence, o historico fica). */
165
181
  function gravarPedido(raiz, pedido) {
package/dist/recall.js CHANGED
@@ -257,7 +257,9 @@ function recallDaThread(carregado, memoria, threadId, opcoes = {}) {
257
257
  falhas.push({ id: 'memoria-busca', titulo: 'Descoberta de handoffs da fase', location: '',
258
258
  orkmind: null, retrieve_when: fase, resolvido: false, motivo: 'orkmind-indisponivel', via: null,
259
259
  conteudo: '', metodo: '', sha256: '', intervalo: null,
260
- detalhe: `${memoria.leituraRestrita ? falhaDeConsulta(erro) : 'memory.transport.export'}: busca semantica falhou; resultado nao comprova colecao vazia` });
260
+ // RM-038: a janela cheia do export tem codigo proprio; o resto segue como falha de transporte.
261
+ detalhe: `${memoria.leituraRestrita ? falhaDeConsulta(erro) : erro instanceof Error && erro.message === 'memory.query.window-saturated'
262
+ ? erro.message : 'memory.transport.export'}: busca semantica falhou; resultado nao comprova colecao vazia` });
261
263
  }
262
264
  }
263
265
  if (opcoes.id !== undefined && resolvidos.length === 0 && falhas.length === 0 && adiados.length === 0) {
@@ -0,0 +1,399 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ehIdDeMaquina = exports.pastaDaRede = exports.TETO_DE_TENTATIVA_MS = exports.REPOSITORIO_PADRAO = exports.CONTRATO_DA_ADESAO = void 0;
37
+ exports.lerConfigDaRede = lerConfigDaRede;
38
+ exports.gravarConfigDaRede = gravarConfigDaRede;
39
+ exports.adesaoDaRede = adesaoDaRede;
40
+ exports.lerIdDaMaquina = lerIdDaMaquina;
41
+ exports.idDaMaquina = idDaMaquina;
42
+ exports.idDerivado = idDerivado;
43
+ exports.publicacaoDesligada = publicacaoDesligada;
44
+ exports.tomarVezDePublicar = tomarVezDePublicar;
45
+ exports.publicarRedeEmSegundoPlano = publicarRedeEmSegundoPlano;
46
+ exports.registrarNaRede = registrarNaRede;
47
+ /**
48
+ * RM-053 (D2, D9): a adesao desta maquina a rede da pessoa, e o disparo em segundo plano.
49
+ *
50
+ * Modulo leve de proposito, como a `fabrica-publicar`: quem publica em segundo plano (thread nova,
51
+ * despacho de fase, entrega) chega aqui por ela e nao pode arrastar o retrato nem a forja.
52
+ *
53
+ * A adesao e da MAQUINA, em `~/.orkastery/rede.json` (contrato `ork.rede/v1`), gravado por
54
+ * `ork network entrar` e `ork network sair`. Sem esse arquivo, a maquina que ja fez
55
+ * `ork fabrica entrar` (`fabricaCompartilhada: true` em `maquina.json`) e membro HERDADO: entra na
56
+ * rede sem refazer nada. `ork network sair` grava `membro: false`, que vence a heranca. O manifesto
57
+ * do projeto nao conta: um time nao inscreve a maquina de ninguem na rede pessoal.
58
+ */
59
+ const node_crypto_1 = require("node:crypto");
60
+ const node_child_process_1 = require("node:child_process");
61
+ const fs = __importStar(require("node:fs"));
62
+ const os = __importStar(require("node:os"));
63
+ const path = __importStar(require("node:path"));
64
+ const maquina_1 = require("./maquina");
65
+ const monitor_lock_1 = require("./monitor-lock");
66
+ const util_1 = require("./util");
67
+ exports.CONTRATO_DA_ADESAO = 'ork.rede/v1';
68
+ exports.REPOSITORIO_PADRAO = 'orkastery-network';
69
+ /**
70
+ * D9: no maximo uma tentativa de publicacao em segundo plano (evento ou batida) a cada 14 minutos.
71
+ * Um minuto de folga sob o cron de 15: com o teto igual ao periodo, a oscilacao do horario de cada
72
+ * batida pulava metade delas (B4 do CHECK 1).
73
+ */
74
+ exports.TETO_DE_TENTATIVA_MS = 14 * 60 * 1000;
75
+ /** `~/.orkastery/rede`: o cache bare da casa, as marcas e o log da rede. */
76
+ const pastaDaRede = () => path.join((0, maquina_1.pastaDoUsuario)(), 'rede');
77
+ exports.pastaDaRede = pastaDaRede;
78
+ const arquivoDaConfig = () => path.join((0, maquina_1.pastaDoUsuario)(), 'rede.json');
79
+ const arquivoDaTentativa = () => path.join((0, exports.pastaDaRede)(), 'tentativa.json');
80
+ const IDENTIFICADOR = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
81
+ const HOST = /^[A-Za-z0-9.-]+(?::\d+)?$/;
82
+ const texto = (v, padrao) => typeof v === 'string' && padrao.test(v) ? v : null;
83
+ function lerConfigDaRede() {
84
+ try {
85
+ const bruto = JSON.parse(fs.readFileSync(arquivoDaConfig(), 'utf8'));
86
+ if (!bruto || bruto.contrato !== exports.CONTRATO_DA_ADESAO || typeof bruto.membro !== 'boolean')
87
+ return null;
88
+ return {
89
+ contrato: exports.CONTRATO_DA_ADESAO, membro: bruto.membro,
90
+ forja: bruto.forja === 'github' || bruto.forja === 'gitlab' ? bruto.forja : null,
91
+ host: texto(bruto.host, HOST), dono: texto(bruto.dono, IDENTIFICADOR), repositorio: texto(bruto.repositorio, IDENTIFICADOR),
92
+ atualizadoEm: typeof bruto.atualizadoEm === 'string' ? bruto.atualizadoEm : '',
93
+ };
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ /** Grava a adesao (troca atomica). Campo ausente na mudanca fica como estava. */
100
+ function gravarConfigDaRede(mudanca) {
101
+ const atual = lerConfigDaRede();
102
+ const config = {
103
+ contrato: exports.CONTRATO_DA_ADESAO,
104
+ membro: mudanca.membro ?? atual?.membro ?? false,
105
+ forja: mudanca.forja !== undefined ? mudanca.forja : atual?.forja ?? null,
106
+ host: mudanca.host !== undefined ? mudanca.host : atual?.host ?? null,
107
+ dono: mudanca.dono !== undefined ? mudanca.dono : atual?.dono ?? null,
108
+ repositorio: mudanca.repositorio !== undefined ? mudanca.repositorio : atual?.repositorio ?? null,
109
+ atualizadoEm: new Date().toISOString(),
110
+ };
111
+ if (config.host !== null && !HOST.test(config.host))
112
+ throw new Error(`rede.config: host invalido ("${config.host}")`);
113
+ for (const [campo, valor] of [['dono', config.dono], ['repositorio', config.repositorio]]) {
114
+ if (valor !== null && !IDENTIFICADOR.test(valor))
115
+ throw new Error(`rede.config: ${campo} invalido ("${valor}")`);
116
+ }
117
+ const dir = (0, maquina_1.pastaDoUsuario)();
118
+ fs.mkdirSync(dir, { recursive: true });
119
+ const temp = path.join(dir, `.rede.json.${process.pid}.tmp`);
120
+ fs.writeFileSync(temp, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
121
+ fs.renameSync(temp, arquivoDaConfig());
122
+ return config;
123
+ }
124
+ /** Esta maquina esta na rede? Explicita pelo `rede.json`, senao herdada da fabrica. */
125
+ function adesaoDaRede() {
126
+ const config = lerConfigDaRede();
127
+ if (config)
128
+ return { membro: config.membro, adesao: config.membro ? 'rede' : null, config };
129
+ const herdada = (0, maquina_1.lerConfigDaMaquina)()?.fabricaCompartilhada === true;
130
+ return { membro: herdada, adesao: herdada ? 'fabrica' : null, config: null };
131
+ }
132
+ const ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
133
+ const arquivoDoId = () => path.join((0, maquina_1.pastaDoUsuario)(), 'maquina-id');
134
+ function lerId(arquivo) {
135
+ try {
136
+ const id = fs.readFileSync(arquivo, 'utf8').trim();
137
+ return ID.test(id) ? id : null;
138
+ }
139
+ catch {
140
+ return null;
141
+ }
142
+ }
143
+ /** O id desta instalacao, sem criar: `null` quando ela nunca publicou (a leitura nao cria estado). */
144
+ function lerIdDaMaquina() {
145
+ return lerId(arquivoDoId());
146
+ }
147
+ /**
148
+ * B7 do CHECK 1: um identificador aleatorio desta instalacao, criado na primeira publicacao. Vai no
149
+ * retrato para duas maquinas com o mesmo nome (hostname `ubuntu`, o corte em 64 caracteres) nao
150
+ * regravarem o arquivo uma da outra. Nao identifica pessoa.
151
+ *
152
+ * S3 da revisao 2: mora em `~/.orkastery/maquina-id`, ao lado do `maquina.json`, e NAO na pasta de
153
+ * cache `~/.orkastery/rede/`: apagar o cache nao pode fazer a maquina virar outra de si mesma.
154
+ * U6 da revisao 3: sem arquivo, `link` de um temporario cria so se nao existe (atomico).
155
+ * V5 da revisao 4: arquivo ruim e trocado por um id DERIVADO dele, o mesmo para todo processo que o
156
+ * viu. W7 e W8 da revisao 5: nenhum caminho expoe arquivo vazio, e link pendurado tambem e trocado.
157
+ * X6 do CHECK 6: sem hard link, e na troca do arquivo ruim, o id vem de uma reserva (`idReservado`),
158
+ * igual para todo host que divide a pasta; o derivado so fica para quando a reserva falha.
159
+ * Todo caminho devolve o que ficou gravado, nunca o que tentou gravar.
160
+ */
161
+ function idDaMaquina() {
162
+ const arquivo = arquivoDoId();
163
+ for (let tentativa = 0; tentativa < 3; tentativa++) {
164
+ const lido = lerId(arquivo);
165
+ if (lido)
166
+ return lido;
167
+ fs.mkdirSync((0, maquina_1.pastaDoUsuario)(), { recursive: true });
168
+ let st = null;
169
+ try {
170
+ st = fs.lstatSync(arquivo, { bigint: true });
171
+ }
172
+ catch (e) {
173
+ if (e.code !== 'ENOENT')
174
+ throw e;
175
+ }
176
+ if (st)
177
+ trocarIdInvalido(arquivo, st);
178
+ else
179
+ criarId(arquivo);
180
+ }
181
+ const persistido = lerId(arquivo);
182
+ if (!persistido)
183
+ throw new Error(`rede.id: nao consegui gravar nem ler ${arquivo}`);
184
+ return persistido;
185
+ }
186
+ function gravarTemporario(conteudo) {
187
+ const temporario = path.join((0, maquina_1.pastaDoUsuario)(), `.maquina-id.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
188
+ fs.writeFileSync(temporario, conteudo, { mode: 0o600 });
189
+ return temporario;
190
+ }
191
+ /** Troca o que estiver no caminho (arquivo ou link) por um arquivo inteiro com o id: `rename` e atomico. */
192
+ function trocarPor(arquivo, id) {
193
+ const temporario = gravarTemporario(id);
194
+ try {
195
+ fs.renameSync(temporario, arquivo);
196
+ }
197
+ finally {
198
+ fs.rmSync(temporario, { force: true });
199
+ }
200
+ }
201
+ const SEM_HARD_LINK = new Set(['EPERM', 'ENOTSUP', 'EOPNOTSUPP', 'ENOSYS']);
202
+ /** Cria o id so se o arquivo nao existe; perdeu a corrida, fica o do outro. */
203
+ function criarId(arquivo) {
204
+ const temporario = gravarTemporario((0, node_crypto_1.randomUUID)());
205
+ try {
206
+ fs.linkSync(temporario, arquivo);
207
+ return;
208
+ }
209
+ catch (e) {
210
+ const codigo = e.code ?? '';
211
+ if (codigo === 'EEXIST')
212
+ return;
213
+ if (!SEM_HARD_LINK.has(codigo))
214
+ throw e;
215
+ }
216
+ finally {
217
+ fs.rmSync(temporario, { force: true });
218
+ }
219
+ // W7 da revisao 5: sistema de arquivos sem hard link (vboxsf, alguns FUSE e SMB). Nada de arquivo
220
+ // vazio nem de espera por relogio. X6 do CHECK 6: o id sai da reserva, o mesmo para todo processo
221
+ // de todo host que divide a pasta; so se a reserva falhar, o id DERIVADO da pasta e do boot.
222
+ const reservado = idReservado('maquina-id.reserva');
223
+ if (reservado)
224
+ return trocarPor(arquivo, reservado);
225
+ const pasta = fs.statSync((0, maquina_1.pastaDoUsuario)(), { bigint: true });
226
+ trocarPor(arquivo, idDerivado(['novo', pasta.dev, pasta.ino]));
227
+ }
228
+ /**
229
+ * X6 do CHECK 6: cria-se-ausente sem hard link, sem relogio e sem arquivo vazio. A pasta temporaria
230
+ * ganha o id inteiro antes do `rename`; `rename` de pasta sobre pasta NAO vazia falha (ENOTEMPTY ou
231
+ * EEXIST no POSIX, EPERM no Windows), entao so a primeira entra, e todo processo, de qualquer host ou
232
+ * conteiner que divida `~/.orkastery`, le dela o mesmo id. A reserva fica: e ela que responde ao lento.
233
+ * Clones de uma imagem (W6) tem disco proprio e criam cada um a sua. Qualquer falha devolve `null`, e
234
+ * quem chamou usa o id derivado de antes.
235
+ */
236
+ function idReservado(nome) {
237
+ const reserva = path.join((0, maquina_1.pastaDoUsuario)(), nome);
238
+ const jaReservado = lerId(path.join(reserva, 'id'));
239
+ if (jaReservado)
240
+ return jaReservado;
241
+ const temporaria = path.join((0, maquina_1.pastaDoUsuario)(), `.${nome}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
242
+ try {
243
+ fs.mkdirSync(temporaria, { mode: 0o700 });
244
+ fs.writeFileSync(path.join(temporaria, 'id'), (0, node_crypto_1.randomUUID)(), { mode: 0o600 });
245
+ fs.renameSync(temporaria, reserva);
246
+ }
247
+ catch { /* outro entrou antes (ou a pasta recusa): vale o que estiver na reserva */ }
248
+ finally {
249
+ fs.rmSync(temporaria, { recursive: true, force: true });
250
+ }
251
+ return lerId(path.join(reserva, 'id'));
252
+ }
253
+ /** O arquivo ruim (vazio, corrompido, link pendurado) vira o id derivado dele; o que mudou no meio, quem chamou rele. */
254
+ function trocarIdInvalido(arquivo, st) {
255
+ let conteudo;
256
+ if (st.isSymbolicLink()) {
257
+ // W8 da revisao 5: o link pendurado (ou para arquivo ruim) sai; o alvo fica como esta. X2 da revisao
258
+ // 6: se outro processo ja o trocou (ou tirou) depois do `lstat`, quem chamou rele.
259
+ let alvo;
260
+ try {
261
+ alvo = fs.readlinkSync(arquivo);
262
+ }
263
+ catch (e) {
264
+ if (['ENOENT', 'EINVAL'].includes(e.code ?? ''))
265
+ return;
266
+ throw e;
267
+ }
268
+ conteudo = Buffer.from(`link:${alvo}`);
269
+ }
270
+ else if (st.isFile()) {
271
+ let fd;
272
+ try {
273
+ fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
274
+ }
275
+ catch (e) {
276
+ if (['ENOENT', 'ELOOP'].includes(e.code ?? ''))
277
+ return;
278
+ throw e;
279
+ }
280
+ try {
281
+ // Conteudo e metadados do MESMO inode: o `fstat` do descritor aberto, nao do caminho.
282
+ st = fs.fstatSync(fd, { bigint: true });
283
+ const buffer = Buffer.alloc(4096);
284
+ conteudo = buffer.subarray(0, fs.readSync(fd, buffer, 0, buffer.length, 0));
285
+ }
286
+ finally {
287
+ fs.closeSync(fd);
288
+ }
289
+ if (ID.test(conteudo.toString('utf8').trim()))
290
+ return;
291
+ }
292
+ else {
293
+ throw new Error(`rede.id: ${arquivo} nao e um arquivo; apague-o e rode de novo`);
294
+ }
295
+ // X6 do CHECK 6: a reserva da troca e chaveada pelo que todo host ve igual do arquivo ruim (conteudo,
296
+ // tamanho e hora); `dev` e local a cada host e fica so no id derivado, o caminho de quando a reserva falha.
297
+ const sha = (0, node_crypto_1.createHash)('sha256').update(conteudo).digest('hex');
298
+ const chave = (0, node_crypto_1.createHash)('sha256').update([sha, st.size, st.mtimeNs].join('\0')).digest('hex').slice(0, 32);
299
+ trocarPor(arquivo, idReservado(`maquina-id.troca-${chave}`) ?? idDerivado([sha, st.dev, st.ino, st.mtimeNs, st.size]));
300
+ }
301
+ /**
302
+ * O id que todo processo desta maquina deriva do mesmo estado, agora. W6 da revisao 5: alem das
303
+ * partes (o arquivo ruim, ou a pasta), o hostname e o boot (`boot_id` e `machine-id`): VMs clonadas
304
+ * da mesma imagem, com o mesmo arquivo, inode e hostname, tem boot proprio e ganham ids diferentes.
305
+ * Formato de UUID v4.
306
+ */
307
+ function idDerivado(partes, fontes = fontesDaMaquina()) {
308
+ const h = (0, node_crypto_1.createHash)('sha256').update([...partes.map(String), ...fontes].join('\0')).digest('hex');
309
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-${'89ab'[parseInt(h[16], 16) & 3]}${h.slice(17, 20)}-${h.slice(20, 32)}`;
310
+ }
311
+ function fontesDaMaquina() {
312
+ const ler = (arquivo) => { try {
313
+ return fs.readFileSync(arquivo, 'utf8').trim();
314
+ }
315
+ catch {
316
+ return '';
317
+ } };
318
+ return [os.hostname(), ler('/proc/sys/kernel/random/boot_id'), ler('/etc/machine-id')];
319
+ }
320
+ const ehIdDeMaquina = (v) => typeof v === 'string' && ID.test(v);
321
+ exports.ehIdDeMaquina = ehIdDeMaquina;
322
+ /** Publicacao automatica desligada pelo ambiente (testes, `ork eval`, demo). */
323
+ function publicacaoDesligada(env = process.env) {
324
+ return env.ORK_REDE_PUBLICAR === '0' || env.ORK_FABRICA_PUBLICAR === '0';
325
+ }
326
+ /**
327
+ * D9: toma a vez de tentar publicar, no maximo uma a cada 14 minutos por maquina. Devolve `false`
328
+ * quando a ultima tentativa (evento ou batida) foi ha menos que isso; a marca e gravada antes da
329
+ * tentativa, para duas batidas simultaneas nao dispararem juntas. Revisao de 03/10: a leitura e a
330
+ * gravacao da marca correm sob uma trava (quem nao a pega perde a vez) e a marca troca por rename;
331
+ * sem isso, varios processos na mesma batida liam a marca velha e todos tomavam a vez.
332
+ */
333
+ function tomarVezDePublicar(agoraPedido) {
334
+ try {
335
+ fs.mkdirSync((0, exports.pastaDaRede)(), { recursive: true });
336
+ }
337
+ catch {
338
+ return false;
339
+ }
340
+ const trava = (0, monitor_lock_1.adquirirLockMonitor)(path.join((0, exports.pastaDaRede)(), 'tentativa.lock'));
341
+ if (!trava.ok)
342
+ return false;
343
+ // A hora e lida DENTRO da trava: lida antes, o processo que perdia a CPU entre a leitura e a trava
344
+ // via a marca do vencedor "no futuro", a tomava por relogio que voltou e tomava a vez tambem.
345
+ const agora = agoraPedido ?? Date.now();
346
+ try {
347
+ try {
348
+ const ultima = Date.parse(JSON.parse(fs.readFileSync(arquivoDaTentativa(), 'utf8')).em ?? '');
349
+ if (Number.isFinite(ultima) && agora - ultima < exports.TETO_DE_TENTATIVA_MS && agora >= ultima)
350
+ return false;
351
+ }
352
+ catch { /* primeira vez, ou marca ilegivel: tenta */ }
353
+ try {
354
+ // Dado de maquina: o JSON vai inteiro, sem concatenar o ISO em texto (lint de horario, RM-035).
355
+ const temporario = `${arquivoDaTentativa()}.${process.pid}.tmp`;
356
+ fs.writeFileSync(temporario, JSON.stringify({ em: new Date(agora).toISOString() }), { mode: 0o600 });
357
+ fs.renameSync(temporario, arquivoDaTentativa());
358
+ }
359
+ catch {
360
+ return false;
361
+ }
362
+ return true;
363
+ }
364
+ finally {
365
+ try {
366
+ trava.liberar();
367
+ }
368
+ catch { /* a trava de outro dono nao e desfeita aqui */ }
369
+ }
370
+ }
371
+ /**
372
+ * Evento de thread (criada, fase despachada, entregue, fechada): dispara `ork network publicar
373
+ * --silencioso` sem segurar quem chamou. So para membro, so fora do teto, nunca com a publicacao
374
+ * desligada pelo ambiente.
375
+ */
376
+ function publicarRedeEmSegundoPlano(opcoes = {}) {
377
+ const env = opcoes.env ?? process.env;
378
+ if (publicacaoDesligada(env) || !adesaoDaRede().membro)
379
+ return false;
380
+ const cli = opcoes.cli ?? path.join(__dirname, 'index.js');
381
+ if (!fs.existsSync(cli) || !tomarVezDePublicar(opcoes.agora))
382
+ return false;
383
+ try {
384
+ const filho = (0, node_child_process_1.spawn)(process.execPath, [cli, 'network', 'publicar', '--silencioso'], { cwd: opcoes.diretorio ?? process.cwd(), detached: true, stdio: 'ignore', env });
385
+ filho.unref();
386
+ return true;
387
+ }
388
+ catch {
389
+ return false;
390
+ }
391
+ }
392
+ /** Uma linha JSON por publicacao em segundo plano, em `~/.orkastery/rede/rede.log`: quem publica sozinho deixa rastro. */
393
+ function registrarNaRede(registro) {
394
+ try {
395
+ fs.mkdirSync((0, exports.pastaDaRede)(), { recursive: true });
396
+ fs.appendFileSync(path.join((0, exports.pastaDaRede)(), 'rede.log'), JSON.stringify({ ts: (0, util_1.agora)(), ...registro }) + '\n', { mode: 0o600 });
397
+ }
398
+ catch { /* o log e informativo */ }
399
+ }