@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
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.fxPedidoColado = exports.PROMPTS_DE_CONDUCAO = exports.FRASE_DO_PEDIDO_COLADO = exports.PEDIDO_COLADO_DO_INCIDENTE = void 0;
37
+ /**
38
+ * RM-057 (fatia 2): o canario do incidente de 01/10/2026.
39
+ *
40
+ * Na noite de 01 para 02/10 o condutor recebeu, colado, o pedido do dono com push e merge
41
+ * autorizados ate um horario, parou para pedir um "confirmo" em texto livre e a fabrica ficou mais
42
+ * de 10 h sem produzir. O canario refaz a conducao inteira em sandbox SIMULADO e exige que o mesmo
43
+ * pedido colado siga sem parar:
44
+ *
45
+ * 1. o modo sai da #TAG do texto colado (`#Auto`), pelo nucleo;
46
+ * 2. o `#Auto` nao abre pausa humana na fase;
47
+ * 3. a duvida dentro da autorizacao vira decisao informada (`ork decisao registrar`), sem pergunta;
48
+ * 4. o "confirmo" em texto livre que o condutor tentaria abrir e recusado com
49
+ * `hitl.selecao.texto-livre`, sem gravar nada no ledger;
50
+ * 5. o pulse nao mostra ninguem esperando o dono, e o tempo parado por HITL de conducao e zero;
51
+ * 6. os quatro adaptadores de conducao (Claude Code, Codex, Hermes e OpenClaw) dizem que o pedido
52
+ * colado com autorizacao explicita vale como instrucao do dono.
53
+ *
54
+ * Nada aqui e resposta humana nem rede: e a forma da conducao, provada contra o codigo real.
55
+ */
56
+ const fs = __importStar(require("node:fs"));
57
+ const path = __importStar(require("node:path"));
58
+ const decisao_autonoma_1 = require("./decisao-autonoma");
59
+ const hitl_contract_1 = require("./hitl-contract");
60
+ const hitl_gates_1 = require("./hitl-gates");
61
+ const ledger_1 = require("./ledger");
62
+ const ledger_stats_1 = require("./ledger-stats");
63
+ const modos_1 = require("./modos");
64
+ const pulse_1 = require("./pulse");
65
+ const sandbox_1 = require("./sandbox");
66
+ const thread_1 = require("./thread");
67
+ /** O pedido do dono como chegou colado no incidente, com a autorizacao explicita e o prazo. */
68
+ exports.PEDIDO_COLADO_DO_INCIDENTE = '#Auto Conduza as threads RM-032, RM-056, RM-053, RM-040 e RM-055 no orkastery. ' +
69
+ 'Push e merge autorizados até 02/10 às 12h. Não me pergunte nada: dúvida menor vira decisão registrada e segue.';
70
+ /** A frase que os quatro adaptadores de conducao carregam desde a RM-057. */
71
+ exports.FRASE_DO_PEDIDO_COLADO = 'o pedido que ele colou com autorização explícita vale como instrução dele';
72
+ /** Onde cada host le a regra: o comando, as skills e as descricoes das tools do OpenClaw. */
73
+ exports.PROMPTS_DE_CONDUCAO = [
74
+ 'adapters/claude-code/commands/ork.md',
75
+ 'adapters/codex/skills/ork/SKILL.md',
76
+ 'adapters/hermes/skills/orkastery-devmaster/SKILL.md',
77
+ 'adapters/openclaw/dist/index.js',
78
+ ];
79
+ exports.fxPedidoColado = {
80
+ id: 'fx-pedido-colado',
81
+ sobre: 'SIMULADO: o pedido colado com autorização explícita segue sem parar (incidente de 01/10, RM-057)',
82
+ precisaDeGit: true,
83
+ rodar: (ctx) => {
84
+ const s = (0, sandbox_1.sandboxGit)('pedido-colado');
85
+ try {
86
+ const modo = (0, modos_1.extrairTagDoPedido)(exports.PEDIDO_COLADO_DO_INCIDENTE);
87
+ const t = (0, thread_1.novaThread)(s.carregado, { nome: 'pedido-colado', modo: modo ?? 'classic' }).thread;
88
+ const dir = (0, thread_1.dirThread)(s.dir, t.id);
89
+ (0, ledger_1.registrar)(dir, t.id, 'phase_result', { fase: t.faseAtual });
90
+ let semPausaDoModo = false;
91
+ try {
92
+ (0, hitl_gates_1.abrirPedidoGate)(s.dir, t.id);
93
+ }
94
+ catch (e) {
95
+ semPausaDoModo = /não prevê pausa/.test(e.message);
96
+ }
97
+ const decisao = (0, decisao_autonoma_1.registrarDecisao)(s.dir, t.id, {
98
+ decidido: 'Seguir com push e merge dentro da autorização colada', porque: 'o dono autorizou até 02/10 às 12h',
99
+ comoMudar: 'reverter o merge', custoDeReverter: { agora: 'um revert', depois: 'um revert' },
100
+ criterio: { tipo: 'ledger', referencia: `${t.id}#evento:1` }, quemDecidiu: 'canario-SIMULADO', evidencia: 'pedido colado',
101
+ });
102
+ const confirmo = {
103
+ contrato: hitl_contract_1.CONTRATO_HITL_V2, id: 'confirmo-SIMULADO', thread: t.id, fase: t.faseAtual, modo: t.modo,
104
+ criadoEm: new Date().toISOString(), profundidade: 'resumo', classe: 'pergunta',
105
+ alvo: { tipo: 'gate', sobre: 'premissas' }, motivo: 'human.pending', pergunta: 'Posso seguir com o push e o merge?',
106
+ alternativas: [
107
+ { letra: 'a', texto: 'Seguir', acao: 'aprovar', consequencia: 'segue', recomendada: true, porque: 'autorizado no pedido' },
108
+ { letra: 'b', texto: 'Parar', acao: 'recusar', consequencia: 'para' },
109
+ { letra: 'c', texto: 'Esperar', acao: 'esperar', consequencia: 'fica' },
110
+ ],
111
+ corpo: ['Responda confirmo para eu continuar'], tipoDeResposta: 'aberta', irreversivel: false, codigo: 'K3F9',
112
+ prazo: new Date(Date.now() + 3_600_000).toISOString(), acaoPadraoAoExpirar: 'esperar',
113
+ respostaAceita: { tipo: 'texto', maxCaracteres: 200 },
114
+ };
115
+ const antes = (0, ledger_1.lerLedger)(dir).length;
116
+ let confirmoRecusado = 'aceito';
117
+ try {
118
+ (0, hitl_gates_1.registrarPedidoHitl)(s.dir, confirmo);
119
+ }
120
+ catch (e) {
121
+ confirmoRecusado = e instanceof hitl_contract_1.SelecaoRecusada ? e.motivo : `outro: ${e.message}`;
122
+ }
123
+ const nadaGravadoNaRecusa = (0, ledger_1.lerLedger)(dir).length === antes;
124
+ const pulse = (0, pulse_1.montarPulse)(s.carregado, { consulta: { ok: true, sessoes: [], detalhe: 'runtime SIMULADO' } });
125
+ const parado = (0, ledger_stats_1.coletarEstatisticas)(s.dir, { desde: '1h', thread: t.id }).hitlDeConducao;
126
+ // No dist do OpenClaw a frase e uma concatenacao de literais: as emendas `' + '` saem antes da busca.
127
+ const umaLinha = (x) => x.replace(/'\s*\+\s*'/g, '').replace(/\s+/g, ' ');
128
+ const adaptadoresComARegra = exports.PROMPTS_DE_CONDUCAO.filter((rel) => {
129
+ const arquivo = path.join(ctx.catalogo, rel);
130
+ return fs.existsSync(arquivo) && umaLinha(fs.readFileSync(arquivo, 'utf8')).includes(exports.FRASE_DO_PEDIDO_COLADO);
131
+ }).length;
132
+ return {
133
+ simulado: true,
134
+ modoDoPedido: modo,
135
+ semPausaDoModo,
136
+ decisaoInformada: decisao.pedido.classe === 'decidido',
137
+ confirmoRecusado,
138
+ nadaGravadoNaRecusa,
139
+ humanosNoPulse: pulse.resumo.humanos,
140
+ perguntasDeConducao: parado.pedidos,
141
+ tempoParadoMs: parado.paradoMs,
142
+ adaptadoresComARegra,
143
+ };
144
+ }
145
+ finally {
146
+ s.limpar();
147
+ }
148
+ },
149
+ };
package/dist/canarios.js CHANGED
@@ -71,6 +71,7 @@ const canarios_pulse_1 = require("./canarios-pulse");
71
71
  const canarios_sensores_1 = require("./canarios-sensores");
