@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
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.parsePeriodo = parsePeriodo;
37
37
  exports.coletarEstatisticas = coletarEstatisticas;
38
38
  exports.registrarEstimativaPlano = registrarEstimativaPlano;
39
+ exports.linhaDoHitlDeConducao = linhaDoHitlDeConducao;
39
40
  exports.textoDasEstatisticas = textoDasEstatisticas;
40
41
  const fs = __importStar(require("node:fs"));
41
42
  const path = __importStar(require("node:path"));
@@ -43,6 +44,7 @@ const thread_1 = require("./thread");
43
44
  const ledger_1 = require("./ledger");
44
45
  const util_1 = require("./util");
45
46
  const horario_1 = require("./horario");
47
+ const hitl_tempo_parado_1 = require("./hitl-tempo-parado");
46
48
  const MOTIVOS_PREVENTIVOS = new Set([
47
49
  'policy.violation', 'cost.violation', 'verify.regression', 'claims.failed', 'claims.unverifiable',
48
50
  ]);
@@ -118,7 +120,9 @@ function coletarEstatisticas(raiz, filtros) {
118
120
  const riscos = {};
119
121
  const leads = [];
120
122
  const fontes = new Set();
121
- for (const { eventos } of selecionadas) {
123
+ const esperasDeConducao = [];
124
+ for (const { thread, eventos } of selecionadas) {
125
+ esperasDeConducao.push(...(0, hitl_tempo_parado_1.esperasDeHitl)(thread.id, eventos));
122
126
  corrompidos += eventos.filter(e => e.tipo === 'ledger_corrupted').length;
123
127
  const despachos = new Map();
124
128
  for (const e of eventos)
@@ -237,6 +241,7 @@ function coletarEstatisticas(raiz, filtros) {
237
241
  custoReferencia: { natureza: tarifas.nature, moeda: tarifas.currency, valor: precificadas ? Number(custo.toFixed(6)) : null, sessoesPrecificadas: precificadas, sessoesSemTarifa: semTarifa, tarifasVerificadasEm: tarifas.verifiedAt, fontes: [...fontes].sort() },
238
242
  leadTime: { mediaMs: leads.length ? leads.reduce((a, b) => a + b, 0) / leads.length : null, amostras: leads.length },
239
243
  esperaHumana: { milissegundos: esperaMs, fechadas: esperasFechadas, abertas: esperasAbertas },
244
+ hitlDeConducao: (0, hitl_tempo_parado_1.resumirTempoParado)(esperasDeConducao, desdeMs, ateMs),
240
245
  riscosPreventivos: { total: Object.values(riscos).reduce((a, b) => a + b, 0), porMotivo: riscos },
241
246
  estimativasPlano: { amostras: estimativas, semIaHoras, iaSemOrkHoras },
242
247
  qualidade: { ledgersCorrompidos: corrompidos, observacoes },
@@ -255,6 +260,14 @@ function registrarEstimativaPlano(raiz, threadId, dados) {
255
260
  throw Error('ledger.estimate.head-unavailable');
256
261
  return (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, threadId), threadId, 'plan_estimate', { fase: 'PLAN', unidade: 'hours', semIaHoras: dados.semIaHoras, iaSemOrkHoras: dados.iaSemOrkHoras, por: dados.por, metodo: dados.metodo, premissas: dados.premissas, incerteza: dados.incerteza ?? 'nao informada', head: head.stdout.trim() });
257
262
  }
263
+ const minutos = (ms) => ms === null ? 'sem resposta medida' : `${(ms / 60_000).toFixed(1)} min`;
264
+ /** RM-057: uma linha, com a meta ao lado da mediana, para o dono ver o tempo parado sem abrir o JSON. */
265
+ function linhaDoHitlDeConducao(h) {
266
+ const meta = h.dentroDaMeta === null ? '' : h.dentroDaMeta ? ' (dentro da meta de 5 min)' : ' (acima da meta de 5 min)';
267
+ return ` HITL de conducao ${h.pedidos} pergunta(s); parado ${(h.paradoMs / 3_600_000).toFixed(2)} h; ` +
268
+ `mediana ${minutos(h.medianaRespostaMs)}${meta}; ${h.abertos} aberta(s); ` +
269
+ `texto fora da excecao ${h.emTexto.foraDaExcecao}`;
270
+ }
258
271
  function textoDasEstatisticas(r) {
259
272
  const dinheiro = r.custoReferencia.valor === null ? 'sem cobertura' : `USD ${r.custoReferencia.valor.toFixed(6)}`;
260
273
  return [
@@ -265,6 +278,7 @@ function textoDasEstatisticas(r) {
265
278
  ` tokens ${r.tokens.total} total; input ${r.tokens.input} (cached ${r.tokens.cachedInput}); output ${r.tokens.output}`,
266
279
  ` custo referencia ${dinheiro} — estimativa por tarifa publica, nao gasto/fatura`,
267
280
  ` espera humana ${(r.esperaHumana.milissegundos / 3_600_000).toFixed(2)} h; ${r.esperaHumana.abertas} aberta(s)`,
281
+ linhaDoHitlDeConducao(r.hitlDeConducao),
268
282
  ` riscos evitados ${r.riscosPreventivos.total} gate(s) preventivo(s)`,
269
283
  ` estimativas PLAN ${r.estimativasPlano.amostras} amostra(s)`,
270
284
  ...r.qualidade.observacoes.map(o => ` cobertura ${o}`),
package/dist/ledger.js CHANGED
@@ -45,6 +45,7 @@ exports.caminhoLedger = caminhoLedger;
45
45
  exports.registrar = registrar;
46
46
  exports.threadPresente = threadPresente;
47
47
  exports.registrarSeExiste = registrarSeExiste;
48
+ exports.ehEnsaio = ehEnsaio;
48
49
  exports.lerLedger = lerLedger;
49
50
  const fs = __importStar(require("node:fs"));
50
51
  const path = __importStar(require("node:path"));
@@ -140,6 +141,9 @@ exports.TIPOS_DE_EVENTO = {
140
141
  // I-33 (D5): troca de perfil ou de runtime depois de falha da conta (quem, de qual para
141
142
  // qual, evidencia e razao). Faz parte do catalogo, nao de literal solto.
142
143
  perfilRotacionado: 'runtime_profile_rotated',
144
+ // RM-037 (fatia 5): a cota que o observador viu na transcricao com a sessao viva; o perfil saiu do rodizio
145
+ // ali, ate a hora dita na mensagem, e nao so no fecho da fase.
146
+ cotaVistaNaTranscricao: 'runtime_quota_detected',
143
147
  fixAberto: 'go_fix_opened',
144
148
  fixDespachado: 'go_fix_dispatched',
145
149
  reverifyConcluido: 'check_reverify',
@@ -236,6 +240,14 @@ function registrarSeExiste(dirThread, threadId, tipo, dados = {}) {
236
240
  }
237
241
  return evento;
238
242
  }
243
+ /**
244
+ * Fatia 2 do ensaio da 0.5.0 (P2): o evento que um `--dry-run` grava (`dryRun: true`) e ensaio, nao
245
+ * estado. O ledger o guarda como rastro; board, escalonador, monitor (o pulse) e maestro o descartam.
246
+ * O ensaio do ship gravava `gate_blocked human.pending` e a thread aparecia pausada sem ninguem pedir.
247
+ */
248
+ function ehEnsaio(e) {
249
+ return e.dryRun === true;
250
+ }
239
251
  /** Le o ledger inteiro. Linha corrompida vira evento `ledger_corrupted` em vez de quebrar a leitura. */
240
252
  function lerLedger(dirThread) {
241
253
  const caminho = caminhoLedger(dirThread);
package/dist/licoes.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.PISO_DA_PROPOSTA = exports.JANELA_DA_PROPOSTA_DIAS = void 0;
36
+ exports.PISO_DA_PROPOSTA = exports.JANELA_DA_PROPOSTA_DIAS = exports.DICA_DO_MOTIVO = void 0;
37
37
  exports.lerLicoes = lerLicoes;
38
38
  exports.resumoDasLicoes = resumoDasLicoes;
39
39
  exports.textoDasLicoes = textoDasLicoes;
@@ -59,15 +59,17 @@ const estado_thread_1 = require("./estado-thread");
59
59
  const ledger_1 = require("./ledger");
60
60
  const policies_1 = require("./policies");
61
61
  /** O que evita cada bloqueio, na forma de comando. Sem dica, a licao so conta a recorrencia. */
62
- const DICA_DO_MOTIVO = {
62
+ exports.DICA_DO_MOTIVO = {
63
63
  'claims.failed': 'rode o comando da claim antes de registra-la; claim sem prova local volta como GO-FIX',
64
64
  'verify.regression': 'grave a baseline antes do GO (`ork verify <thread> --baseline`) e rode os testes focados antes do CHECK',
65
65
  'verify.failed': 'grave a baseline antes do GO, para a falha ter com que ser comparada',
66
66
  'verify.timeout': 'divida o comando lento ou suba `verify.timeout_ms` no manifesto',
67
67
  'runtime.unavailable': 'declare a ordem de fallback do bloco (`ork setup <modo> --bloco N --fallback runtime:modelo`)',
68
68
  'runtime.quota-exhausted': 'tenha um segundo perfil da conta (`ork accounts add`) ou fallback no bloco',
69
+ 'runtime.profile-invalid': 'confira os perfis do runtime em `ork accounts list` antes de pedir `--perfil`',
69
70
  'tree.blocked': 'sincronize a worktree com a base antes do SHIP (`ork worktree sync <thread>`)',
70
- 'ci.failed': 'rode a suite hermetica (`npm --prefix core run test:ci`) antes do push',
71
+ // Fatia 2 do ensaio da 0.5.0 (P6): o que o CI do projeto roda, e nao o script do Orkastery.
72
+ 'ci.failed': 'rode antes do push o que o CI do projeto roda (`ci.command` do manifesto ou, sem ele, os comandos de `verify`)',
71
73
  'artifact.missing': 'grave o artefato da fase exatamente no caminho que o prompt pede',
72
74
  };
73
75
  const DICA_DA_CLASSE = {
@@ -116,7 +118,7 @@ function recorrencias(licoes) {
116
118
  const classes = new Map();
117
119
  for (const l of licoes) {
118
120
  for (const m of l.motivos) {
119
- const r = motivos.get(m) ?? { chave: `motivo:${m}`, tipo: 'motivo', nome: m, threads: [], vezes: 0, dica: DICA_DO_MOTIVO[m] ?? null };
121
+ const r = motivos.get(m) ?? { chave: `motivo:${m}`, tipo: 'motivo', nome: m, threads: [], vezes: 0, dica: exports.DICA_DO_MOTIVO[m] ?? null };
120
122
  r.threads.push(l.thread);
121
123
  motivos.set(m, r);
122
124
  }
package/dist/liveness.js CHANGED
@@ -120,7 +120,8 @@ function detectarFasesOrfas(carregado, opcoes = {}) {
120
120
  const t = (0, thread_1.lerThread)(carregado.raiz, id), dir = (0, thread_1.dirThread)(carregado.raiz, id);
121
121
  if (t.status !== 'aberta')
122
122
  continue;
123
- const eventos = (0, ledger_1.lerLedger)(dir), di = eventos.map(e => e.tipo).lastIndexOf('phase_dispatch');
123
+ // Fatia 2 do ensaio da 0.5.0 (P2; CHECK, rodada 1, A1): o gate do `ship --dry-run` nao e gate em aberto.
124
+ const eventos = (0, ledger_1.lerLedger)(dir).filter(e => !(0, ledger_1.ehEnsaio)(e)), di = eventos.map(e => e.tipo).lastIndexOf('phase_dispatch');
124
125
  if (di < 0)
125
126
  continue;
126
127
  const d = eventos[di], inicio = Date.parse(d.ts), fase = d.fase;
@@ -47,6 +47,7 @@ const hitl_sessions_1 = require("./hitl-sessions");
47
47
  const thread_1 = require("./thread");
48
48
  const hitl_gates_1 = require("./hitl-gates");
49
49
  const ocupacao_1 = require("./ocupacao");
50
+ const ledger_1 = require("./ledger");
50
51
  const horario_1 = require("./horario");
51
52
  const conducao_1 = require("./conducao");
52
53
  const conducao_texto_1 = require("./conducao-texto");
@@ -74,8 +75,11 @@ const shipView = zod_1.z.union([
74
75
  zod_1.z.object({
75
76
  tipo: zod_1.z.literal('ship_done'), ts: shipTs, pushVerificado: zod_1.z.boolean(), mergeSha: shipSha,
76
77
  shaRemoto: shipSha.nullable().optional(),
78
+ // RM-037 (fatia 5, A1): o push prova a ponta da base; o `mergeSha` e o merge que entregou, que pode ser mais
79
+ // velho que ela. Recibo sem o campo continua valendo pela regra de antes.
80
+ pontaDaBase: shipSha.nullable().optional(),
77
81
  fonteDaProva: zod_1.z.string().min(1).max(512).refine(v => !/[\u0000-\u001f\u007f]/.test(v)).nullable().optional(),
78
- }).refine(s => !s.pushVerificado || (s.shaRemoto === s.mergeSha && typeof s.fonteDaProva === 'string')),
82
+ }).refine(s => !s.pushVerificado || (s.shaRemoto === (s.pontaDaBase ?? s.mergeSha) && typeof s.fonteDaProva === 'string')),
79
83
  zod_1.z.object({ tipo: zod_1.z.enum(['ship_started', 'ship_blocked']), ts: shipTs }),
80
84
  ]);
81
85
  function operationalSources(reader, threads, native = [], now = new Date().toISOString(), threadScope) {
@@ -95,6 +99,8 @@ function operationalSources(reader, threads, native = [], now = new Date().toISO
95
99
  events = raw.split('\n').filter(Boolean).map(line => JSON.parse(line));
96
100
  if (events.some(e => typeof e.tipo !== 'string' || e.thread !== t.id))
97
101
  throw Error('invalid');
102
+ // Fatia 2 do ensaio da 0.5.0 (P2): o ensaio (`dryRun: true`) nao vira bloqueio nem entrega incompleta.
103
+ events = events.filter(e => !(0, ledger_1.ehEnsaio)(e));
98
104
  }
99
105
  catch {
100
106
  for (const name of names.filter(n => !['leases', 'retries'].includes(n)))
package/dist/manifest.js CHANGED
@@ -165,6 +165,28 @@ function booleano(v, padrao) {
165
165
  function mapa(v) {
166
166
  return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
167
167
  }
168
+ /**
169
+ * RM-031 KG5 (D2): `grafo.mcp` so liga com o booleano `true`. Ausente vale `false`; texto que parece
170
+ * booleano, outro tipo ou `grafo` que nao e mapa tambem valem `false`, com aviso: ligar expoe as tools
171
+ * do grafo, e na duvida fica desligado. Chave desconhecida em `grafo` so avisa.
172
+ */
173
+ function lerGrafo(v, avisos) {
174
+ const mostrar = (x) => JSON.stringify(x).slice(0, 80);
175
+ if (v === undefined || v === null)
176
+ return { mcp: false };
177
+ if (typeof v !== 'object' || Array.isArray(v)) {
178
+ avisos.push(`grafo deve ser um mapa com mcp: true ou false; vale mcp: false (${mostrar(v)} nao reconhecido)`);
179
+ return { mcp: false };
180
+ }
181
+ for (const chave of Object.keys(v))
182
+ if (chave !== 'mcp')
183
+ avisos.push(`grafo.${chave} desconhecida; o bloco grafo so tem mcp`);
184
+ const mcp = v.mcp;
185
+ if (mcp === undefined || mcp === null || typeof mcp === 'boolean')
186
+ return { mcp: mcp === true };
187
+ avisos.push(`grafo.mcp deve ser true ou false; vale false (${mostrar(mcp)} nao reconhecido)`);
188
+ return { mcp: false };
189
+ }
168
190
  /** Mesmo contrato na leitura do YAML e nos consumidores de manifesto em memoria. */
169
191
  function validarOptInPlaybook(valor) {
170
192
  if (valor === undefined)
@@ -334,6 +356,16 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
334
356
  avisos.push(`runtime_profiles.${nome} deve ser true ou false; vale o padrao ${padrao} (${JSON.stringify(v)} nao reconhecido)`);
335
357
  return padrao;
336
358
  };
359
+ // RM-056 (D2): opt-in. Valor desconhecido vale o padrao `ordem`, com aviso.
360
+ const distribuicaoDePerfis = () => {
361
+ const v = perfisDeRuntime.distribuir;
362
+ if (v === undefined || v === null || v === 'ordem')
363
+ return 'ordem';
364
+ if (v === 'carga')
365
+ return 'carga';
366
+ avisos.push(`runtime_profiles.distribuir aceita ordem ou carga; vale o padrao ordem (${JSON.stringify(v)} nao reconhecido)`);
367
+ return 'ordem';
368
+ };
337
369
  const liveness = mapa(dados.liveness);
338
370
  const silencioMaxMin = numero(liveness.silencio_max_min, 10);
339
371
  if (!Number.isFinite(silencioMaxMin) || silencioMaxMin <= 0)
@@ -434,6 +466,11 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
434
466
  '(o `ork` nao adivinha DSN, e base alheia e pior do que base nenhuma)');
435
467
  }
436
468
  const embedding = lerEmbedding(memory.embedding, variavelDaDsn, erros);
469
+ const prazoDoUniverso = memory.universo_timeout_ms === undefined ? orkmind_1.TIMEOUT_DO_UNIVERSO_MS : memory.universo_timeout_ms;
470
+ if (typeof prazoDoUniverso !== 'number' || !Number.isSafeInteger(prazoDoUniverso) || prazoDoUniverso <= 0 ||
471
+ prazoDoUniverso > orkmind_1.LIMITE_TIMEOUT_DO_UNIVERSO_MS) {
472
+ erros.push('memory.universo_timeout_ms: esperado inteiro em ms entre 1 e 86400000 (24 h; valor omitido)');
473
+ }
437
474
  const bytes = Buffer.byteLength(bruto, 'utf8');
438
475
  if (bytes > exports.LIMITE_MANIFESTO_BYTES) {
439
476
  erros.push(`manifesto com ${bytes} bytes, acima do limite duro de ${exports.LIMITE_MANIFESTO_BYTES} (prosa vai para memoria com tag, nao para o YAML)`);
@@ -469,7 +506,9 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
469
506
  worktree: {
470
507
  base_branch: texto(worktree.base_branch, 'main'),
471
508
  dir: texto(worktree.dir, '.claude/worktrees'),
472
- por_thread: booleano(worktree.por_thread, true),
509
+ // P4 do ensaio da 0.5.0: o `ork thread new` cria a worktree sem flag quando a chave diz true. Ausente le
510
+ // false, para o projeto que nunca a declarou seguir como era.
511
+ por_thread: booleano(worktree.por_thread, false),
473
512
  },
474
513
  verify: {
475
514
  build: verify.build === null || verify.build === undefined ? undefined : texto(verify.build, ''),
@@ -500,6 +539,7 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
500
539
  runtime_profiles: {
501
540
  rotate_same_runtime_on_quota: chaveDeRotacao('rotate_same_runtime_on_quota', true),
502
541
  rotate_same_runtime_on_auth: chaveDeRotacao('rotate_same_runtime_on_auth', true),
542
+ distribuir: distribuicaoDePerfis(),
503
543
  },
504
544
  // Bloco B3: o limite de escalacao tem padrao, e o padrao e conservador. Manifesto
505
545
  // sem bloco `retry:` continua andando com 3 tentativas e janela estimada de 60 min,
@@ -518,6 +558,7 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
518
558
  cli: texto(memory.cli, 'orkmind'),
519
559
  tenant: texto(memory.tenant, '') || nomeProjeto,
520
560
  timeout_ms: numero(memory.timeout_ms, 15000),
561
+ universo_timeout_ms: typeof prazoDoUniverso === 'number' ? prazoDoUniverso : orkmind_1.TIMEOUT_DO_UNIVERSO_MS,
521
562
  ...(embedding ? { embedding } : {}),
522
563
  },
523
564
  // Bloco B5: governanca de custo dos auditores. Ausente no manifesto quer dizer
@@ -534,6 +575,8 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
534
575
  compartilhada: booleano(fabrica.compartilhada, false),
535
576
  remoto: texto(fabrica.remoto, 'origin'),
536
577
  },
578
+ // RM-031 KG5 (D2): expor as tools do grafo no MCP e opt-in do dono.
579
+ grafo: lerGrafo(dados.grafo, avisos),
537
580
  policies: Object.fromEntries(Object.entries(mapa(dados.policies)).map(([k, v]) => [k, texto(v, '')])),
538
581
  };
539
582
  return { caminho: achado.caminho, raiz, legado: achado.legado, bytes, manifesto, erros, avisos };
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ERRO_PROVA_DE_CANAL = exports.CONTRATO_PEDIDO_DE_NOTA = void 0;
37
+ exports.marcaDeHost = marcaDeHost;
37
38
  exports.exigirNotaSemHost = exigirNotaSemHost;
38
39
  exports.arquivoDasNotas = arquivoDasNotas;
39
40
  exports.pedirNota = pedirNota;
@@ -84,10 +85,11 @@ exports.CONTRATO_PEDIDO_DE_NOTA = 'ork.master-nota/v1';
84
85
  exports.ERRO_PROVA_DE_CANAL = 'master.prova-de-canal';
85
86
  const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
86
87
  /**
87
- * D8: nota em nome de pessoa, vinda de processo de host, e recusada. O canal e lido SO do
88
- * ambiente: `--canal` em argv nao conta, porque quem escreve argv e justamente quem chama.
88
+ * A marca de host ou de despacho do processo, ou `undefined` no terminal do dono. Lida SO do
89
+ * ambiente. Serve para RECUSAR (D8) e para AVISAR (RM-008, aceite por omissao sem thread), nunca
90
+ * para autorizar.
89
91
  */
90
- function exigirNotaSemHost(ambiente = process.env) {
92
+ function marcaDeHost(ambiente = process.env) {
91
93
  let canal;
92
94
  try {
93
95
  canal = (0, conducao_1.canalDoProcesso)(undefined, ambiente);
@@ -97,12 +99,19 @@ function exigirNotaSemHost(ambiente = process.env) {
97
99
  }
98
100
  // B2 do CHECK: `ORK_CANAL=cli` NAO absolve. A sessao despachada herda `ORK_CANAL=cli` quando a
99
101
  // thread e conduzida do terminal, e o Claude Code e o Codex marcam os proprios processos. Qualquer
100
- // marca de host ou de despacho recusa, mesmo com o canal declarado como terminal.
101
- const marca = canal !== 'cli' ? canal
102
+ // marca de host ou de despacho conta, mesmo com o canal declarado como terminal.
103
+ return canal !== 'cli' ? canal
102
104
  : ambiente.ORK_DISPATCH_ID ? 'sessão despachada (ORK_DISPATCH_ID)'
103
105
  : ambiente.CLAUDECODE === '1' ? 'claude-code (CLAUDECODE)'
104
106
  : ambiente.CODEX_SANDBOX || ambiente.CODEX_SANDBOX_NETWORK_DISABLED ? 'codex (CODEX_SANDBOX)'
105
107
  : (ambiente.HERMES_HOME ?? '').trim() ? 'hermes (HERMES_HOME)' : undefined;
108
+ }
109
+ /**
110
+ * D8: nota em nome de pessoa, vinda de processo de host, e recusada. O canal e lido SO do
111
+ * ambiente: `--canal` em argv nao conta, porque quem escreve argv e justamente quem chama.
112
+ */
113
+ function exigirNotaSemHost(ambiente = process.env) {
114
+ const marca = marcaDeHost(ambiente);
106
115
  if (marca) {
107
116
  throw new Error(`${exports.ERRO_PROVA_DE_CANAL}: nota em nome de pessoa vinda de ${marca} exige a prova de canal do ingresso. ` +
108
117
  'Peça a nota com ork master pedir <thread>: o dono responde pelo Telegram com o código, e a nota é gravada com o recibo.');
package/dist/master.js CHANGED
@@ -49,7 +49,9 @@ var __importStar = (this && this.__importStar) || (function () {
49
49
  };
50
50
  })();
51
51
  Object.defineProperty(exports, "__esModule", { value: true });
52
- exports.AUTOR_DA_OMISSAO = exports.CONTRATO_MASTER_PENDENTE = exports.ORDEM_DAS_CLASSES = exports.CLASSES_DE_FALHA = exports.VERSAO_MASTER_LOG = exports.CONTRATO_MASTER_LOG = void 0;
52
+ exports.AUTOR_DA_OMISSAO = exports.CONTRATO_MASTER_PENDENTE = exports.FASE_DO_TIPO = exports.ORDEM_DAS_CLASSES = exports.CLASSE_DO_MOTIVO = exports.CLASSES_DE_FALHA = exports.VERSAO_MASTER_LOG = exports.CONTRATO_MASTER_LOG = void 0;
53
+ exports.classesPelosGates = classesPelosGates;
54
+ exports.sincronizacoesComABase = sincronizacoesComABase;
53
55
  exports.parseClasse = parseClasse;
54
56
  exports.caminhoPostmortem = caminhoPostmortem;
55
57
  exports.caminhoMasterLog = caminhoMasterLog;
@@ -72,6 +74,8 @@ exports.aceitosPorOmissao = aceitosPorOmissao;
72
74
  exports.pendentesDeScore = pendentesDeScore;
73
75
  exports.tabelaDeEntregas = tabelaDeEntregas;
74
76
  exports.aceitarPendentesPorOmissao = aceitarPendentesPorOmissao;
77
+ exports.entregasParaOmissao = entregasParaOmissao;
78
+ exports.motivoForaDaOmissao = motivoForaDaOmissao;
75
79
  exports.textoDoMaster = textoDoMaster;
76
80
  exports.tabelaDeClasses = tabelaDeClasses;
77
81
  const path = __importStar(require("node:path"));
@@ -105,6 +109,65 @@ exports.CLASSES_DE_FALHA = {
105
109
  'scope-creep': 'a entrega cresceu alem do que o GOAL prometia',
106
110
  outra: 'nao cabe em nenhuma classe acima (descreva na justificativa)',
107
111
  };
112
+ /**
113
+ * RM-008 (classe pelo gate): a classe que cada motivo tipado do gate sustenta quando ninguem informa
114
+ * `--classe`. Antes, todo gate virava "outra", a classe que o `ork licoes` ignora: 8 POSTMORTEMs de
115
+ * 03/10 fecharam assim por aceite por omissao. Motivo fora da tabela (prazo, falta de veredito,
116
+ * runtime, conta, custo, espera humana) nao cabe numa classe fixa e segue "outra".
117
+ */
118
+ exports.CLASSE_DO_MOTIVO = {
119
+ 'artifact.missing': 'processo',
120
+ 'claims.failed': 'processo',
121
+ 'claims.unverifiable': 'processo',
122
+ 'policy.violation': 'processo',
123
+ 'runtime.autoconferencia': 'processo',
124
+ 'verify.regression': 'processo',
125
+ 'verify.failed': 'processo',
126
+ 'ci.failed': 'processo',
127
+ 'hitl.formato': 'processo',
128
+ 'runtime.rate-limited': 'rate-limit',
129
+ 'runtime.quota-exhausted': 'rate-limit',
130
+ 'lease.busy': 'conflito',
131
+ 'conducao.em-andamento': 'conflito',
132
+ 'tree.blocked': 'conflito',
133
+ };
134
+ /** As classes que os motivos dos gates sustentam, na ordem canonica e sem repetir. */
135
+ function classesPelosGates(motivos) {
136
+ const achadas = new Set();
137
+ for (const m of motivos)
138
+ achadas.add(exports.CLASSE_DO_MOTIVO[m] ?? 'outra');
139
+ if (achadas.size === 0)
140
+ achadas.add('sem-falha');
141
+ return exports.ORDEM_DAS_CLASSES.filter((c) => achadas.has(c));
142
+ }
143
+ /** Teto de merges conferidos por entrega: a branch de 03/10 com mais sincronizacoes trouxe 16. */
144
+ const TETO_DE_MERGES_POR_ENTREGA = 200;
145
+ /**
146
+ * RM-008 (base-avancou): quantas vezes a thread precisou trazer a base para dentro da branch. Conta os
147
+ * `worktree_synced` do ledger e, em cada entrega com merge, os merges da branch (entre o primeiro e o
148
+ * segundo pai do merge) cujo pai trazido ja estava na base. Em 03/10/2026, 29 de 35 entregas trouxeram
149
+ * a `origin/main` antes do merge, e nenhuma fechou com `base-avancou`. Git sem resposta conta 0 e nunca
150
+ * derruba o MASTER.
151
+ */
152
+ function sincronizacoesComABase(raiz, eventos, mergeShas) {
153
+ let total = eventos.filter((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.worktreeSincronizada).length;
154
+ for (const sha of new Set(mergeShas)) {
155
+ if (!/^[0-9a-f]{40}$/.test(sha))
156
+ continue;
157
+ const r = (0, util_1.exec)('git', ['rev-list', '--merges', '--parents', `--max-count=${TETO_DE_MERGES_POR_ENTREGA}`,
158
+ `${sha}^1..${sha}^2`], raiz, 30000);
159
+ if (!r.ok)
160
+ continue;
161
+ for (const linha of r.stdout.split('\n')) {
162
+ const pais = linha.trim().split(/\s+/).slice(1);
163
+ // O pai trazido (o segundo em diante) ja estava na base: e a base entrando na branch.
164
+ if (pais.slice(1).some((p) => /^[0-9a-f]{40}$/.test(p) &&
165
+ (0, util_1.exec)('git', ['merge-base', '--is-ancestor', p, `${sha}^1`], raiz, 30000).ok))
166
+ total += 1;
167
+ }
168
+ }
169
+ return total;
170
+ }
108
171
  /** Ordem canonica das classes na saida do CLI. */
109
172
  exports.ORDEM_DAS_CLASSES = [
110
173
  'sem-falha',
@@ -138,6 +201,19 @@ function caminhoPostmortem(raiz, id) {
138
201
  function caminhoMasterLog(raiz, id) {
139
202
  return path.join((0, thread_1.dirThread)(raiz, id), 'master-log.json');
140
203
  }
204
+ /**
205
+ * RM-008 (fases pelo tipo): a fase dos eventos que o nucleo grava sem o campo `fase`. O `verify_run`
206
+ * e o `ship_done` nunca trouxeram fase, e 34 dos 35 POSTMORTEM de 03/10 sairam sem o CHECK, e 33 sem o
207
+ * SHIP, embora o ledger os tivesse (`ork-rm037testesi`: 6 `verify_run`, 1 `ship_done`, "GOAL GO MASTER").
208
+ * So vale quando o evento nao traz `fase`: o campo explicito continua vencendo.
209
+ */
210
+ exports.FASE_DO_TIPO = {
211
+ [ledger_1.TIPOS_DE_EVENTO.verificacao]: 'CHECK',
212
+ [ledger_1.TIPOS_DE_EVENTO.reverifyConcluido]: 'CHECK',
213
+ [ledger_1.TIPOS_DE_EVENTO.shipIniciado]: 'SHIP',
214
+ [ledger_1.TIPOS_DE_EVENTO.shipConcluido]: 'SHIP',
215
+ [ledger_1.TIPOS_DE_EVENTO.shipBloqueado]: 'SHIP',
216
+ };
141
217
  /**
142
218
  * As fases que a thread realmente percorreu, reconstruidas do LEDGER.
143
219
  *
@@ -148,7 +224,7 @@ function fasesPercorridas(raiz, id) {
148
224
  const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
149
225
  const porFase = new Map();
150
226
  for (const e of eventos) {
151
- const bruta = typeof e.fase === 'string' ? e.fase.toUpperCase() : null;
227
+ const bruta = typeof e.fase === 'string' ? e.fase.toUpperCase() : (exports.FASE_DO_TIPO[e.tipo] ?? null);
152
228
  if (!bruta || !types_1.FASES.includes(bruta))
153
229
  continue;
154
230
  const atual = porFase.get(bruta) ?? {
@@ -244,7 +320,8 @@ function prepararDocumentos(raiz, id, opcoes) {
244
320
  sincronizarReversao(raiz, id);
245
321
  const eventos = (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, id));
246
322
  const gatesBloqueados = eventos
247
- .filter((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.gateBloqueado)
323
+ // Ensaio de 03/10/2026 (RM-049): o gate barrado por um `ork ship --dry-run` e ensaio, nao reprovacao.
324
+ .filter((e) => e.tipo === ledger_1.TIPOS_DE_EVENTO.gateBloqueado && e.dryRun !== true)
248
325
  .map((e) => ({
249
326
  ts: e.ts,
250
327
  motivo: String(e.motivo ?? '(sem motivo)'),
@@ -264,11 +341,20 @@ function prepararDocumentos(raiz, id, opcoes) {
264
341
  let classes = opcoes.classes ?? [];
265
342
  if (classes.length === 0) {
266
343
  classesInferidas = true;
267
- classes = gatesBloqueados.length > 0 ? ['outra'] : ['sem-falha'];
268
- avisos.push(gatesBloqueados.length > 0
269
- ? `a thread levou ${gatesBloqueados.length} reprovacao(oes) tipada(s) e nenhuma --classe foi ` +
270
- 'informada: o `ork` gravou "outra". Corrija com --classe <classe> --refazer.'
271
- : 'nenhuma --classe informada e nenhum gate reprovou: o `ork` gravou "sem-falha".');
344
+ classes = classesPelosGates(gatesBloqueados.map((g) => g.motivo));
345
+ const motivos = [...new Set(gatesBloqueados.map((g) => g.motivo))];
346
+ const sincronizacoes = sincronizacoesComABase(raiz, eventos, entregas.map((e) => e.mergeSha));
347
+ if (sincronizacoes > 0) {
348
+ classes = exports.ORDEM_DAS_CLASSES.filter((c) => c === 'base-avancou' || (c !== 'sem-falha' && classes.includes(c)));
349
+ }
350
+ const pelaBase = sincronizacoes > 0
351
+ ? ` A branch trouxe a base ${sincronizacoes} vez(es) antes da entrega: o \`ork\` juntou "base-avancou".` : '';
352
+ avisos.push((gatesBloqueados.length > 0
353
+ ? `a thread levou ${gatesBloqueados.length} reprovacao(oes) tipada(s) (${motivos.join(', ')}) e nenhuma --classe foi ` +
354
+ `informada: o \`ork\` inferiu "${classes.join('", "')}" pelo motivo do gate.${pelaBase} Corrija com --classe <classe> --refazer.`
355
+ : sincronizacoes > 0
356
+ ? `nenhuma --classe informada e nenhum gate reprovou.${pelaBase} Corrija com --classe <classe> --refazer.`
357
+ : 'nenhuma --classe informada e nenhum gate reprovou: o `ork` gravou "sem-falha".'));
272
358
  }
273
359
  // I-43 (D3): o indice e DERIVADO do ledger no momento do registro, e acompanha o
274
360
  // score como o que a maquina achava antes de o dono falar. Quando as duas existem,
@@ -351,7 +437,7 @@ function autoriaHumana(por) {
351
437
  }
352
438
  function exigirAutoriaHumana(por) {
353
439
  if (!autoriaHumana(por))
354
- throw new Error('ratificação exige --por humano explícito; agente ou autoria pendente não pode pontuar');
440
+ throw new Error('ratificação exige --por humano explícito (ex.: --por "seu-nome"); agente ou autoria pendente não pode pontuar');
355
441
  }
356
442
  function masterRatificado(raiz, id) {
357
443
  const t = (0, thread_1.lerThread)(raiz, id);
@@ -692,7 +778,8 @@ function tabelaDeEntregas(raiz, todas = false) {
692
778
  (0, util_1.tabela)(['ID', 'SLUG', 'MODO', 'FASE', 'STATUS', 'ENTREGOU', 'INDICE', 'DE ONDE VEIO'], linhas),
693
779
  '',
694
780
  'ENTREGUE E ACEITO, a menos que voce diga o contrario.',
695
- ' ork master --aceitar-omissao aceita as entregues, com o indice e os insumos no ledger',
781
+ ' ork master <id> --aceitar-omissao aceita so esta entrega, com o indice e os insumos no ledger',
782
+ ' ork master --aceitar-omissao aceita TODAS as entregues da lista (--dry-run mostra quais, sem gravar)',
696
783
  ' ork master <id> --score <0-5> --justificativa "<por que>" --por <humano> se quiser reclamar',
697
784
  '',
698
785
  `Classes fixas: ${exports.ORDEM_DAS_CLASSES.join(', ')}`,
@@ -706,10 +793,40 @@ function tabelaDeEntregas(raiz, todas = false) {
706
793
  * ENTREGOU: uma thread que nunca chegou ao ship nao tem entrega para aceitar, e
707
794
  * aceita-la seria inventar um fato.
708
795
  */
709
- function aceitarPendentesPorOmissao(raiz) {
796
+ function aceitarPendentesPorOmissao(raiz, opcoes = {}) {
797
+ return entregasParaOmissao(raiz, opcoes.thread).map((p) => aceitarPorOmissao(raiz, p.thread.id));
798
+ }
799
+ /**
800
+ * RM-008 (03/10/2026): o que `ork master --aceitar-omissao` fecharia, sem gravar nada.
801
+ *
802
+ * Na madrugada de 03/10, tres vezes, um condutor rodou `ork master --aceitar-omissao` para fechar
803
+ * a PROPRIA thread, e o comando fechou tambem as entregues de outras frentes paralelas. A CLI nao
804
+ * deixava indicar a thread. Com `thread`, o alvo e so ela; sem, continua sendo toda entrega que
805
+ * espera decisao (o padrao nao mudou). Thread inexistente recusa pelo `lerThread`.
806
+ */
807
+ function entregasParaOmissao(raiz, thread) {
808
+ if (thread !== undefined)
809
+ (0, thread_1.lerThread)(raiz, thread);
710
810
  return pendentesDeScore(raiz)
711
811
  .filter((p) => p.entregou)
712
- .map((p) => aceitarPorOmissao(raiz, p.thread.id));
812
+ .filter((p) => thread === undefined || p.thread.id === thread);
813
+ }
814
+ /**
815
+ * Por que a thread indicada nao entra no aceite por omissao, ou `null` quando entra.
816
+ * `ja-fechada` e idempotencia (nada a fazer); `fechamento-admin` e `sem-entrega` sao recusa.
817
+ */
818
+ function motivoForaDaOmissao(raiz, id) {
819
+ const thread = (0, thread_1.lerThread)(raiz, id);
820
+ if (thread.fechamentoAdmin) {
821
+ return { motivo: 'fechamento-admin', texto: `a thread ${id} foi fechada administrativamente (${thread.fechamentoAdmin.motivo}); nao ha entrega para aceitar.` };
822
+ }
823
+ if (masterRatificado(raiz, id)) {
824
+ return { motivo: 'ja-fechada', texto: `a thread ${id} ja tem MASTER registrado; nada a aceitar.` };
825
+ }
826
+ if (!entregou(raiz, id)) {
827
+ return { motivo: 'sem-entrega', texto: `a thread ${id} ainda nao entregou (sem ship_done no ledger); registre a entrega antes: ork ship registrar-pr ${id}` };
828
+ }
829
+ return null;
713
830
  }
714
831
  /** Texto de `ork master <thread-id> --score`. */
715
832
  function textoDoMaster(r) {
@@ -150,13 +150,46 @@ function comEscrita(raiz, id, executar) {
150
150
  if (st) {
151
151
  if (!st.isDirectory() || st.isSymbolicLink() || fs.realpathSync(pastaLeases) !== pastaLeases)
152
152
  throw Error('mcp.state.unsafe: leases');
153
- for (const nome of fs.readdirSync(pastaLeases)) {
154
- const s = fs.lstatSync(path.join(pastaLeases, nome));
153
+ for (const nomeBytes of fs.readdirSync(pastaLeases, { encoding: 'buffer' })) {
154
+ const nome = nomeBytes.toString('utf8');
155
+ // Antes do lstat: uma decodificacao com perda nao prova que a entrada desapareceu.
156
+ if (!Buffer.from(nome, 'utf8').equals(nomeBytes))
157
+ throw Error('mcp.state.unsafe: nome fora de UTF-8 em leases');
158
+ const entrada = path.join(pastaLeases, nome), s = fs.lstatSync(entrada, { throwIfNoEntry: false });
159
+ if (!s)
160
+ continue; // Uma retomada pode ter acabado durante o preflight.
161
+ if (s.isDirectory() && nome.endsWith('.json.retomadas')) {
162
+ const base = nome.slice(0, -'.json.retomadas'.length);
163
+ if (!base || encodeURIComponent(decodeURIComponent(base)) !== base)
164
+ throw Error('mcp.state.unsafe: fila de retomada');
165
+ let candidatos;
166
+ try {
167
+ candidatos = fs.readdirSync(entrada, { encoding: 'buffer' });
168
+ }
169
+ catch (e) {
170
+ if (e.code === 'ENOENT')
171
+ continue;
172
+ throw e;
173
+ }
174
+ for (const candidatoBytes of candidatos) {
175
+ const candidato = candidatoBytes.toString('utf8');
176
+ if (!Buffer.from(candidato, 'utf8').equals(candidatoBytes))
177
+ throw Error('mcp.state.unsafe: nome fora de UTF-8 na fila');
178
+ const c = fs.lstatSync(path.join(entrada, candidato), { throwIfNoEntry: false });
179
+ if (!c)
180
+ continue;
181
+ if (!/^\d+-[a-f0-9-]+\.json(?:\.tmp)?$/.test(candidato) || !c.isFile() || c.nlink !== 1)
182
+ throw Error('mcp.state.unsafe: candidato de retomada');
183
+ }
184
+ continue;
185
+ }
155
186
  if (!s.isFile() || s.isSymbolicLink() || s.nlink !== 1)
156
187
  throw Error('mcp.state.unsafe: lease ou fila');
157
188
  }
158
189
  }
159
190
  const nome = `path:.orkastery/threads/${id}`;
191
+ // RM-037 (fatia 3, defeito 4): lease e fila de thread fechada sao orfaos e saem antes da conferencia.
192
+ (0, leases_1.podarRegioesDeThreadsFechadas)(raiz, [nome], id);
160
193
  if ((0, leases_1.lerLease)(raiz, nome) || (0, leases_1.leasesColidentes)(raiz, nome).length)
161
194
  throw Error('lease.busy: estado canonico ocupado');
162
195
  const r = (0, leases_1.adquirirRegiao)(raiz, nome, { thread: id, motivo: 'MCP documento ou claim', ttlMs: 30000, retomarVencido: false });
@@ -229,6 +262,6 @@ function adicionarClaimMcp(raiz, id, entrada) {
229
262
  if ((0, thread_1.lerThread)(raiz, id).status === 'fechada')
230
263
  throw Error('mcp.claim.closed');
231
264
  // Strings sao dados da claim; a reexecucao MCP deve usar o executor confinado.
232
- return (0, claims_1.adicionarClaim)(raiz, id, entrada);
265
+ return (0, claims_1.adicionarClaim)(raiz, id, { ...entrada, semProvaLocal: true });
233
266
  }));
234
267
  }