@orkastery/cli 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/adapters/claude-code/README.md +11 -2
  2. package/adapters/claude-code/commands/onboarding.md +3 -2
  3. package/adapters/openclaw/README.md +11 -0
  4. package/adapters/openclaw/dist/index.js +1 -1
  5. package/adapters/openclaw/openclaw.plugin.json +1 -1
  6. package/adapters/openclaw/package.json +1 -1
  7. package/adapters/openclaw/src/index.ts +1 -1
  8. package/dist/adapters/claude-bg.js +20 -6
  9. package/dist/auditrun.js +1 -1
  10. package/dist/claim-lint.js +3 -1
  11. package/dist/docs.js +71 -3
  12. package/dist/doctor.js +209 -32
  13. package/dist/entrega-pr.js +7 -0
  14. package/dist/fabrica-estado.js +21 -3
  15. package/dist/fix.js +3 -3
  16. package/dist/hitl-resumo.js +24 -1
  17. package/dist/hosts.js +7 -3
  18. package/dist/index.js +42 -14
  19. package/dist/init.js +11 -5
  20. package/dist/integracoes-locais.js +2 -0
  21. package/dist/intelligence-graph-cli.js +181 -27
  22. package/dist/intelligence-graph-contract.js +71 -12
  23. package/dist/intelligence-graph-extract-md.js +41 -23
  24. package/dist/intelligence-graph-extract-ts.js +181 -11
  25. package/dist/intelligence-graph-extract.js +186 -9
  26. package/dist/intelligence-graph-index.js +205 -24
  27. package/dist/intelligence-graph-repo.js +10 -0
  28. package/dist/leases.js +16 -0
  29. package/dist/ledger.js +9 -0
  30. package/dist/licoes.js +5 -4
  31. package/dist/liveness.js +2 -1
  32. package/dist/maestro-runtime.js +3 -0
  33. package/dist/manifest.js +24 -0
  34. package/dist/mcp-artifacts.js +2 -0
  35. package/dist/mcp-git.js +60 -12
  36. package/dist/mcp-grafo-worker.js +91 -0
  37. package/dist/mcp-grafo.js +233 -0
  38. package/dist/mcp-install.js +2 -1
  39. package/dist/mcp-server.js +9 -0
  40. package/dist/mcp-ship.js +2 -0
  41. package/dist/mcp-verify.js +3 -1
  42. package/dist/network-roadmap.js +5 -5
  43. package/dist/ocupacao.js +6 -2
  44. package/dist/onboarding.js +9 -4
  45. package/dist/orquestracao.js +2 -1
  46. package/dist/parado-no-condutor.js +933 -0
  47. package/dist/policies.js +27 -2
  48. package/dist/prova-ativacao.js +345 -0
  49. package/dist/pulse-delivery.js +27 -6
  50. package/dist/pulse.js +53 -3
  51. package/dist/roadmap-status.js +142 -7
  52. package/dist/runtime-context.js +5 -1
  53. package/dist/sessoes-inventario.js +7 -1
  54. package/dist/sessoes.js +4 -1
  55. package/dist/ship.js +25 -1
  56. package/dist/thread.js +21 -2
  57. package/dist/util.js +71 -4
  58. package/dist/verify.js +2 -2
  59. package/dist/worktree.js +29 -6
  60. package/eval/casos/onboarding.json +1 -1
  61. package/package.json +1 -1
  62. package/skills/core/onboarding/SKILL.md +3 -2
  63. package/skills/phases/ship-release/SKILL.md +6 -3
@@ -33,13 +33,14 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.versoesDoParser = exports.EXCLUSOES_DA_ARVORE = exports.MODULOS_DO_EXTRATOR = exports.PRAZO_DE_SOBRA_MS = exports.NOME_DE_INDICE = exports.INDICE_SCHEMA = void 0;
36
+ exports.versoesDoParser = exports.EXCLUSOES_DA_ARVORE = exports.MODULOS_DO_EXTRATOR = exports.PRAZO_DE_SOBRA_MS = exports.LIMITE_DE_ANCESTRAIS = exports.NOME_DE_INDICE = exports.INDICE_SCHEMA_ANTERIOR = exports.INDICE_SCHEMA = void 0;
37
37
  exports.impressaoDoExtrator = impressaoDoExtrator;
38
38
  exports.perfilDoIndice = perfilDoIndice;
39
39
  exports.chaveDoIndice = chaveDoIndice;
40
40
  exports.concessaoLocal = concessaoLocal;
41
41
  exports.pastaDoGrafo = pastaDoGrafo;
42
42
  exports.lerIndice = lerIndice;
43
+ exports.escolherBase = escolherBase;
43
44
  exports.construirIndice = construirIndice;