72
72
  const canarios_hitl_1 = require("./canarios-hitl");
73
73
  const canarios_i43_1 = require("./canarios-i43");
74
+ const canarios_rm057_1 = require("./canarios-rm057");
74
75
  const objective_1 = require("./objective");
75
76
  const maestro_discovery_1 = require("./maestro-discovery");
76
77
  const maestro_cli_1 = require("./maestro-cli");
@@ -373,6 +374,7 @@ exports.CANARIOS = [
373
374
  canarios_sensores_1.CANARIO_SENSORES,
374
375
  ...canarios_hitl_1.CANARIOS_HITL,
375
376
  ...canarios_i43_1.CANARIOS_I43,
377
+ canarios_rm057_1.fxPedidoColado,
376
378
  ];
377
379
  /** Um canario por id, ou null quando a fixture aponta para um canario que nao existe. */
378
380
  function canarioPorId(id) {
package/dist/ci.js CHANGED
@@ -46,6 +46,8 @@ exports.prepararBundleCi = prepararBundleCi;
46
46
  exports.bundleDaBranch = bundleDaBranch;
47
47
  exports.executarCiDaBranch = executarCiDaBranch;
48
48
  exports.executarBundleCi = executarBundleCi;
49
+ /** GitHub CI como CHECK independente (I-12). */
50
+ const branch_de_estado_1 = require("./branch-de-estado");
49
51
  const util_1 = require("./util");
50
52
  const worktree_1 = require("./worktree");
51
53
  const verify_1 = require("./verify");
@@ -73,9 +75,13 @@ const CASOS_DE_VERIFICADOR_COM_INTEGRACAO_LOCAL = [
73
75
  /verify-check-c2-b3\.cjs\s+channel-offer(?:\s|$)/,
74
76
  // I-38: a prova da busca por significado le a memoria do tenant pela DSN do manifesto.
75
77
  /prova-busca-semantica\.sh(?:\s|$)/,
78
+ // RM-038: a prova do universo da busca tambem le a memoria do tenant pela DSN do manifesto.
79
+ /prova-universo-da-busca\.cjs(?:\s|$)/,
76
80
  // RM-051: o ensaio instala o tarball com npm --offline e resolve as dependencias pelo cache do npm
77
81
  // da estacao; o runner hospedado so tem os tarballs do npm ci, sem os metadados do registro.
78
82
  /testar-experiencia-e2e\.cjs(?:\s|$)/,
83
+ // RM-037: a suite local inteira, com as integracoes locais; o runner hospedado roda o test:ci.
84
+ /suite-local\.cjs(?:\s|$)/,
79
85
  ];
80
86
  /**
81
87
  * I-38: o runner hospedado nao oferece OrkMind nem a base do tenant. Claim que chama a memoria
@@ -133,7 +139,8 @@ function consultarCi(carregado, sha, remoto = 'origin', executor = executorPadra
133
139
  const context = ci.context || 'ork-verify';
134
140
  if (!ci.required_for_ship)
135
141
  return { schema: 'ork.ci-status/v1', required: false, ok: true, provider: 'github', repository: null, sha, context, state: 'disabled', url: null, detail: 'gate de CI não exigido pelo manifesto' };
136
- const remote = (0, util_1.exec)('git', ['remote', 'get-url', remoto], carregado.raiz);
142
+ // RM-047: o `--remoto` do `ork ci status` vem da linha de comando; so nome de remoto chega ao git.
143
+ const remote = (0, util_1.exec)('git', ['remote', 'get-url', '--', (0, branch_de_estado_1.exigirRemoto)(remoto, 'ci')], carregado.raiz);
137
144
  const repository = remote.ok ? repositorioGitHub(remote.stdout) : null;
138
145
  if (!repository)
139
146
  return { schema: 'ork.ci-status/v1', required: true, ok: false, provider: 'github', repository: null, sha, context, state: 'unavailable', url: null, detail: `remoto ${remoto} não é um repositório GitHub reconhecível` };
package/dist/claims.js CHANGED
@@ -58,11 +58,14 @@ exports.anexarComando = anexarComando;
58
58
  exports.retirarClaim = retirarClaim;
59
59
  exports.carimbarEstado = carimbarEstado;
60
60
  exports.tabelaDeClaims = tabelaDeClaims;
61
+ const fs = __importStar(require("node:fs"));
61
62
  const path = __importStar(require("node:path"));
62
63
  const ledger_1 = require("./ledger");
63
64
  const manifest_1 = require("./manifest");
64
65
  const prova_minima_1 = require("./prova-minima");
65
66
  const thread_1 = require("./thread");
67
+ const policies_1 = require("./policies");
68
+ const verify_1 = require("./verify");
66
69
  const util_1 = require("./util");
67
70
  const claim_lint_1 = require("./claim-lint");
68
71
  /** Caminho do `claims.jsonl` da thread (append-only, como o ledger). */
@@ -165,7 +168,42 @@ function adicionarClaim(raiz, threadId, opcoes) {
165
168
  estado: claim.estado,
166
169
  ...(claim.lint?.length ? { lint: claim.lint.map((a) => a.regra) } : {}),
167
170
  });
