@orkastery/cli 0.5.1 → 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 (110) hide show
  1. package/adapters/claude-code/agents/ork-master.md +2 -0
  2. package/adapters/claude-code/commands/master.md +8 -0
  3. package/adapters/claude-code/commands/ork.md +9 -0
  4. package/adapters/codex/skills/ork/SKILL.md +18 -3
  5. package/adapters/hermes/README.md +1 -0
  6. package/adapters/hermes/bin/ork-network-status.sh +10 -0
  7. package/adapters/hermes/hermes.plugin.json +1 -0
  8. package/adapters/hermes/hitl-ingress/__init__.py +2 -2
  9. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
  10. package/adapters/openclaw/README.md +24 -4
  11. package/adapters/openclaw/dist/hitl-ingress.js +2 -2
  12. package/adapters/openclaw/dist/index.js +46 -11
  13. package/adapters/openclaw/openclaw.plugin.json +5 -1
  14. package/adapters/openclaw/package.json +2 -2
  15. package/adapters/openclaw/src/hitl-ingress.ts +2 -2
  16. package/adapters/openclaw/src/index.ts +49 -11
  17. package/assets/orkmind_bridge.py +117 -9
  18. package/dist/adapters/claude-bg.js +39 -5
  19. package/dist/adapters/codex.js +4 -3
  20. package/dist/agents-md.js +1 -0
  21. package/dist/auditrun.js +5 -1
  22. package/dist/board.js +16 -14
  23. package/dist/branch-de-estado.js +66 -10
  24. package/dist/busca-semantica.js +52 -12
  25. package/dist/canarios-rm057.js +149 -0
  26. package/dist/canarios.js +2 -0
  27. package/dist/ci.js +8 -1
  28. package/dist/claims.js +39 -1
  29. package/dist/company-brain-context.js +158 -22
  30. package/dist/docs.js +48 -9
  31. package/dist/doctor-pulse-cron.js +194 -0
  32. package/dist/doctor-rede.js +149 -0
  33. package/dist/doctor.js +89 -9
  34. package/dist/entrega-pr.js +20 -8
  35. package/dist/esgotamentos.js +181 -0
  36. package/dist/experiencia.js +12 -2
  37. package/dist/fabrica-estado.js +14 -6
  38. package/dist/fabrica-publicar.js +6 -0
  39. package/dist/fechamento.js +20 -66
  40. package/dist/forja.js +31 -7
  41. package/dist/gates.js +5 -1
  42. package/dist/hitl-contract.js +59 -4
  43. package/dist/hitl-curto.js +11 -6
  44. package/dist/hitl-gates.js +5 -0
  45. package/dist/hitl-lote.js +5 -2
  46. package/dist/hitl-resumo.js +9 -0
  47. package/dist/hitl-tempo-parado.js +201 -0
  48. package/dist/hitl-texto-livre.js +5 -5
  49. package/dist/horario.js +27 -0
  50. package/dist/hosts.js +52 -10
  51. package/dist/impedimento.js +205 -0
  52. package/dist/index.js +391 -74
  53. package/dist/indice-vetorial.js +87 -10
  54. package/dist/indice.js +3 -1
  55. package/dist/init.js +17 -0
  56. package/dist/integracoes-locais.js +2 -0
  57. package/dist/intelligence-graph-cli.js +91 -8
  58. package/dist/intelligence-graph-contexto.js +215 -0
  59. package/dist/intelligence-graph-parsers.js +6 -4
  60. package/dist/leases.js +664 -87
  61. package/dist/ledger-stats.js +15 -1
  62. package/dist/ledger.js +3 -0
  63. package/dist/licoes.js +1 -0
  64. package/dist/maestro-runtime.js +4 -1
  65. package/dist/manifest.js +20 -1
  66. package/dist/master-nota.js +14 -5
  67. package/dist/master.js +129 -12
  68. package/dist/mcp-artifacts.js +34 -3
  69. package/dist/mcp-git.js +20 -3
  70. package/dist/mcp-grafo-worker.js +5 -3
  71. package/dist/mcp-grafo.js +50 -4
  72. package/dist/mcp-server.js +14 -1
  73. package/dist/memoria.js +48 -6
  74. package/dist/network-roadmap.js +191 -16
  75. package/dist/ocupacao.js +11 -3
  76. package/dist/orkmind.js +90 -5
  77. package/dist/orquestracao.js +12 -10
  78. package/dist/parado-no-condutor.js +188 -32
  79. package/dist/phase.js +68 -15
  80. package/dist/policies.js +52 -1
  81. package/dist/portfolio.js +4 -1
  82. package/dist/projeto-alvo.js +5 -1
  83. package/dist/prompts.js +36 -1
  84. package/dist/prova-ativacao.js +75 -6
  85. package/dist/pulse-delivery.js +34 -2
  86. package/dist/pulse.js +49 -3
  87. package/dist/ratelimit.js +20 -4
  88. package/dist/recall.js +3 -1
  89. package/dist/rede-adesao.js +399 -0
  90. package/dist/rede-forja.js +289 -0
  91. package/dist/rede-projetos.js +239 -0
  92. package/dist/rede-status.js +336 -0
  93. package/dist/rede.js +980 -0
  94. package/dist/retry.js +69 -2
  95. package/dist/roadmap-status.js +23 -3
  96. package/dist/runtime-profiles.js +22 -1
  97. package/dist/saida-segura.js +58 -0
  98. package/dist/session-watcher-claude.js +113 -23
  99. package/dist/sessoes-contas.js +187 -0
  100. package/dist/sessoes-inventario.js +6 -18
  101. package/dist/sessoes.js +83 -2
  102. package/dist/ship.js +85 -19
  103. package/dist/thread.js +192 -13
  104. package/dist/worktree.js +5 -2
  105. package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
  106. package/package.json +7 -3
  107. package/references/definition-of-done.md +1 -1
  108. package/skills/README.md +1 -1
  109. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  110. package/skills/phases/master-metrics/SKILL.md +8 -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
