@orkastery/cli 0.4.3 → 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 (146) hide show
  1. package/adapters/README.md +11 -0
  2. package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
  3. package/adapters/claude-code/README.md +11 -2
  4. package/adapters/claude-code/agents/ork-check.md +1 -1
  5. package/adapters/claude-code/agents/ork-go.md +2 -2
  6. package/adapters/claude-code/agents/ork-goal.md +1 -1
  7. package/adapters/claude-code/agents/ork-master.md +1 -1
  8. package/adapters/claude-code/agents/ork-plan.md +1 -1
  9. package/adapters/claude-code/agents/ork-ship.md +1 -1
  10. package/adapters/claude-code/commands/master.md +11 -8
  11. package/adapters/claude-code/commands/onboarding.md +3 -2
  12. package/adapters/claude-code/commands/ork.md +8 -0
  13. package/adapters/claude-code/commands/ship.md +2 -2
  14. package/adapters/codex/skills/ork/SKILL.md +12 -1
  15. package/adapters/hermes/README.md +3 -0
  16. package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
  17. package/adapters/hermes/bin/ork-brain.sh +3 -0
  18. package/adapters/hermes/bin/ork-maestro.sh +4 -1
  19. package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
  20. package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
  21. package/adapters/hermes/hermes.plugin.json +2 -0
  22. package/adapters/hermes/hitl-ingress/__init__.py +36 -3
  23. package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
  24. package/adapters/openclaw/README.md +45 -2
  25. package/adapters/openclaw/dist/hitl-ingress.js +37 -3
  26. package/adapters/openclaw/dist/index.js +93 -21
  27. package/adapters/openclaw/openclaw.plugin.json +10 -1
  28. package/adapters/openclaw/package.json +2 -2
  29. package/adapters/openclaw/src/hitl-ingress.ts +32 -3
  30. package/adapters/openclaw/src/index.ts +109 -21
  31. package/assets/docs/padroes/documentacao-de-produto.md +2 -2
  32. package/assets/docs/padroes/roadmap-de-produto.md +1 -1
  33. package/assets/docs/produto/_modelo-feature.md +2 -0
  34. package/assets/orkmind_bridge.py +214 -1
  35. package/dist/adapters/claude-bg.js +24 -7
  36. package/dist/auditrun.js +1 -1
  37. package/dist/board.js +74 -0
  38. package/dist/branch-de-estado.js +5 -4
  39. package/dist/busca-semantica.js +161 -0
  40. package/dist/ci.js +141 -10
  41. package/dist/claim-lint.js +3 -1
  42. package/dist/company-brain-cli.js +27 -2
  43. package/dist/company-brain-context.js +132 -0
  44. package/dist/company-brain-dossie.js +376 -0
  45. package/dist/company-brain-mcp.js +4 -1
  46. package/dist/conducao.js +71 -18
  47. package/dist/decisao-autonoma.js +42 -0
  48. package/dist/docs.js +135 -25
  49. package/dist/doctor.js +242 -32
  50. package/dist/entrega-pr.js +116 -5
  51. package/dist/experiencia-instalacao.js +256 -0
  52. package/dist/experiencia.js +72 -0
  53. package/dist/fabrica-estado.js +63 -12
  54. package/dist/fechamento.js +105 -0
  55. package/dist/fix.js +3 -3
  56. package/dist/forja.js +435 -0
  57. package/dist/gates.js +9 -1
  58. package/dist/hitl-classificacao.js +11 -1
  59. package/dist/hitl-contract.js +50 -7
  60. package/dist/hitl-curto.js +158 -0
  61. package/dist/hitl-gates.js +67 -1
  62. package/dist/hitl-lote.js +30 -15
  63. package/dist/hitl-presentation.js +10 -7
  64. package/dist/hitl-resumo.js +57 -13
  65. package/dist/hitl-texto-livre.js +150 -0
  66. package/dist/hosts.js +92 -3
  67. package/dist/index.js +637 -45
  68. package/dist/indice-vetorial.js +317 -0
  69. package/dist/init.js +24 -5
  70. package/dist/integracoes-locais.js +3 -1
  71. package/dist/intelligence-benchmark-contract.js +570 -0
  72. package/dist/intelligence-graph-cli.js +584 -0
  73. package/dist/intelligence-graph-contract.js +636 -0
  74. package/dist/intelligence-graph-extract-md.js +487 -0
  75. package/dist/intelligence-graph-extract-ts.js +980 -0
  76. package/dist/intelligence-graph-extract.js +546 -0
  77. package/dist/intelligence-graph-index.js +693 -0
  78. package/dist/intelligence-graph-parsers.js +327 -0
  79. package/dist/intelligence-graph-query.js +344 -0
  80. package/dist/intelligence-graph-repo.js +197 -0
  81. package/dist/leases.js +100 -0
  82. package/dist/ledger.js +23 -0
  83. package/dist/licoes.js +5 -4
  84. package/dist/liveness.js +2 -1
  85. package/dist/maestro-cli.js +10 -1
  86. package/dist/maestro-contract.js +5 -1
  87. package/dist/maestro-discovery.js +16 -2
  88. package/dist/maestro-runtime.js +3 -0
  89. package/dist/maestro-snapshot.js +6 -2
  90. package/dist/manifest.js +166 -5
  91. package/dist/master-batch.js +3 -2
  92. package/dist/master-digest.js +2 -2
  93. package/dist/master-nota.js +203 -0
  94. package/dist/master.js +4 -0
  95. package/dist/mcp-artifacts.js +2 -0
  96. package/dist/mcp-experiencia.js +164 -0
  97. package/dist/mcp-git.js +64 -12
  98. package/dist/mcp-grafo-worker.js +91 -0
  99. package/dist/mcp-grafo.js +233 -0
  100. package/dist/mcp-install.js +5 -2
  101. package/dist/mcp-maestro.js +3 -2
  102. package/dist/mcp-server.js +93 -5
  103. package/dist/mcp-ship.js +2 -0
  104. package/dist/mcp-verify.js +3 -1
  105. package/dist/memoria.js +147 -1
  106. package/dist/network-roadmap.js +907 -0
  107. package/dist/ocupacao.js +43 -3
  108. package/dist/onboarding.js +122 -11
  109. package/dist/orkmind.js +165 -9
  110. package/dist/orquestracao.js +2 -1
  111. package/dist/parado-no-condutor.js +933 -0
  112. package/dist/phase.js +166 -13
  113. package/dist/policies.js +29 -2
  114. package/dist/projeto-alvo.js +551 -0
  115. package/dist/prompts.js +1 -1
  116. package/dist/prova-ativacao.js +345 -0
  117. package/dist/pulse-consentimento.js +4 -2
  118. package/dist/pulse-delivery.js +32 -6
  119. package/dist/pulse-resposta.js +674 -49
  120. package/dist/pulse.js +74 -3
  121. package/dist/retry.js +51 -8
  122. package/dist/roadmap-reservas.js +262 -20
  123. package/dist/roadmap-status.js +348 -0
  124. package/dist/runtime-context.js +5 -1
  125. package/dist/sessoes-inventario.js +7 -1
  126. package/dist/sessoes.js +4 -1
  127. package/dist/ship.js +25 -1
  128. package/dist/thread-close.js +3 -0
  129. package/dist/thread.js +21 -2
  130. package/dist/util.js +71 -4
  131. package/dist/verify.js +6 -5
  132. package/dist/worktree.js +29 -6
  133. package/eval/casos/onboarding.json +1 -1
  134. package/eval/casos/orchestration-experience-pt-br.json +48 -0
  135. package/eval/casos/orchestration-experience.json +48 -0
  136. package/package.json +1 -1
  137. package/schemas/code-artifact-graph.v1.schema.json +581 -0
  138. package/schemas/graph-benchmark.v1.schema.json +2240 -0
  139. package/schemas/maestro-snapshot.schema.json +23 -0
  140. package/skills/README.md +7 -5
  141. package/skills/core/onboarding/SKILL.md +10 -2
  142. package/skills/core/orchestration-experience/SKILL.md +113 -0
  143. package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
  144. package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
  145. package/skills/phases/go-implementation/SKILL.md +2 -0
  146. package/skills/phases/ship-release/SKILL.md +6 -3