168
- return claim;
171
+ const avisosDePolicy = opcoes.semProvaLocal ? [] : conferirProvaLocal(raiz, thread, claim, opcoes.executarProva ?? verify_1.executar);
172
+ return avisosDePolicy.length ? { ...claim, avisosDePolicy } : claim;
173
+ }
174
+ /**
175
+ * RM-008 (B8): com `claim_sem_prova_local` (ou `claims_failed`) declarada, o registro roda o comando da
176
+ * claim uma vez, na worktree da thread e no prazo do verify. A claim ja entrou: reprovacao ou estouro
177
+ * so gravam `policy_warn` no ledger e voltam como aviso. Sem a policy, nada roda.
178
+ */
179
+ function conferirProvaLocal(raiz, thread, claim, executarProva) {
180
+ const carregado = (0, manifest_1.carregarManifesto)(raiz);
181
+ if (!carregado || claim.verificar.length === 0 || !(0, policies_1.policyDeProvaLocal)(carregado.manifesto))
182
+ return [];
183
+ const prazoMs = carregado.manifesto.verify.timeout_ms ?? verify_1.TIMEOUT_VERIFY_MS;
184
+ const cwd = (0, verify_1.cwdDaThread)(raiz, thread);
185
+ let reprovada;
186
+ // Suspeitas da revisao de 03/10: sem a worktree (apagada), nada roda e o aviso diz por que, sem culpar a claim.
187
+ if (!fs.existsSync(cwd))
188
+ reprovada = { comando: claim.verificar[0], code: -1, estourou: false, semDiretorio: cwd };
189
+ for (const comando of reprovada ? [] : claim.verificar) {
190
+ const r = executarProva(`claim ${claim.id}`, comando, cwd, prazoMs);
191
+ if (!r.ok) {
192
+ reprovada = { comando, code: r.code, estourou: r.causa === 'timeout' };
193
+ break;
194
+ }
195
+ }
196
+ if (!reprovada)
197
+ return [];
198
+ const violacoes = (0, policies_1.avisos)((0, policies_1.avaliarPolicies)(carregado.manifesto, {
199
+ gate: 'claims.add', threadId: thread.id, modo: thread.modo, fase: claim.fase ?? undefined,
200
+ provaLocalReprovada: { claim: claim.id, ...reprovada, prazoMs },
201
+ }));
202
+ for (const v of violacoes) {
203
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, thread.id), thread.id, ledger_1.TIPOS_DE_EVENTO.politicaAviso, { gate: 'claims.add', claim: claim.id,
204
+ fase: claim.fase, policy: v.policy, motivo: v.motivo, detalhe: v.detalhe, correcao: v.correcao, modo: thread.modo });
205
+ }
206
+ return violacoes;
169
207
  }