44
45
  exports.indiceDoHead = indiceDoHead;
45
46
  exports.estadoDosIndices = estadoDosIndices;
@@ -61,6 +62,11 @@ exports.limparIndices = limparIndices;
61
62
  * publica, por pasta temporaria e `rename`; a leitura confere tamanho e digest dos bytes.
62
63
  *
63
64
  * O indice nao e autoridade de fato: e projecao descartavel do repositorio. Nada nele concede acesso.
65
+ *
66
+ * RM-031 KG4 (D1, D2, D5, D7): o indice guarda tambem as unidades por arquivo (`unidades.json`), e a
67
+ * construcao sem indice do HEAD parte do indice da revisao ancestral mais proxima com o mesmo perfil:
68
+ * reextrai so o que a mudanca alcanca e grava os mesmos bytes da extracao completa. Sem base que
69
+ * prove isso, extrai completo e diz por que.
64
70
  */
65
71
  const node_crypto_1 = require("node:crypto");
66
72
  const fs = __importStar(require("node:fs"));
@@ -69,9 +75,13 @@ const intelligence_graph_contract_1 = require("./intelligence-graph-contract");
69
75
  const intelligence_graph_extract_1 = require("./intelligence-graph-extract");
70
76
  const intelligence_graph_parsers_1 = require("./intelligence-graph-parsers");
71
77
  const intelligence_graph_repo_1 = require("./intelligence-graph-repo");
72
- exports.INDICE_SCHEMA = 'ork.code-graph-index/v0';
78
+ /** KG4 (D2): a v1 tem as unidades por arquivo; a v0 (KG3) nao serve de base e o `limpar` a remove. */
79
+ exports.INDICE_SCHEMA = 'ork.code-graph-index/v1';
80
+ exports.INDICE_SCHEMA_ANTERIOR = 'ork.code-graph-index/v0';
73
81
  exports.NOME_DE_INDICE = /^idx-[a-f0-9]{64}$/;
74
- const ARQUIVOS = { manifesto: 'indice.json', grafo: 'grafo.json', relatorio: 'relatorio.json' };
82
+ const ARQUIVOS = { manifesto: 'indice.json', grafo: 'grafo.json', relatorio: 'relatorio.json', unidades: 'unidades.json' };
83
+ /** KG4 (D1): quantas revisoes, do HEAD para tras, a escolha da base percorre. */
84
+ exports.LIMITE_DE_ANCESTRAIS = 512;
75
85
  /** Temporario e lixo de construcao interrompida so saem no `limpar` depois deste prazo. */
76
86
  exports.PRAZO_DE_SOBRA_MS = 60 * 60 * 1000;
77
87
  /** D3: os modulos compilados da extracao e do indice. Codigo corrigido sem troca de versao muda a chave. */
@@ -189,6 +199,31 @@ function lerPrivado(arquivo) {
189
199
  fs.closeSync(fd);
190
200
  }
191
201
  }