@@ -141,6 +141,9 @@ exports.TIPOS_DE_EVENTO = {
141
141
  // I-33 (D5): troca de perfil ou de runtime depois de falha da conta (quem, de qual para
142
142
  // qual, evidencia e razao). Faz parte do catalogo, nao de literal solto.
143
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',
144
147
  fixAberto: 'go_fix_opened',
145
148
  fixDespachado: 'go_fix_dispatched',
146
149
  reverifyConcluido: 'check_reverify',
package/dist/licoes.js CHANGED
@@ -66,6 +66,7 @@ exports.DICA_DO_MOTIVO = {
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
71
  // Fatia 2 do ensaio da 0.5.0 (P6): o que o CI do projeto roda, e nao o script do Orkastery.
71
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`)',
@@ -75,8 +75,11 @@ const shipView = zod_1.z.union([
75
75
  zod_1.z.object({
76
76
  tipo: zod_1.z.literal('ship_done'), ts: shipTs, pushVerificado: zod_1.z.boolean(), mergeSha: shipSha,
77
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(),
78
81
  fonteDaProva: zod_1.z.string().min(1).max(512).refine(v => !/[\u0000-\u001f\u007f]/.test(v)).nullable().optional(),
79
- }).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')),
80
83
  zod_1.z.object({ tipo: zod_1.z.enum(['ship_started', 'ship_blocked']), ts: shipTs }),
81
84
  ]);
82
85
  function operationalSources(reader, threads, native = [], now = new Date().toISOString(), threadScope) {
package/dist/manifest.js CHANGED
@@ -356,6 +356,16 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
356
356
  avisos.push(`runtime_profiles.${nome} deve ser true ou false; vale o padrao ${padrao} (${JSON.stringify(v)} nao reconhecido)`);
357
357
  return padrao;
358
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
+ };
359
369
  const liveness = mapa(dados.liveness);
360
370
  const silencioMaxMin = numero(liveness.silencio_max_min, 10);
361
371
  if (!Number.isFinite(silencioMaxMin) || silencioMaxMin <= 0)
@@ -456,6 +466,11 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
456
466
  '(o `ork` nao adivinha DSN, e base alheia e pior do que base nenhuma)');
457
467
  }
458
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
+ }
459
474
  const bytes = Buffer.byteLength(bruto, 'utf8');
460
475
  if (bytes > exports.LIMITE_MANIFESTO_BYTES) {
461
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)`);
@@ -491,7 +506,9 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
491
506
  worktree: {
492
507
  base_branch: texto(worktree.base_branch, 'main'),
493
508
  dir: texto(worktree.dir, '.claude/worktrees'),
494
- 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),
495
512
  },