170
208
  /** Prefixo do arquivo das claims de criterio, para o veredito dizer de onde veio. */
171
209
  exports.ARQUIVO_DONEWHEN = 'thread.json#doneWhen';
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONTEXT_SCHEMA = void 0;
3
+ exports.SERVER_CONTEXT_SCHEMA = exports.CONTEXT_SCHEMA = void 0;
4
4
  exports.buildContext = buildContext;
5
5
  const portfolio_1 = require("./portfolio");
6
6
  const company_brain_client_1 = require("./company-brain-client");
7
7
  const company_brain_contract_1 = require("./company-brain-contract");
8
8
  const company_brain_source_1 = require("./company-brain-source");
9
9
  exports.CONTEXT_SCHEMA = 'ork.brain-context/v1';
10
+ exports.SERVER_CONTEXT_SCHEMA = 'orkmind.company-brain-context/v1';
11
+ const SEM_MODO_CONTEXT = 'brain.selection.context-unsupported';
12
+ const SERVIDOR_INVALIDO = 'brain.context.server-invalid';
10
13
  const ID = /^(prod|proj|init)-[a-z0-9][a-z0-9-]{2,47}$/;
11
14
  const ORDEM = { prod: 0, proj: 1, init: 2 };
12
15
  const LIMITE = 1000;
@@ -27,25 +30,122 @@ function citacaoDe(source) {
27
30
  return null;
28
31
  return { instance, source_ref, source_hash, source_version, location };
29
32
  }
33
+ /** O servidor exige também `authority`: o pacote dele só traz item com a citação inteira; a consulta exige o mesmo. */
34
+ const citadoPeloServidor = (source) => citacaoDe(source) !== null && typeof source.authority === 'string' && source.authority !== '';
30
35
  /**
31
- * Monta o pacote dos ids pedidos mais a cadeia de pais. A fonte canônica é o portfólio local;
32
- * o Brain é projeção, e a diferença entre os dois sai como frescor e lacuna, nunca é corrigida aqui.
36
+ * Confere um pacote `orkmind.company-brain-context/v1` contra o lote pedido: schema, tenant, pedido,
37
+ * digest recalculado, citação inteira, ordem, fecho de pais e destino de cada id pedido.
33
38
  */