package/dist/manifest.js CHANGED
@@ -37,10 +37,13 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  };
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.LIMITE_MANIFESTO_BYTES = exports.DIR_ESTADO = exports.NOME_MANIFESTO_LEGADO = exports.NOME_MANIFESTO = void 0;
40
+ exports.EMBEDDING_PADRAO = exports.LIMITE_MANIFESTO_BYTES = exports.DIR_ESTADO = exports.NOME_MANIFESTO_LEGADO = exports.NOME_MANIFESTO = void 0;
41
+ exports.fixarDiretorioDoProjeto = fixarDiretorioDoProjeto;
42
+ exports.diretorioDoProjeto = diretorioDoProjeto;
41
43
  exports.acharRaiz = acharRaiz;
42
44
  exports.dirEstado = dirEstado;
43
45
  exports.validarOptInPlaybook = validarOptInPlaybook;
46
+ exports.configDeEmbedding = configDeEmbedding;
44
47
  exports.carregarManifesto = carregarManifesto;
45
48
  exports.exigirManifesto = exigirManifesto;
46
49
  const fs = __importStar(require("node:fs"));
@@ -53,13 +56,28 @@ const slug_1 = require("./slug");
53
56
  const util_1 = require("./util");
54
57
  const delegation_1 = require("./delegation");
55
58
  const horario_1 = require("./horario");
59
+ const experiencia_1 = require("./experiencia");
60
+ const runtime_ambiente_1 = require("./runtime-ambiente");
61
+ const orkmind_1 = require("./orkmind");
56
62
  exports.NOME_MANIFESTO = 'orkastery.yaml';
57
63
  exports.NOME_MANIFESTO_LEGADO = 'devmaster.yaml';
58
64
  exports.DIR_ESTADO = '.orkastery';
59
65
  /** Limite duro: o manifesto nao pode virar o novo arquivo de 100 KB. */
60
66
  exports.LIMITE_MANIFESTO_BYTES = 16384;