202
+ /** KG4: as mesmas regras de `lerPrivado` e o tamanho do manifesto, sem ler o conteudo (a consulta nao precisa das unidades). */
203
+ function conferirPrivado(arquivo, bytes) {
204
+ let fd;
205
+ try {
206
+ fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
207
+ }
208
+ catch (e) {
209
+ const codigo = e.code;
210
+ if (codigo === 'ENOENT')
211
+ falha('grafo.indice.corrompido', `${path.basename(arquivo)} ausente`);
212
+ falha('grafo.indice.permissao-invalida', `${path.basename(arquivo)} (${codigo ?? 'erro'})`);
213
+ }
214
+ try {
215
+ const st = fs.fstatSync(fd);
216
+ const dono = uid();
217
+ if (!st.isFile() || st.nlink !== 1 || (dono !== null && st.uid !== dono) || (st.mode & 0o777) !== 0o600) {
218
+ falha('grafo.indice.permissao-invalida', path.basename(arquivo));
219
+ }
220
+ if (st.size !== bytes)
221
+ falha('grafo.indice.corrompido', `${path.basename(arquivo)} nao bate com o tamanho`);
222
+ }
223
+ finally {
224
+ fs.closeSync(fd);
225
+ }
226
+ }
192
227
  function gravarPrivado(arquivo, texto) {
193
228
  const fd = fs.openSync(arquivo, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600);
194
229
  try {
@@ -213,7 +248,11 @@ const HEX64 = /^[a-f0-9]{64}$/;
213
248
  function manifestoValido(m, chave) {
214
249
  const o = m;
215
250
  const inteiro = (n) => Number.isSafeInteger(n) && n >= 0;
251
+ if (o && typeof o === 'object' && o.schema === exports.INDICE_SCHEMA_ANTERIOR) {
252
+ falha('grafo.indice.formato-anterior', `${exports.INDICE_SCHEMA_ANTERIOR} (KG3), sem unidades; o ork grafo limpar o remove`);
253
+ }
216
254
  if (!o || typeof o !== 'object' || o.schema !== exports.INDICE_SCHEMA || o.chave !== chave || o.graph_schema !== intelligence_graph_contract_1.GRAFO_SCHEMA
255
+ || o.units_schema !== intelligence_graph_extract_1.UNIDADES_SCHEMA || !HEX64.test(o.units_digest) || !inteiro(o.units_bytes)
217
256
  || typeof o.revision !== 'string' || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(o.revision)
218
257
  || !HEX64.test(o.graph_digest) || !HEX64.test(o.report_digest) || !inteiro(o.graph_bytes) || !inteiro(o.report_bytes)
219
258
  || typeof o.snapshot_id !== 'string' || typeof o.repository_id !== 'string' || typeof o.tenant_id !== 'string'
@@ -259,7 +298,27 @@ function lerIndice(ctx, chave, opcoes = {}) {
259
298
  }
260
299
  }
261
300
  const relatorio = opcoes.relatorio ? JSON.parse(bytesDoRelatorio.toString('utf8')) : null;
262
- return { dir, manifesto, grafo, relatorio };
301
+ // KG4: as unidades so sao lidas (e o digest conferido) por quem as usa; a consulta confere so o tamanho.
302
+ let unidades = null;
303
+ if (opcoes.unidades) {
304
+ let u;
305
+ try {
306
+ u = JSON.parse(conferido(ARQUIVOS.unidades, manifesto.units_bytes, manifesto.units_digest).toString('utf8'));
307
+ }
308
+ catch (e) {
309
+ if (e instanceof SyntaxError)
310
+ falha('grafo.indice.corrompido', 'unidades.json');
311
+ throw e;
312
+ }
313
+ if (!u || u.schema !== intelligence_graph_extract_1.UNIDADES_SCHEMA || u.snapshot_id !== manifesto.snapshot_id || u.graph_digest !== manifesto.graph_digest
314
+ || !Array.isArray(u.arquivos) || !Array.isArray(u.globais_ts)) {
315
+ falha('grafo.indice.corrompido', 'unidades.json fora do manifesto');
316
+ }
317
+ unidades = u;
318
+ }
319
+ else
320
+ conferirPrivado(path.join(dir, ARQUIVOS.unidades), manifesto.units_bytes);
321
+ return { dir, manifesto, grafo, relatorio, unidades };
263
322
  }
264
323
  /** Embaralhamento reproduzivel (LCG): a permutacao da verificacao nao depende de acaso. */
265
324
  function embaralhar(itens, semente) {
@@ -296,11 +355,88 @@ function removerPasta(grafoDir, nome) {
296
355
  fs.renameSync(alvo, lixo);
297
356
  fs.rmSync(lixo, { recursive: true, force: true });
298
357
  }
358
+ /** O perfil que um manifesto guarda: tudo o que entra na chave, menos a revisao. */
359
+ const perfilDoManifesto = (m) => ({
360
+ repository_id: m.repository_id, tenant_id: m.tenant_id, acl_refs: m.acl_refs, analisadores: m.analisadores, pacotes: m.pacotes, codigo: m.codigo,
361
+ });
362
+ const curta = (r) => r.slice(0, 12);
363
+ /**
364
+ * KG4 (D1, D5): a base e o indice da revisao ancestral mais proxima do HEAD (nas `LIMITE_DE_ANCESTRAIS`
365
+ * do `git rev-list`) com o mesmo perfil. Sem ela, o motivo diz o que havia: indice ancestral de outro
366
+ * extrator, indice do mesmo extrator fora da linha do HEAD (historico reescrito ou outro ramo), indice
367
+ * de formato anterior, ou nenhum. Base que nao passa na leitura e trocada pela proxima, com o erro.
368
+ */
369
+ function escolherBase(ctx, perfil, head, raiz) {
370
+ let dir;
371
+ try {
372
+ dir = pastaDoGrafo(ctx, false);
373
+ }
374
+ catch (e) {
375
+ if (e.message === 'grafo.indice.ausente')
376
+ return { base: null, motivo: 'nenhum indice guardado' };
377
+ throw e;
378
+ }
379
+ const posicao = new Map((0, intelligence_graph_repo_1.revisoesAncestrais)(raiz, exports.LIMITE_DE_ANCESTRAIS).map((r, i) => [r, i]));
380
+ const desejado = (0, intelligence_graph_contract_1.canonico)(perfil), identidade = (0, intelligence_graph_contract_1.canonico)([perfil.repository_id, perfil.tenant_id, perfil.acl_refs]);
381
+ const candidatos = [];
382
+ let outroExtrator = null, foraDaLinha = null, anterior = false;
383
+ for (const nome of fs.readdirSync(dir).sort(intelligence_graph_contract_1.compararUtf8)) {
384
+ if (!exports.NOME_DE_INDICE.test(nome))
385
+ continue;
386
+ let m;
387
+ try {
388
+ pastaPrivada(path.join(dir, nome), false);
389
+ m = manifestoValido(JSON.parse(lerPrivado(path.join(dir, nome, ARQUIVOS.manifesto)).toString('utf8')), nome);
390
+ }
391
+ catch (e) {
392
+ if (e.message.startsWith('grafo.indice.formato-anterior'))
393
+ anterior = true;
394
+ continue;
395
+ }
396
+ if (m.revision === head)
397
+ continue;
398
+ const p = perfilDoManifesto(m), i = posicao.get(m.revision);
399
+ if ((0, intelligence_graph_contract_1.canonico)(p) === desejado) {
400
+ if (i === undefined)
401
+ foraDaLinha ??= m.revision;
402
+ else
403
+ candidatos.push({ chave: nome, revision: m.revision, posicao: i });
404
+ }
405
+ else if (i !== undefined && (0, intelligence_graph_contract_1.canonico)([p.repository_id, p.tenant_id, p.acl_refs]) === identidade && (!outroExtrator || i < outroExtrator.posicao)) {
406
+ const campos = ['analisadores', 'pacotes', 'codigo'].filter((k) => (0, intelligence_graph_contract_1.canonico)(p[k]) !== (0, intelligence_graph_contract_1.canonico)(perfil[k]));
407
+ outroExtrator = { revision: m.revision, campos, posicao: i };
408
+ }
409
+ }
410
+ const erros = [];
411
+ for (const c of candidatos.sort((a, b) => a.posicao - b.posicao)) {
412
+ try {
413
+ const lido = lerIndice(ctx, c.chave, { grafo: false, unidades: true });
414
+ return { base: { revision: c.revision, chave: c.chave, unidades: lido.unidades }, motivo: null };
415
+ }
416
+ catch (e) {
417
+ erros.push(`${curta(c.revision)}: ${e.message}`);
418
+ }
419
+ }
420
+ if (erros.length)
421
+ return { base: null, motivo: `base ilegivel (${erros.join('; ')})` };
422
+ const motivos = [];
423
+ if (foraDaLinha) {
424
+ motivos.push(`nenhum indice de revisao ancestral nas ultimas ${exports.LIMITE_DE_ANCESTRAIS}; o de ${curta(foraDaLinha)} nao esta entre elas (historico reescrito, outro ramo ou revisao mais antiga)`);
425
+ }
426
+ if (outroExtrator)
427
+ motivos.push(`o extrator mudou desde o indice de ${curta(outroExtrator.revision)} (${outroExtrator.campos.join(', ')})`);
428
+ if (!motivos.length && anterior)
429
+ motivos.push(`so ha indice de formato anterior (${exports.INDICE_SCHEMA_ANTERIOR})`);
430
+ return { base: null, motivo: motivos.length ? motivos.join('; ') : 'nenhum indice de revisao ancestral com este extrator' };
431
+ }
432
+ const textoDasUnidades = (u) => JSON.stringify(u);
299
433
  /**
300
- * Constroi o indice do HEAD limpo, ou confirma o que ja existe. `--forcar` extrai de novo e so
301
- * troca os arquivos se o conteudo mudou; `--verificar` extrai de novo, prova o determinismo com a
302
- * ordem de leitura trocada e reprova se o guardado integro difere; indice que nao passa na leitura
303
- * e substituido.
434
+ * Constroi o indice do HEAD limpo, ou confirma o que ja existe. Sem o indice do HEAD, parte do indice
435
+ * ancestral com o mesmo perfil (KG4) e reextrai so o que a mudanca alcanca; sem base, ou se o
436
+ * incremental falha, extrai completo e diz por que. `--forcar` extrai completo e so troca os arquivos se
437
+ * o conteudo mudou; `--verificar` extrai completo, prova o determinismo com a ordem de leitura trocada,
438
+ * compara com o incremental quando ha base e reprova se o guardado integro difere; indice que nao passa
439
+ * na leitura e substituido. O incremental passa pelas mesmas validacoes da completa antes de publicar.
304
440
  */
305
441
  function construirIndice(ctx, opcoes = {}) {
306
442
  const inicio = process.hrtime.bigint();
@@ -308,10 +444,11 @@ function construirIndice(ctx, opcoes = {}) {
308
444
  const arvore = (0, intelligence_graph_repo_1.revisaoDaArvore)(ctx.raiz);
309
445
  if (arvore.motivo !== null || arvore.head === null)
310
446
  falha('grafo.indice.arvore-nao-limpa', arvore.motivo ?? 'sem-commit');
447
+ const head = arvore.head;
311
448
  const parser = opcoes.parser ?? (0, intelligence_graph_parsers_1.carregarAnalisadores)();
312
449
  const leitura = leituraDo(ctx, opcoes.leitura);
313
450
  const perfil = perfilDoIndice(arvore.raiz, (0, exports.versoesDoParser)(parser), leitura);
314
- const chave = chaveDoIndice(arvore.head, perfil);
451
+ const chave = chaveDoIndice(head, perfil);
315
452
  const grafoDir = pastaDoGrafo(ctx, true);
316
453
  const final = path.join(grafoDir, chave);
317
454
  let motivo = null;
@@ -320,55 +457,96 @@ function construirIndice(ctx, opcoes = {}) {
320
457
  try {
321
458
  const atual = lerIndice(ctx, chave, { grafo: false });
322
459
  // `--verificar` nunca confia no guardado: extrai de novo e compara, como o `--forcar`.
323
- if (!opcoes.forcar && !opcoes.verificar)
324
- return { estado: 'existente', motivo: null, chave, dir: final, manifesto: atual.manifesto, determinismo: null, ms: ms() };
460
+ if (!opcoes.forcar && !opcoes.verificar) {
461
+ return {
462
+ estado: 'existente', motivo: null, chave, dir: final, manifesto: atual.manifesto, determinismo: null, ms: ms(),
463
+ modo: null, base: null, motivo_completo: null, reaproveitamento: null, incremental: null,
464
+ };
465
+ }
325
466
  }
326
467
  catch (e) {
327
468
  motivo = e.message;
328
469
  }
329
470
  }
330
471
  const entrada = (0, intelligence_graph_repo_1.lerRepositorio)(arvore.raiz, leitura);
331
- if (entrada.revision !== arvore.head)
472
+ if (entrada.revision !== head)
332
473
  falha('grafo.indice.arvore-nao-limpa', entrada.revision_unavailable_reason ?? 'revisao-mudou');
333
474
  const fora = (entrada.excluidas ?? []).filter((e) => exports.EXCLUSOES_DA_ARVORE.has(e.motivo)).sort((a, b) => (0, intelligence_graph_contract_1.compararUtf8)(a.path, b.path));
334
475
  if (fora.length) {
335
476
  falha('grafo.indice.arvore-nao-limpa', `rastreado fora da leitura (${fora[0].motivo}: ${fora[0].path}${fora.length > 1 ? ` e mais ${fora.length - 1}` : ''})`);
336
477
  }
337
- const r = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser);
478
+ // KG4 (D1, D5, D7): a base, salvo com `--forcar`.
479
+ let base = null, motivoCompleto = 'pedido com --forcar';
480
+ if (!opcoes.forcar)
481
+ ({ base, motivo: motivoCompleto } = escolherBase(ctx, perfil, head, arvore.raiz));
482
+ let r = null, modo = 'completo';
483
+ if (base && !opcoes.verificar) {
484
+ try {
485
+ r = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser, base.unidades);
486
+ modo = 'incremental';
487
+ }
488
+ catch (e) {
489
+ // O incremental nunca impede o indice: a completa decide, e o motivo fica dito.
490
+ motivoCompleto = `o incremental a partir de ${curta(base.revision)} falhou e a extracao foi completa (${e.message})`;
491
+ r = null;
492
+ }
493
+ }
494
+ if (!r)
495
+ r = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser);
338
496
  const bytesDe = new Map(entrada.fontes.map((f) => [f.path, f.bytes]));
339
497
  const fornecidas = new Map(r.grafo.snapshot.source_manifest.map((m) => {
340
498
  const b = bytesDe.get(m.path);
341
499
  return [m.path, { tipo: (0, intelligence_graph_extract_1.decodificarUtf8)(b) === null ? 'binario' : 'texto', bytes: b }];
342
500
  }));
343
- const conferencia = (0, intelligence_graph_contract_1.conferirFontes)(r.grafo, fornecidas);
501
+ // KG4 (D6): `r.grafo` e o retorno de `validarGrafo` dentro da extracao; validar de novo so repetiria.
502
+ const conferencia = (0, intelligence_graph_contract_1.conferirFontesDoGrafoValidado)(r.grafo, fornecidas);
344
503
  if (conferencia.estado !== 'verificada' || conferencia.evidenciasIndisponiveis > 0)
345
504
  falha('grafo.indice.fontes-nao-conferidas');
346
- let determinismo = null;
505
+ const textoDoGrafo = (0, intelligence_graph_contract_1.canonico)(r.grafo), textoDoRelatorio = (0, intelligence_graph_contract_1.canonico)(r.relatorio), unidades = textoDasUnidades(r.unidades);
506
+ if (sha256(textoDoGrafo) !== r.digest)
507
+ falha('grafo.indice.digest-divergente');
508
+ let determinismo = null, incremental = null;
347
509
  if (opcoes.verificar) {
348
- const relatorio = (0, intelligence_graph_contract_1.sha256DoCanonico)(r.relatorio);
510
+ const relatorio = sha256(textoDoRelatorio), dasUnidades = sha256(unidades);
349
511
  determinismo = [['ordem invertida', [...entrada.fontes].reverse()], ['ordem embaralhada', embaralhar(entrada.fontes, 2026)]]
350
512
  .map(([ordem, fontes]) => {
351
513
  const outra = (0, intelligence_graph_extract_1.extrairGrafo)({ ...entrada, fontes }, parser);
352
- const doOutro = (0, intelligence_graph_contract_1.sha256DoCanonico)(outra.relatorio);
353
- return { ordem, digest: outra.digest, relatorio: doOutro, igual: outra.digest === r.digest && doOutro === relatorio };
514
+ const doOutro = (0, intelligence_graph_contract_1.sha256DoCanonico)(outra.relatorio), unidadesDoOutro = sha256(textoDasUnidades(outra.unidades));
515
+ return {
516
+ ordem, digest: outra.digest, relatorio: doOutro, unidades: unidadesDoOutro,
517
+ igual: outra.digest === r.digest && doOutro === relatorio && unidadesDoOutro === dasUnidades,
518
+ };
354
519
  });
355
520
  const diferente = determinismo.find((d) => !d.igual);
356
521
  if (diferente)
357
522
  falha('grafo.indice.nao-deterministico', diferente.ordem);
523
+ if (base) {
524
+ // KG4 (D7): a prova no repositorio de quem usa: o incremental da base da os mesmos bytes da completa.
525
+ let inc;
526
+ try {
527
+ inc = (0, intelligence_graph_extract_1.extrairGrafo)(entrada, parser, base.unidades);
528
+ }
529
+ catch (e) {
530
+ falha('grafo.indice.incremental-divergente', `o incremental da base ${curta(base.revision)} falhou: ${e.message}`);
531
+ }
532
+ const igual = (0, intelligence_graph_contract_1.canonico)(inc.grafo) === textoDoGrafo && (0, intelligence_graph_contract_1.canonico)(inc.relatorio) === textoDoRelatorio && textoDasUnidades(inc.unidades) === unidades;
533
+ if (!igual)
534
+ falha('grafo.indice.incremental-divergente', `base ${curta(base.revision)}`);
535
+ incremental = { base: base.revision, igual, reaproveitamento: inc.reaproveitamento };
536
+ }
358
537
  }
359
- const textoDoGrafo = (0, intelligence_graph_contract_1.canonico)(r.grafo), textoDoRelatorio = (0, intelligence_graph_contract_1.canonico)(r.relatorio);
360
- if (sha256(textoDoGrafo) !== r.digest)
361
- falha('grafo.indice.digest-divergente');
362
538
  const manifesto = {
363
- schema: exports.INDICE_SCHEMA, chave, revision: arvore.head, ...perfil, graph_schema: intelligence_graph_contract_1.GRAFO_SCHEMA, snapshot_id: r.grafo.snapshot.snapshot_id,
539
+ schema: exports.INDICE_SCHEMA, chave, revision: head, ...perfil, graph_schema: intelligence_graph_contract_1.GRAFO_SCHEMA, snapshot_id: r.grafo.snapshot.snapshot_id,
364
540
  graph_digest: r.digest, graph_bytes: Buffer.byteLength(textoDoGrafo, 'utf8'),
365
541
  report_digest: sha256(textoDoRelatorio), report_bytes: Buffer.byteLength(textoDoRelatorio, 'utf8'),
366
542
  contagens: r.relatorio.contagens,
367
543
  conferencia: { fontes: conferencia.fontesVerificadas.length, evidencias: conferencia.evidenciasVerificadas },
544
+ units_schema: intelligence_graph_extract_1.UNIDADES_SCHEMA, units_digest: sha256(unidades), units_bytes: Buffer.byteLength(unidades, 'utf8'),
368
545
  };
369
546
  const conteudos = {
370
- [ARQUIVOS.manifesto]: (0, intelligence_graph_contract_1.canonico)(manifesto), [ARQUIVOS.grafo]: textoDoGrafo, [ARQUIVOS.relatorio]: textoDoRelatorio,
547
+ [ARQUIVOS.manifesto]: (0, intelligence_graph_contract_1.canonico)(manifesto), [ARQUIVOS.grafo]: textoDoGrafo, [ARQUIVOS.relatorio]: textoDoRelatorio, [ARQUIVOS.unidades]: unidades,
371
548
  };
549
+ const daBase = modo === 'incremental' && base ? { revision: base.revision, chave: base.chave } : opcoes.verificar && base ? { revision: base.revision, chave: base.chave } : null;
372
550
  const tmp = path.join(grafoDir, `.tmp-${(0, node_crypto_1.randomUUID)()}`);
373
551
  pastaPrivada(tmp, true);
374
552
  try {
@@ -376,7 +554,10 @@ function construirIndice(ctx, opcoes = {}) {
376
554
  gravarPrivado(path.join(tmp, nome), texto);
377
555
  sincronizarPasta(tmp);
378
556
  opcoes.antesDePublicar?.();
379
- const resultado = (estado) => ({ estado, motivo, chave, dir: final, manifesto, determinismo, ms: ms() });
557
+ const resultado = (estado) => ({
558
+ estado, motivo, chave, dir: final, manifesto, determinismo, ms: ms(), modo, base: daBase,
559
+ motivo_completo: modo === 'incremental' ? null : motivoCompleto, reaproveitamento: r?.reaproveitamento ?? null, incremental,
560
+ });
380
561
  if (!existia && fs.lstatSync(final, { throwIfNoEntry: false })) {
381
562
  // Corrida: outra construcao publicou a mesma chave durante esta. Vale a dela, se estiver integra.
382
563
  lerIndice(ctx, chave, { grafo: false });
@@ -37,6 +37,7 @@ exports.TENANT_PADRAO = void 0;
37
37
  exports.identidadeDaLeitura = identidadeDaLeitura;
38
38
  exports.revisaoDaArvore = revisaoDaArvore;
39
39
  exports.headsDasArvores = headsDasArvores;
40
+ exports.revisoesAncestrais = revisoesAncestrais;
40
41
  exports.lerRepositorio = lerRepositorio;
41
42
  /**
42
43
  * RM-031 KG2 (D8): leitura do repositorio Git local para o extrator. E a unica borda de E/S do
@@ -118,6 +119,15 @@ function headsDasArvores(diretorio) {
118
119
  const saida = obrigatorio(diretorio, ['worktree', 'list', '--porcelain']).toString('utf8');
119
120
  return [...new Set(saida.split('\n').filter((l) => /^HEAD [0-9a-f]{40,64}$/.test(l)).map((l) => l.slice(5)))].sort();
120
121
  }
122
+ /**
123
+ * RM-031 KG4 (D1): o HEAD e as revisoes ancestrais dele (`git rev-list`), da mais recente para a mais
124
+ * antiga, ate o limite. Sem commit, ou com o Git falhando, nao ha ancestral.
125
+ */
126
+ function revisoesAncestrais(diretorio, limite) {
127
+ const raiz = obrigatorio(diretorio, ['rev-parse', '--show-toplevel']).toString('utf8').trim();
128
+ const r = git(raiz, ['rev-list', `--max-count=${Math.max(1, Math.floor(limite))}`, 'HEAD']);
129
+ return r.ok ? r.saida.toString('utf8').split('\n').filter((l) => /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(l)) : [];
130
+ }
121
131
  /** Le o repositorio que contem `diretorio` e devolve a entrada do extrator. */
122
132
  function lerRepositorio(diretorio, opcoes = {}) {
123
133
  const raiz = obrigatorio(diretorio, ['rev-parse', '--show-toplevel']).toString('utf8').trim();
package/dist/leases.js CHANGED
@@ -78,6 +78,7 @@ exports.adquirirRegiao = adquirirRegiao;
78
78
  exports.tabelaDeLeases = tabelaDeLeases;
79
79
  exports.soltarDaThread = soltarDaThread;
80
80
  exports.soltarThreadFechada = soltarThreadFechada;
81
+ exports.podarRegioesDeThreadsFechadas = podarRegioesDeThreadsFechadas;
81
82
  const fs = __importStar(require("node:fs"));
82
83
  const path = __importStar(require("node:path"));
83
84
  const manifest_1 = require("./manifest");
@@ -634,3 +635,18 @@ function podarThreadsFechadas(raiz, nome, quem) {
634
635
  soltarThreadFechada(raiz, thread, 'poda', quem);
635
636
  }
636
637
  }
638
+ /**
639
+ * RM-037 (fatia 3, defeito 4): a mesma poda, para as conferencias previas do MCP. O `ork_git_commit`
640
+ * conferia `lerLease` antes do `adquirirRegiao` e recusava com `lease.busy` o lease que a ork-companybrai3
641
+ * (fechada) deixou, e soltar o lease de outra thread exigia o dono. Lease e fila de thread fechada sao
642
+ * orfaos: saem aqui, com registro no ledger dela. Lease de thread aberta, vencido ou nao, e o da propria
643
+ * thread seguem como estao, e a conferencia de quem chama decide. De melhor esforco, como no `adquirirRegiao`.
644
+ */
645
+ function podarRegioesDeThreadsFechadas(raiz, nomes, quem) {
646
+ for (const nome of nomes) {
647
+ try {
648
+ podarThreadsFechadas(raiz, nome, quem);
649
+ }
650
+ catch { /* a conferencia de quem chama decide */ }
651
+ }
652
+ }
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"));
@@ -236,6 +237,14 @@ function registrarSeExiste(dirThread, threadId, tipo, dados = {}) {
236
237
  }
237
238
  return evento;
238
239
  }
240
+ /**
241
+ * Fatia 2 do ensaio da 0.5.0 (P2): o evento que um `--dry-run` grava (`dryRun: true`) e ensaio, nao
242
+ * estado. O ledger o guarda como rastro; board, escalonador, monitor (o pulse) e maestro o descartam.
243
+ * O ensaio do ship gravava `gate_blocked human.pending` e a thread aparecia pausada sem ninguem pedir.
244
+ */
245
+ function ehEnsaio(e) {
246
+ return e.dryRun === true;
247
+ }
239
248
  /** Le o ledger inteiro. Linha corrompida vira evento `ledger_corrupted` em vez de quebrar a leitura. */
240
249
  function lerLedger(dirThread) {
241
250
  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,7 +59,7 @@ 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',
@@ -67,7 +67,8 @@ const DICA_DO_MOTIVO = {
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
69
  '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',
70
+ // Fatia 2 do ensaio da 0.5.0 (P6): o que o CI do projeto roda, e nao o script do Orkastery.
71
+ '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
72
  'artifact.missing': 'grave o artefato da fase exatamente no caminho que o prompt pede',
72
73
  };
73
74
  const DICA_DA_CLASSE = {
@@ -116,7 +117,7 @@ function recorrencias(licoes) {
116
117
  const classes = new Map();
117
118
  for (const l of licoes) {
118
119
  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 };
120
+ const r = motivos.get(m) ?? { chave: `motivo:${m}`, tipo: 'motivo', nome: m, threads: [], vezes: 0, dica: exports.DICA_DO_MOTIVO[m] ?? null };
120
121
  r.threads.push(l.thread);
121
122
  motivos.set(m, r);
122
123
  }
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");
@@ -95,6 +96,8 @@ function operationalSources(reader, threads, native = [], now = new Date().toISO
95
96
  events = raw.split('\n').filter(Boolean).map(line => JSON.parse(line));
96
97
  if (events.some(e => typeof e.tipo !== 'string' || e.thread !== t.id))
97
98
  throw Error('invalid');
99
+ // Fatia 2 do ensaio da 0.5.0 (P2): o ensaio (`dryRun: true`) nao vira bloqueio nem entrega incompleta.
100
+ events = events.filter(e => !(0, ledger_1.ehEnsaio)(e));
98
101
  }
99
102
  catch {
100
103
  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)
@@ -534,6 +556,8 @@ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
534
556
  compartilhada: booleano(fabrica.compartilhada, false),
535
557
  remoto: texto(fabrica.remoto, 'origin'),
536
558
  },
559
+ // RM-031 KG5 (D2): expor as tools do grafo no MCP e opt-in do dono.
560
+ grafo: lerGrafo(dados.grafo, avisos),
537
561
  policies: Object.fromEntries(Object.entries(mapa(dados.policies)).map(([k, v]) => [k, texto(v, '')])),
538
562
  };
539
563
  return { caminho: achado.caminho, raiz, legado: achado.legado, bytes, manifesto, erros, avisos };
@@ -157,6 +157,8 @@ function comEscrita(raiz, id, executar) {
157
157
  }
158
158
  }
159
159
  const nome = `path:.orkastery/threads/${id}`;
160
+ // RM-037 (fatia 3, defeito 4): lease e fila de thread fechada sao orfaos e saem antes da conferencia.
161
+ (0, leases_1.podarRegioesDeThreadsFechadas)(raiz, [nome], id);
160
162
  if ((0, leases_1.lerLease)(raiz, nome) || (0, leases_1.leasesColidentes)(raiz, nome).length)
161
163
  throw Error('lease.busy: estado canonico ocupado');
162
164
  const r = (0, leases_1.adquirirRegiao)(raiz, nome, { thread: id, motivo: 'MCP documento ou claim', ttlMs: 30000, retomarVencido: false });