496
513
  verify: {
497
514
  build: verify.build === null || verify.build === undefined ? undefined : texto(verify.build, ''),
@@ -522,6 +539,7 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
522
539
  runtime_profiles: {
523
540
  rotate_same_runtime_on_quota: chaveDeRotacao('rotate_same_runtime_on_quota', true),
524
541
  rotate_same_runtime_on_auth: chaveDeRotacao('rotate_same_runtime_on_auth', true),
542
+ distribuir: distribuicaoDePerfis(),
525
543
  },
526
544
  // Bloco B3: o limite de escalacao tem padrao, e o padrao e conservador. Manifesto
527
545
  // sem bloco `retry:` continua andando com 3 tentativas e janela estimada de 60 min,
@@ -540,6 +558,7 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
540
558
  cli: texto(memory.cli, 'orkmind'),
541
559
  tenant: texto(memory.tenant, '') || nomeProjeto,
542
560
  timeout_ms: numero(memory.timeout_ms, 15000),
561
+ universo_timeout_ms: typeof prazoDoUniverso === 'number' ? prazoDoUniverso : orkmind_1.TIMEOUT_DO_UNIVERSO_MS,
543
562
  ...(embedding ? { embedding } : {}),
544
563
  },
545
564
  // Bloco B5: governanca de custo dos auditores. Ausente no manifesto quer dizer
@@ -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,8 +150,39 @@ 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
  }
@@ -231,6 +262,6 @@ function adicionarClaimMcp(raiz, id, entrada) {
231
262
  if ((0, thread_1.lerThread)(raiz, id).status === 'fechada')
232
263
  throw Error('mcp.claim.closed');
233
264
  // Strings sao dados da claim; a reexecucao MCP deve usar o executor confinado.
234
- return (0, claims_1.adicionarClaim)(raiz, id, entrada);
265
+ return (0, claims_1.adicionarClaim)(raiz, id, { ...entrada, semProvaLocal: true });
235
266
  }));
236
267
  }
package/dist/mcp-git.js CHANGED
@@ -195,7 +195,9 @@ function arvoreMetadados(root, alvo, opcoes = {}) {
195
195
  ? '; empacote os objetos soltos sem perda com git repack -d (git count-objects -v mostra quantos)' : ''));
196
196
  if (profundidade > 32)
197
197
  falha(`metadata.unsupported: mais de 32 niveis em ${rotulo(alvo)}`);
198
- const st = fs.lstatSync(f);
198
+ const st = fs.lstatSync(f, { throwIfNoEntry: false });
199
+ if (!st)
200
+ return; // Uma retomada pode remover a fila ou o candidato depois da enumeracao.
199
201
  if (st.isSymbolicLink())
200
202
  falha(`metadata.unsafe: link simbolico em ${rotulo(f)}`);
201
203
  if (!st.isDirectory() && !st.isFile())
@@ -205,9 +207,24 @@ function arvoreMetadados(root, alvo, opcoes = {}) {
205
207
  `tire o vinculo sem perder conteudo: cp -p ${rotulo(f)} ${rotulo(f + '.tmp')} && mv ${rotulo(f + '.tmp')} ${rotulo(f)}`);
206
208
  if (f !== root && !dentro(root, f))
207
209
  falha(`metadata.unsafe: ${rotulo(f)} fora de ${path.basename(root)}`);