67
+ let diretorioFixado = null;
68
+ /**
69
+ * RM-052: o projeto-alvo fixado pelo CLI (`--projeto`, `ORK_PROJETO`, host sem cwd) vira o ponto
70
+ * de partida padrao das raizes. Sem ele, vale o cwd, como sempre. Quem fixa e `fixarProjetoAlvo`.
71
+ */
72
+ function fixarDiretorioDoProjeto(dir) {
73
+ diretorioFixado = dir;
74
+ }
75
+ /** De onde as raizes padrao partem: o projeto-alvo fixado ou, sem ele, o cwd do processo. */
76
+ function diretorioDoProjeto() {
77
+ return diretorioFixado ?? process.cwd();
78
+ }
61
79
  /** Raiz do projeto: o diretorio que contem o manifesto; senao, a raiz do repositorio git. */
62
- function acharRaiz(dirInicial = process.cwd()) {
80
+ function acharRaiz(dirInicial = diretorioDoProjeto()) {
63
81
  const porManifesto = (0, util_1.subirAte)(dirInicial, exports.NOME_MANIFESTO);
64
82
  if (porManifesto)
65
83
  return porManifesto;
@@ -116,12 +134,59 @@ function prazosDoVerify(verify, erros) {
116
134
  saida.timeout_ms_por_comando = porComando;
117
135
  return saida;
118
136
  }
137
+ /**
138
+ * RM-037 (rm037defeito, defeito 5): `ci.external_repositories`, mapa `dono/nome` para o check exigido no
139
+ * head do PR (texto vazio ou nulo: repositorio sem CI). Forma invalida e erro do manifesto, nunca adivinhada.
140
+ */
141
+ function repositoriosExternos(v, erros) {
142
+ if (v === undefined || v === null)
143
+ return {};
144
+ if (typeof v !== 'object' || Array.isArray(v)) {
145
+ erros.push('ci.external_repositories deve ser um mapa "dono/nome": "check exigido" (vazio para repositorio sem CI)');
146
+ return {};
147
+ }
148
+ const r = {};
149
+ for (const [repo, check] of Object.entries(v)) {
150
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo)) {
151
+ erros.push(`ci.external_repositories: "${repo}" nao e dono/nome do GitHub`);
152
+ continue;
153
+ }
154
+ if (check !== null && typeof check !== 'string') {
155
+ erros.push(`ci.external_repositories.${repo}: o check exigido e texto (vazio para sem CI)`);
156
+ continue;
157
+ }
158
+ r[repo] = (check ?? '').trim();
159
+ }
160
+ return r;
161
+ }
119
162
  function booleano(v, padrao) {
120
163
  return typeof v === 'boolean' ? v : padrao;
121
164
  }
122
165
  function mapa(v) {
123
166
  return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
124
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
+ }
125
190
  /** Mesmo contrato na leitura do YAML e nos consumidores de manifesto em memoria. */
126
191
  function validarOptInPlaybook(valor) {
127
192
  if (valor === undefined)
@@ -133,6 +198,81 @@ function validarOptInPlaybook(valor) {
133
198
  }
134
199
  return valor.ativo;
135
200
  }
