@orkastery/cli 0.6.0 → 0.6.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 (65) hide show
  1. package/adapters/openclaw/openclaw.plugin.json +1 -1
  2. package/adapters/openclaw/package.json +1 -1
  3. package/dist/adapters/claude-bg.js +6 -6
  4. package/dist/adapters/grok-events.js +227 -0
  5. package/dist/adapters/grok-mcp-servidor.js +612 -0
  6. package/dist/adapters/grok-mcp.js +178 -0
  7. package/dist/adapters/grok.js +686 -0
  8. package/dist/agendador-cpu.js +92 -0
  9. package/dist/branch-de-estado.js +7 -2
  10. package/dist/check-agil.js +48 -20
  11. package/dist/check-paralelo.js +73 -18
  12. package/dist/conducao.js +5 -0
  13. package/dist/docs.js +9 -16
  14. package/dist/doctor-forja.js +1 -3
  15. package/dist/doctor.js +1 -1
  16. package/dist/entrega-pr.js +10 -10
  17. package/dist/fabrica-estado.js +20 -11
  18. package/dist/horario.js +4 -1
  19. package/dist/index.js +20 -10
  20. package/dist/integracoes-locais.js +2 -0
  21. package/dist/locale-accounts.js +11 -3
  22. package/dist/locale-ajuda.js +10 -6
  23. package/dist/locale-forja.js +2 -4
  24. package/dist/locale-operacao-restante.js +2 -2
  25. package/dist/locale-retry.js +40 -2
  26. package/dist/locale-ship.js +10 -2
  27. package/dist/manifest.js +6 -1
  28. package/dist/mcp-install.js +22 -12
  29. package/dist/mcp-server.js +42 -16
  30. package/dist/merge-da-entrega.js +85 -0
  31. package/dist/network-roadmap.js +18 -14
  32. package/dist/noctum-aceite.js +154 -14
  33. package/dist/noctum-cli.js +69 -12
  34. package/dist/noctum-entrega.js +55 -7
  35. package/dist/noctum-envelope.js +1 -1
  36. package/dist/noctum-execucao.js +5 -1
  37. package/dist/noctum-fechamento.js +18 -7
  38. package/dist/noctum-fila.js +6 -1
  39. package/dist/noctum-guarda.js +81 -4
  40. package/dist/noctum-maquinas.js +5 -1
  41. package/dist/noctum-modo.js +35 -7
  42. package/dist/noctum-reconciliacao.js +4 -1
  43. package/dist/noctum-rede.js +102 -0
  44. package/dist/noctum-relatorio.js +44 -10
  45. package/dist/noctum-retomada-rede.js +71 -0
  46. package/dist/noctum-vigia.js +49 -9
  47. package/dist/parado-no-condutor.js +14 -15
  48. package/dist/phase.js +43 -15
  49. package/dist/postura-local.js +7 -3
  50. package/dist/preflight.js +2 -1
  51. package/dist/ratelimit.js +1 -0
  52. package/dist/retry.js +103 -10
  53. package/dist/runtime-context.js +165 -45
  54. package/dist/runtime-escada.js +7 -1
  55. package/dist/runtime-profiles.js +27 -17
  56. package/dist/runtimes.js +42 -5
  57. package/dist/security-scan/agil.js +18 -7
  58. package/dist/security-scan/executor.js +8 -0
  59. package/dist/session-watcher-grok.js +425 -0
  60. package/dist/session-watcher.js +19 -4
  61. package/dist/sessoes-contas.js +33 -5
  62. package/dist/setup.js +11 -11
  63. package/dist/varredura-acoes.js +8 -15
  64. package/dist/verify.js +26 -10
  65. package/package.json +2 -2
@@ -76,6 +76,7 @@ const fila_pesada_1 = require("./fila-pesada");
76
76
  const decisao_autonoma_1 = require("./decisao-autonoma");
77
77
  const conducao_texto_1 = require("./conducao-texto");
78
78
  const mcp_ship_1 = require("./mcp-ship");
79
+ const grok_1 = require("./adapters/grok");
79
80
  const company_brain_mcp_1 = require("./company-brain-mcp");
80
81
  const claude_bg_1 = require("./adapters/claude-bg");
81
82
  const session_watcher_claude_1 = require("./session-watcher-claude");