208
- if (st.isDirectory())
209
- for (const n of fs.readdirSync(f))
210
+ if (st.isDirectory()) {
211
+ let entradas;
212
+ try {
213
+ entradas = fs.readdirSync(f, { encoding: 'buffer' });
214
+ }
215
+ catch (e) {
216
+ if (e.code === 'ENOENT')
217
+ return;
218
+ throw e;
219
+ }
220
+ for (const bytes of entradas) {
221
+ const n = bytes.toString('utf8');
222
+ // Perda de bytes nao e ENOENT transitorio: o nome decodificado pode nem apontar para esta entrada.
223
+ if (!Buffer.from(n, 'utf8').equals(bytes))
224
+ falha(`metadata.unsafe: nome fora de UTF-8 em ${rotulo(f)}`);
210
225
  visitar(path.join(f, n), profundidade + 1);
226
+ }
227
+ }
211
228
  };
212
229
  if (fs.realpathSync(alvo) !== alvo)
213
230
  falha(`metadata.unsafe: ${rotulo(alvo)} passa por link simbolico`);
@@ -38,7 +38,7 @@ exports.rodarConsulta = rodarConsulta;
38
38
  * RM-031 KG5 (D3, D8): o worker da consulta do grafo pelo MCP.
39
39
  *
40
40
  * Roda num processo filho, aberto pelo `mcp-grafo.ts` com o ambiente minimo do MCP. Le `{raiz, argv}`