201
+ /** I-38 (D5): sem o bloco `memory.embedding`, os embeddings ficam desligados. */
202
+ exports.EMBEDDING_PADRAO = Object.freeze({
203
+ provider: 'none', model: '', dim: 1024, api_key_env: '', fallback_model: '', max_tokens_por_execucao: 1_000_000,
204
+ });
205
+ const CHAVES_DE_EMBEDDING = ['provider', 'model', 'dim', 'api_key_env', 'fallback_model', 'max_tokens_por_execucao'];
206
+ /** Valor que parece segredo (URL, prefixo de chave, sequencia longa) nunca e repetido num erro. */
207
+ function exibir(v) {
208
+ const t = typeof v === 'string' ? v : JSON.stringify(v);
209
+ return /:\/\/|\bsk-|[A-Za-z0-9_-]{32,}/.test(t) ? '(valor omitido: parece segredo)' : JSON.stringify(v);
210
+ }
211
+ /**
212
+ * I-38 (D5): le e valida `memory.embedding`. A chave so entra pelo NOME da variavel: valor com
213
+ * cara de chave ou DSN reprova o manifesto, e nome da lista de provider pago tambem, porque a
214
+ * entrada do `ork` apagaria a variavel sob `subscription-only` e o preflight reprovaria o codex.
215
+ */
216
+ function lerEmbedding(bruto, variavelDaDsn, erros) {
217
+ if (bruto === undefined || bruto === null)
218
+ return undefined;
219
+ if (typeof bruto !== 'object' || Array.isArray(bruto)) {
220
+ erros.push('memory.embedding precisa ser um bloco com provider, model, dim, api_key_env, fallback_model e max_tokens_por_execucao');
221
+ return undefined;
222
+ }
223
+ const e = bruto;
224
+ for (const chave of Object.keys(e)) {
225
+ if (!CHAVES_DE_EMBEDDING.includes(chave)) {
226
+ erros.push(`memory.embedding.${chave} nao existe (a chave entra pelo ambiente: declare so o NOME em api_key_env)`);
227
+ }
228
+ }
229
+ const provider = texto(e.provider, exports.EMBEDDING_PADRAO.provider).trim();
230
+ if (provider !== 'none' && provider !== 'openrouter') {
231
+ erros.push(`memory.embedding.provider invalido: ${exibir(provider)} (aceitos: none, openrouter)`);
232
+ }
233
+ const model = texto(e.model, '').trim();
234
+ const apiKeyEnv = texto(e.api_key_env, '').trim();
235
+ const fallback = texto(e.fallback_model, '').trim();
236
+ const inteiro = (chave, v, padrao, min, max) => {
237
+ if (v === undefined || v === null)
238
+ return padrao;
239
+ const n = typeof v === 'number' ? v : typeof v === 'string' && v.trim() !== '' ? Number(v) : NaN;
240
+ if (!Number.isInteger(n) || n < min || n > max) {
241
+ erros.push(`memory.embedding.${chave} precisa ser inteiro entre ${min} e ${max} (recebido ${exibir(v)})`);
242
+ return padrao;
243
+ }
244
+ return n;
245
+ };
246
+ const dim = inteiro('dim', e.dim, exports.EMBEDDING_PADRAO.dim, 32, 4096);
247
+ const teto = inteiro('max_tokens_por_execucao', e.max_tokens_por_execucao, exports.EMBEDDING_PADRAO.max_tokens_por_execucao, 1, 10_000_000);
248
+ if (apiKeyEnv.includes('://') || /^sk-/i.test(apiKeyEnv)) {
249
+ erros.push('memory.embedding.api_key_env recebe o NOME da variavel de ambiente com a chave, nunca o valor da chave nem uma DSN');
250
+ }
251
+ else if (apiKeyEnv !== '' && !/^[A-Z][A-Z0-9_]*$/.test(apiKeyEnv)) {
252
+ erros.push('memory.embedding.api_key_env invalido: esperado nome de variavel de ambiente, [A-Z][A-Z0-9_]* (o valor recebido nao e repetido)');
253
+ }
254
+ else if (runtime_ambiente_1.ENVS_DE_PROVIDER_PAGO.includes(apiKeyEnv)) {
255
+ erros.push(`memory.embedding.api_key_env nao pode ser ${apiKeyEnv}: nomes de provider pago sao removidos sob subscription-only; use uma chave dedicada com nome proprio`);
256
+ }
257
+ else if (apiKeyEnv !== '' && apiKeyEnv === variavelDaDsn) {
258
+ erros.push('memory.embedding.api_key_env nao pode repetir memory.database_url_env: a DSN nunca vai ao provider');
259
+ }
260
+ if (provider === 'openrouter') {
261
+ if (!orkmind_1.MODELO_DE_EMBEDDING.test(model))
262
+ erros.push(`memory.embedding.model invalido: ${exibir(model)} (esperado org/nome, por exemplo qwen/qwen3-embedding-8b)`);
263
+ if (apiKeyEnv === '')
264
+ erros.push('memory.embedding.api_key_env e obrigatorio com provider openrouter (o NOME da variavel com a chave dedicada)');
265
+ }
266
+ if (fallback !== '' && !orkmind_1.MODELO_DE_EMBEDDING.test(fallback)) {
267
+ erros.push(`memory.embedding.fallback_model invalido: ${exibir(fallback)} (esperado org/nome de um modelo do Hugging Face)`);
268
+ }
269
+ return { provider: provider === 'openrouter' ? 'openrouter' : 'none', model, dim, api_key_env: apiKeyEnv,
270
+ fallback_model: fallback, max_tokens_por_execucao: teto };
271
+ }
272
+ /** I-38 (D5): leitura unica da configuracao de embedding, com o padrao `none`. */
273
+ function configDeEmbedding(manifesto) {
274
+ return { ...exports.EMBEDDING_PADRAO, ...(manifesto.memory.embedding ?? {}) };
275
+ }
136
276
  /** I-33 (N2): texto e numero que dizem sim ou nao sem ambiguidade; o resto nao e reconhecido. */