@@ -113,7 +114,8 @@ const modoDeConducao = zod_1.z.string().trim().min(1).max(40)
113
114
  const nova = zod_1.z.object({ nome: zod_1.z.string().trim().min(1).max(200),
114
115
  modo: modoDeConducao.optional() }).strict();
115
116
  const fase = daThread.extend({ fase: zod_1.z.enum(['GOAL', 'PLAN', 'GO', 'CHECK', 'SHIP', 'MASTER']),
116
- prompt: zod_1.z.string().min(1).max(65536), runtime: zod_1.z.enum(['claude-bg', 'codex']).optional(),
117
+ // RM-067 (F4b): o grok tambem, como o --runtime do CLI, sempre com o model dele.
118
+ prompt: zod_1.z.string().min(1).max(65536), runtime: zod_1.z.enum(['claude-bg', 'codex', 'grok']).optional(),
117
119
  model: zod_1.z.string().trim().min(1).max(200).optional(),
118
120
  effort: zod_1.z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
119
121
  // RM-056 (D1): o perfil pedido, validado contra o store pelo mesmo caminho do CLI.
@@ -125,8 +127,11 @@ const resposta = (valor) => ({ content: [{ type: 'text', text: JSON.stringify(va
125
127
  function criarServidorMcp(opcoes) {
126
128
  if (!path.isAbsolute(opcoes.projeto))
127
129
  throw Error('mcp.project.invalid: raiz absoluta obrigatoria');
128
- if (!['claude-code', 'codex'].includes(opcoes.host))
130
+ if (!['claude-code', 'codex', 'grok'].includes(opcoes.host))
129
131
  throw Error('mcp.host.invalid');
132
+ // RM-067 (F4b, G1): o servidor do grok so serve a sessao filha; o despacho grava --thread na config da worktree.
133
+ if (opcoes.host === 'grok' && opcoes.threadId === undefined)
134
+ throw Error('mcp.host.child-only: --host grok serve so a sessao filha vinculada a uma thread (--thread), que o despacho do ork grava na config da worktree');
130
135
  if (opcoes.permissoesFilho !== undefined && !['interactive', 'worktree'].includes(opcoes.permissoesFilho))
131
136
  throw Error('mcp.child-permissions.invalid');
132
137
  if (opcoes.threadId !== undefined)
@@ -187,6 +192,18 @@ function criarServidorMcp(opcoes) {
187
192
  };
188
193
  if (opcoes.threadId)
189
194
  thread(opcoes.threadId);
195
+ // RM-067 (F4b, G1): o servidor do grok lista so as ferramentas do perfil filho; o headless nao aprova outra em dialogo.
196
+ const doGrok = opcoes.host === 'grok'
197
+ ? new Set((0, grok_1.ferramentasServidasAoGrok)(opcoes.permissoesFilho ?? 'interactive', (0, mcp_grafo_1.grafoLigado)((0, manifest_1.exigirManifesto)(raiz).manifesto))) : null;
198
+ // RM-067 (F4c, D4): a lista do despacho e um subconjunto da uniao do perfil filho; fora dela, ou sem o host grok, recusa.
199
+ if (opcoes.ferramentas !== undefined) {
200
+ const pedidas = opcoes.ferramentas;
201
+ if (!doGrok || pedidas.some(n => !doGrok.has(n)) || new Set(pedidas).size !== pedidas.length)
202
+ throw Error('mcp.tools.invalid: a lista do despacho sai do perfil filho do grok');
203
+ for (const nome of [...doGrok])
204
+ if (!pedidas.includes(nome))
205
+ doGrok.delete(nome);
206
+ }
190
207
  const pendencias = (id) => {
191
208
  const t = thread(id), eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id)), contexto = (0, hitl_gates_1.contextoHitl)(raiz, id);
192
209
  const pedidos = [(0, hitl_presentation_1.pedidoHitlAberto)(raiz, id, t.faseAtual, null),
@@ -195,7 +212,7 @@ function criarServidorMcp(opcoes) {
195
212
  // e so ele. Nenhum outro canal mcp-local sai `disponivel` daqui por suposicao.
196
213
  return pedidos.filter(p => p && eventos.some(e => e.tipo === 'hitl_requested' && e.contexto === contexto &&
197
214
  e.pedido?.id === p.id)).map(p => ({ pedido: p, estado: (0, hitl_contract_1.estadoDoPedido)(p),
198
- canais: (0, hitl_presentation_1.ofertaDoPedido)(p, [opcoes.host]), prazoLocal: (0, hitl_presentation_1.prazoLocalDoPedido)(p) }));
215
+ canais: (0, hitl_presentation_1.ofertaDoPedido)(p, opcoes.host === 'grok' ? [] : [opcoes.host]), prazoLocal: (0, hitl_presentation_1.prazoLocalDoPedido)(p) }));
199
216
  };
200
217
  const fases = (id) => {
201
218
  thread(id);
@@ -334,6 +351,8 @@ function criarServidorMcp(opcoes) {
334
351
  function registrarTool(nome, config, executar) {
335
352
  if (opcoes.threadId && ['ork_thread_new', 'ork_phase_run', 'ork_request_decision', 'ork_request_master_note', 'ork_preflight'].includes(nome))
336
353
  return;
354
+ if (doGrok && !doGrok.has(nome))
355
+ return;
337
356
  const entrada = config.inputSchema.extend({ projeto: campoProjeto.optional() });
338
357
  const schema = converterSchema(entrada, { $refStrategy: 'none' });
339
358
  ferramentas.set(nome, { tool: { name: nome, description: config.description, annotations: config.annotations,
@@ -347,6 +366,7 @@ function criarServidorMcp(opcoes) {
347
366
  }
348
367
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: [...ferramentas.values()].map(f => f.tool) }));
349
368
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request, extra) => {
369
+ opcoes.emCurso?.(1);
350
370
  try {
351
371
  const f = ferramentas.get(request.params.name);
352
372
  if (!f)
@@ -358,12 +378,16 @@ function criarServidorMcp(opcoes) {
358
378
  return { ...resposta(e.recusa), isError: true };
359
379
  return { ...resposta({ erro: e.message }), isError: true };
360
380
  }
381
+ finally {
382
+ opcoes.emCurso?.(-1);
383
+ }
361
384
  });
362
385
  const conexaoLocal = (0, node_crypto_1.randomUUID)();
363
386
  // RM-059 (F9, G3): a caixa de saida do Mensageiro de Sessoes so existe no servidor do dono do Claude Code, na memoria:
364
387
  // a resposta a uma sessao claude-bg que entra pelo dialogo deste servidor sai por ela, e o texto nunca vai a disco.
365
388
  const caixa = opcoes.host === 'claude-code' && !opcoes.threadId ? (0, entrega_por_mod_1.novaCaixaDeSaida)() : null;
366
- const ingresso = (0, hitl_local_1.criarIngressoLocal)(raiz, { host: opcoes.host, connectionId: conexaoLocal }, async (pedido, signal) => {
389
+ // RM-067 (F4b, G5): o headless do grok nao tem dialogo, e a sessao filha nao pede decisao nem nota: sem ingresso local.
390
+ const ingresso = opcoes.host === 'grok' ? null : (0, hitl_local_1.criarIngressoLocal)(raiz, { host: opcoes.host, connectionId: conexaoLocal }, async (pedido, signal) => {
367
391
  carregar();
368
392
  thread(pedido.thread);
369
393
  // D12: pergunta nativa que aceita texto NAO vira menu. Um menu sobre pergunta aberta
@@ -388,14 +412,14 @@ function criarServidorMcp(opcoes) {
388
412
  }, { caixa });
389
413
  // RM-059 (fatia F8, T8.1): a nota do MASTER pelo mesmo ingresso local. O formulario e fixo (a nota de 0 a 5 e o
390
414
  // porque); sem a capacidade de formulario no cliente, a recusa vem antes de qualquer escrita.
391
- const ingressoDaNota = (0, master_nota_local_1.criarIngressoDaNota)(raiz, { host: opcoes.host, connectionId: conexaoLocal }, async (apresentacao, signal) => {
415
+ const ingressoDaNota = opcoes.host === 'grok' ? null : (0, master_nota_local_1.criarIngressoDaNota)(raiz, { host: opcoes.host, connectionId: conexaoLocal }, async (apresentacao, signal) => {
392
416
  carregar();
393
417
  thread(apresentacao.thread);
394
418
  const r = await server.elicitInput({ mode: 'form', message: apresentacao.mensagem,
395
419
  requestedSchema: apresentacao.esquema }, { signal, timeout: 300000 });
396
420
  return r.action === 'accept' ? { action: 'accept', content: r.content } : { action: r.action };
397
421
  }, () => Boolean(server.getClientCapabilities()?.elicitation?.form));
398
- server.onclose = () => { ingresso.fechar(); ingressoDaNota.fechar(); };
422
+ server.onclose = () => { ingresso?.fechar(); ingressoDaNota?.fechar(); };
399
423
  (0, company_brain_mcp_1.registerBrainTools)(registrarTool, carregar, thread, opcoes.brainWrites ?? []);
400
424
  (0, mcp_experiencia_1.registrarConsultasExperiencia)(registrarTool, { raiz, carregar, transporte: opcoes.transporteShip ?? 'github-ssh' });
401
425
  // RM-026 K7 (fatia 2): o conhecimento escrito, só leitura, do projeto fixado; também na sessão filha vinculada.
@@ -595,16 +619,18 @@ function criarServidorMcp(opcoes) {
595
619
  livre(threadId);
596
620
  return resposta((0, hitl_gates_1.abrirPedidoGate)(raiz, threadId, motivo));
597
621
  });
598
- registrarTool('ork_request_decision', { description: 'Apresenta pedido existente ao dono por dialogo nativo. Somente IDs; nunca forneca resposta nos argumentos.',
599
- inputSchema: daDecisao, annotations: { readOnlyHint: false, destructiveHint: false } }, async ({ threadId, pedidoId }, extra) => {
600
- thread(threadId);
601
- return resposta(await ingresso.solicitar(threadId, pedidoId, extra.signal));
602
- });
603
- registrarTool('ork_request_master_note', { description: 'Pede ao dono, pelo dialogo nativo do host, a nota de 0 a 5 e o porque de uma entrega. Somente o threadId; nunca forneca nota nem porque nos argumentos.',
604
- inputSchema: daThread, annotations: { readOnlyHint: false, destructiveHint: false } }, async ({ threadId }, extra) => {
605
- thread(threadId);
606
- return resposta(await ingressoDaNota.solicitar(threadId, extra.signal));
607
- });
622
+ if (ingresso)
623
+ registrarTool('ork_request_decision', { description: 'Apresenta pedido existente ao dono por dialogo nativo. Somente IDs; nunca forneca resposta nos argumentos.',
624
+ inputSchema: daDecisao, annotations: { readOnlyHint: false, destructiveHint: false } }, async ({ threadId, pedidoId }, extra) => {
625
+ thread(threadId);
626
+ return resposta(await ingresso.solicitar(threadId, pedidoId, extra.signal));
627
+ });
628
+ if (ingressoDaNota)
629
+ registrarTool('ork_request_master_note', { description: 'Pede ao dono, pelo dialogo nativo do host, a nota de 0 a 5 e o porque de uma entrega. Somente o threadId; nunca forneca nota nem porque nos argumentos.',
630
+ inputSchema: daThread, annotations: { readOnlyHint: false, destructiveHint: false } }, async ({ threadId }, extra) => {
631
+ thread(threadId);
632
+ return resposta(await ingressoDaNota.solicitar(threadId, extra.signal));
633
+ });
608
634
  // RM-059 (F9, T9.1): o Mensageiro de Sessoes nas duas pontas. Nenhuma das duas entra no --allowedTools do modelo (D8):
609
635
  // quem chama e o mod ork-mensageiro, e nenhuma responde, aprova ou abre pedido.
610
636
  if (caixa)
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.branchRegistradaDaEntrega = branchRegistradaDaEntrega;
4
+ exports.mergesDasEntregasNaBase = mergesDasEntregasNaBase;
5
+ /**
6
+ * Reconhecimento unico do merge que entrega uma thread na linha de primeiro pai da base.
7
+ *
8
+ * A forja pode preservar o titulo `ship(<thread>)` no assunto ou usar sua mensagem padrao.
9
+ * Nos formatos padrao, a branch e o vinculo: ela precisa ser exatamente a branch registrada
10
+ * para a thread. O fallback pela convencao existe para o `docs verificar` no CI, que nao tem
11
+ * `.orkastery/`, e fecha o id antes de aceitar o sufixo da fase (`ork-abc` nao casa `ork-abcd`).
12
+ */
13
+ const branch_de_estado_1 = require("./branch-de-estado");
14
+ const ledger_1 = require("./ledger");
15
+ const thread_1 = require("./thread");
16
+ const util_1 = require("./util");
17
+ /** A branch da thread como foi registrada, preferindo o evento especifico da worktree. */
18
+ function branchRegistradaDaEntrega(raiz, thread) {
19
+ try {
20
+ const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread));
21
+ const worktree = [...eventos].reverse().find((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.worktreeCriada && (0, branch_de_estado_1.branchValida)(e.branch));
22
+ if (worktree)
23
+ return String(worktree.branch);
24
+ const criada = [...eventos].reverse().find((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.threadCriada);
25
+ const base = criada?.base;
26
+ if (base && typeof base === 'object' && !Array.isArray(base)) {
27
+ const branch = base.branch;
28
+ if ((0, branch_de_estado_1.branchValida)(branch))
29
+ return branch;
30
+ }
31
+ }
32
+ catch { /* checkout de CI ou thread sem estado local */ }
33
+ return null;
34
+ }
35
+ const branchConvencionalDaThread = (branch, thread) => {
36
+ const prefixo = `ork/${thread}`;
37
+ return branch === prefixo || branch.startsWith(`${prefixo}-`);
38
+ };
39
+ function threadDoAssunto(assunto, entregas, base) {
40
+ const ship = /^ship\(([^)]+)\)/.exec(assunto);
41
+ if (ship?.[1])
42
+ return ship[1];
43
+ const github = /^Merge pull request #[1-9][0-9]* from [^/\s]+\/(\S+)$/.exec(assunto);
44
+ const gitlab = /^Merge branch '(.+)' into '(.+)'$/.exec(assunto);
45
+ const branch = github?.[1] ?? (gitlab?.[2] === base ? gitlab[1] : null);
46
+ if (!branch)
47
+ return null;
48
+ return entregas.find((entrega) => entrega.branch === null
49
+ ? branchConvencionalDaThread(branch, entrega.thread)
50
+ : branch === entrega.branch)?.thread ?? null;
51
+ }
52
+ /**
53
+ * Merges das entregas na base, em ordem do git log (mais novo primeiro por omissao).
54
+ *
55
+ * O assunto e lido por `%s`, portanto uma citacao somente no corpo nao entra. `--first-parent`
56
+ * tira commits da branch lateral. O segundo pai continua opcional no retorno para preservar a
57
+ * recusa tipada `entrega.sem-vinculo` de squash/rebase quando a CI e obrigatoria.
58
+ */
59
+ function mergesDasEntregasNaBase(raiz, refBase, base, entregas, reverse = false) {
60
+ const saida = new Map(entregas.map((e) => [e.thread, []]));
61
+ if (!(0, branch_de_estado_1.branchValida)(refBase) || !(0, branch_de_estado_1.branchValida)(base))
62
+ return saida;
63
+ const args = ['log', refBase, '--first-parent', '--format=%H%x09%P%x09%cI%x09%s', ...(reverse ? ['--reverse'] : []), '--'];
64
+ const r = (0, util_1.exec)('git', args, raiz, 60000);
65
+ if (!r.ok)
66
+ return saida;
67
+ for (const linha of r.stdout.split('\n')) {
68
+ const [sha, pais, em, assunto] = linha.split('\t');
69
+ if (!(0, branch_de_estado_1.shaValido)(sha) || typeof assunto !== 'string')
70
+ continue;
71
+ const thread = threadDoAssunto(assunto, entregas, base);
72
+ if (!thread)
73
+ continue;
74
+ const segundo = (pais ?? '').split(' ')[1] ?? null;
75
+ const ms = Date.parse(em ?? '');
76
+ const merges = saida.get(thread) ?? [];
77
+ merges.push({
78
+ mergeSha: sha,
79
+ headSha: segundo && (0, branch_de_estado_1.shaValido)(segundo) ? segundo : null,
80
+ em: Number.isFinite(ms) ? new Date(ms).toISOString() : null,
81
+ });
82
+ saida.set(thread, merges);
83
+ }
84
+ return saida;
85
+ }
@@ -454,7 +454,7 @@ function maquinasDoPanorama(retratos, local, ctx, lacunas) {
454
454
  const idadeMs = Math.max(0, agora - Date.parse(m.publicadoEm));
455
455
  const semBatida = idadeMs > fabrica_estado_1.LIMIAR_SEM_BATIDA_MS;
456
456
  if (semBatida) {
457
- lacunas.push(lacuna('maquina.sem-batida', 'fabrica', m.maquina, `${m.maquina} sem retrato novo há ${(0, horario_1.duracaoCurta)(Math.floor(idadeMs / 60000))} (último em ${(0, horario_1.formatarDataHora)(m.publicadoEm, { fuso: ctx.fuso })}): ` +
457
+ lacunas.push(lacuna('maquina.sem-batida', 'fabrica', m.maquina, `${m.maquina} sem retrato novo há ${(0, horario_1.duracaoCurta)(Math.floor(idadeMs / 60000))} (último em ${(0, horario_1.formatarDataHora)(m.publicadoEm, { fuso: ctx.fuso, agora: ctx.quando })}): ` +
458
458
  'as threads dela podem ter andado', `confira a ${m.maquina}: ela publica ao criar thread, despachar fase, entregar e a cada batida do pulse`));
459
459
  }
460
460
  saida.push({ maquina: m.maquina, por: m.por, publicadoEm: m.publicadoEm, idadeMin: Math.floor(idadeMs / 60000), semBatida,
@@ -632,7 +632,7 @@ function lerProjetoDoClone(p, geral) {
632
632
  fontes.push({ parte, origem: 'clone', onde, ref: nome, commit: r.ponta, dataDoCommit, lidoEm: ctx.quando, atualizado: r.atualizado, existe: true });
633
633
  if (!r.atualizado) {
634
634
  lacunas.push(lacuna(`${parte}.sem-leitura`, parte, onde, `sem leitura nova de ${onde}${porque(r.tentou)}; ` +
635
- `vale a última cópia desta máquina, commit ${r.ponta.slice(0, 7)} de ${(0, horario_1.formatarDataHora)(dataDoCommit, { fuso: ctx.fuso })}`, correcao()));
635
+ `vale a última cópia desta máquina, commit ${r.ponta.slice(0, 7)} de ${(0, horario_1.formatarDataHora)(dataDoCommit, { fuso: ctx.fuso, agora: ctx.quando })}`, correcao()));
636
636
  }
637
637
  return r.ponta;
638
638
  }
@@ -1033,15 +1033,15 @@ function descreverProjeto(x) {
1033
1033
  function linhaDaFonte(f, quando, fuso) {
1034
1034
  const m = (0, locale_1.msg)().network.roadmap;
1035
1035
  const parte = m.fonteParte(f.parte);
1036
- const lido = f.lidoEm === quando ? m.lidoAgora : m.lidoEm((0, horario_1.formatarDataHora)(f.lidoEm, { fuso }));
1036
+ const lido = f.lidoEm === quando ? m.lidoAgora : m.lidoEm((0, horario_1.formatarDataHora)(f.lidoEm, { fuso, agora: quando }));
1037
1037
  if (f.origem === 'estado-local')
1038
1038
  return m.fonteLocal(parte, f.onde, lido);
1039
1039
  if (!f.existe)
1040
1040
  return m.fonteInexistente(parte, f.onde, lido);
1041
1041
  const alvo = f.parte === 'roadmap' ? m.alvoDoRoadmap(f.onde) : f.onde;
1042
- return m.fonteCommit(parte, alvo, (f.commit ?? '').slice(0, 7), (0, horario_1.formatarDataHora)(f.dataDoCommit, { fuso }), f.atualizado ? lido : m.ultimaCopia);
1042
+ return m.fonteCommit(parte, alvo, (f.commit ?? '').slice(0, 7), (0, horario_1.formatarDataHora)(f.dataDoCommit, { fuso, agora: quando }), f.atualizado ? lido : m.ultimaCopia);
1043
1043
  }
1044
- function linhasDasMaquinas(x, fuso) {
1044
+ function linhasDasMaquinas(x, quando, fuso) {
1045
1045
  const m = (0, locale_1.msg)().network.roadmap;
1046
1046
  if (x.maquinas === null)
1047
1047
  return [m.maquinasNaoLidas];
@@ -1051,11 +1051,11 @@ function linhasDasMaquinas(x, fuso) {
1051
1051
  const quem = `${m.maquina}${m.estaMaquina ? (0, locale_1.msg)().network.roadmap.estaMaquina : ''}`;
1052
1052
  // Fatia 3: a maquina so da rede por pessoa; as threads dela neste projeto nao foram lidas.
1053
1053
  if (m.origem === 'rede') {
1054
- return [(0, locale_1.msg)().network.roadmap.maquinaSoNaRede(quem, (0, horario_1.formatarDataHora)(m.publicadoEm, { fuso }), (0, horario_1.duracaoCurta)(m.idadeMin), m.semBatida, fabrica_estado_1.BRANCH_DA_FABRICA)];
1054
+ return [(0, locale_1.msg)().network.roadmap.maquinaSoNaRede(quem, (0, horario_1.formatarDataHora)(m.publicadoEm, { fuso, agora: quando }), (0, horario_1.duracaoCurta)(m.idadeMin), m.semBatida, fabrica_estado_1.BRANCH_DA_FABRICA)];
1055
1055
  }
1056
1056
  const rm = (0, locale_1.msg)().network.roadmap;
1057
1057
  const batida = m.origem === 'estado-local' ? rm.estadoLocalAgora
1058
- : rm.retrato((0, horario_1.formatarDataHora)(m.publicadoEm, { fuso }), (0, horario_1.duracaoCurta)(m.idadeMin), m.semBatida);
1058
+ : rm.retrato((0, horario_1.formatarDataHora)(m.publicadoEm, { fuso, agora: quando }), (0, horario_1.duracaoCurta)(m.idadeMin), m.semBatida);
1059
1059
  const cabeca = rm.resumoMaquina(quem, m.ativas.length, m.entreguesSemMaster, batida);
1060
1060
  // RM-037 (rm037noite, defeito 4): o runtime e o modelo de cada thread, quando o retrato os traz.
1061
1061
  return [cabeca, ...m.ativas.map((t) => ` ${t.id} · ${t.modo} · ${t.fase} · ${t.roadmap ?? rm.semItem}` +
@@ -1063,30 +1063,34 @@ function linhasDasMaquinas(x, fuso) {
1063
1063
  (t.esperaVoce ? rm.esperaVoce(t.pergunta ?? rm.veredito) : ''))];
1064
1064
  });
1065
1065
  }
1066
- function linhasDasReservas(x, fuso) {
1066
+ function linhasDasReservas(x, quando, fuso) {
1067
1067
  const m = (0, locale_1.msg)().network.roadmap;
1068
1068
  if (x.reservas === null)
1069
1069
  return [m.reservasNaoLidas];
1070
1070
  if (x.reservas.length === 0)
1071
1071
  return [m.nenhumaReserva];
1072
- return x.reservas.map((r) => m.reserva(r.item, r.maquina, r.thread ?? m.semThread, (0, horario_1.formatarDataHora)(r.desdeEm, { fuso })));
1072
+ return x.reservas.map((r) => m.reserva(r.item, r.maquina, r.thread ?? m.semThread, (0, horario_1.formatarDataHora)(r.desdeEm, { fuso, agora: quando })));
1073
1073
  }
1074
1074
  /** Fatia 3: a casa da rede por pessoa e as maquinas dela, com a batida e os projetos que declaram. */
1075
1075
  function linhasDaRede(r, quando, fuso) {
1076
1076
  const x = (0, locale_1.msg)().network.roadmap;
1077
- const lido = r.lidoEm === quando ? x.redeLidaAgora : x.redeLidaEm((0, horario_1.formatarDataHora)(r.lidoEm, { fuso }));
1077
+ const lido = r.lidoEm === quando ? x.redeLidaAgora : x.redeLidaEm((0, horario_1.formatarDataHora)(r.lidoEm, { fuso, agora: quando }));
1078
1078
  const casa = !r.casa ? x.casaAusente
1079
1079
  : r.atualizado === null ? x.casaNaoLida(r.casa)
1080
1080
  : x.casaLida(r.casa, r.ponta ? r.ponta.slice(0, 7) : x.semCommit, r.atualizado ? lido : x.casaUltimaCopia);
1081
1081
  if (r.membros.length === 0)
1082
1082
  return [casa, x.nenhumRetrato];
1083
- return [casa, ...r.membros.map((m) => x.membro(m.maquina, m.estaMaquina ? x.estaMaquina : '', (0, horario_1.formatarDataHora)(m.publicadoEm, { fuso }), m.idadeMin === null ? x.batidaIlegivel : x.idade((0, horario_1.duracaoCurta)(m.idadeMin)), m.semBatida, m.versaoOrk ? x.versao(m.versaoOrk) : '', m.projetos.length ? x.projetos(m.projetos.join(', ')) : x.nenhumProjeto))];
1083
+ return [casa, ...r.membros.map((m) => x.membro(m.maquina, m.estaMaquina ? x.estaMaquina : '', (0, horario_1.formatarDataHora)(m.publicadoEm, { fuso, agora: quando }), m.idadeMin === null ? x.batidaIlegivel : x.idade((0, horario_1.duracaoCurta)(m.idadeMin)), m.semBatida, m.versaoOrk ? x.versao(m.versaoOrk) : '', m.projetos.length ? x.projetos(m.projetos.join(', ')) : x.nenhumProjeto))];
1084
1084
  }
1085
1085
  const linhaDaLacuna = (l) => {
1086
1086
  const m = (0, locale_1.msg)().network.roadmap;
1087
1087
  return m.lacuna(l.tipo, l.alvo ? ` (${l.alvo})` : '', m.lacunaDetalhe(l.tipo, l.parte, l.alvo, l.detalhe), m.lacunaCorrecao(l.tipo, l.correcao));
1088
1088
  };
1089
- /** O texto de `ork network roadmap`: o consultado e o nao consultado no alto, e cada parte com a fonte e a hora. */
1089
+ /**
1090
+ * O texto de `ork network roadmap`: o consultado e o nao consultado no alto, e cada parte com a fonte e a hora. RM-074 (F4):
1091
+ * as datas omitem o ano pelo ano da leitura (`consultadoEm`), nao pelo relogio de quem le o texto: o mesmo panorama da o
1092
+ * mesmo texto em qualquer dia, e o retrato pt-BR F3c nao fica vermelho na virada do ano.
1093
+ */
1090
1094
  function textoDoPanoramaDaRede(p) {
1091
1095
  const m = (0, locale_1.msg)().network.roadmap;
1092
1096
  if (p.contrato !== exports.CONTRATO_PANORAMA_DA_REDE)
@@ -1108,8 +1112,8 @@ function textoDoPanoramaDaRede(p) {
1108
1112
  // Fatia 2 do ensaio (P7): o fuso ja sai no fim do panorama (e no bloco, quando difere): sem a legenda do status.
1109
1113
  linhas.push(x.roadmap ? (0, roadmap_status_1.textoDoStatusDoRoadmap)(x.roadmap, fuso, { legenda: false })
1110
1114
  : m.roadmapNaoLido(x.projeto.nome, x.lacunas.map((l) => l.tipo).filter((t) => t !== 'projeto.sem-clone').join(', ') || m.semFonte));
1111
- linhas.push('', m.threadsPorMaquina, ...linhasDasMaquinas(x, fuso));
1112
- linhas.push('', m.reservas, ...linhasDasReservas(x, fuso));
1115
+ linhas.push('', m.threadsPorMaquina, ...linhasDasMaquinas(x, p.consultadoEm, fuso));
1116
+ linhas.push('', m.reservas, ...linhasDasReservas(x, p.consultadoEm, fuso));
1113
1117
  linhas.push('', m.fontes, ...(x.fontes.length ? x.fontes.map((f) => linhaDaFonte(f, p.consultadoEm, fuso)) : [m.nenhumaFonte]));
1114
1118
  linhas.push('', m.lacunas, ...(x.lacunas.length ? x.lacunas.map(linhaDaLacuna) : [m.nenhumaLacuna]));
1115
1119
  // A legenda e a da hora da leitura, como os horarios do panorama: com a de agora, o horario de verao mudava o texto.