41
- * do stdin, confere que o cwd e a raiz, aceita so as quatro consultas no argv que as tools montam (com
41
+ * do stdin, confere que o cwd e a raiz, aceita so as cinco consultas no argv que as tools montam (com
42
42
  * `--json` e `--teto-bytes`), carrega o manifesto da raiz (a worktree da thread) e chama o
43
43
  * `executarGrafo` com o argv que a CLI receberia: a resposta e a do `ork grafo`, byte a byte. O `main` do
44
44
  * `ork` nao roda aqui, entao nada do argv vira `--projeto`, `--version` ou `--help`.
@@ -52,7 +52,8 @@ const zod_1 = require("zod");
52
52
  const intelligence_graph_cli_1 = require("./intelligence-graph-cli");
53
53
  const manifest_1 = require("./manifest");
54
54
  const estado_thread_1 = require("./estado-thread");
55
- const CONSULTAS = Object.freeze({ vizinhos: 1, chamadores: 1, importadores: 1, caminho: 2 });
55
+ const mcp_grafo_1 = require("./mcp-grafo");
56
+ const CONSULTAS = Object.freeze({ vizinhos: 1, chamadores: 1, importadores: 1, caminho: 2, contexto: 1 });
56
57
  const OPCAO = /^--(profundidade|sentido|tipo|limite|teto-bytes)=[a-z0-9_,]{1,120}$/;
57
58
  const PEDIDO = zod_1.z.object({
58
59
  raiz: zod_1.z.string().min(1).max(4096).refine((p) => path.isAbsolute(p), 'raiz absoluta'),
@@ -78,7 +79,8 @@ function rodarConsulta(entrada, cwd, escrever) {
78
79
  const carregado = (0, manifest_1.exigirManifesto)(raiz);
79
80
  if (fs.realpathSync(carregado.raiz) !== fs.realpathSync(raiz))
80
81
  throw Error('grafo.mcp.worker: o manifesto nao e o da raiz pedida');
81
- return (0, intelligence_graph_cli_1.executarGrafo)(argv, { raiz: carregado.raiz, estado: (0, estado_thread_1.raizDoEstado)(carregado.raiz), repositorio: carregado.manifesto.project.name, escrever });
82
+ return (0, intelligence_graph_cli_1.executarGrafo)(argv, { raiz: carregado.raiz, estado: (0, estado_thread_1.raizDoEstado)(carregado.raiz), repositorio: carregado.manifesto.project.name,
83
+ contextoDaThread: (id) => (0, mcp_grafo_1.lerEntradaDaThread)(carregado.raiz, id), escrever });
82
84
  }
83
85
  if (require.main === module) {
84
86
  try {
package/dist/mcp-grafo.js CHANGED
@@ -35,16 +35,17 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.LIMITE_MAXIMO_DO_MCP = exports.PROFUNDIDADE_MAXIMA_DO_MCP = exports.TIPOS_DE_ARESTA_DO_MCP = exports.PRAZO_DA_CONSULTA_MS = exports.TETO_MAXIMO = exports.TETO_MINIMO = exports.TETO_PADRAO = exports.TOOLS_DO_GRAFO = void 0;
37
37
  exports.grafoLigado = grafoLigado;
38
+ exports.lerEntradaDaThread = lerEntradaDaThread;
38
39
  exports.argvDaTool = argvDaTool;
39
40
  exports.consultarPeloWorker = consultarPeloWorker;
40
41
  exports.registrarConsultasDoGrafo = registrarConsultasDoGrafo;
41
42
  /**
42
43
  * RM-031 KG5: a consulta do grafo de codigo pelas fases, pelo MCP do projeto.
43
44
  *
44
- * Quatro tools de leitura com o contrato do `ork grafo` (D6): cada uma roda, na worktree da thread, o
45
+ * Cinco tools de leitura com o contrato do `ork grafo` (D6): cada uma roda, na worktree da thread, o
45
46
  * argv que a CLI receberia (`<consulta> ... --json --teto-bytes N`) e devolve o que ela escreve, sem
46
47
  * transformar: a resposta e JSON `ork.code-graph-query/v0` com a evidencia de cada aresta e no maximo
47
- * `tetoBytes` bytes (D4); a recusa da consulta sai compacta e nao passa pelo teto.
48
+ * `tetoBytes` bytes (D4); contexto usa ork.thread-graph-context/v2. A recusa nao passa pelo teto.
48
49
  *
49
50
  * Desligadas por padrao (D2): so existem com `grafo.mcp: true` no manifesto da raiz, lido no startup, e
50
51
  * cada chamada confere a flag de novo. A consulta roda num worker (D3), um processo filho por chamada,
@@ -58,7 +59,10 @@ const path = __importStar(require("node:path"));
58
59
  const node_child_process_1 = require("node:child_process");
59
60
  const zod_1 = require("zod");
60
61
  const mcp_git_1 = require("./mcp-git");
61
- exports.TOOLS_DO_GRAFO = ['ork_grafo_vizinhos', 'ork_grafo_chamadores', 'ork_grafo_importadores', 'ork_grafo_caminho'];
62
+ const mcp_artifacts_1 = require("./mcp-artifacts");
63
+ const thread_1 = require("./thread");
64
+ const estado_thread_1 = require("./estado-thread");
65
+ exports.TOOLS_DO_GRAFO = ['ork_grafo_vizinhos', 'ork_grafo_chamadores', 'ork_grafo_importadores', 'ork_grafo_caminho', 'ork_grafo_contexto'];
62
66
  /** D4: o teto das respostas, em bytes do JSON. */
63
67
  exports.TETO_PADRAO = 32768;
64
68
  exports.TETO_MINIMO = 4096;
@@ -134,7 +138,49 @@ const DEFINICOES = [
134
138
  schema: zod_1.z.object({ threadId, de: no(`origem: ${DESCRICAO_DO_NO}`), para: no('destino, na mesma forma'), sentido, tipos, tetoBytes }).strict(),
135
139
  argv: (a) => ['caminho', a.de, a.para, ...opcoes(a)],
136
140
  },
141
+ {
142
+ nome: 'ork_grafo_contexto',
143
+ descricao: 'Pacote deterministico da thread: diff da worktree contra base (ignorado sem worktree), GOAL, PLAN e claims; fan-in agregado e evidencias compactas. '
144
+ + 'JSON ork.thread-graph-context/v2 limitado em bytes, refs locais, prioridade entre arquivos e por proximidade ao diff. Sem indice do HEAD, recusa com ork grafo indexar.',
145
+ schema: zod_1.z.object({ threadId, tetoBytes }).strict(),
146
+ argv: (a) => ['contexto', a.threadId, ...opcoes(a)],
147
+ },
137
148
  ];
149
+ /** Leitura compartilhada por CLI e worker, fora da familia do grafo. Nunca escreve estado. */
150
+ function lerEntradaDaThread(raiz, id) {
151
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(id))
152
+ throw Error('grafo.contexto.thread-invalida: id invalido');
153
+ const projeto = fs.realpathSync((0, estado_thread_1.raizDoEstado)(raiz));
154
+ (0, mcp_artifacts_1.validarArquivosEstadoMcp)(projeto, id);
155
+ const t = (0, thread_1.lerThread)(projeto, id);
156
+ const cwd = fs.realpathSync(t.worktree ?? projeto);
157
+ if (cwd !== projeto && !cwd.startsWith(projeto + path.sep))
158
+ throw Error('grafo.contexto.escopo: worktree fora do projeto');
159
+ if (fs.realpathSync((0, estado_thread_1.raizDoEstado)(cwd)) !== projeto)
160
+ throw Error('grafo.contexto.escopo: worktree de outro repositorio');
161
+ // Em uma WT, consultar outra thread daria um pacote de outra arvore silenciosamente.
162
+ const origem = fs.realpathSync(raiz);
163
+ if (origem !== projeto && origem !== cwd)
164
+ throw Error('grafo.contexto.escopo: thread de outra worktree');
165
+ if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(t.base.commit))
166
+ throw Error('grafo.contexto.base-invalida: thread sem commit base');
167
+ const git = (args) => {
168
+ const r = (0, node_child_process_1.spawnSync)('git', ['-c', 'core.fsmonitor=false', ...args], {
169
+ cwd, env: { ...(0, mcp_git_1.ambienteGitMcp)(), GIT_OPTIONAL_LOCKS: '0' }, encoding: 'utf8', timeout: 10000, maxBuffer: 8 * 1024 * 1024,
170
+ });
171
+ if (r.error || r.status !== 0)
172
+ throw Error('grafo.contexto.git-falhou: nao foi possivel ler HEAD/base/diff da thread');
173
+ return r.stdout;
174
+ };
175
+ const head = git(['rev-parse', '--verify', 'HEAD']).trim();
176
+ const diff = !t.worktree || cwd === projeto ? [] : git(['diff', '--no-ext-diff', '--no-textconv', '--no-renames', '--name-only', '-z', t.base.commit, '--']).split('\0').filter(Boolean);
177
+ const goal = (0, mcp_artifacts_1.lerArtefatoMcp)(projeto, id, 'goal').conteudo;
178
+ const plan = (0, mcp_artifacts_1.lerArtefatoMcp)(projeto, id, 'plan').conteudo;
179
+ const claims = (0, mcp_artifacts_1.listarClaimsMcp)(projeto, id).filter((c) => c.estado !== 'retirada').map(({ id, arquivo }) => ({ id, arquivo }));
180
+ if (head !== git(['rev-parse', '--verify', 'HEAD']).trim())
181
+ throw Error('grafo.contexto.revisao-mudou: HEAD mudou durante a leitura; repita');
182
+ return { raiz: cwd, head, entrada: { thread: id, base: t.base.commit, diff, diffEstado: !t.worktree || cwd === projeto ? 'ignorado-sem-worktree' : 'coletado-na-worktree', goal, plan, claims } };
183
+ }
138
184
  /** D9: o argv que a tool monta para os argumentos dados; a medida offline usa o mesmo. */
139
185
  function argvDaTool(nome, args) {
140
186
  const d = DEFINICOES.find((x) => x.nome === nome);
@@ -204,7 +250,7 @@ function consultarPeloWorker(raiz, argv, opcoesDoWorker = {}) {
204
250
  }
205
251
  const texto = (t, isError = false) => ({ content: [{ type: 'text', text: t }], ...(isError ? { isError: true } : {}) });
206
252
  /**
207
- * As quatro tools, chamadas pelo servidor so com a flag ligada no startup. `carregar` e `thread` sao os
253
+ * As cinco tools, chamadas pelo servidor so com a flag ligada no startup. `carregar` e `thread` sao os
208
254
  * do servidor: o projeto fixado e a thread conferida (escopo da sessao filha e worktree confinada).
209
255
  */
210
256
  function registrarConsultasDoGrafo(registrar, contexto) {