137
277
  const BOOLEANOS_RECONHECIVEIS = new Map([
138
278
  ['true', true], ['yes', true], ['on', true], ['sim', true], ['1', true],
@@ -146,7 +286,7 @@ function booleanoReconhecivel(v) {
146
286
  return BOOLEANOS_RECONHECIVEIS.get(v.trim().toLowerCase()) ?? null;
147
287
  }
148
288
  /** Le e valida o manifesto. Nunca lanca: erros e avisos voltam na estrutura. */
149
- function carregarManifesto(dirInicial = process.cwd()) {
289
+ function carregarManifesto(dirInicial = diretorioDoProjeto()) {
150
290
  const raiz = acharRaiz(dirInicial);
151
291
  const candidatos = [
152
292
  { caminho: path.join(raiz, exports.NOME_MANIFESTO), legado: false },
@@ -171,6 +311,22 @@ function carregarManifesto(dirInicial = process.cwd()) {
171
311
  const fusoDoDono = (0, horario_1.lerFusoDoDono)(mapa(dados.owner).timezone);
172
312
  if (fusoDoDono.aviso)
173
313
  avisos.push(fusoDoDono.aviso);
314
+ // Preferência inválida segue o fuso: avisa e cai no padrão, sem parar os comandos (e o próprio
315
+ // onboarding set, que é o caminho para corrigir).
316
+ const preferencias = {};
317
+ for (const chave of ['language', 'depth', 'experience']) {
318
+ const valor = mapa(dados.owner)[chave];
319
+ if (valor === undefined)
320
+ continue;
321
+ try {
322
+ Object.assign(preferencias, (0, experiencia_1.validarPreferencias)({ [chave]: valor }));
323
+ }
324
+ catch (e) {
325
+ avisos.push(`${e.message}; vale o padrão até corrigir`);
326
+ }
327
+ }
328
+ if (fusoDoDono.origem === 'manifesto')
329
+ preferencias.timezone = fusoDoDono.fuso;
174
330
  const board = mapa(dados.board);
175
331
  const runtime = mapa(dados.runtime);
176
332
  const conduction = mapa(dados.conduction);
@@ -299,6 +455,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
299
455
  avisos.push('memory.mode: orkmind sem memory.database_url_env: o regime efetivo cai para files ' +
300
456
  '(o `ork` nao adivinha DSN, e base alheia e pior do que base nenhuma)');
301
457
  }
458
+ const embedding = lerEmbedding(memory.embedding, variavelDaDsn, erros);
302
459
  const bytes = Buffer.byteLength(bruto, 'utf8');
303
460
  if (bytes > exports.LIMITE_MANIFESTO_BYTES) {
304
461
  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)`);
@@ -313,7 +470,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
313
470
  stage: texto(project.stage, 'nascente') ?? 'nascente',
314
471
  repo_root: texto(project.repo_root, raiz),
315
472
  },
316
- ...(fusoDoDono.origem === 'manifesto' ? { owner: { timezone: fusoDoDono.fuso } } : {}),
473
+ ...(Object.keys(preferencias).length ? { owner: preferencias } : {}),
317
474
  board: {
318
475
  adapter: texto(board.adapter, 'hermes-kanban'),
319
476
  default: texto(board.default, 'default'),
@@ -350,6 +507,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
350
507
  required_for_ship: booleano(ci.required_for_ship, false),
351
508
  context: texto(ci.context, 'ork-verify'),
352
509
  command: ci.command === null || ci.command === undefined ? undefined : texto(ci.command, ''),
510
+ external_repositories: repositoriosExternos(ci.external_repositories, erros),
353
511
  },
354
512
  concurrency: {
355
513
  max_parallel_threads: numero(concurrency.max_parallel_threads, 3),
@@ -382,6 +540,7 @@ function carregarManifesto(dirInicial = process.cwd()) {
382
540
  cli: texto(memory.cli, 'orkmind'),
383
541
  tenant: texto(memory.tenant, '') || nomeProjeto,
384
542
  timeout_ms: numero(memory.timeout_ms, 15000),
543
+ ...(embedding ? { embedding } : {}),
385
544
  },
386
545
  // Bloco B5: governanca de custo dos auditores. Ausente no manifesto quer dizer
387
546
  // "nao declarada", nao "sem limite": a rodada avisa que so `eco` e o escopo
@@ -397,12 +556,14 @@ function carregarManifesto(dirInicial = process.cwd()) {
397
556
  compartilhada: booleano(fabrica.compartilhada, false),
398
557
  remoto: texto(fabrica.remoto, 'origin'),
399
558
  },
559
+ // RM-031 KG5 (D2): expor as tools do grafo no MCP e opt-in do dono.
560
+ grafo: lerGrafo(dados.grafo, avisos),
400
561
  policies: Object.fromEntries(Object.entries(mapa(dados.policies)).map(([k, v]) => [k, texto(v, '')])),
401
562
  };
402
563
  return { caminho: achado.caminho, raiz, legado: achado.legado, bytes, manifesto, erros, avisos };
403
564
  }
404
565
  /** Carrega o manifesto ou encerra com mensagem acionavel (uso nos subcomandos). */
405
- function exigirManifesto(dirInicial = process.cwd()) {
566
+ function exigirManifesto(dirInicial = diretorioDoProjeto()) {
406
567
  const carregado = carregarManifesto(dirInicial);
407
568
  if (!carregado) {
408
569
  throw new Error(`manifesto ${exports.NOME_MANIFESTO} nao encontrado a partir de ${dirInicial}. Rode: ork init`);
@@ -24,7 +24,7 @@ function listarBatch(raiz, todas = false) {
24
24
  return (0, master_1.pendentesDeScore)(raiz, todas).map(p => ({ thread: p.thread.id, nome: p.thread.nome, entregue: p.entregou,
25
25
  proposta: p.thread.score_proposto ?? null, assinatura: p.thread.score_proposto ? assinaturaProposta(p.thread.score_proposto) : null }));
26
26
  }
27
- function ratificarBatch(raiz, selecao, por) {
27
+ function ratificarBatch(raiz, selecao, por, prova) {
28
28
  (0, master_1.exigirAutoriaHumana)(por);
29
29
  if (!selecao.length || new Set(selecao.map(s => s.thread)).size !== selecao.length)
30
30
  throw new Error('selecione threads distintas explicitamente');
@@ -34,7 +34,8 @@ function ratificarBatch(raiz, selecao, por) {
34
34
  if (!item?.proposta || item.assinatura !== s.assinatura)
35
35
  throw new Error(`proposta ausente ou alterada: ${s.thread}; revise o batch`);
36
36
  const p = item.proposta;
37
- const opcoes = { score: p.valor, justificativa: p.justificativa, classes: s.classe ? [s.classe] : p.classes, resumo: p.resumo, por };
37
+ const opcoes = { score: p.valor, justificativa: p.justificativa, classes: s.classe ? [s.classe] : p.classes, resumo: p.resumo, por,
38
+ ...(prova ? { prova } : {}) };
38
39
  (0, master_1.preflightMaster)(raiz, s.thread, opcoes);
39
40
  return { id: s.thread, opcoes };
40
41
  });
@@ -100,14 +100,14 @@ function lerDigest(raiz, semana) {
100
100
  throw new Error('digest inválido');
101
101
  return job;
102
102
  }
103
- function responderLoteDigest(raiz, resposta, por) {
103
+ function responderLoteDigest(raiz, resposta, por, prova) {
104
104
  const partes = resposta.trim().split(/\s+/);
105
105
  if (partes.length !== 3 || partes[0] !== 'ratificar-lote')
106
106
  throw new Error('resposta de lote inválida');
107
107
  const job = lerDigest(raiz, partes[1]);
108
108
  if (!job.concluidoEm || partes[2] !== job.assinatura)
109
109
  throw new Error('lote não entregue ou assinatura divergente');
110
- return (0, master_batch_1.ratificarBatch)(raiz, job.selecao, por);
110
+ return (0, master_batch_1.ratificarBatch)(raiz, job.selecao, por, prova);
111
111
  }
112
112
  function enviarDigest(opcoes) {
113
113
  const quando = opcoes.quando ?? new Date().toISOString(), semana = sextaLocal(quando);
@@ -0,0 +1,203 @@
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.ERRO_PROVA_DE_CANAL = exports.CONTRATO_PEDIDO_DE_NOTA = void 0;
37
+ exports.exigirNotaSemHost = exigirNotaSemHost;
38
+ exports.arquivoDasNotas = arquivoDasNotas;
39
+ exports.pedirNota = pedirNota;
40
+ exports.pedidoDeNotaDoCodigo = pedidoDeNotaDoCodigo;
41
+ exports.textoDoPedidoDeNota = textoDoPedidoDeNota;
42
+ exports.lerNota = lerNota;
43
+ exports.reciboDoCanal = reciboDoCanal;
44
+ exports.mensagemJaUsadaEmNota = mensagemJaUsadaEmNota;
45
+ /**
46
+ * RM-048 (item 8, D8): a nota do MASTER com prova de origem humana.
47
+ *
48
+ * Ate aqui toda nota gravava o nome que viesse em `--por`: a `ork_master` do OpenClaw repassava o
49
+ * parametro `quem` do modelo, e `master ratificar`, `batch --aceitar` e `digest responder` idem.
50
+ * `autoriaHumana` so recusa nome de agente; ela mesma diz que nao e autenticacao. O dono pediu que
51
+ * a nota dada por ferramenta de host passe pela MESMA prova de canal que o gate ja exige.
52
+ *
53
+ * O caminho com prova e este, e ele nao cria autenticacao nova:
54
+ *
55
+ * 1. `ork master pedir <thread>` guarda um pedido de nota com codigo curto e devolve a linha
56
+ * "<codigo> <0 a 5> <porque>" para o canal mostrar ao dono;
57
+ * 2. o dono responde no Telegram; o ingresso (Hermes ou OpenClaw) assina o texto dele no
58
+ * endereco do pulse, com a chave do proprio canal, e o nucleo confere com a mesma
59
+ * `autenticarResposta` de sempre, ANTES de ler o conteudo;
60
+ * 3. a nota e gravada com `por` = o remetente autenticado (`telegram:<id>`) e com o recibo:
61
+ * canal, conta, mensagem, instante, o sha da prova e o envelope assinado guardado para a
62
+ * auditoria reconferir o HMAC depois.
63
+ *
64
+ * O teclado do digest semanal ("ratificar <thread> <assinatura> <classe>") passa pelo mesmo
65
+ * endereco. E o processo de host (Claude Code, Codex, Hermes, OpenClaw, sessao despachada) que
66
+ * tenta gravar nota com `--por` recebe `master.prova-de-canal`.
67
+ *
68
+ * LIMITE DITO: o canal do processo e declarado pelo ambiente (`canalDoProcesso`). Ele serve para
69
+ * RECUSAR, nunca para autorizar, e um agente que apague as variaveis do proprio host de proposito
70
+ * passa como terminal. Essa e a mesma fronteira do `humano-no-cli` do SHIP; fecha-la de vez exige o
71
+ * dialogo nativo do host para a nota, que fica fora desta entrega.
72
+ */
73
+ const fs = __importStar(require("node:fs"));
74
+ const path = __importStar(require("node:path"));
75
+ const node_crypto_1 = require("node:crypto");
76
+ const estado_thread_1 = require("./estado-thread");
77
+ const thread_1 = require("./thread");
78
+ const ledger_1 = require("./ledger");
79
+ const thread_close_1 = require("./thread-close");
80
+ const conducao_1 = require("./conducao");
81
+ const pulse_consentimento_1 = require("./pulse-consentimento");
82
+ const monitor_lock_1 = require("./monitor-lock");
83
+ exports.CONTRATO_PEDIDO_DE_NOTA = 'ork.master-nota/v1';
84
+ exports.ERRO_PROVA_DE_CANAL = 'master.prova-de-canal';
85
+ const sha = (s) => (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
86
+ /**
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.
89
+ */
90
+ function exigirNotaSemHost(ambiente = process.env) {
91
+ let canal;
92
+ try {
93
+ canal = (0, conducao_1.canalDoProcesso)(undefined, ambiente);
94
+ }
95
+ catch {
96
+ canal = 'mcp';
97
+ }
98
+ // B2 do CHECK: `ORK_CANAL=cli` NAO absolve. A sessao despachada herda `ORK_CANAL=cli` quando a
99
+ // 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
+ : ambiente.ORK_DISPATCH_ID ? 'sessão despachada (ORK_DISPATCH_ID)'
103
+ : ambiente.CLAUDECODE === '1' ? 'claude-code (CLAUDECODE)'
104
+ : ambiente.CODEX_SANDBOX || ambiente.CODEX_SANDBOX_NETWORK_DISABLED ? 'codex (CODEX_SANDBOX)'
105
+ : (ambiente.HERMES_HOME ?? '').trim() ? 'hermes (HERMES_HOME)' : undefined;
106
+ if (marca) {
107
+ throw new Error(`${exports.ERRO_PROVA_DE_CANAL}: nota em nome de pessoa vinda de ${marca} exige a prova de canal do ingresso. ` +
108
+ '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.');
109
+ }
110
+ }
111
+ function arquivoDasNotas(raiz, estadoDir) {
112
+ return path.join(estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor'), 'master-notas.json');
113
+ }
114
+ function lerNotas(raiz, estadoDir) {
115
+ const arquivo = arquivoDasNotas(raiz, estadoDir);
116
+ if (!fs.existsSync(arquivo))
117
+ return {};
118
+ const v = JSON.parse(fs.readFileSync(arquivo, 'utf8'));
119
+ if (v.contrato !== exports.CONTRATO_PEDIDO_DE_NOTA || !v.notas || typeof v.notas !== 'object')
120
+ throw new Error('pedidos de nota: estado inválido; exige inspeção');
121
+ return v.notas;
122
+ }
123
+ function gravarNotas(raiz, notas, estadoDir) {
124
+ const arquivo = arquivoDasNotas(raiz, estadoDir);
125
+ fs.mkdirSync(path.dirname(arquivo), { recursive: true });
126
+ const tmp = `${arquivo}.${process.pid}.tmp`;
127
+ fs.writeFileSync(tmp, JSON.stringify({ contrato: exports.CONTRATO_PEDIDO_DE_NOTA, notas }) + '\n', { mode: 0o600 });
128
+ fs.renameSync(tmp, arquivo);
129
+ }
130
+ /**
131
+ * Pede a nota ao dono. Idempotente: a mesma entrega sem nota devolve o MESMO codigo, para a
132
+ * linha que o dono recebeu nao vencer. So entrega provada recebe pedido de nota.
133
+ */
134
+ function pedirNota(raiz, thread, opcoes = {}) {
135
+ const quando = opcoes.quando ?? new Date().toISOString();
136
+ (0, thread_close_1.exigirEntrega)(raiz, thread);
137
+ const estadoDir = opcoes.estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor');
138
+ // A4 do CHECK: o registro dos pedidos de nota tem dois escritores (pedir e o receptor do pulse);
139
+ // um de cada vez, pelo mesmo lock da conversa.
140
+ return (0, monitor_lock_1.comLockDaConversa)(estadoDir, () => {
141
+ const notas = lerNotas(raiz, estadoDir);
142
+ const existente = Object.values(notas).find(n => n.thread === thread);
143
+ if (existente && !notaDada(raiz, existente))
144
+ return existente;
145
+ // A1 do CHECK: o codigo da nota nao repete o de gate aberto nem o do resumo (quem chama passa).
146
+ const codigo = (0, pulse_consentimento_1.gerarCodigo)([...Object.keys(notas), ...(opcoes.codigosEmUso ?? [])]);
147
+ const pedido = { contrato: exports.CONTRATO_PEDIDO_DE_NOTA, pedidoId: (0, node_crypto_1.randomUUID)(), thread, codigo, criadoEm: quando };
148
+ (0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, thread), thread, ledger_1.TIPOS_DE_EVENTO.notaPedida, { fase: 'MASTER', pedidoId: pedido.pedidoId, codigo });
149
+ gravarNotas(raiz, { ...Object.fromEntries(Object.entries(notas).filter(([, n]) => n.thread !== thread)), [codigo]: pedido }, estadoDir);
150
+ return pedido;
151
+ });
152
+ }
153
+ /** A nota deste pedido ja foi dada pelo canal. */
154
+ function notaDada(raiz, p) {
155
+ return (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, p.thread)).some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.masterConcluido &&
156
+ e.notaPedida?.pedidoId === p.pedidoId);
157
+ }
158
+ /** O pedido de nota que usa este codigo, quando ha um. */
159
+ function pedidoDeNotaDoCodigo(raiz, codigo, estadoDir) {
160
+ try {
161
+ return lerNotas(raiz, estadoDir)[codigo.trim().toUpperCase()];
162
+ }
163
+ catch {
164
+ return undefined;
165
+ }
166
+ }
167
+ /** A linha que o canal mostra ao dono. Uma linha de contexto e uma de resposta. */
168
+ function textoDoPedidoDeNota(raiz, p, canal) {
169
+ const t = (0, thread_1.lerThread)(raiz, p.thread);
170
+ const tg = canal === 'telegram';
171
+ return [
172
+ `${tg ? '🧾 ' : ''}Nota da entrega ${p.thread} (${t.nome.slice(0, 80)})`,
173
+ `${tg ? '• ' : '- '}Sem nota, vale a aceita por omissão, com o índice derivado do ledger ao lado.`,
174
+ `${tg ? '↩️ ' : ''}Responda: ${p.codigo} <0 a 5> <porquê>, por exemplo: ${p.codigo} 4 entregou o que pedi.`,
175
+ ].join('\n');
176
+ }
177
+ /** "4 entregou o que pedi" vira nota e justificativa; o resto e devolvido como pergunta. */
178
+ function lerNota(resto) {
179
+ const m = /^\s*([0-5])(?:\s*\/\s*5)?\s*[-:,.]?\s+(\S[\s\S]{2,})$/.exec(resto);
180
+ return m ? { score: Number(m[1]), justificativa: m[2].trim() } : undefined;
181
+ }
182
+ /**
183
+ * O recibo que acompanha a nota no `master_done`. O envelope assinado inteiro fica guardado, para
184
+ * que a auditoria reconfira o HMAC do canal depois (`autenticarResposta` no instante recebido). Um
185
+ * arquivo por mensagem: a mesma mensagem nunca vira duas notas.
186
+ */
187
+ function reciboDoCanal(raiz, envelope, vinculo, estadoDir) {
188
+ const dir = path.join(estadoDir ?? path.join((0, estado_thread_1.raizDoEstado)(raiz), '.orkastery', 'monitor'), 'master-nota');
189
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
190
+ const arquivo = path.join(dir, `${sha(envelope.mensagem)}.json`);
191
+ const corpo = JSON.stringify({ contrato: exports.CONTRATO_PEDIDO_DE_NOTA, vinculo, envelope }) + '\n';
192
+ if (!fs.existsSync(arquivo))
193
+ fs.writeFileSync(arquivo, corpo, { mode: 0o600, flag: 'wx' });
194
+ return {
195
+ origem: envelope.origem, canal: envelope.canal ?? null, conta: envelope.conta ?? null, mensagem: envelope.mensagem,
196
+ recebidoEm: envelope.recebidoEm, recibo: sha(envelope.prova), contratoResposta: envelope.canal === undefined ? 'ork.hitl-answer/v1' : 'ork.hitl-answer/v2',
197
+ evidencia: path.relative((0, estado_thread_1.raizDoEstado)(raiz), arquivo), evidenciaSha256: sha(fs.readFileSync(arquivo, 'utf8')), ...vinculo,
198
+ };
199
+ }
200
+ /** Mensagem ja usada numa nota desta thread nao vale de novo, igual ao gate. */
201
+ function mensagemJaUsadaEmNota(raiz, thread, mensagem) {
202
+ return (0, ledger_1.lerLedger)((0, thread_1.dirThread)(raiz, thread)).some(e => e.tipo === ledger_1.TIPOS_DE_EVENTO.masterConcluido && e.mensagem === mensagem);
203
+ }
package/dist/master.js CHANGED
@@ -88,6 +88,7 @@ const horario_1 = require("./horario");
88
88
  const indice_1 = require("./indice");
89
89
  const ship_1 = require("./ship");
90
90
  const fabrica_publicar_1 = require("./fabrica-publicar");
91
+ const fechamento_1 = require("./fechamento");
91
92
  /** Identificador do contrato congelado do MASTER log. */
92
93
  exports.CONTRATO_MASTER_LOG = 'ork.master-log/v1';
93
94
  /** Versao do contrato. Uma mudanca de contrato exige uma versao nova, nao um campo novo. */
@@ -392,7 +393,10 @@ function registrarMaster(raiz, id, opcoes) {
392
393
  classesDeFalha: masterLog.classesDeFalha, regime: postmortem.score.regime,
393
394
  contrato: exports.CONTRATO_MASTER_LOG, arquivo: path.relative((0, estado_thread_1.raizDoEstado)(raiz), r.caminhoMasterLog), estado: 'thread fechada',
394
395
  masterLogSha256: hashDocumento(r.caminhoMasterLog), postmortemSha256: hashDocumento(r.caminhoPostmortem),
396
+ ...(opcoes.prova ? { prova: 'ingresso-autenticado', ...opcoes.prova } : {}),
395
397
  });
398
+ // RM-037 (rm037noite): a thread fechada solta o que segurava, com registro no ledger dela.
399
+ (0, fechamento_1.liberarAoFechar)(raiz, id);
396
400
  return r;
397
401
  }
398
402
  exports.CONTRATO_MASTER_PENDENTE = 'ork.master-pending/v1';
@@ -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 });