34
- function buildContext(c, ids, transport, thread) {
35
- const tenant = c.manifesto.memory.tenant;
36
- const pedido = [...new Set(ids)].sort(comparar);
37
- if (!pedido.length)
38
- throw Error('brain.context.ids-required');
39
- if (pedido.some(id => !ID.test(id)))
40
- throw Error('brain.context.id-invalid');
41
- if (pedido.length > LIMITE)
42
- throw Error('brain.context.too-many');
43
- const fonte = new Map((0, company_brain_source_1.portfolioEntities)((0, portfolio_1.readPortfolio)(c.raiz), { tenant, instance: c.manifesto.project.name, thread, aclRef: 'ork-factory' }).map(e => [e.id, e]));
44
- const base = { schema: exports.CONTEXT_SCHEMA, tenant, contrato: company_brain_contract_1.CONTRACT_HASH, thread, pedido };
45
- const encerrar = (r) => ({ ...base, state: r.state, ...(r.error ? { error: r.error } : {}), itens: [], lacunas: [], digest: null, consultadoEm: new Date().toISOString() });
39
+ function conferirPacoteDoServidor(r, tenant, lote) {
40
+ const ctx = r.context;
41
+ if (!['ok', 'empty'].includes(r.state) || !ctx || typeof ctx !== 'object' || Array.isArray(ctx))
42
+ return false;
43
+ if (Object.keys(ctx).sort().join(',') !== 'digest,gaps,items,requested,schema,tenant_id')
44
+ return false;
45
+ const { digest: recebido, ...corpo } = ctx;
46
+ if (ctx.schema !== exports.SERVER_CONTEXT_SCHEMA || ctx.tenant_id !== tenant || (0, company_brain_contract_1.canonical)(ctx.requested) !== (0, company_brain_contract_1.canonical)(lote) ||
47
+ !Array.isArray(ctx.items) || !Array.isArray(ctx.gaps) || typeof recebido !== 'string' || (0, company_brain_contract_1.digest)(corpo) !== recebido)
48
+ return false;
49
+ if (r.state !== (ctx.items.length ? 'ok' : 'empty'))
50
+ return false;
51
+ const pedidos = new Set(lote), vistos = new Map(), codigos = new Map();
52
+ for (const g of ctx.gaps) {
53
+ if (!g || typeof g.id !== 'string' || typeof g.code !== 'string' || !ID.test(g.id))
54
+ return false;
55
+ if (!codigos.has(g.id))
56
+ codigos.set(g.id, new Set());
57
+ codigos.get(g.id).add(g.code);
58
+ }
59
+ for (const item of ctx.items) {
60
+ if (!item || typeof item.id !== 'string' || !ID.test(item.id) || vistos.has(item.id))
61
+ return false;
62
+ if (item.state === 'withheld') {
63
+ if (Object.keys(item).length !== 2)
64
+ return false;
65
+ }
66
+ else if (item.state !== 'ok' || !item.entity || typeof item.entity !== 'object' || item.entity.kind !== kindDe(item.id) ||
67
+ !Number.isInteger(item.entity.version) || !citadoPeloServidor(item.entity.source))
68
+ return false;
69
+ vistos.set(item.id, item);
70
+ }
71
+ const ordem = ctx.items.map((i) => i.id);
72
+ const ordenado = [...ordem].sort((a, b) => ORDEM[kindDe(a)] - ORDEM[kindDe(b)] || comparar(a, b));
73
+ if ((0, company_brain_contract_1.canonical)(ordem) !== (0, company_brain_contract_1.canonical)(ordenado))
74
+ return false;
75
+ // Fecho: todo item fora do pedido é pai, um kind acima, de um item citado do pacote.
76
+ const paisCitados = new Set();
77
+ for (const item of ctx.items)
78
+ if (item.state === 'ok') {
79
+ const parent = item.entity.parent_id;
80
+ if (typeof parent === 'string' && ID.test(parent) && ORDEM[kindDe(parent)] < ORDEM[kindDe(item.id)])
81
+ paisCitados.add(parent);
82
+ }
83
+ for (const id of vistos.keys())
84
+ if (!pedidos.has(id) && !paisCitados.has(id))
85
+ return false;
86
+ // Destino: todo id pedido sai como item ou como lacuna de desconhecido ou de citação incompleta.
87
+ for (const id of lote) {
88
+ const c = codigos.get(id);
89
+ if (!vistos.has(id) && !(c?.has('entity.unknown') || c?.has('citation.incomplete')))
90
+ return false;
91
+ if (vistos.has(id) && (c?.has('entity.unknown') || c?.has('citation.incomplete')))
92
+ return false;
93
+ }
94
+ return true;
95
+ }
96
+ /**
97
+ * B4.2: resolve o escopo pelo modo `context` do servidor. Devolve null quando o OrkMind não tem o modo
98
+ * (o chamador usa a consulta), ou a resposta que encerra o pacote.
99
+ */
100
+ function resolverPeloServidor(transport, tenant, pedido, fonte, pai) {
101
+ const escopo = new Set(pedido), brain = new Map(), retidos = new Set(), semCitacao = new Set();
102
+ let primeiro = true;
103
+ for (let pendentes = pedido; pendentes.length;) {
104
+ const resolvidosNaVolta = [];
105
+ for (let inicio = 0; inicio < pendentes.length; inicio += LIMITE) {
106
+ const lote = pendentes.slice(inicio, inicio + LIMITE);
107
+ const r = transport({ schema: company_brain_client_1.BRAIN_API, operation: 'query', payload: (0, company_brain_contract_1.validateContract)({ schema: 'orkmind.company-brain-selection/v1',
108
+ tenant_id: tenant, facets: { ids: lote, kinds: [], workspace_ids: [], source_instances: [] }, mode: 'context',
109
+ limit: lote.length, offset: 0 }) });
110
+ if (r.state === 'unavailable' && r.error === SEM_MODO_CONTEXT)
111
+ return primeiro ? null : { state: 'conflict', error: SERVIDOR_INVALIDO };
112
+ primeiro = false;
113
+ if (falha(r.state))
114
+ return r;
115
+ if (!conferirPacoteDoServidor(r, tenant, lote))
116
+ return { state: 'conflict', error: SERVIDOR_INVALIDO };
117
+ const ctx = r.context;
118
+ for (const g of ctx.gaps)
119
+ if (g.code === 'citation.incomplete')
120
+ semCitacao.add(g.id);
121
+ for (const item of ctx.items) {
122
+ // Pai que outro lote ou volta já resolveu vale uma vez só: a primeira resposta fica.
123
+ if (escopo.has(item.id) && !lote.includes(item.id))
124
+ continue;
125
+ escopo.add(item.id);
126
+ resolvidosNaVolta.push(item.id);
127
+ if (item.state === 'withheld')
128
+ retidos.add(item.id);
129
+ else
130
+ brain.set(item.id, { ...item.entity, id: item.id });
131
+ }
132
+ resolvidosNaVolta.push(...lote);
133
+ }
134
+ // O servidor fecha os pais que o Brain conhece; os que só a fonte local conhece vão na próxima volta.
135
+ const novos = new Set();
136
+ for (const id of resolvidosNaVolta)
137
+ if (!retidos.has(id))
138
+ for (const parent of [pai(id, fonte.get(id)?.parent_id), pai(id, brain.get(id)?.parent_id)])
139
+ if (parent && !escopo.has(parent))
140
+ novos.add(parent);
141
+ novos.forEach(id => escopo.add(id));
142
+ pendentes = [...novos].sort(comparar);
143
+ }
144
+ return { brain, retidos, semCitacao, escopo };
145
+ }
146
+ /** B4.1: o caminho anterior ao modo `context`, por `query` e `get`, para um OrkMind sem o modo. */
147
+ function resolverPelaConsulta(transport, tenant, pedido, fonte, pai) {
46
148
  const escopo = new Set(pedido), brain = new Map(), retidos = new Set();
47
- // O pai só entra por um filho visível e tem kind acima dele: init, proj e prod fecham em três voltas.
48
- const pai = (id, parent) => typeof parent === 'string' && ID.test(parent) && ORDEM[kindDe(parent)] < ORDEM[kindDe(id)] ? parent : null;
49
149
  let gets = 0;
50
150
  for (let pendentes = pedido; pendentes.length;) {
51
151
  for (let inicio = 0; inicio < pendentes.length; inicio += LIMITE) {
@@ -54,7 +154,7 @@ function buildContext(c, ids, transport, thread) {
54
154
  tenant_id: tenant, facets: { ids: lote, kinds: [], workspace_ids: [], source_instances: [] }, mode: 'selection',
55
155
  limit: lote.length, offset: 0 }) });
56
156
  if (falha(r.state))
57
- return encerrar(r);
157
+ return r;
58
158
  const itens = Array.isArray(r.items) ? r.items : [];
59
159
  for (const item of itens)
60
160
  if (item?.state === 'ok' && doLote.has(item.entity?.id))
@@ -67,10 +167,10 @@ function buildContext(c, ids, transport, thread) {
67
167
  else if (retidosNoLote)
68
168
  for (const id of faltantes) {
69
169
  if (++gets > LIMITE_DE_GET)
70
- return encerrar({ state: 'unavailable', error: 'brain.context.get-limit' });
170
+ return { state: 'unavailable', error: 'brain.context.get-limit' };
71
171
  const g = transport({ schema: company_brain_client_1.BRAIN_API, operation: 'get', payload: { tenant_id: tenant, id } });
72
172
  if (falha(g.state))
73
- return encerrar(g);
173
+ return g;
74
174
  if (g.state === 'withheld')
75
175
  retidos.add(id);
76
176
  else if (g.state === 'ok' && g.entity?.id === id)
@@ -87,6 +187,35 @@ function buildContext(c, ids, transport, thread) {
87
187
  }
88
188
  pendentes = novos.sort(comparar);
89
189
  }
190
+ return { brain, retidos, semCitacao: new Set(), escopo };
191
+ }
192
+ /**
193
+ * Monta o pacote dos ids pedidos mais a cadeia de pais. A fonte canônica é o portfólio local;
194
+ * o Brain é projeção, e a diferença entre os dois sai como frescor e lacuna, nunca é corrigida aqui.
195
+ */
196
+ function buildContext(c, ids, transport, thread) {
197
+ const tenant = c.manifesto.memory.tenant;
198
+ const pedido = [...new Set(ids)].sort(comparar);
199
+ if (!pedido.length)
200
+ throw Error('brain.context.ids-required');
201
+ if (pedido.some(id => !ID.test(id)))
202
+ throw Error('brain.context.id-invalid');
203
+ if (pedido.length > LIMITE)
204
+ throw Error('brain.context.too-many');
205
+ const fonte = new Map((0, company_brain_source_1.portfolioEntities)((0, portfolio_1.readPortfolio)(c.raiz), { tenant, instance: c.manifesto.project.name, thread, aclRef: 'ork-factory' }).map(e => [e.id, e]));
206
+ const base = { schema: exports.CONTEXT_SCHEMA, tenant, contrato: company_brain_contract_1.CONTRACT_HASH, thread, pedido };
207
+ // O pai só entra por um filho visível e tem kind acima dele: init, proj e prod fecham em três voltas.
208
+ const pai = (id, parent) => typeof parent === 'string' && ID.test(parent) && ORDEM[kindDe(parent)] < ORDEM[kindDe(id)] ? parent : null;
209
+ const doServidor = resolverPeloServidor(transport, tenant, pedido, fonte, pai);
210
+ const caminho = doServidor === null ? 'consulta' : 'servidor';
211
+ const encerrar = (r) => ({ ...base, state: r.state, ...(r.error ? { error: r.error } : {}), itens: [], lacunas: [],
212
+ digest: null, consultadoEm: new Date().toISOString(), caminho });
213
+ if (doServidor && 'state' in doServidor)
214
+ return encerrar(doServidor);
215
+ const resolvido = doServidor ?? resolverPelaConsulta(transport, tenant, pedido, fonte, pai);
216
+ if ('state' in resolvido)
217
+ return encerrar(resolvido);
218
+ const { escopo, brain, retidos, semCitacao } = resolvido;
90
219
  const itens = [], lacunas = [];
91
220
  const lacuna = (id, codigo) => lacunas.push({ id, codigo });
92
221
  for (const id of escopo) {
@@ -95,12 +224,19 @@ function buildContext(c, ids, transport, thread) {
95
224
  lacuna(id, 'brain.retido');
96
225
  continue;
97
226
  }
227
+ // Sem citação no Brain vale como no caminho da consulta: o Brain tem a entidade, mas ela não é citável.
228
+ if (semCitacao.has(id)) {
229
+ lacuna(id, 'citacao.incompleta');
230
+ continue;
231
+ }
98
232
  const b = brain.get(id), f = fonte.get(id);
99
233
  if (!b && !f) {
100
234
  lacuna(id, 'entidade.desconhecida');
101
235
  continue;
102
236
  }
103
- const entidade = b ?? f, citacao = citacaoDe(entidade.source);
237
+ // Suspeitas da revisao de 03/10: a citacao exige `authority` nos dois caminhos, como o servidor (o
238
+ // schema do Source a exige); sem isso, um Brain fora do contrato dava pacote e digest diferentes por caminho.
239
+ const entidade = b ?? f, citacao = citadoPeloServidor(entidade.source) ? citacaoDe(entidade.source) : null;
104
240
  if (!citacao) {
105
241
  lacuna(id, 'citacao.incompleta');
106
242
  continue;
@@ -128,5 +264,5 @@ function buildContext(c, ids, transport, thread) {
128
264
  lacunas.sort((a, b) => comparar(a.id, b.id) || comparar(a.codigo, b.codigo));
129
265
  // O digest cobre o que foi dito e de onde veio; o horário da consulta fica fora para o pacote ser reproduzível.
130
266
  const corpo = { ...base, state: (itens.length ? 'ok' : 'empty'), itens, lacunas };
131
- return { ...corpo, digest: (0, company_brain_contract_1.digest)(corpo), consultadoEm: new Date().toISOString() };
267
+ return { ...corpo, digest: (0, company_brain_contract_1.digest)(corpo), consultadoEm: new Date().toISOString(), caminho };
132
268
  }
package/dist/docs.js CHANGED
@@ -43,6 +43,7 @@ exports.comandosDaAjuda = comandosDaAjuda;
43
43
  exports.verificarDocs = verificarDocs;
44
44
  exports.renderizarRelance = renderizarRelance;
45
45
  exports.renderizarEstado = renderizarEstado;
46
+ exports.docsPendentesDoMerge = docsPendentesDoMerge;
46
47
  exports.textoDaVerificacao = textoDaVerificacao;
47
48
  exports.escreverYaml = escreverYaml;
48
49
  exports.mergeDaThread = mergeDaThread;
@@ -576,20 +577,58 @@ function verificarEstadoDoRoadmap(raiz, d, base, achados) {
576
577
  * mesmo `ship(<thread>)` do `sincronizarDocs`: tudo o que ela acusa o `sincronizar` corrige.
577
578
  */
578
579
  function verificarMergeDaThread(raiz, d, base, achados, pr = false) {
579
- const sdlc = ehMapa(d.dados.sdlc) ? d.dados.sdlc : null;
580
- const thread = sdlc && typeof sdlc.thread === 'string' ? sdlc.thread.trim() : '';
581
- if (!thread)
582
- return;
583
- const merges = mergesDaThreadNoGit(raiz, thread, base);
584
- if (merges.length === 0)
585
- return;
586
- const codigo = String((ehMapa(d.dados.estado) ? d.dados.estado.codigo : null) ?? '');
587
- if (codigo === 'Mesclado')
580
+ const pendente = pendenciaDeMerge(raiz, d, base);
581
+ if (!pendente)
588
582
  return;
583
+ const { thread, merges, codigo } = pendente;
589
584
  achados.push((pr ? aviso : erro)(d.arquivo, d.id, 'docs.paridade.merge', `a thread ${thread} entrou na ${base} pelo merge ${merges[0].slice(0, 7)} (ship(${thread})), e o estado.codigo diz "${codigo || 'vazio'}"` +
590
585
  (pr ? ' (no PR, aviso: o push da main reprova)' : ''), `abra um PR de docs sobre a ${base} atualizada com ork docs sincronizar --escrever --so ${d.id} (o item e os índices); ` +
591
586
  `se o item tem outra fatia em curso, aponte sdlc.thread para a thread dela`));
592
587
  }
588
+ /**
589
+ * A regra do `docs.paridade.merge`, isolada para o `ork ship registrar-pr` (RM-044, A3): a pagina cujo
590
+ * `sdlc.thread` ja tem merge `ship(<thread>)` na base e cujo `estado.codigo` ainda nao diz `Mesclado`.
591
+ * `thread`, quando dada, restringe a pagina a essa thread.
592
+ */
593
+ function pendenciaDeMerge(raiz, d, base, thread) {
594
+ const sdlc = ehMapa(d.dados.sdlc) ? d.dados.sdlc : null;
595
+ const daPagina = sdlc && typeof sdlc.thread === 'string' ? sdlc.thread.trim() : '';
596
+ if (!daPagina || (thread !== undefined && daPagina !== thread))
597
+ return null;
598
+ const codigo = String((ehMapa(d.dados.estado) ? d.dados.estado.codigo : null) ?? '');
599
+ if (codigo === 'Mesclado')
600
+ return null;
601
+ const merges = mergesDaThreadNoGit(raiz, daPagina, base);
602
+ if (merges.length === 0)
603
+ return null;
604
+ return { thread: daPagina, merges, codigo };
605
+ }
606
+ /**
607
+ * RM-044 (A3): as paginas do roadmap, lidas da `ref` (a base remota depois do fetch, nao a copia local,
608
+ * que pode estar atras), que a regra `docs.paridade.merge` vai acusar por causa da `thread`. Nao grava
609
+ * nada: so lista. Ref ilegivel ou sem `docs/roadmap` devolve lista vazia.
610
+ */
611
+ function docsPendentesDoMerge(raiz, thread, ref) {
612
+ const ls = git(raiz, ['ls-tree', '--name-only', `${ref}:${exports.DIR_ROADMAP}`]);
613
+ if (!ls.ok || !ls.saida)
614
+ return [];
615
+ const pendentes = [];
616
+ for (const nome of ls.saida.split('\n').filter(ehPaginaDeDocs).sort()) {
617
+ const arquivo = `${exports.DIR_ROADMAP}/${nome}`;
618
+ const blob = git(raiz, ['show', `${ref}:${arquivo}`]);
619
+ if (!blob.ok)
620
+ continue;
621
+ const lido = documentoDeTexto(arquivo, `${blob.saida}\n`);
622
+ if (!('doc' in lido) || lido.doc.tipo !== 'roadmap')
623
+ continue;
624
+ const p = pendenciaDeMerge(raiz, lido.doc, ref, thread);
625
+ if (!p)
626
+ continue;
627
+ pendentes.push({ id: lido.doc.id, arquivo, codigo: p.codigo, merge: p.merges[0],
628
+ comando: `ork docs sincronizar --escrever --so ${lido.doc.id}` });
629
+ }
630
+ return pendentes;
631
+ }
593
632
  /** Os indices gerados (`docs/roadmap/README.md`, `docs/produto/README.md`) e o que o frontmatter diz. */
594
633
  const INDICES_GERADOS = [
595
634
  [`${exports.DIR_ROADMAP}/README.md`, (docs) => tabelaDoRoadmap(docs)],